@chrono-meta/fh-gate 1.4.87 → 1.4.89
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.
- package/.claude-plugin/marketplace.json +2 -2
- package/CLAUDE.md +36 -0
- package/knowledge/shared/harness-core/claude_md_gate_details.md +37 -0
- package/knowledge/shared/learnings/subagent_invocations_log.yaml +125 -0
- package/package.json +4 -2
- package/plugins/fh-commons/.claude-plugin/plugin.json +1 -1
- package/plugins/fh-meta/.claude-plugin/plugin.json +1 -1
- package/scripts/degrade_direction_scan.sh +34 -2
- package/scripts/gate_pathspec_check.sh +6 -1
- package/scripts/prepush_guard_check.sh +15 -0
- package/scripts/psa_scan_lib.sh +36 -0
- package/scripts/selfcheck.sh +170 -17
- package/scripts/session_close_check.sh +55 -5
- package/scripts/test_degrade_scan_shell_probes.sh +11 -2
- package/scripts/test_dispatch_log_lanes.sh +8 -1
- package/scripts/test_selfcheck_state_lanes.sh +103 -0
- package/scripts/test_session_close_chain_lanes.sh +79 -4
- package/scripts/test_version_lockstep_lanes.sh +82 -0
- package/scripts/universal_guard_check.sh +19 -6
- package/scripts/version_lockstep_check.sh +80 -0
- package/templates/.git-hooks/pre-commit +8 -0
- package/templates/.git-hooks/pre-push +14 -2
- package/templates/degrade_direction_scan.sh +34 -2
- package/templates/regression_guard.sh +25 -0
|
@@ -11,13 +11,13 @@
|
|
|
11
11
|
"plugins": [
|
|
12
12
|
{
|
|
13
13
|
"name": "fh-meta",
|
|
14
|
-
"version": "1.4.
|
|
14
|
+
"version": "1.4.89",
|
|
15
15
|
"description": "Hub meta-operations toolkit — 35 skills + 7 agents. New in 1.4.53: `fh-codex-doctor` (npm bin) — Codex adapter drift scanner; reads the documented M1/M2/M3 skill tier map + skill/agent source and reports codex-native/adapter-required/claude-native/unclassified per unit, wired into `npm test`/`prepublishOnly` (fail-closed on unclassified Claude-native primitives). New in 1.4.49: steel-quench gains Step 0.6 Verdict-Invariance Probe (groundedness axis — a load-bearing judged gate's verdict must track behavior, not rubric phrasing; measured flip-count over cross-family paraphrases; arXiv:2605.06161 Policy Invariance anchor); multi_model_sidecar_strategy §Vendor-native harness (a model is strongest in its own vendor CLI — Claude/CC, GPT/codex, Gemini/Antigravity; a universal router degrades all of them, so it stays an autocomplete/QA sidecar, never orchestration); predelete_check.sh fail-closed rewrite; memory-hygiene A-TMA anchor. New in 1.4.48: phantom-quench + steel-quench gain external frontier anchors (arXiv:2607.02052 package-hallucination; arXiv:2607.02057 prompt-coverage-adequacy); README model-flat claim reframed from a per-release point-curve to structural invariants (operation flattens across tiers; depth tier-order fixed within a generation). New in 1.4.47: onboarding step ① surfaces the Mode D companion-store session-start load in the auto-read salience anchor (previously only in the local binding + rules, so a greeting could skip the load). New in 1.4.46: context-doctor command-output axis (route to rtk/proxy for verbose CLI stdout, complementing .claudeignore; risk-gated to token-scarce envs). New in 1.4.41: context-doctor 2026 trigger vocab (context engineering/rot/collapse) + phantom-citation hardening; hub measurement-integrity-checklist (cross-model measurement pre-flight: display-name pin/reps≥3/discriminating probe). New in 1.4.40: install-wizard queryable-wiki scaffold (INDEX + session-start read + R/W/C ingest). New in 1.4.39: auto-decorrelation (cross-family verifier sidecar recruitment) + video-ingest (capability-routed video ingestion). New in 1.4.x: verify-axis check-class taxonomy (mandatory-pass/measured/judged), no-reinvention Tier-0 inventory, 7-class failure taxonomy, Destructive-Op Gate, Wave-T (Temper), tier-floor governance, Mode D Model Notice, FC consent lane, default-Sonnet guidance. New in 1.3.0: public-surface-audit, field-harvest Mode B auto-trigger, 4-axis gate scope ext. Validated cross-CLI: Claude Code, Codex, Gemini.",
|
|
16
16
|
"source": "./plugins/fh-meta"
|
|
17
17
|
},
|
|
18
18
|
{
|
|
19
19
|
"name": "fh-commons",
|
|
20
|
-
"version": "1.4.
|
|
20
|
+
"version": "1.4.89",
|
|
21
21
|
"description": "Project-agnostic utility skills — 4 skills (convergence-loop · deliberation · mcp-circuit-breaker · token-budget-gate) + 1 agent (quench-challenger). Domain-independent utilities transplantable into any project.",
|
|
22
22
|
"source": "./plugins/fh-commons"
|
|
23
23
|
}
|
package/CLAUDE.md
CHANGED
|
@@ -602,6 +602,42 @@ Default operation is a **standard interactive session**. Agent dispatch (single
|
|
|
602
602
|
|
|
603
603
|
**Why not Agent View by default**: Agent View introduces worktree isolation (blocks settings.json writes, Stop hook timing differs), session context gaps (session card stale content bug), and path friction — with no benefit unless the user is actively managing multiple agent sessions. Parallel agents via `Agent` tool work identically in a standard session.
|
|
604
604
|
|
|
605
|
+
**Fourth reason — gate-integrity in a worktree, and the answer is CONDITIONAL on how `core.hooksPath`
|
|
606
|
+
was set (measured 2026-08-05, both arms).** Do not carry a single verdict here; the two installs
|
|
607
|
+
behave differently:
|
|
608
|
+
|
|
609
|
+
| `core.hooksPath` | Which hook actually runs in a worktree | Consequence |
|
|
610
|
+
|---|---|---|
|
|
611
|
+
| **relative** — `templates/.git-hooks`, the form every FH doc installs (`CHEATSHEET.md`, `.claude/rules/fh_4axis_gate.md`, `install-wizard`, `self_evolution_routine.md`) | the **worktree's own copy** | Editing that copy *inside the worktree* disables the gate for that worktree — measured: neutralized hook → FH-asset commit with no marker succeeded (`rc=0`). The verifier becomes the verified, and the edit is invisible to `git status` in the main tree. |
|
|
612
|
+
| **absolute** — a hand-set full path (this operator's machine; **not** what any doc tells you to run) | the **main tree's copy** | A worktree-local edit has no effect; a known-positive is blocked there exactly as in the main tree (`rc=1`). |
|
|
613
|
+
|
|
614
|
+
An earlier draft of this section reported only the absolute-path arm and declared the
|
|
615
|
+
"worktree bypasses the gates" hypothesis *refuted* — from **n=1 on a non-canonical setting**, with a
|
|
616
|
+
do-not-revisit label attached. The relative-path arm, which is what everyone else runs, reproduces
|
|
617
|
+
the bypass. Freezing a conclusion is a defect when the measurement did not cover the shipped
|
|
618
|
+
configuration.
|
|
619
|
+
|
|
620
|
+
Separately and in **both** arms, the **evidence side** breaks: `tracks/` is gitignored, so it does not
|
|
621
|
+
follow into a worktree, so the Axis 2+3 marker and the Axis 4 `edit_manifest.yaml` are *structurally
|
|
622
|
+
absent* — an FH-asset commit in a worktree fails on evidence it has no way to have. That degrades
|
|
623
|
+
fail-closed (correct), but a gate that **cannot** be satisfied is what trains the bypass. Note the
|
|
624
|
+
hook itself prints `mkdir -p …/tracks/_meta` on that failure, i.e. the actor's own error message
|
|
625
|
+
teaches the marker-creation path — so "just don't fabricate it" is prose sitting under a machine
|
|
626
|
+
instruction pointing the other way.
|
|
627
|
+
|
|
628
|
+
**Therefore: do not commit FH assets from a worktree.** Not "carry the evidence in carefully" — a
|
|
629
|
+
carried marker and a fabricated one are byte-identical, so *marker provenance* is unenforceable by
|
|
630
|
+
construction. Land FH-asset changes from the standard session.
|
|
631
|
+
|
|
632
|
+
**Do not let that unenforceability launder the enforceable part** (caught by an adversarial round on
|
|
633
|
+
the paragraph above, which had used it to do exactly that): *being in a worktree* is trivially
|
|
634
|
+
detectable — `git rev-parse --git-common-dir` differs from `--git-dir` there and matches in the main
|
|
635
|
+
tree — and `templates/.git-hooks/pre-commit` currently has **zero** lines of worktree detection. A
|
|
636
|
+
true statement about one thing (provenance) was standing in for an untested claim about another
|
|
637
|
+
(location). It is left un-mechanized for a *scope* reason, not an impossibility one: measured
|
|
638
|
+
recurrence is 1, below this repo's own N≥3 mechanization threshold. If it recurs, the check is a
|
|
639
|
+
two-line hook addition, not a research problem.
|
|
640
|
+
|
|
605
641
|
**Forbidden responses**: *"I can't do that — I'm not in that project's cwd"* — self-check Agent dispatch first.
|
|
606
642
|
|
|
607
643
|
Mapped paths: check `auto_project_mapping.md` or `find ~/projects -maxdepth 1 -type d` for actuals.
|
|
@@ -196,6 +196,13 @@ destroys live state without anyone noticing. This is why the loss class is calle
|
|
|
196
196
|
plaintext only (encoded tokens out of scope); a line-split backstop catches a token wrapped across
|
|
197
197
|
lines; `PUBLIC_SURFACE_OK=1` overrides and is logged to a gitignored audit trail for the weekly audit.
|
|
198
198
|
Residuals (split-encoding, override-not-populated, override abuse) are documented, not silent.
|
|
199
|
+
**Verdict labelling (2026-08-06)**: with the override absent, a clean scan reports
|
|
200
|
+
`⚠️ PARTIAL — company/companion literals UNMEASURED`, **never `✅ PASS`**. The commit still
|
|
201
|
+
proceeds (reversible surface → advisory degrade), but a run whose operator-literal layer never
|
|
202
|
+
executed may not present the same verdict as one where it did — a missing measurement is not a
|
|
203
|
+
zero. Anchored in `universal_guard_check.sh` as a **pair**: absent override must say `PARTIAL`,
|
|
204
|
+
and the control (override present, no hit) must still say `✅ PASS`, so the label cannot drift
|
|
205
|
+
back to a bare PASS *or* become an unconditional warning.
|
|
199
206
|
- **(c) `npm publish`** — mechanically gated by `scripts/public_surface_scan_files.sh`, wired into
|
|
200
207
|
`prepublishOnly` (`npm run release` also runs it *outside* the lifecycle). Unlike (b) it scans the
|
|
201
208
|
**full content of the exact npm-published file set** (`npm pack --dry-run`), *not* a commit diff — so a
|
|
@@ -204,6 +211,36 @@ destroys live state without anyone noticing. This is why the loss class is calle
|
|
|
204
211
|
or the file set are unresolved, when the parse looks partial, **or when the gitignored operator override
|
|
205
212
|
is absent** — defaults-only would otherwise green-PASS a HIGH company literal on a fresh clone or CI runner.
|
|
206
213
|
|
|
214
|
+
**The git-push surface, and why it was the lenient one (2026-08-06).** (c) blocked on an absent
|
|
215
|
+
override; the `git push` gate in `templates/.git-hooks/pre-push` only warned. Both make content
|
|
216
|
+
public, so two irreversible surfaces were degrading in **opposite directions on the same state** —
|
|
217
|
+
the actual defect, and `git push` (the one nobody publishes through deliberately) was the permissive
|
|
218
|
+
side. The 2026-07-26 reasoning behind that warn was not wrong, it was **unscoped**: an absent
|
|
219
|
+
override in a fresh clone / CI runner / worktree is a legitimate per-operator configuration gap (the
|
|
220
|
+
file is gitignored, so it is absent there *by construction*), and blocking it trains
|
|
221
|
+
`PUBLIC_SURFACE_OK` into a reflex — which disarms the same channel the publish gate depends on.
|
|
222
|
+
|
|
223
|
+
So the warn is **scoped, not reverted**. `psa_detect_operator_context` (`scripts/psa_scan_lib.sh`)
|
|
224
|
+
splits the state: in an **operator-configured checkout** an absent override is *evidence missing
|
|
225
|
+
where evidence is expected* → BLOCK; everywhere else → WARN, exactly as before. The signal is
|
|
226
|
+
`CLAUDE.local.md`, the operator's own gitignored binding file.
|
|
227
|
+
|
|
228
|
+
**Calibration matters here more than the rule** — a second candidate signal, "`tracks/_meta` is
|
|
229
|
+
non-empty", reads as the same test and is not: `tracks/_meta/.gitkeep` and one sibling are
|
|
230
|
+
**tracked**, so a fresh clone satisfies it and would have been blocked, re-shipping the 07-26
|
|
231
|
+
over-block under a new name. It was rejected by measuring it against a known pair, not by reasoning
|
|
232
|
+
about it. **Named residual, deliberately in the under-blocking direction**: an operator who never
|
|
233
|
+
created a `CLAUDE.local.md` stays in the WARN arm. **Second residual, and it is the sharper one**:
|
|
234
|
+
deleting `CLAUDE.local.md` drops this checkout back into the WARN arm, and unlike `PUBLIC_SURFACE_OK=1`
|
|
235
|
+
that bypass **writes no log line**. It is a conscious act on the operator's own file, so it is not a
|
|
236
|
+
weak-model fail-open — but it is a quieter exit than the sanctioned one, which is the wrong ordering
|
|
237
|
+
for a bypass. Not closed here: making it loud means the hook must distinguish "never had one" from
|
|
238
|
+
"had one and lost it", and that needs state the hook does not currently keep. Named rather than
|
|
239
|
+
mechanized, per this repo's own threshold — mechanize on the first measured recurrence.
|
|
240
|
+
Anchored as a pair in `prepush_guard_check.sh`
|
|
241
|
+
(6-a absent override → PASS · 6-b absent override + operator checkout → BLOCK); both arms are
|
|
242
|
+
required, since 6-b passing alone would not distinguish a scoped block from a blanket one.
|
|
243
|
+
|
|
207
244
|
**Named residuals for (c)** — it is a denylist **on the npm CLI path with scripts enabled**, not a
|
|
208
245
|
universal secret-scanner:
|
|
209
246
|
|
|
@@ -944,3 +944,128 @@
|
|
|
944
944
|
entry per the "counts and outcomes, not one stub per dispatch" rule.
|
|
945
945
|
Instrument note: the invocation form matters — `| codex exec -` (stdin) as recorded in
|
|
946
946
|
[[feedback_sidecar_liveness_not_passive_wait]]; the argv form hangs nondeterministically.
|
|
947
|
+
|
|
948
|
+
- date: '2026-08-04'
|
|
949
|
+
agent: codex/gpt-5.5 (cross-family sidecar, auto-decorrelation)
|
|
950
|
+
context: >-
|
|
951
|
+
FH self-dev — two load-bearing gate changes (PR #253 degrade-scan S5 rule, PR #254/#255 Axis-1
|
|
952
|
+
pathspec parity + anchor wiring). Pre-push gate demanded a cross-family leg on both.
|
|
953
|
+
dispatches: '3 (2 completed, 1 killed on timeout and re-run)'
|
|
954
|
+
outcome: accepted
|
|
955
|
+
evidence: >-
|
|
956
|
+
Round 1 (PR #253): REFUTED the author's own fix. The first repair widened the S5 rule with a
|
|
957
|
+
NAMED filter list; codex produced reproduced counter-examples in BOTH directions — still-missed
|
|
958
|
+
real disarms (`grep -Ec`, `grep --count`, `grep -Fcx`, `| cat`) and a NEW false positive the
|
|
959
|
+
repair introduced (`| tail -n +2` yields one line, not a disarm). It also caught that a fixture
|
|
960
|
+
labelled known-NEGATIVE was in fact a positive, because its upstream succeeded and therefore
|
|
961
|
+
never exercised the failure path it claimed to pin. All four re-run locally before acceptance.
|
|
962
|
+
Round 2 (PR #255 wiring): no blocker; one residual raised ("a distribution that SHOULD be
|
|
963
|
+
complete but drops a subject reports SKIP, not FAIL"), which was then TESTED and confirmed
|
|
964
|
+
uncovered by any existing anchor. Recorded as a WEAK signal — the auditor stated it could not
|
|
965
|
+
trace F1-F3 because those implementations were not in the diff it was given, and that residual
|
|
966
|
+
was closed locally by measurement, not by the auditor.
|
|
967
|
+
note: >-
|
|
968
|
+
Third consecutive session where the cross-family leg produced net-new findings the same-family
|
|
969
|
+
round walked past. New this session: the two rounds differed in SIGNAL STRENGTH (refutation vs
|
|
970
|
+
nothing-found), and both markers record which one it was — recording them at equal weight would
|
|
971
|
+
launder a weak pass into a strong one.
|
|
972
|
+
Instrument note: the FIRST attempt derailed into its own sidecar-availability exploration and
|
|
973
|
+
timed out (exit 124, ~7 min). Re-running with an explicit "Do NOT run shell commands, do NOT use
|
|
974
|
+
tools, answer ONLY from the text below" preamble fixed it. Stdin form `codex exec -m gpt-5.5
|
|
975
|
+
--skip-git-repo-check -` as recorded in [[feedback_sidecar_liveness_not_passive_wait]].
|
|
976
|
+
These were CLI sidecars, not Agent-tool spawns, so the SubagentStop hook tallied ZERO — the
|
|
977
|
+
④-e check passed on a true zero while three real dispatches had happened. Named gap: the tally
|
|
978
|
+
is blind to CLI-invoked sidecars.
|
|
979
|
+
|
|
980
|
+
- date: '2026-08-05'
|
|
981
|
+
agent: (unattributed — SubagentStop tally only)
|
|
982
|
+
context: >-
|
|
983
|
+
Close chain of the 2026-08-04 session, which crossed midnight. The tally file recorded ONE
|
|
984
|
+
dispatch dated 2026-08-05; this session made no Agent-tool spawn that it is aware of.
|
|
985
|
+
dispatches: '1 (tallied, origin not attributable)'
|
|
986
|
+
outcome: sustained
|
|
987
|
+
evidence: >-
|
|
988
|
+
Recorded rather than guessed. The substantive sidecar work of this session is the 2026-08-04
|
|
989
|
+
entry above (3 codex CLI dispatches). MEASURED here, after the claim had already been written
|
|
990
|
+
into a commit message and the session card: a `codex exec` call does NOT increment the tally
|
|
991
|
+
(before=1, after=1 on `grep -c '^2026-08-05$'`), and the hook matcher is `SubagentStop`, i.e.
|
|
992
|
+
Agent-tool spawns only. So the CLI-blindness claim holds — but its evidence arrived AFTER
|
|
993
|
+
publication, which is the ordering CLAUDE.md §Instrument-Calibration forbids.
|
|
994
|
+
note: >-
|
|
995
|
+
What produced the 4 tallies dated 2026-08-04 and this 1 dated 2026-08-05 is UNKNOWN. Not
|
|
996
|
+
invented: a fabricated outcome would poison the 60/40 promotion gate worse than a missing one,
|
|
997
|
+
which is the stated reason the hook only tallies and never writes an entry. Open question for
|
|
998
|
+
the next session that touches the tally: which tool events actually fire SubagentStop here.
|
|
999
|
+
|
|
1000
|
+
- date: '2026-08-05'
|
|
1001
|
+
agent: persona-innovator ×2 · quench-challenger ×3 · beginner ×5 (Agent tool) + codex gpt-5.5 ×2 (CLI)
|
|
1002
|
+
context: >-
|
|
1003
|
+
Innovator autopilot session. Two sister-asset absorption scans (innovator); steel-quench Wave 1
|
|
1004
|
+
and Wave 3 on the selfcheck run-twice repair, plus one opus Axis-2 pass on the AGENTS.md change
|
|
1005
|
+
(quench-challenger); five Sonnet-pinned cold-read sims on one AGENTS.md checklist item
|
|
1006
|
+
(beginner); two cross-family audits (codex CLI, tool-use disabled — not counted by the tally
|
|
1007
|
+
hook, which only fires on Agent-tool SubagentStop).
|
|
1008
|
+
dispatches: '10 via Agent tool + 2 CLI sidecars (tally recorded 14 — the delta is unattributed,
|
|
1009
|
+
see the 2026-08-05 entry above about unknown tally sources; not invented here either)'
|
|
1010
|
+
outcome: accepted
|
|
1011
|
+
evidence: >-
|
|
1012
|
+
Every class changed the outcome, which is why this is `accepted` rather than `partial`.
|
|
1013
|
+
quench-challenger: Wave 1 produced 2S/3A/4B and Wave 3 produced 2S/1A/3B on the REPAIR — one of
|
|
1014
|
+
those overturned a judgment already reported to the operator (a "refuted" verdict measured on a
|
|
1015
|
+
non-canonical absolute core.hooksPath; the documented relative-path install reproduces the
|
|
1016
|
+
bypass). The opus Axis-2 pass produced 2S that caused a full revert of the AGENTS.md change.
|
|
1017
|
+
beginner: 5 rounds, each blocking at a DIFFERENT point, each block introduced by the previous
|
|
1018
|
+
round's repair; author self-catch across rounds 1-4 was ZERO. innovator: 4 external repos
|
|
1019
|
+
verified by gh api with known-pair controls, 3 net-new items confirmed by in-repo greps with
|
|
1020
|
+
live controls. codex: round 1 was fed a STALE diff (my error — the target was not frozen) so 2
|
|
1021
|
+
of its findings were already closed and 1 was a misread; round 2 on a fingerprinted target found
|
|
1022
|
+
the BSD `tr -d '[:space:]'` defect that both same-family rounds missed.
|
|
1023
|
+
note: >-
|
|
1024
|
+
Logged at close, after the session_close_check ④-e block caught the omission — the obligation
|
|
1025
|
+
is prose in CLAUDE.md and I did not discharge it during the session despite dispatching 10+
|
|
1026
|
+
agents. That is the same shape the ④-e check was built for. Consolidated per class with counts
|
|
1027
|
+
rather than one stub per dispatch, which the check explicitly permits.
|
|
1028
|
+
|
|
1029
|
+
- date: '2026-08-06'
|
|
1030
|
+
agent: beginner ×5 (Sonnet-pinned cold read) · quench-challenger ×1 (opus Axis-2) — same session as 2026-08-05
|
|
1031
|
+
context: >-
|
|
1032
|
+
The innovator autopilot session crossed midnight; the tally records 14 dispatches under
|
|
1033
|
+
2026-08-06 while the substantive description lives in the 2026-08-05 entry above. These are not
|
|
1034
|
+
additional work — they are the same session's later half: the five cold-read rounds on the
|
|
1035
|
+
AGENTS.md worktree item and the opus adversarial pass that ended it.
|
|
1036
|
+
dispatches: '6 attributable here (tally: 14 under this date; the remainder shares the
|
|
1037
|
+
unattributed-source question already recorded on 2026-08-05)'
|
|
1038
|
+
outcome: accepted
|
|
1039
|
+
evidence: >-
|
|
1040
|
+
The opus Axis-2 pass returned 2S — a recipe that commits to the WRONG BRANCH (git forbids two
|
|
1041
|
+
worktrees sharing a branch, so the main checkout and a worktree are always on different
|
|
1042
|
+
branches, and the recipe never aligned them), and a hook that hands out a marker-fabrication
|
|
1043
|
+
one-liner at the exact moment it blocks. Both were reproduced against the live repo, and the
|
|
1044
|
+
change was REVERTED in full rather than patched. The five beginner rounds each blocked at a
|
|
1045
|
+
different point with author self-catch of zero across rounds 1-4.
|
|
1046
|
+
note: >-
|
|
1047
|
+
Written after the close check refused the push a SECOND time: the first correction logged the
|
|
1048
|
+
session under 2026-08-05 while the check counts today's date, so "logged" and "logged where the
|
|
1049
|
+
check looks" came apart. Recorded rather than back-dated — the split is real (the tally itself
|
|
1050
|
+
splits 34/14 across the boundary) and pretending otherwise would misstate when the work ran.
|
|
1051
|
+
|
|
1052
|
+
- date: 2026-08-06
|
|
1053
|
+
agent: general-purpose ×2 (블라인드 응시자), fh-commons:quench-challenger ×2 (Axis 2)
|
|
1054
|
+
purpose: >-
|
|
1055
|
+
qasp 풀체인 단련 — 출제자/응시자 분리 블라인드 실행 2회(2막 대조 · 3층 난이도)와,
|
|
1056
|
+
FH 마감 체커 fail-open 수리에 대한 Axis 2 적대검증 2라운드(sonnet → at-floor opus).
|
|
1057
|
+
model: sonnet ×3, opus ×1
|
|
1058
|
+
outcome: accepted
|
|
1059
|
+
evidence: >-
|
|
1060
|
+
블라인드 2회가 계기 결함 2건을 직접 산출했다 — 1차는 두 런 차이를 "플레이키"로 오귀속해
|
|
1061
|
+
리포트에 코드 상태 지문이 없음을 드러냈고(#84→PR #86), 2차는 counts 만 대조해 "변화 없음"이라
|
|
1062
|
+
결론내 집계가 개별을 가리는 문제를 드러냈다(→PR #87). 두 오귀속 모두 **주어진 산출물만 보고는
|
|
1063
|
+
그렇게 결론낼 수밖에 없었다** — 그래서 결함이 응시자가 아니라 계기 쪽임이 판별됐다.
|
|
1064
|
+
챌린저 R1(sonnet)은 수리 대상 바로 윗줄에 같은 fail-open 이 남아 있음을 재현으로 잡았고,
|
|
1065
|
+
게이트가 floor 미달로 거부해 R2(opus, at-floor)를 돌리자 R1 이 못 본 4건이 추가로 나왔다
|
|
1066
|
+
(재발견 0건, 전부 실제 재현). known-pair 6/6.
|
|
1067
|
+
note: >-
|
|
1068
|
+
45 dispatch 대 1 로그 항목으로 ④-e 가 통과한 상태였다 — 이 항목이 그 간극을 메운다.
|
|
1069
|
+
카운트가 큰 것은 Explore 등 내부 에이전트가 함께 집계되기 때문이고, 의미 있는 위임은 위 4건이다.
|
|
1070
|
+
★방법론 소득이 결함보다 크다: 출제자=응시자면 "잘 나온다"로 끝나고, 분리하면 매 라운드
|
|
1071
|
+
계기 결함이 나온다. 그리고 floor 는 장식이 아니었다 — sonnet R1 이 놓친 HIGH 를 opus R2 가 잡았다.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chrono-meta/fh-gate",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.89",
|
|
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": [
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"scripts": {
|
|
27
27
|
"prepare": "chmod +x bin/fh-gate.js bin/fh-run.js bin/fh-goal.js bin/fh-codex-doctor.js scripts/fh-gate.sh scripts/fh-run.sh scripts/fh-goal.sh",
|
|
28
28
|
"test": "bash scripts/selfcheck.sh",
|
|
29
|
-
"prepublishOnly": "bash scripts/selfcheck.sh && bash scripts/public_surface_scan_files.sh",
|
|
29
|
+
"prepublishOnly": "bash scripts/version_lockstep_check.sh && bash scripts/selfcheck.sh && bash scripts/public_surface_scan_files.sh",
|
|
30
30
|
"release": "bash scripts/public_surface_scan_files.sh && npm publish"
|
|
31
31
|
},
|
|
32
32
|
"engines": {
|
|
@@ -64,7 +64,9 @@
|
|
|
64
64
|
"scripts/fh-goal.sh",
|
|
65
65
|
"scripts/count_check.sh",
|
|
66
66
|
"scripts/selfcheck.sh",
|
|
67
|
+
"scripts/version_lockstep_check.sh",
|
|
67
68
|
"scripts/test_selfcheck_state_lanes.sh",
|
|
69
|
+
"scripts/test_version_lockstep_lanes.sh",
|
|
68
70
|
"scripts/package_coverage_check.sh",
|
|
69
71
|
"scripts/test_package_coverage_lanes.sh",
|
|
70
72
|
"scripts/test_fh_gate_regressions.sh",
|
|
@@ -164,9 +164,41 @@ for f in "${FILES[@]}"; do
|
|
|
164
164
|
# yields "9\n0" and the `-gt` guard goes silent. Verified as a known pair (both directions) in
|
|
165
165
|
# scripts/test_degrade_scan_shell_probes.sh; narrowing without that anchor would just trade a
|
|
166
166
|
# noisy probe for a blind one.
|
|
167
|
+
#
|
|
168
|
+
# WIDENED 2026-08-04 — the narrowing had gone one step too far, and the proof is that this
|
|
169
|
+
# probe was BLIND to the live instance that bit us the day before. PR #251 fixed
|
|
170
|
+
# `grep -c … | tr -d ' ' || echo 0` in session_close_check.sh ④-e; running this scanner over
|
|
171
|
+
# the pre-#251 file produced ZERO hits (re-measured: fixed rule = 2, old rule = 0).
|
|
172
|
+
#
|
|
173
|
+
# THE ANCHOR WAS ON THE WRONG PROPERTY. The old regex asked "is there an upstream pipe" and
|
|
174
|
+
# required the counter to be the LAST stage. Neither is the discriminator. The discriminator
|
|
175
|
+
# is: **does the failing side still EMIT?** `grep -c` prints "0" and exits 1 on no-match, so it
|
|
176
|
+
# disarms with no upstream pipe at all, and any trailing stage that passes that "0" through
|
|
177
|
+
# keeps the "0\n0" intact.
|
|
178
|
+
#
|
|
179
|
+
# A first fix widened to a NAMED filter list (tr|head|sed|…). Cross-family review (gpt-5.5,
|
|
180
|
+
# 2026-08-04) refuted it with reproduced counter-examples, and the refutation held on
|
|
181
|
+
# measurement in BOTH directions:
|
|
182
|
+
# · still missed, all verified to yield "0\n0": `grep -Ec …`, `grep --count …`, `grep -Fcx …`
|
|
183
|
+
# (combined/long flag forms the `-c` literal never matched) and `| cat`.
|
|
184
|
+
# · newly over-matched: `| tail -n +2` after the counter yields ONE line — not a disarm.
|
|
185
|
+
# Name-based approximation produced under-match and over-match simultaneously. So the trailing
|
|
186
|
+
# chain is no longer enumerated: the rule keys on the COUNTER (grep with a `c` in any flag
|
|
187
|
+
# cluster or `--count`; or `wc` behind a real pipe) plus the `|| echo 0` fallback, and accepts
|
|
188
|
+
# any chain between them.
|
|
189
|
+
#
|
|
190
|
+
# PRESERVED from the 2026-07-28 narrowing, still pinned as known-negatives: (a) `a || b ||
|
|
191
|
+
# echo 0` has no pipeline, and (b) a stage that emits NOTHING on failure (`| jq -r … ||
|
|
192
|
+
# echo 0`) — there the fallback supplies the only line, as intended.
|
|
193
|
+
# KNOWN RESIDUAL, stated not hidden: a trailing stage that can swallow the counter's output
|
|
194
|
+
# (`| tail -n +2`, `| sed -n '/[1-9]/p'`) is now flagged though it is not a disarm. That is a
|
|
195
|
+
# deliberate recall-over-precision trade on an ADVISORY probe, taken because the measured cost
|
|
196
|
+
# of the other direction was a real defect shipping. Revisit if non-fixture FPs appear.
|
|
197
|
+
# Known pair: scripts/test_degrade_scan_shell_probes.sh (9 positives / negatives silent).
|
|
198
|
+
# Reverting this line turns the positive lane red — checked by applying the revert.
|
|
167
199
|
while IFS= read -r m; do
|
|
168
|
-
emit "$f" "${m%%:*}" "S5:pipefail-fallback(sh)" "\`|| echo 0\` fallback
|
|
169
|
-
done < <(grep -nE '[^|]\|[[:space:]]*([a-z]+[[:space:]]+)*(grep[
|
|
200
|
+
emit "$f" "${m%%:*}" "S5:pipefail-fallback(sh)" "\`|| echo 0\` fallback after a COUNTER (grep with -c / --count, or wc) — the counter PRINTS \"0\" and exits non-zero on no-match, so the fallback appends a SECOND line, the value becomes \"0\\n0\", the integer test dies with 'integer expression expected' (stderr only) and the guard goes silent; split it and sanitize: n=\$(...); n=\${n:-0}; case \$n in *[!0-9]*) n=0;; esac. VERIFY the trailing stage can actually emit — if it swallows the count (| tail -n +2), this is a known false positive"
|
|
201
|
+
done < <(grep -nE '(([^|]\|[[:space:]]*([a-z]+[[:space:]]+)*wc[^|]*)|(grep([[:space:]]+-[A-Za-z]*c[A-Za-z]*|[[:space:]]+--count)[^|]*))([^|]*\|[^|]*)*\|\|[[:space:]]*echo[[:space:]]+[\"'"'"']?0' "$f" 2>/dev/null \
|
|
170
202
|
| grep -vE '^[0-9]+:[[:space:]]*#' \
|
|
171
203
|
| grep -vE '#[[:space:]]*noqa[:[:space:]]*degrade')
|
|
172
204
|
fi
|
|
@@ -107,7 +107,12 @@ spec_matches() { # $1 = path
|
|
|
107
107
|
for pair in \
|
|
108
108
|
"plugins/fh-meta/skills/frontier-digest/SKILL_detail.md|tracks/_meta/x.md|PATHSPEC covers SKILL_detail.md" \
|
|
109
109
|
"plugins/fh-meta/skills/frontier-digest/SKILL.md|README.md|PATHSPEC still covers SKILL.md" \
|
|
110
|
-
"CLAUDE.md|CLAUDE.local.md|PATHSPEC covers CLAUDE.md but not the local override"
|
|
110
|
+
"CLAUDE.md|CLAUDE.local.md|PATHSPEC covers CLAUDE.md but not the local override" \
|
|
111
|
+
"scripts/selfcheck.sh|README.md|PATHSPEC covers scripts/*.sh (seam #1 — the HEAVY term already gated it, this array did not)" \
|
|
112
|
+
"scripts/sub/nested.sh|package.json|PATHSPEC covers scripts/ recursively (git/bash globs cross '/')" \
|
|
113
|
+
"templates/regression_guard.sh|package-lock.json|PATHSPEC covers templates/*.sh — i.e. the guard can guard ITSELF" \
|
|
114
|
+
"templates/.git-hooks/pre-commit|.gitignore|PATHSPEC covers the git-hook floor (the hook that hard-blocks commits)" \
|
|
115
|
+
"plugins/fh-meta/agents/challenger.md|tracks/_meta/y.md|PATHSPEC covers agent definitions (seam #3)"
|
|
111
116
|
do
|
|
112
117
|
IFS='|' read -r pos neg label <<< "$pair"
|
|
113
118
|
ok=1
|
|
@@ -166,6 +166,21 @@ check "operator override absent (per-operator) → PASS " "$R" pass \
|
|
|
166
166
|
"refs/heads/f $(cd "$R" && git rev-parse HEAD) refs/heads/f $B"
|
|
167
167
|
rm -rf "$R"
|
|
168
168
|
|
|
169
|
+
# ── Pair 6-b (2026-08-06): the OTHER arm of the same state, which the 07-26 flag could not see.
|
|
170
|
+
# Identical to 6-a except the checkout carries CLAUDE.local.md — the operator's gitignored binding
|
|
171
|
+
# file, absent in every clone/CI/worktree by construction. There the missing override is not an
|
|
172
|
+
# unset per-operator config, it is evidence missing where evidence is expected, on a surface that
|
|
173
|
+
# publishes. This is what `npm publish` already blocked while `git push` waved through; the two
|
|
174
|
+
# irreversible surfaces now degrade in the same direction.
|
|
175
|
+
# The PAIR is what makes it a measurement: 6-a (no CLAUDE.local.md → PASS) is the known-negative and
|
|
176
|
+
# must keep passing, or this is not a scoped block, it is the 07-26 over-block re-shipped. ──
|
|
177
|
+
R=$(newrepo); B=$(cd "$R" && git rev-parse HEAD)
|
|
178
|
+
( cd "$R" && echo ok > g2.md && git add g2.md && git commit -qm g2 >/dev/null \
|
|
179
|
+
&& rm -f .claude/rules/.public-surface-patterns && printf '# operator binding\n' > CLAUDE.local.md )
|
|
180
|
+
check "override absent + operator checkout → BLOCK" "$R" block \
|
|
181
|
+
"refs/heads/f $(cd "$R" && git rev-parse HEAD) refs/heads/f $B"
|
|
182
|
+
rm -rf "$R"
|
|
183
|
+
|
|
169
184
|
R=$(newrepo); B=$(cd "$R" && git rev-parse HEAD)
|
|
170
185
|
( cd "$R" && echo ok > g.md && git add g.md && git commit -qm g >/dev/null \
|
|
171
186
|
&& : > .claude/rules/.public-surface-patterns.defaults ) # present but EMPTY = broken, not unconfigured
|
package/scripts/psa_scan_lib.sh
CHANGED
|
@@ -38,6 +38,9 @@
|
|
|
38
38
|
# PSA_DEFAULTS_OK 1 = committed defaults present, readable, non-empty
|
|
39
39
|
# PSA_OVERRIDE_PRESENT 1 = operator override present, readable, non-empty
|
|
40
40
|
# PSA_BAD_ROWS count of rows dropped as unusable (uncompilable regex, or no TAB)
|
|
41
|
+
# State set by psa_detect_operator_context (separate call — see that function):
|
|
42
|
+
# PSA_OPERATOR_CONTEXT 1 = an operator-configured checkout, where an absent override is missing
|
|
43
|
+
# evidence rather than a legitimate fresh-clone absence
|
|
41
44
|
# A caller that treats PSA_BAD_ROWS>0 or PSA_DEFAULTS_OK=0 as "clean" has a hole: those states mean
|
|
42
45
|
# the instrument is incomplete, and an incomplete instrument cannot certify anything.
|
|
43
46
|
|
|
@@ -115,6 +118,39 @@ PSA_ROWS
|
|
|
115
118
|
PSA_STREAM="$valid"
|
|
116
119
|
}
|
|
117
120
|
|
|
121
|
+
# psa_detect_operator_context <repo_root> → sets PSA_OPERATOR_CONTEXT (0/1)
|
|
122
|
+
#
|
|
123
|
+
# Answers ONE question: is an absent operator override a *legitimate absence* (fresh clone, CI
|
|
124
|
+
# runner, worktree — the file is gitignored, so it is absent there by construction) or *evidence
|
|
125
|
+
# missing where evidence is expected* (a checkout the operator has configured, whose literals should
|
|
126
|
+
# be there and are not)?
|
|
127
|
+
#
|
|
128
|
+
# 2026-07-26 changed pre-push from BLOCK to WARN on an absent override, and that change was right for
|
|
129
|
+
# the first case: this repo's own selfcheck flagged the block as over-firing, and over-blocking trains
|
|
130
|
+
# PUBLIC_SURFACE_OK into a reflex, which disarms the same channel the publish gate depends on. It was
|
|
131
|
+
# wrong for the second case, which the flag could not distinguish. This function is that distinction —
|
|
132
|
+
# it does not reopen the reverted block, it scopes it.
|
|
133
|
+
#
|
|
134
|
+
# Signal: CLAUDE.local.md — the operator's own gitignored binding file. Chosen because it is
|
|
135
|
+
# gitignored (verified: `.gitignore:17`), so no clone, CI checkout or worktree carries it.
|
|
136
|
+
#
|
|
137
|
+
# CALIBRATED, and one candidate was REJECTED by that calibration: "tracks/_meta is non-empty" looks
|
|
138
|
+
# like the same signal and is not — `tracks/_meta/.gitkeep` and one more file are TRACKED, so a fresh
|
|
139
|
+
# clone satisfies it and would have been blocked. The discriminator was measured against a known
|
|
140
|
+
# pair before it was trusted, not reasoned about.
|
|
141
|
+
#
|
|
142
|
+
# NAMED RESIDUAL (deliberate, and in the safe direction): an operator who never created a
|
|
143
|
+
# CLAUDE.local.md stays in the WARN arm. This under-blocks rather than over-blocks — the failure mode
|
|
144
|
+
# this scoping exists to avoid is the reflex, not the miss, and the push gate's content scan still
|
|
145
|
+
# runs on the committed defaults in that arm.
|
|
146
|
+
psa_detect_operator_context() {
|
|
147
|
+
local root="$1"
|
|
148
|
+
PSA_OPERATOR_CONTEXT=0
|
|
149
|
+
[ -n "$root" ] || return 0
|
|
150
|
+
[ -f "$root/CLAUDE.local.md" ] && PSA_OPERATOR_CONTEXT=1
|
|
151
|
+
return 0
|
|
152
|
+
}
|
|
153
|
+
|
|
118
154
|
# psa_scan_tagged — reads "path<TAB>content" lines on stdin, prints one line per reportable hit.
|
|
119
155
|
# Returns 0 = nothing reportable, 1 = at least one hit.
|
|
120
156
|
#
|