@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
@@ -55,12 +55,17 @@ mkuap_raw() { printf '%s\n' "$1" > "$TD/u.md"; }
55
55
 
56
56
  mkreg "$OKCLASS"
57
57
  mkuap 'standing_consent:
58
- ok: {granted: 2026-07-29, expires: 2026-12-31, effects: [read], target: t}'
58
+ ok: {owner: o, mode: m, sinks: [], granted: 2026-07-29, expires: 2026-12-31, effects: [read], target: t}'
59
59
  lane "N1 a well-formed registry + eligible unexpired scoped grant passes" 0 ""
60
60
 
61
61
  mkuap 'standing_consent:
62
62
  ok: declined'
63
- lane "N2 a declined record is a valid non-grant state, not a malformed grant" 0 ""
63
+ # EXPECTATION CHANGED 0 -> 3 (F4-b, 2026-07-31). A `declined` record is still a valid non-grant
64
+ # state and still must not be a VIOLATION — that half of the lane is unchanged and is what the
65
+ # 3-vs-1 distinction now asserts. What changed is that "the user said no" may not be reported
66
+ # through the same channel as "the user said yes": exit 0 is the only code a caller may skip a
67
+ # prompt on, and a file recording nothing but refusals has granted nothing.
68
+ lane "N2 a declined record is a non-grant state (3), never a malformed grant (1)" 3 ""
64
69
 
65
70
  mkreg ' - {name: s, owner: o, mode: m, target: t, capabilities: [read], sinks: [go-public], feeds: [], promotion_eligible: true}'
66
71
  mkuap 'standing_consent: {}'
@@ -71,31 +76,31 @@ lane "P2 taint — a class that FEEDS an irreversible sink cannot be promotable"
71
76
 
72
77
  mkreg "$OKCLASS"
73
78
  mkuap 'standing_consent:
74
- ghost: {granted: 2026-07-29, expires: 2026-12-31, effects: [read], target: t}'
79
+ ghost: {owner: o, mode: m, sinks: [], granted: 2026-07-29, expires: 2026-12-31, effects: [read], target: t}'
75
80
  lane "P3 a grant on an unregistered class is refused (unregistered == unknown)" 1 "R3"
76
81
 
77
82
  mkuap 'standing_consent:
78
- ok: {granted: 2026-01-01, expires: 2026-06-30, effects: [read], target: t}'
83
+ ok: {owner: o, mode: m, sinks: [], granted: 2026-01-01, expires: 2026-06-30, effects: [read], target: t}'
79
84
  lane "P4 an expired lease does not keep running" 1 "R5"
80
85
 
81
86
  mkuap 'standing_consent:
82
- ok: {granted: 2026-07-29, expires: 2026-12-31}'
87
+ ok: {owner: o, mode: m, sinks: [], granted: 2026-07-29, expires: 2026-12-31}'
83
88
  lane "P5 a grant with no recorded scope has no re-validation baseline" 1 "R6"
84
89
 
85
90
  # P6-P8 are the round-3 fail-opens. Each of these PASSED before the fix.
86
91
  mkreg ' - {name: q, owner: o, mode: m, target: t, capabilities: [read], sinks: [], feeds: [], promotion_eligible: "false"}'
87
92
  mkuap 'standing_consent:
88
- q: {granted: 2026-07-29, expires: 2026-12-31, effects: [read], target: t}'
93
+ q: {owner: o, mode: m, sinks: [], granted: 2026-07-29, expires: 2026-12-31, effects: [read], target: t}'
89
94
  lane "P6 quoted \"false\" is rejected as a type error, not read as truthy" 1 "R1-b"
90
95
 
91
96
  mkreg ' - {name: d, owner: o, mode: m, target: t, capabilities: [read], sinks: [go-public], feeds: [], promotion_eligible: false}
92
97
  - {name: d, owner: o, mode: m, target: t, capabilities: [read], sinks: [], feeds: [], promotion_eligible: true}'
93
98
  mkuap 'standing_consent:
94
- d: {granted: 2026-07-29, expires: 2026-12-31, effects: [read], target: t}'
99
+ d: {owner: o, mode: m, sinks: [], granted: 2026-07-29, expires: 2026-12-31, effects: [read], target: t}'
95
100
  lane "P7 a duplicate class name cannot launder an ineligible class" 1 "R1-c"
96
101
 
97
102
  mkreg "$OKCLASS"
98
- mkuap 'standing_consent: {ok: {granted: 2026-01-01, expires: 2020-01-01, effects: [read], target: t}}'
103
+ mkuap 'standing_consent: {ok: {owner: o, mode: m, sinks: [], granted: 2026-01-01, expires: 2020-01-01, effects: [read], target: t}}'
99
104
  lane "P8 an INLINE grant is parsed, not silently read as 'nothing granted'" 1 "R5"
100
105
 
101
106
  # P10-P11 are the round-4 fail-opens. Both PASSED before the fix, and both were confirmed against a
@@ -106,16 +111,21 @@ mkuap 'standing_consent: {}
106
111
  notes in between
107
112
 
108
113
  standing_consent:
109
- ok: {granted: 2026-01-01, expires: 2020-01-01, effects: [read], target: t}'
114
+ ok: {owner: o, mode: m, sinks: [], granted: 2026-01-01, expires: 2020-01-01, effects: [read], target: t}'
110
115
  lane "P10 an early empty consent block cannot shadow a later grant (first-match)" 1 "R3"
111
116
 
112
117
  mkuap 'standing_consent:
113
- ok: {state: "revoked ", granted: 2026-07-29, expires: 2026-12-31, effects: [read], target: t}'
114
- if bash "$CHK" "$TD/r.yaml" "$TD/u.md" 2>&1 | grep -q "0 active grant"; then
118
+ ok: {owner: o, mode: m, sinks: [], state: "revoked ", granted: 2026-07-29, expires: 2026-12-31, effects: [read], target: t}'
119
+ # ASSERTION REWRITTEN 2026-07-31 (F4-b). This lane read the VERDICT out of prose ("0 active grant")
120
+ # through a pipe, which is both the grep-a-prose-verdict pattern this repo has a memory entry
121
+ # against and — via the pipe — a read of grep's status, not the checker's. It now asserts the typed
122
+ # channel: a normalized `revoked ` leaves zero active grants, which is exit 3, not 0 and not 1.
123
+ bash "$CHK" "$TD/r.yaml" "$TD/u.md" >"$TD/o" 2>&1; rc=$?
124
+ if [ "$rc" -eq 3 ] && grep -q "NONE of them an active grant" "$TD/o"; then
115
125
  ok "P11 a mapping non-grant state is normalized like the scalar one (no divergent normalizer)"
