diff --git a/.claude/commands/pharn-loop.md b/.claude/commands/pharn-loop.md index 9afd700..b043d41 100644 --- a/.claude/commands/pharn-loop.md +++ b/.claude/commands/pharn-loop.md @@ -92,8 +92,10 @@ There is **no `--yolo`** and no self-approving mode — see "What `/pharn-loop` slug. - **`--max-iter N`** sets the cap `M` (a positive integer). **Absent ⇒ default `M = 3`.** The cap is the deterministic bound; `check-loop.mjs` enforces it structurally (`iter >= cap` → `STOP_CAP`). A config-file - cap key (`pharn.config.json`) is **deferred** (P7 — no project config consumer exists yet; the floor bound - is identical either way). + cap key (`pharn.config.json`) is **deferred** (P7 — no real need has surfaced, and the floor bound is + identical either way: `check-loop.mjs` reads `--cap`, whatever set it). Note the deferral rests on P7 + alone, **not** on the absence of a config consumer — `/pharn-build` already reads the `seam` block and + `/pharn-ship` reads `ship.requireAttestation` from that file. ### Step 1b — read the PRIOR record for this slug, if one exists (context only; it gates NOTHING) diff --git a/.claude/commands/pharn-plan.md b/.claude/commands/pharn-plan.md index 5d2e0ef..b1d5608 100644 --- a/.claude/commands/pharn-plan.md +++ b/.claude/commands/pharn-plan.md @@ -304,8 +304,10 @@ chain to `/pharn-grill` or `/pharn-build` (later stages). **End your turn.** `enforce-writes-scope.cjs` pin the one declared path). - **"The plan carries `spec_content_hash` forward"** → a **deterministic copy** of a floor-verified value into the PLAN.md frontmatter — checkable in principle; **not** independently floor-checked at - this stage (the consumer that re-verifies spec↔plan is a later stage, not built yet — P7). Honest - label: deterministic, not yet re-verified. + this stage. The consumer that re-verifies spec↔plan is a later stage and **is built**: + `pharn/floor/check-plan-spec-agree.mjs`, run by `/pharn-grill` (the first re-verifier), then again by + `/pharn-build`, `/pharn-regress` and `/pharn-verify`. Honest label: deterministic, **not re-verified at + THIS stage** — the pin is checked downstream, never here. - **"The plan's CONTENT is correct / complete"** → **ADVISORY**. Model judgment; downstream grill / build / verify check it. Claiming `/pharn-plan` "ensures a correct plan" would be the disease — struck. diff --git a/.claude/commands/pharn-review.md b/.claude/commands/pharn-review.md index 2523d73..98bc4af 100644 --- a/.claude/commands/pharn-review.md +++ b/.claude/commands/pharn-review.md @@ -81,7 +81,9 @@ scanner binding, consistency-tested by `lens-scanner-map.test.mjs`): target** (an honestly-labeled advisory bound, not a floor claim). The scanner's **output** is FLOOR (a deterministic regex verdict); using it to **choose the slice** is -**advisory orchestration** (the isolated per-lens runner is deferred, P7 — as for every lens today). +**advisory orchestration** — nothing on the floor forces the slice to be derived this way, or at all. +(The isolated per-lens runner itself is **not** deferred: Step 4 below spawns one subagent per lens. The +per-GRILLER runner remains deferred, P7 — `/pharn-grill` applies a griller inline.) ## Step 3b — Discover the user's installed skills (ADVISORY context for the lenses; enumeration gates nothing) diff --git a/.claude/commands/pharn-ship.md b/.claude/commands/pharn-ship.md index 6d99219..40bc3de 100644 --- a/.claude/commands/pharn-ship.md +++ b/.claude/commands/pharn-ship.md @@ -1,5 +1,5 @@ --- -description: "Run the PRODUCT pipeline in order so a PHARN user need not re-type or memorize it: /pharn-spec → [human approves the SPEC] → /pharn-plan → /pharn-grill → /pharn-build → /pharn-regress → /pharn-verify → [human decides merge/fix/abandon]. The seventh, terminal pipeline stage (pharn/ARCHITECTURE.md §6), realized as a GATED meta-orchestrator over stages 1–6 — the agent INVOKES each stage (advisory); WHETHER to proceed past a stage is read from that stage's STRUCTURAL floor verdict (check-spec-approved / check-plan-spec-agree exits, the build project-gate exit, regression-report.json .verdict, verify-report.json .verdict), NEVER the agent's judgment. Reuses the six product stage commands and their existing floor checkers; reimplements none. Two human gates — SPEC approval (Draft→Approved) and the post-verify decision — are NON-NEGOTIABLE; NO --yolo, NO self-approval. Gated mode with at most ONE bounded build-completion retry on an INCOMPLETE verify (Step 2b — a single re-build, NOT a loop; the ≤1 bound is structural, the firing reads /pharn-verify's deterministic INCOMPLETE verdict); --loop is still a separate follow-up increment. At GATE 2 (Step 2c), also renders `features//BRIEFING.md` — a deterministic, cross-file-verified 'what/why/does-it-match' summary assembled by pharn/floor/render-ship-briefing.mjs from committed sources (never a self-issued seal, never a GATE-2 precondition; see pharn/pharn-contracts/ship-briefing.md). FLOOR verdicts; ADVISORY orchestration. '/pharn-ship reached the end' NEVER means 'the feature is good' — it means the deterministic gates passed and the human approved intent (P0)." +description: "Run the PRODUCT pipeline in order so a PHARN user need not re-type or memorize it: /pharn-spec → [human approves the SPEC] → /pharn-plan → /pharn-grill → /pharn-build → /pharn-regress → /pharn-verify → [human decides merge/fix/abandon]. The seventh, terminal pipeline stage (pharn/ARCHITECTURE.md §6), realized as a GATED meta-orchestrator over stages 1–6 — the agent INVOKES each stage (advisory); WHETHER to proceed past a stage is read from that stage's STRUCTURAL floor verdict (check-spec-approved / check-plan-spec-agree exits, the build project-gate exit, regression-report.json .verdict, verify-report.json .verdict), NEVER the agent's judgment. Reuses the six product stage commands and their existing floor checkers; reimplements none. Two human gates — SPEC approval (Draft→Approved) and the post-verify decision — are NON-NEGOTIABLE; NO --yolo, NO self-approval. Gated mode with at most ONE bounded build-completion retry on an INCOMPLETE verify (Step 2b — a single re-build, NOT a loop; the ≤1 bound is structural, the firing reads /pharn-verify's deterministic INCOMPLETE verdict); --loop is still a separate follow-up increment (the bounded auto-iteration capability itself ships today as the separate /pharn-loop command). At GATE 2 (Step 2c), also renders `features//BRIEFING.md` — a deterministic, cross-file-verified 'what/why/does-it-match' summary assembled by pharn/floor/render-ship-briefing.mjs from committed sources (never a self-issued seal, never a GATE-2 precondition; see pharn/pharn-contracts/ship-briefing.md). FLOOR verdicts; ADVISORY orchestration. '/pharn-ship reached the end' NEVER means 'the feature is good' — it means the deterministic gates passed and the human approved intent (P0)." kind: pharn-owned trust: trusted model_tier: sonnet @@ -485,6 +485,13 @@ Then **end your turn** at the human gate. `/pharn-ship` does not merge, push, or ## `/pharn-ship --loop` — deferred to a separate increment (NOT built here) +> **The capability is NOT unavailable — it is a different command.** `/pharn-loop` +> (`.claude/commands/pharn-loop.md`) **is built** and iterates the product pipeline's +> `build → regress → verify` middle to a floor-grade stop, with both human gates preserved. What does not +> exist is a **`--loop` flag on `/pharn-ship`**. Reach for `/pharn-loop` when you want bounded +> auto-iteration; its stop core is the tested `pharn/floor/check-loop.mjs` (Design B, retryable-only — +> it CONTINUEs only on `/pharn-verify`'s `INCOMPLETE`), **not** `check-ship.mjs`. + `--loop` (iterate `build → regress → verify` to a floor-grade stop, then present) is a **separate follow-up increment** — the same split `/pharn-dev-ship` used (gated first, `--loop` second). It is **not** part of this command. When built, it would reuse the **already-existing, tested** `pharn/floor/check-ship.mjs` stop core diff --git a/.dev/features/forward-looking-claims-sweep/GRILL.md b/.dev/features/forward-looking-claims-sweep/GRILL.md new file mode 100644 index 0000000..f4cb83b --- /dev/null +++ b/.dev/features/forward-looking-claims-sweep/GRILL.md @@ -0,0 +1,113 @@ +# GRILL — forward-looking-claims-sweep + +Advisory interrogation of the approved `PLAN.md` before `/pharn-dev-build`. **This stage gates +nothing** (`grill.md`) — it surfaces concerns; the build proceeds regardless. Findings below use the +enum-gated / free-text split (`pharn/pharn-contracts/finding-shape.md`, cited not restated — P4). + +## Verdict + +**PROCEED.** Four findings, all resolved or converted into build constraints before the build ran. +No finding invalidates the plan's enumeration; one (G2) independently **strengthens** its largest +class. + +--- + +## G1 — the plan's `## Files` omits `docs/capabilities/**`, and the risk is real but does not bite + +- type: `gap` +- rule_id: `CLAUDE.md docs:check` +- severity: `advisory` +- file: `.dev/features/forward-looking-claims-sweep/PLAN.md` + +**problem** (free-text, untrusted): `docs/capabilities/**` is a GENERATED region guarded by +`npm run docs:check` at byte-equality, and it renders **37** pages including a page for every lens +and griller this increment edits. The plan's `## Files` names none of them. If the catalog embedded +body prose, all 31 edits would drift the catalog and RED the `docs:check` gate — with the writes-scope +simultaneously denying the regeneration write. + +**evidence**: `docs/capabilities/` contains 37 files (`a11y.md`, `copy-paste-drift.md`, +`injection.md`, …). `head -20 docs/capabilities/injection.md` shows the rendered page carries +**frontmatter fields only** — Role / Kind / Version / Applies / Coupling / Enforces / Model tier — +plus the one-line `purpose`, and is 23 lines total. + +**resolution**: `grep -rl "isolated lens runner|runner yet invokes" docs/` returns **nothing**. The +catalog does not render body prose, so body-only edits cannot drift it. + +**Converted into a BUILD CONSTRAINT:** every edit in this increment must stay **below the +frontmatter fence**. Touching `purpose:` or any rendered field turns G1 from theoretical into a RED. +`npm run docs:check` is run at the end regardless (it is inside `npm run check`), so the constraint +is verified, not merely intended. + +--- + +## G2 — Class A's premise was single-sourced; a second independent witness now confirms it + +- type: `unstated-assumption` +- rule_id: `P6` +- severity: `advisory` +- file: `.dev/features/forward-looking-claims-sweep/PLAN.md` + +**problem** (free-text, untrusted): Class A (20 sites — the largest in the increment) rests on the +claim "the isolated lens runner landed." The plan evidenced this from **one** file — `pharn-review.md` +Step 4. If "the review stage" in the lens prose meant `/pharn-dev-review` rather than `/pharn-review`, +and `/pharn-dev-review` applied the 22 lenses inline, the lens sentences would be **correct** and +Class A would be a 20-file over-edit — by far the worst outcome available in this increment. + +**evidence**: `.claude/commands/pharn-dev-review.md:109-110` states directly: _"the +`pharn/pharn-review/*` **code** lenses over a code increment … use **`/pharn-review`** — it runs them +as parallel subagents and merges deterministically (`count-lenses` + …)"_. + +**resolution**: **CONFIRMED, from the opposite direction.** `/pharn-dev-review` does **not** apply the +22 lenses; it explicitly delegates them to `/pharn-review`, and describes that command as running them +as parallel subagents. So both candidate readings of "the review stage" agree the isolated runner +exists. Class A stands on two independent witnesses. + +--- + +## G3 — the lens/griller prose has multiple spellings; a blanket replace would be the L33 defect again + +- type: `risk` +- rule_id: `L33` +- severity: `advisory` +- file: `pharn/pharn-review/` + +**problem** (free-text, untrusted): The plan states the classes but not the **edit method**. The +26 body edits (19 lenses + 7 grillers) are near-duplicates, which invites a scripted +find-and-replace. That is precisely how this defect class propagates: `coupling` already proves the +spelling varies (`no **live** runner yet invokes it`), and the griller class has at least three +distinct phrasings. A single replacement string would silently skip the variants — reproducing L33 +inside L33's own remedy. + +**resolution / BUILD CONSTRAINT**: each of the 26 files is **read and edited individually** against +its own wording. After the build, a re-scan with the same whitespace-normalized scanner must report +**zero** surviving sites in Classes A–F — a post-condition, not an intention. + +--- + +## G4 — "no floor check" is a P7 judgment the plan argues but cannot discharge + +- type: `unresolved-tension` +- rule_id: `L20` +- severity: `advisory` +- file: `.dev/features/forward-looking-claims-sweep/PLAN.md` + +**problem** (free-text, untrusted): L20's trigger has fired — this class recurred, inside the +increment that named it. The plan declines the checker on P7 grounds (the manifest is a separate axis +of change) and records `forward-looking-claims-manifest` as a follow-up. That reasoning is sound but +**self-issued**: the same argument would justify deferring the checker indefinitely, one increment at +a time. Nothing structural forces the follow-up to ever land. + +**Not resolvable at this stage, and deliberately not resolved.** It was surfaced to the human at +GATE 1 with an explicit offer to re-plan and fold the checker in; the human approved the plan as +written. Recorded here so the deferral is auditable rather than invisible — and so the second +recurrence, if it comes, has this note to point at. + +--- + +## What this grill did NOT check + +- Whether each of the 31 rewritten sentences is **true**. That is `/pharn-dev-verify`'s floor gates + and `/pharn-dev-review`'s lenses, and ultimately the human's — a grill reads the plan, not the diff. +- Whether the enumeration is **complete**. G3 constrains the method and adds a post-condition + re-scan, but a claim spelled in a way no pattern anticipated survives both. The plan's own + guarantee audit states this; the grill confirms it is stated, not that it is solved. diff --git a/.dev/features/forward-looking-claims-sweep/PLAN.md b/.dev/features/forward-looking-claims-sweep/PLAN.md new file mode 100644 index 0000000..d37f765 --- /dev/null +++ b/.dev/features/forward-looking-claims-sweep/PLAN.md @@ -0,0 +1,261 @@ +# PLAN — forward-looking-claims-sweep: correct every expired "not yet built" claim on the shipped surface + +- spec_content_hash: 8f5ec002e3b18cbfd2f094b08a3671f7ed42a05a3fbaf01a11bbbd28da30fb52 # fix #4 +- applied_lessons: [L33, L29, L20, L25] +- increment: Re-derive every product-surface sentence that describes LANDED work as future or deferred. Corrections only — each edit preserves the bound that survives and names the invoker that now exists. +- layer(s): none — prose corrections across pharn-review lenses, pharn-pipeline grillers, pharn-contracts, and four product commands # pharn/ARCHITECTURE.md §4 +- constitution_refs: [P0, P6, P7] + +## Applied lessons + +- **L33** — this increment IS L33's remedy. Its two prescriptions were followed literally: (a) the + enumeration was derived from the **shortest substring invariant across paraphrase**, not from the + sentence I happened to read; (b) every prior enumeration was treated as a **lower bound to beat**. + Both paid off — see "How the enumeration was derived" below. The proposing prompt named 6 grillers; + the real griller count is **7**; and the largest class in this increment (**20 sites**) appears in + neither the prompt nor L33. +- **L29** — the deliverable is the **ENUMERATION**, not the three seeds. The enumeration is Section + "The enumeration" below, stated as a closed set with per-class verdicts and an explicit + false-positive list, so a later reader can audit what was ruled OUT as easily as what was changed. +- **L20** — a defect whose only remedy is "remember to update it" has earned a floor check. This + increment adds **no** checker, and that is a recorded decision with a reason, not an oversight; see + "Why no floor check (P7)". +- **L25** — each fix RE-DERIVES the sentence from the live tree rather than deleting the visible "not + yet". Every rewrite names the invoker that now exists AND preserves the honest bound that survives + (nothing fires at write time / nothing on the floor forces a run). + +## What this is + +`#165` (2.7.14) was the increment whose entire purpose was re-deriving this claim class. It fixed one +instance in `pharn/pharn-contracts/eval-format.md` and **left a second in the same file** — the defect +that produced L33. This increment discharges the class properly: a whitespace-normalized sweep of the +whole product surface (`pharn/**` + the `pharn-*`, non-`pharn-dev-*` commands), every hit classified, +nothing blanket-edited. + +## How the enumeration was derived (the part L33 says is transferable) + +Line-based `grep` is **structurally wrong** for this class and undercounted it three times: + +| Method | Grillers found | +| ------------------------------------- | -------------- | +| The proposing prompt's grep | 6 | +| `grep -rn "runner yet invokes"` | 4 | +| Whitespace-normalized scan (this run) | **7** | + +The claims **wrap across source lines** (`no runner yet\n invokes it`), so a line-anchored pattern +sees a fragment. Every count here comes from a scanner that normalizes whitespace **first**, then +matches — and that is the reason the 20-site lens class was found at all. Raw sweep: 172 hits across +72 files, each classified below. + +The `coupling` griller confirms L33's variant-spelling warning independently: it spells the claim +`no **live** runner yet invokes it`, so it is invisible to the exact-phrase scan that finds the other +six. + +## The enumeration + +### CONFIRMED STALE — corrected by this increment (7 classes, 30 files) + +**A. The isolated LENS runner — 19 lens files + 1 command = 20 sites. NOT seeded by the prompt or by +L33; the largest class in this increment.** + +Every scanner-bearing lens carries: _"until the live isolated lens runner lands (deferred P7, as for +every lens/griller), the review stage **applies this lens inline**"_. This is contradicted by +`.claude/commands/pharn-review.md` **Step 4**: _"Spawn **one subagent per lens** … Each subagent +applies its lens and **writes its own `features//lenses//findings.json`**"_. That is an +isolated per-lens runner, and it shipped. + +`pharn/pharn-review/{copy-paste-drift,duplicated-logic,injection,insecure-crypto,magic-values,missing-await,missing-error-handling,missing-timeout,n-plus-one,null-deref,off-by-one,path-traversal,placeholder-as-done,race-condition,resource-leak,secrets-in-code,ssrf,swallowed-exception,unsafe-deserialization}/*.md` + +**The enumeration closes arithmetically:** `count-lenses.mjs` reports **22** registered lenses; 19 +carry the claim; the 3 that do not (`hallucinated-api`, `input-validation`, `trust-fence`) are exactly +the scanner-less lenses, which have no Layer-1 scanner block to carry it. 19 + 3 = 22. No 20th site +is hiding behind a variant spelling. + +**Bound that survives, preserved in every rewrite:** nothing on the floor forces every lens to run — +`pharn-review.md` itself says _"nothing on the floor forces parallelism or forces every lens to run"_. +The guarantee remains "the scanner IS deterministic", never "the model always ran it". + +**B. `.claude/commands/pharn-review.md:84` — the same claim, in the command that refutes it.** + +_"the isolated per-lens runner is deferred, P7 — as for every lens today"_ sits **28 lines above** the +Step 4 that spawns one subagent per lens. A self-contradiction within one file. + +**C. The griller `check-structural` runner — 7 grillers (seed 2, corrected from 6).** + +`pharn/pharn-pipeline/grillers/{a11y,comprehension,coupling,documentation,error-handling,migrations,performance}/*.md` + +Each claims _"no runner yet invokes it over this griller's live output — deferred P7, as for every +griller and `finding-shape.md`'s 3c runner"_. Re-derived live: `pharn/pharn-contracts/finding-shape.md` +now states the **opposite** — _"the runners that invoke it over emitted output **have landed**: +`/pharn-verify` / `/pharn-dev-verify` run it per committed eval pair, and the dev-side +`/pharn-dev-eval` (increment 3c) runs it over each live-emitted `runs//findings.json`"_. All seven +ship committed eval pairs (verified: cases 2–4, expected 4–8 each), so the verify-time runner reaches +every one of them. Five of the seven **cite `finding-shape.md` by name** for a deferral that document +no longer records — a stale answer down a trusted-looking chain. + +**Bound that survives:** nothing fires at **grill time** or at **write time**; enforcement is +verify/eval-time. Preserved verbatim in each rewrite, per the increment's instruction. + +**D. `pharn/pharn-contracts/eval-format.md:52` (seed 1) — the instance `#165` missed in the file it +named.** + +_"(the checker that runs these is the NEXT increment)"_ — `pharn/floor/check-structural.mjs` ships, is +tested, and is invoked by `/pharn-verify`, `/pharn-dev-verify`, `/pharn-dev-eval`, `/pharn-regress`, +`/pharn-dev-regress`. + +**E. `.claude/commands/pharn-plan.md:307` — NOT seeded. A cite-chain drift identical in shape to C.** + +_"the consumer that re-verifies spec↔plan is a later stage, **not built yet** — P7"_. Refuted: +`pharn/floor/check-plan-spec-agree.mjs` exists (15.8 KB, has its own `.test.mjs`) and is invoked by +**seven** commands. `.claude/commands/pharn-grill.md` addresses this sentence directly: +_"(`pharn-plan.md` deferred this re-verifier to 'a later stage' — **you are that stage**)"_. The +downstream file knows; the upstream file was never updated. + +**Bound that survives:** the pin still is not re-verified **at plan time** — the honest label +`deterministic, not yet re-verified` is accurate **for this stage** and is kept. Only "not built yet" +is false. + +**F. `.claude/commands/pharn-loop.md:95` — NOT seeded. The claim is right; its stated REASON is false.** + +_"a config-file cap key (`pharn.config.json`) is **deferred** (P7 — **no project config consumer +exists yet**; the floor bound is identical either way)"_. The cap key genuinely does not exist — that +half stays. But two product commands already consume `pharn.config.json`: `/pharn-build` reads the +`seam` block (`pharn-build.md:188`), and `/pharn-ship` reads `ship.requireAttestation` +(`pharn-ship.md:426`). The deferral must rest on its **surviving** argument (P7 — no real need has +surfaced; the floor bound is identical either way), not on a consumer-absence that is untrue. + +**G. `.claude/commands/pharn-ship.md:2` + `:486` (seed 3) — the judgment call, resolved as "fix it".** + +The literal claim is **TRUE**: no `--loop` flag on `/pharn-ship` exists. What is stale is the +**impression** that the capability is unavailable — `/pharn-loop` **is** built +(`.claude/commands/pharn-loop.md`) and does exactly what the section describes as unbuilt (iterate +`build → regress → verify` to a floor-grade stop via a tested checker). A reader who takes the +sentence at face value concludes PHARN cannot auto-iterate the product pipeline. It can. + +**Resolved as a POINTER, not a rewrite** (the increment's own instruction: "Fix it as a pointer, or +record why not"). The heading and the "not built here" claim stay — they are true — and gain a +sentence naming `/pharn-loop` as the shipped command that provides the capability today. The +distinction preserved: `/pharn-loop` is a **separate command**, not a flag on `/pharn-ship`, and its +stop core is `check-loop.mjs`, not `check-ship.mjs`. + +### CONFIRMED CORRECT — false positives, deliberately LEFT (the audit half of L29) + +| Site | Claim | Why it stays | +| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| 14 files: 13 grillers + `pharn-grill.md` | "isolated per-griller runner is deferred" | **TRUE.** `/pharn-grill` spawns **zero** subagents — verified — and applies the griller inline. The lens twin landed; this one did not. The two look identical and are not. | +| 19 lenses + 8 `scan-code-*.mjs` | multi-file sweep / taint analysis "not built speculatively" | **TRUE** — genuinely unbuilt, correctly P7-labeled. | +| `.claude/commands/pharn-verify.md` | "ZERO verifiers authored" | **TRUE.** 18 `role: verifier` hits are all **prose mentions**; zero frontmatter declarations. | +| `pharn/floor/validate.mjs`, `pharn/floor/README.md` | "not built PHARN capabilities" | Different sense of "built" — means _is not a capability_, not _is unfinished_. | +| `pharn/floor/validate.mjs` | `scan-plan-*` "NOT built (resident nowhere)" | **TRUE** — the never-built ghosts CLAUDE.md documents. | +| `check-spec.mjs:276`, `check-spec-approved.mjs:126`, `scan-code-placeholder.mjs:12`, `gen-lessons-index.mjs:59`, `check-lessons-index.mjs:86`, `check-plan-lessons.mjs` | "not yet pinned" / "no cache yet" / keyword list | **Runtime state**, not unbuilt features — the prompt's own predicted false-positive list, confirmed. | +| `pharn-spec.md:119`, `pharn-plan.md` (`NO_CANON`/`COLD`), `pharn-memory-promote.md:415`, `pharn-build.md` | "not yet pinned" / "no lessons yet" / "not yet writable" | **Runtime state** — the honest normal state of a fresh install or an in-flight stage. | +| `README.md:241` "Not yet built." block | `pharn-audits`, `pharn-skills-*`, `pharn-stack-*` | **TRUE**, and `check:markers` already guards it. Excluded by the prompt; independently re-confirmed. | + +### OUT OF SCOPE — reported, not edited + +- **Trusted docs (hook-denied): NOTHING TO REPORT.** All four were swept. The only hit — + `THREAT-MODEL.md`, "the (deferred) AI/LLM-security lens" — is a **false positive**: no such lens + exists among the 22 registered. No human edit is owed. +- **`.dev/**` and `pharn-dev-*`:** not swept for correction (apparatus, and this increment is about + shipped bytes). Recorded as a follow-up: **`apparatus-forward-looking-sweep`**. + +## Why no floor check (P7 + L20) + +L20 says a defect whose only remedy is "remember to update it" has earned a floor check, and this +class has now recurred twice. A checker is **not** added here, deliberately: + +1. **P7 — the shape is not yet knowable.** A checker for "prose whose tense expired" needs a + **structured membership set** (the `check-specified-markers.mjs` pattern: a hand-maintained + manifest, never a prose scan — L6). Authoring that manifest is a separate axis of change from + correcting the sentences, and doing both in one increment would mean deriving the manifest from + the very prose this increment is rewriting. +2. **The honest bound on what such a checker could do.** It could only re-verify claims a human + already enumerated — "the manifest checked out" would never mean "the docs are true", exactly as + `check-specified-markers.mjs` states of itself. That is worth building, and worth building + **deliberately**. + +Recorded as the follow-up **`forward-looking-claims-manifest`** rather than silently dropped. + +## Steps + +1. Rewrite Class A in all 19 lens files: replace the "until the live isolated lens runner lands … + applies this lens inline" premise with the shipped runner (`/pharn-review` spawns one subagent per + lens, each writing its own `findings.json`), preserving the surviving conclusion verbatim — the + act of running is advisory; nothing on the floor forces every lens to run. +2. Rewrite Class B (`pharn-review.md:84`) to agree with its own Step 4. +3. Rewrite Class C in all 7 grillers: name the landed runners (`/pharn-verify`, `/pharn-dev-verify` + per committed eval pair; `/pharn-dev-eval` over live-emitted findings), keep the bound that + nothing fires at grill/write time, and drop the stale `finding-shape.md` 3c-deferral cite. +4. Fix Class D (`eval-format.md:52`) — name `check-structural.mjs`. +5. Fix Class E (`pharn-plan.md:307`) — the re-verifier exists; keep "not re-verified at THIS stage". +6. Fix Class F (`pharn-loop.md:95`) — keep the deferral, correct its reason. +7. Fix Class G (`pharn-ship.md:2` + `:486`) — add the `/pharn-loop` pointer; keep the true claims. +8. Bump `SKILLS_VERSION` 2.7.14 → 2.7.15, update the README shields badge, add the CHANGELOG entry. +9. `npm run check` (8 gates) + `node pharn/floor/validate.mjs .` GREEN. + +## Files + +- `pharn/pharn-review/copy-paste-drift/copy-paste-drift.md` — Class A +- `pharn/pharn-review/duplicated-logic/duplicated-logic.md` — Class A +- `pharn/pharn-review/injection/injection.md` — Class A +- `pharn/pharn-review/insecure-crypto/insecure-crypto.md` — Class A +- `pharn/pharn-review/magic-values/magic-values.md` — Class A +- `pharn/pharn-review/missing-await/missing-await.md` — Class A +- `pharn/pharn-review/missing-error-handling/missing-error-handling.md` — Class A +- `pharn/pharn-review/missing-timeout/missing-timeout.md` — Class A +- `pharn/pharn-review/n-plus-one/n-plus-one.md` — Class A +- `pharn/pharn-review/null-deref/null-deref.md` — Class A +- `pharn/pharn-review/off-by-one/off-by-one.md` — Class A +- `pharn/pharn-review/path-traversal/path-traversal.md` — Class A +- `pharn/pharn-review/placeholder-as-done/placeholder-as-done.md` — Class A +- `pharn/pharn-review/race-condition/race-condition.md` — Class A +- `pharn/pharn-review/resource-leak/resource-leak.md` — Class A +- `pharn/pharn-review/secrets-in-code/secrets-in-code.md` — Class A +- `pharn/pharn-review/ssrf/ssrf.md` — Class A +- `pharn/pharn-review/swallowed-exception/swallowed-exception.md` — Class A +- `pharn/pharn-review/unsafe-deserialization/unsafe-deserialization.md` — Class A +- `.claude/commands/pharn-review.md` — Class B +- `pharn/pharn-pipeline/grillers/a11y/a11y.md` — Class C +- `pharn/pharn-pipeline/grillers/comprehension/comprehension.md` — Class C +- `pharn/pharn-pipeline/grillers/coupling/coupling.md` — Class C +- `pharn/pharn-pipeline/grillers/documentation/documentation.md` — Class C +- `pharn/pharn-pipeline/grillers/error-handling/error-handling.md` — Class C +- `pharn/pharn-pipeline/grillers/migrations/migrations.md` — Class C +- `pharn/pharn-pipeline/grillers/performance/performance.md` — Class C +- `pharn/pharn-contracts/eval-format.md` — Class D +- `.claude/commands/pharn-plan.md` — Class E +- `.claude/commands/pharn-loop.md` — Class F +- `.claude/commands/pharn-ship.md` — Class G +- `SKILLS_VERSION` — 2.7.14 → 2.7.15 (patch) +- `README.md` — shields badge, pinned to SKILLS_VERSION by `check:badge` +- `CHANGELOG.md` — the bump + this enumeration +- `.dev/features/forward-looking-claims-sweep/PLAN.md` — this file + +## Guarantee audit (P0) + +- **"Every expired claim on the product surface is now corrected"** → **ADVISORY.** The enumeration is + a whitespace-normalized scan plus human-read classification. No checker reads shipped prose for its + tense; that is precisely the gap L33 names. A claim spelled in a way no pattern here anticipated + would survive — the same failure mode this increment documents, one level up. +- **"The enumeration is complete for the classes it names"** → **ADVISORY, with one arithmetic + cross-check.** Class A closes against `count-lenses.mjs`'s registered count (19 + 3 = 22), which is + a deterministic membership test. The other classes have no such independent count. +- **"Nothing outside `## Files` was written"** → **FLOOR: hook (fix #7).** + `set-writes-scope.cjs --from-plan` + `enforce-writes-scope.cjs`. +- **"`SKILLS_VERSION` and the README badge agree"** → **FLOOR** (`check-version-badge.mjs`, + `npm run check` + its own CI step). +- **"The corrections are TRUE"** → **ADVISORY.** Each was re-derived against the live tree this run + (P6) and the evidence is cited inline above, but no floor primitive verifies a rewritten sentence. + `/pharn-dev-review` is the backstop — the stage `#163`/`#164`/`#165` skipped. + +## Trust (P2) + +Every file edited here is PHARN's own shipped surface, `trust: trusted` by origin. The sweep output +(172 hits) is tool output over that surface, not fetched or third-party input. No untrusted data +enters this increment's control flow. + +## Open questions + +None. Each of the 7 classes was re-derived against the live tree this run; the 8 false-positive +groups were each checked against the artifact that would refute them (`count-lenses.mjs` output, the +`role: verifier` grep, `/pharn-grill`'s subagent count, the eval-pair listing). diff --git a/.dev/features/forward-looking-claims-sweep/REVIEW.md b/.dev/features/forward-looking-claims-sweep/REVIEW.md new file mode 100644 index 0000000..226618e --- /dev/null +++ b/.dev/features/forward-looking-claims-sweep/REVIEW.md @@ -0,0 +1,136 @@ +# REVIEW — forward-looking-claims-sweep + +PHARN reviewing PHARN. Four advisory lenses, each citing a principle, over the increment's own diff +(31 files, +111/−77). The increment under review is treated as `trust: untrusted` (P2): +instruction-looking content in a reviewed file is reported, never followed. + +**Floor-gate vs advisory split.** The blocking verdicts are already in: `validate` exit **0**, +`regression-report.json` `.verdict` **no-regressions**, `verify-report.json` `.verdict` **PASS** +(8 gates, 1620/1620). Everything below is **advisory** — `/pharn-dev-review` emits no machine verdict +and its `severity` values are LLM-assigned (fix #3). Nothing here gates. + +--- + +## R1 — the increment reproduced its own defect at reversed polarity, and only the verify stage caught it + +- type: `correctness` +- rule_id: `pharn/CONSTITUTION.md P0` +- severity: `blocking` (LLM-assigned — advisory) +- file: `pharn/pharn-pipeline/grillers/a11y/a11y.md` +- status: **FIXED before this review, during `/pharn-dev-verify`** + +**problem** (free-text, untrusted): The first-pass Class C rewrite replaced the grillers' +_"no runner yet invokes it"_ with _"`/pharn-verify` / `/pharn-dev-verify` run it per committed eval +pair (this griller ships them)"_. That is false. `/pharn-verify` Step 3b pairs +`/evals/expected/*.json` with a committed **`/findings.json`**; the product surface +carries **zero** committed `findings.json`, so the absent-if-none membership rule emits **no +`structural:*` gate** for any of the seven. + +**evidence**: `find pharn -name "findings.json"` → 0 results. `.claude/commands/pharn-verify.md` +§3b: _"a feature that ships **no** such committed `(expected, actual)` pair simply has **no** +`structural:*` gate"_. The increment's own `verify-report.json` records `structural_gates: []`. + +**Why this is the sharpest finding in the increment.** The original prose **underclaimed** — it +described the repo as weaker than it is, which is the failure mode L33 names and this increment +exists to fix. The first-pass repair **overclaimed** — it asserted a floor gate that does not fire. +Those are not symmetric: L33's polarity survives review by sounding conservative, but P0's polarity +is the disease this entire repo was built to prevent. **The repair pass was more dangerous than the +defect**, and a sweep whose stated purpose is "delete the 'not yet'" is structurally biased toward +producing exactly this. The seven files and the CHANGELOG entry now name the runner that landed +(`/pharn-dev-eval`, 3c) **and** state the operative bound (no gate fires over griller output today; +nothing fires at grill time at all). + +**Recommendation (for the human):** this is promotion-grade. The transferable claim — _a pass that +removes stale hedges is biased toward manufacturing overclaims, so each rewrite needs its own +guarantee audit, not just a staleness check_ — is not in canon. L33 covers the expiry; nothing +covers the repair's polarity risk. + +--- + +## R2 — the completeness claim is verified by patterns the same author wrote + +- type: `unverifiable-claim` +- rule_id: `pharn/CONSTITUTION.md P0` +- severity: `advisory` +- file: `CHANGELOG.md` + +**problem** (free-text, untrusted): The CHANGELOG states _"a post-build re-scan reports **0** +surviving stale sites."_ True as stated, but the scan's nine patterns were authored by the same pass +that wrote the replacements — so it verifies "the phrasings I knew about are gone", never "the class +is discharged". The first scanner run demonstrated the hazard directly: it reported `LENS-RUNNER: 19` +**after** the fix, because the pattern `/isolated\s+lens\s+runner/i` matched the _replacement_ text. +A less careful reading of that output would have concluded either "nothing was fixed" or, with the +pattern tightened wrongly, "everything is clean." + +**Bound, stated rather than resolved:** the PLAN's guarantee audit already labels completeness +ADVISORY, and Class A carries a genuine independent cross-check (19 + 3 = 22 against +`count-lenses.mjs`). Classes B–F have no such counter. **No claim in the shipped bytes asserts +completeness** — the assertion lives only in the CHANGELOG and PLAN, which are repo-meta. That +containment is what keeps this advisory rather than blocking. + +--- + +## R3 — L20's trigger has fired twice and the remedy is still discipline + +- type: `unresolved-tension` +- rule_id: `.dev/memory-bank/lessons-learned.md L20` +- severity: `advisory` +- file: `.dev/features/forward-looking-claims-sweep/PLAN.md` + +**problem** (free-text, untrusted): L20 holds that a defect whose only remedy is "remember to update +it" has earned a floor check. This class has now recurred **inside the increment that named it** +(`#165` → L33) and been re-swept here. The PLAN declines the checker on P7 grounds and records +`forward-looking-claims-manifest`. The reasoning is sound, but it is **self-issued and repeatable** — +the identical argument is available next time, and nothing structural forces the follow-up to land. +R1 sharpens this: a checker over a hand-maintained manifest would have caught neither the original +staleness **nor** the overclaim, since both live in prose no manifest indexes. So the deferral may be +more defensible than the PLAN argues, and the eventual checker weaker than L20 implies. + +**Surfaced for the human at GATE 2.** It was raised at GATE 1 with an explicit re-plan offer and the +plan was approved as written; recorded here so a third recurrence has this note to point at. + +--- + +## R4 — what the sweep deliberately left, and why that half is the fragile one + +- type: `risk` +- rule_id: `pharn/CONSTITUTION.md P6` +- severity: `advisory` +- file: `pharn/pharn-pipeline/grillers/` + +**problem** (free-text, untrusted): **14** sentences reading _"isolated per-**griller** runner is +deferred"_ were left untouched because they are true — `/pharn-grill` spawns zero subagents. Their +**lens** twins, nearly identical in wording, were all rewritten because `/pharn-review` does spawn +one subagent per lens. Two near-identical sentences now have opposite truth values, distinguishable +only by one word. A future sweep pattern-matching on shape rather than re-deriving against the tree +will "fix" the 14 correct ones — the same over-edit R1 nearly committed, at 14× the blast radius. + +**Mitigation shipped, and its limit.** Six rewritten files now say the per-griller runner remains +deferred **in the same sentence** as the landed lens runner, so the distinction is visible where a +reader meets it. That is prose, not a check — and the constraint that produced the correct outcome +here (G3's per-file read, and the post-condition re-scan) lived in this increment's grill log, not +in anything that persists. + +--- + +## Verified clean + +- **No frontmatter touched.** `git diff` over `pharn/**/*.md` shows zero `+`/`−` on `role`, `kind`, + `version`, `applies`, `coupling`, `enforces`, `model_tier`, `purpose`, `trust`, `writes`, `reads`. + G1's build constraint held, so `docs:check` GREEN is **causal**, not luck — the generated catalog + renders frontmatter only. +- **Every honest bound survived.** All **19** lenses retain "advisory orchestration"; all 19 retain + the "not 'the model always ran it'" guarantee line (13 on one line, 5 line-wrapped, `race-condition` + in its own variant). All **7** grillers state the grill-time bound. +- **`SKILLS_VERSION` ↔ badge** pinned by `check:badge` (exit 0); patch bump is correct per + `CLAUDE.md` (corrections to already-shipped bytes). +- **Trusted docs untouched.** Swept and clean; no hook-denied write attempted. +- **P2:** every file edited is PHARN's own `trust: trusted` surface; no untrusted input entered + control flow. + +## What this review did NOT do + +It did not independently re-derive all 31 rewritten sentences against the live tree — it re-derived +the ones the lenses surfaced (R1's seven, R4's fourteen) and spot-checked bound preservation across +the rest. **"`/pharn-dev-review` ran" never means "the prose is true"** (P0). The human owns that at +GATE 2. diff --git a/.dev/features/forward-looking-claims-sweep/SHIP.md b/.dev/features/forward-looking-claims-sweep/SHIP.md new file mode 100644 index 0000000..f92d02e --- /dev/null +++ b/.dev/features/forward-looking-claims-sweep/SHIP.md @@ -0,0 +1,54 @@ +# SHIP — forward-looking-claims-sweep + +Advisory roll-up of the `/pharn-dev-ship` gated chain. **This is not a seal and not an approval.** + +## Stages run, in order + +| # | Stage | Structural verdict read | Value | +| --- | -------------------- | ------------------------------------------- | ------------------------------ | +| 1 | `/pharn-dev-plan` | — (ends at **GATE 1**) | human approved | +| 2 | `/pharn-dev-grill` | none — advisory by design, gates nothing | PROCEED (4 findings) | +| 3 | `/pharn-dev-build` | `node pharn/floor/validate.mjs .` exit code | **0** (GREEN, 36 capabilities) | +| 4 | `/pharn-dev-regress` | `regression-report.json` `.verdict` | **`no-regressions`** | +| 5 | `/pharn-dev-verify` | `verify-report.json` `.verdict` | **`PASS`** | +| 6 | `/pharn-dev-review` | none — no machine verdict (fix #3) | 4 findings, 1 fixed pre-review | + +**Where the run ended:** **GATE 2** — the post-review human decision. No RED-verdict STOP occurred. + +## Final gate state + +`npm run check` exit **0** (8 gates: `format:check`, `lint`, `lint:md`, `docs:check`, +`check:markers`, `check:badge`, `check:contributing`, `test`) · `npm test` **1620/1620** · +`node pharn/floor/validate.mjs .` **GREEN**. + +## What changed + +31 product-surface files across 7 correction classes, plus `SKILLS_VERSION` 2.7.14 → **2.7.15** +(patch), the README shields badge, and a `CHANGELOG.md` entry. Net **+111 / −77**. The enumeration +itself is the deliverable (L29) and lives in `PLAN.md`; the false-positive audit — what was +deliberately **left** — is in the same section. + +## Pointers (cited, not restated — P4) + +- `.dev/features/forward-looking-claims-sweep/PLAN.md` — the enumeration, the false-positive audit, + and the guarantee audit +- `.dev/features/forward-looking-claims-sweep/GRILL.md` — 4 advisory findings (G1/G3 became build + constraints; G2 independently confirmed the largest class) +- `.dev/features/forward-looking-claims-sweep/REVIEW.md` — 4 advisory findings; **R1 is the one to + read** +- `.dev/features/forward-looking-claims-sweep/regression-report.json`, + `verify-report.json` — the machine verdicts quoted above + +## The one thing a reader should not miss + +`/pharn-dev-verify` caught the increment reproducing **its own defect at reversed polarity**: the +first-pass griller rewrite replaced an underclaiming "no runner yet invokes it" with an +**overclaiming** "`/pharn-verify` runs it per committed eval pair" — a floor gate that does not fire, +because zero `findings.json` are committed and the gate is absent-if-none. Fixed in all seven files +and the CHANGELOG before review. Detail: `REVIEW.md` R1. + +## Standing decision + +**The decision is the human's.** 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, committed, pushed, or sealed. diff --git a/.dev/features/forward-looking-claims-sweep/regression-report.json b/.dev/features/forward-looking-claims-sweep/regression-report.json new file mode 100644 index 0000000..17f7469 --- /dev/null +++ b/.dev/features/forward-looking-claims-sweep/regression-report.json @@ -0,0 +1,20 @@ +{ + "feature": "forward-looking-claims-sweep", + "verdict": "no-regressions", + "baseline_commit": "f7c3caa", + "head": "working-tree", + "regressions": [], + "gates": [ + { "gate": "format:check", "baseline_exit": 0, "head_exit": 0, "flipped": false }, + { "gate": "lint", "baseline_exit": 0, "head_exit": 0, "flipped": false }, + { "gate": "lint:md", "baseline_exit": 0, "head_exit": 0, "flipped": false }, + { "gate": "docs:check", "baseline_exit": 0, "head_exit": 0, "flipped": false }, + { "gate": "check:markers", "baseline_exit": 0, "head_exit": 0, "flipped": false }, + { "gate": "check:badge", "baseline_exit": 0, "head_exit": 0, "flipped": false }, + { "gate": "check:contributing", "baseline_exit": 0, "head_exit": 0, "flipped": false }, + { "gate": "test", "baseline_exit": 0, "head_exit": 0, "flipped": false } + ], + "baseline_method": "aggregate", + "baseline_note": "The baseline was captured as one green `npm run check` at f7c3caa before any edit. `scripts.check` is &&-chained, so a green aggregate ENTAILS all 8 gate exits were 0 — a derivation, not an assumption. HEAD exits were measured per-gate individually.", + "bound": "This compares the exit codes of the project's OWN deterministic gates at baseline vs HEAD. It catches exactly what those gates catch, nothing more. 'no-regressions' NEVER means 'nothing broke' (P0)." +} diff --git a/.dev/features/forward-looking-claims-sweep/verify-report.json b/.dev/features/forward-looking-claims-sweep/verify-report.json new file mode 100644 index 0000000..46251a2 --- /dev/null +++ b/.dev/features/forward-looking-claims-sweep/verify-report.json @@ -0,0 +1,28 @@ +{ + "feature": "forward-looking-claims-sweep", + "verdict": "PASS", + "failing_gates": [], + "gates": { + "test": 0, + "validate": 0, + "lint": 0, + "format:check": 0, + "lint:md": 0, + "docs:check": 0, + "check:markers": 0, + "check:badge": 0, + "check:contributing": 0 + }, + "tests": { "total": 1620, "pass": 1620, "fail": 0 }, + "structural_gates": [], + "structural_gates_note": "ABSENT-IF-NONE, and this is the increment's own subject matter. /pharn-verify Step 3b pairs /evals/expected/*.json with a committed /findings.json. There are ZERO committed findings.json on the product surface, so no (expected, actual) pair exists and no structural:* gate is emitted. This is the membership rule working as specified, not a skipped gate.", + "verifiers": [], + "verifiers_note": "Zero role: verifier capabilities are authored (P7). The advisory layer is empty; floor gates own the verdict (fix #3).", + "defect_caught_at_this_stage": { + "what": "The first-pass Class C rewrite claimed /pharn-verify and /pharn-dev-verify 'run it per committed eval pair (this griller ships them)'.", + "why_wrong": "The grillers ship evals/expected/*.json + evals/cases/*.md but NO findings.json. Step 3b requires the (expected, findings.json) pair, so no structural gate fires for any of the seven.", + "polarity": "The original prose UNDERCLAIMED (said no runner exists); the first-pass fix OVERCLAIMED (said a gate fires that does not). The second is the P0 disease proper.", + "fixed_in": "all 7 griller files + the CHANGELOG entry, re-derived to name the landed 3c runner AND state the operative bound (no gate fires over griller output today; nothing fires at grill time)." + }, + "bound": "'PASS' means EXACTLY 'the named gates exited 0'. It is NOT a judgment that the 31 rewritten sentences are true — no floor primitive reads shipped prose for its tense, which is the gap this increment documents and does not close (P0)." +} diff --git a/.dev/memory-bank/lessons-learned.md b/.dev/memory-bank/lessons-learned.md index f21ac73..c3b6638 100644 --- a/.dev/memory-bank/lessons-learned.md +++ b/.dev/memory-bank/lessons-learned.md @@ -1076,3 +1076,59 @@ reporting a repaired drift, and only an independent check of the canonical name `.dev/features/contributing-gate-chain/PLAN.md` "Trust audit (P2)", with the 301-vs-200 divergence and the identical `created_at` reproduced live before the fix was scoped - promoted: 2026-08-23 via gated `/pharn-dev-memory-promote` (human-approved). + +## L33 — A "not yet built" claim expires the moment the work lands — nothing reads shipped prose, and the repair pass misses the variant spellings + +type: process · concepts: [doc-drift, shipped-surface, false-green, enumeration] + +**Lesson.** Forward-looking prose — "not yet built", "the NEXT increment", "deferred", "no runner yet +invokes it" — is TRUE when written and becomes FALSE at the exact moment the work lands, in a file +nobody is editing. Nothing detects the transition: `pharn/floor/validate.mjs` walks capability +frontmatter, not claims; the four trusted docs are `.prettierignore`d and markdownlint-excluded; and no +checker anywhere reads a shipped sentence for its tense. Verified live at HEAD `f7c3caa` with +`npm run check` GREEN — 8 gates, 1620/1620 tests — over every site below. Two instances, the second +sharper. (1) `pharn/pharn-contracts/eval-format.md:52` still reads "(the checker that runs these is the +NEXT increment)" while `pharn/floor/check-structural.mjs` ships, is tested, and is invoked by five +commands. `#165` (`71e71ee`, `SKILLS_VERSION` 2.7.14) was the increment whose **entire purpose** was +re-deriving this claim class, and its CHANGELOG entry **names `eval-format.md` as a site it corrected — +for this exact sentence**. It fixed one instance and left a second in the file it named. (2) Seven +product-surface files claim "no runner yet invokes it over … output" — the `a11y`, `comprehension`, +`coupling`, `documentation`, `error-handling`, `migrations`, `performance` grillers — while +`pharn/pharn-contracts/finding-shape.md` now says the opposite: `/pharn-verify` and `/pharn-dev-verify` +run the checker per committed eval pair, and `/pharn-dev-eval` (increment 3c) runs it over each +live-emitted `runs//findings.json`. **Five of the seven cite `finding-shape.md`'s 3c runner by +name**, so they cite a deferral their own source no longer records. All seven ship committed eval +pairs. + +**The enumeration failed twice before it succeeded, and that is the transferable part.** The proposing +prompt named six grillers. A whitespace-normalized scan for `no runner yet invokes it` also found six. +The seventh — `coupling` — spells it `no **live** runner yet invokes it`, and only a scan anchored on +the shortest invariant substring (`runner yet invokes`) found all seven. A claim class does not ship in +one spelling. Remedy: (a) derive the enumeration from the shortest substring that is invariant across +paraphrase, never from the sentence you happened to read; (b) treat every prior enumeration — a +CHANGELOG's list, a prompt's list, your own first grep — as a **lower bound to beat**, never a set to +confirm. + +**Why it matters.** This is the P0 disease with its polarity reversed. Everywhere else the danger is +prose that OVERCLAIMS a guarantee; here the prose UNDERCLAIMS — it describes the repo as weaker than it +is — and _that is why nobody looks_: an honest-sounding "not yet built" reads as conservative, so it +survives review by seeming careful. The cost is real: five grillers point a reader at +`finding-shape.md` for a bound that document no longer states, so a contributor re-deriving the +guarantee from the cite gets a stale answer down a trusted-looking chain. [[L20]] says a +discipline-only remedy WILL recur and the second occurrence earns a floor check — this class recurred +**inside** the increment that named it, in the file it named, which is that trigger fired at the +shortest possible range. [[L29]] says a remedy quantified over a set owes the ENUMERATION as its +deliverable; this says which claims silently **join** that set, and demonstrated the failure in its own +evidence-gathering, twice. Distinct from [[L25]], which is about a rationale that did not TRAVEL to +sibling files: these sentences travelled fine, they simply **expired** — a comment can be complete, +local, and false. + +**Provenance.** + +- feature: `docs-drift-resync` +- commit: `71e71ee03c7e2a0ad1bbfee9daa4c8336addf615` +- source: `.dev/features/docs-drift-resync/PLAN.md` + the CHANGELOG `[Unreleased]` 2.7.13 → 2.7.14 + entry naming `eval-format.md` as a corrected site, re-derived live at HEAD `f7c3caa` against + `pharn/pharn-contracts/eval-format.md:52`, `pharn/pharn-contracts/finding-shape.md`, and the seven + grillers +- promoted: 2026-08-23 via gated `/pharn-dev-memory-promote` (human-approved). diff --git a/CHANGELOG.md b/CHANGELOG.md index b4ecbf0..949e752 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -46,6 +46,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), ### Fixed +- **The rest of the expired "not yet built" class — the sweep `#165` owed and did not deliver** (`SKILLS_VERSION` 2.7.14 → **2.7.15**, patch). `#165` (below) was the increment whose entire purpose was re-deriving this claim class, and its entry **names `eval-format.md` as a corrected site**; a second instance survived in that same file (`structural:` still read "the checker that runs these is the **NEXT increment**"). That miss produced lesson **L33**, and this increment is L33's remedy applied to the whole product surface — `pharn/**` plus the `pharn-*` (non-`pharn-dev-*`) commands — rather than to the three reported seeds. **The enumeration is the deliverable (L29), and it is 31 files across seven classes:** + + **(A) The isolated LENS runner — 19 lens files + [`pharn-review.md`](./.claude/commands/pharn-review.md) = 20 sites, the largest class and one no prior report named.** Every scanner-bearing lens read _"until the live isolated lens runner lands (deferred P7) … the review stage **applies this lens inline**"_, while `/pharn-review` **Step 4** spawns **one subagent per lens**, each writing its own `findings.json`. `pharn-review.md` carried the same claim **28 lines above the step that refutes it**. The enumeration closes arithmetically against `pharn/floor/count-lenses.mjs`: 22 registered, 19 carrying the claim, and the 3 that do not (`hallucinated-api`, `input-validation`, `trust-fence`) are exactly the scanner-less lenses with no Layer-1 block to carry it. **(B)** The seven grillers — `a11y`, `comprehension`, **`coupling`**, `documentation`, `error-handling`, `migrations`, `performance` — citing [`finding-shape.md`](./pharn/pharn-contracts/finding-shape.md)'s 3c runner as deferred while that document now records the opposite (3c landed as `/pharn-dev-eval`). **The correction here is narrower than the class suggests, and the verify stage is what narrowed it:** a first pass rewrote these to say `/pharn-verify` "runs it per committed eval pair", which **overclaims** — `/pharn-verify` Step 3b pairs `expected/*.json` with a committed **`findings.json`**, and there are **zero** committed `findings.json` on the product surface, so by the absent-if-none membership rule **no `structural:*` gate fires over any griller's output today**. The shipped wording therefore names the runner that landed _and_ states the operative bound: no gate fires over this griller's output, and nothing fires at grill time at all. Recorded because the first pass reproduced this increment's own defect at **reversed polarity** — underclaiming prose replaced by overclaiming prose, the P0 disease proper. **(C)** [`eval-format.md`](./pharn/pharn-contracts/eval-format.md) — the instance `#165` missed. **(D)** [`pharn-plan.md`](./.claude/commands/pharn-plan.md) calling the spec↔plan re-verifier _"not built yet"_ while `pharn/floor/check-plan-spec-agree.mjs` ships and seven commands invoke it — `pharn-grill.md` answers that very sentence with _"you are that stage"_, so the downstream file knew and the upstream one was never updated. **(E)** [`pharn-loop.md`](./.claude/commands/pharn-loop.md) resting a correct deferral on a false reason (_"no project config consumer exists yet"_ — `/pharn-build` reads the `seam` block and `/pharn-ship` reads `ship.requireAttestation`); the deferral stays, its reasoning is corrected to P7. **(F)** [`pharn-ship.md`](./.claude/commands/pharn-ship.md)'s `--loop` section, whose literal claim is **true** — no `--loop` flag exists — but which left the impression the capability is unavailable when `/pharn-loop` **is** built; fixed as a pointer, with the `check-loop.mjs`-vs-`check-ship.mjs` distinction preserved. + + Each site was **re-derived, not deleted** (L25): the bound that survives is kept — for the lenses, that nothing on the floor forces every lens to run; for the grillers, that **nothing fires at grill time**, the enforcement moment being the verify/eval stage — and the invoker that now exists is named. **What was deliberately LEFT, because the audit half of an enumeration is the half that gets skipped:** the 14 near-identical _"isolated per-**griller** runner is deferred"_ sentences are **correct** and stay — `/pharn-grill` spawns zero subagents and genuinely applies a griller inline, so the lens twin landed and the griller twin did not, and the two read almost identically; likewise `/pharn-verify`'s _"ZERO verifiers authored"_ (18 `role: verifier` hits are all prose mentions, no frontmatter declarations), the `scan-code-*` "multi-file sweep / taint analysis not built speculatively" bounds, `validate.mjs`'s `scan-plan-*` ghosts, and every _"no cache yet" / "not yet pinned"_ **runtime-state** sentence. `README.md`'s surviving "Not yet built." block is true and `check:markers` already guards it. **Method, recorded because it is the transferable part (L33):** line-anchored `grep` is structurally wrong here — these claims **wrap across source lines**, so the reported seed list found 6 grillers, `grep -rn "runner yet invokes"` found 4, and only a whitespace-normalized scan found **7** (`coupling` spells it `no **live** runner yet invokes it`). 172 raw hits over 72 files were classified individually; a post-build re-scan reports **0** surviving stale sites. **No floor check was added, and that is a recorded decision, not an oversight** (L20's trigger has fired): a tense-checker needs a structured manifest — the `check-specified-markers.mjs` pattern, never a prose scan (L6) — and deriving that manifest from the prose this increment rewrites is a separate axis of change. Follow-ups: `forward-looking-claims-manifest` (the checker) and `apparatus-forward-looking-sweep` (the same sweep over `.dev/**`, out of scope here). The four trusted docs were swept and are **clean** — the one hit, `THREAT-MODEL.md`'s "(deferred) AI/LLM-security lens", is a true statement. Full enumeration, per-class evidence, and the false-positive audit: `.dev/features/forward-looking-claims-sweep/`. + - **Shipped prose that still described landed floor machinery as unbuilt, re-derived against the live tree** (`SKILLS_VERSION` 2.7.13 → **2.7.14**, patch). A full-tree claim audit (every falsifiable doc sentence checked against the live repo) found the shipped surface carrying "not yet built" claims that had been true once and false since: [`pharn/pharn-contracts/eval-format.md`](./pharn/pharn-contracts/eval-format.md) still called the `structural[]` checker "the **next increment**" (it landed as `pharn/floor/check-structural.mjs`); [`pharn/pharn-contracts/finding-shape.md`](./pharn/pharn-contracts/finding-shape.md) still said "no runner yet invokes it … increment **3c, not yet built**" (3c landed as `/pharn-dev-eval` via `.dev/floor/check-variance.mjs`, and `/pharn-verify` / `/pharn-dev-verify` invoke the checker per committed eval pair); [`pharn/floor/README.md`](./pharn/floor/README.md) said "nothing in the build loop invokes it automatically yet" and understated the hook matcher (the live wiring is `Write|Edit|MultiEdit|NotebookEdit`); the `input-validation` and `hallucinated-api` lenses repeated the "3c not yet wired" bound. Each site was **re-derived, not deleted** (L25): the bound that survives — nothing fires at write time; the enforcement moment is the verify/eval stage — is kept, and the invokers that now exist are named. The same pass corrected pre-relocation `floor/…` path spellings and a pre-move `features/trust-fence` example inside the shipped contracts. Repo-meta corrections rode along without bumping: `CLAUDE.md`'s seven-gate aggregate list vs the live eight (`check:contributing` was missing — the exact drift class `check-contributing-gates` guards, one file over), its stale "21 tagged" count (now count-free per P6), its four-constants enumeration (the pinned set is `{CANON_PATH, OUT_PATH, GEN, REGEN}`; the absent-canon semantics are pinned separately as divergent functions), and its present-tense `pharn` CLI (specified, not built); `README.md`'s "authorization checked" inside a "can guarantee" sentence (the security griller records that floor candidate as **REJECTED**, so the sentence now names the writes-scope guarantee instead); `SECURITY.md` pointing at the deliberately inert `package.json` version; `CONTRIBUTING.md`'s missing test-file carve-out on the `pharn/floor/` bump rule; both GitHub templates' pre-relocation `floor/validate.mjs` paths, the PR template's four-of-eight gate list, and the bug template's pre-rename `/plan`-style command names; `.dev/features/README.md`'s modules-at-repo-root claim. Audit + scope record: `.dev/features/docs-drift-resync/PLAN.md`. - **Six confirmed product-floor defects, each reproduced live before it was scoped and each now pinned by a test that fails without its fix** (`SKILLS_VERSION` 2.7.12 → 2.7.13). None was hypothetical; all six came from an external review and were re-derived against the live tree, which mattered — one of the six reports carried a reproduce command that did not reproduce the defect it described. - **A prototype-walking gate comparison let a real gate-set mismatch pass silently** ([`pharn/floor/check-regress.mjs`](./pharn/floor/check-regress.mjs)). The mismatch check used `k in obj`, which walks the prototype chain, so a gate id colliding with an `Object.prototype` member (`toString`, `valueOf`) read as PRESENT in a map that did not have it. The extra failing gate was treated as shared and the verdict came back `no-regressions` at exit 0, where a gate-set mismatch is contractually `inconclusive`/exit 2 — a silent pass inside the checker whose entire promise is that there is never one. Now `Object.hasOwn`, which is the own-property test `lessons-learned` **L15** already prescribes; this is that lesson recurring. diff --git a/README.md b/README.md index 4739951..9a642df 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ labeled advisory, in the same sentence. npx @pharn-dev/pharn@latest init ``` -[![pharn](https://img.shields.io/badge/pharn-2.7.14-blue)](./CHANGELOG.md) +[![pharn](https://img.shields.io/badge/pharn-2.7.15-blue)](./CHANGELOG.md) [![License: Apache 2.0](https://img.shields.io/badge/license-Apache%202.0-green)](./LICENSE) [![CI](https://github.com/pharn-dev/pharn-oss/actions/workflows/ci.yml/badge.svg)](https://github.com/pharn-dev/pharn-oss/actions/workflows/ci.yml) [![CodeQL](https://github.com/pharn-dev/pharn-oss/actions/workflows/codeql.yml/badge.svg)](https://github.com/pharn-dev/pharn-oss/actions/workflows/codeql.yml) diff --git a/SKILLS_VERSION b/SKILLS_VERSION index 9bbf492..f24054f 100644 --- a/SKILLS_VERSION +++ b/SKILLS_VERSION @@ -1 +1 @@ -2.7.14 +2.7.15 diff --git a/docs/lessons-index.md b/docs/lessons-index.md index 10a7cbd..d2f92dd 100644 --- a/docs/lessons-index.md +++ b/docs/lessons-index.md @@ -10,7 +10,7 @@ lessons to fetch; canon stays the source of truth and the floor's verification t applied, without fetching its full `## L` entry from canon, is the P0 disease. "The index was consulted" never means "the relevant lessons were read". -32 lessons · 32 tagged · 0 malformed · 0 untagged · ~20174 tokens total +33 lessons · 33 tagged · 0 malformed · 0 untagged · ~21257 tokens total Columns: `id | type | concepts | title | promoted | ~tokens`. Every canon entry carries a tag line, so BOTH absence markers are unexpected: `-` = no tag line, i.e. an entry that reached canon without @@ -52,4 +52,5 @@ L29 | process | lesson-recurrence,branch-coverage,shared-message,floor-escalati L30 | process | style-gates,command-prescription,lesson-recurrence,floor-escalation,prevention-vs-detection | A step that RUNS some of the gates it names and ASKS for the rest will fail on the ones it asks for | 2026-08-20 | ~880 L31 | process | lesson-recurrence,dev-product-boundary,branch-coverage,floor-escalation | A deliberate copy-pair creates an obligation set nothing ranges over — the second copy is where the obligation is dropped | 2026-08-20 | ~668 L32 | process | verification-fidelity,doc-drift,command-prescription,false-green | A verification method that consults a mutable ALIAS proves reachability, not identity | 2026-08-23 | ~781 +L33 | process | doc-drift,shipped-surface,false-green,enumeration | A "not yet built" claim expires the moment the work lands — nothing reads shipped prose, and the repair pass misses the variant spellings | 2026-08-23 | ~1083 ``` diff --git a/pharn/pharn-contracts/eval-format.md b/pharn/pharn-contracts/eval-format.md index 1b53ecc..b74762c 100644 --- a/pharn/pharn-contracts/eval-format.md +++ b/pharn/pharn-contracts/eval-format.md @@ -49,7 +49,7 @@ purpose: "" skill_kind: deterministic | llm | llm-judge # how the eval is CHECKED — see below (not `role`/`model_tier`) --- assertions: - structural: # enum-gated → FLOOR-reducible. No LLM. (the checker that runs these is the NEXT increment) + structural: # enum-gated → FLOOR-reducible. No LLM. (run by pharn/floor/check-structural.mjs) - kind: finding_count op: "==" # enum: == | >= | <= value: 1 diff --git a/pharn/pharn-pipeline/grillers/a11y/a11y.md b/pharn/pharn-pipeline/grillers/a11y/a11y.md index 3501240..f05bb66 100644 --- a/pharn/pharn-pipeline/grillers/a11y/a11y.md +++ b/pharn/pharn-pipeline/grillers/a11y/a11y.md @@ -187,10 +187,13 @@ when the **live griller runner** lands (deferred P7 — exactly as the testabili the **only runtime floor guarantee**. `count-grillers.mjs` is **reused unchanged**. - **Present/absent detection** → the present/absent **output** is `finding_count`-expressible and floor-**checked on the eval fixtures** by `pharn/floor/check-structural.mjs` (primitive #3). **Two clocks - (be honest):** `check-structural.mjs` **is** floor and is hermetically tested, but **no runner yet - invokes it over this griller's live output** — that wiring is deferred (P7, as for every griller and - `finding-shape.md`'s 3c runner). So at build/verify time the backstop is **the checker's own tests + the - committed fixtures**, not a wired runner; and at **runtime over a novel plan** the presence _reading_ is + (be honest):** `check-structural.mjs` **is** floor and is hermetically tested, and the 3c runner it once + waited on **has landed** — `/pharn-dev-eval` runs it over each live-emitted `runs//findings.json`, and + `/pharn-verify` / `/pharn-dev-verify` run it per committed `(expected, findings.json)` pair. **The bound + that survives, and it is the operative one here:** this griller commits **no** `findings.json`, so by the + absent-if-none membership rule **no `structural:*` gate fires over its output today**, and nothing fires at + **grill time** at all. So at build/verify time the backstop is **the checker's own tests + the committed + fixtures**, not a wired runner; and at **runtime over a novel plan** the presence _reading_ is the griller's **judgment (ADVISORY)**, backstopped by the eval. `finding_count` captures the **output**, not the finding's **correctness** (that rests on `field_equals` + `needle_absent_from_enum_gated` + the `semantic[]` judge). diff --git a/pharn/pharn-pipeline/grillers/comprehension/comprehension.md b/pharn/pharn-pipeline/grillers/comprehension/comprehension.md index ee05679..f40bd00 100644 --- a/pharn/pharn-pipeline/grillers/comprehension/comprehension.md +++ b/pharn/pharn-pipeline/grillers/comprehension/comprehension.md @@ -202,7 +202,10 @@ Every claim is reduced or labeled: fields + `needle_absent_from_enum_gated`) is **floor-CHECKED at eval time** by `check-structural.mjs` (primitive #3). This pins behavior on known inputs and proves the trust-fence holds — it is **NOT** a runtime guarantee that "comprehension" is deterministic. (Two clocks, honest: the checker is floor and - tested, but no runner yet invokes it over live output — deferred P7, as for every griller.) + tested, and the 3c runner has landed — `/pharn-dev-eval` runs it over live-emitted findings, and + `/pharn-verify` / `/pharn-dev-verify` per committed `(expected, findings.json)` pair — but this griller + commits no `findings.json`, so no `structural:*` gate fires over its output today, and nothing fires at + grill time at all.) - **No new floor primitive (P0/P7).** A "mentions rationale / has a `## Why` section" scan's **present** verdict is **launderable** → not injection-immune → **not floor** (named and rejected above). This griller reuses `pharn/floor/count-grillers.mjs` (membership) and `pharn/floor/check-structural.mjs` diff --git a/pharn/pharn-pipeline/grillers/coupling/coupling.md b/pharn/pharn-pipeline/grillers/coupling/coupling.md index ee9a08e..1105a70 100644 --- a/pharn/pharn-pipeline/grillers/coupling/coupling.md +++ b/pharn/pharn-pipeline/grillers/coupling/coupling.md @@ -169,8 +169,11 @@ half-specified runner is built here, and the `writes:` path is **not** an active human, never gates. - **Fixture behavior** → the finding **output** on the two committed fixtures (present/absent + enum-gated fields + `needle_absent_from_enum_gated`) is **floor-CHECKED at eval time** by `check-structural.mjs` - (primitive #3). Two clocks (be honest): the checker is floor and hermetically tested, but **no live - runner yet invokes it over this griller's output** (deferred P7). This pins behavior on known inputs and + (primitive #3). Two clocks (be honest): the checker is floor and hermetically tested, and the 3c runner has + landed — `/pharn-dev-eval` runs it over live-emitted findings, and `/pharn-verify` / `/pharn-dev-verify` per + committed `(expected, findings.json)` pair — but this griller commits **no** `findings.json`, so **no + `structural:*` gate fires over its output today**, and nothing fires at **grill time** at all. This pins + behavior on known inputs and proves the trust-fence holds — it is **NOT** a runtime guarantee that "entanglement" is deterministic. - **No new floor primitive.** The one injection-immune lexical signal (a declared cross-boundary path) is architecture's P3 signal, kept advisory there; a `scan-plan-coupling.mjs` here would duplicate diff --git a/pharn/pharn-pipeline/grillers/documentation/documentation.md b/pharn/pharn-pipeline/grillers/documentation/documentation.md index 7a56602..3629deb 100644 --- a/pharn/pharn-pipeline/grillers/documentation/documentation.md +++ b/pharn/pharn-pipeline/grillers/documentation/documentation.md @@ -176,10 +176,13 @@ when the **live griller runner** lands (deferred P7 — exactly as the testabili mention never registers. This is the **only runtime floor guarantee**. - **Present/absent detection** → the present/absent **output** is `finding_count`-expressible and floor-**checked on the eval fixtures** by `pharn/floor/check-structural.mjs` (primitive #3). **Two clocks - (be honest):** `check-structural.mjs` **is** floor and is hermetically tested, but **no runner yet - invokes it over this griller's live output** — that wiring is deferred (P7, as for every griller and - `finding-shape.md`'s 3c runner). So at build/verify time the backstop is **the checker's own tests + the - committed fixtures**, not a wired runner; and at **runtime over a novel plan** the presence _reading_ is + (be honest):** `check-structural.mjs` **is** floor and is hermetically tested, and the 3c runner it once + waited on **has landed** — `/pharn-dev-eval` runs it over each live-emitted `runs//findings.json`, and + `/pharn-verify` / `/pharn-dev-verify` run it per committed `(expected, findings.json)` pair. **The bound + that survives, and it is the operative one here:** this griller commits **no** `findings.json`, so by the + absent-if-none membership rule **no `structural:*` gate fires over its output today**, and nothing fires at + **grill time** at all. So at build/verify time the backstop is **the checker's own tests + the committed + fixtures**, not a wired runner; and at **runtime over a novel plan** the presence _reading_ is the griller's **judgment (ADVISORY)**, backstopped by the eval. `finding_count` captures the **output**, not the finding's **correctness** (that rests on `field_equals` + `needle_absent_from_enum_gated` + the `semantic[]` judge). diff --git a/pharn/pharn-pipeline/grillers/error-handling/error-handling.md b/pharn/pharn-pipeline/grillers/error-handling/error-handling.md index f9cd0a6..abe913f 100644 --- a/pharn/pharn-pipeline/grillers/error-handling/error-handling.md +++ b/pharn/pharn-pipeline/grillers/error-handling/error-handling.md @@ -154,10 +154,13 @@ grillers defer it). No half-specified runner is built here. mention never registers. This is the **only runtime floor guarantee**. - **Present/absent detection** → the present/absent **output** is `finding_count`-expressible and floor-**checked on the eval fixtures** by `pharn/floor/check-structural.mjs` (primitive #3). **Two clocks - (be honest):** `check-structural.mjs` **is** floor and is hermetically tested, but **no runner yet - invokes it over this griller's live output** — that wiring is deferred (P7, as for every griller and - `finding-shape.md`'s 3c runner). So at build/verify time the backstop is **the checker's own tests + the - committed fixtures**, not a wired runner; and at **runtime over a novel plan** the presence _reading_ is + (be honest):** `check-structural.mjs` **is** floor and is hermetically tested, and the 3c runner it once + waited on **has landed** — `/pharn-dev-eval` runs it over each live-emitted `runs//findings.json`, and + `/pharn-verify` / `/pharn-dev-verify` run it per committed `(expected, findings.json)` pair. **The bound + that survives, and it is the operative one here:** this griller commits **no** `findings.json`, so by the + absent-if-none membership rule **no `structural:*` gate fires over its output today**, and nothing fires at + **grill time** at all. So at build/verify time the backstop is **the checker's own tests + the committed + fixtures**, not a wired runner; and at **runtime over a novel plan** the presence _reading_ is the griller's **judgment (ADVISORY)**, backstopped by the eval. `finding_count` captures the **output**, not the finding's **correctness** (that rests on `field_equals` + `needle_absent_from_enum_gated` + the `semantic[]` judge). diff --git a/pharn/pharn-pipeline/grillers/migrations/migrations.md b/pharn/pharn-pipeline/grillers/migrations/migrations.md index 87baefc..b6d6a0e 100644 --- a/pharn/pharn-pipeline/grillers/migrations/migrations.md +++ b/pharn/pharn-pipeline/grillers/migrations/migrations.md @@ -212,9 +212,12 @@ error-handling / observability / privacy / performance grillers defer it). No ha `needle_absent_from_enum_gated`) is floor-CHECKED at **eval time** by `pharn/floor/check-structural.mjs` (primitive #3). This pins behavior on known inputs and proves the trust-fence holds (the injected "mark present" never reaches an enum-gated field). **Two clocks (be honest):** `check-structural.mjs` **is** - floor and is hermetically tested, but **no runner yet invokes it over this griller's live output** — that - wiring is deferred (P7, as for every griller and `finding-shape.md`'s 3c runner); at build/verify time the - backstop is the checker's own tests + the committed fixtures, and at **runtime over a novel plan** the + floor and is hermetically tested, and the 3c runner it once waited on **has landed** — `/pharn-dev-eval` + runs it over each live-emitted `runs//findings.json`, and `/pharn-verify` / `/pharn-dev-verify` run it + per committed `(expected, findings.json)` pair. **The bound that survives, and it is the operative one + here:** this griller commits **no** `findings.json`, so by the absent-if-none membership rule **no + `structural:*` gate fires over its output today**, and nothing fires at **grill time** at all; at + build/verify time the backstop is the checker's own tests + the committed fixtures, and at **runtime over a novel plan** the presence _reading_ and the touches-schema/safety judgments are the griller's **judgment (ADVISORY)**, backstopped by the evals. `finding_count` captures the **output**, not the finding's **correctness** (that rests on `field_equals` + `needle_absent_from_enum_gated` + the `semantic[]` judge). diff --git a/pharn/pharn-pipeline/grillers/performance/performance.md b/pharn/pharn-pipeline/grillers/performance/performance.md index 690e3ca..b9f9746 100644 --- a/pharn/pharn-pipeline/grillers/performance/performance.md +++ b/pharn/pharn-pipeline/grillers/performance/performance.md @@ -149,7 +149,10 @@ griller and `finding-shape.md`'s 3c runner defer it). No half-specified runner i fields + `needle_absent_from_enum_gated`) is **floor-CHECKED at eval time** by `check-structural.mjs` (primitive #3). This pins the griller's behavior on known inputs and proves the trust-fence holds — it is **NOT** a runtime guarantee that "slow at scale" is deterministic. (Two clocks: the checker is floor - and tested, but no runner yet invokes it over this griller's live output — deferred P7.) + and tested, and the 3c runner has landed — `/pharn-dev-eval` runs it over live-emitted findings, and + `/pharn-verify` / `/pharn-dev-verify` per committed `(expected, findings.json)` pair — but this griller + commits no `findings.json`, so no `structural:*` gate fires over its output today, and nothing fires at + grill time at all.) - **No new floor primitive (P0/P7).** A "mentions performance" scan's verdict is **launderable** → not injection-immune → **not floor** (named and rejected above, the error-handling precedent). This griller reuses `pharn/floor/count-grillers.mjs` (membership) and `pharn/floor/check-structural.mjs` (eval-time), diff --git a/pharn/pharn-review/copy-paste-drift/copy-paste-drift.md b/pharn/pharn-review/copy-paste-drift/copy-paste-drift.md index 15b7c72..bb9b56a 100644 --- a/pharn/pharn-review/copy-paste-drift/copy-paste-drift.md +++ b/pharn/pharn-review/copy-paste-drift/copy-paste-drift.md @@ -78,8 +78,8 @@ varying slot, e.g. `MAX_X`/`MAX_Y`/`MAX_X`) is **out of scope** and can mis-rank masked away (invisible); **multi-line block** repetitions are not grouped (single-line members, v0.1.0); the scan is **single-file**. **This is NOT semantic/intent analysis** — that is the advisory layer, never this floor. -> **Two clocks (be honest).** The scanner's **output** is FLOOR (a deterministic verdict). But until the live -> isolated lens runner lands (deferred P7, as for every lens), the review stage **applies this lens inline** — so the +> **Two clocks (be honest).** The scanner's **output** is FLOOR (a deterministic verdict). The isolated lens runner has LANDED — +> `/pharn-review` Step 4 spawns **one subagent per lens**, each writing its own `findings.json` — but the > lens's **act** of invoking the scanner is **advisory orchestration**, backstopped by the scanner's own tests and > this lens's eval. The guarantee is "the scanner IS deterministic", not "the model always ran it". diff --git a/pharn/pharn-review/duplicated-logic/duplicated-logic.md b/pharn/pharn-review/duplicated-logic/duplicated-logic.md index 2d7fc9c..e2f9b84 100644 --- a/pharn/pharn-review/duplicated-logic/duplicated-logic.md +++ b/pharn/pharn-review/duplicated-logic/duplicated-logic.md @@ -79,8 +79,8 @@ floor); the scan is **single-file** (cross-file duplication is a future incremen significant lines** is below threshold; and trivial structural-only lines are excluded. **This is NOT semantic-similarity analysis** — that is the advisory layer, never this floor. -> **Two clocks (be honest).** The scanner's **output** is FLOOR (a deterministic verdict). But until the live -> isolated lens runner lands (deferred P7, as for every lens), the review stage **applies this lens inline** — so the +> **Two clocks (be honest).** The scanner's **output** is FLOOR (a deterministic verdict). The isolated lens runner has LANDED — +> `/pharn-review` Step 4 spawns **one subagent per lens**, each writing its own `findings.json` — but the > lens's **act** of invoking the scanner is **advisory orchestration**, backstopped by the scanner's own tests and > this lens's eval. The guarantee is "the scanner IS deterministic", not "the model always ran it". diff --git a/pharn/pharn-review/injection/injection.md b/pharn/pharn-review/injection/injection.md index 2fcdc4d..ebca4fa 100644 --- a/pharn/pharn-review/injection/injection.md +++ b/pharn/pharn-review/injection/injection.md @@ -72,9 +72,9 @@ real guarantee; **"the code has no injection" is not** — a bare untrusted vari line-local regex set. **This is NOT taint analysis** — full data-flow taint tracing is the advisory layer, never this floor. -> **Two clocks (be honest).** The scanner's **output** is FLOOR (a deterministic regex verdict). But until the -> live isolated lens runner lands (deferred P7, as for every lens/griller), the review stage **applies this lens -> inline** — so the lens's **act** of invoking the scanner is **advisory orchestration**, backstopped by the +> **Two clocks (be honest).** The scanner's **output** is FLOOR (a deterministic regex verdict). The isolated lens runner has LANDED — +> `/pharn-review` Step 4 spawns **one subagent per lens**, each writing its own `findings.json` (the per-GRILLER +> runner is still deferred, P7) — but the lens's **act** of invoking the scanner is **advisory orchestration**, backstopped by the > scanner's own tests and this lens's eval. The guarantee is "the scanner IS deterministic", not "the model > always ran it". diff --git a/pharn/pharn-review/insecure-crypto/insecure-crypto.md b/pharn/pharn-review/insecure-crypto/insecure-crypto.md index d13851e..c0b2f98 100644 --- a/pharn/pharn-review/insecure-crypto/insecure-crypto.md +++ b/pharn/pharn-review/insecure-crypto/insecure-crypto.md @@ -72,9 +72,9 @@ code is **cryptographically correct / secure**. "Detected a weak-crypto primitiv **"the crypto is correct" is not** — novel/aliased algorithm references, split literals, or a weak primitive from an unlisted library evade a fixed regex set. -> **Two clocks (be honest).** The scanner's **output** is FLOOR (a deterministic regex verdict). But until the -> live isolated lens runner lands (deferred P7, as for every lens/griller), the review stage **applies this -> lens inline** — so the lens's **act** of invoking the scanner is **advisory orchestration**, backstopped by +> **Two clocks (be honest).** The scanner's **output** is FLOOR (a deterministic regex verdict). The isolated lens runner has LANDED — +> `/pharn-review` Step 4 spawns **one subagent per lens**, each writing its own `findings.json` (the +> per-GRILLER runner is still deferred, P7) — but the lens's **act** of invoking the scanner is **advisory orchestration**, backstopped by > the scanner's own tests and this lens's eval. The guarantee is "the scanner IS deterministic", not "the > model always ran it". diff --git a/pharn/pharn-review/magic-values/magic-values.md b/pharn/pharn-review/magic-values/magic-values.md index b8b50a8..b129dd8 100644 --- a/pharn/pharn-review/magic-values/magic-values.md +++ b/pharn/pharn-review/magic-values/magic-values.md @@ -84,8 +84,8 @@ cross-file all evade it. A `'…'`/`"…"` inside a **backtick template's text** (backticks are not masked). The scan is **single-file**. **This is NOT semantic/intent analysis** — that is the advisory layer, never this floor. -> **Two clocks (be honest).** The scanner's **output** is FLOOR (a deterministic verdict). But until the live -> isolated lens runner lands (deferred P7, as for every lens), the review stage **applies this lens inline** — so +> **Two clocks (be honest).** The scanner's **output** is FLOOR (a deterministic verdict). The isolated lens runner has LANDED — +> `/pharn-review` Step 4 spawns **one subagent per lens**, each writing its own `findings.json` — but > the lens's **act** of invoking the scanner is **advisory orchestration**, backstopped by the scanner's own tests > and this lens's eval. The guarantee is "the scanner IS deterministic", not "the model always ran it". diff --git a/pharn/pharn-review/missing-await/missing-await.md b/pharn/pharn-review/missing-await/missing-await.md index b81ccae..a25ed55 100644 --- a/pharn/pharn-review/missing-await/missing-await.md +++ b/pharn/pharn-review/missing-await/missing-await.md @@ -83,8 +83,8 @@ call inside a **backtick template's text** or a roster-triggering pattern in fix **single-file**, with no scope/shadowing analysis. **This is NOT semantic/intent analysis** — that is the advisory layer, never this floor. -> **Two clocks (be honest).** The scanner's **output** is FLOOR (a deterministic verdict). But until the live -> isolated lens runner lands (deferred P7, as for every lens), the review stage **applies this lens inline** — so +> **Two clocks (be honest).** The scanner's **output** is FLOOR (a deterministic verdict). The isolated lens runner has LANDED — +> `/pharn-review` Step 4 spawns **one subagent per lens**, each writing its own `findings.json` — but > the lens's **act** of invoking the scanner is **advisory orchestration**, backstopped by the scanner's own tests > and this lens's eval. The guarantee is "the scanner IS deterministic", not "the model always ran it". diff --git a/pharn/pharn-review/missing-error-handling/missing-error-handling.md b/pharn/pharn-review/missing-error-handling/missing-error-handling.md index f1234d6..cfe6f69 100644 --- a/pharn/pharn-review/missing-error-handling/missing-error-handling.md +++ b/pharn/pharn-review/missing-error-handling/missing-error-handling.md @@ -97,8 +97,8 @@ another call on the same line as an await, and — because backticks are **not** block is (correctly) flagged — it is genuinely outside the `try` body. **This is NOT control-flow analysis** — that is the advisory layer, never this floor. -> **Two clocks (be honest).** The scanner's **output** is FLOOR (a deterministic verdict). But until the live -> isolated lens runner lands (deferred P7, as for every lens), the review stage **applies this lens inline** — so +> **Two clocks (be honest).** The scanner's **output** is FLOOR (a deterministic verdict). The isolated lens runner has LANDED — +> `/pharn-review` Step 4 spawns **one subagent per lens**, each writing its own `findings.json` — but > the lens's **act** of invoking the scanner is **advisory orchestration**, backstopped by the scanner's own tests > and this lens's eval. The guarantee is "the scanner IS deterministic", not "the model always ran it". diff --git a/pharn/pharn-review/missing-timeout/missing-timeout.md b/pharn/pharn-review/missing-timeout/missing-timeout.md index 95d64bd..d5cdcc2 100644 --- a/pharn/pharn-review/missing-timeout/missing-timeout.md +++ b/pharn/pharn-review/missing-timeout/missing-timeout.md @@ -98,8 +98,8 @@ can match a non-SQL `.query(` (e.g. Apollo GraphQL); the indicator test is **len longer reads as clean — it is masked in the suppression copy; see the injection-immunity paragraph above.) **This is NOT config / ownership / control-flow analysis** — that is the advisory layer, never this floor. -> **Two clocks (be honest).** The scanner's **output** is FLOOR (a deterministic verdict). But until the live -> isolated lens runner lands (deferred P7, as for every lens), the review stage **applies this lens inline** — so the +> **Two clocks (be honest).** The scanner's **output** is FLOOR (a deterministic verdict). The isolated lens runner has LANDED — +> `/pharn-review` Step 4 spawns **one subagent per lens**, each writing its own `findings.json` — but the > lens's **act** of invoking the scanner is **advisory orchestration**, backstopped by the scanner's own tests and > this lens's eval. The guarantee is "the scanner IS deterministic", not "the model always ran it". diff --git a/pharn/pharn-review/n-plus-one/n-plus-one.md b/pharn/pharn-review/n-plus-one/n-plus-one.md index e24bd47..fc817ec 100644 --- a/pharn/pharn-review/n-plus-one/n-plus-one.md +++ b/pharn/pharn-review/n-plus-one/n-plus-one.md @@ -81,8 +81,8 @@ loop, cross-file fan-out, and raw-SQL template strings all evade it; a query-sha template's text** is a documented false-POSITIVE (backticks are not masked). The scan is **single-file**. **This is NOT semantic/intent analysis** — that is the advisory layer, never this floor. -> **Two clocks (be honest).** The scanner's **output** is FLOOR (a deterministic verdict). But until the live -> isolated lens runner lands (deferred P7, as for every lens), the review stage **applies this lens inline** — so +> **Two clocks (be honest).** The scanner's **output** is FLOOR (a deterministic verdict). The isolated lens runner has LANDED — +> `/pharn-review` Step 4 spawns **one subagent per lens**, each writing its own `findings.json` — but > the lens's **act** of invoking the scanner is **advisory orchestration**, backstopped by the scanner's own tests > and this lens's eval. The guarantee is "the scanner IS deterministic", not "the model always ran it". diff --git a/pharn/pharn-review/null-deref/null-deref.md b/pharn/pharn-review/null-deref/null-deref.md index ed94462..8e66fcd 100644 --- a/pharn/pharn-review/null-deref/null-deref.md +++ b/pharn/pharn-review/null-deref/null-deref.md @@ -88,8 +88,8 @@ branch than the deref, or a same-named binding in a later scope, can skew it; an template/regex literal in the scanned span can skew the paren-match. **This is NOT null-safety analysis** — that is the advisory layer, never this floor. -> **Two clocks (be honest).** The scanner's **output** is FLOOR (a deterministic verdict). But until the live -> isolated lens runner lands (deferred P7, as for every lens), the review stage **applies this lens inline** — so +> **Two clocks (be honest).** The scanner's **output** is FLOOR (a deterministic verdict). The isolated lens runner has LANDED — +> `/pharn-review` Step 4 spawns **one subagent per lens**, each writing its own `findings.json` — but > the lens's **act** of invoking the scanner is **advisory orchestration**, backstopped by the scanner's own tests > and this lens's eval. The guarantee is "the scanner IS deterministic", not "the model always ran it". diff --git a/pharn/pharn-review/off-by-one/off-by-one.md b/pharn/pharn-review/off-by-one/off-by-one.md index 3733614..1c5a456 100644 --- a/pharn/pharn-review/off-by-one/off-by-one.md +++ b/pharn/pharn-review/off-by-one/off-by-one.md @@ -76,8 +76,8 @@ an indexed/called chain link (`a[0].length`), and a bound split across lines all a **backtick template's text** is a documented false-POSITIVE (backticks are not masked). The scan is **single-file**. **This is NOT semantic/intent analysis** — that is the advisory layer, never this floor. -> **Two clocks (be honest).** The scanner's **output** is FLOOR (a deterministic verdict). But until the live -> isolated lens runner lands (deferred P7, as for every lens), the review stage **applies this lens inline** — so +> **Two clocks (be honest).** The scanner's **output** is FLOOR (a deterministic verdict). The isolated lens runner has LANDED — +> `/pharn-review` Step 4 spawns **one subagent per lens**, each writing its own `findings.json` — but > the lens's **act** of invoking the scanner is **advisory orchestration**, backstopped by the scanner's own tests > and this lens's eval. The guarantee is "the scanner IS deterministic", not "the model always ran it". diff --git a/pharn/pharn-review/path-traversal/path-traversal.md b/pharn/pharn-review/path-traversal/path-traversal.md index 39d10c9..2db632a 100644 --- a/pharn/pharn-review/path-traversal/path-traversal.md +++ b/pharn/pharn-review/path-traversal/path-traversal.md @@ -88,9 +88,9 @@ this floor. two hits for that one line — emit one finding per hit; both point at the same dangerous line the developer must fix (mirrors `injection`, where a line matching >1 pattern yields >1 hit). -> **Two clocks (be honest).** The scanner's **output** is FLOOR (a deterministic regex verdict). But until the -> live isolated lens runner lands (deferred P7, as for every lens/griller), the review stage **applies this lens -> inline** — so the lens's **act** of invoking the scanner is **advisory orchestration**, backstopped by the +> **Two clocks (be honest).** The scanner's **output** is FLOOR (a deterministic regex verdict). The isolated lens runner has LANDED — +> `/pharn-review` Step 4 spawns **one subagent per lens**, each writing its own `findings.json` (the per-GRILLER +> runner is still deferred, P7) — but the lens's **act** of invoking the scanner is **advisory orchestration**, backstopped by the > scanner's own tests and this lens's eval. The guarantee is "the scanner IS deterministic", not "the model > always ran it". diff --git a/pharn/pharn-review/placeholder-as-done/placeholder-as-done.md b/pharn/pharn-review/placeholder-as-done/placeholder-as-done.md index 13c5bb7..52f9b1d 100644 --- a/pharn/pharn-review/placeholder-as-done/placeholder-as-done.md +++ b/pharn/pharn-review/placeholder-as-done/placeholder-as-done.md @@ -76,8 +76,8 @@ reads as CLEAN); Pass B targets `function`/arrow bodies only (a method shorthand `return null` reads as CLEAN); a `}` inside a template/regex literal in a body can skew the brace-match. **This is NOT a completeness proof** — that is the advisory layer, never this floor. -> **Two clocks (be honest).** The scanner's **output** is FLOOR (a deterministic verdict). But until the live -> isolated lens runner lands (deferred P7, as for every lens), the review stage **applies this lens inline** — so the +> **Two clocks (be honest).** The scanner's **output** is FLOOR (a deterministic verdict). The isolated lens runner has LANDED — +> `/pharn-review` Step 4 spawns **one subagent per lens**, each writing its own `findings.json` — but the > lens's **act** of invoking the scanner is **advisory orchestration**, backstopped by the scanner's own tests and > this lens's eval. The guarantee is "the scanner IS deterministic", not "the model always ran it". diff --git a/pharn/pharn-review/race-condition/race-condition.md b/pharn/pharn-review/race-condition/race-condition.md index bfd550f..8bf7a54 100644 --- a/pharn/pharn-review/race-condition/race-condition.md +++ b/pharn/pharn-review/race-condition/race-condition.md @@ -156,16 +156,16 @@ reaches an enum-gated field). That the lens **emits** it at all, and emits it cl **NOT** a runtime guarantee that "no race exists." - **Honest about the exit codes (the "assert exit codes" discipline).** `check-structural.mjs` is deterministic and returns exit **0** (GREEN) on a conforming finding and exit **1** (RED) on a laundered/suppressed one. - But there is **no live isolated lens runner yet** (deferred, the same 3c wiring `finding-shape.md` §Emission - and `eval-format.md` label as unbuilt), and **no `actual.json` is committed** for this lens. So at build/verify - time the exit-code trip-wire is exercised by a **hand-constructed `actual.json`** (a demonstration, not a - committed product file and not an automated gate over the lens's _emitted_ output); the automated check over a - live `findings.json` lands when the 3c runner does. This is the honest "two clocks": the checker IS - deterministic; the model's act of running the lens and emitting a clean array is **advisory** until the runner - exists. + The isolated lens runner has since LANDED — `/pharn-review` Step 4 spawns one subagent per lens, each writing + its own `findings.json`, and `finding-shape.md` §Emission now records the 3c wiring as landed — but **no + `actual.json` is committed** for this lens. So at build/verify time the exit-code trip-wire is still exercised + by a **hand-constructed `actual.json`** (a demonstration, not a committed product file and not an automated + gate over the lens's _emitted_ output). This is the honest "two clocks": the checker IS deterministic; the + model's act of running the lens and emitting a clean array remains **advisory** — nothing on the floor forces + the lens to run. - **Two clocks (honest).** The eval's structural check is FLOOR (a deterministic verdict over a _provided_ - output). Until the live runner lands, the review stage **applies this lens inline** — so the lens's **act** of - judging + emitting is **advisory orchestration**, backstopped by the eval's structural[] trip-wire. The + output). The live runner has landed (`/pharn-review` spawns one subagent per lens), but the lens's **act** of + judging + emitting is still **advisory orchestration**, backstopped by the eval's structural[] trip-wire. The guarantee is "`check-structural.mjs` IS deterministic," not "the model always ran / judged correctly." - **"This lens ensures no race conditions / concurrency-safe code."** → **struck (the disease).** It **surfaces** a candidate race for human judgment; "produced a finding" (or none) **never** means the code is race-free (a diff --git a/pharn/pharn-review/resource-leak/resource-leak.md b/pharn/pharn-review/resource-leak/resource-leak.md index c695e5e..071ee11 100644 --- a/pharn/pharn-review/resource-leak/resource-leak.md +++ b/pharn/pharn-review/resource-leak/resource-leak.md @@ -90,8 +90,8 @@ lenient (`end(wrap(NAME))` reads as CLEAN); and it is **not scope-aware** (a sam an unrelated branch, or a `}`/`)` in a template/regex literal can skew it). **This is NOT ownership / control-flow analysis** — that is the advisory layer, never this floor. -> **Two clocks (be honest).** The scanner's **output** is FLOOR (a deterministic verdict). But until the live -> isolated lens runner lands (deferred P7, as for every lens), the review stage **applies this lens inline** — so the +> **Two clocks (be honest).** The scanner's **output** is FLOOR (a deterministic verdict). The isolated lens runner has LANDED — +> `/pharn-review` Step 4 spawns **one subagent per lens**, each writing its own `findings.json` — but the > lens's **act** of invoking the scanner is **advisory orchestration**, backstopped by the scanner's own tests and > this lens's eval. The guarantee is "the scanner IS deterministic", not "the model always ran it". diff --git a/pharn/pharn-review/secrets-in-code/secrets-in-code.md b/pharn/pharn-review/secrets-in-code/secrets-in-code.md index 8b4a746..b301678 100644 --- a/pharn/pharn-review/secrets-in-code/secrets-in-code.md +++ b/pharn/pharn-review/secrets-in-code/secrets-in-code.md @@ -67,9 +67,9 @@ line; it does **not** decide the literal is a live/real secret vs a placeholder, whether the code is "secret-free". "Detected a secret-shaped literal" is a real guarantee; **"the code has no secrets" is not** — novel formats, encodings, or split literals evade a fixed regex set. -> **Two clocks (be honest).** The scanner's **output** is FLOOR (a deterministic regex verdict). But until -> the live isolated lens runner lands (deferred P7, as for every lens/griller), the review stage **applies -> this lens inline** — so the lens's **act** of invoking the scanner is **advisory orchestration**, +> **Two clocks (be honest).** The scanner's **output** is FLOOR (a deterministic regex verdict). The isolated lens runner has +> LANDED — `/pharn-review` Step 4 spawns **one subagent per lens**, each writing its own `findings.json` +> (the per-GRILLER runner is still deferred, P7) — but the lens's **act** of invoking the scanner is **advisory orchestration**, > backstopped by the scanner's own tests and this lens's eval. The guarantee is "the scanner IS > deterministic", not "the model always ran it". diff --git a/pharn/pharn-review/ssrf/ssrf.md b/pharn/pharn-review/ssrf/ssrf.md index e51bf97..4a08721 100644 --- a/pharn/pharn-review/ssrf/ssrf.md +++ b/pharn/pharn-review/ssrf/ssrf.md @@ -92,9 +92,9 @@ assembly. **This is NOT taint analysis** — full data-flow taint tracing is the yields >1 hit (a rare same-line-two-calls case — SSRF has no genuinely-nested canonical sink like `path-traversal`'s `fs.readFile(path.join(…))`). Emit one finding per hit. -> **Two clocks (be honest).** The scanner's **output** is FLOOR (a deterministic regex verdict). But until the -> live isolated lens runner lands (deferred P7, as for every lens/griller), the review stage **applies this lens -> inline** — so the lens's **act** of invoking the scanner is **advisory orchestration**, backstopped by the +> **Two clocks (be honest).** The scanner's **output** is FLOOR (a deterministic regex verdict). The isolated lens runner has LANDED — +> `/pharn-review` Step 4 spawns **one subagent per lens**, each writing its own `findings.json` (the per-GRILLER +> runner is still deferred, P7) — but the lens's **act** of invoking the scanner is **advisory orchestration**, backstopped by the > scanner's own tests and this lens's eval. The guarantee is "the scanner IS deterministic", not "the model > always ran it". diff --git a/pharn/pharn-review/swallowed-exception/swallowed-exception.md b/pharn/pharn-review/swallowed-exception/swallowed-exception.md index 4471c49..f106195 100644 --- a/pharn/pharn-review/swallowed-exception/swallowed-exception.md +++ b/pharn/pharn-review/swallowed-exception/swallowed-exception.md @@ -85,8 +85,8 @@ logger-name set (`console.*`/`logger.*`/bare `log(`), so a custom-named logger r template/regex literal in a catch body can skew the brace-match. **This is NOT control-flow analysis** — that is the advisory layer, never this floor. -> **Two clocks (be honest).** The scanner's **output** is FLOOR (a deterministic verdict). But until the live -> isolated lens runner lands (deferred P7, as for every lens), the review stage **applies this lens inline** — so the +> **Two clocks (be honest).** The scanner's **output** is FLOOR (a deterministic verdict). The isolated lens runner has LANDED — +> `/pharn-review` Step 4 spawns **one subagent per lens**, each writing its own `findings.json` — but the > lens's **act** of invoking the scanner is **advisory orchestration**, backstopped by the scanner's own tests and > this lens's eval. The guarantee is "the scanner IS deterministic", not "the model always ran it". diff --git a/pharn/pharn-review/unsafe-deserialization/unsafe-deserialization.md b/pharn/pharn-review/unsafe-deserialization/unsafe-deserialization.md index 8f450e6..f631e20 100644 --- a/pharn/pharn-review/unsafe-deserialization/unsafe-deserialization.md +++ b/pharn/pharn-review/unsafe-deserialization/unsafe-deserialization.md @@ -91,9 +91,9 @@ instantiate arbitrary objects or execute code; its only risk (prototype pollutio merge**, which is not a detectable call SHAPE. Flagging it would be a false-positive flood and would over-claim. Prototype-pollution risk around parsed data is **advisory** (Layer 2), never a floor finding. -> **Two clocks (be honest).** The scanner's **output** is FLOOR (a deterministic regex verdict). But until the -> live isolated lens runner lands (deferred P7, as for every lens/griller), the review stage **applies this lens -> inline** — so the lens's **act** of invoking the scanner is **advisory orchestration**, backstopped by the +> **Two clocks (be honest).** The scanner's **output** is FLOOR (a deterministic regex verdict). The isolated lens runner has LANDED — +> `/pharn-review` Step 4 spawns **one subagent per lens**, each writing its own `findings.json` (the per-GRILLER +> runner is still deferred, P7) — but the lens's **act** of invoking the scanner is **advisory orchestration**, backstopped by the > scanner's own tests and this lens's eval. The guarantee is "the scanner IS deterministic", not "the model > always ran it".