Repository navigation
Thread: writ origin — a deployment says where it came from #806
Description
The scenario
A developer deploys her environment onto a new laptop. Three repositories contribute — noblefactor-ops
as base, a team layer, and personal. writ resolves them, merges the trees, and writes the files.
A week later she is on a different machine and something is wrong: a script behaves differently than it
did. She asks the obvious question.
What is deployed here, and where did it come from?
writ answers the first half precisely. The trace's catalog names every file it wrote and digests each
one. The second half it cannot answer at all. Nothing in a trace names a repository, a ref, or whether
the tree was clean when it was read. writ records what it did and never what it did it from.
Every consequence follows from that one gap:
- She cannot reproduce the deployment, because she cannot name its inputs.
- She cannot be told the base layer has moved, because nothing recorded where it was.
- CI cannot build against the foundation she deployed, because the deployment does not state one.
writ reconcilecompares disk against trace, so it reports file drift while staying silent about
source drift — which is the drift that would have explained her problem.
The narrative, in beats
Each beat is a step in that story, and each is already an issue in whichever epic owns the machinery.
The thread supplies the order; the epics keep the ownership.
| Beat | What must become true | Issue | Epic |
|---|---|---|---|
| 0 | A trace can be read back at all | #758 | Serialization |
| 1 | A layer source is a named thing with a role and a precedence | #468 | LayerSplit |
| 2 | writ deploys from several sources at once | #469 | LayerSplit |
| 3 | The trace records each source's repository, ref, and cleanliness | #473 | LayerSplit |
| 4 | Upgrade pins and records origin too, so a regenerated copy has provenance | #844 | LayerSplit |
| 5 | Pinned layers link into the kept snapshot, so a deployment is a commit | #847 | LayerSplit |
| 6 | Drift is reported per source, not only per file | #471 | LayerSplit |
| 7 | reconcile presents it as part of the lifecycle surface |
#762 | WritDeployment |
| 8 | A consumer repository pins to the deployed foundation | NobleFactor/noblefactor-ops#147 | Process |
Beat 0 is a hard gate. Today op.LoadTrace fails with yaml decode: op.ResourceState: unknown state "1",
every trace in the store is unreadable, and reconcile returns "entries": null. There is no history to
add an origin to until that is fixed.
Beat 3 is the center. The beats before it make it possible; the beats after it make it useful.
What beat 3 adds
Trace metadata today has five top-level keys — graph_checksum, run_status, stack, catalog, and
checksum/signature — and no origin block of any kind. The addition:
origin:
tool: writ
layers:
- layer: base
repository: NobleFactor/noblefactor-ops
ref: 4f2c9ab...
dirty: falsedirty can be recorded rather than merely refused: writ deploy already blocks on an uncommitted base,
team, or personal tree, so a clean read is the default and the flag documents a deliberate override.
Why this is a thread and not an epic
The work is owned by four epics here and one in another repository. Minting Epic:WritOrigin would strip
five issues from the epics that own their machinery — #473 belongs with layer pinning whether or not this
scenario is ever written down. The scenario is real and needs a name; the ownership is already correct.
A thread names a scenario and orders the work that scenario requires. Members keep their kind and
their epic and gain a Thread: label. Two properties distinguish it from Epic::
Thread:is multi-valued. An issue may serve more than one scenario; it has only one owner.- A thread crosses repositories. Beat 6 lives in
noblefactor-ops, which no epic here can reach.
A thread issue carries no Epic: label: a scenario has no owner. Ruled 2026-09-05; issue-standards.md §The two exceptions.
Activity
David-Noble-at-work commentedon Sep 6, 2026
Beat 3 (#473) is smaller than filed: the per-layer commit hashes and the dirty-layer list are already recorded in the persisted graph's origin annotations (
PinInfo.CommitHashes,DirtyLayers) on every deploy — verified on today's first base-layer deploy. What is missing is surfacing them: the trace's metadata andreconcile's output. Details and the plan-from-HEAD / serve-from-working-tree semantic on #473.