116
126
  else
117
- bad "P11 \`state: \"revoked \"\` was validated as an ACTIVE grant"
118
- bash "$CHK" "$TD/r.yaml" "$TD/u.md" 2>&1 | sed 's/^/ /'
127
+ bad "P11 \`state: \"revoked \"\` was not normalized to a non-grant (exit $rc, expected 3)"
128
+ sed 's/^/ /' "$TD/o"
119
129
  fi
120
130
 
121
131
  # P12 is the round-5 fail-open: `or {}` collapsed FALSY non-mappings into a valid-empty mapping
@@ -140,11 +150,11 @@ done
140
150
  mkreg "$OKCLASS"
141
151
  mkuap 'standing_consent: {}
142
152
 
143
- standing_consent : {ok: {granted: 2026-01-01, expires: 2020-01-01, effects: [read], target: t}}'
153
+ standing_consent : {ok: {owner: o, mode: m, sinks: [], granted: 2026-01-01, expires: 2020-01-01, effects: [read], target: t}}'
144
154
  lane "P13 a space-before-colon key cannot hide behind a canonical block" 1 "R3"
145
155
 
146
156
  mkuap 'standing_consent :
147
- ok: {granted: 2026-01-01, expires: 2020-01-01, effects: [read], target: t}'
157
+ ok: {owner: o, mode: m, sinks: [], granted: 2026-01-01, expires: 2020-01-01, effects: [read], target: t}'
148
158
  lane "P13-b a space-before-colon BLOCK form is parsed and its grant validated" 1 "R5"
149
159
 
150
160
  # N3 EXPECTATION INVERTED 2026-07-31 — and the reason is the whole point of the storage-form change.
@@ -154,7 +164,7 @@ lane "P13-b a space-before-colon BLOCK form is parsed and its grant validated" 1
154
164
  # So the belief was false, and a regex that approximates the YAML spec had been quietly ratifying a
155
165
  # document the spec rejects. Handing the region to the real loader replaces a guess with an answer —
156
166
  # a malformed document is now BROKEN (fail-closed), not silently accepted as equivalent.
157
- mkuap "$(printf 'standing_consent\t: {ok: {granted: 2026-07-29, expires: 2026-12-31, effects: [read], target: t}}')"
167
+ mkuap "$(printf 'standing_consent\t: {ok: {owner: o, mode: m, sinks: [], granted: 2026-07-29, expires: 2026-12-31, effects: [read], target: t}}')"
158
168
  lane "N3 a tab-before-colon key is invalid YAML and fails closed (loader, not regex, decides)" 1 "R3"
159
169
 
160
170
  # P14 — round-7 fail-open: R6 presence-checked `effects`/`target` but never typed them, while the
@@ -162,15 +172,15 @@ lane "N3 a tab-before-colon key is invalid YAML and fails closed (loader, not re
162
172
  # a MISSING field was caught; a type-wrong one passed.
163
173
  mkreg "$OKCLASS"
164
174
  mkuap 'standing_consent:
165
- ok: {granted: 2026-07-29, expires: 2026-12-31, effects: true, target: 123}'
175
+ ok: {owner: o, mode: m, sinks: [], granted: 2026-07-29, expires: 2026-12-31, effects: true, target: 123}'
166
176
  lane "P14 a type-wrong grant scope is rejected, not counted as recorded" 1 "R6"
167
177
 
168
178
  mkuap 'standing_consent:
169
- ok: {granted: 2026-07-29, expires: 2026-12-31, effects: "read", target: " "}'
179
+ ok: {owner: o, mode: m, sinks: [], granted: 2026-07-29, expires: 2026-12-31, effects: "read", target: " "}'
170
180
  lane "P14-b a scalar effects / whitespace-only target is rejected" 1 "R6"
171
181
 
172
182
  mkuap 'standing_consent:
173
- ok: {granted: 2026-07-29, expires: 2026-12-31, effects: [], target: t}'
183
+ ok: {owner: o, mode: m, sinks: [], granted: 2026-07-29, expires: 2026-12-31, effects: [], target: t}'
174
184
  lane "P14-c an EMPTY effects list is not a baseline" 1 "R6"
175
185
 
176
186
  # H1-H9 — round-8 exhaustive field audit found NINE remaining holes in one pass, after four rounds
@@ -196,26 +206,26 @@ lane "H3 an unreadable sink item is UNDECLARED, not an empty sink list" 1 "R1-b"
196
206
 
197
207
  mkreg "$OKCLASS"
198
208
  mkuap 'standing_consent:
199
- ok: {expires: 2026-12-31, effects: [read], target: t}'
209
+ ok: {owner: o, mode: m, sinks: [], expires: 2026-12-31, effects: [read], target: t}'
200
210
  lane "H5 a grant with no \`granted\` date cannot be audited" 1 "R5"
201
211
  mkuap 'standing_consent:
202
- ok: {granted: 2026-07-29, expires: 29991231, effects: [read], target: t}'
212
+ ok: {owner: o, mode: m, sinks: [], granted: 2026-07-29, expires: 29991231, effects: [read], target: t}'
203
213
  lane "H7 a non-ISO integer expiry is not a date" 1 "R5"
204
214
  mkuap 'standing_consent:
205
- ok: {granted: 2026-07-29, expires: 9999-12-31, effects: [read], target: t}'
215
+ ok: {owner: o, mode: m, sinks: [], granted: 2026-07-29, expires: 9999-12-31, effects: [read], target: t}'
206
216
  lane "H7-b an unbounded lease is a transfer wearing a lease's clothes" 1 "R5"
207
217
  mkuap 'standing_consent:
