pi-aia-asf 0.2.2 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.3.0] - 2026-08-25
11
+
12
+ ### Added
13
+
14
+ - (describe changes for 0.3.0)
15
+
16
+
10
17
  ## [0.2.2] - 2026-08-14
11
18
 
12
19
  ### Added
package/index.ts CHANGED
@@ -13,7 +13,7 @@
13
13
 
14
14
  import type { ExtensionAPI, ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
15
15
  import { mkdir, readFile, writeFile } from "node:fs/promises";
16
- import { existsSync } from "node:fs";
16
+ import { existsSync, readFileSync } from "node:fs";
17
17
  import { join } from "node:path";
18
18
  import { homedir } from "node:os";
19
19
 
@@ -51,9 +51,29 @@ const QA_CHECKLIST: Array<{ key: string; label: string }> = [
51
51
  { key: "observable", label: "Observable end state verified as a user would experience it" },
52
52
  { key: "browser", label: "Web surfaces exercised through a real browser (n/a if none)" },
53
53
  { key: "specs", label: "Every MUST spec 'met' with concrete evidence" },
54
+ { key: "trace", label: "Spec-to-code traceability: every met spec has outcome → codePath → test" },
55
+ { key: "surface", label: "Every delivered feature is consumed by a surface (UI or API) — nothing dead" },
56
+ { key: "e2e", label: "Feature specs have an end-to-end behavioral test through the real entry point" },
54
57
  { key: "honest", label: "Skipped/inconclusive checks reported explicitly" },
55
58
  ];
56
59
 
60
+ /** Spec-to-code traceability (M1) — mirrors pi-vigilant's Trace. */
61
+ interface Trace {
62
+ outcome: string;
63
+ codePath: string;
64
+ testFile?: string;
65
+ assertion?: string;
66
+ }
67
+
68
+ interface SpecItem {
69
+ id: string;
70
+ requirement: string;
71
+ area: string;
72
+ priority: string;
73
+ status: string;
74
+ trace?: Trace;
75
+ }
76
+
57
77
  interface ProjectStateFile {
58
78
  current: AsfState | null;
59
79
  history: Array<{ workType: string; phase: AsfPhase; startedAt: string; endedAt: string }>;
@@ -94,6 +114,61 @@ async function saveState(project: string, data: ProjectStateFile): Promise<void>
94
114
  await writeFile(file, JSON.stringify(data, null, 2), "utf-8");
95
115
  }
96
116
 
117
+ // ─── Spec-memory read (shared with pi-vigilant) ────────────────────────────
118
+
119
+ const SPEC_DIR = join(SKILLS_DIR, "spec-memory", "projects");
120
+
121
+ function specTaskFileFor(project: string): string {
122
+ return join(SPEC_DIR, project, "current-task.json");
123
+ }
124
+
125
+ async function loadSpecs(project: string): Promise<SpecItem[] | null> {
126
+ const file = specTaskFileFor(project);
127
+ if (!existsSync(file)) return null;
128
+ try {
129
+ const task = JSON.parse(await readFile(file, "utf-8")) as {
130
+ areas?: Record<string, SpecItem[]>;
131
+ };
132
+ return Object.values(task.areas || {}).flat();
133
+ } catch {
134
+ return null;
135
+ }
136
+ }
137
+
138
+ /** Mechanical M1 validation of a met spec's trace. */
139
+ function validateTrace(spec: SpecItem): { ok: boolean; reason: string } {
140
+ const t = spec.trace;
141
+ if (!t || !t.outcome || !t.codePath) {
142
+ return {
143
+ ok: false,
144
+ reason: "met but trace incomplete — outcome + codePath required (add via update_spec_status trace)",
145
+ };
146
+ }
147
+ if (t.testFile) {
148
+ const abs = join(process.cwd(), t.testFile);
149
+ if (!existsSync(abs)) {
150
+ return { ok: false, reason: `testFile not found: ${t.testFile}` };
151
+ }
152
+ if (t.assertion) {
153
+ let content = "";
154
+ try {
155
+ content = readFileSync(abs, "utf-8");
156
+ } catch {
157
+ /* unreadable → treat as missing */
158
+ }
159
+ if (!content.includes(t.assertion)) {
160
+ return { ok: false, reason: `assertion not found in ${t.testFile}: "${t.assertion}"` };
161
+ }
162
+ }
163
+ }
164
+ return {
165
+ ok: true,
166
+ reason: !t.testFile
167
+ ? "trace complete (no testFile — ensure this is verifiable by inspection, or add an E2E test per 06b Rule 14)"
168
+ : "trace complete",
169
+ };
170
+ }
171
+
97
172
  // ─── Dependency check ──────────────────────────────────────────────────────
98
173
 
99
174
  interface DependencyCheck {
@@ -225,18 +300,79 @@ export default function register(pi: ExtensionAPI): void {
225
300
  }
226
301
  case "verify": {
227
302
  // Gate 7: force an explicit, itemised QA pass before delivery.
303
+ // Large work: mechanical spec-to-code traceability gate (M1).
228
304
  const project = projectName();
229
305
  const state = await loadState(project);
230
306
  if (!state.current) return "No active ASF session — nothing to verify.";
231
307
  await setPhase(ctx, "verification");
232
- return (
233
- `ASF verification gate (${project}) — Definition of Done.\n` +
234
- `Read references/06b-testing-qa.md. Confirm EACH item with concrete evidence\n` +
235
- `(command output, file list, screenshot). Do not tick anything you did not run.\n\n` +
236
- QA_CHECKLIST.map((c, i) => ` ${i + 1}. [ ] ${c.label}`).join("\n") +
237
- `\n\nThen run get_task_specs and close every spec with update_spec_status.\n` +
238
- `Unverifiable 'partial' + ask the user. Never self-certify.`
308
+ const scale = state.current.scale;
309
+ const lines: string[] = [
310
+ `ASF verification gate (${project}) Definition of Done.`,
311
+ `Scale: ${scale || "unset"}${
312
+ scale === "large"
313
+ ? " mechanical traceability gate ACTIVE"
314
+ : scale === "small"
315
+ ? " (automatic — no mechanical gate)"
316
+ : ""
317
+ }`,
318
+ ];
319
+
320
+ if (scale === "large") {
321
+ const specs = await loadSpecs(project);
322
+ lines.push("\nSPEC-TO-CODE TRACEABILITY (M1 — large work):");
323
+ if (!specs || specs.length === 0) {
324
+ lines.push(
325
+ " (no spec-memory task found — capture specs with capture_spec, incl. items from external planning docs)",
326
+ );
327
+ } else {
328
+ const metSpecs = specs.filter((s) => s.status === "met");
329
+ if (metSpecs.length === 0) {
330
+ lines.push(
331
+ " (no specs marked met yet — traceability applies when closing specs)",
332
+ );
333
+ } else {
334
+ let allOk = true;
335
+ for (const spec of metSpecs) {
336
+ const check = validateTrace(spec);
337
+ if (!check.ok) allOk = false;
338
+ lines.push(` [${check.ok ? "PASS" : "FAIL"}] ${spec.id}: ${check.reason}`);
339
+ if (check.ok && spec.trace) {
340
+ lines.push(` outcome: ${spec.trace.outcome}`);
341
+ lines.push(` code path: ${spec.trace.codePath}`);
342
+ if (spec.trace.testFile) {
343
+ lines.push(
344
+ ` test: ${spec.trace.testFile}${spec.trace.assertion ? ` (asserts "${spec.trace.assertion}")` : ""}`,
345
+ );
346
+ }
347
+ }
348
+ }
349
+ lines.push(
350
+ allOk
351
+ ? "\n ✓ All met specs have complete traces."
352
+ : "\n ✗ GATE NOT PASSED — resolve FAIL rows before delivery (attach trace via update_spec_status).",
353
+ );
354
+ }
355
+ }
356
+ }
357
+
358
+ lines.push(
359
+ "\nDefinition of Done checklist (references/06b-testing-qa.md Rule 10):",
360
+ );
361
+ lines.push(
362
+ "Read references/06b-testing-qa.md. Confirm EACH item with concrete evidence",
363
+ );
364
+ lines.push(
365
+ "(command output, file list, screenshot). Do not tick anything you did not run.",
366
+ );
367
+ lines.push("");
368
+ QA_CHECKLIST.forEach((c, i) => lines.push(` ${i + 1}. [ ] ${c.label}`));
369
+ lines.push(
370
+ "\nThen run get_task_specs and close every spec with update_spec_status.",
371
+ );
372
+ lines.push(
373
+ "Unverifiable → 'partial' + ask the user. Never self-certify.",
239
374
  );
375
+ return lines.join("\n");
240
376
  }
241
377
  case "abort":
242
378
  return await setPhase(ctx, "none");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-aia-asf",
3
- "version": "0.2.2",
3
+ "version": "0.3.0",
4
4
  "description": "Ai Applied Agentic Software Factory — codifies the full software development flow: intake, research, spec capture, adversarial analysis, planning with approval gates, test-first implementation, and release. Requires pi-vigilant, pi-smart-web-search, pi-smart-fetch, and pi-aia-browser.",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -70,6 +70,7 @@ Minimum intake checklist (ask anything not yet known, one question at a time or
70
70
  - **Constraints** — musts, must-nots, boundaries, budget, timeline
71
71
  - **Preferences** — stack, language, platform, style (only if the user has them)
72
72
  - **Definition of done** — tests? deploy? release? docs?
73
+ - **External planning docs** — are there IMPROVEMENT-PLAN.md / PLAN.md / requirements docs / delivery logs / ticket lists? Locate them (repo root, `docs/`, referenced by the user); they are inputs to spec capture, not ground truth.
73
74
 
74
75
  For each answer, **capture hard requirements immediately with `capture_spec`** (requirement, area, priority). Specs are the shared contract with pi-vigilant — it will re-verify them at the end.
75
76
 
@@ -105,9 +106,10 @@ Turn the intake answers + research into the authoritative spec set.
105
106
 
106
107
  1. Run `get_task_specs` to see what's already captured.
107
108
  2. Fill gaps: for every requirement the user stated or approved, ensure a spec exists (`capture_spec`).
108
- 3. Decompose broad specs with `parentId` (e.g. "must be secure" auth + encryption sub-specs).
109
- 4. Default priority is `must`; use `should` only when the user says "nice to have".
110
- 5. Areas: functionality, ui-ux, performance, security, error-handling, testing, documentation, compatibility, constraints, format, data, deployment, other.
109
+ 3. **Ingest external planning docs (M6):** every actionable item in an external planning doc (IMPROVEMENT-PLAN.md, PLAN.md, requirements docs, delivery logs, ticket lists) becomes a captured spec with `sourceQuote` pointing at the doc + item id. The doc's own ✅/delivered markers are **claims, not evidence** — each item gets traced and verified like any other spec.
110
+ 4. Decompose broad specs with `parentId` (e.g. "must be secure" auth + encryption sub-specs).
111
+ 5. Default priority is `must`; use `should` only when the user says "nice to have".
112
+ 6. Areas: functionality, ui-ux, performance, security, error-handling, testing, documentation, compatibility, constraints, format, data, deployment, other.
111
113
 
112
114
  **Gate 3** (large only): show the full spec tree (`get_task_specs`) and get user sign-off: "specs correct — proceed to adversarial analysis?" Small work: capture specs silently, no sign-off needed.
113
115
 
@@ -188,9 +190,11 @@ Execute the task list milestone by milestone. Discipline rules:
188
190
  2. **Codebase isolation**: work strictly inside the project's own codebase. Do NOT edit files in other repos, global config, or unrelated directories — **unless the user explicitly instructs otherwise**. If a change would touch another codebase, stop and ask.
189
191
  3. **No scope creep**: if something new is discovered that changes specs, capture it, ask the user, and update the plan before implementing.
190
192
  4. **Descriptive commits**: `git commit -m "type: specific description of what and why"` (e.g. `fix: verify specs before rotation`). No vague messages, no placeholders.
191
- 5. **Browser testing — MANDATORY for any web interface**: if the deliverable has a UI/website/web app, test it through `pi-aia-browser` (`browser_init`, `browser_navigate`, `browser_click`, `browser_type`, `browser_screenshot`, `browser_dom`, …) to replicate the user's real experience — not just curl/API checks. Verify: loads, key user journeys, responsive behavior, console errors.
193
+ 5. **Browser testing — MANDATORY for any web interface**: if the deliverable has a UI/website/web app, test it through `pi-aia-browser` (`browser_init`, `browser_navigate`, `browser_click`, `browser_type`, `browser_screenshot`, `browser_dom`, …) to replicate the user's real experience — not just curl/API checks. Verify: loads, key user journeys, responsive behavior, console errors. **Silent async paths included**: ingest through the real flow and wait for the enrichment to land (06b Rule 9).
192
194
  6. **Let pi-vigilant do its job**: it will auto-continue after premature stops and verify specs at settle. When it asks for `update_spec_status` with evidence, do it.
193
195
  7. **CHANGELOG discipline**: every user-visible change gets a CHANGELOG entry describing exactly what changed (no placeholder text).
196
+ 8. **Challenge approved designs (M4)**: if a spec's literal reading creates product tension (e.g. feedback clusters under "Plan" when "Plan = plans"), stop and resolve it with the user before implementing — never implement blindly and call it delivered.
197
+ 9. **Trace before claiming delivered (M5)**: the delivery log is a claim; the code is the evidence. Before marking anything ✅, trace the actual code path, confirm the output is consumed by a surface, and confirm the operator-facing outcome test passes.
194
198
 
195
199
  ---
196
200
 
@@ -200,6 +204,8 @@ Run the **Definition of Done checklist** in `references/06b-testing-qa.md` (Rule
200
204
 
201
205
  Also check the **modularity DoD** from `references/06c-code-quality.md` (Phase 7 section): no duplicated shared logic, no hardcoded config values, every module tested standalone with the same calls it gets in the host, architecture writeup exists, existing functionality still green.
202
206
 
207
+ **Large work:** run `/asf verify` — it mechanically validates the **spec-to-code traceability matrix** (M1): every `met` spec must carry `trace` (outcome → codePath → testFile + assertion), testFile must exist, assertion must appear in it. FAIL rows block delivery. **Verify ingested specs from external planning docs too** — the doc's ✅ markers are claims, not evidence.
208
+
203
209
  1. Run the full test suite (all of it, not a subset); fix failures; re-run until green.
204
210
  2. **Verify the artifact a user would actually get**: inspect the packaged file list
205
211
  (`npm pack` → `tar tzf`), install/load it clean-room in a fresh dir with caches
@@ -241,14 +247,19 @@ Also check the **modularity DoD** from `references/06c-code-quality.md` (Phase 7
241
247
  - ❌ Shipping a module that cannot run/test standalone outside the host
242
248
  - ❌ Refactoring without the architecture writeup (see `references/06c-code-quality.md`)
243
249
  - ❌ Breaking existing functionality during a refactor — refactoring preserves behavior
250
+ - ❌ Marking a spec delivered from the delivery log instead of tracing the code — the log is a claim
251
+ - ❌ Shipping machinery no surface consumes — delivered = visible in the product (UI or API)
252
+ - ❌ Trusting unit tests as proof of wiring — assert the operator-facing outcome end-to-end
253
+ - ❌ Trusting an external plan's ✅ (IMPROVEMENT-PLAN / delivery log) — ingest its items as specs and verify them
254
+ - ❌ Implementing a spec literally when it creates product tension — challenge it and resolve with the user
244
255
 
245
256
  ## References
246
257
 
247
- - `references/01-intake.md` — question bank and probing techniques
258
+ - `references/01-intake.md` — question bank and probing techniques (incl. external planning docs, M6)
248
259
  - `references/02-research.md` — research playbook with search templates
249
260
  - `references/04-adversarial.md` — adversarial checklist per area
250
- - `references/05-plan.md` — PLAN.md template with examples
251
- - `references/06-implementation.md` — coding discipline details
252
- - `references/06b-testing-qa.md` — **mandatory testing & QA standard** (11 rules + definition of done)
261
+ - `references/05-plan.md` — PLAN.md template with examples (incl. spec-to-code traceability matrix)
262
+ - `references/06-implementation.md` — coding discipline details (incl. M4 challenge designs, M5 trace before claiming)
263
+ - `references/06b-testing-qa.md` — **mandatory testing & QA standard** (14 rules + definition of done)
253
264
  - `references/06c-code-quality.md` — **mandatory modularity & maintainability standard** (8 rules, SSOT, testable-standalone, single escalation path)
254
265
  - `references/07-release.md` — release workflow (versioning, CHANGELOG, tags, npm, CI/CD)
@@ -13,6 +13,24 @@ Keep asking until the user confirms. One question at a time is fine; short batch
13
13
  - "What exists today?" (blank slate / existing repo / replaces something)
14
14
  - "How will we know it's done and correct?" (concrete success criteria)
15
15
  - "Any hard constraints?" (stack, platform, budget, timeline, must-nots)
16
+ - "**Are there external planning docs?**" (IMPROVEMENT-PLAN.md, PLAN.md, requirements docs, delivery logs, ticket lists — in the repo, `docs/`, or referenced by the user)
17
+
18
+ ## External planning docs (M6) — locate and ingest
19
+
20
+ When a task references or contains external planning documents, **realize they
21
+ exist**: look in the repo root, `docs/`, and anything the user points at. Then
22
+ **treat them like own captured specs**:
23
+
24
+ 1. Read the doc(s) and list every actionable item (e.g. `IMP-002/006 — service agent titles`).
25
+ 2. `capture_spec` each item with `sourceQuote` pointing at the doc + item id.
26
+ 3. **The doc's own ✅ / "delivered" markers are claims, not evidence** — each
27
+ item gets traced (outcome → codePath → test) and verified like any other spec.
28
+ 4. If the doc is large, ingest by section and decompose with `parentId`.
29
+
30
+ > **Why this matters (betamaxx audit):** the IMPROVEMENT-PLAN's delivery log said
31
+ > "IMP-006+P-A delivered ✅" and that was trusted as ground truth — the code was
32
+ > never traced. `intelligenceBudget.analyze()` shipped as dead code. Ingesting
33
+ > the plan's items as specs forces each one to be traced and verified.
16
34
 
17
35
  ## Probing techniques
18
36
 
@@ -14,6 +14,12 @@ Challenge every spec and design decision like a hostile reviewer. For each item,
14
14
  > testable standalone with the same calls? Any hardcoded values that belong in
15
15
  > config?
16
16
 
17
+ > **Traceability lens (always applied):** for every spec, ask — *what is the
18
+ > operator-facing outcome, and what code path delivers it?* Trace the path in
19
+ > your head: where could **dead wiring** hide (a function defined but never
20
+ > called, an async enrichment that never lands, a surface that never renders)?
21
+ > If the outcome has no concrete path, the spec is not yet real.
22
+
17
23
  - **Edge cases**: empty input, zero data, max load, missing fields, concurrent access, duplicate input, unicode, huge payloads
18
24
  - **Failure modes**: what breaks first? Is failure loud or silent? Can we recover automatically?
19
25
  - **Security**: authentication, authorization, injection (SQL/XSS), data exposure, secrets, abuse/rate-limiting, supply chain
@@ -29,6 +29,14 @@ Decided approach with rationale. Cite the research (package names, URLs).
29
29
  - **Layering**: one-way dependency rules between layers; what each layer may/may not import
30
30
  - **Standalone testability**: how each module is exercised outside the host with the same calls
31
31
 
32
+ ## Spec-to-code traceability matrix
33
+ For every spec, the operator-facing outcome, the code path that delivers it, and the test that asserts it. This is what `/asf verify` checks mechanically at delivery (M1).
34
+
35
+ | Spec | Operator-facing outcome | Code path | Test (asserts outcome) |
36
+ |------|------------------------|-----------|------------------------|
37
+ | spc-… | e.g. "ingested report has an agent_title on the card" | ingest → analyze() → persist → render | tests/e2e-ingest.test.ts (`agent_title`) |
38
+ | … | | | |
39
+
32
40
  ## Milestones
33
41
  | # | Milestone | Exit criteria |
34
42
  |---|-----------|---------------|
@@ -60,3 +60,26 @@ Every user-visible change gets an entry under `## [Unreleased]` or the released
60
60
  - New hard requirement discovered → `capture_spec` immediately
61
61
  - Requirement changed → capture superseding spec (old → obsolete)
62
62
  - When pi-vigilant injects its verification checklist → respond with `update_spec_status` + evidence
63
+
64
+ ## Challenge approved designs during implementation (M4)
65
+
66
+ A plan was approved, but that does not make it correct. When a spec's literal
67
+ reading creates **product tension** (e.g. feedback clusters rendered under the
68
+ "Plan" tab when "Plan = plans"), **stop and resolve it with the user before
69
+ implementing** — do not implement blindly and call it delivered.
70
+
71
+ - Tension detected → state it plainly, propose the fix, get a decision.
72
+ - Implement the *sensible* version, not the literal-but-wrong one.
73
+ - The user's words "implemented in a SENSIBLE way" are the acceptance bar.
74
+
75
+ ## The delivery log is a claim; the code is the evidence (M5)
76
+
77
+ Before marking anything ✅ (a spec, a milestone, a delivery-log item):
78
+
79
+ 1. **Trace the actual code path** — the function is called, not just defined.
80
+ 2. Confirm the output is **consumed by a surface** (Rule 13).
81
+ 3. Confirm the **operator-facing outcome test** passes (Rule 12).
82
+
83
+ A delivery log saying "IMP-006 delivered ✅" proves nothing. The code is the
84
+ evidence. External planning docs (IMPROVEMENT-PLAN.md, PLAN.md, delivery logs)
85
+ are inputs to spec capture — their ✅ markers are claims, never ground truth.
@@ -125,6 +125,13 @@ API/curl checks do not replicate what a human sees. Via `pi-aia-browser`:
125
125
 
126
126
  A 200 response with a blank or broken page is a **failure**.
127
127
 
128
+ **Silent async paths are the priority.** Fire-and-forget enrichment (an async
129
+ budgeted analysis that fills in a card title after render) fails silently — the
130
+ page renders, nobody notices the enrichment never landed. For any async/silent
131
+ surface: ingest through the real flow, then **wait for the enrichment to appear**
132
+ and assert it (e.g. ingest a report, wait for `agent_title` on the card). If it
133
+ never arrives, the wiring is dead — that is a failed test.
134
+
128
135
  ## Rule 10 — Definition of done (all must hold)
129
136
 
130
137
  - [ ] Typecheck/build passes
@@ -135,6 +142,9 @@ A 200 response with a blank or broken page is a **failure**.
135
142
  - [ ] Observable end state verified as a user would experience it
136
143
  - [ ] Web surfaces exercised through a real browser
137
144
  - [ ] Every MUST spec `met` with concrete evidence (`update_spec_status`)
145
+ - [ ] **Spec-to-code traceability: every `met` spec carries `trace` (outcome → codePath → testFile + assertion); `/asf verify` mechanically validates it (large work)**
146
+ - [ ] **Consumed by a surface: every delivered feature's output is visible in the product (UI or API) — nothing ships as dead machinery**
147
+ - [ ] **E2E behavioral test: every feature spec has a test through the real entry point asserting the operator-facing outcome**
138
148
  - [ ] Unverifiable specs → `partial` + asked the user (never self-certified)
139
149
 
140
150
  ## Rule 11 — Report honestly
@@ -144,3 +154,43 @@ A 200 response with a blank or broken page is a **failure**.
144
154
  - If a check was skipped or inconclusive, **say so explicitly** and say why.
145
155
  - Distinguish "tests pass" from "feature works for the user" — Rule 2.
146
156
  - If you discover you shipped something broken, say it plainly and fix it first.
157
+
158
+ ## Rule 12 — Test the OPERATOR-FACING OUTCOME, not the machinery
159
+
160
+ > **Real failure (betamaxx audit):** `intelligence-budget.test.ts` and
161
+ > `service-session.test.ts` passed — they proved `analyze()` *works in
162
+ > isolation*. Nobody tested "does ingest actually call `analyze()`?". The tests
163
+ > proved the machinery, not the wiring. `intelligenceBudget.analyze()` shipped
164
+ > as **dead code** while every unit test was green.
165
+
166
+ Unit tests prove a component works in isolation. They do **not** prove the
167
+ feature is wired. For every feature spec, the deciding test is the one that
168
+ asserts the **operator-facing outcome** end-to-end:
169
+
170
+ - ❌ "`analyze()` returns a budget verdict" (machinery)
171
+ - ✅ "an ingested report eventually has an `agent_title` rendered on the card" (outcome)
172
+
173
+ If a unit test is green but the outcome test is missing, the feature is **not**
174
+ done — the wiring may be dead.
175
+
176
+ ## Rule 13 — Nothing is delivered until CONSUMED BY A SURFACE
177
+
178
+ > **Real failure (betamaxx audit):** code commented *"Not yet consumed by a
179
+ > surface"* shipped as "delivered". Machinery (budget, runner, verdict
180
+ > contract) was built; the product behavior (cards titled by the agent) never
181
+ > happened.
182
+
183
+ - **Delivered = the output is visible in the product** (UI or API).
184
+ - Code commented "not yet consumed by a surface" is, by definition, **not
185
+ delivered**.
186
+ - Before marking a spec `met`, answer: *where does a user/operator see this?*
187
+ If nowhere — it is not done.
188
+
189
+ ## Rule 14 — Every feature spec ships an E2E behavioral test through the REAL entry point
190
+
191
+ - The E2E test drives the **real entry point** (HTTP endpoint, CLI, UI) with a
192
+ mocked boundary (agent, DB), and asserts the operator-facing outcome.
193
+ - This catches dead wiring in one test: ingest → `analyze()` → persist → render.
194
+ - **Scale note:** mandatory for feature specs in **large/gated work**. For small
195
+ work, required only when the change touches a surface/wiring; otherwise the
196
+ standard test-first rules above suffice.