From e90fb2c913a8732b5bf298931a6ede4be6bdb863 Mon Sep 17 00:00:00 2001 From: REPPL <77722411+REPPL@users.noreply.github.com> Date: Sat, 10 Oct 2026 17:21:54 +0100 Subject: [PATCH] docs: correct drift the v0.13.4 crosscheck found in the surface chapters The v0.13.4 release-gate brief/surface crosscheck (content commit b59cad909) reported 21 findings in the 04-surfaces chapters. Each was re-located at origin/main and checked against the code before any edit: 20 fixed, 1 refuted, 0 already true, none skipped for the verb split. Fixed: embark's lock order and spec-mint race, and where its conflict report is rendered; intent's backing-intent locations, bundle example and edge-finding count; capture migrate's checkout refusal; memory's itd-36 location; the abcd board's plugin forms and itd-20 location; docs' place in the bare-invocation exceptions; history's redacting write paths; lint site's render-when-absent wording; the guard hook's stdout; update's unpublished-build receipt; implement's run-state layout and outage probe exit codes; the source ledger's used_in and corrects fields; the build receipt's handback field. Refuted: 15-prepare-this-repo's claim that `abcd prepare-this-repo` is an unknown command is true of the binary. The finding is a defect in the implement loop's brief-stage refusal text, which names that verb, not drift in the brief. Refs: iss-2609091956001547 Assisted-by: Claude:claude-opus-5-5 --- .../brief/04-surfaces/03-embark.md | 27 ++++++++++--------- .../brief/04-surfaces/05-intent.md | 10 +++---- .../brief/04-surfaces/06-capture.md | 3 ++- .../brief/04-surfaces/07-memory.md | 2 +- .../development/brief/04-surfaces/08-abcd.md | 6 +++-- .../development/brief/04-surfaces/10-docs.md | 4 +-- .../brief/04-surfaces/11-history.md | 10 ++++--- .../development/brief/04-surfaces/16-lint.md | 2 +- .../development/brief/04-surfaces/17-guard.md | 5 ++-- .../brief/04-surfaces/21-update.md | 4 +-- .../brief/04-surfaces/27-implement.md | 8 ++++-- .../brief/04-surfaces/33-source.md | 5 ++-- .../development/brief/04-surfaces/34-build.md | 5 +++- 13 files changed, 54 insertions(+), 37 deletions(-) diff --git a/.abcd/development/brief/04-surfaces/03-embark.md b/.abcd/development/brief/04-surfaces/03-embark.md index 8ccfa87de..08e921fbd 100644 --- a/.abcd/development/brief/04-surfaces/03-embark.md +++ b/.abcd/development/brief/04-surfaces/03-embark.md @@ -23,7 +23,7 @@ whose bytes already match is an idempotent skip, so a re-run is a clean no-op. > **Recovery humility.** The lifeboat is the highest-fidelity floor the originating session could leave behind; it is not the activity that produced it. **When something here does not make sense, hunt the originating session before trusting the lifeboat blindly**: ask the prior author, surface the conversation where the decision happened, look at the rejected alternatives. See [`01-product/03-mental-model.md § The Naurian gap`](../01-product/03-mental-model.md#the-naurian-gap--modification-axis). -> **Ownership** ([adr-33](../../decisions/adrs/0033-launch-phase-ownership-tiered.md)): the lifeboat round-trip belongs to the lifeboat pipeline, whose original plan the retired [Phase 6](../../roadmap/phases/phase-6-lifeboat.md) document holds. [adr-35](../../decisions/adrs/0035-lifeboat-as-coverage-experiment.md) is the model of record, and it settled four things against this chapter's earlier design: the voyage log is operator-level rather than in-tree, there is no in-tree lifeboat home and no shorthand for one, writes go through `os.Root` containment plus independent path validation rather than ordinary file writes, and a refusal path never writes (the core returns the conflicts and the surface renders them). One more, and it is the load-bearing one: **lifeboat text is never injected verbatim into the target's conventions file.** The current marker block is re-injected instead. That is the difference between a data leak and a persistent instruction implant. +> **Ownership** ([adr-33](../../decisions/adrs/0033-launch-phase-ownership-tiered.md)): the lifeboat round-trip belongs to the lifeboat pipeline, whose original plan the retired [Phase 6](../../roadmap/phases/phase-6-lifeboat.md) document holds. [adr-35](../../decisions/adrs/0035-lifeboat-as-coverage-experiment.md) is the model of record, and it settled four things against this chapter's earlier design: the voyage log is operator-level rather than in-tree, there is no in-tree lifeboat home and no shorthand for one, writes go through `os.Root` containment plus independent path validation rather than ordinary file writes, and a refusal path never writes (the core returns the conflicts and a string render of them, and the surface prints it). One more, and it is the load-bearing one: **lifeboat text is never injected verbatim into the target's conventions file.** The current marker block is re-injected instead. That is the difference between a data leak and a persistent instruction implant. ## Sub-verbs @@ -79,14 +79,16 @@ There is no emptiness gate. A target that merely carries unrelated files is not a conflict. Embark refuses only when a planned target path already holds differing bytes, is a non-regular target, is a duplicate target, or sits under a non-directory parent. On any such conflict the core **writes nothing**: it -returns the conflict set it found, and the surface renders it. A refusal that +returns the conflict set it found with a string render of it, and the surface +prints that render or emits the set as JSON. A refusal that writes a file would be a transport-agnostic-core violation. -The write runs under the target's issue-ledger lock (when it writes an issue) -and then its intent store's lock, the order every writer holding both takes, -and every planned write is judged again under them. A record created at a -planned target between the plan and the write — a capture, an intent minted in -the target meanwhile — is a conflict like any other, so it refuses the whole +The write runs under the target's issue-ledger lock (when it writes an issue), +then its intent store's lock, then its spec store's lock (each only when that +store exists), the order every writer holding more than one takes, and every +planned write is judged again under them. A record created at a planned target +between the plan and the write — a capture, an intent or a spec minted in the +target meanwhile — is a conflict like any other, so it refuses the whole write rather than being replaced. A retrospective the reflect verb writes takes the intent store's lock too, so it cannot land inside that window, and every planned write is an exclusive create: a file that lands at its target @@ -144,9 +146,10 @@ scaffolder and no model sit in the write path. ## 4. Conflict UX -The core returns the conflict set and the surface renders it as a **single bulk -report**: one line per conflicting target path with its conflict kind, never a -per-file barrage and never a file written by the core. The shipped unpack writes +The core returns the conflict set and renders it, as a string it never prints, +into a **single bulk report**: one line per conflicting target path with its +conflict kind, never a per-file barrage and never a file written by the core. +The surface prints that report, or emits the set as JSON. The shipped unpack writes nothing on any conflict and exits non-zero. ``` @@ -205,8 +208,8 @@ there is nothing to answer. no special case: the destination is an ordinary explicit path. - **Given** a target holding a file that conflicts with a planned write, **when** the unpack runs, **then** the command refuses, the core returns - the conflict list **without writing any file**, and the surface renders it as - one bulk report. A target that merely holds unrelated files is not a conflict. + the conflict list **without writing any file**, and the surface prints the + core's render of it as one bulk report. A target that merely holds unrelated files is not a conflict. - **Given** the user runs the probe, **when** it completes, **then** the lifeboat is inspected against the target (file tree, schema validation, would-be writes), no target mutation occurs, and the user sees a report ready diff --git a/.abcd/development/brief/04-surfaces/05-intent.md b/.abcd/development/brief/04-surfaces/05-intent.md index 9e0a450fd..39781d967 100644 --- a/.abcd/development/brief/04-surfaces/05-intent.md +++ b/.abcd/development/brief/04-surfaces/05-intent.md @@ -1,6 +1,6 @@ # `/abcd:intent` — Press-Release Intent Capture -> **Delivery state**: the `intent` binary verb ships — bare invocation is read-only status, plus the sub-verbs this chapter's generated appendix lists, and the quoted-text create path `abcd intent ""` (itd-46, itd-80, itd-94). The implement-readiness gate runs eight checks on one intent (bucket, acceptance criteria, mechanism claim, scope conditions, spec link, spec body, the spec's steps, recorded grounds); exit 0 ready / 1 not / 2 fault — and recorded grounds are the one thing it writes: the conjecture behind the gate decision, appended to the intent's `## Grounds` section. The `/abcd:intent` plugin command surface exists (`commands/intent.md`, resolving iss-105) and carries the planning interview an unready intent is routed to. Remaining backing intents sit in `intents/planned/` (itd-27 grill, itd-34 kinds, itd-50 audit loop) and `intents/drafts/` (itd-16, itd-35 — the `/abcd:audit` sub-verbs); delivery state is the intent lifecycle's, not this page's (see the [brief README's provenance note](../README.md)). +> **Delivery state**: the `intent` binary verb ships — bare invocation is read-only status, plus the sub-verbs this chapter's generated appendix lists, and the quoted-text create path `abcd intent ""` (itd-46, itd-80, itd-94). The implement-readiness gate runs eight checks on one intent (bucket, acceptance criteria, mechanism claim, scope conditions, spec link, spec body, the spec's steps, recorded grounds); exit 0 ready / 1 not / 2 fault — and recorded grounds are the one thing it writes: the conjecture behind the gate decision, appended to the intent's `## Grounds` section. The `/abcd:intent` plugin command surface exists (`commands/intent.md`, resolving iss-105) and carries the planning interview an unready intent is routed to. Remaining backing intents sit in `intents/planned/` (itd-50 audit loop) and `intents/drafts/` (itd-16, itd-35 — the `/abcd:audit` sub-verbs); delivery state is the intent lifecycle's, not this page's (see the [brief README's provenance note](../README.md)). abcd uses **intents** in press-release format (Amazon working-backwards) as the unit of forward-looking *user-facing* planning. Intents capture *what user-facing capability exists once shipped*, written in present tense as if already delivered. This is engineered to discipline product clarity before scope creep — a reader of an intent thinks like a product person first, an engineer second. @@ -81,7 +81,7 @@ Every intent has a `kind` declared in frontmatter, set at planning time. Three k | `kind` | Has press release? | Lives in | Maps to | Examples | |---|---|---|---|---| | `standalone` | Yes | `drafts/` → `planned/` → `shipped/` | One spec (1:1) | itd-3, itd-4, itd-7, most of the corpus | -| `bundle-member` | Yes | Same as standalone, with `bundle: ` linking members | Shared spec with bundle-mates (N:1) | itd-20, itd-24, itd-63, itd-69 (bundle `spc-83-operator-surfaces`, in `planned/` — committed but unscheduled, named in no phase doc); see [`intents/README.md`](../../intents/README.md#bundles) | +| `bundle-member` | Yes | Same as standalone, with `bundle: ` linking members | Shared spec with bundle-mates (N:1) | itd-201 and itd-2610030810350727 (bundle `asking-and-layout`, one shared spec, both in `shipped/`); see [`intents/README.md`](../../intents/README.md#bundles) | | `discipline` | **No** — uses `## Rule` instead | `disciplines/` | No spec; imposes acceptance gates on every other spec | itd-1 (AC gate), itd-5 (prompt-quality) | **`kind` is binding once set at plan time.** Late changes go through the reclassify verb (§ 2), which rewrites the kind — and, for a supersession, moves the shelf and writes both directions of the link — in one write, and records the change in the intent's frontmatter `reclassification_history`. **A shipped intent never changes kind:** a rule found after the fact is filed as a discipline that supersedes it. @@ -308,7 +308,7 @@ Later phase — intent-auditor (shape-classification role) scans the corpus |---|---|---| | `/abcd:intent` (no args) | Read-only status: bucket counts (drafts / planned / shipped / disciplines / superseded) with the count of owed intent audits beside them (the owed listing's total, from the same reader), open/closed spec counts, the itd↔spc links, and in the machine-readable form a per-intent listing (id, title, bucket, `ac_state` `real` or `seeded`, and the filing date a timestamp id encodes, null for an ordinal id; iss-242), a ledger-routing hint (`abcd capture "…"` for an observation, `abcd intent "…"` for a user-facing change), and an ideate-routing line (a big, unproven idea? `abcd ideate` runs the optional admission gauntlet and records the verdict either way) | — | | `/abcd:intent ""` | **Canonical create** (spc-30 (predecessor store)/itd-46): a leading quoted seed is the canonical create entry. Seeds a draft skeleton whose `## Press Release` is the quoted text as prose, under an H1 derived from the text's first sentence (cut on a word boundary at the slug cap) or given as a title — one line, non-empty, redacted like the text — with Why This Matters and Acceptance Criteria seeded as prompts for the human to fill; assigns `itd-N` and derives the slug from the text; writes `suggested_kind: null`. An optional impact (additive, breaking or fix) stamps the draft's product impact at create time, and an optional production mode (hand-written, dictated-and-formatted or scribe-transcribed) stamps how its text was produced (itd-178); the draft's `origin` carries no flag and is derived from the verb that ran. Before the draft is written, under the store's mint lock, its title and press release are matched against every open and resolved issue and every intent by the term-overlap heuristic `/abcd:capture` uses (itd-2609212137116617): a record at or above `match.threshold` is written onto the draft as `duplicates:` or `refines:`, at most three links, and the output lists the near misses below it with their scores; the match never refuses the create. A leading quote always creates — never falls through to bare render | writes to `drafts/itd-N-.md` (no spec created) | -| The grill step, on one intent id | Socratic adversarial interview that stress-tests an intent for vagueness, missing acceptance, hidden assumptions before planning. Glossary-aware once `terminology/` exists. A brief-section mode would stress-test a brief section instead. (per itd-27, `intents/planned/` — a later phase; no grill sub-verb ships yet) | (stays in current state) | +| The grill step, on one intent id | Socratic adversarial interview that stress-tests an intent for vagueness, missing acceptance, hidden assumptions before planning. Glossary-aware once `terminology/` exists. A brief-section mode would stress-test a brief section instead. (per itd-27, `intents/superseded/`, superseded by itd-94; no grill sub-verb ships) | (stays in current state) | | Pre-pass (one draft id) | **The input of the pre-pass before the planning interview** (itd-42, spc-2609211918551301; itd-84's next rung). Assembles the four inputs and nothing else, which is the budget: the draft, the brief's numbered invariants (`02-constraints/03-invariants.md`), each principle's rule paragraph, and a one-line index of every other intent (id, title, shelf) on every shelf. It hands the host the four answers an overlap is asked with (keep both, bundle, supersede, refine), the rules its findings are held to, and a digest over all of it. It refuses, on exit 2 with its remedy, an id that is malformed, absent, or not on `drafts/`; a missing invariants register or principles directory degrades the pass with a warning. The judgement is the host's (adr-25): the plugin page's interview runs it before its first question. | (writes nothing) | | Pre-pass brief (one draft id, a findings JSON path) | Validates the host's findings fail-closed against the input as it stands (the type, the intent, the digest recomputed, the shape and its caps; a symlinked or oversized file is refused) and writes the planning brief to `.abcd/.work.local/scratch/planning-briefs/.md`: the summary back, the decomposition as an ungraded proposal, and numbered questions, each saying where its answer lands. A conflict is written as one only when the invariant or principle it names exists and both quotes occur in the anchor and the draft verbatim; an overlap names a sibling from the index and is asked with the four answers, with any recommendation and its reason in prose beside the question, never as a marked option; a concern the binary cannot anchor is written as a question marked unanchored, with the reason. A brief at that path without the pre-pass's marker line was written by hand and is never replaced. Every refusal exits 2 with nothing written. | (writes the brief only) | | Planning interview in a plain Terminal (one intent id) | **The planning interview with no host session** (spc-2610030911534855). The `planning-interviewer` runs on the runner the person routed it to in their own machine's configuration, once per turn: each turn's brief carries the seed (the intent, its record's path, its folder, and the planning brief when the pre-pass wrote one), the asking rules and the answers so far, and the role returns the next question as a receipt of the shared question type, or `done` with a summary of what it changed. After each answer it edits the intent record with its contract's tools (Read, Edit, Grep, Glob), as the host agent does, and never performs the plan act. abcd checks each question before it is drawn (sanitised, then the structural check and the asking limits), draws it when stdin, stdout and stderr are all terminals, records each answer in the answers record in the local tier, and ends by reporting the readiness gate on the record. Refused before anything runs, exit 2 and nothing written: an id that is not a draft or a planned intent, no route of the person's to a runner (naming the role's key, the machine's file and the setup interview), and a run off a terminal with no answers file; with one, questions are answered by ordinal (`Q1`, `Q2`, ...) and the file running out refuses, exit 2, recording nothing. A question that fails the check is the runner's invalid answer, recorded as a fallback, and with no configured host the interview stops, exit 1, keeping the answers given. The role may change the intent's record and nothing else: abcd reads the working tree's state (git's listing of what differs from HEAD, each path's content hashed; every path git ignores, the local tier among them, by its mode, size and modification time; git's own hooks, info files, configuration, HEAD and refs, and each submodule's, and every worktree's entry under `worktrees/` (its `HEAD`, `commondir`, `gitdir`, `config.worktree` and `locked`, an entry made or removed noticed), read in the repository's common git directory and where a link there leads, any hooks directory `core.hooksPath` names outside the tree, read where its links lead, and the push receipts in the local tier of every worktree git lists, each hashed; left out are only the run's own turn directory, where the role writes its receipt, a checkout's local transcript store, a file named `.DS_Store` anywhere and `.claude/scheduled_tasks.lock` at the root, which execute nothing, every other `.claude/` path watched) before and after each dispatch, and any other path that changes during a dispatch stops the interview, whatever changed it (abcd cannot tell the role's writes from another program's, so two interviews run in one checkout stop each other), exit 1, naming each, keeping the answers given and reporting no readiness; what the links one reading follows lead to, a hooks directory `core.hooksPath` names outside the tree, and each worktree's push receipts are read within 1,024 entries and 64 MiB in all (1,024 entries for each worktree's push receipts), and a reading past that stops the interview, exit 1, naming the link, the `core.hooksPath` value or the receipts directory; git's own directory and common directory are the ones the first reading found, so a linked worktree's `.git` file or a `commondir` file changed during a turn stops the interview, naming the file, unfollowed, and a working tree git names other than the first reading's (a `core.worktree` set since) stops it before git reads it; what one reading hashes (the paths git lists, git's own directory) is held to 2 GiB, and the files git reads whole on every command (`HEAD`, the configuration, the packed refs, `info/exclude`, in git's own directory and in each submodule's) are sized against it before git runs, a reading past it stopping the interview, exit 1, naming the file (the largest, when the listed paths hold more, with `.gitignore` advised only for an untracked one); the structured refusal names every path the role changed, and the readiness report lists the paths it changed | edits the record in place (the role's edits); writes the answers record and the turns under `.abcd/.work.local/` | @@ -438,7 +438,7 @@ Discipline-kind intents use a different template — see § 1 "Discipline format ## 5. Frontmatter fields (spc-3 (predecessor store) additions — a later phase) -spc-3 (predecessor store) adds the following optional frontmatter fields to intent files. All are additive — pre-existing intents without them remain valid (schema: `intent.schema.json`, part of the plan-time intent-lint design per [`05-internals/06-lint.md`](../05-internals/06-lint.md) — a later phase). **Not shipped:** the grill sub-verb that writes these fields and the PRD at `.abcd/intents//prd.md` are a design target (backing intent itd-27, `intents/planned/`); the shipped quoted-text create seeds none of them (see § 1's seed skeleton). +spc-3 (predecessor store) adds the following optional frontmatter fields to intent files. All are additive — pre-existing intents without them remain valid (schema: `intent.schema.json`, part of the plan-time intent-lint design per [`05-internals/06-lint.md`](../05-internals/06-lint.md) — a later phase). **Not shipped:** the grill sub-verb that writes these fields and the PRD at `.abcd/intents//prd.md` are a design target (its intent itd-27 is in `intents/superseded/`, superseded by itd-94); the shipped quoted-text create seeds none of them (see § 1's seed skeleton). | Field | Type | When set | Purpose | |-------|------|----------|---------| @@ -498,7 +498,7 @@ The invariants below are the contract the tree is held to, and each names what h - **A planned intent declares the state of the art** (per [sota-per-intent](../../principles/sota-per-intent.md)): a `## SOTA` section naming the existing alternatives, each one's rough maturity, and the path taken. `intent_sota` flags a `planned/` intent with no such section, or with a heading and nothing under it, at warn severity — the warn-first rung of a ratchet whose next rung is blocker once the planned bucket is back-filled. It judges presence, not the path's spelling, and reads neither `drafts/` (not yet shaped) nor `shipped/` (history, most of it older than the principle). - **`kind` is set on intents in `planned/`, `shipped/`, `disciplines/`, and `superseded/`.** Intents in `drafts/` may have `kind: null`. The shipped plan step neither infers a kind nor asks for one: it writes `standalone` wherever the draft left the field null, so `standalone` is what an unstated kind becomes. **A later phase** replaces that default with the proposal the user confirms or overrides (§ 1, "Later phase — plan grows a PRD-freeze front end and multi-kind dispatch"). What the record lint holds meanwhile is the value set per bucket: a draft's kind must be null, `standalone` or `bundle-member`, and a planned or shipped record's must be one of the latter two, non-null (`intent_lifecycle`). - **`kind: bundle-member` requires a `bundle:` field** pointing to a bundle ID; *all* members of a bundle reference the same bundle ID, and bundles are bidirectional in their members' frontmatter. `record_schema` refuses a bundle-member naming a bundle no other record names, unless its `reclassification_history` states the bundle now has one member (the survivor a supersession leaves); a bundle-member carrying no `bundle:` at all is **(convention)**, since both writers stamp the name and the shipped records without one are settled. **Exception for superseded bundle-members:** intents in `superseded/` with `kind_at_supersession: bundle-member` carry `bundle: null` AND `bundle_at_supersession: ` (preserves the bundle the intent was part of when retired, while signalling the bundle is no longer active); the reclassify verb writes both, and no lint reads `bundle_at_supersession`. -- **A dependency edge names a live record, and the edges form no cycle.** `stale_edge` flags a `planned/` or `drafts/` intent whose `builds_on` or `blocked_by` names an intent in `superseded/`, one finding per edge, naming the record the superseded intent's chain ends at: the same walk along `superseded_by` the build's blocked check follows (`intent.SupersessionChainOf`), or why the chain cannot be finished. `edge_cycle` flags a cycle through `builds_on` and `blocked_by` together, across every intent not superseded, in one finding naming every record on it. Both are armed at warn, because this tree carries five findings that each wait on a decision rather than a repair: the drafts itd-22 (`blocked_by`) and itd-33 (`builds_on`) name itd-2, whose successor does not carry the in-session dispatch contract they depended on; itd-2609201916056194 and itd-2609201916151817 build on each other (iss-2609300848016421); and itd-14 and itd-15, and itd-2609081951381895 and itd-2609170822093401, build on each other (iss-2609300903551032). Both rules become blockers once those are decided. +- **A dependency edge names a live record, and the edges form no cycle.** `stale_edge` flags a `planned/` or `drafts/` intent whose `builds_on` or `blocked_by` names an intent in `superseded/`, one finding per edge, naming the record the superseded intent's chain ends at: the same walk along `superseded_by` the build's blocked check follows (`intent.SupersessionChainOf`), or why the chain cannot be finished. `edge_cycle` flags a cycle through `builds_on` and `blocked_by` together, across every intent not superseded, in one finding naming every record on it. Both are armed at warn, because this tree carries three `edge_cycle` findings that each wait on a decision rather than a repair: itd-2609201916056194 and itd-2609201916151817 build on each other (iss-2609300848016421); and itd-14 and itd-15, and itd-2609081951381895 and itd-2609170822093401, build on each other (iss-2609300903551032). Both rules become blockers once those are decided. - **Bundle invariant: no member is blocked by another.** Planning several intents as a bundle refuses a member naming another member in `blocked_by`, naming the edge, before anything moves. See § 1 "Bundle invariant" for the canonical statement. - **`surface_history` entries are well-formed.** Every entry must include `date` (ISO YYYY-MM-DD), `from` (free-form surface descriptor), `to`, and `reason` (non-empty). Lint code `IL012` (severity: warn — it's an audit trail, not a gate). See itd-27's `surface_history` (skill → sub-verb on 2026-05-07) for a worked example. - **`kind: discipline` lives only in `disciplines/` or `superseded/`.** A discipline-kind record in `drafts/` is an error, caught by the record lint over the committed tree rather than at plan time: the `intent_lifecycle` drafts rule admits only a null, `standalone` or `bundle-member` kind, and the disciplines rule demands `discipline`. The gate is the commit, not the promotion. diff --git a/.abcd/development/brief/04-surfaces/06-capture.md b/.abcd/development/brief/04-surfaces/06-capture.md index 59525ac4a..e668192e3 100644 --- a/.abcd/development/brief/04-surfaces/06-capture.md +++ b/.abcd/development/brief/04-surfaces/06-capture.md @@ -309,7 +309,8 @@ commit. Exit 1 is left to a fault: a ledger that could not be read or moved, a record that contradicts itself (a tangled or contested set of answers only a hand edit repairs), or a found-at path the checkout would not let the write check for a reason other than its absence. Migration takes no input to refuse, -so each of its failures is a fault. +so past the refusal outside a git checkout every verb shares (§ 2), each of its +failures is a fault. **Resolving** marks an issue resolved and moves it to `resolved/`. Impact is required, and resolving without it is refused with diff --git a/.abcd/development/brief/04-surfaces/07-memory.md b/.abcd/development/brief/04-surfaces/07-memory.md index 8e1b176c2..8695465d1 100644 --- a/.abcd/development/brief/04-surfaces/07-memory.md +++ b/.abcd/development/brief/04-surfaces/07-memory.md @@ -18,7 +18,7 @@ behaviour, lifecycle class) is surface contract: what the user types and what happens. > **Provenance, and a warning about ids.** The surface traces to itd-36, which -> sits in `intents/planned/`. The write core and the lint family were specified +> sits in `intents/shipped/`. The write core and the lint family were specified > in a predecessor store whose `spc-38` and `spc-39` collide with live ids in > this repo's own store. So on this page the store an id belongs to is read from > the sentence around it, never from the number. diff --git a/.abcd/development/brief/04-surfaces/08-abcd.md b/.abcd/development/brief/04-surfaces/08-abcd.md index aa0cffc5f..0af40b1de 100644 --- a/.abcd/development/brief/04-surfaces/08-abcd.md +++ b/.abcd/development/brief/04-surfaces/08-abcd.md @@ -65,7 +65,9 @@ and in the JSON form's `dir` alike, never by an absolute path consumer acts on `dir`. The text line masks a control character or bidi control in that name, as every other board line does (iss-2609281736483740); `dir` carries the name as it is, escaped by the JSON encoder where it is a control -byte. The plugin command invokes its JSON form. +byte. The plugin command draws the product thinker's view as markdown, the +facilitator's view as markdown when the full board is asked for, and the JSON +form only when the agent reasons over the fields rather than shows them. The text form answers in words: `yes` or `no` for the repository and the record, and the work tiers named and separated by commas, or `none`. Every @@ -299,7 +301,7 @@ reason on stderr, as it refuses build next. ## The board itself is not built -> **Design target (itd-20, `intents/planned/`, `spec_id: null`).** Everything in +> **Design target (itd-20, `intents/superseded/`, superseded by itd-121, `spec_id: null`).** Everything in > the rest of this chapter describes a board that does not exist on any shipped > surface. The shipped status path reads `.git`, `.abcd/development` and the > three work tiers, and nothing else: no visibility, no disembark log, no diff --git a/.abcd/development/brief/04-surfaces/10-docs.md b/.abcd/development/brief/04-surfaces/10-docs.md index d5acf9a33..da9273ef0 100644 --- a/.abcd/development/brief/04-surfaces/10-docs.md +++ b/.abcd/development/brief/04-surfaces/10-docs.md @@ -117,8 +117,8 @@ in a gate, which is what keeps the lint itself deterministic and offline. per-task pass: it states every finding, refuses nothing and exits 0. Bare `abcd docs` prints command usage rather than a status board; the -[surfaces index](README.md) carries the one enumeration of where the -bare-status convention holds, and `docs` is not on it. The working verbs accept +[surfaces index](README.md) carries the one enumeration of the exceptions to +the bare-status convention, and `docs` is on it. The working verbs accept the `docs-lint.json` to load and the repo to work over; the bare citation parent only routes and takes neither. That is what makes the refresh fetch exactly the set the gate demands receipts for. diff --git a/.abcd/development/brief/04-surfaces/11-history.md b/.abcd/development/brief/04-surfaces/11-history.md index 2c5831055..9a20b62ad 100644 --- a/.abcd/development/brief/04-surfaces/11-history.md +++ b/.abcd/development/brief/04-surfaces/11-history.md @@ -330,10 +330,12 @@ transcript is redacted on write, the redaction counts are recorded on the record, and a redaction failure refuses the write rather than storing unredacted content. -The corpus has three write paths that all redact: the explicit capture, -the explicit drain, and the automatic session-start drain. The migration above is a fourth -path into the store, reached from every verb rather than from those three, and -the one that does not redact: it moves bytes verbatim, because a record at the +Every write path into the corpus redacts but one: a capture, whether the +explicit one or a delegated agent's transcript that `abcd build` and the written +interview store through that same capture; an ingest; and a drain, whether +explicit, at session start, or live on a prompt. The migration above is the +exception, a path into the store reached from every verb rather than from +those, and the one that does not redact: it moves bytes verbatim, because a record at the legacy path was redacted by the same engine when it was first stored, and a staged file moves into staging, where the next drain redacts it exactly as it would a freshly staged one. Relocating a corpus is not the moment to rewrite it. diff --git a/.abcd/development/brief/04-surfaces/16-lint.md b/.abcd/development/brief/04-surfaces/16-lint.md index 484d77f75..af4a3a900 100644 --- a/.abcd/development/brief/04-surfaces/16-lint.md +++ b/.abcd/development/brief/04-surfaces/16-lint.md @@ -8,7 +8,7 @@ order. It is **read-only** but for one target: bare and every other target perform zero writes, and the site target renders the site into its output directory when -that directory holds no `index.html`, as the site verb's check does +that directory holds no `index.html`, and gates what it rendered ([`22-site.md`](22-site.md)). Remediation stays with `/abcd:prepare-this-repo` and the technical facilitator. It answers a different question from `/abcd:ahoy`: `ahoy` reports whether the *tool* is installed and diff --git a/.abcd/development/brief/04-surfaces/17-guard.md b/.abcd/development/brief/04-surfaces/17-guard.md index de52b39e1..70ec6faba 100644 --- a/.abcd/development/brief/04-surfaces/17-guard.md +++ b/.abcd/development/brief/04-surfaces/17-guard.md @@ -78,8 +78,9 @@ uses: a verdict, and with it the entry that fired, its tier, why the command is dangerous, and the safe successor. A `matches` list names every entry the same line tripped, the one that fired included, so a command hazardous in two ways reports both rather than only the first; the rendered form says the same thing -on an `also matched:` line. The hook answers the host by its exit code and its -message on stderr alone, and writes nothing on stdout in either output form. +on an `also matched:` line. In either output form the hook answers the host +by its exit code, its message on stderr, and on stdout only the host's deny +above or the question's context object below. ## Taught before it is refused diff --git a/.abcd/development/brief/04-surfaces/21-update.md b/.abcd/development/brief/04-surfaces/21-update.md index c8d94e021..369f21366 100644 --- a/.abcd/development/brief/04-surfaces/21-update.md +++ b/.abcd/development/brief/04-surfaces/21-update.md @@ -153,8 +153,8 @@ the refusal whole rather than a receipt followed by a second envelope. An old version number is only derivable when a release manifest dated the file it replaced. A file swapped under either local proof has no published release naming -those bytes, so the receipt reports the old digest instead and reads `updated -: an unpublished build -> `. An absent old version is therefore the +those bytes, so the receipt opens `abcd updated from an unpublished build to +` and reports the old digest on its `replaced:` line instead. An absent old version is therefore the documented shape, never a broken receipt. ## References diff --git a/.abcd/development/brief/04-surfaces/27-implement.md b/.abcd/development/brief/04-surfaces/27-implement.md index 0d6022b46..9afe1f0a8 100644 --- a/.abcd/development/brief/04-surfaces/27-implement.md +++ b/.abcd/development/brief/04-surfaces/27-implement.md @@ -49,6 +49,8 @@ own state lives in the checkout's local tier, not in the shared run state below; ~/.abcd.noindex/runs//.jsonl the run log, one event per line, per UTC day ~/.abcd.noindex/runs//claims/.json one claim per record ~/.abcd.noindex/runs//sessions/.json one record per joined session +~/.abcd.noindex/runs//outage.json the run's one outage, while it is open +~/.abcd.noindex/runs//.lock the advisory lock every mutation takes ``` The key is the full root-commit SHA, the key the transcript, history and voyage @@ -341,8 +343,10 @@ saved aside. `0` done, and every status of the load check; `2` refused (an unrecognised input, a session that has not joined, a bound the role does not permit, no checkout to key a run on), with nothing written for the refused act; `3` contention (the record is claimed by another -session, or the run state is locked) — back off and take other work. The JSON -form holds on every path: a refusal is the `{"abcd":"error",…}` envelope on stdout. +session, or the run state is locked) — back off and take other work. The +outage probe gives `3` a meaning of its own, wait: the probe is not due, another +session holds it, or a service is still down; and it exits `2` once the run gave +up. The JSON form holds on every path: a refusal is the `{"abcd":"error",…}` envelope on stdout. diff --git a/.abcd/development/brief/04-surfaces/33-source.md b/.abcd/development/brief/04-surfaces/33-source.md index 9f3ecd073..77ce2e8d8 100644 --- a/.abcd/development/brief/04-surfaces/33-source.md +++ b/.abcd/development/brief/04-surfaces/33-source.md @@ -85,8 +85,9 @@ the process list and the shell history. ## Recording influence, and citing Recording an influence appends `{ts, repo, decision_ref, claim, source_key, -locator, influence, cited_publicly}` with `cited_publicly` false, and commits it. There is no edit -path: a correction is a new line naming the line it corrects. +locator, influence, cited_publicly}` with `cited_publicly` false, plus `used_in`, +the repository paths the influence landed in, when the caller names them, and commits it. There is no edit +path: a correction is a new line naming, in `corrects`, the line it corrects. A public citation needs both gates of [adr-41](../../decisions/adrs/0041-corpus-trust-boundary.md). The source grants diff --git a/.abcd/development/brief/04-surfaces/34-build.md b/.abcd/development/brief/04-surfaces/34-build.md index 1f576580e..b8fcc8aa9 100644 --- a/.abcd/development/brief/04-surfaces/34-build.md +++ b/.abcd/development/brief/04-surfaces/34-build.md @@ -479,7 +479,10 @@ every path it names held inside the lane's directory. Its fields are `definition_of_done` (`command`, `exit_code`, `output`), `report`, an optional `model`, the model the implementer's harness reported, and an optional `resolves`: each capture the lane fixed, with the `commit` that fixed it, the -`note`, the `impact` and the `grounds` its resolution records. It verifies +`note`, the `impact` and the `grounds` its resolution records. On an +issue-keyed lane an optional `handback` (`kind`, `reason`, `home`) stands in +for `resolves`: it ends the lane, discards its worktree and branch and hands the +issue back by kind ([`35-drain.md`](35-drain.md)). It verifies only when every commit it names is on the lane's branch and not already on the default branch at the lane's base, the definition of done's output exists non-empty with exit code 0, the report exists non-empty, and each fixed capture