the-session-domain-is-written-once — The session lifecycle is written by the typed domain, and the shell only parses and renders
Each lifecycle transition has one implementation in the typed domain; the shell commands keep their arguments, output and exit codes and delegate; a closed record states whether it is complete.
PLANNED 0 of 9 issues done
- ready
- 1
- blocked
- 8
- active
- 0
- verify
- 0
- done
- 0
- cancelled
- 0
Problem
ADR 0047 built the typed session domain (apps/majordomus-cli/src/session/) additively and recorded a six-step cutover; none of the steps has happened. At 707a04c the lifecycle is still written by lib/session.sh (1341 lines), lib/capture.sh (2821 lines; wc -l at 707a04c), lib/checkpoint.sh and lib/handover.sh, while session/store.rs has a close() no command calls. Freshness exists twice (mj_freshness in lib/common.sh and Freshness in capability/builtin/continuity.rs, both reading session.freshness), and continuity resolution exists twice (mj_resolve_latest and continuity.rs). Records still close with tasks: [] and issues: [] when a session worked on a task, because the task an episode began on is not recorded as a ledger fact of that episode when it was started in an earlier one. Proposed in ADR 0182 (A closed session record is judged by what it can attribute, not only by its schema) (proposed; it carries no authority until the owner accepts it). Sources: chatgpt-majordomus:c097#2, chatgpt-majordomus:c116#7.
Outcome
Each lifecycle transition has one implementation in the typed domain; the shell commands keep their arguments, output and exit codes and delegate; a closed record states whether it is complete.
Current state
Verified on master 707a04c: exactly-once close is enforced in shell under a lock (case 220_exactly_once_close, I1503 DONE); the record body is composed from the ledger (.ai/repo/sessions/README.md, session.record_sections); the six-day-outage fixture is case 130_lifecycle_survives_a_stale_task; per-provider episodes are case 111. The automation inventory classifies the session libraries as disposition A; that label is hand-authored and shell check validates only its form. Cases that need the executable skip without one unless the run passes --no-skips (test/run.sh). No completeness verdict exists.
Desired state
ADR 0047 steps 1-6 landed one PR each; lib/session.sh, lib/checkpoint.sh and lib/handover.sh are argument parsing and rendering; one freshness function and one resolver; a completeness verdict derivable for every closed record and shown where a resuming session reads.
Scope
- apps/majordomus-cli/src/session
- apps/majordomus-cli/src/continuity
- apps/majordomus-cli/src/capability/builtin/continuity.rs
- apps/majordomus-cli/src/commands
- lib/session.sh
- lib/capture.sh
- lib/checkpoint.sh
- lib/handover.sh
- lib/common.sh
- .claude/hooks
- .ai/repo/automation/inventory.jsonl
- share/schemas/majordomus/session-record
- docs/CONTINUITY.md
- lib/rust_bin.sh
- lib/derive.sh
- lib/changed.sh
Out of scope
- A second session store or a session/v2 schema island
- Changing what a record contains beyond the completeness verdict and the task snapshot
- Prompt capture, which already joins episodes and redacts (lib/capture.sh)
Acceptance criteria
- Each of ADR 0047's six cutover steps is one landed change, each independently revertible
- bash test/run.sh --no-skips 54_session_lifecycle_hooks 60_session_lifecycle 61_session_envelope 62_session_divergence 63_session_records 111_session_per_provider 130_lifecycle_survives_a_stale_task 135_session_store_recovery 220_exactly_once_close passes after every step
- No lifecycle semantic remains in shell, proved by I3145's structural case; the inventory disposition follows the cutover and is not its proof
- Every closed record's completeness verdict is derivable, never stored, and reaches the surfaces a resuming session reads
Validation
- bash test/run.sh --no-skips 54_session_lifecycle_hooks 60_session_lifecycle 61_session_envelope 62_session_divergence 63_session_records 111_session_per_provider 130_lifecycle_survives_a_stale_task 135_session_store_recovery 220_exactly_once_close
- majordomus plan validate
Risks
- Several workers edit lib/*.sh at once; ADR 0047 warns a cutover on such a day is a semantic merge conflict. Claim each step on the mesh before starting.
- Hook shims must stay fail-open for the provider (inventory facts for .claude/hooks/*).
Issues
Each one is an execution contract with its own acceptance criteria, validation command and required evidence. The wave column is the layer of the dependency graph it sits in.
| issue | status | wave | depends on | title |
|---|---|---|---|---|
| I3141 | BLOCKED | 1 | I3320 | majordomus session close writes its record through the typed domain's closer, and the shell closer is deleted |
| I3142 | BLOCKED | 2 | I3141, I3320 | An episode opens through the typed domain and records the task and issue it began on |
| I3143 | BLOCKED | 3 | I3142, I3320 | Each provider lifecycle event leaves a typed receipt written by the domain before any guard can return early |
| I3144 | BLOCKED | 3 | I3142 | Checkpoints and handovers are recorded against the episode, and the shell only parses and renders them |
| I3145 | BLOCKED | 4 | I3143, I3144 | Continuity is resolved and judged fresh by one function, and the shell's copies are deleted |
| I3146 | BLOCKED | 3 | I3142 | A closed session record says whether it is complete, and one that changed the tree without saying what it served is not |
| I3311 | BLOCKED | 3 | I3142 | Generated sequences of episode events never move an episode in a way its transition table forbids |
| I3313 | BLOCKED | 4 | I3146 | A failing verification in a closed session record says whether the ledger already held it failing at the task's base |
| I3320 | READY | 0 | — | A lifecycle command run against an absent or stale executable fails loudly and never falls back to a shell writer |
The graph
flowchart LR
I3141["I3141<br/>majordomus session close writes its record through the typed domain's closer, and the shell closer is deleted"]:::blocked
I3142["I3142<br/>An episode opens through the typed domain and records the task and issue it began on"]:::blocked
I3143["I3143<br/>Each provider lifecycle event leaves a typed receipt written by the domain before any guard can return early"]:::blocked
I3144["I3144<br/>Checkpoints and handovers are recorded against the episode, and the shell only parses and renders them"]:::blocked
I3145["I3145<br/>Continuity is resolved and judged fresh by one function, and the shell's copies are deleted"]:::blocked
I3146["I3146<br/>A closed session record says whether it is complete, and one that changed the tree without saying what it served is not"]:::blocked
I3311["I3311<br/>Generated sequences of episode events never move an episode in a way its transition table forbids"]:::blocked
I3313["I3313<br/>A failing verification in a closed session record says whether the ledger already held it failing at the task's base"]:::blocked
I3320["I3320<br/>A lifecycle command run against an absent or stale executable fails loudly and never falls back to a shell writer"]:::ready
I3141 --> I3142
I3142 --> I3143
I3142 --> I3144
I3142 --> I3146
I3142 --> I3311
I3143 --> I3145
I3144 --> I3145
I3146 --> I3313
I3320 --> I3141
I3320 --> I3142
I3320 --> I3143
classDef done stroke:#16a34a,fill:#dcfce7,color:#14532d,stroke-width:2px
classDef active stroke:#2563eb,fill:#eff6ff,color:#1e3a8a,stroke-width:2px
classDef verify stroke:#7c3aed,fill:#f5f3ff,color:#4c1d95,stroke-width:2px
classDef ready stroke:#0891b2,fill:#ecfeff,color:#164e63,stroke-width:2px
classDef planned stroke:#0891b2,fill:#ecfeff,color:#164e63,stroke-width:2px
classDef blocked stroke:#b45309,fill:#fffbeb,color:#78350f,stroke-width:2px
classDef cancelled stroke:#6b7280,fill:#f9fafb,color:#374151,stroke-width:2px
classDef superseded stroke:#6b7280,fill:#f9fafb,color:#374151,stroke-width:2pxLinked from this diagram (9)
- I3141 majordomus session close writes its record through the typed domain's closer, and the shell closer is deleted
- I3142 An episode opens through the typed domain and records the task and issue it began on
- I3143 Each provider lifecycle event leaves a typed receipt written by the domain before any guard can return early
- I3144 Checkpoints and handovers are recorded against the episode, and the shell only parses and renders them
- I3145 Continuity is resolved and judged fresh by one function, and the shell's copies are deleted
- I3146 A closed session record says whether it is complete, and one that changed the tree without saying what it served is not
- I3311 Generated sequences of episode events never move an episode in a way its transition table forbids
- I3313 A failing verification in a closed session record says whether the ledger already held it failing at the task's base
- I3320 A lifecycle command run against an absent or stale executable fails loudly and never falls back to a shell writer
Canonical record: .ai/repo/project/milestones/the-session-domain-is-written-once.yaml. Read it back with majordomus plan show the-session-domain-is-written-once.