Introduction
FIELDNOTE has agreed which inherited count strings mean reliable numbers. It now needs a usable catalog that keeps those meanings and refuses uncertain future writes. A validator alone does not clean existing records, and converting every value can destroy useful distinctions or invent counts.
You will quarantine uncertain originals, convert only approved scalar strings, and add a real MongoDB validator. You will also create and update an ordinary valid kit so the shop can keep working. Complete Classify and Quarantine Equipment Records first: its document policy, original-preservation script, terminal/file syntax and BSON distinctions are used here. This unit starts independently with a fresh mixed catalog and copies of the two completed source files. No files, data or running processes from the earlier environment are required. Use Terminal throughout; MongoDB and the shell are prepared. The duration is an author estimate.
Create repair.js and enforce.js in /home/labex/project/catalog-desk. You may inspect and retain the supplied policy.js and quarantine.js; equivalent policy/migration implementations must honor the same contract. Preserve all original IDs, unrelated fields, original int/long types, complete uncertain originals in quarantine, collection identities and supplied tools. Only approved strings in stock.available may become integer counts. Add KIT-901 as specified and decrement it once; the refused KIT-902 must not exist. Do not drop/rebuild collections, replace unknown counts by zero, overwrite archive originals or tighten away legitimate extra fields. Work is sequential without concurrent writers and does not provide a transaction across the whole migration.
Convert Only Approved String Counts
In this step, you will remove uncertain records through the preserved-original workflow and convert only the agreed string representations. Begin in Terminal. Enter this unit's prepared workspace:
cd /home/labex/project/catalog-desk
Read a normal record and inspect the fresh catalog types:
./catalog show KIT-101
./catalog types
The initial data is mixed again: KIT-101 has Int32 12, KIT-102 has Long zero, KIT-103/KIT-104 have approved strings, and KIT-105 through KIT-111 need review. This is an independent unit, so the earlier unit's successful migration has not been copied into the database.
cat without redirection prints a saved file rather than changing it. Inspect the supplied policy and quarantine script from the earlier guided unit:
cat policy.js
cat quarantine.js
The valid selector accepts nonempty string IDs/names and scalar BSON int/long counts from 0 through 50. repairable accepts the same shape with only canonical decimal strings in that range. bad selects neither. The quarantine script preserves BSON types, retains complete original documents and verifies an existing copy before deleting its corresponding active source. It is safe to rerun in this controlled scene, and it refuses a conflicting old copy rather than overwriting it.
Inspect the actual groups, then run those exact saved sources:
./catalog classify policy.js
./catalog run policy.js quarantine.js
Read the remaining active records and the preserved originals:
./catalog show
./catalog archive
Only KIT-101 through KIT-104 should remain active. Their counts are still int, long, string and string, respectively. KIT-105 through KIT-111 belong to quarantine with full originals, including missing/null/array/Double distinctions and the extra supplier-review fields. No count has been guessed or converted yet.
Now save an update pipeline. An ordinary update document uses update operators; the array below instead contains aggregation stages that calculate each changed field from that record's current value. $set in this pipeline sets the named field, and $toInt: "$stock.available" reads the current nested count and converts the approved string to BSON Int32. The dollar-prefixed string is a field reference, not literal text to store. The dotted destination "stock.available" updates only that nested field, keeping stock.rack and every other original field.
updateMany acts only on records matched by repairable. Existing valid int/long counts do not match that string-only selector and retain their original types. Without a correct filter, a conversion can stop on an unconvertible value after earlier records have changed; the entire multi-record update is not atomic. Using onError: 0 or onNull: 0 would invent a count and violate this task. This prepared sequential scene has no concurrent writers.
The REPAIR here-document writes the complete file literally, as in the earlier unit:
cat > repair.js <<'REPAIR'
printjson(db.equipment.updateMany(
repairable,
[{$set: {"stock.available": {$toInt: "$stock.available"}}}]
));
REPAIR
Execute the policy first so its selector names are in scope, followed by your saved update:
./catalog run policy.js repair.js
Look for matchedCount: 2 and modifiedCount: 2. Check actual documents and types, because a modified count alone cannot establish preservation:
./catalog show
./catalog types
KIT-103 now has Int32 12 and KIT-104 has Int32 zero. KIT-102 must still have its original long zero. All original names, racks, supplier fields and IDs remain. Inspect the remaining classification:
./catalog classify policy.js
All four active records must be in valid; both other groups should be empty. Notice why the quarantine was necessary before this audit: conversion alone would not remove null, missing or uncertain values.
Replay the approved conversion:
./catalog run policy.js repair.js
Both counts should now be zero because no approved strings remain. This is idempotent conversion; it does not mean every possible database error would automatically roll back earlier records. If the file has a syntax error, rewrite the complete here-document and rerun it. If a filename is wrong, use ls and correct it; keep the existing database rather than resetting it.
Enforce Future Writes and Keep Ordinary Work Usable
In this step, you will apply the accepted document contract to future native MongoDB writes and prove that the shop can still create and update a valid kit. Begin in Terminal, in /home/labex/project/catalog-desk.
MongoDB's collMod command changes an existing collection's options. Here it attaches the same valid selector as a validator. validationLevel: "strict" validates inserts and updates, including an update to a record that was already invalid. validationAction: "error" rejects a violating write. moderate can exempt updates to already invalid records; warn can allow a violating write while logging it. Neither is the strict future-write contract required here.
Attaching a validator is not retroactive cleaning: collMod can succeed while invalid existing records remain. That is why you first quarantined uncertain originals, converted only approved strings and audited the active catalog. A successful command response is evidence that the option changed; a real accepted/refused write is evidence of what it does.
Save the native command. db.runCommand executes it on the current course database, and printjson displays its actual response:
cat > enforce.js <<'ENFORCE'
printjson(db.runCommand({
collMod: "equipment",
validator: valid,
validationLevel: "strict",
validationAction: "error"
}));
ENFORCE
Run the policy and this new file unchanged:
./catalog run policy.js enforce.js
Expect ok: 1. Read the real collection options:
./catalog status
The output should include the equipment validator with validationLevel: "strict" and validationAction: "error". This reader also reports actual server/shell versions and the selected database; it does not generate a successful validation result for you.
Try one uncertain future insert with the ordinary writer connection. invalid-text submits a real kit whose count is "12 kits". Expect a nonzero exit and Document failed validation, not a connection error:
./catalog invalid-text KIT-902
Confirm that no partial new record was retained:
./catalog show KIT-902
The result should be []. The rejected write did not turn the unknown text into zero and did not create a replacement quarantine record. Future invalid writes are refused; the quarantine records document the earlier inherited data.
Now create a legitimate new kit. new ID COUNT submits a document with an Int32 count, a rack and supplier fields. Use the agreed new ID KIT-901 and an initial count of 4:
./catalog new KIT-901 4
Look for an acknowledged insertion of KIT-901. Its extra rack/supplier fields must be allowed. use ID COUNT performs a real conditional update: it decrements the available count only when that record has enough units. Use one unit:
./catalog use KIT-901 1
Expect matchedCount: 1 and modifiedCount: 1. Read the actual result:
./catalog show KIT-901
KIT-901 should retain its name, rack NEW and supplier LOCAL, with Int32 3 available. The original four cleaned records and all quarantine originals must remain unchanged. A strict integer contract still permits known zero, the inclusive upper bound, existing long counts, unrelated fields and ordinary valid updates.
Do not replay the new command after it succeeds: the unique _id would produce a duplicate-key error. Do not replay use unless you intend another decrement. The saved repair script has an idempotent string-only predicate; an ordinary stock-use action intentionally changes business state each time. If you accidentally decrement KIT-901 again, the course task allows restoring only this new kit to the required final count. Use a small saved native update, with the same here-document syntax, and then inspect it. Int32(3) explicitly constructs a BSON 32-bit integer with value 3:
cat > restore-new-kit.js <<'RESTORE'
printjson(db.equipment.updateOne({_id: "KIT-901"}, {$set: {"stock.available": Int32(3)}}));
RESTORE
./catalog run restore-new-kit.js
./catalog show KIT-901
This recovery targets only your new kit and should leave the required final count at 3. It is not a recovery method for overwriting inherited or quarantined originals. Your reusable policy, quarantine, repair and enforcement files now express the complete controlled migration, while the live catalog demonstrates that normal valid work remains possible.
Summary
You converted approved string counts without replacing other nested fields or changing already valid numeric types. You audited the cleaned catalog, enforced its contract with strict/error validation, observed a real invalid-write refusal and completed a normal valid insert/update. The saved scripts and preserved originals support a controlled repeatable maintenance workflow; they do not claim atomic migration of the entire catalog or safety against concurrent writers.



