Restore guarantee
A Marrow export bundle must always be restorable to an exact replica of the original workspace.
This is the single non-negotiable promise Marrow makes. Every other architectural decision flows from it.
Why this matters
Section titled “Why this matters”Most knowledge bases give you an “export” feature that’s really a one-way escape hatch — half-broken Markdown, no metadata, no fidelity. The implicit message is: this data lives here; if you ever leave, you’ll lose something.
Marrow inverts that. Your data is always portable, always whole. You can:
- Move from one Marrow instance to another with no fidelity loss.
- Back up your workspace as a single zip file.
- Inspect the bundle by hand — it’s plain Markdown and JSON.
- Restore from a backup taken months earlier, on a different version of Marrow, and trust that the result is the same workspace.
How the guarantee is enforced
Section titled “How the guarantee is enforced”1. Append-only revisions
Section titled “1. Append-only revisions”Every save creates a new row in the revisions table. Existing revisions are never modified. This is enforced by a PostgreSQL trigger (revisions_immutable()) that raises an exception on any UPDATE against the table. A migration that removes this trigger is a critical bug.
2. Transparent bundle format
Section titled “2. Transparent bundle format”Bundles are zip files containing Markdown, JSON, and a manifest.json. No proprietary serialization. See Export bundle format for the layout.
3. The round-trip test
Section titled “3. The round-trip test”api/tests/test_round_trip.py is a regression anchor: it creates a workspace with multiple spaces, a folder/page node tree, revisions, and attachments, exports it, wipes the database, restores from the bundle, and verifies the result is byte-equivalent to the original. This test must pass at all times. It runs in CI on every change.
4. Legacy bundle compatibility
Section titled “4. Legacy bundle compatibility”marrow restore accepts bundles from earlier Marrow versions, including bundles produced before the project was renamed (the freehold-export-*.zip filename prefix is still recognized). The restore guarantee is a forward promise — old bundles must continue to restore on new versions.
Marrow 0.2 introduces bundle schema v4, which carries the new nodes tree shape (folders + pages in one self-referential structure) and node soft-delete state. Older v1/v2/v3 bundles still restore: their legacy collection/page structure is auto-upgraded into the node tree on read, so a backup taken on 0.1 restores cleanly on 0.2 with no manual steps.
5. Soft-deleted nodes
Section titled “5. Soft-deleted nodes”Deleting a node sets deleted_at instead of removing rows — the data goes to trash. By default, exports skip soft-deleted nodes so a “live” backup matches what users see in the UI. Pass --include-trash to marrow export (or ?include_trash=true on the API) to include them; the manifest records the choice so restore knows whether to recreate trash entries.
What this rules out
Section titled “What this rules out”The guarantee imposes constraints contributors should be aware of:
- No silent migrations of revision content. If revision data needs reformatting, it happens at read time, not by editing rows.
- No proprietary blobs in the export bundle. Every value must be representable in plain text or standard formats.
- No hidden state. Attachments live in the bundle. Search indices are derived state and rebuilt on restore.
- No “this only works in v1.0+” features. Adding capability is fine; breaking restore for any older bundle is not.
If you’re proposing a change that conflicts with the restore guarantee, the change is wrong.