CAP-680 · what is not covered
What CAP-680 Does Not Do
CAP-680 built the copier. It has not run it, and it does not reach everything the backfill converted. This page is the measured remainder — each item read out of the code or the database, not inferred from the commit messages.
- Path columns
- 73The authoritative list in path-references.ts
- Covered by CAP-680
- 13Plus qr_codes, which is not a column
- Uncovered, holding paths
- 26Across 55,137 values in clone1
- Excluded by decision
- 2documents.file_path, equipment_models.selected_image
- Queue rows
- 0Never seeded, never run
- Gate 3
- Not runNo account copied or rolled back
The codebase already contains the authoritative list of every column an upload path can reach:
PATH_COLUMNS in shared/modules/storage/path-references.ts, which the temp
sweep depends on being complete. It holds 73 entries.
MIGRATION_DESCRIPTORS reaches 13 of them.
Of the 60 it does not reach, two — documents.file_path and
equipment_models.selected_image — are excluded by decision, and
26 genuinely hold file paths: 55,137 values across 53 JSON key paths and plain
columns. Those rows name a
complete path, so they read correctly today, but no run will copy their objects into the
per-account layout and no run will rewrite them. The largest are
biomeds.extracted_data (20,052 rows) and
camp_inventory_items.additional_details (15,533), and neither table is mentioned
anywhere in the plan.
Because nothing is ever deleted this is not a data-loss risk. It is a completeness risk: the migration would report itself finished while a large minority of references still pointed into the shared legacy folders — precisely the state the plan exists to end.
The columns with no descriptor
Legacy values — a complete path whose first segment is neither img nor doc — in columns the backfill rewrote but the copier does not name. Counted in clone1 on 2026-09-08.
Every uncovered column
Every entry in PATH_COLUMNS that MIGRATION_DESCRIPTORS does not reach and that actually holds a file path — a value that starts with a known BucketFolder, not merely a column whose text mentions one. Each row carries a real value out of clone1 and the destination the upload table would give it; {CAP} is the owning account and {user} a user id, because the destination is per-row.
| Table.column | Values | Key paths that hold a file | An actual file today → where it should go |
|---|---|---|---|
| biomeds.extracted_data | 20,052 | biomedFile 20,052 | inventory/biomed-import/012020260844530001pd_1768919771163.pdfdoc/{CAP}/assets/biomed-import/012020260844530001pd_1768919771163.pdf |
| camp_inventory_items.additional_details | 15,533 | cad.cadUrl 15,319 transferDetail.fileName 214 | document-manager/cad/extraction/A161_Northpoint_ASCA161pd_1776372936871_enhanced.xlsxdoc/{CAP}/cad/A161_Northpoint_ASCA161pd_1776372936871_enhanced.xlsx |
| copilot_launch_proposals.quote | 9,040 | (comma list entry) | ezrfp-quote/00200335PD_1743581224930.PDFdoc/{CAP}/copilot-quote/00200335PD_1743581224930.PDF |
| copilot_launch_proposals.additional_details | 3,733 | parentQuote 3,435 specifications[].fileName 215 poAttachment 83 | ezrfp-quote/1205TG_2436068_SPRING_pd_1pd_1779887679183.pdfdoc/{CAP}/copilot-quote/1205TG_2436068_SPRING_pd_1pd_1779887679183.pdf |
| transaction_equipment_details.equipment_details | 2,361 | inventoryStickerData.inventoryStickerDetail[].stickerName 986 images[] 839 equipmentCategoryData.img 216 inventoryStickerData.inventoryStickerDetail[].sticker 125 images 66 referenceImages 52 inventoryStickerData.inventoryStickerDetail[] 43 userManualPdf 15 serviceContract 5 childInventories[].inventoryStickerData.inventoryStickerDetail[].stickerName 4 warrantyFile 3 tradeInventoryDetail.newImages[] 2 childInventories[].equipmentCategoryData.img 2 tradeInventoryDetail.equipmentCategoryData.img 1 tradeInventoryDetail.images 1 additionalDetails.transferDetail.fileName 1 | inventory/images/00OFGjpe_1781788180450.jpegimg/{CAP}/assets/00OFGjpe_1781788180450.jpeg |
| users.agreement_details | 1,086 | name 1,086 | eula/eula_aaron_hayes1780079378131.pdfdoc/{CAP}/eula/eula_aaron_hayes1780079378131.pdf |
| copilot_requests.proposal_summary | 750 | comparisonViewAttachment 750 | ezrfp/attachments/comparison_view_1761676961963.pdfdoc/{CAP}/copilot/attachments/comparison_view_1761676961963.pdf |
| copilot_requests.attachments | 679 | (array element) | ezrfp/attachments/0001433807_Power_ease_pd_1767797175378.pdfdoc/{CAP}/copilot/attachments/0001433807_Power_ease_pd_1767797175378.pdf |
| copilot_launch_proposals.warranty_document | 678 | (array element) | ezrfp-warranty/12037_2_PHCNA_ULT_Warrantypd_1740585238650.pdfdoc/{CAP}/copilot/warranty/12037_2_PHCNA_ULT_Warrantypd_1740585238650.pdf |
| shipping_estimates.request_details | 289 | items[].images[] 289 | inventory/images/0730Xjpe_1752603773879.jpegimg/{CAP}/assets/0730Xjpe_1752603773879.jpeg |
| po_requests.attachment | 145 | (whole value) | cer-documents/2341727jpe_1726568391940jpe_1728417342272.jpegdoc/{CAP}/cer-documents/2341727jpe_1726568391940jpe_1728417342272.jpeg |
| copilot_parent_requests.action_logs | 114 | equipments[].changes[].new 75 equipments[].changes[].old 36 equipments[].changes[].old[] 3 | ezrfp/attachments/12132024_Aespire_View_adoc_1734721024059.docxdoc/{CAP}/copilot/attachments/12132024_Aespire_View_adoc_1734721024059.docx |
| copilot_requests.quote | 111 | (comma list entry) | ezrfp-quote/1670238683338_PROEE08Y7Fjp_1689604722125.jpgdoc/{CAP}/copilot-quote/1670238683338_PROEE08Y7Fjp_1689604722125.jpg |
| copilot_pre_orders.additional_details | 107 | poAttachments[].attachment 107 | document-manager/purchase-orders/041526Neptune_1776266774975.pdfdoc/{CAP}/document-manager/purchase-orders/041526Neptune_1776266774975.pdf |
| shipping_estimates.shipping_responses | 92 | [].fileName 92 | document-manager/shipping-documents/1719218701621_LZZ7B9GBGZ.pdfdoc/{CAP}/shipping-document/1719218701621_LZZ7B9GBGZ.pdf |
| shipping_estimates.quote_email_details | 80 | attachments[] 80 | document-manager/shipping-documents/1719218701621_LZZ7B9GBGZ.pdfdoc/{CAP}/shipping-document/1719218701621_LZZ7B9GBGZ.pdf |
| copilot_launch_proposals.service_agreement | 72 | (array element) | service-agreement/20200512095946_203860pd_1739564940779.pdfdoc/{CAP}/service-agreement/20200512095946_203860pd_1739564940779.pdf |
| accounts.images_attachment | 54 | assetTag 18 biomedTag 14 logo 11 smallLogo 11 | account-tags/asset-tags/IMG_3633jpe_1763315944357.jpegdoc/{CAP}/account-tags/asset-tags/IMG_3633jpe_1763315944357.jpeg |
| docusign_requests.attachments | 53 | (whole value) | cer-documents/cer_1750418847569MAP6U.pdfdoc/{CAP}/cer-documents/cer_1750418847569MAP6U.pdf |
| po_requests.additional_details | 36 | comparisonView.fileName 36 | ezrfp/attachments/comparison_view_1749043309118.pdfdoc/{CAP}/copilot/attachments/comparison_view_1749043309118.pdf |
| users.profile | 29 | (whole value) | profile/1A4A1364jpe_1772729243492.jpegimg/{CAP}/user/profile/1A4A1364jpe_1772729243492.jpeg |
| denovo_projects.additional_details | 26 | formData.cadDocuments[].filePath 10 denovoTemplate.filePath 9 formData.optionalDocuments[].filePath 3 formData.w9Attachment[].filePath 3 formData.purchasingFormulary[].filePath 1 | denovo-projects/23009_A151A151pd_1777565439300.pdfdoc/{CAP}/denovo-projects/23009_A151A151pd_1777565439300.pdf |
| shipping_estimates.bol_document | 10 | (whole value) | document-manager/shipping-documents/Bill of Lading 1723044241771_1723044241771.pdfdoc/{CAP}/shipping-document/Bill of Lading 1723044241771_1723044241771.pdf |
| copilot_requests.additional_details | 3 | serviceContract.serviceContractFile 3 | inventory/service-contract/Alcon_Constellation_Servicpd_1742494646144.pdfdoc/{CAP}/assets/service-contract/Alcon_Constellation_Servicpd_1742494646144.pdf |
| report_requests.file_path | 3 | (whole value) | reports/1/procurement-requests_96464a92-09db-4c31-8058-27c474d10405.pdfdoc/{CAP}/export/{user}/reports/procurement-requests_96464a92-09db-4c31-8058-27c474d10405.pdf |
| copilot_launch_proposals.equipment_condition_attachment | 1 | (array element) | inventory/images/2jp_1713209028801.jpgimg/{CAP}/assets/2jp_1713209028801.jpg |
| Total | 55,137 | 53 key paths | 26 columns |
An earlier pass asked whether a column's text contained a legacy folder name, and that matched free prose — a service-contract description mentioning a folder counted as a file. Asking instead whether a value starts with one clears service_contracts.extracted_data, chatbot_threads.messages, file_imports.extracted_data and file_import_queues.extracted_data. They are not gaps. The remaining 32 uncovered columns hold nothing at all. Most were registered in PATH_COLUMNS deliberately on the strength of the column's name rather than a traced writer — the sweep's own comment explains why that asymmetry is correct there: a column listed but empty costs one sequential scan a day, while one missing from the list can license the delete of a file still in use. For the copier the same asymmetry runs the other way, so an empty column today is not a reason to leave it out.
Where those objects live
Grouped by the legacy folder they sit in, the uncovered references reach folders no descriptor names at all. The largest are inventory/biomed-import, ezrfp-quote, document-manager, eula, ezrfp/attachments, ezrfp-warranty, service-agreement, pa-documents and profile — so the objects behind them are never copied, not merely left un-rewritten.
Object never copied. Where no descriptor names the object's legacy folder at
all — inventory/biomed-import, ezrfp-quote, eula and the
rest — the object stays in the shared folder forever. Nothing in the plan reaches it.
Object copied, record not rewritten.
transaction_equipment_details.equipment_details is a frozen snapshot of an inventory
at transaction time, so its pictures are the same objects
camp_inventory_items.images copies. Those objects do get copied — but this column has
no write-back target, so the snapshot keeps naming the legacy path. That reads correctly (the
original is never deleted) and may well be intended for a frozen record, but it is not stated
anywhere as a decision.
It is not necessarily a defect in every case. A frozen snapshot arguably should keep
the path it was written with, and users.profile at 29 values is trivial to add. The
finding is that the set was never enumerated: the descriptor list has no
counterpart to path-references.ts, which lists every column that can hold a path for
the temp sweep. Nothing compares the two, so a column omitted from the copier is invisible.
References are not objects
The table above counts rows, which is the right unit for the write-back — every one of them names a legacy path and would need rewriting. It is the wrong unit for the copy. The queue is keyed on the destination, so what the runner actually owes is one copy per (object, owning account) pair.
| Column | References | Distinct objects | Copies owed |
|---|---|---|---|
| biomeds.extracted_data → biomedFile | 20,052 | 159 | 162 |
| copilot_launch_proposals.quote | 9,040 | 5,763 | — |
| copilot_launch_proposals.additional_details → parentQuote | 3,435 | 919 | — |
| users.agreement_details → name | 1,086 | 1,086 | — |
| copilot_requests.attachments | 679 | 664 | — |
| copilot_launch_proposals.warranty_document | 678 | 625 | — |
| docusign_requests.attachments | 53 | 53 | — |
| users.profile | 29 | 29 | — |
The copy work is small; the rewrite work is large. biomeds is the
clearest case: 20,052 rows name a file, but they share only 159 objects between them, and those
resolve to 162 destination copies across 105 accounts — three objects are
referenced by two accounts each, and the rest by one. So the runner would move 162 files, while the
write-back would have to touch 20,052 rows.
That makes the gap cheaper to close than the row count suggests, and it also explains how it stayed hidden: a shared reference table looks enormous by row count and trivial by object count, and neither number on its own tells you whether a descriptor is missing.
The dash means not measured — those columns have no single owner expression to group by, which
is itself part of why they have no descriptor: ownerSql is the field a descriptor
cannot be written without.
It has never been run
file_migration_queue and file_migration_batches both hold zero rows in
clone1 — the database where every migration is applied. The copier has therefore never
been seeded, never copied an object, and never written a path back. Everything on
the previous page is built and reviewed; none of it is
exercised.
Three consequences follow directly:
- Gate 3 has not been passed. The gate requires one account copied, verified and rolled back once for real. No account has been copied, so the rollback — the thing every later batch depends on — has never been run against live data.
- The rehearsal mode is untested in anger.
FILE_MIGRATION_TARGET_PREFIXexists precisely so this can be exercised end to end against a scratch folder. That is the cheapest way to close Gate 3 and it has not been used. - It cannot run against production as it stands.
capCloneis missing the two ledger tables, the run switch, and the PO-path migration — four migrations behindclone1.
Three descriptors that do nothing
These are declared rather than forgotten — the seeder records them so they appear in the report — but they move no files.
| Descriptor | Why it is inert | What it needs |
|---|---|---|
| ezestimator_requests.file_path | Seeded as skipped — no upload module exists for ez-estimator requests | One row in UPLOAD_MODULE_TARGETS and an enum value |
| capture_billings.invoice_path | Seeded as skipped — the owner is ambiguous between the capture and facility accounts | A product decision on which account owns a capture invoice |
| qr_codes.image | Copied, but never written back — the path is derived from the id, so no record names it | Nothing. This one is correct by design |
A QR code is printed onto physical labels. The object must never move, and no row names it — so
a write-back target would have nothing to write. noWriteBack is a declaration, not an
oversight: a descriptor with neither it nor a writeBack is reported as a gap by every
batch that carries one.
What CAP-680 was never meant to cover
Listed so the boundary is explicit rather than assumed. These belong to other steps, and CAP-680 not doing them is correct — but they all gate a real run.
| Item | Belongs to | State |
|---|---|---|
| Deploying the thumbnail service | Step 3 | deployed and running |
| Backfilling thumbnails for existing images | Step 3 | already done |
| The upload shape flag | Step 4 | not built — behaviour is unconditional |
| Legacy-read logging by app version | Step 6 | not built |
| The mobile leg of the shared path rule | Step 2 | neither mobile repo carries it |
| Two upload sites still on the legacy folder | Step 4 | docusign helper, one marketplace invoice |
The service is deployed on the finalize event, and the backfill for images already in the bucket has been run — so thumbnail coverage is not something the copier still owes. Step 5's copy does fire finalize, so objects it moves get a thumbnail at their new path as a side effect, and the existing ones already have theirs.
What would close it
- Assert the two lists against each other. A test that walks
PATH_COLUMNSand fails on any entry with neither a descriptor nor a recorded exemption would have caught all 26 of these at authoring time. It is the fix that stops this class of gap returning, and it is cheap — both lists already exist. - Decide each of the 26 deliberately. Add a descriptor, or record why it does
not need one — the way
qr_codesalready does withnoWriteBack. Start withbiomeds.extracted_data(20,052) andcamp_inventory_items.additional_details(15,533, nearly allcad.cadUrl): neither table appears anywhere in the plan. - Record the two exclusions in code.
documents.file_pathandequipment_models.selected_imageare out of scope by decision, but nothing in the repository says so — which is the same invisibility the other 30 have. AskipReasonagainst each would make the choice legible to the next reader. - Run the rehearsal. Set
FILE_MIGRATION_TARGET_PREFIX, seed, run one account, write back, roll back. That closes Gate 3 without touching a real destination. - Confirm the deployed thumbnail service keeps up with a batch run. It is live on the finalize event, so a copier batch will fire one invocation per object copied — worth knowing the concurrency ceiling before a large account goes through.
- Bring
capCloneup to date — four migrations, or the copier has nothing to work through in production.