Storage reorganisation · validation

Validation

The plan measured against the live databases and the three repositories, step by step. Every figure below was read back on 2026-09-08 — nothing is carried over from the design.

Values verified
1,488,632Across 32 columns and JSON key paths
Complete paths
1,488,59499.997% of the set
Bare names left
38All pre-existing, all in one column
Steps complete
2 of 6Two built but not run, two unbuilt
Gates passed
0 of 3Gate 1 substantially met
The headline

The data half of the plan holds up. Every column and JSON key the design lists now stores a complete path, the conversion preserved list order, and the only bare names left predate the work and are present identically in production. Steps 1 and 2 are real.

The object half has not started. The copier is built but has never been run, and the two remaining steps are unbuilt. Nothing has moved in the bucket — which is exactly where the plan says Gate 1 should leave things.

Step by step

StepStateEvidence
Step 1 — store the full file pathcomplete9 columns retyped, 17 backfilled, 32 JSON key paths rewritten. 1,488,632 values re-counted; 38 bare, all pre-existing.
Step 2 — one rule finds every filepartialupload-targets.ts is the single rule in the API; the web app mirrors it. The mobile leg carries none of this work.
Gate 1partialZero files moved and the rule is in place. Open: the mobile leg, and evidence the step-1 reversal has been run once.
Step 3 — thumbnails learn both shapesdeployed · runningDeployed as a Cloud Function on the finalize event; 78 of 78 tests pass. Also mirrors new-layout images back to their legacy folder.
Step 4 — new uploads change shapepartialStaging works as designed. The flag does not exist — finalPrefixFor is unconditional, so the behaviour is already live.
Gate 2not startedThe thumbnail and reclaim halves are both live. Open: the phone half, which needs the mobile leg of step 2.
Step 5 — copy the existing filesbuilt · not runCAP-680 built seed, run, write-back and rollback. Both ledger tables hold 0 rows.
Gate 3not startedNot performed. No account copied, no rollback exercised on live data.
Step 6 — keep and watch the old foldersnot startedNo legacy-read logging exists. Nothing writes to the legacy folders any more, which is the first half by construction.

Read back out of the database

Every value in the columns and keys below, counted by whether it contains a slash. clone1 carries every migration; capClone is the production comparison.

Column or keyValuesComplete pathBare
inventory_sticker_details.sticker_name757,840757,8400
camp_inventory_items.images678,643678,60538
biomeds.extracted_data → biomedFile20,05220,0520
copilot_launch_proposals.quote9,0449,0440
equipment_models.images8,2808,2800
equipment_models.files5,0885,0880
copilot_launch_proposals.additional_details → parentQuote3,4353,4350
users.agreement_details → name1,0861,0860
transactions.additional_documents → sellerInvoice9629620
transactions.additional_documents → buyerInvoice9619610
transactions.additional_documents → poDocument7557550
copilot_requests.attachments6846840
copilot_launch_proposals.warranty_document6816810
transactions.additional_documents → poAttachments[].attachment4834830
copilot_requests.quote1151150
equipment_categories.default_image1051050
shipping_estimates.shipping_responses → [].fileName92920
copilot_launch_proposals.service_agreement75750
docusign_requests.attachments53530
po_requests.additional_details → comparisonView.fileName36360
transactions.additional_documents → shippingQuote35350
users.profile29290
transactions.additional_documents → cerDocument22220
warranties.file18180
transaction_equipment_details.equipment_details → userManualPdf15150
transactions.additional_documents → billOfSale12120
shipping_estimates.bol_document10100
bill_of_sale_requests.file_name880
transactions.additional_documents → bolDocument770
transaction_equipment_details.equipment_details → warrantyFile330
feedbacks.attachment220
copilot_launch_proposals.equipment_condition_attachment110
Total1,488,6321,488,59438
The 38 bare names are not a regression

All 38 are array entries in camp_inventory_items.images, which is derived from inventory_sticker_details.sticker_name — and that column is fully converted, 757,840 of 757,840. So those entries point at stickers that no longer exist. The same 38 are present in capClone, which puts them older than this work. Cleanup, not a blocker.

Both path shapes are live at once

An old file keeps its legacy flat path. A new save writes the new layout. A value with a slash is used as it stands either way, which is what lets a half-migrated table read correctly throughout. The split in clone1 today:

Layoutclone1capClone
New tree — img/… or doc/…260
Legacy flat folder, complete path1,488,5681,488,562
Bare name3838
Total1,488,6321,488,600
What that 26 tells you

Those 26 values are new uploads that landed in the account layout, and they are the evidence that step 4's behaviour is already live — the plan's flag would have kept them at zero until step 3 was deployed. capClone has none, so the new layout exists only in clone1.

capClone is not a clean "before" picture either: it already carries the path conversion, so every value in it is a complete path too. It differs only in that all of them are still in the legacy folders.

Migration drift between the clones

Fifteen migrations from 2026-09-01 onward are applied in clone1. Four of them are not in capClone.

Migrationclone1capClone
20260901134627 — create presigned_url_linksappliedapplied
20260902101440 — PO paths on transactionsappliednot applied
20260902101441 … 101448 — widen + backfillappliedapplied
20260904102729 — invoice pathsappliedapplied
20260905084518 — recreate the ledger with sweep columnsappliedapplied
20260905110855 — file_migration queue and batch tablesappliednot applied
20260905110946 — the file_migration_enabled switchappliednot applied
20260908064726 — ledger refs to ref_data jsonbappliednot applied
This blocks a production run

The copier cannot run against capClone as it stands: the two tables it works through and the switch that starts it are not there. Its own PO-path migration is missing too, so transactions.poDocument in production has not had the offline purchase-order prefix applied that clone1 has.

Where the build departs from the plan

The plan saysWhat is built
Step 4 flips one settingfinalPrefixFor is unconditional; no setting gates the upload shapedeviation
Step 3 is deployed before step 4Step 3 is deployed, so the order holds — but by circumstance, not by the flagorder holds
Step 2 covers the mobile serviceNeither mobile repository carries the storage worknot done
Step 5 copies thumbnail before pictureBuilt — copy_thumb is carried on every queue rowcomplete
Step 5 verifies, then updates the recordBuilt — write-back is a separate pass, gated on the copy having succeededcomplete
Every step reversible on its ownBuilt for steps 1 and 5; steps 2 and 4 reverse by deploy rather than by switchpartial
The gap nothing else records

PATH_COLUMNS — the codebase's own list of every column an upload path can reach — holds 73 entries. The copier reaches 13. Of the 60 it does not, 32 hold legacy paths in clone1 — two of those are excluded by decision, leaving 30 columns across 51,636 rows unaccounted for, including biomeds.extracted_data and camp_inventory_items.additional_details, neither of which the plan mentions. Enumerated in full on CAP-680 Gaps; the ordered punch list of everything outstanding is on Remaining.