diff --git a/.abcd/development/brief/04-surfaces/18-ideate.md b/.abcd/development/brief/04-surfaces/18-ideate.md new file mode 100644 index 00000000..1544bc6f --- /dev/null +++ b/.abcd/development/brief/04-surfaces/18-ideate.md @@ -0,0 +1,98 @@ +# `/abcd:ideate` — Gate an Idea Before It Becomes a Record Entry + +`/abcd:ideate` puts a big, unproven idea through a three-leg admission gauntlet +and records the verdict — whether the idea survives or dies. Only a survivor +graduates to a draft intent, and ideate mints no intent itself. + +It is **optional and never a gate**. No other verb requires it, nothing warns +when it is skipped, and capture friction stays at one line. The routing help in +the `intent` and `capture` surfaces names it for big, unproven ideas; that is a +pointer, not a precondition. + +## Division of labour + +Ideate follows the host-delegated pattern the `disembark` synthesis family +proved. The **host** runs the three judgement legs as agents; the **binary** is +the deterministic frame that validates their output, proves every citation, and +writes the record. The binary never fetches a source, never calls a model, and +never decides whether an idea is any good. + +## The three legs, in order + +| # | Leg | What it produces | +|---|---|---| +| 1 | Primary-source research | A claims table: each load-bearing claim, the **primary** source it was checked against, and the finding (`verified` / `falsified` / `unverifiable`) | +| 2 | Record grill | Hits on the existing record (brief, intents in every bucket, ADRs, principles), each cited by record id with a relation (`covered` / `contradicted` / `superseded`) | +| 3 | Adversarial review | Kill attempts, each with an outcome (`survived` / `partial` / `fatal`) | + +The order is validated, not assumed: the legs travel as an ordered array and a +payload whose legs are missing, reordered, or duplicated is refused. Each leg +changes what the next one is looking at, so running them out of order produces a +different — and weaker — result. + +Leg 3 is defined by its **conduct**: fresh-context, off-policy, unknown +authorship. The evaluator did not conduct the research and receives the idea as +an artefact whose provenance it does not know. That is the +evaluator-outside-the-loop principle applied to ideas, and it is the one measured +debiasing effect the protocol rests on. The binary cannot observe how an agent +was run, so this obligation lives in the command page's orchestration script, +which instructs the host to strip authorship and prior-leg framing before the +hand-off. + +## Behaviour + +```bash +abcd ideate record --verdict-json --json +``` + +`--verdict-json` is **required**. Unlike the `disembark` synthesis verbs there is +no deterministic fallback mode: those can fall back because a packed lifeboat's +own files carry evidence, and there is no evidence-only verdict an idea could +have. A binary that invented one would be doing the judging. + +The verb writes two things: + +- `.abcd/development/research/YYYY-MM-DD-ideate-.md` — the verdict + record: the idea as captured, the three legs, the verdict, and the rejected + alternatives, rendered for a human. +- one dated pointer line in `.abcd/work/DECISIONS.md`. + +No new record family: a killed idea is a research outcome, and the research +directory is where a session looks before re-proposing one. + +Exit codes are the release-cut shape without the middle state: `0` recorded, `2` +refused with nothing written. There is no exit 1, because a verdict is either +recordable or it is not. + +## What the binary refuses + +| Refusal | Why | +|---|---| +| A cited record id that does not resolve in the repository | A grill hit on a record that does not exist is a hit on nothing. The refusal names every offending id | +| A cited value that is not a record id at all | Bounded before it is matched or echoed | +| Legs missing, reordered, or duplicated; a leg carrying another leg's evidence | The gauntlet is exactly three legs, in order | +| An out-of-enum verdict, claim status, grill relation, or kill outcome | Each set is closed; an unregistered value is a whole-document refusal, never a coercion | +| A claim naming no primary source | A claim checked against nothing is an assertion | +| An empty `rejected_alternatives` list with no explicit `no_rejected_alternatives` marker | Silence and "nothing was weighed" are indistinguishable to a later reader, and the record exists to stop the idea being re-litigated | +| A slug that is not lower-case kebab-case | The slug becomes a filename; the grammar is the lexical half of the write containment, `os.Root` the other | +| A verdict record that already exists for this slug and date | Overwriting would erase a recorded reason, which is the one thing the verb exists to preserve | +| A repository with no `.abcd/work/DECISIONS.md` | A record nothing points at is a record nobody finds — refused before anything is written | +| A symlinked component anywhere in `.abcd/development/research/` | The write goes through one `os.Root` opened at the repository root, which refuses symlink traversal at every level, not just the leaf | + +The research directory itself is **created when absent**: nothing else in abcd +establishes it and no convention check requires it, so refusing would fail the +first run in every repository — after the three host legs have already been paid +for, and with the verdict unrecoverable when it arrived on stdin. The exclusive +create is the no-overwrite guarantee, and the decision log's read-modify-write +runs under the same advisory lock the ledger allocators use. + +Refusals are **whole-document**, never cite-or-be-dropped. A verdict record with +a quietly-dropped falsified claim or grill hit is worse than no record, because a +later session trusts it. + +## References + +- Plugin command: [`commands/abcd/ideate.md`](../../../../commands/abcd/ideate.md) +- Intent: [`itd-104`](../../intents/planned/itd-104-abcd-gates-a-new-idea-before-it-becomes-a-record-entry-resea.md) +- Spec: [`spc-18`](../../specs/open/spc-18-abcd-gates-a-new-idea-before-it-becomes-a-record-entry-resea.md) +- Routing-help neighbours: [`05-intent.md`](05-intent.md), [`06-capture.md`](06-capture.md) diff --git a/.abcd/development/brief/04-surfaces/README.md b/.abcd/development/brief/04-surfaces/README.md index 814196fa..c48f7548 100644 --- a/.abcd/development/brief/04-surfaces/README.md +++ b/.abcd/development/brief/04-surfaces/README.md @@ -21,6 +21,7 @@ The brief's user-facing command surface is the set enumerated below (not all are | 15 | `/abcd:prepare-this-repo` | shipped | Bring an owned repo up to abcd's conventions (interim bridge) — host-delegated command, no Go verb | [`15-prepare-this-repo.md`](15-prepare-this-repo.md) | | 16 | `/abcd:audit` | shipped | Check whether a repo conforms to the working conventions (three-tier layout, AGENTS.md router, durable decisions, docs currency, privacy hygiene) — read-only, tri-state exit; backs `prepare-this-repo` and gates CI (itd-85) | [`16-audit.md`](16-audit.md) | | 17 | `/abcd:guard` | shipped | Shell-hazard guard (`check`) — decide one candidate command line against the bundled hazard registry merged with the repo's committed `.abcd/guard.json`, and answer allow / warn / block with the plain-language why and the safe successor. Read-only. The `hook` sub-verb is the host pre-tool-use adapter, live-wired from `hooks/hooks.json` behind a fail-open-loud shim; guard health (hook installed, binary reachable, registry loadable) is reported by `abcd ahoy` (itd-103, spc-16) | [`17-guard.md`](17-guard.md) | +| 18 | `/abcd:ideate` | shipped | Idea-admission gauntlet (itd-104, spc-18): three host-run legs in a validated order — primary-source research, a record grill whose every hit is cited by an id the binary proves resolves, and a fresh-context/off-policy/unknown-authorship adversarial review — then `abcd ideate record --verdict-json` writes the dated verdict record under `.abcd/development/research/` and one pointer line in `.abcd/work/DECISIONS.md`. Recorded whether the idea survives or dies, with rejected alternatives that an explicit marker is required to leave empty. Optional and never a gate: the `intent` and `capture` routing help names it, nothing requires it | [`18-ideate.md`](18-ideate.md) | The **Status** column is machine-checked: the `surface_coverage` record-lint rule asserts every `shipped` row has a backing surface (`commands/abcd/.md` or `skills//`) and every `staged` row (a design target) has none — and, in reverse, that every real surface has a row here. The bare `/abcd` top-level is binary-backed (its command file is `commands/abcd.md`, not a `commands/abcd/.md` entry) and is exempt from the file check. Keeping this column honest is how the brief's surface set stays reconciled with the shipped binary; the semantic half — whether each row's *prose* matches binary behaviour — stays a release-gate agent check. diff --git a/.abcd/development/intents/drafts/itd-104-abcd-gates-a-new-idea-before-it-becomes-a-record-entry-resea.md b/.abcd/development/intents/planned/itd-104-abcd-gates-a-new-idea-before-it-becomes-a-record-entry-resea.md similarity index 99% rename from .abcd/development/intents/drafts/itd-104-abcd-gates-a-new-idea-before-it-becomes-a-record-entry-resea.md rename to .abcd/development/intents/planned/itd-104-abcd-gates-a-new-idea-before-it-becomes-a-record-entry-resea.md index abd2f3f3..3af59631 100644 --- a/.abcd/development/intents/drafts/itd-104-abcd-gates-a-new-idea-before-it-becomes-a-record-entry-resea.md +++ b/.abcd/development/intents/planned/itd-104-abcd-gates-a-new-idea-before-it-becomes-a-record-entry-resea.md @@ -1,8 +1,8 @@ --- id: itd-104 slug: abcd-gates-a-new-idea-before-it-becomes-a-record-entry-resea -spec_id: null -kind: null +spec_id: spc-18 +kind: standalone suggested_kind: null reclassification_history: [] builds_on: [] diff --git a/.abcd/development/release/surface.json b/.abcd/development/release/surface.json index c54a67f0..3882dccc 100644 --- a/.abcd/development/release/surface.json +++ b/.abcd/development/release/surface.json @@ -426,6 +426,24 @@ "hidden": false, "flags": [] }, + { + "path": "abcd ideate", + "hidden": false, + "flags": [] + }, + { + "path": "abcd ideate record", + "hidden": false, + "flags": [ + { + "name": "verdict-json", + "shorthand": "", + "type": "string", + "required": false, + "hidden": false + } + ] + }, { "path": "abcd intent", "hidden": false, diff --git a/.abcd/development/specs/open/spc-18-abcd-gates-a-new-idea-before-it-becomes-a-record-entry-resea.md b/.abcd/development/specs/open/spc-18-abcd-gates-a-new-idea-before-it-becomes-a-record-entry-resea.md new file mode 100644 index 00000000..e0d3de75 --- /dev/null +++ b/.abcd/development/specs/open/spc-18-abcd-gates-a-new-idea-before-it-becomes-a-record-entry-resea.md @@ -0,0 +1,98 @@ +--- +id: spc-18 +slug: abcd-gates-a-new-idea-before-it-becomes-a-record-entry-resea +intent: itd-104 +--- +# abcd-gates-a-new-idea-before-it-becomes-a-record-entry-resea + +## Summary + +spc-18 delivers itd-104's idea-admission protocol: `/abcd:ideate`, an +optional three-leg gauntlet — primary-source research, a record grill, and an +independent adversarial review — whose outcome is a registered verdict with +rejected alternatives, recorded whether the idea survives or dies. Only a +survivor graduates to a draft intent. The design decisions below were settled +by the 2026-07-27 grill; this spec records them together with the mechanism — +it does not reopen them. + +## Settled constraints (from the grill) + +- **Optional verb, never a gate.** Capture friction stays at one line: no + ideate step is required or suggested as blocking by the intent or capture + verbs. The routing help carries the nudge — ideate is named for big, + unproven ideas. +- **The adversarial leg codifies fresh-context, off-policy, unknown + authorship** — the evaluator did not do the research and receives the idea + as an artefact of unknown authorship. This is the only measured debiasing + effect, per the salvaged 2026-07-14 research record, and it is the + evaluator-outside-the-loop principle applied to ideas. +- **Two load-bearing steps survive automation** (from the proven manual run): + check the record first, and open the primary source. + +## Mechanism + +### Division of labour + +Ideate follows the host-delegated pattern the disembark family proved: the +host runs the judgement legs as agents; the abcd binary is the deterministic +frame that scaffolds the run, validates the legs' outputs, and writes the +record. The binary never does LLM work; the host never writes the record +directly. + +### The three legs, in order + +1. **Primary-source research.** A host agent researches the idea's + load-bearing claims, each checked against its primary source — a claim + carries its primary URL or document reference, never only a secondary + citation. Output: a claims table (claim, primary source, verified / + falsified / unverifiable). +2. **Record grill.** A host agent reads the existing record — brief, intents + (all buckets), ADRs, principles — and answers: does an entry cover, + contradict, or supersede this idea? Every hit is cited by record id. The + binary validates at ingest that every cited id resolves in the repository + — a verdict citing a non-existent record is refused. +3. **Adversarial review.** A fresh-context agent, off-policy, receiving the + idea and the two earlier legs' outputs as artefacts of unknown authorship, + tries to kill the idea. It did not conduct the research (evaluator outside + the loop). + +### Verdict record (spec-time decision) + +The verdict is a dated research record: +`.abcd/development/research/YYYY-MM-DD-ideate-.md`, written by the +binary from a structured verdict the host supplies +(`abcd ideate record --verdict-json `). It contains the +idea as captured, the three legs' findings, the verdict +(survives / killed / reframed), the rejected alternatives, and — for record +grill hits — the cited record ids. A one-line dated entry in +`.abcd/work/DECISIONS.md` points at it. No new record family: killed ideas +are research outcomes, and the research directory is where a future session +looks before re-proposing one. A surviving idea graduates through the +existing quoted-text intent create — ideate mints no intents itself. + +### Wiring + +Both planes at delivery: the `abcd ideate` verb family (CLI) and the +`/abcd:ideate` skill (`commands/abcd/ideate.md`) that orchestrates the three +legs and feeds the verdict to the binary. The routing help in the intent and +capture surfaces names ideate as the optional path for big, unproven ideas — +a pointer, never a precondition. + +## Acceptance-criteria mapping + +- AC 1 (never blocking; routing help names it) → Settled constraints + + Wiring. +- AC 2 (three legs in order; adversarial leg fresh-context, off-policy, + unknown authorship) → The three legs. +- AC 3 (decision recorded with rejected alternatives either way; only + survivors graduate) → Verdict record. +- AC 4 (record-grill hits cited by id, validated) → The three legs + Verdict + record. + +## Out of scope + +- Consuming a lifeboat or external document as the idea source (open + question retained; the idea arrives as quoted text in this spec). +- Any change to capture or intent-create flow beyond the routing-help text. +- Automation of the legs inside the binary — the legs are host agents by + boundary; the binary validates and records. diff --git a/.abcd/work/DECISIONS.md b/.abcd/work/DECISIONS.md index 5a16e2fb..8a64f7ae 100644 --- a/.abcd/work/DECISIONS.md +++ b/.abcd/work/DECISIONS.md @@ -866,3 +866,4 @@ parallel-agent merge contention bites. - 2026-07-27: itd-103 guard wiring — `abcd guard hook` is a sub-verb of the USER-facing `guard` verb, not a fifth entrypoint under the hidden `hook` subtree. Reason: guard health has to be legible (AC 1), and a hidden adapter documents nothing; the other `hook` entrypoints are injection transport with no user-facing counterpart, this one is the same decision the user can also ask for by hand. Consequence: it appears in the generated CLI reference and must stay host-agnostic in its prose. - 2026-07-27: itd-103 guard wiring — the two front doors disagree on ONE case by design. A guard that cannot be evaluated (unparsable command, registry that will not load) is a FAULT for `guard check` (exit 2: a script must never read silence as clearance) and fail-OPEN for `guard hook` (exit 0 + loud stderr: a broken guard must never stop a session). Rejected: one shared behaviour, which would either brick sessions or teach scripts that silence means safe. - 2026-07-27: itd-103 guard wiring — the fail-open-loud shim is a shell wrapper in the plugin's `hooks/hooks.json` PreToolUse entry (pass through only the binary's own 0/2; everything else warns UNGUARDED and allows), not Go code. The failure it guards against is the Go binary not running at all, so it cannot live in Go. Tested by executing the committed manifest string under /bin/sh against a fake plugin root. +- 2026-07-28: itd-104 spec minted as spc-16 (third live iss-80 instance this run: 16 collided with #156, 17 with the itd-101 branch); renumbered to spc-18 by hand. diff --git a/.gitignore b/.gitignore index e46f63d5..600712f5 100644 --- a/.gitignore +++ b/.gitignore @@ -46,3 +46,6 @@ go.work.sum # capture ledger allocator lock (runtime artefact) .abcd/work/issues/.iss-alloc.lock + +# decision-log append lock (runtime artefact) +.abcd/work/.decisions.lock diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c3aca56..2093b50a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,26 @@ called out in a **Breaking** section. ### Added +- **`/abcd:ideate` — the idea-admission gauntlet** (itd-104, spc-18). A big, + unproven idea can be put through three legs before it becomes a record entry: + primary-source research (each load-bearing claim checked against its **primary** + source, never a secondary citation), a grill against the existing record, and an + adversarial review that is fresh-context, off-policy, and receives the idea as an + artefact of unknown authorship. The legs are host work; `abcd ideate record + --verdict-json ` is the deterministic frame that validates + them and writes the durable verdict — the dated record under + `.abcd/development/research/` plus one pointer line in `.abcd/work/DECISIONS.md`. + The verdict is recorded **whether the idea survives or dies**, and its rejected + alternatives may be empty only behind an explicit marker, because silence and + "nothing was weighed" read the same to a session tempted to re-propose the idea. + Every record-grill hit is **cited by id and proved to resolve** in the + repository: an id naming no record refuses the whole verdict and names the id. + The legs travel as an ordered array so "three legs, in order" is checked rather + than assumed, and refusals are whole-document — nothing is written unless + everything validates. Ideate is **optional and never a gate**: the `intent` and + `capture` routing help names it for big unproven ideas, and nothing requires it + or warns when it is skipped. + - **`abcd guard` — the shell-hazard guard, wired into a live session** (itd-103, spc-16). `abcd guard check --command ""` evaluates one candidate command against the hazard registry and reports allow, warn, or block: a blocker exits @@ -108,6 +128,20 @@ called out in a **Breaking** section. ### Fixed +- **Untrusted prose can no longer open raw HTML in a record abcd writes.** The + shared cleaner every host-delegated ingest routes through — the release + changelog ingest, the lifeboat synthesis writers, and the ideate verdict — + neutralised newlines and HTML comment markers but left a bare tag intact. In + CommonMark a `<` followed by a letter, `/`, `!`, or `?` opens an HTML block, and + several of those block types run to the end of the document: a single + `", "< script>alert(1)< /script>"}, + {"table-tag", "
forged", "< table>< tr>< td>forged"}, + {"declaration", "", "< !DOCTYPE html>"}, + {"comment-open", "text