@chrono-meta/fh-gate 1.4.83 → 1.4.85

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/.claude/rules/fh_4axis_gate.md +45 -1
  2. package/.claude-plugin/marketplace.json +2 -2
  3. package/CATALOG.md +23 -1
  4. package/CHEATSHEET.md +1 -1
  5. package/CLAUDE.md +21 -7
  6. package/knowledge/shared/harness-core/field_harness_diagnostic.md +1 -0
  7. package/knowledge/shared/harness-core/harness_incubator_doctrine.md +10 -0
  8. package/knowledge/shared/harness-core/harness_verification_core_extended.md +167 -0
  9. package/knowledge/shared/learnings/subagent_invocations_log.yaml +206 -0
  10. package/knowledge/shared/rules/operational_adaptation.md +35 -9
  11. package/package.json +3 -1
  12. package/plugins/fh-commons/.claude-plugin/plugin.json +1 -1
  13. package/plugins/fh-commons/skills/convergence-loop/SKILL.md +25 -10
  14. package/plugins/fh-meta/.claude-plugin/plugin.json +1 -1
  15. package/plugins/fh-meta/agents/challenger.md +1 -1
  16. package/plugins/fh-meta/skills/phantom-quench/SKILL.md +1 -1
  17. package/plugins/fh-meta/skills/steel-quench/SKILL.md +78 -10
  18. package/plugins/fh-meta/skills/steel-quench/SKILL_detail.md +3 -3
  19. package/scripts/consent_registry_check.sh +187 -15
  20. package/scripts/destructive_pre_gate.sh +32 -5
  21. package/scripts/package_coverage_check.sh +10 -0
  22. package/scripts/pipe_verdict_guard.sh +43 -3
  23. package/scripts/selfcheck.sh +117 -2
  24. package/scripts/session_close_check.sh +103 -1
  25. package/scripts/stale_clone_guard.sh +49 -6
  26. package/scripts/substrate_jump_detector.sh +21 -0
  27. package/scripts/test_consent_registry.sh +341 -42
  28. package/scripts/test_destructive_pre_gate_lanes.sh +42 -0
  29. package/scripts/test_dispatch_log_lanes.sh +99 -0
  30. package/scripts/test_pipe_verdict_guard_lanes.sh +71 -0
  31. package/scripts/test_selfcheck_state_lanes.sh +102 -0
  32. package/scripts/test_session_close_lanes.sh +187 -15
  33. package/scripts/test_stale_clone_guard_lanes.sh +54 -0
  34. package/templates/.git-hooks/pre-push +10 -1
  35. package/templates/consent_classes.yaml.example +18 -0
@@ -75,10 +75,20 @@ referent. Record the human-readable grounds in the body, the value in the frontm
75
75
 
76
76
  **Mechanical floor**: `scripts/consent_registry_check.sh` — joins `standing_consent` against the
77
77
  registry and enforces schema, eligibility soundness (a class naming an irreversible or unlisted sink
78
- **cannot** declare itself promotable), registration, expiry, and recorded scope. Missing registry → N/A
79
- + promotion disabled; unparseable fail-closed. Run it before trusting any grant; the prose above is
78
+ **cannot** declare itself promotable), registration, expiry, and the full recorded scope
79
+ (owner · mode · target · effects · sinks).
80
+
81
+ **Read the exit code, never the printed words** — the code is the only machine-readable channel, and
82
+ prose that says "DISABLED" disables nothing: **`0` = VERIFIED** (a real grant was joined; the ONLY code
83
+ on which a prompt may be skipped) · **`3` = UNMEASURED** (nothing to join — no registry, zero classes,
84
+ no UAP, **or a UAP recording no active grant**: keep asking) · **`1` = BROKEN** (unparseable or invalid;
85
+ cannot decide == not allowed). `if scripts/consent_registry_check.sh; then run_unprompted; fi` is
86
+ **wrong** — it reads 3 as success. Test `-eq 0` explicitly.
87
+
88
+ Run it before trusting any grant; the prose above is
80
89
  the salience layer over this check, not the enforcement. Anchor: `scripts/test_consent_registry.sh`
