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 finding that matters most

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.columnValuesKey paths that hold a fileAn actual file today → where it should go
biomeds.extracted_data20,052
biomedFile 20,052
inventory/biomed-import/012020260844530001pd_1768919771163.pdfdoc/{CAP}/assets/biomed-import/012020260844530001pd_1768919771163.pdf
camp_inventory_items.additional_details15,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.quote9,040(comma list entry)ezrfp-quote/00200335PD_1743581224930.PDFdoc/{CAP}/copilot-quote/00200335PD_1743581224930.PDF
copilot_launch_proposals.additional_details3,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_details2,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_details1,086
name 1,086
eula/eula_aaron_hayes1780079378131.pdfdoc/{CAP}/eula/eula_aaron_hayes1780079378131.pdf
copilot_requests.proposal_summary750
comparisonViewAttachment 750
ezrfp/attachments/comparison_view_1761676961963.pdfdoc/{CAP}/copilot/attachments/comparison_view_1761676961963.pdf
copilot_requests.attachments679(array element)ezrfp/attachments/0001433807_Power_ease_pd_1767797175378.pdfdoc/{CAP}/copilot/attachments/0001433807_Power_ease_pd_1767797175378.pdf
copilot_launch_proposals.warranty_document678(array element)ezrfp-warranty/12037_2_PHCNA_ULT_Warrantypd_1740585238650.pdfdoc/{CAP}/copilot/warranty/12037_2_PHCNA_ULT_Warrantypd_1740585238650.pdf
shipping_estimates.request_details289
items[].images[] 289
inventory/images/0730Xjpe_1752603773879.jpegimg/{CAP}/assets/0730Xjpe_1752603773879.jpeg
po_requests.attachment145(whole value)cer-documents/2341727jpe_1726568391940jpe_1728417342272.jpegdoc/{CAP}/cer-documents/2341727jpe_1726568391940jpe_1728417342272.jpeg
copilot_parent_requests.action_logs114
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.quote111(comma list entry)ezrfp-quote/1670238683338_PROEE08Y7Fjp_1689604722125.jpgdoc/{CAP}/copilot-quote/1670238683338_PROEE08Y7Fjp_1689604722125.jpg
copilot_pre_orders.additional_details107
poAttachments[].attachment 107
document-manager/purchase-orders/041526Neptune_1776266774975.pdfdoc/{CAP}/document-manager/purchase-orders/041526Neptune_1776266774975.pdf
shipping_estimates.shipping_responses92
[].fileName 92
document-manager/shipping-documents/1719218701621_LZZ7B9GBGZ.pdfdoc/{CAP}/shipping-document/1719218701621_LZZ7B9GBGZ.pdf
shipping_estimates.quote_email_details80
attachments[] 80
document-manager/shipping-documents/1719218701621_LZZ7B9GBGZ.pdfdoc/{CAP}/shipping-document/1719218701621_LZZ7B9GBGZ.pdf
copilot_launch_proposals.service_agreement72(array element)service-agreement/20200512095946_203860pd_1739564940779.pdfdoc/{CAP}/service-agreement/20200512095946_203860pd_1739564940779.pdf
accounts.images_attachment54
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.attachments53(whole value)cer-documents/cer_1750418847569MAP6U.pdfdoc/{CAP}/cer-documents/cer_1750418847569MAP6U.pdf
po_requests.additional_details36
comparisonView.fileName 36
ezrfp/attachments/comparison_view_1749043309118.pdfdoc/{CAP}/copilot/attachments/comparison_view_1749043309118.pdf
users.profile29(whole value)profile/1A4A1364jpe_1772729243492.jpegimg/{CAP}/user/profile/1A4A1364jpe_1772729243492.jpeg
denovo_projects.additional_details26
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_document10(whole value)document-manager/shipping-documents/Bill of Lading 1723044241771_1723044241771.pdfdoc/{CAP}/shipping-document/Bill of Lading 1723044241771_1723044241771.pdf
copilot_requests.additional_details3
serviceContract.serviceContractFile 3
inventory/service-contract/Alcon_Constellation_Servicpd_1742494646144.pdfdoc/{CAP}/assets/service-contract/Alcon_Constellation_Servicpd_1742494646144.pdf
report_requests.file_path3(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_attachment1(array element)inventory/images/2jp_1713209028801.jpgimg/{CAP}/assets/2jp_1713209028801.jpg
Total55,13753 key paths26 columns
Four columns I had counted hold no file at all

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.

Two different failure modes, worth separating

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.

What this is not

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.

ColumnReferencesDistinct objectsCopies owed
biomeds.extracted_data → biomedFile20,052159162
copilot_launch_proposals.quote9,0405,763
copilot_launch_proposals.additional_details → parentQuote3,435919
users.agreement_details → name1,0861,086
copilot_requests.attachments679664
copilot_launch_proposals.warranty_document678625
docusign_requests.attachments5353
users.profile2929
What this changes about the finding

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:

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.

DescriptorWhy it is inertWhat it needs
ezestimator_requests.file_pathSeeded as skipped — no upload module exists for ez-estimator requestsOne row in UPLOAD_MODULE_TARGETS and an enum value
capture_billings.invoice_pathSeeded as skipped — the owner is ambiguous between the capture and facility accountsA product decision on which account owns a capture invoice
qr_codes.imageCopied, but never written back — the path is derived from the id, so no record names itNothing. This one is correct by design
The QR code case is deliberate, and should stay that way

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.

ItemBelongs toState
Deploying the thumbnail serviceStep 3deployed and running
Backfilling thumbnails for existing imagesStep 3already done
The upload shape flagStep 4not built — behaviour is unconditional
Legacy-read logging by app versionStep 6not built
The mobile leg of the shared path ruleStep 2neither mobile repo carries it
Two upload sites still on the legacy folderStep 4docusign helper, one marketplace invoice
Thumbnails are not part of the remainder

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

  1. Assert the two lists against each other. A test that walks PATH_COLUMNS and 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.
  2. Decide each of the 26 deliberately. Add a descriptor, or record why it does not need one — the way qr_codes already does with noWriteBack. Start with biomeds.extracted_data (20,052) and camp_inventory_items.additional_details (15,533, nearly all cad.cadUrl): neither table appears anywhere in the plan.
  3. Record the two exclusions in code. documents.file_path and equipment_models.selected_image are out of scope by decision, but nothing in the repository says so — which is the same invisibility the other 30 have. A skipReason against each would make the choice legible to the next reader.
  4. 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.
  5. 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.
  6. Bring capClone up to date — four migrations, or the copier has nothing to work through in production.