208
- ok: {state: revokedd, granted: 2026-07-29, expires: 2026-12-31, effects: [read], target: t}'
218
+ ok: {owner: o, mode: m, sinks: [], state: revokedd, granted: 2026-07-29, expires: 2026-12-31, effects: [read], target: t}'
209
219
  lane "H6 a typo'\''d state fails closed instead of passing as active" 1 "R3"
210
220
  mkuap 'standing_consent:
211
- ok: {granted: 2026-07-29, expires: 2026-12-31, effects: [repo-mutation], target: t}'
221
+ ok: {owner: o, mode: m, sinks: [], granted: 2026-07-29, expires: 2026-12-31, effects: [repo-mutation], target: t}'
212
222
  lane "H8 a grant wider than its registered capabilities is refused" 1 "R7"
213
223
  mkuap 'standing_consent:
214
- ok: {granted: 2026-07-29, expires: 2026-12-31, effects: [read], target: everything}'
224
+ ok: {owner: o, mode: m, sinks: [], granted: 2026-07-29, expires: 2026-12-31, effects: [read], target: everything}'
215
225
  lane "H8-b grant/class target drift is refused" 1 "R7"
216
226
  mkuap 'standing_consent:
217
- ok: {granted: 2026-07-29, expires: 2020-01-01, effects: [read], target: t}
218
- ok: {granted: 2026-07-29, expires: 2026-12-31, effects: [read], target: t}'
227
+ ok: {owner: o, mode: m, sinks: [], granted: 2026-07-29, expires: 2020-01-01, effects: [read], target: t}
228
+ ok: {owner: o, mode: m, sinks: [], granted: 2026-07-29, expires: 2026-12-31, effects: [read], target: t}'
219
229
  lane "H9 duplicate YAML keys are rejected, not resolved last-wins" 1 ""
220
230
 
221
231
  # H4 had a fix but NO lane — the mutation sweep caught that (disabling it failed zero lanes).
@@ -224,7 +234,7 @@ lane "H9 duplicate YAML keys are rejected, not resolved last-wins" 1 ""
224
234
  # still exits 1 via "not in the registry", so a rule-id assertion passed either way and the mutation
225
235
  # sweep caught zero lanes. A lane that cannot separate two paths is not measuring the one it names.
226
236
  mkuap 'standing_consent:
227
- 123: {granted: 2026-07-29, expires: 2026-12-31, effects: [read], target: t}'
237
+ 123: {owner: o, mode: m, sinks: [], granted: 2026-07-29, expires: 2026-12-31, effects: [read], target: t}'
228
238
  bash "$CHK" "$TD/r.yaml" "$TD/u.md" >"$TD/o" 2>&1
229
239
  if [ $? -eq 1 ] && grep -q 'grant key 123 must be a non-blank string' "$TD/o"; then
230
240
  ok "H4 a non-string grant key is refused AS A TYPE ERROR (not merely as unregistered)"
@@ -243,7 +253,7 @@ lane "P9 a scalar where a list is required is a type error" 1 "R1-b"
243
253
  # up as a lane change rather than being rediscovered from scratch — and so nobody reads the 40/40
244
254
  # as "no known over-block".
245
255
  mkreg "$OKCLASS"
246
- mkuap 'defaults: &d {ok: {granted: 2026-07-29, expires: 2026-12-31, effects: [read], target: t}}
256
+ mkuap 'defaults: &d {ok: {owner: o, mode: m, sinks: [], granted: 2026-07-29, expires: 2026-12-31, effects: [read], target: t}}
247
257
  standing_consent:
248
258
  <<: *d'
249
259
  # N4 REMOVED 2026-07-31 — it called ok() in BOTH branches, so it passed on every possible exit code
@@ -287,7 +297,7 @@ lane "D1-d a missing UAP is UNMEASURED (3) — no grants is not a verified pass"
287
297
  # return the same code again, the channel has collapsed back into prose.
288
298
  mkreg "$OKCLASS"
289
299
  mkuap 'standing_consent:
290
- ok: {granted: 2026-07-29, expires: 2026-12-31, effects: [read], target: t}'
300
+ ok: {owner: o, mode: m, sinks: [], granted: 2026-07-29, expires: 2026-12-31, effects: [read], target: t}'
291
301
  lane "D1-e 0 still means VERIFIED, and only that" 0 ""
292
302
 
293
303
  printf 'classes: [ {name: broken\n' > "$TD/r.yaml"
@@ -306,7 +316,7 @@ lane "D2 an unparseable registry fails closed (cannot decide == not allowed)" 1
306
316
  # through — which is exactly why the doctrine calls local tier evidence-of and never terminal.
307
317
  mkreg ' - {name: rewrite, owner: o, mode: m, target: t, capabilities: [history-rewrite], sinks: [], feeds: [], promotion_eligible: true}'
308
318
  mkuap 'standing_consent:
309
- rewrite: {granted: 2026-07-29, expires: 2026-12-31, effects: [history-rewrite], target: t}'
319
+ rewrite: {owner: o, mode: m, sinks: [], granted: 2026-07-29, expires: 2026-12-31, effects: [history-rewrite], target: t}'
310
320
  lane "R9-1 a declared irreversible CAPABILITY is not promotable, empty sinks or not" 1 "R2-b"
311
321
 
312
322
  # The same class WITHOUT the promotion claim is fine: declaring an irreversible capability is not
@@ -314,7 +324,9 @@ lane "R9-1 a declared irreversible CAPABILITY is not promotable, empty sinks or
314
324
  # can do. Over-blocking trains the override reflex — the checker already records one such revert.
315
325
  mkreg ' - {name: rewrite, owner: o, mode: m, target: t, capabilities: [history-rewrite], sinks: [], feeds: [], promotion_eligible: false}'
316
326
  mkuap 'standing_consent: {}'
317
- lane "R9-1b the same irreversible capability is fine when not claimed promotable" 0 ""
327
+ # EXPECTATION CHANGED 0 -> 3 (F4-b): the anti-over-block point of this lane is that the registry
328
+ # must NOT be a violation, i.e. not 1. Its UAP grants nothing, so the join is empty -> UNMEASURED.
329
+ lane "R9-1b the same irreversible capability is not a violation when not claimed promotable" 3 ""
318
330
 
