# Native storage contracts

Source: https://holofolder.com/app/docs/native-storage

Authenticated native storage commands, replay and recovery through REST and MCP.

**Supported native developer operations use the trusted storage executor.** REST uses a personal API key; MCP uses OAuth. Current actor, credential, membership, billing and operation permissions remain required. Public share links grant no private inventory access.

Five [native developer reads](https://holofolder.com/app/docs/markdown/native-reads) return redacted pages and current edit preconditions. Their `completeSnapshot:false` results are never private planning snapshots or browser queue acknowledgements. [Copied preset previews](https://holofolder.com/app/docs/markdown/preset-previews) require `catalog:read` and `binders:read`, allocate no creation identities, and do not create ownership.

The app uses the same executor through its first-party routes. Unsupported operations return errors; they are never approximated or filled with guessed card data.

## Commands

Use the same arguments with the REST path below or the same-named MCP tool. Paths are relative to `/app/api/v1`. All commands require `collection:write` and `binders:write`; recovery also requires `collection:read`.

| MCP tool                                                    | REST POST path                                                | Effect                                                                           |
| ----------------------------------------------------------- | ------------------------------------------------------------- | -------------------------------------------------------------------------------- |
| `execute_slot_commands`                                     | `/storage/slots/commands`                                     | Insert, update, clear, remove or reorder stable slots                            |
| `execute_inventory_commands`                                | `/storage/inventory/commands`                                 | Add, edit, remove, assign, move or unassign physical copies; delete a collection |
| `create_storage_collection`                                 | `/storage/collections`                                        | Atomically create native storage and its copied targets                          |
| `import_storage_arrangement`                                | `/storage/imports/arrangement`                                | Import explicit copies and arrangement into a compatible native destination      |
| `restore_raw_collection_content`                            | `/storage/restores/raw-content`                               | Reconcile supported same-owner raw content                                       |
| `preview_storage_recovery` / `apply_storage_recovery`       | `/storage/recovery/preview` / `/storage/recovery/apply`       | Review and apply version 1 native recovery                                       |
| `preview_storage_recovery_v2` / `apply_storage_recovery_v2` | `/storage/recovery/v2/preview` / `/storage/recovery/v2/apply` | Explicit default-creation replacement recovery                                   |

Use current inventory, collection, layout, slot, item and placement revisions. Preserve the original `operationId`, identities, expected owner and request across retries. A timeout or lost response is an unknown outcome. Never refresh preconditions or allocate a replacement ID to retry an unknown write.

Only explicit `removeOwned` deletes ownership. Clearing or removing a slot, removing a placement, and deleting a collection release its copies to Unassigned. To delete a collection together with its cards, send `deleteCollection` with `removeOwned: true` (migration 111): raw copies in it are deleted, graded copies still go to Unassigned because their label assets can't be restored. Deleting the default requires an explicit valid replacement where the command requires one. Empty targets, spacers and wants never manufacture owned copies.

## Preserve private fields

Developer reads omit certificates, private label assets and immutable source evidence. Use `privateFields:"preserve"` and `expectedOwnerUserId` for supported mutations of that redacted state. The expected owner is checked against the authenticated current scope before execution. The SQL executor preserves hidden data after the original-intent replay lookup; the client never reconstructs private JSON from a redacted read.

Supported operations include slot insertion, reorder and target-only updates; explicit inventory add, removeOwned, unassign and placement; `patchGrading` on declared public grading fields; `setFinish`, which corrects the recorded finish of a card copy in place (same copy, grading and placement, and still matching any finish its pocket pins); and selected-want consumption using exact `expectedPublic` evidence. Private certificates/assets/provenance and unrelated wants remain intact. Selected wants require the exact locked slot/index public projection.

Whole grading/wants replacement and destructive slot clearing are unsupported in preserve mode. Omitting the field or selecting `replace` keeps full-replacement semantics. Use that only with deliberately complete values, never by filling hidden fields with nulls.

## Atomic slot insertion and copy assignment

`insertAssign` inserts a stable slot before an exact existing slot reference, or appends with `before:null`. Preserve `target:null` for a free slot or the explicit target. `selection:null` creates no ownership; `existing` needs an exact Unassigned copy/version; `new` explicitly acquires the given stable copy. It never chooses a grade or moves an assigned copy implicitly.

The executor bounds insertion to 4,096 slots and 8,192 insertion work rows, 100 commands and 256 affected selected entities. Ordinary commands retain 256 KiB request and 1 MiB receipt bounds, a 10-second deadline and private audit bounds. Oversized evidence rejects atomically. See the generated endpoint reference for each exact schema and limit.

## Creation and arrangement import

Creation saves the named collection, copied manifest and ordered stable slots in one transaction. Retain operation, collection and slot IDs. An owner's first collection creates their inventory root from revision 0 and becomes the default. If a first creation fails, retain the exact original request for retry or reconciliation; an unavailable response is not a terminal discard proof. Existing count/grid entitlements still apply. Empty goals create no ownership or wants. Creation receipts retain conservative `recoverySupported:false`; recovery preview determines supported evidence independently.

Reviewed set/variant checklists additionally persist `manifest.setScope`: exact language, verified finish families, copied catalog evidence, promos, exclusions, count, capture time and warnings. Creation validates current mirror eligibility, then preserves the historical snapshot. `list_storage_collections` returns it in `presetManifest`. Later targets and free slots stay editable; membership is never permanently enforced. See [Set checklists](https://holofolder.com/app/docs/markdown/set-checklist-drafts).

Arrangement import requires an empty or target-only native destination with an already identical copied manifest. Every physical copy needs an explicit add or versioned existing-copy move. It does not replace collections/configuration, import assets, authorize backup-file IDs or restore arbitrary historical snapshots.

## Terminal rejection and exact replay

A committed terminal ledger rejection returns REST `409` or MCP `isError:true` with matching structured/text JSON. The error code is `storage_command_rejected`, `storage_creation_rejected`, `storage_import_rejected`, `storage_recovery_rejected` or `storage_restore_rejected` for the corresponding family. Strict `error.rejection` contains only `outcome:"rejected"`, the original `operationId`, a known `code`, `inventoryRevision`, and the version where that contract requires it.

MCP `outputSchema` describes accepted output only; `holofolder.com/errorSchemas` declares errors separately. Arbitrary 409s, SQL conflicts, timeouts, cancellation and failed COMMIT are not terminal rejection proof. They do not authorize dropping retained intent.

Current authorization precedes lifetime exact accepted/rejected replay. A replay receipt is historical. Browser consumers must obtain a fresh same-session complete current read started after the reply and explicitly reconcile before acknowledging, discarding or reviewing a replacement operation. Developer projections cannot supply that browser read authority.

## Recovery

Preview returns public-safe Activity changes and conflicts, never private inverse state. Unsupported dependencies return no usable plan. Apply requires the exact reviewed `planToken`, explicit confirmation, version and retry-stable operation ID. Newer or ABA changes conflict. Version 2 default-creation recovery requires an explicit same-owner replacement and current inventory/content/layout revisions. There is no implicit replacement or fallback. Private label asset evidence can remain unsupported with `private_assets_unverifiable`.

## Bounded same-owner raw restore

Raw restore reconciles one existing raw collection with the same stable slot IDs and unchanged configuration/grid. Version 4 is the only command version. It carries the exact copied manifest, canonical per-slot origins and `slotOrder.before`/`after`, either equal or a permutation of the same IDs; only a reordering advances the layout revision. Content and origins bind by stable ID, slot versions advance, and the receipt carries both exact orders. A command has at most 1,025 slots/manifest entries, 2,050 distinct copy references, 100 wants per slot, `2S+I+W ≤ 8,192` and 1.75 MiB command/JSONB text. Its complete prospective audit and undo graph is capped at 2.5 MiB.

Explicit current choices and trusted actor/owner/session or credential/membership evidence bind the plan. File IDs never prove provenance. Every displaced copy needs explicit release consent and remains Unassigned. Copies placed elsewhere cannot move automatically. Acquisitions, graded copies, changed manifests/origins, metadata/settings/assets/CSV and multiple-collection restores are unsupported. SQL validates the complete actual audit graph and its undo before commit.

Accepted receipts and dedicated rejections carry `version:4`. Other command versions return `invalid_input` and preserve device records. Files with unverified wanted-card image URLs remain blocked by the existing export/review policy; no URL is silently removed. First-party planning accepts a backup file up to 10 MiB. No file is stored as a server backup and no provider refresh is triggered.

## First-party app routes

The cookie-authenticated app uses `GET /app/api/inventory`, `POST /app/api/binder-slots`, `POST /app/api/inventory`, `POST /app/api/storage-collections`, `POST /app/api/native-recovery` and `POST /app/api/raw-content-restore`. These are private, no-store boundaries. Write requests bind the current owner and auth session and use the app origin. Developer credentials use their separate REST/MCP boundaries and never impersonate an app cookie session.