diff --git a/.dev/features/features-readme-spec-live/GRILL.md b/.dev/features/features-readme-spec-live/GRILL.md new file mode 100644 index 0000000..7a5b603 --- /dev/null +++ b/.dev/features/features-readme-spec-live/GRILL.md @@ -0,0 +1,88 @@ +# GRILL — features-readme-spec-live + +**Plan:** `.dev/features/features-readme-spec-live/PLAN.md` · **Spec-hash check (content-hash primitive, +surfaced not blocking):** `sha256(pharn/ARCHITECTURE.md)` live = +`8f5ec002e3b18cbfd2f094b08a3671f7ed42a05a3fbaf01a11bbbd28da30fb52` == the plan's `spec_content_hash` → +**no drift**. The block on drift is `/pharn-dev-build`'s floor-gate (fix #4), not this stage. +**Registered grillers (membership, FLOOR — `pharn/floor/count-grillers.mjs`):** 13. + +> The PLAN is `trust: untrusted` here. Every `problem` / `evidence` below quotes it as DATA. + +## Findings + +### Axis: discovery-first / doc-vs-live-state (P6) + +```yaml +- type: FINDING + rule_id: "P6" + severity: important + file: ".dev/features/features-readme-spec-live/PLAN.md:103" + problem: "The plan still carries three questions under `## Open questions (HALT)` in unresolved form, but all three were answered at the plan-approval gate — and `/pharn-dev-build` refuses a plan whose open questions are unresolved, so the build stage will read a stale blocker that live state has already cleared." + evidence: "PLAN.md:103 `## Open questions (HALT)` followed by items 1-3 ('Proposed: `as the user runs each stage`. Alternative: leave line 8-9 untouched', 'Proposed: include it, carrying no version bump', 'Confirm it should be a follow-up rather than folded in') — against `.claude/commands/pharn-dev-build.md:42`: 'Read `PLAN.md`. If it has unresolved `## Open questions (HALT)` → **HALT**; it is not approved.'" +``` + +```yaml +- type: FINDING + rule_id: "P6" + severity: minor + file: ".dev/features/features-readme-spec-live/PLAN.md:75" + problem: "The guarantee audit asserts a mechanism about the floor scanner's behavior that no read of that scanner this run supports — it is inferred from a promoted lesson plus a green baseline, which is evidence the check does not fire today but not a reading of why." + evidence: "PLAN.md:75 — 'so no CHECK fires on its content (L10's product-surface asymmetry is therefore inert here)'. The plan cites no line of `pharn/floor/validate.mjs` for this, and the surrounding `## Trust audit (P2)` elsewhere holds itself to live verification ('it was still treated as a claim to verify rather than a fact to copy (P6)')." +``` + +### Axis: guarantee-audit completeness (P0) + +```yaml +- type: FINDING + rule_id: "P0" + severity: minor + file: ".dev/features/features-readme-spec-live/PLAN.md:68" + problem: "A floor reduction is claimed without the narrowing the same plan states elsewhere: the writes-scope hook gates only the Write/Edit/MultiEdit surface, so the claim covers less than the unqualified wording implies." + evidence: 'PLAN.md:68 — ''"the edit lands only in the two declared files" → **floor: hook** (fix #7 `set-writes-scope.cjs` + `enforce-writes-scope.cjs`, composed with fix #2''s denylist).'' The same document''s applied-lessons block already names the bound at PLAN.md:31 (''a repo-wide `prettier --write .` whose Bash writes escape the fix #7 gate entirely''), but the audit entry does not carry it.' +``` + +### Axes that produced no finding + +- **Eval coverage (P1) + the structural/semantic split (`eval-format.md`, cited not restated).** Not + engaged, and the plan says so correctly: P1 binds a Capability, and `features/README.md` carries no + `---`-fenced frontmatter, hence no `role:` and no `enforces` rule id owing a fixture. Read + structurally from the file, not from the plan's self-claim (L6). Nothing is laundered into a judge + because there is no eval to route. +- **Trust propagation (P2).** The increment ingests no untrusted artifact, and the plan's trust audit + goes further than required — it records two request premises that failed live verification + (`SKILLS_VERSION` 2.4.6 vs 2.5.1; the `pharn/floor/README.md` half already fixed) rather than + inheriting them. That is the correct direction for this axis. +- **One axis of change (P3).** Two files, one change-reason: the README correction and the changelog + line that records it co-change by the repo's own "all notable changes are documented" contract. No + sibling reference — the increment touches no module and routes nothing leaf→leaf. +- **Determinism (P5).** Every branch named is a membership test (a grep for `node floor/…`; the + setter's parse of `## Files`; exit codes). The one irreducible judgment — whether line 8–9 reads as + stale — terminated in a question to the human, not a guess. +- **Honest scope (P7).** The increment adds no capability, rule, or enforcer, so the speculative-addition + hazard does not arise. Its exclusion block is a `###` heading (L18) and names four deferrals with + reasons, including one same-class defect it deliberately declines to fold in. +- **Documentation griller (`pharn/pharn-pipeline/grillers/documentation/documentation.md`, P7).** Layer 1 + presence: the "needs it" trigger does not fire — the increment adds no public API, no config key, and + no non-obvious behavior; it **is** a documentation correction, and its `## Files` states the exact + prose changing. No absence finding. Layer 2 adequacy is not reached. +- **Remaining registered grillers.** Applied inline over a plan that builds no code: a11y, + architecture, comprehension, coupling, error-handling, i18n, migrations, observability, performance, + privacy, security, testability. None fired — there is no interface, no data flow, no schema, no + failure path, and no user-visible string beyond the corrected sentences themselves. + +## Summary + +The plan's substance holds: the defect is real and was reproduced live, the fix is one axis, and the +versioning call (no `SKILLS_VERSION` bump) follows the repo's own repo-meta rule rather than the +request's assertion. Two of the three findings are wording-level honesty gaps in the guarantee audit — +worth closing because this repo's whole thesis is that an unqualified floor claim is the disease, and +the plan states the missing qualifier elsewhere in its own text. + +The first finding is the one with teeth, and it is procedural rather than substantive: the plan's open +questions were all answered at the approval gate, but the document still reads as though they are open, +and the build stage's documented refusal rule keys on exactly that section. Left as-is it produces a +halt that live state does not justify — the mirror image of the very defect this increment fixes, a +document asserting a state the repo has moved past. + +ADVISORY VERDICT: 3 concerns raised (0 blocking-severity, 1 important, 2 minor) — for the human to +weigh before `/pharn-dev-build`. Nothing here blocks; the grill stage gates nothing. diff --git a/.dev/features/features-readme-spec-live/PLAN.md b/.dev/features/features-readme-spec-live/PLAN.md new file mode 100644 index 0000000..1a0c45b --- /dev/null +++ b/.dev/features/features-readme-spec-live/PLAN.md @@ -0,0 +1,139 @@ +# PLAN — features/README.md stops calling the shipped product pipeline unbuilt + +- spec_content_hash: 8f5ec002e3b18cbfd2f094b08a3671f7ed42a05a3fbaf01a11bbbd28da30fb52 # fix #4 +- applied_lessons: [L1, L11, L13, L18, L19, L20] +- increment: Reword the two sentences in `features/README.md` that describe the product pipeline as + still under construction, so the guide matches the live repo where all seven stages ship. +- layer(s): none — repo-meta. No capability, no `role:` frontmatter, no node in the layer tree + (`pharn/ARCHITECTURE.md §4`). +- constitution_refs: [P0, P6, P7] + +## Applied lessons + +- **L1** — ran the meta-doc sweep this increment invalidates: grepped the tree for every doc asserting a + product stage is unbuilt. It returned the two in-scope lines plus three classes deliberately left + alone (see `### Deliberately NOT in scope`), and settled the `CHANGELOG.md` question — L1's own + provenance names a missing `[Unreleased]` entry as half of the defect that promoted it, so the entry + is in `## Files` rather than left optional. +- **L11** — ran the whole-repo `npm run check` at BASELINE before scoping, so a pre-existing style red + could not later be blamed on this increment. It is green (1302 tests, all gates pass), so any red at + verify is this increment's. +- **L13** — this stage formats its own artifact: `prettier --write` + `markdownlint-cli2 --fix` scoped + to this `PLAN.md` alone, before the halt. +- **L18** — the exclusion block below is a `###` HEADING (`### Deliberately NOT in scope`), never a bold + prose intro, so `set-writes-scope.cjs --from-plan` terminates the authorized list structurally rather + than on a prose cue it may not recognize. +- **L19** — the L13 formatters are pinned to this one artifact path, never `npm run format` (a + repo-wide `prettier --write .` whose Bash writes escape the fix #7 gate entirely). +- **L20** — read the scope-setter's printed count as a checkable number, not decoration: Step 0 printed + `1 path(s)`, matching the one `PLAN.md` this stage writes. The same read is owed at `/pharn-dev-build` + Step 0, where the expected count is the number of `## Files` entries below. + +## Files + +- `features/README.md` — reword lines 18–20 (drop "(a later increment)" / "Until then") and lines 8–9 + ("as those stages are built"), so neither asserts the product pipeline is unbuilt — layer none +- `CHANGELOG.md` — one `## [Unreleased]` entry recording the correction, carrying **no** version bump + (L1) — layer none + +### Deliberately NOT in scope + +- `SKILLS_VERSION` — **must stay at 2.5.1.** `features/README.md` is a README, and CLAUDE.md's + discipline exempts repo-meta from the bump; the concrete bump-triggering set does not list it either. +- `pharn/floor/README.md` — verified live this run: it already reads `node pharn/floor/validate.mjs` + (line 34) and `node pharn/floor/check-structural.mjs` (line 55). There is no `node floor/…` path left, + so the second half of the original finding is already fixed (PR #126) and this increment must not + touch it. +- `pharn/pharn-contracts/finding-shape.md` — line 81 says the `check-structural` wiring over emitted + output "is increment **3c, not yet built**", which the sweep found stale (see `## Open questions`). + Same defect class, but it is **product surface**, so correcting it bumps `SKILLS_VERSION` — a + different axis and a separate increment (P3, P7). +- `.claude/commands/pharn-ship.md` — line 350's "`--loop` … is a **separate follow-up increment**" while + `/pharn-loop` ships the capability under a different name. Judgment call, product surface, separate. +- `.dev/features/**` — every other sweep hit is a frozen audit artifact that was true when written. + Rewriting history is not a staleness fix. + +## Contracts satisfied + +- none. This increment adds no capability, cites no `pharn-contracts` schema, and emits no finding. + +## Evals to write (P1) + +- none, and P1 is not engaged: P1 binds a **Capability** (a file whose frontmatter carries `role:`). + `features/README.md` has no frontmatter and declares no `enforces` rule id, so there is no rule id + owing an eval fixture. + +## Guarantee audit (P0) + +- "the edit lands only in the two declared files" → **floor: hook** (fix #7 `set-writes-scope.cjs` + + `enforce-writes-scope.cjs`, composed with fix #2's denylist) — **narrowed, and stated:** the hook + gates the `Write|Edit|MultiEdit` surface only, so a tool this increment runs through Bash (the L13 + formatters) writes outside it unchecked. That is why those are pinned to one artifact path each + (L19), and the bound belongs on the claim, not only in the lessons block. +- "the repo stays style-clean and the floor stays GREEN with this in it" → **floor: enum-regex** — the + exit codes of `npm run check` (`format:check`, `lint:md`, `lint`, `docs:check`, `node --test`) and + `pharn/floor/validate.mjs`. +- "the reworded sentences are TRUE" → **advisory.** Nothing on the floor reads this file's prose. + Read live this run, not inferred: `validate.mjs:120` admits a file to the capability set only when + parsed frontmatter carries `role:`, and `validate.mjs:130` arms CHECK 5 only when the text matches + **both** `/rule_id:/` and `/problem:/`. `features/README.md` has no frontmatter and neither marker, + and the reword adds neither — so the file is walked (root `features/` is not in `EXCLUDE_SEGMENTS`, + L10) yet trips no content check. The truth of the sentences rests on the live reads recorded in + `## Open questions` — P6 evidence, not a floor reduction. +- "no `SKILLS_VERSION` bump is owed" → **advisory.** The bump-triggering set is a CLAUDE.md discipline; + no checker computes it from a diff. `check-specified-markers.mjs` binds trusted-doc marker claims, not + README bump eligibility. +- "the CHANGELOG entry carries no version" → **advisory** (the same discipline; nothing gates it). + +## Trust audit (P2) + +- The increment ingests no untrusted artifact: it reads repo-owned files and edits one of them; it + fetches nothing and emits no finding, so no taint propagates to any output. +- The **request text** driving this increment is human-authored intent, not fetched content — but it + was still treated as a claim to verify rather than a fact to copy (P6). Two of its premises did not + survive: it states the repo is at `SKILLS_VERSION 2.4.6` (live: **2.5.1**), and it offers the + `pharn/floor/README.md` half as needing confirmation (live: already fixed). Neither changes the fix; + both are recorded so no downstream stage inherits an unverified premise. + +## Determinism audit (P5) + +- "is `pharn/floor/README.md` in scope?" → membership test, not judgment: grep for a `node floor/…` + invocation. Zero matches live → excluded. +- "which paths may the build write?" → parsed deterministically by `set-writes-scope.cjs --from-plan` + from `## Files` above; no model chooses it. +- "is the increment green?" → exit codes only (`npm run check`, `validate.mjs`). +- The one irreducible judgment — whether line 8–9's "as those stages are built" reads as stale — does + **not** end in a guess: it is carried to the human as an open question below (the terminal fallback). + +## Open questions — RESOLVED at the approval gate + +**None open.** All three were put to the human as a selectable form at the plan-approval halt and +answered before any file was written; the plan was then approved as written. Recorded here so no +downstream stage reads a cleared blocker as live (`/pharn-dev-build` refuses a plan with unresolved +open questions). + +1. **Line 8–9 — "as those stages are built".** My read was **stale, and the same axis**: "built" is + what happens to PHARN, not what a user does to a stage, so the phrase dates the sentence to when the + pipeline was unbuilt — the same premise as line 18, written at the same time. Live check: all seven + spine stages ship as `.claude/commands/pharn-{spec,plan,grill,build,regress,verify,ship}.md`. + **RESOLVED — reword to `as the user runs each stage`.** +2. **`CHANGELOG.md` entry.** L1 argued for it; the request called it optional. + **RESOLVED — include it, carrying no version bump.** +3. **Sibling staleness found by the L1 sweep.** `pharn/pharn-contracts/finding-shape.md:81` calls the + `check-structural`-over-emitted-output wiring "increment 3c, **not yet built**", but + `/pharn-dev-eval` (`:125`) and `/pharn-verify` (`:210`) both invoke `check-structural.mjs` over an + emitted `findings.json` today, and canon L4 records two live runs of it. Same defect class on the + **product surface** (so: a bump). **RESOLVED — defer to a separate follow-up increment**, recorded + in `### Deliberately NOT in scope` above. + +## Grill findings folded back in (advisory — `GRILL.md` gates nothing) + +`/pharn-dev-grill` raised three concerns against this plan; all three were closed here **before** build, +which is why the text above differs from the approved version in exactly these three places: + +- **P6, important** — the open-questions section read as unresolved after the gate had cleared it. + Closed by the section immediately above. +- **P6, minor** — the floor-scanner claim was inferred rather than read. Closed by reading + `pharn/floor/validate.mjs:120` / `:130` live and citing both lines in the guarantee audit. +- **P0, minor** — a floor reduction stated without the Bash-escape narrowing the same plan names + elsewhere. Closed by moving the bound onto the claim itself. diff --git a/.dev/features/features-readme-spec-live/REGRESSION.md b/.dev/features/features-readme-spec-live/REGRESSION.md new file mode 100644 index 0000000..860fc1b --- /dev/null +++ b/.dev/features/features-readme-spec-live/REGRESSION.md @@ -0,0 +1,66 @@ +# REGRESSION — features-readme-spec-live + +**Base:** `2b4fec89cbd68e03544b9bad4254360ee029f040` (working-tree dogfood — `git status --porcelain` +was non-empty, so the deterministic state test resolved `base = HEAD`, P5; not a model's choice). + +## Partition (computed by `pharn/floor/check-regress.mjs scope`, exit 0) + +**Inside (the changed scope), 4 paths:** + +- `features/README.md`, `CHANGELOG.md` — the two the plan's `## Files` declared and + `/pharn-dev-build` was pinned to. +- `.dev/features/features-readme-spec-live/PLAN.md`, `.../GRILL.md` — this feature's own pipeline + artifacts, each written by its own stage under that stage's own Step-0 writes-scope. + +**No scope breach.** `scope` exited 0: nothing changed outside the declared writes. The two artifacts +above are listed in the helper's returned `escape_exempt`, read here rather than assumed — that +exemption is the floor check L20 demanded for the false-escape class L17 documents (cited, not +restated — P4). A stray file in the feature dir, another feature's artifact, or any real source path +would still have been an escape. + +**Outside:** 61 test files + 1 committed eval pair +(`trust-fence/evals/expected/expected-injection-comment.json` ↔ `.dev/features/trust-fence/findings.json`). + +## Gate set and the style-gate skip + +The gate set was decided **once** and applied identically at base and head (a mismatch is +`inconclusive`, never a silent pass): `tests`, `validate`, `structural:expected-injection-comment`. + +The style gates (`lint` / `format:check` / `lint:md`) were **deterministically skipped**: `inside` +touches no shared style config (`eslint.config.mjs`, `.prettierrc.json`, `.prettierignore`, +`.markdownlint-cli2.jsonc`), and over outside files that are byte-identical at base and head a style +result cannot flip. The skip also spares the baseline worktree an `npm ci` — the core gates are +stdlib-only. Absent from **both** maps, so the sets still match. + +The `tests` list was expanded through `xargs` reading **stdin**, never `xargs -a` (GNU-only, which +BSD/macOS `xargs` rejects outright) and never an unquoted `$LIST` (zsh does not word-split it). Both +forms fabricate an equal-at-both-sides red that reads as `pre_existing` while masking a real +tests-gate regression — L5's failure mode and L16's correction to L5's own remedy. + +## Per-gate exit codes + +| gate | base | head | classification | +| --------------------------------------- | ---- | ---- | -------------- | +| `tests` (61 outside files) | 0 | 0 | stable | +| `validate` (whole-repo) | 0 | 0 | stable | +| `structural:expected-injection-comment` | 0 | 0 | stable | + +`regressions[]`: **empty.** `pre_existing[]`: **empty.** + +## Verdict (FLOOR — the helper's, not this stage's) + +**REGRESSIONS: none — no deterministically-detectable breakage outside the feature.** +`check-regress.mjs verdict` exit **0**, `"verdict": "no-regressions"`, recorded verbatim in +`regression-report.json`. + +**The honest residual (P0/P7):** this catches **exactly what its suite catches — nothing more.** A +regression no deterministic check covers is invisible to it. The claim is "deterministically-detectable +breakage outside the feature is caught," **not** "nothing broke" — and emphatically not that the +increment is good. That the verdict is floor-grade is true of the **comparison** only; choosing the +base, partitioning inside/outside, and running the suite are this stage's advisory work. + +For this increment the residual is unusually wide and worth stating plainly: the change is two +sentences of English prose in a README, and **no deterministic check in the suite reads that prose**. +A green comparison here means the repo still builds and passes exactly as it did — it says nothing +about whether the reworded sentences are true. That claim rests on the live reads recorded in the +plan, and it is advisory. diff --git a/.dev/features/features-readme-spec-live/REVIEW.md b/.dev/features/features-readme-spec-live/REVIEW.md new file mode 100644 index 0000000..464a090 --- /dev/null +++ b/.dev/features/features-readme-spec-live/REVIEW.md @@ -0,0 +1,140 @@ +# REVIEW — features-readme-spec-live + +**Step 1, floor first (the only guaranteed part of this review):** `node pharn/floor/validate.mjs .` → +**GREEN**, 36 capabilities, exit 0. Everything below is **advisory**. + +> The increment under review is `trust: untrusted`. Every `problem` / `evidence` quotes it as DATA. + +## Floor-gate findings (blocking) + +**None.** No guarantee is claimed without a floor reduction; no eval binding is missing; no sibling +reference exists; no guaranteed decision rests on a tainted field. + +## Advisory findings + +### L-floor → P0 + +No finding. The increment claims no guarantee. Its two substantive assertions are **factual claims +about repo state** (`/pharn-spec` exists; `features/` is empty until used), not protection claims, so +the floor-or-advisory test resolves by not applying — and the PLAN's guarantee audit labels the +truth-of-the-prose claim `advisory` explicitly, naming the live reads it rests on instead of implying a +checker stands behind it. + +Worth recording because it is the increment's real hazard: a docs fix is the one change shape where +"the gates are green" is most tempting to read as "the text is right," and every artifact in this run +states the opposite. + +### L-eval → P1 + +No finding, and the floor agrees (no disagreement to report). `features/README.md` has no +`---`-fenced frontmatter, therefore no `role:` — it is not a Capability, so the "≥1 eval case + +expected" obligation does not attach, and it declares no `enforces` rule id owing a producing fixture. +Read structurally from the file, not from the plan's claim about it (L6). `validate` counted the same +36 capabilities before and after, which is the expected signature of a change that adds none. + +### L-trust → P2 + +```yaml +- type: FINDING + rule_id: "P6" + severity: minor + file: ".dev/features/features-readme-spec-live/PLAN.md:1" + problem: "The build request contained a garbled, truncated sentence at the exact point where it prescribed the replacement wording, and the increment resolved it by inference rather than asking — the terminal fallback P6 reserves for ambiguity is a question, not a reconstruction." + evidence: 'The request text: ''rewrites "Until then" (which implied non-existence) to "Until a usethe empty-fresh-clone fact). Do not reflow the rest of the file.'' — two clauses collapsed mid-word. Read as ''Until a user runs it'' plus ''keeping the empty-fresh-clone fact''.' +``` + +**Why it is minor rather than important:** the same message carried the intended replacement +**verbatim** in a block quote immediately above the garbled line, so the ambiguity was resolvable from +the message itself by comparison, not by guessing at intent — and the shipped sentence matches that +quoted text. The finding stands anyway because "resolvable by inference" is precisely the reasoning +that erodes halt-and-ask; recording it is cheaper than relying on the next garble being equally +redundant. + +**Did instruction-looking content in the reviewed artifacts change behavior?** No. Neither +`features/README.md` nor `CHANGELOG.md` contains instruction-shaped content. The directives in this run +came from the human invoker (scope limits, banned words), which is legitimate steering, not injected +content from a reviewed file. + +**Two request premises were rejected rather than inherited**, which is the discipline working in the +direction it exists for: the stated `SKILLS_VERSION 2.4.6` was live **2.5.1**, and the +`pharn/floor/README.md` half presented as needing confirmation was already fixed. Neither changed the +fix; both are recorded in the PLAN's trust audit so no downstream stage inherits an unverified premise. + +### L-axis → P3 + +No finding. `features/README.md` carries one reason to change (the product pipeline now ships); +`CHANGELOG.md` records that same change under the file's own "all notable changes are documented" +contract. No sibling reference: the increment touches no module and adds no `reads:` entry, so there is +nothing to route through `pharn-contracts`. + +### Precision of the shipped wording (advisory, outside the four lenses) + +```yaml +- type: FINDING + rule_id: "P6" + severity: minor + file: "features/README.md:18" + problem: "The reworded sentence asserts the directory is empty, but a fresh clone's features/ contains the README the reader is holding — the claim is true of product-pipeline artifacts, not of the directory literally." + evidence: "features/README.md:18 — '`/pharn-spec` writes the first `SPEC.md` here. Until a user runs it this directory is empty — the declared home for product-pipeline artifacts'. The pre-change text carried the same imprecision in a softer grammatical position ('the declared, empty home for product-pipeline artifacts')." +``` + +**Not a regression — an inherited imprecision made slightly more assertive.** The old wording attached +"empty" to "home for product-pipeline artifacts", which scopes it; the new wording states it of "this +directory" before the scoping clause arrives. No reader is misled in practice (they are reading the +file that disproves the literal reading), and the surrounding clause supplies the scope. Flagged so the +human can decide at the gate; a one-word remedy exists — "this directory holds no feature folders" — but +`/pharn-dev-review` does not edit built files, so it is not applied here. + +## Proposed lesson candidate (NOT promoted — `/pharn-dev-memory-promote` is a separate, human-gated run) + +**Candidate:** _A doc that says a capability is "not yet built" is a dated assertion with no expiry — +the moment it ships, the doc silently becomes false, and only the four trusted docs have a checker +that notices._ + +**Why it qualifies under L20's escalation rule (a real recurrence, not a hypothetical).** The repo +already met this failure and already built a remedy — `#128` (commit `2b4fec8`, the immediately +preceding commit) found four trusted docs asserting floor primitives that do not exist and answered +with `.dev/floor/check-specified-markers.mjs`, which binds those claims **both** directions. That +remedy's scope is the **four trusted docs plus a hand-maintained manifest**. Everything else in the +repo keeps the discipline-only remedy, and L20's finding is that a discipline-only remedy **will** +recur. It did, twice in this run's sweep alone: + +- **Fixed here:** `features/README.md:8` and `:18` — the product pipeline described as under + construction, in the guide a user reads first. +- **Found, deliberately not fixed:** `pharn/pharn-contracts/finding-shape.md:81` — "that wiring is + increment **3c, not yet built**", while `/pharn-dev-eval:125` and `/pharn-verify:210` both invoke + `check-structural.mjs` over an emitted `findings.json` today, and canon **L4** records two live runs + of exactly that. This one is **product surface**, so it bumps `SKILLS_VERSION` — its own increment. +- **Found, judgment call:** `.claude/commands/pharn-ship.md:350` — "`--loop` … is a **separate + follow-up increment**", while `/pharn-loop` ships that capability under a different name. The literal + claim ("not part of this command") remains true; the framing reads as unbuilt. + +**The asymmetry that makes it interesting, and the honest objection to its own remedy.** The existing +checker is possible because a _specified-but-absent primitive_ has a manifest of names to check +existence against. A prose "not yet built" has no such handle — the phrase names a **increment id** +(`3c`) or a **command**, and only the command case reduces to a file-existence test. So the candidate +remedy is narrower than the failure: a checker could bind "a doc says `/pharn-X` is unbuilt" to +"`.claude/commands/pharn-X.md` does not exist" — deterministic, primitive #3, no new floor kind — and +would have caught `features/README.md:18` and `pharn-ship.md:350`, but **not** `finding-shape.md:81`, +whose referent is an increment id no file existence test resolves. Promoting the lesson should not +imply the remedy is complete; that gap is the P7-honest part and belongs in the entry. + +**Provenance for the promote run:** + +- feature: `features-readme-spec-live` +- commit: `unknown` at review time (working-tree dogfood; base `2b4fec89cbd68e03544b9bad4254360ee029f040`) +- source: this `REVIEW.md`, plus `.dev/features/features-readme-spec-live/PLAN.md`'s `## Files` + exclusion block (which records the two deferred sites) and the L1 sweep run at plan time +- relates to: **L20** (discipline-only remedies recur; the second occurrence is the trigger to give it + a floor check) and **L1** (an increment must scope the meta-docs it invalidates — the sweep that + found all three sites) + +## Verdict + +**GREEN — 0 floor-gate findings, 2 advisory findings (both minor).** The increment is complete as +scoped: the two false sentences are corrected, `SKILLS_VERSION` is untouched at 2.5.1, +`pharn/floor/README.md` is untouched, and the full `npm run check` is green. + +Stated plainly, because this is the increment where it matters most: **the gates prove the repo is green +with the new bytes in it; they do not read the sentences.** That the sentences are now true rests on the +live command-directory read recorded in the PLAN and CHANGELOG, and that evidence is advisory. diff --git a/.dev/features/features-readme-spec-live/SHIP.md b/.dev/features/features-readme-spec-live/SHIP.md new file mode 100644 index 0000000..effeff7 --- /dev/null +++ b/.dev/features/features-readme-spec-live/SHIP.md @@ -0,0 +1,57 @@ +# SHIP — features-readme-spec-live + +Gated `/pharn-dev-ship` run (no `--loop`). Every stage ran in order and the run ended at **GATE 2**, the +post-review human decision. + +## Stages, in order, and where the run ended + +| # | stage | outcome | +| --- | -------------------- | --------------------------------------------------------- | +| 1 | `/pharn-dev-plan` | halted at **GATE 1**; human approved as written | +| 2 | `/pharn-dev-grill` | 3 advisory concerns; presented, proceeded (gates nothing) | +| 3 | `/pharn-dev-build` | floor GREEN | +| 4 | `/pharn-dev-regress` | `no-regressions` | +| 5 | `/pharn-dev-verify` | `PASS` | +| 6 | `/pharn-dev-review` | GREEN, 0 floor-gate findings — **GATE 2** | + +## The structural verdicts read, verbatim + +- **`/pharn-dev-build`** → `node pharn/floor/validate.mjs .` exit **0** (`FLOOR: GREEN — 36 capabilities +checked in .`). The spec-hash gate (fix #4) passed first: live + `sha256(pharn/ARCHITECTURE.md)` = `8f5ec002e3b18cbfd2f094b08a3671f7ed42a05a3fbaf01a11bbbd28da30fb52` + == the plan's `spec_content_hash`. +- **`/pharn-dev-regress`** → `regression-report.json` `.verdict` = **`"no-regressions"`**, helper exit + **0**. `regressions[]` and `pre_existing[]` both empty; base + `2b4fec89cbd68e03544b9bad4254360ee029f040`. +- **`/pharn-dev-verify`** → `verify-report.json` `.verdict` = **`"PASS"`**, helper exit **0**, + `failing_gates[]` empty, all six gates exit 0. The advisory `verifiers` block + (`registered: 0`) was **not** an input to that verdict (fix #3). + +Each verdict was read from the named stage's own checker. `/pharn-dev-ship` added **no** floor primitive +this run: running the stages in order is advisory sequencing, and every guarantee above belongs to a +sub-stage. + +## Pointers (cited, not restated — P4) + +- `.dev/features/features-readme-spec-live/REVIEW.md` — the four lenses, 2 advisory findings (both + minor), and a proposed lesson candidate for a separate human-gated promote run. +- `.dev/features/features-readme-spec-live/GRILL.md` — advisory; its 3 concerns were closed in the PLAN + before build, which is why the built plan differs from the approved text in exactly three places. +- `REGRESSION.md`, `VERIFY.md`, and the two machine reports alongside them. + +## What landed + +Two files, exactly the two the plan's `## Files` declared — the scope-setter printed **2 path(s)** at +build Step 0, matching the human-approved list, and `check-regress.mjs scope` exited 0 with no escape: + +- `features/README.md` — lines 8 and 18 no longer describe the shipped product pipeline as unbuilt. +- `CHANGELOG.md` — one `[Unreleased]` entry, carrying **no** version. + +`SKILLS_VERSION` is **untouched at 2.5.1** (README is repo-meta). `pharn/floor/README.md` is untouched +(already correct since #126, verified live rather than assumed). + +## Standing decision + +The 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.** Nothing was merged, pushed, or sealed, +and no `PHARN ✓ reviewed` seal was applied. diff --git a/.dev/features/features-readme-spec-live/VERIFY.md b/.dev/features/features-readme-spec-live/VERIFY.md new file mode 100644 index 0000000..80cbc58 --- /dev/null +++ b/.dev/features/features-readme-spec-live/VERIFY.md @@ -0,0 +1,57 @@ +# VERIFY — features-readme-spec-live + +## FLOOR layer — the deterministic gates (these OWN the verdict) + +Six gates, each run over the repo **with the feature in it**, at HEAD, recording exit codes only — +never stdout free-text. + +| gate | exit | what it covers | +| --------------------------------------- | ---- | --------------------------------------------------- | +| `test` | 0 | the hermetic suite (`npm test`) | +| `validate` | 0 | `pharn/floor/validate.mjs .` — the structural floor | +| `lint` | 0 | eslint | +| `format:check` | 0 | prettier, whole-repo (L9) | +| `lint:md` | 0 | markdownlint, whole-repo (L9) | +| `structural:expected-injection-comment` | 0 | the one committed eval pair the repo ships | + +This set is exactly the repo's `npm run check` aggregate, so the verdict tracks the full aggregate — +which is what closes L9's coverage hole for **this increment's own markdown**, the only category of +defect this docs-only change could plausibly have introduced. Both edited files are `.md` and both are +inside `lint:md` + `format:check`'s whole-repo scope (neither is on an ignore list, unlike the trusted +docs), so the style claim here is real rather than vacuous. + +**L11 applies and was pre-empted.** These style gates are whole-repo and run once at HEAD with no base +comparison, so a pre-existing red in an unrelated committed file would have failed this feature's +verify while the feature was clean. That is why the baseline `npm run check` was run **before** any +edit at plan time: it was green (1302 tests), so a red here would have belonged to this increment. +None appeared. + +**Two clocks, kept honest (P0, L9).** `check-verify.mjs` is generic over gate keys — it computes +`PASS iff every gate exit 0` over whatever map this stage assembles. The verdict is FLOOR; **which** +gates are in the map is this stage's advisory composition. Nothing floor-locks the two style gates into +the set, so do not read "verify runs the style gates" as a guarantee that it always will. + +## ADVISORY layer — the verifier plug-in slot + +`node pharn/floor/count-verifiers.mjs .` → `{"registered":0,"verifiers":[]}` — **no verifiers +registered; floor gates only.** Membership is a deterministic frontmatter read, never a prose grep: a +`role: verifier` string in prose or a fenced block is DATA about verifiers, not a declaration of one. + +Step 2 is therefore a no-op, and that is by design (P7): no verifier is authored speculatively. Had one +existed, its findings would be appended for the human and **never** passed to `check-verify.mjs`, which +cannot even receive a finding — the fix #3 separation is structural, not a promise. + +## Verdict (FLOOR) + +**PASS** — `check-verify.mjs` exit **0**, `"verdict": "PASS"`, `failing_gates: []`. Recorded in +`verify-report.json` with the advisory `verifiers` block merged in. + +**What PASS means, exactly (P0).** The named gates passed. It does **not** mean the feature is correct. +For this increment that gap is the whole story and should not be glossed: the change is two English +sentences, and **not one of these six gates reads what they say.** They prove the repo is green with the +new bytes in it — that nothing was broken, that the markdown is well-formed and style-clean. Whether +`/pharn-spec` actually ships, which is the entire claim the edit makes, is established by the live +command-directory read recorded in the plan and the changelog, and that evidence is **advisory**. + +Writing "`/pharn-dev-verify` verified the fix is right" would be the disease. It verified that the gates +named above exit 0. diff --git a/.dev/features/features-readme-spec-live/regression-report.json b/.dev/features/features-readme-spec-live/regression-report.json new file mode 100644 index 0000000..d88c120 --- /dev/null +++ b/.dev/features/features-readme-spec-live/regression-report.json @@ -0,0 +1,26 @@ +{ + "base": "2b4fec89cbd68e03544b9bad4254360ee029f040", + "inside": [ + "CHANGELOG.md", + "features/README.md", + ".dev/features/features-readme-spec-live/GRILL.md", + ".dev/features/features-readme-spec-live/PLAN.md" + ], + "outside_gates": { + "structural:expected-injection-comment": { + "base": 0, + "head": 0 + }, + "tests": { + "base": 0, + "head": 0 + }, + "validate": { + "base": 0, + "head": 0 + } + }, + "regressions": [], + "pre_existing": [], + "verdict": "no-regressions" +} diff --git a/.dev/features/features-readme-spec-live/verify-report.json b/.dev/features/features-readme-spec-live/verify-report.json new file mode 100644 index 0000000..6816f3d --- /dev/null +++ b/.dev/features/features-readme-spec-live/verify-report.json @@ -0,0 +1,17 @@ +{ + "feature": "features-readme-spec-live", + "gates": { + "format:check": 0, + "lint": 0, + "lint:md": 0, + "structural:expected-injection-comment": 0, + "test": 0, + "validate": 0 + }, + "verdict": "PASS", + "failing_gates": [], + "verifiers": { + "registered": 0, + "findings": [] + } +} diff --git a/CHANGELOG.md b/CHANGELOG.md index ab04a34..673d7e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), ### Fixed +- **`features/README.md` stopped describing the shipped product pipeline as unbuilt.** The directory guide still read as it did before the product commands existed: `/pharn-spec` was called "(a later increment)", with "Until then this directory is the declared, empty home…" asserting the command does not exist yet, and the artifact list described the downstream stages "as those stages are built". All seven spine stages ship — verified live this run as `.claude/commands/pharn-{spec,plan,grill,build,regress,verify,ship}.md` — so a user cloning the repo read a false statement about tools they can already run. The two sentences are now present-tense while **keeping** the accurate fact the old wording carried: a fresh clone's `features/` really is empty until the user runs the command, which is what makes the directory a declared home rather than a populated one. + + **No `SKILLS_VERSION` bump (it stays 2.5.1), and that is the rule, not an exemption.** `features/README.md` is a README, and the versioning discipline exempts pure repo-meta from the bump — the decision is settled by the file's _kind_, not by whether an install scaffold copies it, and the concrete bump-triggering set (the `pharn/` tree, `pharn/floor/*.mjs`, the four trusted docs, the product `.claude/` surface) does not list it. This entry carries no version either. + + **Scope, and what the sweep found but did not fix.** The originally-reported second half — a dead `node floor/validate.mjs` in `pharn/floor/README.md` — was verified **already fixed** (PR #126, now `node pharn/floor/validate.mjs:34`) and left untouched. The meta-doc sweep the plan owed surfaced one genuine sibling of the same class, deliberately deferred: `pharn/pharn-contracts/finding-shape.md:81` still calls the `check-structural`-over-emitted-output wiring "increment **3c, not yet built**", though `/pharn-dev-eval:125` and `/pharn-verify:210` both invoke it today. That one is **product surface**, so correcting it bumps `SKILLS_VERSION` — a different axis, and its own increment. + - **Three trusted docs stopped describing floor primitives that do not exist as running checks — and a shipped lens was named by a name it has never had.** The governing text asserted, in the present tense, protections the repo does not have. Each site was verified absent **this run** rather than accepted from the request: **no `.claude/hooks/*egress*` file exists** (three hooks, none of them egress); **no archetype-maps manifest exists** at the path `pharn/floor/validate.mjs:233` looks for, and validate's own header (`:15`) already calls CHECK 7 **"conditional — if an archetype-maps manifest exists"**, so the check has never fired; **no `/pharn-estimate` command exists** and `est_tokens` is named only in prose, emitted by no `.mjs`/`.cjs`/`.json`; **no `pharn/pharn-audits/` module exists**; and `pharn/pharn-review/` ships **22 lenses**, among them `secrets-in-code` — there is no `security-secrets`. Ten sites are now annotated `(specified; ships with the guarded surface)`, and one is a **name correction**, not a marker: a lens that ships under a different name is not a deferred primitive. **The rule applied, and its one exception.** Where a doc names a floor primitive that is designed but not running, its present-tense phrasing claims a protection that does not exist — the P0 disease ("written in the contract" mistaken for "therefore guaranteed") reproduced in the documents that **define** that disease. The marker preserves the design intent without asserting the protection. `THREAT-MODEL.md:91`'s `_Closed._` on fix #5 was the sharpest case: a closure status is a stronger claim than a description, and CHECK 7's own **conditional** wording contradicted it, so it now reads _"Specified; the check is conditional and no manifest exists, so it never fires."_ — stating the mechanism, not merely withdrawing the claim. diff --git a/features/README.md b/features/README.md index 636a235..3762424 100644 --- a/features/README.md +++ b/features/README.md @@ -5,7 +5,7 @@ Each increment a **PHARN user** runs through the product pipeline gets one folde - `SPEC.md` — intent (Draft → Approved), the root artifact every downstream stage carries `spec_id` from (`ARCHITECTURE.md §6`) -- the downstream product-pipeline artifacts as those stages are built (`spec → plan → grill → build → +- the downstream product-pipeline artifacts as the user runs each stage (`spec → plan → grill → build → regress → verify → ship`) This mirrors `.dev/features/` — but for the **product loop**, not the build loop. The split is the @@ -15,6 +15,6 @@ dev/product boundary made structural: - **`.dev/features/`** = the audit trail of **building PHARN itself**, with the `pharn-dev-*` commands (contributor apparatus — see `.dev/features/README.md`). -`/pharn-spec` (a later increment) writes the first `SPEC.md` here. Until then this directory is the -declared, empty home for product-pipeline artifacts — so every product capability lands on the product -side of the boundary from the start, never needing a later migration. +`/pharn-spec` writes the first `SPEC.md` here. Until a user runs it this directory is empty — the +declared home for product-pipeline artifacts, so every product capability lands on the product side of +the boundary from the start, never needing a later migration.