319
331
  # `unknown` is in IRREVERSIBLE for the sink rule ("unlisted sinks are UNKNOWN, and unknown is not
320
332
  # reversible"); it must mean the same thing in the capability position.
@@ -326,7 +338,7 @@ lane "R9-1c an \`unknown\` capability is not promotable either" 1 "R2-b"
326
338
  # sinks is the N1 shape and must stay passing. If this lane ever flips, the fix went too wide.
327
339
  mkreg "$OKCLASS"
328
340
  mkuap 'standing_consent:
329
- ok: {granted: 2026-07-29, expires: 2026-12-31, effects: [read], target: t}'
341
+ ok: {owner: o, mode: m, sinks: [], granted: 2026-07-29, expires: 2026-12-31, effects: [read], target: t}'
330
342
  lane "R9-1d a reversible capability with empty sinks still passes (anti-over-block)" 0 ""
331
343
 
332
344
  # R9-1e — the SAME field must be normalised the SAME way by every rule that reads it. R7 compares
@@ -340,17 +352,24 @@ lane "R9-1e whitespace around an irreversible capability does not evade R2-b" 1
340
352
 
341
353
  # The shipped example must satisfy the validator — otherwise the artifact FH hands users is the
342
354
  # first counter-example. (Hand-verify-one-sample discipline, applied to our own template.)
343
- if bash "$CHK" "$ROOT/templates/consent_classes.yaml.example" /dev/null >/dev/null 2>&1; then
344
- ok "S1 the shipped templates/consent_classes.yaml.example validates"
355
+ # ASSERTION REWRITTEN 2026-07-31 (F4-b). This lane used the bare-command form — precisely the
356
+ # `if check; then ...` convention finding F4 identified as the fail-open so it asked "is the exit
357
+ # status truthy?" when the question is "is the REGISTRY broken?". With no grants to join the answer
358
+ # is now 3 (UNMEASURED), which is a correct verdict about the grants and says nothing bad about the
359
+ # registry. 1 is the only code that means the shipped example is invalid.
360
+ bash "$CHK" "$ROOT/templates/consent_classes.yaml.example" /dev/null >"$TD/o" 2>&1; rc=$?
361
+ if [ "$rc" -ne 1 ]; then
362
+ ok "S1 the shipped templates/consent_classes.yaml.example validates (exit $rc, not BROKEN)"
345
363
  else
346
364
  bad "S1 the shipped example registry does NOT validate"
365
+ sed 's/^/ /' "$TD/o"
347
366
  fi
348
367
 
349
368
  # ---- F: STORAGE FORM (frontmatter) -------------------------------------------------
350
369
  # These lanes test the machine-region boundary itself, not grant semantics, so they write the UAP
351
370
  # byte-exact via mkuap_raw instead of going through the frontmatter-wrapping helper.
352
371
  mkreg "$OKCLASS"
353
- GRANT=' ok: {granted: 2026-07-29, expires: 2026-12-31, effects: [read], target: t}'
372
+ GRANT=' ok: {owner: o, mode: m, sinks: [], granted: 2026-07-29, expires: 2026-12-31, effects: [read], target: t}'
354
373
 
355
374
  # F1/F2 are the two false-clean shapes, and they are the reason this section exists. A grant that
356
375
  # sits where no parser reads it must never report as "nothing granted" — an unread grant is not an
@@ -364,17 +383,20 @@ lane "F2 frontmatter exists but the grant was written BELOW it — fails closed"
364
383
  # F3 is the anti-over-block control for F1: absence must stay cheap. A UAP predating this format has
365
384
  # no machine region and grants nothing; painting that red would train the override reflex.
366
385
  mkuap_raw "$(printf '# UAP\n\nno machine region here, and nothing granted.\n')"
367
- lane "F3 no frontmatter and no grant mentioned is simply nothing granted (not a failure)" 0 ""
386
+ # EXPECTATION CHANGED 0 -> 3 (F4-b). "Absence must stay cheap" is preserved exactly: 3 is not red,
387
+ # it is N/A. What it no longer does is answer the conventional `if check; then run_unprompted; fi`
388
+ # with success on a UAP that granted nothing at all.
389
+ lane "F3 no frontmatter and no grant mentioned is nothing granted (3, not a failure)" 3 ""
368
390
 
369
391
  # F4 pins the OVER-BLOCK THIS CHANGE RETIRES. Under the slicer a merge-key grant was refused because
370
392
  # the anchor lived outside the extracted fragment; the fragment is now the whole document, so
371
393
  # ordinary DRY YAML resolves. Over-blocking was a defect of the same weight as a fail-open.
372
- mkuap_raw "$(printf -- '---\ndefaults: &d {granted: 2026-07-29, expires: 2026-12-31, effects: [read], target: t}\nstanding_consent:\n ok:\n <<: *d\n---\n')"
394
+ mkuap_raw "$(printf -- '---\ndefaults: &d {owner: o, mode: m, sinks: [], granted: 2026-07-29, expires: 2026-12-31, effects: [read], target: t}\nstanding_consent:\n ok:\n <<: *d\n---\n')"
373
395
  lane "F4 a merge-key/anchor grant now resolves (the measured over-block is retired)" 0 ""
374
396
 
375
397
  # F5 — duplicate keys stay rejected on this side too. safe_load is last-wins, so an expired grant
376
398
  # followed by a future one would silently keep the future one.
377
- mkuap_raw "$(printf -- '---\nstanding_consent:\n ok: {granted: 2026-01-01, expires: 2020-01-01, effects: [read], target: t}\n ok: {granted: 2026-07-29, expires: 2026-12-31, effects: [read], target: t}\n---\n')"
399
+ mkuap_raw "$(printf -- '---\nstanding_consent:\n ok: {owner: o, mode: m, sinks: [], granted: 2026-01-01, expires: 2020-01-01, effects: [read], target: t}\n ok: {granted: 2026-07-29, expires: 2026-12-31, effects: [read], target: t}\n---\n')"
378
400
  lane "F5 duplicate grant keys are refused, not last-wins" 1 "R3"
379
401
 