81
- (64 lanes, incl. the `F*` storage-form lanes). Four mutants were run against it — each false-clean
90
+ (75 lanes, incl. the `F*` storage-form lanes and the round-9 `P-F3*`/`D3*` fingerprint + exit-channel
91
+ lanes; each `P`/`D` lane was measured returning the fail-open verdict against the pre-fix script). Four mutants were run against it — each false-clean
82
92
  net, the fence regex, and the falsy-laundering guard — and each turned its lanes red, so the green is
83
93
  measured rather than assumed. Cross-family review found the first draft's green was partly vacuous
84
94
  (one lane called `ok` in both branches; three others passed via a path other than the one they named).
@@ -103,7 +113,17 @@ sessions from the UAP outcome log. Refinements that keep the count honest:
103
113
  The offer **quotes the three approvals and the exact scope**; a grant the user cannot audit is not
104
114
  consent. Then:
105
115
 
106
- - **granted** → write `standing_consent: <class>: {granted: <date>, expires: <date+N>, effects: [...]}`.
116
+ - **granted** → write `standing_consent: <class>: {granted: <date>, expires: <date+N>, owner: <gate/skill>,
117
+ mode: <mode>, target: <target scope>, effects: [...], sinks: [...]}`.
118
+ **All five scope fields are mandatory, not illustrative** — they are the fingerprint §Consent binds to
119
+ the action's SHAPE requires, and `scripts/consent_registry_check.sh` R6 refuses a grant missing any of
120
+ them (a fingerprint that omits a field cannot detect drift in that field). `sinks: []` is a real
121
+ recorded fingerprint, not an omission. *Origin (cross-family round 9, 2026-07-31)*: this line listed
122
+ only `effects`, so the shape a grant was told to write did not contain the fields the same document's
123
+ fingerprint rule binds to — and the checker, written from this line, read only what it listed. A
124
+ class's `owner` or `mode` could then be swapped underneath a live grant and the required re-ask never
125
+ fired. When the write-instruction and the invariant disagree, the write-instruction wins in practice,
126
+ because it is the one someone follows.
107
127
  Later instances run unprompted, each **states in one line what it did**, and each **appends a durable
108
128
  entry to `tracks/_meta/consent_runs.log`**. *Post-action chat notice is not a control* (cross-family
109
129
  round 2): a line the user scrolls past has stopped the prompt without replacing it. The chat line is
@@ -139,10 +159,13 @@ Their cost is not the prompt, it is that the thing cannot be undone — grain-in
139
159
  answer "yes" to once, knowingly and in scope, which is why it does not contradict the "acceptance alone
140
160
  never auto-runs" rule above.
141
161
 
142
- **Degrade direction (fail-closed), three ways**: no UAP (ephemeral/cloud session, wiped profile) → **no
143
- promotion, keep asking**. No registry entry → **no promotion, keep asking**. Expired or unparseable
162
+ **Degrade direction (fail-closed), four ways**: no UAP (ephemeral/cloud session, wiped profile) → **no
163
+ promotion, keep asking**. No registry entry → **no promotion, keep asking**. A UAP that exists but
164
+ records **no active grant** — empty, or holding only `declined`/`revoked`/`unset` — → **no promotion,
165
+ keep asking** (this is the same state as the two above and gets the same answer; it was the one branch
166
+ that used to report success). Expired or unparseable
144
167
  record → **`unset`, keep asking**. A missing consent record is never `granted` — an absent measurement is
145
- not a yes.
168
+ not a yes, and a refusal is never the reason a prompt is skipped.
146
169
 
147
170
  **Named residual — the ledger is self-attested (cross-family, 2026-07-29, HIGH, NOT closed).** The UAP
148
171
  outcome log is written by the same agent that benefits from fewer prompts, and it is gitignored, so there
@@ -158,9 +181,12 @@ on the assumption the ledger is trustworthy.**
158
181
  a class name is a string, and the action behind it can change after consent is granted. A sim that
159
182
  merely returned a report when you said "stop asking" may, ten sessions later, write into shared memory
160
183
  and trigger a downstream commit — same label, different blast radius, HITL skipped. So a grant records
