Status: Accepted
Date: 2026-10-01
Deciders: Stijn Dejongh (owner), through the operator ruling
decision-ledger-partition (Decision Moment DM-01M3V4BXG1EP7JX83TM5K6WSJ8) and the
sibling ruling legacy-ledger of Mission coord-artifact-single-home-01M3V4BE.
Technical Story: #5023, Mission
coord-artifact-single-home-01M3V4BE (FR-009, FR-009a, FR-009b, FR-009c). Reverses the
classification chosen under #3928.
Context and Problem Statement
The Decision Moment ledger is the pair decisions/index.json and decisions/DM-<ulid>.md
under a Mission's directory (MissionArtifactKind.DECISION_LEDGER). Two facts disagreed:
- The taxonomy said COORD. #3928 classified the kind as COORD-partition, "single-writer coordination bookkeeping", on the theory that the ledger's writer resolved its directory through the coordination status surface. No ADR recorded that intent; it lived in code comments.
- The code said PRIMARY. #4966 (AC-D2) had already moved the ledger's own reads and
writes onto the PRIMARY partition:
decisions/service.py::_ledger_dirresolvesPRIMARY_METADATAthroughplacement_seam(...).read_dir(...). Only the decision events (status.events.jsonl,decisions.events.jsonl) were written to the coordination surface.
The disagreement is what #5023 reported. spec-commit classifies by the taxonomy, so it
routed the ledger's files onto the coordination branch, while the writer kept producing
them on the PRIMARY partition. The ledger then forked from the Mission's own history, and a
coordination teardown could destroy the only committed copy.
This ADR is the durable record the #3928 intent never had. It is narrower than, and separate from, ADR 2026-06-19-1: that ADR governs the empty coordination surface, and its 2026-10-01 amendment links here instead of hosting the reversal.
Decision Drivers
- The taxonomy must describe the write side that already exists, not the reverse (read/write symmetry, ADR 2026-06-24-1).
- A committed ledger must travel with its Mission's branches and survive coordination teardown.
- No automatic log merge (C-003), and no silent loss of an index entry (FR-011).
- Fix forward (C-004): Missions created before this change must be repairable.
Considered Options
- Classify the ledger PRIMARY (chosen). The taxonomy moves to match where the ledger is read and written today.
- Move the ledger's reads and writes to the coordination surface. The code would move to match the #3928 classification.
These are the two options the operator ruling weighed (Decision Moment
DM-01M3V4BXG1EP7JX83TM5K6WSJ8); its Other slot was not used.
Decision Outcome
Chosen option 1, because it is the only option that changes the taxonomy rather than the writer. The writer and every reader of the ledger content were already PRIMARY (#4966); option 2 would have reversed that work and put the ledger on a branch that lanes do not carry and that consolidation tears down.
- Partition.
DECISION_LEDGERis in_PRIMARY_ARTIFACT_KINDS(src/mission_runtime/artifacts.py)._COORD_RESIDUE_DIRS["decisions"]still maps the directory to the same kind; only the partition membership moved.kind_is_coordination_residueis therefore false for it, sodecisions/is never reset as coordination residue. - Events stay COORD. Decision events in
status.events.jsonlanddecisions.events.jsonl(DECISION_LOG) are unchanged.doctor decisionsjoins the two surfaces; the ledger content is PRIMARY and the event log is COORD. - Committers. The ledger is committed by
spec-commitandacceptonly. The commit router routes the ledger to the target branch.acceptclassifies the current Mission's uncommitted ledger files through the artifact taxonomy (acceptance/ledger_dirt.py::mission_decision_ledger_files), commits them, and still fails closed on any other dirt, including another Mission's ledger. The consolidation dirty gate now refuses uncommitted ledger files instead of resetting them as residue; its message namesacceptorspec-commit. - Index merge driver. The index travels with lane branches, so concurrent additions
conflict on
decisions/index.json. Aspec-kitty-decision-indexdriver (consolidation/drivers.py::union_decision_index, run byrun_decision_index_driver, commandspec-kitty merge-driver-decision-index) takes the keyed union ofentriesbydecision_id; a same-id collision resolves with the fold precedence indecisions/index_fold.py, so a terminal status beatsopen.DM-*.mdneeds no driver: each file is ULID-named and written once. The registration surfaces are.gitattributes(kitty-specs/**/decisions/index.json merge=spec-kitty-decision-index), theinitseed, and the migrationm_4_0_0rc5_decision_index_merge_driverfor already-initialised clones. The planning-recency resolver skips driver-covered paths so it cannot overwrite the union result. Thetest_merge_reconciliation_class_guard.pysingle-writer ruling fordecisions/was amended to say so. - Fix-forward for pre-fix Missions. A Mission whose ledger was committed only on the
coordination branch is reported by
doctor decisionsasDECISION_LEDGER_ONLY_ON_COORDINATION.doctor decisions --repaircopies the missingDM-*.mdfiles and merges the index entries into the PRIMARY ledger additively (sameunion_decision_index, under the ledger's lock), and creates no commit; the operator commits withspec-commitoraccept. - Teardown refusal. The bookkeeping projection excludes PRIMARY kinds, so a
coordination-only ledger would be destroyed with the coordination triple. Both
coordination/teardown.py::teardown_coordination_topologyand the consolidation preflight (consolidation/executor.py::_pre_mutation_safety_preflight) therefore refuse withCOORDINATION_LEDGER_UNREPAIREDuntil the ledger is repaired. Nothing is mutated on refusal. - Fork posture.
doctor decisionsandagent decision verifydetect a forked decision event stream (DECISION_LOG_FORKED) from refs and worktrees through one detector,decisions/fork.py::detect_decision_forks.--repairnever drops an entry whose events exist on either surface and never re-sequences a forked log (C-003); on a fork it prints the reconcile steps and exits 1.
Consequences
Positive
- The taxonomy, the writer and the committers agree;
spec-commitno longer forks the ledger onto the coordination branch. - A committed ledger lands on the target branch and survives coordination teardown.
- Concurrent lane additions to the index merge without losing an entry.
Negative
- Behaviour change for coordination-routed Missions: an uncommitted ledger now blocks the consolidation dirty gate instead of being reset. That is intended; the message names the committers.
- Every reader that classified
decisions/as residue flipped (commit router grouping, accept gate, retrospect, record-analysis, implement, move-task, auto-rebase, rollback, ordering, workspace teardown, bulk-edit diff check, and others). Non-coordination topologies (lanes,single_branch) keep today's verdicts (C-008).
Neutral
- Decision events and the status log are untouched; the single-writer posture still holds for them.
Confirmation
- Focused reader tests for each flipped site, the red-first characterisation for the non-coordination topologies, and the merge-driver round-trip test.
tests/integration/test_coord_single_home_workflow.py, whose accept leg lands an uncommitted ledger on the target branch tip.tests/architectural/test_merge_reconciliation_class_guard.pyand the write-surface placement guard, both updated in the same Mission.
Supersession
This ADR reverses the classification half of #3928 for DECISION_LEDGER only. #3928's
other classifications stand. It does not supersede
ADR 2026-09-24-2, whose list of
coord-partition kinds carries a dated pointer to this ADR.
Links
- Related: ADR 2026-06-19-1, its 2026-10-01 amendment (the single-home write rule).
- Seam page: The Artifact Placement Seam,
"Worked example:
DECISION_LEDGERmoved COORD to PRIMARY (2026-10-01)". - Research:
kitty-specs/coord-artifact-single-home-01M3V4BE/research.md(D12 to D15). - Contract:
kitty-specs/coord-artifact-single-home-01M3V4BE/contracts/doctor-decisions-fork-report.md.