380
402
  # F6 — a leading BOM must not make the machine region look absent. Without the tolerance this still
@@ -398,18 +420,22 @@ lane "F8 an unterminated --- block is not a machine region and fails closed" 1 "
398
420
  # went green without the empty-region path ever running. Lane F9-b below is its control: same empty
399
421
  # region, but WITH a prose mention, which can only stay green if the region was really recognized.
400
422
  mkuap_raw "$(printf -- '---\n---\n\n# UAP\n')"
401
- lane "F9 an empty frontmatter block grants nothing and is not an error" 0 ""
423
+ # EXPECTATION CHANGED 0 -> 3 (F4-b): still not an error (not 1), but an empty region grants nothing.
424
+ lane "F9 an empty frontmatter block grants nothing (3) and is not an error (1)" 3 ""
402
425
 
403
426
  # F9-b — the discriminating control for F9. If the empty region is NOT recognized, this file reads as
404
427
  # "no frontmatter + standing_consent mentioned" and fails closed via F1. Green here means the empty
405
428
  # frontmatter was genuinely parsed. (Without this control F9 cannot tell the two paths apart.)
406
429
  mkuap_raw "$(printf -- '---\n---\n\n# UAP\n\nthe key name standing_consent appears in prose but not as a key.\n')"
407
- lane "F9-b empty region + a prose MENTION is still nothing granted (region truly parsed)" 0 ""
430
+ # EXPECTATION CHANGED 0 -> 3 (F4-b). The discriminating power of this control is UNCHANGED: if the
431
+ # empty region were not recognized, this file would fail CLOSED via the F1 net at exit 1, which is
432
+ # still distinct from the 3 asserted here.
433
+ lane "F9-b empty region + a prose MENTION is still nothing granted (region truly parsed)" 3 ""
408
434
 
409
435
  # F10-F13 — YAML stream forms the canonical loader accepts and the first fence regex rejected. Each
410
436
  # failed CLOSED (no leak) but as an OVER-BLOCK whose message blamed an absent frontmatter that was
411
437
  # present. A gate that misdirects the fix gets overridden, so these are lanes, not footnotes.
412
- EXPIRED=' ok: {granted: 2026-01-01, expires: 2020-01-01, effects: [read], target: t}'
438
+ EXPIRED=' ok: {owner: o, mode: m, sinks: [], granted: 2026-01-01, expires: 2020-01-01, effects: [read], target: t}'
413
439
  mkreg "$OKCLASS"
414
440
  mkuap_raw "$(printf -- '---\nstanding_consent:\n%s\n...\n' "$EXPIRED")"
415
441
  lane "F10 a '...' document terminator closes the machine region (grant is READ, then R5-caught)" 1 "R5"
@@ -429,6 +455,279 @@ lane "F13 a %YAML directive before the fence does not hide the region" 1 "R5"
429
455
  mkuap_raw "$(printf -- '---\nsidecar_consent: granted\n---\n\n# UAP\n\n"standing_consent":\n%s\n' "$EXPIRED")"
430
456
  lane "F14 a QUOTED grant key in the prose region is caught too (nets agree)" 1 "R3"
431
457
 
