New to Kestra?
Use blueprints to kickstart your first workflows.
Find MongoDB documents that fail their collection $jsonSchema validator, backfill a missing required field behind an approval, then prove it landed.
A $jsonSchema validator added to a collection that already holds data applies to new writes
only. MongoDB never goes back over what is already there. So every document written before the
validator keeps breaking the rule nobody checks: a required field is missing or a field holds
the wrong BSON type. Readers that trust the schema break on those documents. The day somebody
runs a migration that touches one, the write fails with error 121 even though the migration had
nothing to do with the broken field.
This blueprint reads the collection with $nor on the schema, which is exactly the set of
documents the validator would reject today. Then it splits that set in two. A document whose
only problem is the one missing required field can be repaired from a default. Everything else
is reported with its BSON value type and its missing required keys, then left alone. Behind a
safe mode gate and an approval it backfills the repairable documents with $set, in batches of
document ids, reads the collection again and fails the run if any repairable violator is still
there.
This blueprint was created by zkasuran.
auto_remediate true, dry_run false, then a human answering the Pause. Three conditions,
all of them, before a document is touched.required_field the schema does not require is
refused. A default_value whose JSON type contradicts the bsonType the schema declares for
that field is refused. A repair that writes a number into a string field only trades one
broken rule for another.batch_size,
one batch at a time. A document that changed since the detection ran no longer matches the
filter, so it is skipped rather than written over. Drift created while the run was paused is
never swept in silently. It shows up in the re-read instead.check_inputs (io.kestra.plugin.core.flow.If) refuses a required_field that is not in
the schema required list and a default_value whose JSON type contradicts the declared
bsonType. It fails the run before the first read.detect (io.kestra.plugin.mongodb.Aggregate) matches {"$nor": [{"$jsonSchema": schema}]},
then counts in one pass: every violator, the ones repairable by backfilling the field, the
capped list of their ids, plus a sample of the refused documents carrying the $type of the
field and the required keys they are missing. Repairability is decided by testing the
document against the same schema with the target field dropped from required, so a
document that is broken in a second way is never claimed as repairable.report (io.kestra.plugin.core.log.Log) logs the counts. Task outputs are not in the
execution JSON, so the numbers the gates act on are written to the log.route (io.kestra.plugin.core.flow.If): zero violators logs clean and stops. One or more
sends alert to Slack and prints planned, which states what a repair run would do on every
run including a dry one.remediate_gate (io.kestra.plugin.core.flow.If) opens only with auto_remediate true and
dry_run false. hold (io.kestra.plugin.core.flow.Pause) waits for an answer.remediate (io.kestra.plugin.core.flow.Loop, concurrencyLimit: 1) walks the
id batches and backfill (io.kestra.plugin.mongodb.Update, UPDATE_MANY) applies the
$set. With nothing repairable the run logs nothing_to_repair and writes nothing.repaired_check (io.kestra.plugin.mongodb.Aggregate) measures the targeted ids, reverify
(io.kestra.plugin.mongodb.Find) runs the detection predicate across the collection again
and verify_gate fails the run with io.kestra.plugin.core.execution.Fail if anything
repairable remains. Without approval declined records the decision.errors posts to Slack when any task fails, for example when MongoDB is unreachable or the
validator rejects the repair itself.MongoDB enforces a validator at write time and offers nothing to audit what is already stored.
There is no command that lists the documents a validator would reject, so the usual answer is a
one-off script somebody runs by hand once. This flow makes it a scheduled, reviewable job. The
detection is one server-side pipeline, the repair sits behind auto_remediate, dry_run and a
Pause with the decision recorded on the execution, the batches are serialised by
concurrencyLimit: 1 and the re-read turns the write into a checked outcome. The Slack alert
and the error path are the same ones the rest of your flows use.
On Kestra 2.0.5 OSS against a MongoDB 7.0 container, on the final file. appdata.orders held 13
documents written before the validator was added with collMod: 6 missing the required status
field, 4 fully compliant, 2 holding status as an int and a bool, 1 missing both required
fields. The validator requires order_id and status as strings. Every before and after number
was read back with mongosh, not only from the Kestra log.
| Run | Inputs | Result |
|---|---|---|
| Clean | a collection with 3 compliant documents | 0 violators. clean logged, no Slack alert, SUCCESS |
| Detect | defaults | 9 violators, 6 repairable, 3 refused with types int, bool, missing keys. Slack alert, SUCCESS |
| Dry run | defaults | Reports the repair it would make. mongosh still counts 7 documents with no status |
| Armed but dry | auto_remediate: true, dry_run: true |
Gate stayed closed, no Pause, nothing written |
| Declined | auto_remediate: true, dry_run: false, resumed approved: false |
Paused, then declined. Nothing written |
| Repair | the same, resumed approved: true |
6 backfilled. mongosh: violators 9 to 3, compliant 4 to 10, the 4 compliant documents byte for byte unchanged |
| Nothing repairable | run again on the repaired collection | 3 violators, 0 repairable, nothing_to_repair, no write |
| Re-verify fails | validator set to validationAction: warn, a writer unset status on a compliant document during the Pause |
The 6 targeted documents were repaired, the collection wide re-read found 1 left, verify_gate FAILED the run and errors alerted |
| Bad default | default_value: 0 against a string field |
Refused before any read |
| Wrong field | required_field: total, which the schema does not require |
Refused before any read |
| Unreachable | connection_uri pointed at a dead host |
detect failed, errors posted to Slack, execution FAILED |
Four ways, all measured. The clean collection returned 0 while the drifted one returned 9 with
nothing else changed. The refused class is not a rounding error: 3 documents the flow could have
quietly coerced were counted, named by BSON type and left alone, which mongosh confirms after
the repair run. The repair itself is checked twice, by id and collection wide. And the gate was
forced to fire by unsetting a required field on a compliant document while the run was paused,
which the id scoped write correctly ignored, so only the re-read caught it and the run failed.
The real world bite was reproduced too. With a violator present, updateOne setting an
unrelated field on it was rejected with error 121 by the validator, because the result still
failed the schema. After the repair run the identical update succeeded.
$jsonSchema validator. Read the schema with
db.getCollectionInfos({name: 'orders'})[0].options.validator and pass the $jsonSchema
object as the json_schema input.docker run -d --name mdb --network mynet mongo:7.0, insert documents, add the
validator with collMod afterwards, then run the flow with connection_uri: mongodb://mdb:27017.MONGODB_URI: the MongoDB connection string. Read when connection_uri is left empty, which
is how the flow should run in production. In Kestra OSS, provide it as the base64-encoded
environment variable SECRET_MONGODB_URI.SLACK_WEBHOOK_URL: used by alert and the errors block. Provide it as
SECRET_SLACK_WEBHOOK_URL.| Input | Default | Purpose |
|---|---|---|
connection_uri |
empty | Plain connection string for a local test. Empty reads MONGODB_URI |
database, collection |
appdata, orders |
The collection to audit |
json_schema |
the worked example | The $jsonSchema the collection is validated against |
required_field |
status |
The one missing required field this run backfills |
default_value |
"unknown" |
JSON literal written into that field. Quote a string value |
repair_limit |
1000 |
Ceiling on documents repaired per run |
batch_size |
250 |
Document ids per write batch |
sample_limit |
25 |
Refused documents named per report, also the re-check read cap |
approval_timeout |
PT2H |
How long the pause waits for an answer |
auto_remediate |
false |
Allow the backfill |
dry_run |
true |
Must be false for a write to apply |
MONGODB_URI and SLACK_WEBHOOK_URL secrets.$jsonSchema into json_schema, set database and collection, then
run the flow. It reports the drift and stops.required_field with its default_value, then auto_remediate: true and
dry_run: false, run it again and answer the pause. The backfill runs and the re-read proves
the repairable drift is gone.daily schedule, which always runs in safe mode.io.kestra.plugin.core.flow.Loop over a
list of collection and schema pairs, with concurrencyLimit: 1 on the part that writes.runCommand style task instead of passing
it as an input, once your plugin build ships one, so the audit can never drift from the live
validator.$set document. Apply them in
one write, because under validationLevel: strict an intermediate document that is still
invalid is rejected.validationLevel: strict with
validationAction: error so the drift cannot come back.$jsonSchema query operator: https://www.mongodb.com/docs/manual/reference/operator/query/jsonSchema/