161
- **what it was granted for**: the owning gate/skill, and the set of **effect classes** the action had at
184
+ **what it was granted for**: the owning gate/skill (`owner`) **and its `mode`** the two fields that say
185
+ *who* acts and *what it does* when it acts — and the set of **effect classes** the action had at
162
186
  grant time (reads · local writes · network · dispatch · repo-mutation) **plus the `target` scope and
163
- the `sinks` fingerprint**. On any later run whose fingerprint is **not a subset** of the granted one,
187
+ the `sinks` fingerprint**. That is the same five-field scope the offer above quotes to the user, and the
188
+ same five `consent_registry_check.sh` R6/R7 enforce; the three lists must not drift apart. On any later
189
+ run whose fingerprint is **not a subset** of the granted one,
164
190
  standing consent **reverts to `unset` and asks again**, naming what widened. Effect classes alone are
165
191
  too coarse to be the whole test (cross-family round 2): "local write" stays "local write" whether the
166
192
  target is a scratch report or a policy file — the *target* is where that drift shows, which is why it
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chrono-meta/fh-gate",
3
- "version": "1.4.83",
3
+ "version": "1.4.85",
4
4
  "description": "FH runtime adapters — run FH governance, skills, and agents via Claude or Codex with machine-parseable gates.",
5
5
  "license": "MIT",