458
+ # ---- R9-F3: THE FINGERPRINT WAS A PARTIAL HASH ------------------------------------
459
+ # Cross-family round 9 (codex @ gpt-5.6-sol). The rule says consent binds to the action's SHAPE and
460
+ # enumerates that shape — "the owning gate/skill, and the set of effect classes ... plus the
461
+ # `target` scope and the `sinks` fingerprint", offered to the user as `<mode · target ·
462
+ # capabilities · sinks>`. The baseline the checker recorded was `effects` + `target` ONLY.
463
+ # So the fields that say WHO acts (`owner`) and WHAT IT DOES when it acts (`mode`) were outside the
464
+ # floor: keep the class name, target, capabilities and empty sinks, swap the owner to a different
465
+ # skill, and R6 found its two fields present, R7 found the effects still a subset, and the gate
466
+ # returned 0 with the blast radius re-pointed under a live grant.
467
+ # The rule's own sentence is the indictment: "the name is exactly what does not change when the
468
+ # danger does" — and neither, it turned out, did anything the floor was reading.
469
+ # Each P-lane below was confirmed to return 0 before the fix and 1 after; each is paired with an
470
+ # anti-over-block N-lane so the check is calibrated on a known-negative, not only a known-positive.
471
+ FPGRANT=' ok: {owner: o, mode: m, sinks: [], granted: 2026-07-29, expires: 2026-12-31, effects: [read], target: t}'
472
+
473
+ # P-F3a / P-F3b — the drift the review reproduced: same name, same target, same capabilities, same
474
+ # empty sinks; only the acting skill (or what it does) changed.
475
+ mkreg ' - {name: ok, owner: DANGEROUS-OTHER-SKILL, mode: m, target: t, capabilities: [read], sinks: [], feeds: [], promotion_eligible: true}'
476
+ mkuap "standing_consent:
477
+ $FPGRANT"
478
+ lane "P-F3a a class re-pointed to a different OWNER under a live grant is refused" 1 "R7"
479
+
480
+ mkreg ' - {name: ok, owner: o, mode: unrestricted, target: t, capabilities: [read], sinks: [], feeds: [], promotion_eligible: true}'
481
+ mkuap "standing_consent:
482
+ $FPGRANT"
483
+ lane "P-F3b a class whose MODE changed under a live grant is refused" 1 "R7"
484
+
485
+ # N-F3c — the control for both: an intact fingerprint must still pass. Without it P-F3a/b prove
486
+ # only that something fails, not that the owner/mode comparison is what fired.
487
+ mkreg "$OKCLASS"
488
+ mkuap "standing_consent:
489
+ $FPGRANT"
490
+ lane "N-F3c an intact owner/mode/target/sinks fingerprint still passes (anti-over-block)" 0 ""
491
+
492
+ # P-F3d — presence. A grant written without the fingerprint cannot detect drift in it later, which
493
+ # is the same argument R6 already made for effects/target and did not apply to the other three.
494
+ mkuap 'standing_consent:
495
+ ok: {granted: 2026-07-29, expires: 2026-12-31, effects: [read], target: t}'
496
+ lane "P-F3d a grant recording no owner/mode/sinks has no fingerprint to compare" 1 "R6"
497
+
498
+ # N-F3e — the FALSY-LAUNDERING control, the defect class this file has now been bitten by three
499
+ # times. `sinks: []` is a REAL fingerprint ("this class crossed nothing at grant time") and must not
500
+ # collapse into "not recorded". If this lane ever goes red with an R6 sinks message, a sentinel
501
+ # check was replaced by a truthiness test.
502
+ mkuap "standing_consent:
503
+ $FPGRANT"
504
+ lane "N-F3e an EMPTY sinks list is a recorded fingerprint, not a missing one" 0 ""
505
+
506
+ # P-F3f/g — types. The registry side got strict types at R1-b and the grant side got them for
507
+ # effects/target at R6; the three new fields inherit the same rule rather than a laxer one.
508
+ mkuap 'standing_consent:
509
+ ok: {owner: o, mode: m, sinks: "go-public", granted: 2026-07-29, expires: 2026-12-31, effects: [read], target: t}'
510
+ lane "P-F3f a scalar sinks fingerprint is unreadable, and unreadable is undeclared" 1 "R6"
511
+
512
+ mkuap 'standing_consent:
513
+ ok: {owner: " ", mode: m, sinks: [], granted: 2026-07-29, expires: 2026-12-31, effects: [read], target: t}'
514
+ lane "P-F3g a blank owner is not a recorded owner" 1 "R6"
515
+
516
+ # N-F3h — ONE normalizer, both sides. The registry side reads `owner` through _norm; if the grant
517
+ # side ever compares raw strings, ` o ` and `o` become two values and the gate fires on a file that
518
+ # did not drift. Divergent normalizers is exactly how R2-b was first written wrong.
519
+ mkuap 'standing_consent:
520
+ ok: {owner: " o ", mode: m, sinks: [], granted: 2026-07-29, expires: 2026-12-31, effects: [read], target: t}'
521
+ lane "N-F3h whitespace around an owner is not drift (single normalizer, both sides)" 0 ""
522
+
523
+ # ---- R9-F4-b: THE SAME FAIL-OPEN, ONE BRANCH OVER ---------------------------------
524
+ # F4 gave "nothing to join" its own exit code for a MISSING registry, ZERO classes and a MISSING
525
+ # UAP — and left a PRESENT UAP holding ZERO grants returning 0. Identical state, two codes. Lane
526
+ # D1-d already asserted in its own name that "no grants is not a verified pass", and the EXIT
527
+ # CONTRACT already defined 3 as "there was nothing to join"; only the code disagreed. A caller
528
+ # writing `if scripts/consent_registry_check.sh; then run_unprompted; fi` ran unprompted against a
529
+ # profile that had granted it nothing.
530
+ mkreg "$OKCLASS"
531
+ mkuap 'standing_consent: {}'
532
+ lane "D3 a valid registry with an EXPLICITLY empty grant set is UNMEASURED (3)" 3 ""
533
+
534
+ # D3-b — the discriminating control. If D3 and this lane ever return the same code again, the typed
535
+ # channel has collapsed back into prose and 0 no longer means "a real grant was joined".
536
+ mkuap "standing_consent:
537
+ $FPGRANT"
538
+ lane "D3-b 0 is still reachable ONLY by joining a real grant" 0 ""
539
+
540
+ # D3-c — precedence. BROKEN outranks UNMEASURED: a registry violation is a decided negative, while
541
+ # "nothing granted" is merely nothing to join. Without this lane the F4-b change could silently
542
+ # convert a real violation into a soft N/A whenever the UAP happened to be empty.
543
+ mkreg ' - {name: ok, owner: o, mode: m, target: t, capabilities: [read], sinks: [], feeds: [], promotion_eligible: "false"}'
544
+ mkuap 'standing_consent: {}'
545
+ lane "D3-c a registry violation still outranks 'nothing granted' (1 beats 3)" 1 "R1-b"
546
+
547
+
548
+ # ── D3-d: the prose summary must agree with the typed exit ────────────────────
549
+ # A tail-reading operator saw "consent-registry: PASS" on a run that exited 3 and whose own line
550
+ # said "(not a PASS)". Machines read the code; humans read the last line. They must not disagree.
551
+ cat > "$TD/r.yaml" <<'YAML'
552
+ classes:
553
+ - name: safe-thing
554
+ owner: some-skill
555
+ mode: readonly
556
+ target: "tracks/_meta/**"
557
+ capabilities: [read]
558
+ effects: [read]
559
+ sinks: []
560
+ feeds: []
561
+ promotion_eligible: true
562
+ YAML
563
+ printf -- '---\nstanding_consent: {}\n---\n' > "$TD/u.md"
564
+ bash "$CHK" "$TD/r.yaml" "$TD/u.md" >"$TD/o" 2>&1; _rc=$?
565
+ if [ "$_rc" -ne 0 ] && tail -1 "$TD/o" | grep -q '^consent-registry: PASS$'; then
566
+ bad "D3-d prose summary says PASS while the typed exit is $_rc"
567
+ else
568
+ ok "D3-d prose summary agrees with the typed exit (rc=$_rc)"
569
+ fi
570
+
571
+
572
+ # ── P-VOCAB: the irreversible vocabulary must not be evadable by case or invisible characters ──
573
+ # Carried for two sessions as a named residual ("_norm does not case-fold"). A cross-family round
574
+ # MEASURED it as a live fail-open: a class whose capability is a history rewrite was PASSing and
575
+ # therefore promotable. Each variant below returned rc=0 before the fix.
576
+ for _v in 'History-Rewrite' 'HISTORY-REWRITE' 'local-wrtie'; do
577
+ cat > "$TD/r.yaml" <<YAML
578
+ classes:
579
+ - name: rewrite
580
+ owner: o
581
+ mode: m
582
+ target: t
583
+ capabilities: ["$_v"]
584
+ effects: [read]
585
+ sinks: []
586
+ feeds: []
587
+ promotion_eligible: true
588
+ YAML
589
+ cat > "$TD/u.md" <<'YAML'
590
+ ---
591
+ standing_consent:
592
+ rewrite: {owner: o, mode: m, sinks: [], granted: 2026-07-29, expires: 2026-12-31, effects: [read], target: t}
593
+ ---
594
+ YAML
595
+ bash "$CHK" "$TD/r.yaml" "$TD/u.md" >"$TD/o" 2>&1; _rc=$?
596
+ # Assert the RULE, not merely a non-zero exit. These fixtures also trip R7 (their grant effect is
597
+ # not a subset of the malformed capability list), so "exit != 0" would have passed even with the
598
+ # vocabulary check removed — right answer, wrong reason, which is the shape this suite's own
599
+ # lane() helper exists to reject. (Partial vacuity found by cross-family round 2.)
600
+ if [ "$_rc" -eq 0 ]; then bad "P-VOCAB '$_v' still PASSes (irreversible/unknown floor evaded)"
601
+ elif grep -qE '❌ (R2-b|R2-c)' "$TD/o"; then ok "P-VOCAB '$_v' refused BY THE FLOOR (R2-b/R2-c)"
602
+ else bad "P-VOCAB '$_v' refused, but not by R2-b/R2-c — the vocabulary check was not the reason"; fi
603
+ done
604
+
605
+ # N-VOCAB: the anti-over-block control. A class using ONLY declared capabilities must still pass —
606
+ # a vocabulary check that refuses legitimate input teaches bypass and is a defect of equal rank.
607
+ cat > "$TD/r.yaml" <<'YAML'
608
+ classes:
609
+ - name: ok-class
610
+ owner: o
611
+ mode: m
612
+ target: t
613
+ capabilities: [read, local-write, network, dispatch, repo-mutation]
614
+ effects: [read]
615
+ sinks: []
616
+ feeds: []
617
+ promotion_eligible: true
618
+ YAML
619
+ cat > "$TD/u.md" <<'YAML'
620
+ ---
621
+ standing_consent:
622
+ ok-class: {owner: o, mode: m, sinks: [], granted: 2026-07-29, expires: 2026-12-31, effects: [read], target: t}
623
+ ---
624
+ YAML
625
+ lane "N-VOCAB every declared capability is still accepted (no over-block)" 0 ""
626
+
627
+ # ── D4: BROKEN outranks UNMEASURED even at the zero-class early return ──
628
+ printf 'classes: []\n' > "$TD/r.yaml"
629
+ cat > "$TD/u.md" <<'YAML'
630
+ ---
631
+ standing_consent:
632
+ ok: {owner: o, mode: m, sinks: [], granted: 2026-07-29, expires: 2026-12-31, effects: [read], target: t}
633
+ ---
634
+ YAML
635
+ lane "D4 live grant + zero registered classes = BROKEN (1), not 'nothing to join' (3)" 1 ""
636
+ printf 'classes: []\n' > "$TD/r.yaml"
637
+ printf -- '---\nstanding_consent: {}\n---\n' > "$TD/u.md"
638
+ lane "D4-b no grant + zero classes is still UNMEASURED (3) — the discriminating control" 3 ""
639
+
640
+ # ── K1: KNOWN OVER-BLOCK, pinned as known, NOT as correct ─────────────────────
641
+ # A canonical YAML merge-key override (`<<: *anchor` then a field override) is reported as a
642
+ # duplicate key and refused. This is a REAL over-block. It is pinned rather than fixed because the
643
+ # one attempt to rewrite the parser regressed 16 of 41 lanes and was reverted in the same session;
644
+ # the fix belongs in its own change with its own lanes. If this lane ever flips to rc=0, the
645
+ # over-block was fixed — update the label, do not silence it.
646
+ cat > "$TD/r.yaml" <<'YAML'
647
+ classes:
648
+ - name: ok
649
+ owner: o
650
+ mode: m
651
+ target: t
652
+ capabilities: [read]
653
+ effects: [read]
654
+ sinks: []
655
+ feeds: []
656
+ promotion_eligible: true
657
+ YAML
658
+ cat > "$TD/u.md" <<'YAML'
659
+ ---
660
+ defaults: &d {owner: o, mode: m, sinks: [], granted: 2026-07-29, expires: 2026-12-31, effects: [read], target: WRONG}
661
+ standing_consent:
662
+ ok:
663
+ <<: *d
664
+ target: t
665
+ ---
666
+ YAML
667
+ # PROMOTED from "known over-block, pinned not endorsed" to a real assertion (2026-08-02). The
668
+ # over-block was closed by moving duplicate detection BEFORE merge resolution — a reorder, not the
669
+ # parser rewrite that regressed 16 of 41 lanes. Both directions are asserted: a canonical override is
670
+ # accepted here, and K1-b below keeps the literal-duplicate detection it was protecting.
671
+ lane "K1 a canonical YAML merge-key override is ACCEPTED (over-block closed)" 0 ""
672
+
673
+ cat > "$TD/u.md" <<'YAML'
674
+ ---
675
+ standing_consent:
676
+ ok: {owner: o, mode: m, sinks: [], granted: 2026-07-29, expires: 2026-12-31, effects: [read], target: t}
677
+ ok: {owner: o, mode: m, sinks: [], granted: 2026-07-29, expires: 2026-12-31, effects: [read], target: t}
678
+ ---
679
+ YAML
680
+ lane "K1-b a LITERAL duplicate key is still fail-closed (the paired control)" 1 ""
681
+
682
+
683
+
684
+ # ── N-SPELL / P-SPELL: effects⊆capabilities is a VOCABULARY question ────────────
685
+ # Splitting the normalizer by purpose fixed a fail-open and opened an over-block: this comparison was
686
+ # left on the identity normalizer, so `capabilities: [READ]` with a grant `effects: [read]` — the same
687
+ # effect class, spelled differently — was refused. Over-blocking is not a safe default; a gate that
688
+ # refuses correct input is one the operator learns to route around. Both directions are pinned so a
689
+ # future normalizer change cannot fix one by breaking the other.
690
+ cat > "$TD/r.yaml" <<'YAML'
691
+ classes:
692
+ - name: c
693
+ owner: o
694
+ mode: m
695
+ target: t
696
+ capabilities: [READ]
697
+ effects: [READ]
698
+ sinks: []
699
+ feeds: []
700
+ promotion_eligible: true
701
+ YAML
702
+ cat > "$TD/u.md" <<'YAML'
703
+ ---
704
+ standing_consent:
705
+ c: {owner: o, mode: m, sinks: [], granted: 2026-07-29, expires: 2026-12-31, effects: [read], target: t}
706
+ ---
707
+ YAML
708
+ lane "N-SPELL a spelling variant of the SAME effect class is accepted (no over-block)" 0 ""
709
+
710
+ cat > "$TD/r.yaml" <<'YAML'
711
+ classes:
712
+ - name: c
713
+ owner: o
714
+ mode: m
715
+ target: t
716
+ capabilities: [read]
717
+ effects: [read]
718
+ sinks: []
719
+ feeds: []
720
+ promotion_eligible: true
721
+ YAML
722
+ cat > "$TD/u.md" <<'YAML'
723
+ ---
724
+ standing_consent:
725
+ c: {owner: o, mode: m, sinks: [], granted: 2026-07-29, expires: 2026-12-31, effects: [delete], target: t}
726
+ ---
727
+ YAML
728
+ lane "P-SPELL a grant WIDER than its class is still refused (the paired control)" 1 "R7"
729
+
730
+
432
731
  echo "----"
