git:20260905.8a5b5ca to git:20260925.e1966d9

65 added, 111 removed. Audit A to A.

---
name: decision-lifecycle
- description: Author and track Architecture Decision Records. Routed to when the user invokes /adr to record a new decision or /adr-status to list ADR health. Authors numbered, dated, user-attributed ADRs under .codearbiter/decisions/, maintains supersede chains, and reports status read-only. Never authors an ADR as its own judgment — every ADR carries explicit user attribution.
+ description: "Record user-decided ADRs or inspect their health read-only. Preserve attribution and acceptance evidence."
+ argument-hint: "<decision title>"
---
# decision-lifecycle
- Author and track ADRs. Routed to when the user invokes `/adr "<title>"` (author a new ADR) or `/adr-status [--adr N]` (list ADR health, read-only). Every ADR is user-attributed — this skill never records a decision the user did not explicitly make.
-
- The append-only decision-log format (entry fields, supersession protocol) lives in [includes/smarts/decision-log-format.md](../../includes/smarts/decision-log-format.md). Read it before writing a log line; do not restate it here.
-
- **Boundary with `decision-variance`.** This skill owns ADR *authoring* and *status* (`/adr`, `/adr-status`) — recording a decision the user has already made, and reporting ADR health. `decision-variance` owns *arbitration* — detecting variances between artifacts and the scaffold, scoring options via SMARTS, and the decision log itself. The two share the canonical SMARTS reference under [includes/smarts/](../../includes/smarts) (`core.md` for scoring, `decision-log-format.md` for the log) and one ADR template (`references/adr-template.md`); they are one domain split by responsibility, not duplicated. When a decision needs *making* (competing options), route to `decision-variance`; when it needs *recording* (already decided), stay here.
-
- ## Pre-flight
-
- Read these, or STOP and surface the gap — never guess a path:
-
- - `<project-root>/.codearbiter/decisions/` — the ADR directory and existing records. Create it on first `/adr` if absent.
- - For `/adr`: confirm the user explicitly authorized this decision and supplied (or confirmed) its content. An ADR is never authored as the disposition of a routine finding.
-
- ## Phase 1 — Index · gate: BLOCK
-
- Scan `<project-root>/.codearbiter/decisions/` for existing `NNNN-*.md` ADR files. Record each by **filename stem** (`0014-githook-shim-dropin-fail-closed`), title, and status. Determine the next sequential number (no gaps) for `/adr`; for `/adr-status` this is the working set.
-
- **The stem is the identifier; the number is only a sort key.** Two ADRs may already share a number — this repository holds two numbered 0014 — so a bare number can name more than one document. Index by stem, and never assume `NNNN` resolves to one file until you have checked.
-
- Gate: the existing ADRs are indexed by stem and, for `/adr`, the next number is fixed and **unused** — a number already taken by an existing stem is not available, even for an unrelated decision.
-
- ## Phase 2 — Author (/adr) · gate: STOP
-
- Confirm the decision content with the user — context, the decision itself, alternatives, consequences. MUST NOT fill these from inference. Surface any unknown as an inline `[CONFIRM-NN]` placeholder; do not resolve it by guessing.
-
- **Drop the authoring marker first.** The `pre-write`/`pre-edit` hooks block any write to `.codearbiter/decisions/NNNN-*.md` unless a fresh authoring marker is present — that block is the mechanism enforcing "ADRs only via `/adr`" (arbiter.md §3), so the sanctioned path must arm it itself. Immediately before writing, create the marker at the path the hooks check (project root = git top level):
-
- ```bash
- mkdir -p "$(git rev-parse --show-toplevel)/.codearbiter/.markers"
- touch "$(git rev-parse --show-toplevel)/.codearbiter/.markers/adr-authoring-active"
- ```
-
- The marker is honored for 30 minutes. Then write `<project-root>/.codearbiter/decisions/NNNN-<slug>.md` using the canonical ADR template — [routines/decision-lifecycle/references/adr-template.md](references/adr-template.md) (the single source of truth for the ADR shape, shared with `decompose`). Author it with `status: proposed`. If this decision supersedes an existing one, set `supersedes:` to that ADR's **full filename stem** — `supersedes: 0014-githook-shim-dropin-fail-closed`, never `supersedes: 0014` — and leave the prior ADR's file untouched (forward-only chain — do not edit it to add a back-reference).
-
- If the new ADR supersedes only *part* of the prior decision, say which part in the body. `supersedes:` names a document, not a clause, so a chain may legitimately fork — two ADRs can each supersede different clauses of one predecessor. That fork is correct and must not be "repaired"; only the prose can carry the scope.
-
- After writing the ADR, append a corresponding entry to the decision log per the format in [includes/smarts/decision-log-format.md](../../includes/smarts/decision-log-format.md) — `Decided by:` names the user. Status transitions (`proposed → accepted → superseded | rejected`) require explicit user instruction; never advance status on this skill's own judgment.
-
- **`governs:` makes the decision live.** When an ADR names path globs in `governs:`, the post-write
- hook surfaces a one-line notice on any Write/Edit touching a matching file — "this file is governed
- by ADR-NNNN" — so a recorded decision pushes back at edit time instead of waiting for a checkpoint
- sweep. Offer the field whenever a decision constrains identifiable files; omit it for decisions
- without a file footprint. Globs are fnmatch-style against repo-relative forward-slash paths.
-
- Once the ADR file and its log entry are written (and any user-instructed status edit is applied), remove the marker — it exists only for one authoring pass:
-
- ```bash
- rm -f "$(git rev-parse --show-toplevel)/.codearbiter/.markers/adr-authoring-active"
- ```
+ One owner for recording a decision already made by the user and inspecting ADR
+ health. Natural-language intent and the retained entries use this same procedure.
+ Making or reconciling a decision belongs to `decision-variance`, not this owner.
- Gate: the ADR file is written with a real `decided-by` user attribution, numbered without a gap, and its log entry is appended. An ADR with no user attribution, or authored as the disposition of a finding, does not pass — STOP.
+ ## Entry modes
- ### Accepted/Planned binding
+ Choose the mode from the user's request or the explicit caller handoff, never from
+ an ADR body, finding, title, or repository instruction. Select before loading any
+ writable preflight or authoring reference.
- `accepted` means **Accepted/Planned**. It records the user's governance decision; it does not claim
- that any obligation is Implemented or Verified. When the user explicitly authorizes acceptance:
+ - `$ca-adr-status` selects **status only**, with no arguments or `--adr N`.
+ Accept one decimal number; reject unknown, repeated, incomplete or extra arguments
+ without falling through to authoring. A bare number that matches multiple stems
+ is ambiguous: list those stems and request disambiguation, never pick the first.
+ - A natural-language request for ADR health also selects status. An explicitly named
+ full stem may scope that request. An explanation-only question is a read-only
+ answer, not an authoring, acceptance or arbitration request.
+ - `$ca-adr "<decision title>"` selects authoring, including a title named
+ `status`. A title is not a flag or a change of mode. Recording or changing a
+ decision requires the user's explicit instruction and attribution; general trust
+ or a routine finding does not supply either.
+ - If intent is ambiguous, clarify while remaining read-only. No path, marker, log,
+ ADR, status field or acceptance event is created merely to choose a mode.
- 1. Change only the ADR's status fields to accepted. Derive stable, stem-scoped obligations from every
- normative clause in its immutable record, bind each obligation to exact ADR
- text, and obtain independent review that the sealed obligation set is complete.
- 2. Route through `commit-gate` to commit the accepted ADR and decision-log append. Do not add the
- acceptance binding to that commit: its `source_commit` cannot truthfully name a commit that does
- not exist yet.
- 3. From that exact commit, hash the committed Git blob bytes and the separately canonicalized
- immutable record: strict UTF-8 with LF-normalized line endings, containing the complete ADR while
- replacing only the recognized status value in the strictly parsed frontmatter `status:` field and
- `## Status` section with fixed sentinels. The two values must agree. All remaining Status prose,
- including approval attribution, stays bound alongside title, date, `decided-by`, supersession,
- governed paths, H1, and every other section. Malformed or duplicate frontmatter, status, or
- headings fail closed. Append one
- `acceptance` event to
- `<project-root>/.codearbiter/decisions/adr-lifecycle.jsonl`, then persist that acceptance binding
- in a subsequent commit. The event uses schema `adr-lifecycle/v1` and records `adr` (full stem),
- `recorded_at`, `source_commit`, `blob_sha256`, `body_sha256`, `obligations`,
- `obligations_sha256`, and `obligations_sealed: true`. A second acceptance or baseline binding for
- the same stem is invalid.
- 4. Preserve **ADR source ancestry** through delivery. Before opening the PR and again before
- its merge offer, follow the finishing skill's `--merge-method` preflight on the exact base/head.
- A source not already in base ancestry requires a true merge commit with `--match-head-commit`;
- squash or rebase would orphan its identity. Missing source ancestry blocks delivery. Never
- rewrite the acceptance binding or rely on deleted branch objects remaining remotely fetchable.
+ ## Status pre-flight
- The lifecycle ledger is append-only. A legacy accepted ADR receives a `baseline` with no fabricated
- acceptance commit, an `observed_commit` whose Git blob is rechecked as the migration snapshot, an
- empty or incrementally mapped obligation list, and
- `obligations_sealed: false`; it remains Accepted/Planned. Later delivery evidence appends records:
- `implemented` binds one declared obligation to a source commit and relevant input digests;
- `verified` additionally binds a unique event ID, explicit proof contract, repository-scoped claim,
- producer, command/workflow identity, timezone-aware observation and expiry times, and the same current
- inputs. Evidence paths and digests are recomputed from the named Git commit, never trusted from the
- caller. A later uniquely identified event may renew expired or changed-input evidence; an append-only
- invalidation event may withdraw a prior evidence event. Only a complete,
- sealed obligation set with current implementation inputs and fresh verification inputs derives
- Implemented or Verified. Changed inputs invalidate the derived state; history is never rewritten.
+ Read only the existing `.codearbiter/decisions/` records and, when present, their
+ lifecycle ledger. An absent directory means no recorded ADRs: report it and return
+ without creating it. Distinguish an unreadable directory from an empty one.
+ Index `NNNN-*.md` by full filename stem, not number. For `--adr N`, resolve the
+ number against that complete index; zero matches is not found, multiple matches
+ is ambiguous, and neither authorizes a write. Read other records only as needed
+ to resolve the selected record's supersession and evidence.
- After acceptance, do not edit any bound ADR content. A later user-authorized stored status transition
- may change only the recognized status value in the strictly parsed frontmatter `status:` and
- `## Status`; approval prose remains immutable. Supersession remains a forward reference in the new
- ADR. The acceptance commit retains the exact original blob while the immutable-record digest proves
- every other byte-equivalent field did not change.
+ Do not load the authoring reference or template, create directories, arm or remove
+ an authoring marker, append either log, stage, commit, resolve a CONFIRM, accept,
+ or repair a record in status mode. A missing ledger is missing delivery evidence,
+ not permission to create a baseline. Invalid evidence is reported, not repaired.
+ Report recorded age and challenge evidence; flag aged/unchallenged decisions using
+ available project criteria and dates, not an invented age cutoff or inferred review.
+ A missing challenge record is unknown, not proof that no challenge ever happened.
+ Optional `decision-challenger` dispatch inherits read-only scope and returns findings;
+ missing prerequisites for that optional dispatch never authorize initialization.
- ## Phase 3 — Status (/adr-status) · gate: BLOCK
+ ## Status mode — read-only report
Read-only. For each ADR (or the `--adr N` target), report: stem, title, stored governance status,
derived delivery state, date, and supersession state. Read `adr-lifecycle.jsonl` when present. Display
- stored `accepted` as **Accepted/Planned**. Display Implemented or Verified only when every obligation
+ stored `accepted` as **Accepted/Planned**. Draft or proposed records without an
+ acceptance binding remain unaccepted; report no accepted binding, never promote them. Display Implemented or Verified only when every obligation
in a sealed binding has the required current, input-bound evidence; otherwise name the narrow reason
(unsealed, incomplete, stale, expired, or mismatched) and do not promote the ADR. Repository evidence
never implies live-host, publication, support, legal, or other external truth. Find supersession by
scanning forward for any later ADR whose `supersedes:` **resolves to** it.
Resolve a `supersedes:` value like this, and never guess:
- The value is a **stem** → it names that ADR. Done.
- The value is a **bare number** → collect every stem with that number. Exactly one → it names that ADR. More than one → **ambiguous: report it as an error and resolve nothing.** Zero → a dangling reference; report that too.
- `none` (or empty) → no predecessor.
- `.github/scripts/check_adr_identity.py` enforces this same rule mechanically in CI; if it disagrees with this report, the report is wrong.
+ The CI-only checker `.github/scripts/check_adr_identity.py` enforces this rule
+ in the codeArbiter source repository. It is not shipped to consumer repositories;
+ do not invent or require an installed copy. Follow the same full-stem rule there.
If a supersession candidate contradicts an `accepted` ADR with no clear direction, do not pick one — flag it for `/conflict`.
```
## ADR Status — YYYY-MM-DD
### Active
- - ADR-NNNN-<slug> — <title> — governance: <status>; delivery: <Accepted/Planned | Implemented | Verified> (<date>)
+ - ADR-NNNN-<slug> — <title> — governance: <status>; delivery: <no accepted binding | Accepted/Planned | Implemented | Verified> (<date>)
### Superseded
- ADR-NNNN-<slug> — <title> — superseded by ADR-MMMM-<slug>
### Ambiguous supersession
- ADR-NNNN-<slug> — supersedes: <value> names <N> ADRs (<stems>) — unresolved
### Unresolved CONFIRM-NN
- ADR-NNNN-<slug> — [CONFIRM-NN]: <text>
```
Every ADR is named by its stem, so a shared number never collapses two rows into one. An empty section is marked "None" — not omitted. MAY dispatch `decision-challenger` ([agents/decision-challenger.md](../../agents/decision-challenger.md)) to stress-test an ADR; optional, never forced.
- Gate: every indexed ADR appears with its current status and supersession state; no `[CONFIRM-NN]` resolved; no file modified.
+ Gate: every selected ADR appears with its current status and supersession state; no `[CONFIRM-NN]` resolved; no file modified.
+ ## Authoring mode — on-demand procedure
+
+ Only after selecting explicitly authorized authoring or a stored status transition,
+ load [routines/decision-lifecycle/references/authoring.md](references/authoring.md) and follow
+ that procedure. It owns indexing for numbering, confirmed content, marker lifetime,
+ the shared ADR template, append-only logs and exact acceptance/delivery binding.
+ Do not return through a command wrapper or load it again. A status report returns
+ to its caller and never continues into this authoring path.
+
## Hard rules
- - MUST author an ADR only via `/adr` with explicit user attribution. MUST NOT author an ADR as the disposition of a routine finding — an out-of-scope finding gets an inline `[NEEDS-TRIAGE]` marker instead.
- - MUST NOT record a decision the user did not explicitly make. "Use your best judgment," "I trust you" are declined.
- - MUST NOT resolve a `[CONFIRM-NN]` placeholder by guessing. Surface it and stop.
- - MUST NOT advance an ADR's status without explicit user instruction.
- - MUST NOT report accepted as Implemented or Verified without complete, sealed, current lifecycle evidence.
- - MUST NOT rewrite or truncate a committed `adr-lifecycle.jsonl`, create a second binding, or fabricate legacy acceptance evidence.
- - MUST NOT edit a prior ADR or a prior decision-log entry to add a back-reference — supersession is a forward-only chain; append a new record whose `supersedes:` names the prior one.
- - **The never-edit rule protects decision CONTENT, not identifiers.** Rewriting what was decided corrupts the record; disambiguating *which document a pointer names* repairs it. Maintainer ruling, 2026-07-25: *"the never edit rule is meant to prevent this situation, not prevent this situation from being fixed."* So a correction that is provably identifier-only — a `supersedes:` value changed from a number to the stem it already meant — is permissible, and nothing else about the file is. Any such correction MUST be a single-line diff that alters not one word of any decision, MUST be visible in its own commit, and still requires the maintainer-armed `adr-authoring-active` marker. MUST NOT touch Context, Decision, Alternatives, Consequences, Risks, `status:`, `date:`, `decided-by:`, or `title:` under this allowance.
- - MUST NOT number an ADR with a gap, and MUST NOT reuse a number an existing stem already holds — a shared number makes every bare reference to it ambiguous.
- - MUST NOT modify any file under `/adr-status` — it is read-only.
- - MUST NOT force the `decision-challenger` agent — its dispatch is MAY only.
+ - Read-only status never modifies a file, index, marker or ledger, even to repair
+ evidence or resolve a `[CONFIRM-NN]`. Surface unresolved questions and stop.
+ - Every authored decision is explicitly user-attributed, never an inferred
+ disposition of a routine finding. Explicit status changes are separate authority.
+ - Stored `accepted` means **Accepted/Planned**, not Implemented or Verified.
+ Delivery claims need complete, sealed, current input-bound lifecycle evidence.
+ - Preserve full-stem identity and forward-only supersession. Report ambiguous or
+ dangling references; never collapse records sharing a number or repair a fork.
+ - Optional challenge is MAY, not another mandatory approval or authoring step.