6
6
  "keywords": [
@@ -63,6 +63,7 @@
63
63
  "scripts/fh-goal.sh",
64
64
  "scripts/count_check.sh",
65
65
  "scripts/selfcheck.sh",
66
+ "scripts/test_selfcheck_state_lanes.sh",
66
67
  "scripts/package_coverage_check.sh",
67
68
  "scripts/test_package_coverage_lanes.sh",
68
69
  "scripts/test_fh_gate_regressions.sh",
@@ -103,6 +104,7 @@
103
104
  "scripts/prepush_guard_check.sh",
104
105
  "scripts/psa_scan_lib.sh",
105
106
  "scripts/session_close_check.sh",
107
+ "scripts/test_dispatch_log_lanes.sh",
106
108
  "scripts/memory_link_check.py",
107
109
  "scripts/test_memory_link_check.sh",
108
110
  "scripts/memory_nearcheck.py",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fh-commons",
3
- "version": "1.4.83",
3
+ "version": "1.4.85",
4
4
  "engines": {
5
5
  "claudeCode": ">=1.0.0"
6
6
  },
@@ -54,15 +54,18 @@ Round 1
54
54
  │ → FAIL occurs: List FAIL items → Execute FIX → Round 2
55
55
 
56
56
 
57
- Round 2
57
+ Round N (N ≥ 2)
58
58
  │ Re-execute same gate (with FIX applied + search for new FAILs)
59
- │ → All items pass: Round 2 passed Round 3 (final check)
60
- New FAIL: List FIX Round 3
59
+ │ → All items pass AND no FIX was applied in response to THIS round:
60
+ Declare truly passed ← the only terminating exit
61
+ │ → All items pass BUT you fixed something this round (any severity):
62
+ │ the fix is unverified → Round N+1
63
+ │ → New FAIL: List → FIX → Round N+1
61
64
 
62
65
 
63
- Round 3 (final)
64
- │ → All items pass: Declare truly passed
65
- FAILs remain: "Structural redesign required" Escalate
66
+ │ → FAILs remain after N rounds: "Structural redesign required" → Escalate
67
+ │ → Rounds clean but each keeps producing fixes: the fixes are manufacturing the
68
+ findings REDUCE THE DESIGN, then re-run (steel-quench §Convergence Criteria 4)
66
69
 
67
70
  ▼ (if not converged within N rounds)
68
71
  Escalation
@@ -100,10 +103,18 @@ Escalation: [who to escalate to / how, if not converged within N rounds]
100
103
  ### Convergence Judgment
101
104
 
102
105
  ```
103
- Convergence = 0 new FAILs across 2 consecutive rounds
106
+ Convergence = a round returns 0 new FAILs AND no FIX was applied in response to it
104
107
  Conditions to declare truly passed:
105
108
  1. All items pass AND
106
- 2. At least 2 rounds executed (single-round pass treated as "provisionally passed" only)
109
+ 2. Nothing was changed in response to THIS round, at any severity
110
+ (a FAIL you accept as residual is terminal; a FAIL you fix is not — the fix is unverified) AND
111
+ 3. At least 2 rounds executed (a single clean round is "provisionally passed" only)
112
+ ← condition 3 is NOT redundant with 2 and was nearly lost when 2 replaced it: freezing answers
113
+ "did the artifact change under the audit", min-2 answers "is one look enough". Independent
114
+ questions, both still open. Done When and the pipeline diagram enforce min-2 as well.
115
+ NOT "0 new FAILs across 2 consecutive rounds": while every round ships fixes that can never fire,
116
+ so it reads as permanently not-converged and gets shipped past anyway. Stricter form for quench
117
+ waves: steel-quench §Convergence Criteria.
107
118
  ```
108
119
 
109
120
  ### Escalation Root Cause Classification
@@ -141,7 +152,7 @@ Max rounds: N | Actual convergence round: M
141
152
  | Situation | Related Skill |
142
153
  |---|---|
143
154
  | Applied to skill diagnostic gate | `harness-doctor` → convergence-loop wrapper |
144
- | Applied to quench wave | `steel-quench` Wave 3+ convergence criteria (same principle) |
155
+ | Applied to quench wave | `steel-quench` Wave 3+ **stricter**: S/A grade + the no-repair clause. Defer to its §Convergence Criteria, do not re-derive |
145
156
  | Applied to session harvest loop | `harvest-loop` extract→attack→synthesize cycle |
146
157
  | When gate redesign is needed | `meta-prompt-builder` |
147
158
 
@@ -150,7 +161,11 @@ Max rounds: N | Actual convergence round: M
150
161
  ```
151
162
  Setup complete (gate name, pass criteria, max rounds confirmed)
152
163
  + Minimum 2 rounds executed
153
- + Convergence declared (all items pass for 2 consecutive rounds) or escalation triggered
164
+ + Convergence declared (a round returns zero new failures AND you make no repairs in response to
165
+ it, at any grade) or escalation triggered
166
+ ⚠️ NOT "2 consecutive rounds": while every round ships repairs that criterion can never fire, so
167
+ it reads as permanently not-converged and gets shipped past. Adjudicated with measured evidence in
168
+ `steel-quench` §Convergence Criteria (2026-08-02).
154
169
  + Per-round result table output
155
170
  ```
156
171
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fh-meta",
3
- "version": "1.4.83",
3
+ "version": "1.4.85",
4
4
  "engines": {
5
5
  "claudeCode": ">=1.0.0"
6
6
  },
@@ -107,7 +107,7 @@ After completing all attack angles:
107
107
  ```
108
108
  New S-grade attacks this round: N
109
109
  Attack potency trend: [Increasing / Stable / Declining]
110
- Convergence signal: [Not yet / Approaching (LOW-confidence dominant) / Achieved (zero new S)]
110
+ Convergence signal: [Not yet / Approaching (LOW-confidence dominant) / Achieved (zero new S/A, and no repairs made in response — B included)]
111
111
  Residual risk: [List A/B items that remain unresolved]
112
112
  ```
113
113
 
@@ -117,7 +117,7 @@ Scan artifact quickly to classify claim distribution:
117
117
  | `risk_level` | external publish / arXiv citations → all claim types, max depth, **and Step 2-E (external fetch+support) is mandatory** |
118
118
  | `source_count` | 0 declared sources → S-grade blocker immediately (skip to Step 3 prescription) |
119
119
  | `quantitative_density` | > 3 numerical claims → focus numerical+range types first |
120
- | `external_citation` | artifact (or its diff) contains `arXiv:` / `DOI` / `http(s)://` / a version token (`x.y.z`) → **route those claims to Step 2-E** (governance binding: these are the load-bearing external claims FH's substantive carve-out already gates — `CLAUDE.md §Substantive carve-out`) |
120
+ | `external_citation` | artifact (or its diff) contains `arXiv:` / `DOI` / `http(s)://` / a version token (`x.y.z`) → **route those claims to Step 2-E** (governance binding: these are the load-bearing external claims FH's substantive carve-out already gates — `.claude/rules/fh_4axis_gate.md §Substantive carve-out`) |
121
121
 
122
122
  Scope recommendation output:
123
123
  ```
@@ -46,10 +46,10 @@ A designer's anxiety is most dangerous when vague. steel-quench breaks that anxi
46
46
  | **Phase 0** (optional) | Counterexample calibration — extract patterns from external bad cases, merge into Wave 1 | No external case → skip |
47
47
  | **Wave 1** | Challenger attack (quench-challenger) — surface critical flaws, no defense | — |
48
48
  | **Wave 2** | Defense — defend or state as residual risk | — |
49
- | **Wave 3+** | Convergence — repeat until zero new S-grade | Zero new S-grade |
50
- | **Wave 4** (optional) | Meta-Aware Adversary — AI uses its own nature as attack vector | Zero new S-grade + AI-specific criteria |
49
+ | **Wave 3+** | Convergence — repeat until a round is clean AND triggers no repair | Zero new S/A **and no repairs made in response, B included** (§Convergence Criteria) |
50
+ | **Wave 4** (optional) | Meta-Aware Adversary — AI uses its own nature as attack vector | Wave 3+ criterion (clean + no-repair) + AI-specific criteria |
51
51
  | **Wave-P3** (optional) | Gate-passage re-attack — when an upstream gate declares PASS, re-attack the just-passed artifact on Coverage / Narrative / False-confidence | All 3 dimensions Attack Failed |
52
- | **Wave 5** (optional) | Multi-Team Adversarial Panel — external CLIs or cross-session Claude | Zero new S-grade cross-team |
52
+ | **Wave 5** (optional) | Multi-Team Adversarial Panel — external CLIs or cross-session Claude | Wave 3+ criterion, cross-team |
53
53
  | **Wave-T** (after convergence) | Temper — measure complexity the quench *added*; flag over-hardening | τ-PASS or named τ-FAIL |
54
54
 
55
55
  ---
@@ -310,7 +310,7 @@ domain-coupled (a spec→test-case gate) form to a gate-agnostic boundary hook.
310
310
  ## Wave-T — Temper (post-convergence)
311
311
 
312
312
  Quench hardens, but quenched steel is brittle — no smith ships it un-tempered. steel-quench attacks
313
- until zero new S-grade; nothing in that loop asks whether the hardening itself **introduced complexity
313
+ until a frozen-artifact round is clean; nothing in that loop asks whether the hardening itself **introduced complexity
314
314
  beyond what the fixes required** (defense scaffolding, decorative wiring). Wave-T is that inverse
315
315
  corrective. It runs **after Wave 3+ convergence, before Done When**. It does not attack; it measures
316
316
  the cost of the convergence just achieved.
@@ -381,21 +381,89 @@ Add new rows as new patterns are discovered.
381
381
  ## Done When
382
382
 
383
383
  ```
384
- Wave convergence criteria met: zero new S-grade blockers
384
+ Wave convergence criteria met: zero new S/A-grade findings in a round that triggered NO repairs at
385
+ any grade (B included — a repaired B is an unverified change, and that is the measured way a
386
+ 'clean' round has already leaked an A) — see §Convergence Criteria
385
387
  + Residual risk card output (A-grade · B-grade items)
386
388
  + "steel-quench Complete" declaration output
387
389
  ```
388
390
 
389
- Verdict: PASS (zero S-grade, convergence reached) | CONDITIONAL_PASS (A/B-grade remain) | FAIL (S-grade persist) | ESCALATE (structural ambiguity requiring human judgment)
391
+ Verdict: PASS (zero new S/A **and no repairs made in response, at any grade** — §Convergence Criteria; repairing even a B and then declaring convergence is the measured round-4 failure) | CONDITIONAL_PASS (A/B-grade remain) | FAIL (S-grade persist) | ESCALATE (structural ambiguity requiring human judgment)
390
392
 
391
393
  ---
392
394
 
393
395
  ## Convergence Criteria + Downstream Chaining
394
396
 
395
397
  ### Convergence Criteria
396
- 1. **Zero new S-grade blockers** → terminate
397
- 2. A-grade or higher complex improvements skill-ize with `/meta-prompt-builder`
398
- 3. Full Wave results recommend persisting to `tracks/_meta/steel_quench_YYYY_MM_DD_{slug}.md`
398
+
399
+ **Convergence is measured over a FROZEN artifact.** A round that produced repairs did not verify the
400
+ artifact you are shipping — it verified the previous one. So the terminating round must be a round
401
+ over **unchanged** code:
402
+
403
+ 1. **Terminate** when a round returns **zero new S/A-grade** findings **AND you make no repairs in
404
+ response to it — at ANY grade, B included**. A round whose B findings you *accept as residual* is
405
+ terminal; a round whose B findings you *fix* is not, because the fix is unverified.
406
+ Why the B clause is the load-bearing half, not a formality: in the measured history below, round 4
407
+ returned `0A+3B` — clean by every S/A-based criterion, and the old rule would have stopped there.
408
+ The three B's were repaired, and round 5 found a genuine **A inside those repairs**. So "zero new
409
+ S/A" is not the discriminator; "nothing was changed in response" is. The loop ends when you stop
410
+ repairing, not when the challenger runs out of ideas.
411
+ **A post-convergence Wave-T de-brittling edit counts as a repair for this purpose.** Wave-T runs
412
+ after convergence and before Done When, so trimming a construct there is an unaudited edit that
413
+ the completion declaration would still describe truthfully ("no repairs were made in response to
414
+ this round") while the invariant it certifies — the shipped artifact is the audited one — is
415
+ false. Either ship the τ-FAIL as a **named** τ-FAIL (the pipeline table's own alternative, and
416
+ terminal) or run one more round over the trimmed artifact before declaring Done.
417
+ 2. **Record the per-round yield vector** in the Axis-2 marker — `axis2-rounds: 4A / 3A+1B / 2A /
418
+ 0A+3B / 1A+2B`. It is what makes the stop decision auditable afterwards, and a flat or rising
419
+ vector is criterion 4's only input.
420
+
421
+ **PIN THE TARGET — mandatory on every dispatched Wave, and the reason criterion 1 can be trusted
422
+ at all.** The dispatch prompt carries a measured fingerprint and orders the agent to report
423
+ `WRONG-TARGET` and stop on mismatch:
424
+
425
+ ```bash
426
+ git rev-parse --short HEAD; git diff HEAD | wc -l; git status --porcelain | grep -c '^??'
427
+ ```
428
+
429
+ An audit that did not pin its target **cannot be counted as a terminating round** — it may have
430
+ read a tree that changed under it, which voids the verdict wholesale
431
+ ([[feedback_audit_target_must_be_frozen]]). This lives HERE, inline, next to the claim it holds
432
+ up: the first draft put it in `SKILL_detail.md §Wave5`, reachable only when `--sidecar` is
433
+ active — gate-locality on the very fix that was closing a gate-locality gap, caught by the round
434
+ that audited it.
435
+
436
+ > **Named residual — the freeze claim is currently SELF-ATTESTED, and deliberately not
437
+ > mechanized.** Criterion 1 turns on "unmodified since the previous audit", and nothing checks
438
+ > that. Two attempts to mechanize it on the day the rule was written were both wrong: a hook
439
+ > advisory that fired on 100% of markers (noise, and its convergence detector could not match the
440
+ > hook's own prescribed evidence vocabulary), and a `base-SHA + diff-line-count` fingerprint that
441
+ > is **invariant under an in-place edit** — the modal shape of a prose repair — and structurally
442
+ > blind to untracked files. Both were removed rather than patched. This repo mechanizes at
443
+ > **repetition**, not at first sight, and a one-day-old rule that has already produced two wrong
444
+ > machines is telling you which side of that line it is on. What holds the claim honest today is
445
+ > practice, not mechanism: a dispatched audit pins the target fingerprint in its Step 0 and
446
+ > reports `WRONG-TARGET` on mismatch ([[feedback_audit_target_must_be_frozen]]). **Mechanize on
447
+ > the first recurrence of a convergence claim that turns out to be false** — and when you do,
448
+ > content-address the full working state (tracked diff **and** untracked files), because that is
449
+ > the specific hole the discarded attempt fell into.
450
+ 3. A-grade or higher complex improvements → skill-ize with `/meta-prompt-builder`
451
+ 4. **If the yield does not fall across rounds, stop tightening and REDUCE THE DESIGN.** A vector that
452
+ stays flat is not telling you to review harder; it is telling you that each round's repairs are
453
+ manufacturing the next round's findings. Cut the scope that is generating them (see the
454
+ Added-Scope Gate in `.claude/rules/fh_4axis_gate.md`) and re-audit the smaller artifact.
455
+ 5. Full Wave results → recommend persisting to `tracks/_meta/steel_quench_YYYY_MM_DD_{slug}.md`
456
+
457
+ > **Why this replaced "zero new S-grade → terminate" (measured 2026-08-02, PR #231).** A single
458
+ > change ran 5 rounds with yields `4A / 3A+1B / 2A / 0A+3B(cross-family) / 1A+2B`. The old criterion
459
+ > would have terminated at round 4 (zero S/A) — and round 5, run over round 4's repairs, found a
460
+ > genuine A. Meanwhile the competing criterion carried in operator memory ("two consecutive rounds
461
+ > with zero new") can never fire while every round ships repairs, so it read as permanently
462
+ > not-converged and the change shipped with that stated instead. **Both criteria were wrong in the
463
+ > same way: they counted rounds instead of asking whether anything had changed underneath.** Rounds
464
+ > 2, 3 and 5 found defects exclusively in code the previous round had added. Freezing is the fix that
465
+ > both were reaching for — the same discipline the single-audit rule already requires of its target,
466
+ > applied to the convergence loop itself.
399
467
 
400
468
  ### Connected Skills
401
469
 
@@ -414,7 +482,7 @@ Verdict: PASS (zero S-grade, convergence reached) | CONDITIONAL_PASS (A/B-grade
414
482
  ### Required Pre-External-Deployment Sequence
415
483
 
416
484
  ```
417
- steel-quench convergence (zero new S-grade)
485
+ steel-quench convergence (zero new S/A over a frozen artifact)
418
486
  ↓ pass residual risk list
419
487
  sim-conductor Area A (external user perspective)
420
488
  ↓ new items found that steel-quench missed?
@@ -163,7 +163,7 @@ New S-grade blockers: N (from AI-specific vectors: N)
163
163
  | Hallucination cumulative contamination | Mandate citing original file, commit hash, measured value — "LLM-reconstructed" not accepted |
164
164
  | Tool Dependency Lock-in | Checklist for core function after tool removal (degraded mode possible?) |
165
165
 
166
- **Wave 4 Convergence Criteria** (additional, beyond Wave 3 zero new S-grade):
166
+ **Wave 4 Convergence Criteria** (additional, beyond the Wave 3+ clean-and-no-repair criterion):
167
167
  1. At least 3 AI-specific vectors actually reviewed — not simply "no attacks"
168
168
  2. Hallucination defense arguments based on original file references
169
169
  3. Context Collapse scenario simulated at least once (waivable if session is short)
@@ -444,7 +444,7 @@ Running steel-quench in a meta-harness environment structurally lowers devil's a
444
444
 
445
445
  Devil attacks only static code in an isolated environment; the defender pulls evidence from the living system outside that isolated environment — this asymmetry is the basis for Wave 2 being structurally superior to Wave 1.
446
446
 
447
- As Wave N deepens, decreasing new S-grade blockers = evidence of the system genuinely becoming more robust. Zero new S-grade = fundamental flaws exhausted → termination condition.
447
+ As Wave N deepens, decreasing new S-grade blockers = evidence of the system genuinely becoming more robust. Zero new S/A AND no repairs made in response (B included) = fundamental flaws exhausted → termination condition. Repairing a B and declaring convergence is the measured failure this replaced.
448
448
 
449
449
  **Wave Deepening Principle**:
450
450
 
@@ -460,7 +460,7 @@ As Wave N deepens, decreasing new S-grade blockers = evidence of the system genu
460
460
  ```
461
461
  ## steel-quench Complete
462
462
 
463
- Wave N converged. Zero new S-grade blockers confirmed.
463
+ Wave N converged. Zero new S/A-grade findings, and NO repairs were made in response to this round at any grade.
464
464
 
465
465
  Residual Risk Card:
466
466
  - [List only A-grade · B-grade residual items]