433
732
  echo "consent-registry anchor: $pass passed, $fail failed"
434
733
  [ "$fail" -eq 0 ] || exit 1
@@ -60,6 +60,26 @@ expect "stash pop is CLEAN" CLEAN 'git stash pop'
60
60
  expect "worktree remove --force" HIT 'git worktree remove --force ../wt'
61
61
  expect "worktree remove plain is CLEAN" CLEAN 'git worktree remove ../wt'
62
62
 
63
+ echo "-- global-option allowlist completion (leg-C MED round, 2026-08-01) --"
64
+ # Any global option the normalizer does not consume lets `git <opt> reset --hard` sail past every
65
+ # git row. One HIT lane per newly consumed class + adjacent CLEANs (known-pair rule).
66
+ expect "literal-pathspecs then reset --hard" HIT 'git --literal-pathspecs reset --hard'
67
+ expect "namespace= then reset --hard" HIT 'git --namespace=ns reset --hard'
68
+ expect "config-env= then clean -fd" HIT 'git --config-env=AUTHOR=E clean -fd'
69
+ expect "exec-path= then reset --hard" HIT 'git --exec-path=/opt/git reset --hard'
70
+ expect "no-optional-locks then push --force" HIT 'git --no-optional-locks push --force origin main'
71
+ expect "stacked globals then reset --hard" HIT 'git --bare --no-advice -c a=b reset --hard'
72
+ expect "literal-pathspecs status is CLEAN" CLEAN 'git --literal-pathspecs status'
73
+ expect "namespace= log is CLEAN" CLEAN 'git --namespace=ns log --oneline'
74
+
75
+ # Healing-path citation (leg-C MED): the advisory must point at the runnable path, not a bare name.
76
+ h_out=$(printf '%s' 'git reset --hard' | bash "$G" --stdin-raw 2>&1)
77
+ if printf '%s' "$h_out" | grep -q 'templates/predelete_check.sh'; then
78
+ printf ' ✅ %-52s OK\n' "advisory cites templates/predelete_check.sh path"; pass=$((pass+1))
79
+ else
80
+ printf ' ❌ %-52s out=%s\n' "advisory cites templates/predelete_check.sh path" "$h_out"; fail=$((fail+1))
81
+ fi
82
+
63
83
  echo "-- rm scope (precision over recall: only root/home/cwd/glob) --"
