diff --git a/.claude/hooks/set-writes-scope.cjs b/.claude/hooks/set-writes-scope.cjs index a50b865..d93a83f 100644 --- a/.claude/hooks/set-writes-scope.cjs +++ b/.claude/hooks/set-writes-scope.cjs @@ -166,8 +166,18 @@ function pathsFromPlanFiles(file) { // Boundary 2 — CUE fallback for a HEAD-LESS prose exclusion intro (`Files NOT written:`), anchored // to a NON-path line so an authorized item's own description ("… the public API is not touched") // never trips it. `\W*` (not `\s+`) tolerates markdown markup, e.g. `**not** touched`. + // A blockquote line (`> …`) is EXPLANATORY commentary, never an exclusion-section intro, so it is + // EXEMPT: an explanatory note under `## Files` that mentions "not touched" (e.g. a reference to the + // `### Explicitly not touched` subsection) must NOT truncate the authorized list. Only blockquotes + // are exempted, so a head-less NON-blockquote intro (`Files NOT written:`) and an exclusion-only + // `## Files` still fail closed (CF-E, .dev/features/product-pipeline-probe/PROBE.md). const isPathItem = /^\s*-\s+`[^`]+`/.test(line); - if (!isPathItem && /\bnot\W*(touch|writ|modif|edit|chang)|\bexplicitly\W*excluded|\bout\W*of\W*scope|\boff\W*limits/i.test(line)) { + const isBlockquote = /^\s*>/.test(line); + if ( + !isPathItem && + !isBlockquote && + /\bnot\W*(touch|writ|modif|edit|chang)|\bexplicitly\W*excluded|\bout\W*of\W*scope|\boff\W*limits/i.test(line) + ) { break; } const m = line.match(/^\s*-\s+`([^`]+)`/); diff --git a/.claude/hooks/set-writes-scope.test.cjs b/.claude/hooks/set-writes-scope.test.cjs index bff1412..2b5242e 100644 --- a/.claude/hooks/set-writes-scope.test.cjs +++ b/.claude/hooks/set-writes-scope.test.cjs @@ -123,6 +123,50 @@ test("--from-plan on a /pharn-plan-shaped PLAN (## Files with back-tick paths) e assert.equal(rec.scope.includes("src/should-not-leak.ts"), false); }); +// --- CF-E regression: an explanatory BLOCKQUOTE under `## Files` must NOT truncate the authorized list. +// A `> …` note that mentions an exclusion cue ("not touched", e.g. a reference to the +// `### Explicitly not touched` subsection) is explanatory commentary, never an exclusion-section intro. +// Before the fix it tripped the head-less-exclusion Boundary-2 break and zeroed the scope → fail-closed +// exit 1, blocking a valid plan (CF-E, .dev/features/product-pipeline-probe/PROBE.md). Only blockquotes are +// exempted — the next test pins that a NON-blockquote head-less intro still fails closed. + +test("--from-plan: a blockquote note with an exclusion cue ABOVE the paths does NOT truncate scope (CF-E)", () => { + const cwd = tmp(); + const plan = join(cwd, "PLAN.md"); + fs.writeFileSync( + plan, + [ + "## Files", + "", + "> Explanatory note: the files below are written; others are not touched (see the", + "> `### Explicitly not touched` subsection).", + "", + "- `src/widget.ts` — the new widget module", + "", + "### Explicitly not touched", + "", + "- `src/legacy.ts` — reused, never edited", + "", + ].join("\n") + ); + const r = setter(cwd, "--from-plan", plan); + assert.equal(r.status, 0); // the blockquote no longer fails it closed + const rec = JSON.parse(fs.readFileSync(join(cwd, ".pharn", "writes-scope.json"), "utf8")); + assert.deepEqual(rec.scope, ["src/widget.ts"]); // the path AFTER the blockquote is collected … + assert.equal(rec.scope.includes("src/legacy.ts"), false); // … and the `### Explicitly not touched` path stays excluded +}); + +test("--from-plan: a NON-blockquote head-less exclusion intro (`Files NOT written:`) still fails closed", () => { + const cwd = tmp(); + const plan = join(cwd, "PLAN.md"); + // No authorized path precedes the head-less intro, so Boundary 2 (preserved for non-blockquotes) breaks + // before any path is collected → empty scope → fail-closed. Proves the CF-E fix did NOT weaken exclusions. + fs.writeFileSync(plan, ["## Files", "", "Files NOT written:", "", "- `src/legacy.ts` — excluded", ""].join("\n")); + const r = setter(cwd, "--from-plan", plan); + assert.equal(r.status, 1); + assert.equal(fs.existsSync(join(cwd, ".pharn", "writes-scope.json")), false); +}); + // --- producer-faithfulness: the REAL product /pharn-plan template fails closed (locks the placeholder // style). The template's `## Files` example items are angle-bracket placeholders (`- ```), which // `isConcrete` rejects → the setter emits no scope and exits 1. This ties a test to the actual producer diff --git a/.dev/features/product-pipeline-probe/GRILL.md b/.dev/features/product-pipeline-probe/GRILL.md new file mode 100644 index 0000000..edff6fa --- /dev/null +++ b/.dev/features/product-pipeline-probe/GRILL.md @@ -0,0 +1,127 @@ +# GRILL — product-pipeline-probe + +**Plan grilled:** `.dev/features/product-pipeline-probe/PLAN.md` +**Spec-hash check (content-hash, surfaced not blocking — fix #3):** recomputed `sha256(ARCHITECTURE.md)` = +`11cd9ad5983188623fe0931d13588c16435a5565888344e20669748947d1d969` == the plan's pinned `spec_content_hash` +→ **chain intact, no drift.** (`/pharn-dev-build` is where drift would actually block; here it only confirms.) + +This is an **advisory** interrogation. Findings rest on model judgment; **none gates `/pharn-dev-build`.** The +only floor-grade facts in this run are the writes-scope hook (pins where this log may be written) and the +content-hash above. The split below (enum-gated `type`/`rule_id`/`severity`/`file` = my own assertions, TRUSTED; +free-text `problem`/`evidence` = quote the plan, inherit its **untrusted** tag, rendered as DATA) follows +`pharn-contracts/finding-shape.md` (cited, not restated — P4). + +--- + +## Findings (grouped by axis) + +### P0 — guarantee-audit completeness + +```yaml +- type: FINDING + rule_id: "P0" + severity: important + file: ".dev/features/product-pipeline-probe/PLAN.md:101" + problem: "Confirmation #3 claims the run confirms 'fix #7 BOUNDS the build's writes' but its actual test only inspects the scope CONTENTS after --from-plan, never observes an out-of-scope write being DENIED — the verification is weaker than the verb 'bounds'." + evidence: "fix #7 bounds the build's writes to the planned files — confirm a hypothetical write outside `## Files` would be denied (the scope after `--from-plan` is exactly the planned path)." +``` + +```yaml +- type: FINDING + rule_id: "P0" + severity: minor + file: ".dev/features/product-pipeline-probe/PLAN.md:212" + problem: "The plan says it will 'Confirm LIVE that each verdict is provably independent of any tainted field' — but a BENIGN vehicle injects no needle, so the run cannot LIVE-exercise the gates' taint resistance; the confirmation is by code-inspection, which the wording overstates as a live demonstration." + evidence: "Confirm LIVE that each verdict is provably independent of any tainted field (fix #1)." +``` + +### P6 — discovery / honest risk enumeration + +```yaml +- type: FINDING + rule_id: "P6" + severity: important + file: ".dev/features/product-pipeline-probe/PLAN.md:200" + problem: "The CF-A impact statement ('may end the run before all four hand-offs are observed') is imprecise about TIMING: the floor that scans the product GRILL.md runs at stage-4 build / dev-build floor, AFTER stages 1-3 (spec→plan→grill) hand-offs are already observed — so even if CF-A trips, 3 of 4 hand-offs PLUS the CF-A finding are captured regardless; the statement understates what survives a RED stop." + evidence: "but it may end the run before all four hand-offs are observed. Recorded, not hidden." +``` + +```yaml +- type: FINDING + rule_id: "P6" + severity: important + file: ".dev/features/product-pipeline-probe/PLAN.md:200" + problem: "The plan's RED-risk list names ONLY CF-A (validate CHECK 5 on the GRILL.md) but omits a SECOND, independent RED path: the product artifacts on the scanned root-features/ surface are also subject to lint:md (markdownlint) + format:check (prettier) when /pharn-dev-regress and /pharn-dev-verify re-run `npm run check`; if a stock pharn-* command emits markdown that is not style-clean, dev-verify goes RED with no relation to CF-A — an untested interaction not in the risk list." + evidence: "if the product `GRILL.md` ... lacks the split-doc strings, `validate.mjs` CHECK 5 (fix #1) trips → dev-build's floor goes **RED**" +``` + +```yaml +- type: FINDING + rule_id: "P6" + severity: minor + file: ".dev/features/product-pipeline-probe/PLAN.md:200" + problem: "The plan conflates 'dev-build's floor' with the product /pharn-build's own Step-4 floor — BOTH run `validate.mjs .` and both scan the just-written GRILL.md, so CF-A would first trip at the PRODUCT build's floor (stage 4), not at the outer dev-build floor; the plan should attribute the first RED to the correct stage." + evidence: "If a stock `/pharn-grill` `GRILL.md` lacks those strings → RED floor at dev-build (a surfaced finding, not a plan failure)." +``` + +### P1 — acceptance-criterion coverage + +```yaml +- type: FINDING + rule_id: "P1" + severity: minor + file: ".dev/features/product-pipeline-probe/PLAN.md:173" + problem: "The advisory acceptance-criterion check (greet('World')==='Hello, World!') is described as 'checkable' but the plan never commits to actually RUNNING it and recording its result in PROBE.md — so the AC the SPEC carries through the whole chain risks being carried but never exercised, weakening 'real enough that there's a SPEC to approve'." + evidence: 'node -e "import(''./features/probe-greeting/greet.mjs'').then(m => process.exit(m.greet(''World'')===''Hello, World!''?0:1))" → exit 0 (advisory; not a floor gate ...)' +``` + +### P7 / P3 — scope honesty (no findings that block) + +No new finding. The increment is one coherent axis (the product-pipeline integration probe); the vehicle is +trivial and triggered by a real gap (the product chain has never run as a chain); CF-A…CF-D fixes are correctly +deferred to separate increments; `greet.mjs` is import-free (no sibling-import risk). The two ``s +(`product-pipeline-probe` dev increment + `probe-greeting` product vehicle) are inherent to the nesting and the +plan is explicit about both — not a bundling smell. + +### P5 — determinism + +No finding. Every product gate the plan relies on is a membership/equality test (state enum, content-hash +equality, `## Files` path membership); the one irreducible judgment (intent approval) has the human halt as its +terminal fallback (CF-B). The vehicle is pure. + +--- + +## Prose summary + +The plan is unusually self-aware for a meta-increment: it pins the spec hash correctly (no drift), it labels +the integration claim as **evidence, not guarantee** (P0 honored), and it surfaces four real candidate findings +(CF-A…CF-D) **before running** — which is the probe working as designed. The grill did not find a single +unlabeled guarantee or a determinism gap. + +What it found instead is a cluster of **honesty-of-verification** gaps — places where the plan's prose claims +slightly more than its method delivers: + +- **The strongest concern (P6, important):** the RED-risk list is incomplete. CF-A (validate CHECK 5 on the + GRILL.md) is well-described, but a **second independent RED path** — `lint:md` + `format:check` over the + product artifacts when `/pharn-dev-verify` re-runs `npm run check` — is unmentioned. The run could go RED at + verify for markdown-style reasons that have nothing to do with the trust-split. Whoever runs the build should + expect TWO ways the floor can stop the chain, not one. +- **Timing imprecision (P6, important):** "may end the run before all four hand-offs are observed" undersells the + audit value of a RED stop — stages 1-3 are observed before any floor scans the GRILL.md, so the measurement + survives even a CF-A RED. +- **Soft confirmations (P0/P1, important→minor):** "fix #7 bounds the writes" is tested only by inspecting the + scope contents (not by observing a deny); "confirm taint independence" is code-inspection on a needle-free + vehicle (not a live adversarial test); the acceptance-criterion check is described but not committed to being + run. None is wrong, but each claims a notch more rigor than the benign vehicle can deliver. + +None of these blocks the build. They are refinements the operator should fold into how the run is conducted and +how PROBE.md states its conclusions — most importantly, **expect two RED paths (CF-A and the style gates), and +record that stages 1-3 are captured regardless of a stage-4 RED.** + +--- + +**ADVISORY VERDICT: 7 concerns raised (0 blocking-severity, 4 important, 3 minor) — for the human to weigh +before `/pharn-dev-build`.** The spec→plan chain is intact (content-hash verified). The grill gates nothing; the +only floor-grade results this run produced are the writes-scope pin on this log and the spec-hash match above. +"Grill produced a GRILL.md" does **not** mean the plan is good — it means the chain held and these concerns were +surfaced for the human (P0). diff --git a/.dev/features/product-pipeline-probe/PLAN.md b/.dev/features/product-pipeline-probe/PLAN.md new file mode 100644 index 0000000..25c5203 --- /dev/null +++ b/.dev/features/product-pipeline-probe/PLAN.md @@ -0,0 +1,286 @@ +# PLAN — product-pipeline-probe (first end-to-end run of the PRODUCT pipeline) + +- spec_content_hash: 11cd9ad5983188623fe0931d13588c16435a5565888344e20669748947d1d969 # fix #4 — sha256(ARCHITECTURE.md), computed LIVE this run (P6); identical to .dev/features/pipeline-integration-probe/PLAN.md:3 → no drift since probe #14 +- increment: push ONE trivial throwaway feature through the first four PRODUCT stages `/pharn-spec → /pharn-plan → /pharn-grill → /pharn-build`, observing every hand-off — to **measure whether the product chain integrates** (it currently never has run as one chain). The feature is a **vehicle**; the deliverable is a **measured chain run with every hand-off observed + any mismatch surfaced**, not the feature. +- layer(s): the **dev-loop** artifact is `.dev/features/product-pipeline-probe/PROBE.md` (a build-loop audit trail — NOT a Capability, no `role:`; under `.dev/`, which `validate.mjs:30` excludes wholesale). The **product** vehicle (`features/probe-greeting/greet.mjs`, a `.mjs` → `validate.mjs:53` only walks `.md`, so it is floor-invisible) and the product pipeline artifacts are run-time outputs of the `pharn-*` sub-commands, not of this dev-build. # ARCHITECTURE.md §4, §6 +- constitution_refs: [P0, P2, P5, P6, P7] + +--- + +## What this increment IS (read this first — it is unusual) + +This is the **product-loop analogue of `.dev/features/pipeline-integration-probe/` (dev probe #14)**. #14 +pushed a vehicle through the **dev** loop (`/pharn-dev-*`); this pushes a vehicle through the **product** +loop (`/pharn-*`). The four product stages are each built + unit-tested in isolation but have **NEVER run +together as one chain on one feature**. Integration is an **assumption**; this run measures it. + +**The structure is NESTED, by construction (invoked via `/pharn-dev-ship`):** + +- the **outer** dev loop (`/pharn-dev-plan` → this PLAN → human approves → `/pharn-dev-grill` → + `/pharn-dev-build` → `/pharn-dev-regress` → `/pharn-dev-verify` → `/pharn-dev-review`) manages the + increment and gives it an audit trail in `.dev/features/product-pipeline-probe/`; +- the **inner** product loop is the **build WORK** that `/pharn-dev-build` performs: it invokes the real + `/pharn-spec → /pharn-plan → /pharn-grill → /pharn-build` on the vehicle and records each hand-off into + `PROBE.md` (the one file dev-build writes directly). + +Consequence to expect (see CF-B): the product `/pharn-spec` **halts for a human approval INSIDE +`/pharn-dev-build`** — a human gate the `/pharn-dev-ship` chain does not natively model. So this run pauses +for the human **twice more** after plan approval: once to approve the product SPEC (mid-dev-build), and +finally at the post-review GATE 2. + +--- + +## Step 0 — Discovery results (live this run, P6 — never asserted from memory) + +**The gap this run targets, confirmed on disk (not speculation):** + +- **The product chain has never run as a chain.** Root `features/` contains only `README.md`, `ship-gated/`, + `ship-loop/` (the latter two hold **dev**-loop artifacts — see CF-D). There is **no product `SPEC.md`, + `GRILL.md`, or `BUILD.md` anywhere** — so `/pharn-spec`, `/pharn-grill`, `/pharn-build` have never emitted a + live product artifact, and the spec→plan→grill→build hash chain has never been traversed end-to-end. + +**State that shapes the run (read live):** + +- **Floor GREEN — 1 capability** (`node .dev/floor/validate.mjs .` → `FLOOR: GREEN — 1 capabilities checked in .`, + exit 0). `validate.mjs:30` `EXCLUDE_SEGMENTS` = `.claude/commands/`, **`.dev/`**, `node_modules/`, `.git/`. + Root `features/` is **NOT** excluded → product artifacts there are **scanned** (this drives CF-A). `walk()` + (`validate.mjs:53`) collects **only `.md`** → a `.mjs` vehicle is invisible to the floor regardless of location. +- **Spec hash matches** the live recompute and the most-recent pin (`pipeline-integration-probe/PLAN.md:3`) → + no drift; `/pharn-dev-build` re-verifies (fix #4). +- **Live git state (P6), drives the `/pharn-dev-regress` base rule:** working tree **clean**, on `main`, + `HEAD = 31fcefb`. `/pharn-dev-regress`'s auto-detect resolves base = `HEAD` only when the tree is **dirty**; + a clean tree resolves base = `merge-base = HEAD` → empty `inside` (useless). The run therefore uses the + **dirty-tree dogfood flow** (commit discipline below) — exactly as probe #14 did. +- **Four product commands read live + cross-checked** (`.claude/commands/pharn-{spec,plan,grill,build}.md`): + their declared `reads:`/`writes:` and floor checkers agree on the hand-off shapes (matrix below). All four + cite the SAME chain checkers (`check-spec.mjs`, `check-spec-approved.mjs`, `check-plan-spec-agree.mjs`). +- **`.pharn/writes-scope.json` exists** (leftover from a prior command, `set_at` 2026-06-30). Each stage + re-sets it at its own Step 0 (mutable, not a stack) — drives CF-C. + +--- + +## The feature (a VEHICLE — keep it trivial, P7) + +A single pure function, the smallest thing that is real enough to have an Acceptance Criterion the chain can +carry and that `/pharn-build` must actually write: + +- **`features/probe-greeting/greet.mjs`** — exports a pure, deterministic `greet(name)` returning the string + `` `Hello, ${name}!` ``. Zero imports, no I/O, no network. A `.mjs` file → **floor-invisible** (`validate.mjs` + walks only `.md`), so it keeps the floor trivially GREEN, capability count unchanged at 1. Header comment + states it is a **throwaway integration-probe vehicle scheduled for revert** (disposition is Open Question 2). +- **Acceptance Criterion (carried in the product SPEC):** `greet("World") === "Hello, World!"`. Checkable by a + one-line `node -e` — **advisory** (there is no test runner wired for `features/`; the AC check is manual, not + a floor gate). The vehicle **makes no guarantee claim** → it owes no P0 reduction itself. + +**This is not a Capability** (no `role:` frontmatter), so P1's "no Capability ships without `evals/`" does not +bind it — identical to how probe #14's `floor/exit-label.mjs` shipped without `evals/`. + +--- + +## The integration-probe protocol (the real deliverable — observe every hand-off) + +`/pharn-dev-build` runs each PRODUCT stage **with its real command, in order, no shortcuts** (invoke the +`pharn-spec` / `pharn-plan` / `pharn-grill` / `pharn-build` skills — do not simulate a stage). At each hand-off +record _what the stage emits_ and _whether it is the shape the next stage consumes_. A mismatch (stage N emits +X, stage N+1 expects Y) is a **real integration finding** — surfacing it is the entire point. + +| # | product stage | sets scope to (fix #7) | consumes | emits | hand-off check (verify LIVE) | +| --- | -------------- | ------------------------------------------------------------------------- | ------------------------- | ---------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | +| 1 | `/pharn-spec` | `features/probe-greeting/SPEC.md` | the user's prose intent | Draft `SPEC.md` → (HUMAN APPROVES) → Approved | **GATE halts** (CF-B); on approval `state:Approved`, `spec_id`, `spec_content_hash == sha256(body)` pinned via `check-spec.mjs --hash` | +| 2 | `/pharn-plan` | `features/probe-greeting/PLAN.md` | the Approved `SPEC.md` | `PLAN.md` carrying `spec_id`+`spec_content_hash` | `check-spec-approved.mjs` GATE passes (Approved + un-drifted); the emitted `## Files` parses to `greet.mjs` (so `/pharn-build --from-plan` scopes it) | +| 3 | `/pharn-grill` | `features/probe-greeting/GRILL.md` | the `PLAN.md` + `SPEC.md` | `GRILL.md` (chain result header + advisory findings) | `check-plan-spec-agree.mjs` re-verifies the carried hash == current SPEC body hash → GREEN; interrogation advisory, does NOT block | +| 4 | `/pharn-build` | product `## Files` → `greet.mjs`, then `features/probe-greeting/BUILD.md` | the `PLAN.md` + `SPEC.md` | `greet.mjs` + `BUILD.md` | `check-plan-spec-agree.mjs` re-verifies AGAIN (2nd consumer); fix #7 bounds the write to `greet.mjs`; a write outside `## Files` is DENIED | + +**The four specific confirmations the run owes (from the WHAT-TO-TEST brief):** + +1. **The hash chain holds spec→plan→grill→build** — all four agree on `spec_content_hash`: `/pharn-spec` pins + it = sha256(SPEC body); `/pharn-plan` carries it verbatim; `/pharn-grill` re-verifies (1st enforcing consumer); + `/pharn-build` re-verifies (2nd). Record the actual hash at each stage and that they are equal. +2. **The `## Files` `/pharn-plan` emits is parseable by `/pharn-build`'s `--from-plan` setter** — the + `plan-files-scope` fix working in situ: `set-writes-scope.cjs --from-plan` exits 0 and the scope = `greet.mjs`. +3. **fix #7 bounds the build's writes to the planned files** — confirm a hypothetical write outside `## Files` + would be denied (the scope after `--from-plan` is exactly the planned path). +4. **The human-approval gate on the SPEC actually halts** — `/pharn-spec` does NOT self-approve; it ends its + turn for the human (CF-B). + +**writes-scope across stages (the fix #7 propagation check, CF-C).** Each product stage re-runs its own Step 0 +setter, **overwriting** `.pharn/writes-scope.json`. Confirm: (a) every stage sets its own scope before writing; +(b) a **stale** scope from a prior stage never blocks a legitimate next-stage write; (c) `/pharn-dev-build` +itself must **re-set its scope** (from this plan's `## Files`) after the product pipeline clobbered it, before +writing `PROBE.md`. If a stale scope blocks any stage, that is a **real finding**. + +**Commit discipline (required — derived from `/pharn-dev-regress` base detection, P6 — see CF-1-amplified).** +`/pharn-dev-regress`'s `scope` check asserts `inside ⊆ declared (## Files)`, where `inside = git diff + +untracked`. This dev-build legitimately produces (via the product sub-commands) a whole tree of product +artifacts (`features/probe-greeting/{SPEC,PLAN,GRILL,BUILD}.md`, `greet.mjs`) that are **correctly absent** from +this plan's `## Files` (they are the sub-commands' outputs, not dev-build's). To keep them out of `inside`, +**commit the product artifacts as they are produced** (and `PROBE.md`'s siblings), leaving **only** +`.dev/features/product-pipeline-probe/PROBE.md` (this plan's one `## Files` entry) uncommitted when +`/pharn-dev-regress` runs. Then dirty tree → base = `HEAD` → `inside = {PROBE.md}` = `## Files` → no false escape. + +--- + +## Files + +> `## Files` is `/pharn-dev-build`'s writes-scope source (fix #7): dev-build runs +> `set-writes-scope.cjs --from-plan` over the back-tick paths below; they become the only paths dev-build writes +> **directly** (plus `.pharn/**`). The product vehicle + the product pipeline artifacts are declared below the +> path list — they are written by the `pharn-*` sub-commands under **their own** scopes, exactly as probe #14 +> handled its per-stage artifacts. _(CF-E: the original wording here tripped the setter's exclusion-cue scan — +> recorded in PROBE.md; fix is a SEPARATE increment.)_ + +- `.dev/features/product-pipeline-probe/PROBE.md` — **NEW.** The integration-probe report: the filled hand-off + matrix above, the four confirmations, the actual hashes observed at each stage, and the CF-A…CF-D findings + confirmed/refined during the run. The **real deliverable**. Under `.dev/` → excluded from `validate.mjs` → + cannot itself trip CHECK 5 even though it quotes product findings. + +### Explicitly **not** touched (declared NOT written — keeps them out of dev-build scope) + +- `ARCHITECTURE.md`, `CONSTITUTION.md`, `THREAT-MODEL.md`, `LIMITS.md` — human-only (hook-denied, fix #2). Any + doc-vs-impl gap this run finds is **reported for a human**, never agent-edited. +- `.claude/commands/pharn-{spec,plan,grill,build}.md`, the floor checkers, the hooks, `package.json` — + **unchanged.** The probe _invokes_ the existing product stages; it edits none of them. **If a stage reveals a + bug (CF-A…CF-D), that fix is a SEPARATE increment** — do not fix inline and muddy the integration test. +- `features/probe-greeting/greet.mjs` — the **product vehicle**, written by the product `/pharn-build` under the + **product** plan's `## Files` scope (`--from-plan`), NOT by this dev-build. Listed here only to declare it is + intentionally out of THIS plan's scope. +- `features/probe-greeting/{SPEC,PLAN,GRILL,BUILD}.md` — run-time outputs of `/pharn-spec` / `/pharn-plan` / + `/pharn-grill` / `/pharn-build`, each written under that command's own writes-scope — **not** dev-build + deliverables. +- The dev-loop's own downstream artifacts (`GRILL.md`, `REGRESSION.md` + `regression-report.json`, `VERIFY.md` + + `verify-report.json`, `REVIEW.md`, `SHIP.md`) are written by `/pharn-dev-grill` / `-regress` / `-verify` / + `-review` / `-ship` under their own scopes — not listed here. + +--- + +## Contracts satisfied + +- `ARCHITECTURE.md §6` (the pipeline spine `spec → plan → grill → build → …` + its typed-artifact table) — this + run is the **first end-to-end traversal of the PRODUCT half of that spine** on one feature; it observes each + row's artifact actually being produced and consumed. Cited, not restated (P4). +- `ARCHITECTURE.md §8` / `pharn-contracts/finding-shape.md` (the enum-gated / free-text split) — the run + confirms LIVE that the product chain gates (`check-spec*.mjs`, `check-plan-spec-agree.mjs`) read only + enum-gated / floor-verifiable fields (state enum, content-hash, section presence), never tainted free text. +- fix #4 (content-hash pin carried through the chain) and fix #7 (writes-scope from the plan's `## Files`) — the + run exercises both across all four product stages on a live feature. Cited, not restated (P4). + +--- + +## Evals to write (P1) — binds the vehicle's check, not a Capability + +`greet.mjs` is a non-Capability vehicle (no `role:`), so P1's Capability-evals rule does not bind it (identical +to probe #14's `floor/exit-label.mjs`). Its proof is the advisory Acceptance-Criterion check: + +- `node -e "import('./features/probe-greeting/greet.mjs').then(m => process.exit(m.greet('World')==='Hello, World!'?0:1))"` + → exit 0 (advisory; not a floor gate — there is no wired test for `features/`). +- **Floor check after the product run:** `node .dev/floor/validate.mjs .` must still print `GREEN — 1 capabilities` + (count unchanged — `.mjs` invisible) **unless CF-A trips on the product `GRILL.md`** (see Guarantee audit). +- **The real eval is the pipeline run itself:** the deliverable is the observed hand-off matrix + the four + confirmations, with any mismatch surfaced as a finding in `PROBE.md`. + +--- + +## Guarantee audit (P0) — state the deliverable honestly + +- **"The four product stages integrate / the hash chain holds spec→plan→grill→build / fix #7 bounds the build"** + → this run produces **evidence** (a measured chain run, every hand-off observed), **NOT a guarantee**. The only + floor-grade facts it yields are the deterministic verdicts the stages themselves emit (`check-spec.mjs`, + `check-spec-approved.mjs`, `check-plan-spec-agree.mjs` exit codes; the fix #7 writes-scope hooks; the + dev-loop's own `validate` / `check-regress` / `check-verify` verdicts). The _claim_ "the product chain + integrates" is **advisory** — first evidence the chain runs as a chain, not a proof it is bug-free. +- **The vehicle is meaningless by design (P7).** This run does NOT make `greet.mjs` a meaningful capability. + Writing "the product pipeline is proven" or "build succeeded therefore greet is correct" would be the P0 + disease — `/pharn-build` guarantees only "built within scope from a current approved plan," never correctness. +- **What IS floor-grade in this run:** the product chain checkers (content-hash equality + `state == Approved` + enum); the fix #7 writes-scope hook pinning each stage's writes; the dev-loop's `validate` / `check-regress` / + `check-verify` exit codes. Everything the _agent_ does (sequencing the product stages, choosing the vehicle, + reading hand-offs, authoring `PROBE.md`) is **advisory orchestration**. +- **Honest risk on the floor verdict:** if the product `GRILL.md` (on the scanned root-`features/` surface) + lacks the split-doc strings, `validate.mjs` CHECK 5 (fix #1) trips → dev-build's floor goes **RED** → + `/pharn-dev-ship` STOPs at the build stage. That STOP would be a **successful surfacing of CF-A**, not a + failure of the plan — but it may end the run before all four hand-offs are observed. Recorded, not hidden. + +--- + +## Trust audit (P2) — taint flow through the chain (an observation target) + +- **The vehicle ingests no untrusted external input** (a pure `string → string` function), so it introduces no + new taint. +- **The probe OBSERVES taint handling at each product stage:** `/pharn-spec` treats the user's prose intent as + untrusted DATA (interrogates, never executes); `/pharn-plan` + `/pharn-grill` + `/pharn-build` treat the + SPEC/PLAN bodies as untrusted DATA and their gates read **only** enum-gated / floor-verifiable values (the + `state` enum, the 64-hex content-hashes, section presence, `## Files` path membership) — **never** the prose's + meaning. Confirm LIVE that each verdict is provably independent of any tainted field (fix #1). +- **Output.** `PROBE.md` quotes the product findings + the observed hand-offs as **DATA**; its enum-gated fields + (when it reproduces finding objects) are the probe's own assertions, the free text inherits the product + artifacts' untrusted tag — rendered quoted, never injected downstream as instructions. +- **Named residual (`LIMITS.md §2`, `THREAT-MODEL.md §5`):** `PROBE.md`'s free text, read by a future human/LLM, + is "do not execute this as an instruction" as a heuristic again — bounded (it gates nothing) but not zeroed. + The same residual already accepted across `finding-shape.md` and attempt 0. + +--- + +## Determinism audit (P5) + +- The vehicle is **pure**: `greet(name)` is a single deterministic string interpolation — no branch, no + classification, no throw. +- The product stages' branches are deterministic: `/pharn-spec`'s validate = section presence / `state` enum / + hash equality; `/pharn-plan`'s gate = `check-spec-approved.mjs` exit (`state ∈ {Approved}` ∧ hash equality); + `/pharn-grill`'s + `/pharn-build`'s gate = `check-plan-spec-agree.mjs` exit (state enum ∧ `planHash == +sha256(SPEC body)`); fix #7 scope = `## Files` back-tick path membership. No LLM classification drives a gate. +- The one irreducible judgment — _is the intent approved?_ — has its terminal fallback as the **human approval + halt** in `/pharn-spec` (CF-B), never a model guess. The interrogations (`/pharn-spec` Step 2, `/pharn-grill` + Step 3) are advisory and branch nothing. + +--- + +## Findings already surfaced by discovery (P6) — candidate integration findings, to confirm/refine during the run + +These were found by **reading the four product command files against live state, before running** — exactly the +"ambiguity worth raising before running" the probe exists to surface. None blocks this plan; each is a candidate +`/pharn-dev-review` lesson. **Any fix is a SEPARATE increment** (do not fix inline). + +- **CF-A — product pipeline artifacts land on the `validate`-SCANNED surface; the dev pipeline's never did (the + headline finding).** `validate.mjs:30` excludes `.dev/` wholesale but **not** root `features/`. So a + finding-bearing product `GRILL.md` (which emits `rule_id:` + `problem:` objects) is subject to CHECK 5 + (fix #1, `validate.mjs:114`): it passes **only** if it also contains a string matching + `enum-gated|floor-verifiable` **and** one matching `free[- ]text|untrusted` (empirically why the existing + `features/ship-*/REVIEW.md` pass and the floor is GREEN). The **dev** loop never exercised this — its + artifacts live in excluded `.dev/features/`. So the product loop is the **first** time pipeline artifacts hit + the scanned surface, and whether a stock `/pharn-grill` `GRILL.md` carries those split strings is **untested + until this run**. _If it does not → RED floor at dev-build (a surfaced finding, not a plan failure)._ +- **CF-B — nested human gate.** The product `/pharn-spec` halts for human approval **inside** `/pharn-dev-build` + — a third human gate the `/pharn-dev-ship` chain (GATE 1 plan / GATE 2 review only) does not model. The run + pauses mid-dev-build for the human; this is the **intended** test of "the SPEC gate actually halts," but the + orchestration interaction (dev-build spanning a human gate) is itself worth noting. _(Advisory.)_ +- **CF-C — writes-scope thrash inside dev-build.** Each product sub-command overwrites the single mutable + `.pharn/writes-scope.json`, so after the product pipeline runs, `/pharn-dev-build` must **re-set** its own + scope (from this plan's `## Files`) before writing `PROBE.md`. Mirrors the known `/pharn-dev-ship` SHIP.md / + `/pharn-build` BUILD.md re-scope pattern, now inherited by dev-build running a nested pipeline. _(Advisory.)_ +- **CF-1-amplified — `/pharn-dev-regress` conflates the nested sub-command outputs with dev-build's `## Files`.** + As in probe #14's CF-1, `inside = git diff base + untracked` will include the product artifacts + (`features/probe-greeting/**`) that are correctly absent from this plan's `## Files`; without the commit + discipline above they appear as `inside ⊄ declared` → a false blocking fix#7 "escape" that halts + `/pharn-dev-regress`. Mitigated for this run by committing the product artifacts before regress. _(Advisory.)_ +- **CF-D — pre-existing dev apparatus on the product surface (tangential).** Root `floor/check-ship.mjs` + + `check-ship.test.mjs` (git-tracked, NOT under `.dev/`) duplicate `.dev/floor/check-ship.mjs`; and root + `features/ship-gated/` + `features/ship-loop/` hold **dev**-loop artifacts (`REGRESSION.md` / `VERIFY.md` / + `REVIEW.md`) on the product side. Tangential to the hand-off probe, but surfaced by discovery — likely + `command-artifact-paths` territory. _Reported, never agent-edited; SEPARATE increment._ + +--- + +## Open questions (HALT) — RESOLVED (human-approved 2026-06-30; "Approve as written") + +1. **Feature vehicle** → **`features/probe-greeting/greet.mjs`** exporting `greet(name) → "Hello, !"`, AC + `greet("World") === "Hello, World!"` (recommended option chosen). _Declined:_ `isEven(n)` / `add(a,b)`. +2. **Post-run disposition** → **revert the vehicle (and its product artifacts) in a follow-up increment** + (recommended; cleanest P7, mirrors probe #14). _Declined:_ keep-it / decide-later. The vehicle header comment + records this. +3. **Run flow acknowledged** → the human approved knowing this `/pharn-dev-ship` run will **pause two more + times** (product SPEC approval mid-`/pharn-dev-build` per CF-B; final GATE 2) and **may STOP RED** at + dev-build's floor if CF-A trips (a successful surfacing, not a failure). + +> **RESOLVED & APPROVED (2026-06-30).** Spec hash `11cd9ad5…` re-verified (no drift, fix #4). Next stage: +> `/pharn-dev-grill` on this `PLAN.md`. `/pharn-dev-build` runs only after grill, and only against this +> approved, un-drifted plan. diff --git a/.dev/features/product-pipeline-probe/PROBE.md b/.dev/features/product-pipeline-probe/PROBE.md new file mode 100644 index 0000000..146204b --- /dev/null +++ b/.dev/features/product-pipeline-probe/PROBE.md @@ -0,0 +1,176 @@ +# PROBE — product-pipeline-probe (first measured end-to-end run of the PRODUCT pipeline) + +**What this is:** the deliverable of the product-loop analogue of dev probe #14 — a **measured** traversal of +the first four product stages `/pharn-spec → /pharn-plan → /pharn-grill → /pharn-build` on one trivial vehicle +(`features/probe-greeting/greet.mjs`), with **every hand-off observed live**. The vehicle is meaningless by +design (P7); the deliverable is this measurement. Run date 2026-06-30, against `sha256(ARCHITECTURE.md) = +11cd9ad5…` (no drift). + +**Headline result:** the four product stages **do integrate as a chain** — every hand-off produced exactly the +artifact the next stage consumed, and the spec→plan→grill→build content-hash chain held across all four. The run +ALSO surfaced **one new latent bug (CF-E)**, **confirmed three anticipated interactions (CF-A/B/C)**, and leaves +a **second RED path** for the downstream dev-verify stage to watch. This is **evidence the chain runs as a +chain — not a proof it is bug-free** (P0). + +--- + +## The filled hand-off matrix (observed LIVE, in order) + +| # | stage | scope it set (fix #7) | consumed | emitted | hand-off shape = what next stage expects? | +| --- | -------------- | ------------------------------------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------- | +| 1 | `/pharn-spec` | `features/probe-greeting/SPEC.md` | user prose intent | `SPEC.md` Draft → **(human approved)** → Approved; `spec_id: probe-greeting`; `spec_content_hash: 843b4388…e852807` = sha256(body); `check-spec` GREEN | ✅ next stage wants an Approved `features//SPEC.md` — exactly produced | +| 2 | `/pharn-plan` | `features/probe-greeting/PLAN.md` | the Approved `SPEC.md` | `PLAN.md` carrying `spec_id`+`spec_content_hash` (verbatim `843b4388…`); clean `## Files` | ✅ `check-spec-approved` gate GREEN (Approved+un-drifted); `## Files` parses to `[greet.mjs]` (setter exit 0) | +| 3 | `/pharn-grill` | `features/probe-greeting/GRILL.md` | `PLAN.md` + `SPEC.md` | `GRILL.md`: chain-GREEN header + 1 minor finding (advisory) | ✅ `check-plan-spec-agree` re-verified the carried hash == current SPEC body hash → GREEN; interrogation did not block | +| 4 | `/pharn-build` | `[greet.mjs]` → then `features/probe-greeting/BUILD.md` | `PLAN.md` + `SPEC.md` | `greet.mjs` + `BUILD.md` | ✅ `check-plan-spec-agree` re-verified AGAIN (2nd consumer); fix #7 bounded the write to `greet.mjs`; floor GREEN | + +**No hand-off mismatch in the product pipeline.** Stage N's output was, in every case, the exact shape stage N+1 +read. The only friction this run hit was CF-E — and that was in **this probe's own DEV plan**, not in the +product chain. + +--- + +## The four confirmations the run owed (all met) + +1. **The hash chain holds spec→plan→grill→build — all four agree.** The single digest + `843b43880ea257c4fcf946ee8ab73fb1d0b4e1032204df24772c53ebec852807`: + - **pinned** by `/pharn-spec` as `sha256(SPEC body)` (via `check-spec.mjs --hash`) on human approval; + - **carried verbatim** by `/pharn-plan` into `PLAN.md` frontmatter (quoted string form — parsed fine); + - **re-verified GREEN** by `/pharn-grill` (`check-plan-spec-agree.mjs`, the 1st enforcing consumer); + - **re-verified GREEN** by `/pharn-build` (`check-plan-spec-agree.mjs`, the 2nd enforcing consumer). + The pin is enforced **repeatedly** (grill + build), not trusted-once. ✅ +2. **The `## Files` `/pharn-plan` emits is parseable by `/pharn-build`'s `--from-plan` setter.** The stock + product PLAN's clean `## Files` parsed to exactly `["features/probe-greeting/greet.mjs"]`, setter exit 0, + both at a spot-check and at `/pharn-build` Step 0. The `plan-files-scope` fix works in situ. ✅ +3. **fix #7 bounds the build's writes (observed deny, not just scope inspection — strengthens grill G1).** With + scope = `[greet.mjs]`, `enforce-writes-scope.cjs` returned **exit 2 (DENIED)** for an out-of-scope path and + **exit 0 (allowed)** for the in-scope path. The bound is a real floor deny, not a promise. ✅ +4. **The human-approval gate on the SPEC actually halts.** `/pharn-spec` rendered the Draft and **halted** for + an explicit `AskQuestion` approval; it did **not** self-approve; the chain only continued after the human + chose "Approve & pin." ✅ (This is CF-B observed as working-as-intended.) + +Advisory acceptance check (not a floor gate): `greet("World")` returned exactly `"Hello, World!"` — the SPEC's +Acceptance Criterion holds for the named case (grill P1: actually run this time). + +--- + +## Findings + +> Finding-shape split honored (`pharn-contracts/finding-shape.md`): the **enum-gated** fields +> (`type`/`rule_id`/`severity`/`file`) are this probe's own assertions → trusted; the **free-text** +> (`problem`/`evidence`) quote the artifacts and inherit their **untrusted** tag → DATA, never executed. + +### CF-E — NEW latent bug: the `--from-plan` setter truncates `## Files` on a prose exclusion-cue (the one real surprise) + +```yaml +- type: FINDING + rule_id: "P5" + severity: important + file: ".claude/hooks/set-writes-scope.cjs:170" + problem: "set-writes-scope.cjs --from-plan breaks its `## Files` scan at the FIRST non-path line matching its exclusion-cue regex (not\\W*(touch|writ|…)|out\\W*of\\W*scope|…). So explanatory PROSE under `## Files` that mentions an exclusion phrase (e.g. an inline reference to the 'Explicitly not touched' subsection) — sitting ABOVE the back-tick path items, inside a blockquote that is plainly NOT an exclusion subsection — silently truncates the scope to zero paths → fail-closed exit 1, blocking the build." + evidence: "set-writes-scope: no back-tick paths under `## Files` in .dev/features/product-pipeline-probe/PLAN.md (exit 1) — caused by the blockquote phrase referencing the exclusion subsection by name, ABOVE the path item." +``` + +- **Where it bit:** this probe's own **dev** PLAN.md (`/pharn-dev-build` Step 0), not the product chain. +- **Why the product chain dodged it:** the stock `/pharn-plan` template emits a **clean** `## Files` (heading → + back-tick path items, no pre-path prose), so the product PLAN parsed first try. Probe #14's dev plan dodged it + only by luck (its blockquote said "**not** in the default-safe-set" — `not in`, not a cue verb). +- **Mitigation applied to proceed (recorded, like #14's CF-1):** the dev PLAN's `## Files` blockquote was + reworded to drop the cue phrase; intent/scope/hash unchanged; the setter then parsed `[PROBE.md]`. **The + parser fragility itself is NOT fixed here** — it is a candidate for a separate increment (e.g. anchor the cue + to a line that is NOT inside an explanatory blockquote, or require the exclusion cue to be a heading/list-lead + rather than mid-prose). + +### CF-A — CONFIRMED latent: product pipeline artifacts land on the validate-SCANNED surface; finding-bearing ones must carry the split-doc strings or trip CHECK 5 + +```yaml +- type: FINDING + rule_id: "P0" + severity: important + file: ".dev/floor/validate.mjs:30" + problem: "validate.mjs EXCLUDE_SEGMENTS excludes .dev/ wholesale but NOT root features/, so the product GRILL.md (which emits rule_id:+problem: findings) is subject to CHECK 5 (fix #1). It passed this run ONLY because the grill documented the enum-gated/free-text split (1 enum-gated string, 2 free-text/untrusted strings); a product grill that emitted BARE findings would trip CHECK 5 → RED floor. The dev pipeline never exercised this — its GRILL.md lives in excluded .dev/features/." + evidence: "FLOOR: GREEN — 1 capabilities checked; features/probe-greeting/GRILL.md has rule_id:×1 problem:×1 AND enum-gated×1 free-text|untrusted×2 — passes only because the split is documented." +``` + +- **Status:** real and latent — did NOT trip this run (passed by convention), but it is a genuine product↔floor + coupling the dev loop never had. Fix is a separate increment (e.g. exclude `features/**/GRILL.md` from + validate like `.dev/`, or have `/pharn-grill` always emit the split-doc strings). + +### CF-B — CONFIRMED interaction: a human approval gate nested INSIDE the dev-build stage + +```yaml +- type: FINDING + rule_id: "P6" + severity: minor + file: ".claude/commands/pharn-spec.md:133" + problem: "The product /pharn-spec halts for human approval. When the product pipeline is run as the build WORK of an outer /pharn-dev-build (this probe's nested structure), that halt is a human gate that /pharn-dev-ship's chain (GATE 1 plan / GATE 2 review only) does not natively model — the dev-build stage spans a human pause. Behaviourally correct (the gate SHOULD halt), but the orchestration nesting is worth documenting for anyone wrapping the product loop in the dev loop." + evidence: "/pharn-spec Step 4 — 'HALT for explicit human approval ... The model NEVER flips Draft → Approved on its own.'" +``` + +### CF-C — CONFIRMED interaction: single mutable writes-scope ⇒ thrash; the wrapper must re-set its own scope + +```yaml +- type: FINDING + rule_id: "P5" + severity: minor + file: ".claude/hooks/set-writes-scope.cjs:200" + problem: "Each product sub-command (spec/plan/grill/build) overwrote the single .pharn/writes-scope.json at its own Step 0; after the nested pipeline finished, the scope was left on the product BUILD.md, so the outer /pharn-dev-build had to RE-SET its scope (--from-plan, back to PROBE.md) before writing its deliverable. The single mutable scope file (not a stack) works, but every stage AND the wrapper must re-set before writing — observed cleanly at all four product stages plus the dev-build re-entry." + evidence: "scope set_by trail: pharn-spec.md → pharn-plan.md → pharn-grill.md → pharn-build.md (BUILD.md) → product-pipeline-probe/PLAN.md (PROBE.md)." +``` + +### CF-D — pre-existing, tangential (reported, not exercised by this probe) + +```yaml +- type: FINDING + rule_id: "P3" + severity: minor + file: "floor/check-ship.mjs:1" + problem: "Dev apparatus sits on the product surface: root floor/check-ship.mjs + check-ship.test.mjs are git-tracked OUTSIDE .dev/ (duplicating .dev/floor/check-ship.mjs), and root features/ship-gated/ + features/ship-loop/ hold dev-loop artifacts (REGRESSION/VERIFY/REVIEW). Tangential to the hand-off probe; likely command-artifact-paths territory." + evidence: "git ls-files floor/ → floor/check-ship.mjs, floor/check-ship.test.mjs (not under .dev/); features/ship-*/ contain REGRESSION.md/VERIFY.md/REVIEW.md." +``` + +### Carried from the grill of THIS probe's dev plan (addressed in-run) + +```yaml +- type: FINDING + rule_id: "P6" + severity: important + file: ".dev/features/product-pipeline-probe/PLAN.md:200" + problem: "The plan's RED-risk list named only CF-A but omitted a SECOND independent RED path: the product artifacts on the scanned root-features/ surface are also subject to lint:md (markdownlint) + format:check (prettier) when /pharn-dev-regress and /pharn-dev-verify re-run `npm run check`. The downstream dev-loop stages must watch for a style-gate RED on the product SPEC/PLAN/GRILL/BUILD markdown, unrelated to CF-A." + evidence: "grill GRILL.md P6 finding; npm run check = format:check + lint + lint:md + test, globbing repo markdown including root features/." +``` + +(Grill G1 — "fix #7 bounds" was tested only by scope inspection — was **addressed** by the live deny demo in +confirmation #3. Grill P1 — AC never run — was **addressed** by actually running the AC check.) + +--- + +## Guarantee audit (P0) — what this run does and does NOT establish + +- **"The four product stages integrate; the hash chain + fix #7 hold across them"** → **evidence, advisory.** + The floor-grade facts are the deterministic verdicts the stages emitted (`check-spec`, `check-spec-approved`, + `check-plan-spec-agree` exit codes; the fix #7 hook deny/allow; `validate` GREEN). The _claim_ "the product + chain integrates" is the **first evidence it runs as a chain**, not a proof it is bug-free — CF-E shows a real + bug was hiding one layer out (the plan-files setter). +- **The vehicle is meaningless by design.** `greet.mjs` is a throwaway (revert in a follow-up — human-approved + 2026-06-30). "build succeeded therefore greet is correct" would be the P0 disease; `/pharn-build` certified + only "built within scope from a current approved plan." +- **What IS floor-grade here:** the product chain checkers (content-hash equality + `state==Approved` enum), the + fix #7 writes-scope hook (observed deny), and `validate` GREEN. Everything the agent did (sequencing the + stages, authoring the vehicle + this report) is advisory orchestration. + +--- + +## Net for the human + +- **The product pipeline runs as a chain** — first evidence, every hand-off observed, no mismatch in the four + product stages. +- **One new bug found (CF-E)** — the `--from-plan` setter's exclusion-cue scan is fragile to pre-path prose; + worth a small follow-up fix. It blocked the **dev** build until the dev plan was reworded; the product chain + was unaffected. +- **Three interactions confirmed (CF-A/B/C)** — all anticipated by discovery, all behaving as reasoned; CF-A is + a latent RED the floor doesn't yet guard, CF-B/CF-C are orchestration realities of nesting the product loop + inside the dev loop. +- **Watch the downstream stages:** `/pharn-dev-regress` (commit discipline to avoid the CF-1-amplified false + escape) and `/pharn-dev-verify` (the second RED path: style gates over the product markdown). +- **Disposition:** revert `features/probe-greeting/` in a follow-up increment (human-approved). This run does + NOT make the vehicle meaningful. diff --git a/.dev/features/product-pipeline-probe/REGRESSION.md b/.dev/features/product-pipeline-probe/REGRESSION.md new file mode 100644 index 0000000..35efe00 --- /dev/null +++ b/.dev/features/product-pipeline-probe/REGRESSION.md @@ -0,0 +1,39 @@ +# REGRESSION — product-pipeline-probe + +- **Base:** `a730f28` (dirty-tree dogfood flow → base = HEAD; the commit that holds the probe's process + artifacts + vehicle, leaving only `PROBE.md` uncommitted). +- **Inside (the changed scope):** `.dev/features/product-pipeline-probe/PROBE.md` — the one uncommitted + dev-build output; equals the dev plan's `## Files` after the commit discipline (see note below). +- **Outside tests:** 15 committed `*.test.{mjs,cjs}` suites. **Outside eval pairs:** none. + +## Per-gate exit codes (base → head) + +| gate | base | head | flipped? | +| ---------------------------------------- | ---- | ---- | -------- | +| `tests` (15 suites, `node --test`) | 0 | 0 | no | +| `validate` (`.dev/floor/validate.mjs .`) | 0 | 0 | no | + +(Style gates `lint`/`format:check`/`lint:md` were **skipped** deterministically — `inside` = `PROBE.md` only, +which touches no shared style config; a style result over byte-identical outside files cannot flip.) + +## Verdict + +**REGRESSIONS: none — no deterministically-detectable breakage outside the feature** (`check-regress.mjs verdict` +exit 0). `regressions: []`, `pre_existing: []`. + +_Honest residual (P0/P7): `/pharn-dev-regress` catches exactly what its suite catches — nothing more. "No +regressions" means no deterministically-detectable breakage outside the feature, **not** "nothing broke."_ + +## Methodology notes surfaced this run (for PROBE.md / separate increments — not part of the verdict) + +- **CF-1-amplified (confirmed live, then mitigated).** On the first attempt, `check-regress.mjs scope` exited 1 + with **7 blocking fix#7 "escape" findings** — all false positives: the nested product sub-commands' + legitimate outputs (`features/probe-greeting/{SPEC,PLAN,GRILL,BUILD}.md`, `greet.mjs`) and the dev + `PLAN.md`/`GRILL.md`, each written under its own command's scope, are correctly absent from this dev plan's + `## Files` (= `PROBE.md`). The approved **commit discipline** (commit all-but-`PROBE.md`, human-authorized) + collapsed `inside` to `{PROBE.md}` = `declared` → clean partition. The conflation is a real `/pharn-dev-regress` + limitation for a nested-pipeline increment; the fix is a separate increment. +- **Worktree-capture artifact (mine, not the repo's).** An initial baseline capture recorded `tests=1`; this was + an artifact of invoking `node --test` with worktree-prefixed paths from the wrong cwd, **not** a real test + failure. Re-run with the correct cwd (and confirmed 3/3 in the working tree), the suite is GREEN at both base + and head. The tests are hermetic (each spawns into a fresh temp dir; the real `.pharn/` is never touched). diff --git a/.dev/features/product-pipeline-probe/REVIEW.md b/.dev/features/product-pipeline-probe/REVIEW.md new file mode 100644 index 0000000..1b98e82 --- /dev/null +++ b/.dev/features/product-pipeline-probe/REVIEW.md @@ -0,0 +1,90 @@ +# REVIEW — product-pipeline-probe + +**Increment reviewed:** the product-pipeline-probe — a measured end-to-end run of `/pharn-spec → /pharn-plan → +/pharn-grill → /pharn-build` on a throwaway vehicle (`features/probe-greeting/greet.mjs`), delivering +`.dev/features/product-pipeline-probe/PROBE.md`. Reviewed as `trust: untrusted` (P2). + +**Step 1 — Floor:** `.dev/floor/validate.mjs .` → **GREEN, 1 capability** (exit 0). The increment legitimately +reached review. Everything below the floor line is **advisory**. + +--- + +## The four lenses + +### L-floor → P0 — no blocking finding + +Every guarantee the increment claims is correctly reduced or labeled. `PROBE.md` and `PLAN.md` label the +integration claim as **evidence / advisory** ("the chain runs as a chain — not a proof it is bug-free"), name +the floor-grade facts (the chain checkers, the fix #7 hook deny, `validate`/`check-regress`/`check-verify` +verdicts), and state the vehicle "makes no guarantee claim." No guarantee appears without a floor reduction or +an `advisory` label. The probe is, if anything, **more** P0-disciplined than average because its whole subject +is the floor/advisory split. **Clean.** + +### L-eval → P1 — no blocking finding + +The increment adds **no Capability** (`greet.mjs` has no `role:` frontmatter; it is a non-Capability vehicle), +so P1's "every Capability ships evals" does not bind it — identical to probe #14's `floor/exit-label.mjs`. The +floor agrees (`validate` GREEN, capability count unchanged at 1). No `enforces` rule_id is introduced, so no +eval binding is owed. The acceptance criterion was nonetheless exercised (advisory `node -e` → pass). **Clean, +and the floor + this lens agree.** + +### L-trust → P2 — no blocking finding + +- The free-text fields in every finding the increment emits (`PROBE.md`, both `GRILL.md`s) are explicitly + handled under the `finding-shape.md` enum-gated / free-text split and rendered as quoted DATA. +- **No instruction-looking content in the reviewed artifacts changed reviewer behavior** — the vehicle and its + prose are benign (a pure `greet`; a header comment that is plainly descriptive). No injection was present to + resist; none was followed. +- **No guaranteed decision rests on a tainted field:** every gate the probe drove (`check-spec`, + `check-spec-approved`, `check-plan-spec-agree`, the fix #7 hook, `validate`, `check-regress`, `check-verify`) + reads only enum/exit-code/path inputs. The probe explicitly verified the fix #7 deny by **observation** (exit + 2), strengthening rather than asserting the bound. **Clean.** + +### L-axis → P3 — no blocking finding + +One coherent axis (the integration probe). `greet.mjs` is import-free → no sibling reference. The dev plan's +`## Files` declares one file (`PROBE.md`); the product artifacts are each their own command's output. No file +carries two reasons to change. The two slugs (`product-pipeline-probe` dev increment + `probe-greeting` product +vehicle) are inherent to the nesting, not a bundling smell. **Clean.** + +--- + +## Verdict + +**GREEN — 0 floor-gate (blocking) findings.** The increment is done in the floor sense: the floor is GREEN, no +guarantee is unlabeled, no Capability lacks evals, no tainted field gates a decision, no sibling reference. The +substantive **output** of this increment is the measurement + findings in `PROBE.md`, not new product surface. + +## Advisory observations (not blocking — for the human / separate increments) + +- The increment **edited its own approved PLAN.md mid-build** (the CF-E conformance reword) to get past the + `--from-plan` setter. Recorded transparently and intent/scope/hash were unchanged, but "editing an approved + plan during build" is a process smell worth noting — the cleaner path (a parser fix so the reword is + unnecessary) is CF-E's separate increment. +- The probe required **human-authorized commit discipline mid-run** (the CF-1-amplified false escape) and a + **prettier pass on its own artifacts** (G3) to complete the dev loop — both are real interactions a + nested-pipeline increment forces, both resolved, both candidates for separate fixes. + +## Proposed lessons for canon (PROPOSED only — `/pharn-dev-memory-promote` writes canon, behind its own gate + human accept; never here) + +> Provenance for both: increment `product-pipeline-probe`, branch `product-pipeline-probe`, commit `a730f28` +> (+ uncommitted run artifacts), `.dev/features/product-pipeline-probe/PROBE.md`. Each is a **real** failure +> surfaced this run (P7), not hypothetical. + +- **Candidate L-?: Pipeline artifacts on the validate-scanned surface inherit the floor + style gates that + `.dev/`-excluded dev artifacts never faced.** A finding-bearing product `GRILL.md` must carry the + enum-gated/free-text split-doc strings (or it trips `validate` CHECK 5 — CF-A) **and** be prettier/markdownlint + clean (or it REDs `/pharn-dev-verify` — G3). The dev pipeline never exercised either because its artifacts live + in excluded `.dev/features/`. _Why it matters:_ the first real product-pipeline run can RED the floor/verify + for reasons unrelated to the user's code. _How to apply:_ either exclude product pipeline artifacts from + `validate` + the style globs (mirroring `.dev/`), or make the `pharn-*` commands emit split-documented, + style-clean markdown by construction. +- **Candidate L-?: The `--from-plan` writes-scope setter truncates `## Files` at a pre-path prose + exclusion-cue (CF-E).** Explanatory prose under `## Files` that mentions `not touched` / `out of scope` / etc. + ABOVE the back-tick path items zeroes the scope → fail-closed exit 1, blocking the build. _Why it matters:_ a + perfectly valid plan with an explanatory blockquote is rejected, and the failure message ("no back-tick paths + under `## Files`") does not point at the cue as the cause. _How to apply:_ anchor the exclusion-cue boundary to + a heading/list-lead (not mid-blockquote prose), or only honor the cue at/after the first path item. + +These are **proposals**. No canon is written by `/pharn-dev-review` (scope is `REVIEW.md` only); promotion is a +separate human-gated `/pharn-dev-memory-promote` run. diff --git a/.dev/features/product-pipeline-probe/SHIP.md b/.dev/features/product-pipeline-probe/SHIP.md new file mode 100644 index 0000000..140f6f2 --- /dev/null +++ b/.dev/features/product-pipeline-probe/SHIP.md @@ -0,0 +1,47 @@ +# SHIP — product-pipeline-probe (gated `/pharn-dev-ship` roll-up) + +**Run type:** gated `/pharn-dev-ship` (no `--loop`). **Ended at:** GATE 2 (post-review) — handed to the human. +This roll-up is **advisory**; every guarantee belongs to a sub-stage. `/pharn-dev-ship` added no floor primitive. + +## Stages that ran, in order + +| stage | result | structural verdict read (verbatim) | +| -------------------- | ------------------------------------------ | ----------------------------------------------------------------- | +| `/pharn-dev-plan` | PLAN.md written → **GATE 1** approved | _(human approval — not a floor verdict)_ | +| `/pharn-dev-grill` | GRILL.md written (7 concerns, advisory) | _(advisory; gates nothing)_ | +| `/pharn-dev-build` | ran the nested PRODUCT pipeline + PROBE.md | `validate.mjs .` exit **0** (GREEN) | +| `/pharn-dev-regress` | regression-report.json | `.verdict` = **`no-regressions`** | +| `/pharn-dev-verify` | verify-report.json | `.verdict` = **`PASS`** | +| `/pharn-dev-review` | REVIEW.md (4 lenses) | GREEN — 0 floor-gate findings _(no structural verdict; advisory)_ | + +All three floor verdicts are GREEN: `validate` exit 0 · `no-regressions` · `PASS`. + +## Human gates / stops hit this run (the honest record) + +- **GATE 1 (plan approval):** approved "as written" (vehicle `greet(name)`, disposition = revert in a follow-up). +- **Nested product-SPEC gate (CF-B):** the product `/pharn-spec` halted **inside** `/pharn-dev-build` for a second + human approval; approved "Approve & pin." This confirmed probe confirmation #4 (the SPEC gate halts) live. +- **Regress STOP → human decision:** `/pharn-dev-regress`'s scope partition first exited 1 with a CF-1-amplified + **false** fix#7 escape (the nested sub-commands' legitimate outputs vs the dev plan's one-path `## Files`). The + human chose **branch + commit discipline + continue**; after committing all-but-`PROBE.md` (branch + `product-pipeline-probe`, commit `a730f28`), the partition was clean and the verdict computed `no-regressions`. +- **GATE 2 (post-review):** this stop. The decision (merge / fix / abandon) is the human's. + +## Pointers (cited, not restated — P4) + +- Substantive deliverable + all findings: `.dev/features/product-pipeline-probe/PROBE.md` (the measured hand-off + matrix, the four confirmations, CF-A/B/C/E + G3). +- Advisory interrogation of the plan: `.dev/features/product-pipeline-probe/GRILL.md`. +- Advisory review of the increment: `.dev/features/product-pipeline-probe/REVIEW.md` (GREEN; two proposed + lessons for a future `/pharn-dev-memory-promote`). + +## What this run established (and did not) + +The product pipeline **runs as a chain** — first evidence, every hand-off observed, no mismatch in the four +product stages; the spec→plan→grill→build content-hash chain held; fix #7 bounded the build (observed deny); the +SPEC gate halted. It surfaced **one new latent bug** (CF-E, the `--from-plan` cue truncation) and **confirmed +four anticipated interactions** (CF-A scanned-surface CHECK 5; CF-B nested gate; CF-C scope thrash; CF-1-amplified +regress conflation; G3 verify style gates). The vehicle is meaningless by design and is slated for revert. + +_chain ran; the named floor verdicts are as shown — this is NOT a judgment that the increment is good or wise; +that is the human's call at the post-review gate. `/pharn-dev-ship` did not merge, push, or seal._ diff --git a/.dev/features/product-pipeline-probe/VERIFY.md b/.dev/features/product-pipeline-probe/VERIFY.md new file mode 100644 index 0000000..1963985 --- /dev/null +++ b/.dev/features/product-pipeline-probe/VERIFY.md @@ -0,0 +1,39 @@ +# VERIFY — product-pipeline-probe + +**Feature verified:** `product-pipeline-probe` (the dev increment wrapping the product-pipeline probe). + +## FLOOR layer — the deterministic gates (own the verdict) + +| gate | exit | green? | +| -------------------------------------------------------------------------------------- | ---- | ------ | +| `test` (`npm test`, 165 suites) | 0 | ✅ | +| `validate` (`.dev/floor/validate.mjs .`) | 0 | ✅ | +| `lint` (`eslint .`) | 0 | ✅ | +| `format:check` (`prettier --check .`) | 0 | ✅ | +| `lint:md` (`markdownlint-cli2`) | 0 | ✅ | +| `structural:…/expected-injection-comment.json` (trust-fence eval ↔ committed findings) | 0 | ✅ | + +**VERIFIED: floor gates PASS** (`.dev/floor/check-verify.mjs` → `verdict: "PASS"`, exit 0, `failing_gates: []`). + +## ADVISORY layer — verifiers + +**No verifiers registered — floor gates only** (`.dev/floor/count-verifiers.mjs .` → `{"registered":0,"verifiers":[]}`). +Step 2 was a no-op (membership → ∅); the verdict is the floor gates alone (P7 — no verifier authored speculatively). + +## Note on the G3 style-gate interaction (the verify-stage RED path the grill predicted) + +`format:check` and `lint:md` are GREEN **now**, but they were **RED on the first attempt** — the probe's own +markdown artifacts (the product `GRILL.md` and the dev `PLAN`/`GRILL`/`PROBE`/`REGRESSION`/`regression-report`) +were not prettier/markdownlint-clean (notably **MD060 table-column-style** on the hand-off-matrix tables). They +were formatted (`prettier --write` over the probe's own files) to conform, and both gates then passed. This is +the **second RED path** the grill flagged (G3) — confirmed live, then resolved as normal dev hygiene. It is a +real product↔floor interaction: any pipeline artifact on the scanned surface must satisfy the repo's style gates +at verify, not just the structural floor. (Candidate for a separate increment — e.g. have the `pharn-*` commands +emit prettier-clean markdown, or exclude pipeline artifacts from the style globs, mirroring CF-A's choice for +`validate`.) + +--- + +_verified = the named gates passed; this is NOT a guarantee of correctness beyond what those gates check — +verifier concerns are advisory help, not assurance (and there are none today). The probe's substantive findings +(CF-A/B/C/E + G3) live in `PROBE.md`, not here; `/pharn-dev-verify` certifies only that the listed gates ran green._ diff --git a/.dev/features/product-pipeline-probe/regression-report.json b/.dev/features/product-pipeline-probe/regression-report.json new file mode 100644 index 0000000..25ac289 --- /dev/null +++ b/.dev/features/product-pipeline-probe/regression-report.json @@ -0,0 +1,17 @@ +{ + "base": "a730f28", + "inside": [".dev/features/product-pipeline-probe/PROBE.md"], + "outside_gates": { + "tests": { + "base": 0, + "head": 0 + }, + "validate": { + "base": 0, + "head": 0 + } + }, + "regressions": [], + "pre_existing": [], + "verdict": "no-regressions" +} diff --git a/.dev/features/product-pipeline-probe/verify-report.json b/.dev/features/product-pipeline-probe/verify-report.json new file mode 100644 index 0000000..2cde4aa --- /dev/null +++ b/.dev/features/product-pipeline-probe/verify-report.json @@ -0,0 +1,14 @@ +{ + "feature": "product-pipeline-probe", + "gates": { + "format:check": 0, + "lint": 0, + "lint:md": 0, + "structural:pharn-review/trust-fence/evals/expected/expected-injection-comment.json": 0, + "test": 0, + "validate": 0 + }, + "verdict": "PASS", + "failing_gates": [], + "verifiers": { "registered": 0, "findings": [] } +} diff --git a/.dev/features/setter-cue-fix/PLAN.md b/.dev/features/setter-cue-fix/PLAN.md new file mode 100644 index 0000000..0623539 --- /dev/null +++ b/.dev/features/setter-cue-fix/PLAN.md @@ -0,0 +1,28 @@ +# PLAN — setter-cue-fix (CF-E: blockquote prose under `## Files` must not truncate the scope) + +- spec_content_hash: 11cd9ad5983188623fe0931d13588c16435a5565888344e20669748947d1d969 # fix #4 — sha256(ARCHITECTURE.md), live this run; no drift +- increment: fix the `--from-plan` writes-scope setter so an EXPLANATORY blockquote line under `## Files` (a `> …` note that happens to contain an exclusion cue like "not touched") no longer triggers the head-less-exclusion Boundary-2 break and silently zeroes the scope. Triggered by a REAL failure (CF-E, `.dev/features/product-pipeline-probe/PROBE.md`) — P7 satisfied. +- layer(s): build apparatus — `.claude/hooks/` (the fix #7 setter + its test). # not a product Capability +- constitution_refs: [P5, P7] + +## Files + +- `.claude/hooks/set-writes-scope.cjs` — exempt blockquote lines (`> …`) from the Boundary-2 exclusion-cue break in `pathsFromPlanFiles`, so explanatory prose above the path items cannot truncate the authorized list. +- `.claude/hooks/set-writes-scope.test.cjs` — add a regression test: a `## Files` with a blockquote note containing "not touched" ABOVE the back-tick path still parses the path into scope (the CF-E reproduction). + +### Explicitly not touched + +- _(the exclusion-cue behaviour for NON-blockquote head-less intros like "Files NOT written:" is preserved — only blockquotes are exempted, so an exclusion-only `## Files` still fails closed.)_ + +## Guarantee audit (P0) + +- "the setter parses a `## Files` whose authorized paths are preceded by an explanatory blockquote" → FLOOR: the setter is deterministic (regex/membership); the test pins the new behaviour. The fix only makes the parser MORE permissive about blockquotes (it can add a previously-truncated path, never remove one), so it cannot weaken the fail-closed guarantee for genuine exclusions. +- "blockquotes are explanatory, never exclusion-section intros" → the design assumption justifying the exemption; an exclusion section is a heading (`### …`, Boundary 1) or non-blockquote prose (Boundary 2), never a `> …` blockquote. + +## Determinism audit (P5) + +- The added branch is a membership test (`/^\s*>/` blockquote detection); no LLM. The exclusion-only / head-less-intro fail-closed path is unchanged (non-blockquote prose still breaks). + +## Open questions (HALT) — none + +> Direct fix of a surfaced bug (CF-E); scope is two build-apparatus files; no product surface, no trusted doc, no new guarantee. Proceeding under the human's "do everything" authorization. diff --git a/.dev/features/setter-cue-fix/REVIEW.md b/.dev/features/setter-cue-fix/REVIEW.md new file mode 100644 index 0000000..b34abf8 --- /dev/null +++ b/.dev/features/setter-cue-fix/REVIEW.md @@ -0,0 +1,99 @@ +# REVIEW — setter-cue-fix (CF-E) + +**Increment reviewed:** the CF-E fix — `.claude/hooks/set-writes-scope.cjs` (Boundary-2 blockquote exemption in +`pathsFromPlanFiles`) + `.claude/hooks/set-writes-scope.test.cjs` (+2 tests), under +`.dev/features/setter-cue-fix/PLAN.md`. Reviewed as `trust: untrusted` (P2). _(Built directly under a +"do everything" authorization without a review pass — this review backfills that gate.)_ + +**Step 1 — Floor:** `.dev/floor/validate.mjs .` → **GREEN, 1 capability** (exit 0). `npm test` → **167 pass** +(incl. the 2 new tests). The increment legitimately reached review. Everything below the floor line is +**advisory**. + +--- + +## The four lenses + +### L-floor → P0 — no blocking finding + +The PLAN's guarantee audit is honest: the claim "the fix only makes the parser MORE permissive about +blockquotes (it can add a previously-truncated path, never remove one), so it cannot weaken the fail-closed +guarantee" is **verified true** — the change only adds `!isBlockquote` to the Boundary-2 **break** condition, so +a blockquote line can no longer stop the scan early; it never causes an earlier stop. No guarantee is claimed +without a floor reduction (the setter is deterministic; the tests pin the behaviour). The "blockquotes are never +exclusion intros" assumption is **labeled as an assumption**, not sold as a guarantee. **Clean.** + +### L-eval → P1 — no blocking finding + +The increment is a floor **hook** (`.claude/hooks/`, no `role:`), so P1's Capability-evals rule does not bind it; +it ships its proof as `*.test.cjs` (the hook convention), run by `npm test` (the floor `validate` excludes +`.claude/`). The 2 tests **bind both halves**: the CF-E reproduction (a blockquote cue above the paths → +scope collected) and the preservation guard (a NON-blockquote head-less intro → still fails closed). The binding +is demonstrated, not merely asserted. **Clean.** + +### L-trust → P2 — no blocking finding + +No instruction-looking content in the reviewed diff changed reviewer behaviour (it is a regex + a comment). No +new taint path: the exemption **cannot be exploited to sneak a path into scope**, because a back-tick path +written inside a blockquote (a `>`-led line) is never collected — the collection match (line 184) still requires +a leading dash, which a `>`-led line fails. So exempting blockquotes from the _break_ changes only whether prose **truncates** the list, +never which paths are **collected**. No guaranteed decision rests on a tainted/free-text field. **Clean.** + +### L-axis → P3 — no blocking finding + +One axis (the CF-E blockquote fix), one source file + its test + the increment's PLAN. No sibling reference (a +standalone hook; no `reads:` edge). **Clean.** + +--- + +## Verdict + +**GREEN — 0 floor-gate (blocking) findings.** The fix is correct, minimal, well-tested, and honestly audited; the +floor is GREEN and the full suite passes with the regression tests added. + +## Advisory findings (not blocking) + +```yaml +- type: FINDING + rule_id: "P5" + severity: important + file: ".claude/hooks/set-writes-scope.cjs:179" + problem: "The fix patches the blockquote CASE but not the CLASS: Boundary-2 is a regex-on-prose heuristic trying to distinguish an exclusion-intro from explanatory prose, and a NON-blockquote explanatory paragraph containing a cue (e.g. a plain line 'Everything else is not touched.' above the path items) would STILL truncate the scope. CF-E (the real, blockquote form) is resolved, but the underlying fragility of content-cue boundary detection remains." + evidence: "const isBlockquote = /^\\s*>/.test(line); … !isBlockquote && /\\bnot\\W*(touch|writ|…)|…/i.test(line)" +``` + +```yaml +- type: FINDING + rule_id: "P5" + severity: minor + file: ".claude/hooks/set-writes-scope.cjs:174" + problem: "The exemption is sound largely because of an unstated collateral property — blockquote-internal back-tick paths are never COLLECTED (the line-184 match requires a leading '-') — so a blockquote can neither truncate the list nor inject a path. The PLAN justifies the exemption only by 'blockquotes are explanatory', not by this stronger structural safety; recording WHY it is safe would harden the rationale against a future edit to the collection regex." + evidence: "isPathItem = /^\\s*-\\s+`[^`]+`/ … m = line.match(/^\\s*-\\s+`([^`]+)`/) — both require a leading dash, which a `>`-led blockquote line fails." +``` + +```yaml +- type: FINDING + rule_id: "P1" + severity: minor + file: ".claude/hooks/set-writes-scope.test.cjs:1" + problem: "Test coverage targets the cue forms (blockquote-with-cue collected; non-blockquote intro fails closed). Not explicitly exercised: a benign blockquote WITHOUT a cue (skipped, path collected) and a blockquote AFTER the path list (must not retroactively drop a collected path). Both behave correctly by the same mechanism, so this is a coverage note, not a defect." + evidence: "the two added tests both use a cue-bearing or exclusion-intro fixture." +``` + +## Proposed lesson for canon (PROPOSED only — `/pharn-dev-memory-promote` writes canon, behind its own gate + human accept) + +> Provenance: increment `setter-cue-fix`, branch `product-pipeline-probe`, commit `0077dd1` (the fix), surfaced +> by `.dev/features/product-pipeline-probe/PROBE.md` CF-E + this REVIEW.md. A **real** failure (CF-E bit a live +> build), not hypothetical (P7). + +- **Candidate: a content-cue boundary is fragile; prefer a STRUCTURAL anchor.** The writes-scope setter ends its + `## Files` scan two ways: Boundary 1 (a markdown **heading** — structural, robust) and Boundary 2 (a prose + **cue regex** — fragile, the one CF-E exploited). The blockquote exemption narrows Boundary 2 but does not + remove its class of failure. _Why it matters:_ a parser that infers structural intent from free-text prose + will keep misfiring as plan prose varies (the same "written prose mistaken for structure" shape the repo + guards against elsewhere). _How to apply:_ evaluate whether Boundary 1 (headings, e.g. `### Explicitly not +touched`) plus the angle-bracket-placeholder fail-closed already covers every real exclusion case — and if so, + consider removing Boundary 2 rather than continuing to patch it. Relates to L6 (read membership from the + structured location, not grepped free text). + +This is a **proposal**. No canon is written by `/pharn-dev-review`; promotion is a separate human-gated +`/pharn-dev-memory-promote` run. diff --git a/.dev/memory-bank/lessons-learned.md b/.dev/memory-bank/lessons-learned.md index 7807845..ee8e446 100644 --- a/.dev/memory-bank/lessons-learned.md +++ b/.dev/memory-bank/lessons-learned.md @@ -268,3 +268,33 @@ captures). set) + proposed lesson `verify-include-style-gates`; corroborated by `.dev/features/plan-files-scope/VERIFY.md` "Style-gate correction". - promoted: 2026-06-30 via gated `/pharn-dev-memory-promote` (human-approved). + +## L10 — Product-pipeline artifacts sit on the validate-SCANNED surface; `.dev/` dev artifacts don't + +**Lesson.** The dev/product boundary is symmetric on the WRITE side (dev artifacts → `.dev/features/`, product +artifacts → root `features/`) but ASYMMETRIC at the floor's SCAN side: `validate.mjs` `EXCLUDE_SEGMENTS` +excludes `.dev/` wholesale but NOT root `features/`. So a finding-bearing PRODUCT artifact (e.g. a `/pharn-grill` +`GRILL.md` emitting `rule_id:` + `problem:`) is subject to validate CHECK 5 (fix #1 — it must document the +enum-gated / free-text split or trip RED), whereas the equivalent DEV artifact (`/pharn-dev-grill`'s `GRILL.md` +in excluded `.dev/features/`) is never scanned. Consequence: moving the same pipeline from the dev loop to the +product loop silently subjects its audit artifacts to a floor check they never faced — the first real +product-pipeline run can RED validate for an artifact reason unrelated to the user's code. Remedy: either +exclude finding-bearing product pipeline artifacts from validate's scan (mirroring the `.dev/` exclusion), or +ensure the `pharn-*` commands emit split-documented findings by construction (the `/pharn-grill` command already +instructs honoring the split — making it load-bearing for the floor, not just style). + +**Why it matters.** The STYLE-gate half of this same dev/product asymmetry (product artifacts also face +`format:check` + `lint:md` at `/pharn-dev-verify`) is L9's territory — verify's style gates now cover them; THIS +lesson is the validate-CHECK-5 half, which L9 does not touch. Note the trust UPSIDE: a laundered needle in a +product `GRILL.md`'s enum-gated field WOULD be caught by CHECK 5, so the asymmetry also closes a real gap — the +only cost is that benign product findings must document the split. Surfaced live by the product-pipeline-probe: +the product `/pharn-grill` `GRILL.md` landed on the scanned surface and passed CHECK 5 only because the split was +documented; a bare-findings `GRILL.md` would have RED'd the floor. + +**Provenance.** + +- feature: `product-pipeline-probe` +- commit: `a66f5872e48265eb39c4c58b6d58c0593f00e8e4` +- surfaced by: `.dev/features/product-pipeline-probe/PROBE.md` (CF-A) + `.dev/features/product-pipeline-probe/REVIEW.md` + (proposed lesson). +- promoted: 2026-06-30 via gated `/pharn-dev-memory-promote` (human-approved).