Storage reorganisation · the plan

The Plan

Separate every stored file into a folder of its own account — in the bucket we already have, under two top-level names it has never used. Nothing is deleted and nothing is moved: every file is copied, and every original stays exactly where it is.

Buckets
1The folders change, not the address
Trees
2img for pictures, doc for everything else
Steps
6Three gates, each a full stop
Deletions
0In every step, at every scale

The problem

Files live in one shared set of flat folders today — inventory/images/, ezrfp-quote/, profile/ — and the folder name is added by the software at the moment it is needed. A row stores pump_1788.jpg and nothing more, so every reader has to already know which folder that name came from. That knowledge is spread across four codebases and is not written down anywhere the row itself can see.

Two consequences follow. There is no way to answer "which files belong to this account" without consulting the software, and there is no way to move a file without finding every reader that reconstructs its folder by hand.

The goal

Make the path itself say who owns the file, and make the stored value the whole answer. After that, one account's files are a folder listing, and a reader needs no knowledge beyond the value it was handed.

The shape we are moving to

Everything stays in the bucket we already have. The first segment is the tree:

TreeHolds
imgPictures — asset photos, logos, tag sheets, QR codes
docEverything else — quotes, contracts, invoices, exports, PDFs

The second segment says who the file belongs to, and that decides the rest of the shape. The path shape is the access model, not a cosmetic detail:

account       <tree>/<capexId>/<domain>/<file>
account-user  <tree>/<capexId>/<branch>/<userId>/<domain>/<file>
user          <tree>/<capexId>/user/<domain>/<file>
library       img/<branch>/<file>          doc/<branch>/<domain>/<file>
global        <tree>/<branch>/<file>       belongs to nobody

Worked through, with real values out of clone1:

img/CAP-0YNN74P/assets/2BQISjpe_1782915476233CTS5HSW.jpeg   account
img/CAP-BBA2A600/user/profile/avatar_1788.png                user
doc/CAP-NJQJGDJ/copilot-quote/OrderCAPSKOCI7V4DZZE9K.pdf     account
doc/CAP-BBA2A600/export/1/assets/camp_export_1788.xlsx       account-user — user 1 ran it
img/model-library/pump_x200.jpg                              library
doc/export/system/nightly_1788.xlsx                          global

The five rules that make it safe

  1. One bucket. No new bucket and no rename, so there is never a moment where two buckets have to be kept in step.
  2. Complete paths in records. The stored value is read as it stands. Nothing prefixes a folder onto it.
  3. Copy, never move. Every original stays where it is. A half-migrated table reads correctly throughout, and every rollback lands on originals that were never touched.
  4. Uploads stage in temp/. A browser upload cannot be written to its final home, because the record referencing it does not exist yet.
  5. The old folders are kept. Nothing is written to them again and nothing is removed from them.
Why the old folders stay

The risky part is not the copying. It is that customers run mobile app versions we cannot update, and those versions ask for the old locations. So the sequence keeps the old locations working for as long as anything asks for them, and evidence — not a date — decides when they can go.

The running order

Six steps and three gates. Steps 1 to 3 are invisible to users; behaviour changes at step 4; nothing is deleted or moved in any of them.

Step 1Store the full file pathWiden the existing columns and fill in where every file is now. Add the migration table. No file moves.
Step 2One rule finds every fileEvery place that glues a location together by hand calls one shared rule instead — API, website, mobile service. Still no file moves.
Gate 1StopBoth steps live, every repo green, older records still displaying, and zero files moved.
Step 3Thumbnails learn both shapesThe live service watches the new folders too and files each thumbnail beside its picture. Must be deployed before step 4.
Step 4New uploads change shapeFlip one setting: new files land in the account's folder, staged under temp/ until the record is saved. Existing files untouched.
Gate 2StopAn upload from web and phone lands correctly and gets a thumbnail; five abandoned uploads never leave temp/.
Step 5Copy the existing filesAccount by account, smallest first, thumbnail before picture. Copy, verify, update the record, leave the original.
Gate 3StopOne account copied, verified, and rolled back once for real before anything runs at scale.
Step 6Keep and watch the old foldersStop writing to them, log which app versions still read them, and let evidence — not a date — decide when they can go.

Each step is reversible on its own, and each gate is a full stop rather than a checkpoint. Step by Step takes each one in turn; Validation says where each one actually stands.