Versioned publishing
The moment an authored thing is used by someone else's in-progress work, editing it in place stops being an edit and becomes a rewrite of history.
The symptom is always the same and always reported late: a completed record no longer matches what the person actually saw. Scores move. A field they filled in has disappeared. A report from March renders with April's wording.
1. Publishing creates a version; published versions are immutable
Authoring edits a draft. Publishing snapshots the draft into a new numbered, immutable version. Nothing ever writes to a published version again — not a typo fix, not a reorder, not "just the label".
template v1 (published) <- submissions started before Tuesday reference this
template v2 (published) <- submissions started after reference this
template (draft) <- being edited now
The typo fix is v3. This feels heavy for a one-character change and it is the only thing that keeps old records readable.
2. In-flight sessions keep the version they started on
A record captures its version id at creation and holds it to completion. It does not follow the latest.
The alternative — everything reads current — produces a form that gains a required question halfway through, which the user cannot answer because they have left the building.
Decide and document what happens to a long-lived draft when the template moves on. The defensible answers are "it finishes on its version" or "it is offered an upgrade explicitly". The one to avoid is "it silently migrates".
3. Reading a record means reading its version
Every consumer — the detail view, the export, the PDF, the score recomputation, the analytics job — resolves through the version stored on the record.
This is where in-place editing usually leaks back in: the form renders correctly from the stored version, and then the report joins to the current template for the section titles. Grep for every place the template is loaded and check what picks the version.
4. Archive; do not delete
Retiring something removes it from the pickers and from anything new. It does not remove the row, because completed records still reference it.
- A status or lifecycle field, or a move to an archive store — either works, as long as the row remains resolvable by id.
- Every list that offers items to users filters on that status. Every list that renders historical records does not.
- Audits must know about the archive. A retired item looks identical to a missing one, and to a broken one. Findings about archived entities are almost always noise, and checking the status first is what stops an audit reporting defects in things nobody serves.
5. Ids are stable; everything else can change
An item's id is its identity across every version, both directions. Titles get rewritten, options get reordered, sections get moved — all fine, all traceable, as long as the id does not move.
Regenerating ids on publish, or deriving them from the title or the position, breaks every historical reference at once and is close to unrecoverable. If ids are currently positional, fixing that is more urgent than whatever you came here to do.
6. The copy-then-customise pattern
A shared library of items plus per-tenant copies is a good shape, and it has one sharp edge: what happens to the copy when the original changes?
Decide explicitly:
- Fork on copy — the tenant's copy is independent. Simple, predictable, and the library's improvements never reach anyone who already copied.
- Track with opt-in — the copy records its origin and version, and the tenant is shown "the library version has been updated" with a diff and a button.
Either is defensible. What is not defensible is pushing library edits into tenant copies silently, which rewrites someone else's document without asking.
7. The version diff is the changelog
If a version is a snapshot, the diff between consecutive versions is a free, accurate record of what changed. Generate it rather than asking people to maintain release notes by hand — hand-maintained notes drift, and the diff cannot.
Check it into the repo where a snapshot is exportable. It becomes the answer to "when did this question change, and who changed it?", which is otherwise archaeology.
Checklist
- Publishing snapshots an immutable numbered version
- In-progress records store and keep their version id
- Every reader resolves through the record's version, including exports and reports
- Retiring archives rather than deletes; historical reads still resolve
- Audits check archived status before reporting a defect
- Ids stable across versions and never derived from title or position
- Library-to-copy propagation rule chosen and documented
- Version diff generated, not hand-written