64
84
  expect "rm -rf /" HIT 'rm -rf /'
65
85
  expect "rm -rf ~" HIT 'rm -rf ~'
@@ -218,6 +238,28 @@ else
218
238
  printf ' ❌ %-52s rc=%s out=%s\n' "C5 non-Bash payload: silent" "$c_rc" "$c_out"; fail=$((fail+1))
219
239
  fi
220
240
 
241
+ echo "-- GPT leg-C round (2026-08-01): option-before-pathspec + staged-only + unicode-input --"
242
+ expect "checkout -f . (leg-C bypass)" HIT 'git checkout -f .'
243
+ expect "restore --source=HEAD . (leg-C bypass)" HIT 'git restore --source=HEAD .'
244
+ expect "restore -s HEAD ." HIT 'git restore -s HEAD .'
245
+ expect "restore --staged . is CLEAN (unstage only)" CLEAN 'git restore --staged .'
246
+ expect "restore --staged --worktree ." HIT 'git restore --staged --worktree .'
247
+ expect "checkout .gitignore still CLEAN" CLEAN 'git checkout .gitignore'
248
+
249
+ # C6 — unicode command + ascii-codec env: the INPUT extractor must survive (leg-C HIGH #8 —
250
+ # output encoder was pinned, input was not; a non-ASCII char anywhere in the payload emptied CMD
251
+ # and the guard failed CLEAN-open).
252
+ c_out=$(python3 -c 'import json;print(json.dumps({"tool_name":"Bash","tool_input":{"command":"git reset --hard # 한글 주석"}}))' | PYTHONIOENCODING=ascii bash "$G" 2>/dev/null); c_rc=$?
253
+ if [ "$c_rc" -eq 0 ] && printf '%s' "$c_out" | python3 -c '
254
+ import json, sys
255
+ d = json.load(sys.stdin)
256
+ assert "DESTRUCTIVE-OP" in d["hookSpecificOutput"]["additionalContext"]
257
+ ' 2>/dev/null; then
258
+ printf ' ✅ %-52s %s\n' "C6 unicode cmd + ascii env: still HIT" OK; pass=$((pass+1))
259
+ else
260
+ printf ' ❌ %-52s rc=%s out=%s\n' "C6 unicode cmd + ascii env: still HIT" "$c_rc" "$c_out"; fail=$((fail+1))
261
+ fi
262
+
221
263
  echo
222
264
  if [ "$fail" -eq 0 ]; then
223
265
  echo "[destructive-pre-gate] ✅ all $pass known pairs hold"; exit 0