@chrono-meta/fh-gate 1.4.77 → 1.4.78

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.
@@ -0,0 +1,233 @@
1
+ # AGENTS.md Runtime Details
2
+
3
+ > **Load: on demand.** `AGENTS.md` is the always-loaded runtime entry point. Read only the section
4
+ > named by its imperative pointer.
5
+
6
+ ## §Architecture-and-output-routing
7
+
8
+ forge-harness has two distinct layers:
9
+
10
+ | Layer | Contents | Compatibility |
11
+ |---|---|---|
12
+ | **Methodology** | `tracks/`, `knowledge/`, `SKILL.md` documents, session protocols | Model-agnostic |
13
+ | **Automation** | Plugin agents, hooks, slash commands, `CLAUDE.md` rules | Claude Code-native |
14
+
15
+ FH agents ship under `plugins/*/agents/` through the plugin channel. `.claude/agents/` is the
16
+ field-project local/override slot, not FH's shipping location. Skills straddle both layers: their
17
+ methodology is portable, while their automatic invocation is Claude-native.
18
+
19
+ The methodology layer is Codex-compatible beta. Gemini, Codex, and other runtimes can apply it by
20
+ replacing hooks and native dispatch with manual invocation.
21
+
22
+ Directory names do not determine publication residency:
23
+
24
+ | Content | Default destination |
25
+ |---|---|
26
+ | Reusable methodology, docs, skills, public guidance, polished external conclusions | Public mirror: `knowledge/`, `plugins/`, `docs/` |
27
+ | Raw signals, operator observations, private validation, handoffs, paper drafts, PR-background reasoning | Private companion store or local-only |
28
+
29
+ In a workspace pairing a public mirror with a private companion store, preserve repository ownership
30
+ even when both are locally available. Treat observational or operator-specific material as
31
+ private-first and promote only the polished result.
32
+
33
+ ## §Sidecar-routing-and-waiting
34
+
35
+ A sidecar is a capability-routing layer, not a second harness or co-governor. Gemini/Antigravity is
36
+ suited to breadth and multimodal work. Codex's primary FH role is repo-grounded audit: file reads,
37
+ source-close grep, diff/patch review, gate execution, and phantom/backtrace. A Codex session with
38
+ Browser or Chrome connectors may also take live web-flow automation.
39
+
40
+ Sidecars are Bash/adapter invocations coordinated inline; they bypass plugin agent dispatch and this
41
+ registry. Route Codex to repo-grounded audit by default, not breadth, discovery, or design-depth work.
42
+
43
+ After dispatch, wait mechanically:
44
+
45
+ ```bash
46
+ printf '%s' "$prompt" | bash scripts/sidecar_wait.sh out.txt 900 -- codex exec -m gpt-5.5 -
47
+ ```
48
+
49
+ Interpret only the typed verdict:
50
+
51
+ | Verdict | Meaning |
52
+ |---|---|
53
+ | `SIDECAR_VERDICT=COMPLETE exit=0 bytes=N` | The process completed; read the output file |
54
+ | `SIDECAR_VERDICT=TIMEOUT waited=Ns bytes=N` | The process is still alive; this is not a result |
55
+ | `SIDECAR_VERDICT=EMPTY exit=0` | The completed process returned no content |
56
+
57
+ A live process and a completed empty process can both show a zero-byte file. Never judge state by
58
+ file inspection. Canonical authority and waiting doctrine:
59
+ `knowledge/shared/harness-core/multi_model_sidecar_strategy.md §Runtime Authority` and
60
+ `plugins/fh-meta/skills/auto-decorrelation/SKILL.md §S-1b`.
61
+
62
+ ## §Mandatory-checklist-procedures
63
+
64
+ ### FH asset changes
65
+
66
+ Read `.claude/rules/fh_4axis_gate.md` before changing an FH asset. It defines the mandatory
67
+ Backward, Adversarial, Forward, and Record axes, marker fields, lightweight exception, and
68
+ substantive carve-out. The pre-commit hook blocks commits that lack required evidence.
69
+
70
+ When running `templates/regression_guard.sh`, prefer `REGRESSION_GUARD_RESULT_FILE=<path>` and read
71
+ `result=pass|review|block|skip|error`. Without that environment variable, read the typed
72
+ `REGRESSION_GUARD_RESULT=` stdout line. Exit 0 alone cannot distinguish PASS from SKIP.
73
+
74
+ ### Company residency
75
+
76
+ Keep raw company source, secrets, hostnames, internal names, stack traces, and unredacted findings
77
+ local. Outbound requests may contain only a sanitized summary. An exception requires explicit
78
+ operator approval plus a gitignored audit note. Canonical procedure:
79
+ `CLAUDE.md §Field-Harness Diagnostic`.
80
+
81
+ ### Author exposure
82
+
83
+ Before completing a material deliverable, use `agent-composer §Author-Exposure Table`:
84
+
85
+ | Blind spot | Lens |
86
+ |---|---|
87
+ | Cold entry | `beginner` |
88
+ | Everyday friction | `main-player` |
89
+ | Outside currency | `expert` |
90
+ | Optimistic self-verification | `challenger` plus cross-family evidence |
91
+ | Rebuilding an existing asset | `fact-checker` |
92
+ | Ungrounded numbers or references | `phantom-quench` |
93
+ | Unclear | `challenger` |
94
+
95
+ Run an agent lens through `fh-run` or a direct `codex exec` reading the agent spec. The result remains
96
+ evidence for the governor to source-close.
97
+
98
+ ### Intent marshaling
99
+
100
+ For ordinary work requests, read
101
+ `knowledge/shared/harness-core/intent_marshaling_general_work.md` before applying its ladder or when a
102
+ capability gap appears. The required loop is: restate deliverable and doneness; enumerate installed
103
+ and mapped capability with trust tiers; compose and run reversible FH-native work; cite the scan
104
+ before declaring a gap; search the internal registry, then external capability, then synthesize
105
+ in-session; apply the Author-Exposure check to material output.
106
+
107
+ Marshaling never upgrades trust. Non-FH sibling `ask-tier` capability remains propose-only.
108
+ Send, post, deploy, delete, and payment retain their own gates. Installing external capability routes
109
+ to `plugin-recommender` HITL; persisting a synthesized skill routes to the New-Skill gate.
110
+
111
+ ### Measurement integrity
112
+
113
+ Read `knowledge/shared/harness-core/measurement-integrity-checklist.md` before relying on a scan,
114
+ checker, or metric. Demonstrate that the instrument separates one known-positive target from one
115
+ known-clean target, and inspect at least one hit before stating a count. Report no-target and
116
+ mid-run failure as `UNMEASURED`. Treat all-pass or all-fail output as an instrument warning.
117
+
118
+ ### Irreversible surfaces
119
+
120
+ The Pre-Publish and Destructive-Op gates in `CLAUDE.md` fire on intent rather than file paths.
121
+ Read the relevant gate before any publish, delete, or history rewrite. The pre-push hook provides
122
+ only the git-side mechanical backstop.
123
+
124
+ ## §Invocation-patterns
125
+
126
+ ### Single agent
127
+
128
+ Ask for the required lens directly, for example: "Analyze this SKILL.md for structural flaws before
129
+ I commit it." Claude may description-dispatch `quench-challenger`; a non-Claude runtime invokes the
130
+ agent through the adapter.
131
+
132
+ ### Parallel independent work
133
+
134
+ Dispatch two or more agents concurrently only when their tasks are independent, such as a
135
+ `fact-checker` duplicate scan and a `persona-innovator` naming-gap scan. The orchestrator integrates
136
+ their evidence after both finish.
137
+
138
+ ### Wave composition
139
+
140
+ For complex dependent work, use `agent-composer`: Wave 0 reconnaissance, Wave 1 execution, then
141
+ Wave 2 synthesis.
142
+
143
+ ## §Codex-entry-points
144
+
145
+ Read a skill workflow directly:
146
+
147
+ ```bash
148
+ cat plugins/fh-meta/skills/steel-quench/SKILL.md
149
+ ```
150
+
151
+ Prefer the runtime adapter:
152
+
153
+ ```bash
154
+ FH_BACKEND=codex npx --package @chrono-meta/fh-gate fh-run \
155
+ --skill steel-quench \
156
+ --file path/to/artifact.md
157
+
158
+ FH_BACKEND=codex npx --package @chrono-meta/fh-gate fh-run \
159
+ --agent fh-commons:quench-challenger \
160
+ --file path/to/artifact.md
161
+ ```
162
+
163
+ Direct headless fallback:
164
+
165
+ ```bash
166
+ cat plugins/fh-meta/skills/steel-quench/SKILL.md path/to/artifact.md \
167
+ | codex exec -m gpt-5.5 -
168
+ ```
169
+
170
+ `codex exec -m gpt-5.5 -` reads stdin headlessly. Interactive `npx @openai/codex` requires a TTY and
171
+ is not the headless substitute.
172
+
173
+ ## §Compatibility-tiers
174
+
175
+ | Tier | Definition | Examples |
176
+ |---|---|---|
177
+ | **M1 — Full** | No Claude-native dependency | `token-budget-gate`, `asset-placement-gate`, `phantom-quench`, `deep-clarify`, `convergence-loop` |
178
+ | **M2 — Partial** | Core works; native agent/slash-command steps need adaptation | `deliberation`, `steel-quench`, `harness-doctor`, `context-doctor`, `sim-conductor`, `harvest-loop` |
179
+ | **M3 — Claude-only** | Requires a Claude hook or session-scoped dispatch | `goal-quench`, `hub-cc-pr-reviewer`, `install-wizard` |
180
+
181
+ **Which phase needs adapting** — the operative half of the M2/M3 rows. Without this a tier label
182
+ tells a non-Claude runtime that a skill is "partial" but not *where* to intervene, which is the only
183
+ thing it can act on. (Restored 2026-07-30 during review of the salience split: the rows survived the
184
+ move, these per-skill cues did not, and they existed in no other file.)
185
+
186
+ | Skill | Runs unchanged | Needs substitution |
187
+ |---|---|---|
188
+ | `steel-quench` | Waves 1–3 | the `quench-challenger` agent step |
189
+ | `harvest-loop` | the git-scan phase | PR auto-proposal |
190
+ | `deliberation` | proposal/synthesis structure | Mediator and Jury agent steps |
191
+ | `goal-quench` (M3) | — | Phase 3 depends on a Claude Stop hook |
192
+ | `hub-cc-pr-reviewer` (M3) | — | needs Claude session context |
193
+ | `install-wizard` (M3) | — | writes `settings.json` |
194
+
195
+ For M2, replace `Agent(subagent_type=...)` and slash-command steps with `fh-run` or direct
196
+ `codex exec` reading the relevant spec.
197
+
198
+ Use Codex native goal/session control when available. FH's portable role is the post-goal quality
199
+ gate (`fh-gate`). `fh-goal` is for non-interactive one-shot runs followed automatically by
200
+ `fh-gate`; it does not replace native goal control.
201
+
202
+ ## §Beta-removal
203
+
204
+ | Condition | Status |
205
+ |---|---|
206
+ | Known-limitations document published at `docs/codex-compat.md` | Done |
207
+ | At least 5 externally validated M1 skill runs from non-authors | Pending |
208
+ | At least 1 external Codex user confirms methodology reproduction | Pending |
209
+ | README badge removes `beta` | Blocked on the external conditions |
210
+
211
+ Internal author validation does not satisfy the external conditions. The author validated
212
+ `phantom-quench` against a phantom-seeded fixture and `asset-placement-gate` against a duplicate-skill
213
+ proposal on 2026-06-04. See `docs/codex-compat.md` for limitations and validation details.
214
+
215
+ Report external validation through an issue on `chrono-meta/forge-harness` with the
216
+ `codex-validation` label.
217
+
218
+ ## §Adding-agents
219
+
220
+ Before adding an agent:
221
+
222
+ 1. Run `asset-placement-gate` and confirm the role does not duplicate an existing asset.
223
+ 2. Use a plain description with no self-marketing language.
224
+ 3. Define at least one explicit `Done When` condition.
225
+ 4. Provide at least three natural-language trigger examples.
226
+ 5. Make the agent independently executable or document its dependencies.
227
+ 6. Add the canonical spec under the correct `plugins/*/agents/` directory.
228
+ 7. Synchronize `AGENTS.md` and `.claude/registry/agent_cards.json`.
229
+
230
+ Before commit, apply `.claude/rules/fh_4axis_gate.md §FH Improvement 4-Axis Auto-Gate` and read
231
+ `knowledge/shared/rules/operations.md §Sub-agent Operations`. After at least two weeks of use,
232
+ strengthen an agent when accepted invocations are at least 60%; redefine or deprecate it when
233
+ rejected invocations are at least 40%.
@@ -716,7 +716,7 @@ Missing any layer = compression risk. (Path conventions adapt per project — se
716
716
  ## References
717
717
 
718
718
  - `README.md §Architecture — 2-layer design` — sidecar note in Automation layer section
719
- - `AGENTS.md §2-Layer Architecture Context` — sidecar note distinguishing Bash invocation from agent dispatch
719
+ - `knowledge/shared/harness-core/agents_md_runtime_details.md §Sidecar-routing-and-waiting` — sidecar note distinguishing adapter invocation from agent dispatch
720
720
  - FH paper (Zenodo DOI: 10.5281/zenodo.20397566, arXiv: submit/7657304) — harness-as-durable-layer thesis
721
721
  - A sister-harness `sidecar-orchestrator` SKILL.md (2026-06-01) — gh copilot + corporate endpoint + 3-tier fallback + 3-layer persistence
722
722
  - arXiv:2605.26302 AgingBench — compression aging defense rationale
@@ -580,3 +580,17 @@
580
580
  withheld by a "standing session instruction"; the operator pointed out they never said that — it is
581
581
  session-level SYSTEM configuration, and the wording blurred the source. Corrected in the markers and
582
582
  here. The operator then explicitly requested the agent, which satisfies the configured exception.
583
+ - date: 2026-07-30
584
+ agent: fh-meta:challenger
585
+ task: "Axis-2 adversarial review of the per-node floor check + install-wizard SessionStart gap (5 rounds + 1 confirmation)"
586
+ mode: isolated Agent, opus, same-family (no cross-family sidecar reachable — disclosed in the marker, not silently passed)
587
+ outcome: accepted
588
+ evidence: "S-grade 6 → 5 → 4 → 2 → 0, CONVERGED. Caught, among others: a false 'tracked settings.json survives a clone' claim; two of this repo's own gate scripts already red; a group-level hook merge that deleted a user's own hook; a broken machine going permanently silent after one report; foreign (husky) hooks counted as FH floors; and a Mode D applicability gate that silenced the exact incident that started the work."
589
+ note: "Its standing prescription — write the known-pair lanes BEFORE the fix — was adopted at round 4 and ended three consecutive rounds of fix-induced regressions (0 in rounds 4, 5, confirmation). One prescription was REJECTED with measured grounds (widening the package-coverage predicate: 25 new hits, 24 artifacts), and the challenger then correctly showed my rejection rationale was itself circular; the file now labels that check UNQUANTIFIED."
590
+ - date: 2026-07-30
591
+ agent: general-purpose (Sonnet-pinned target-tier sim)
592
+ task: "Blind fresh-session simulation: would a new machine end up correctly wired if the user opens with a task, not a greeting?"
593
+ mode: isolated Agent, sonnet, run BEFORE the fixes
594
+ outcome: accepted
595
+ evidence: "Two findings, both closed. (1) The node check was unreachable on the fresh-machine case because it lived in a gitignored registration — drove the split into scripts/fh_node_check.sh + a tracked snippet. (2) The wizard wrote a literal '<your-store>' placeholder and reported success, leaving a dead hook — the real BE_DIR is now baked in at write time."
596
+ note: "Sonnet tier was the right instrument here: both findings are about instructions being followed literally, which is exactly where a weaker tier diverges from an author's intent."
@@ -28,136 +28,9 @@ This loop fills that gap. It is deliberately thin: it **reuses** existing parts
28
28
 
29
29
  Runs at field-session close, **riding `field-harvest` Mode B** — no new trigger, never an interception. One pass per session.
30
30
 
31
- - **READ** (session start / proposal time): apply UAP — suppress a skill proposal rejected 3+ times, apply any **standing consent** granted per §Consent promotion below (an `accepted` record on its own still carries **no** positive auto-action — acceptance alone never auto-runs anything; only a granted standing consent does), default to the preferred tier, mute cadence nags the user always declines, and **apply capability-escalation consent** (`sidecar_consent`/`floorup_consent` `declined` → route to the Sonnet / Tier-3 floor, recommend-only, no re-nag; `unset` → ask-once at first need per the consent protocol). (Tier note: the UAP tier default is a session-depth setting; the Mode D model notice is model-only + advisory and never overrides it.)
31
+ - **READ** (session start / proposal time): apply UAP — suppress a skill proposal rejected 3+ times (an `accepted` record carries **no** positive auto-action — the skill simply stays surfaced; do not auto-run on acceptance), default to the preferred tier, mute cadence nags the user always declines, and **apply capability-escalation consent** (`sidecar_consent`/`floorup_consent` `declined` → route to the Sonnet / Tier-3 floor, recommend-only, no re-nag; `unset` → ask-once at first need per the consent protocol). (Tier note: the UAP tier default is a session-depth setting; the Mode D model notice is model-only + advisory and never overrides it.)
32
32
  - **WRITE** (session close): update outcome counts + new friction points.
33
33
 
34
- ## Consent promotion (accept-side) — repeated approval must offer to stop asking
35
-
36
- The suppression rule above is **one-directional**: repeated *rejection* changes FH's behavior, repeated
37
- *approval* changed nothing, so a user who approves the same class every time keeps paying the same
38
- prompt forever. That asymmetry is the defect this section closes. It is the general-user form of the
39
- elastic-granularity principle already granted to the operator (`feedback_hitl_elastic_granularity_cto`):
40
- **the floor is fixed, the approval grain is not.**
41
-
42
- **Promotion is not the mirror image of suppression.** Rejection suppresses a *proposal* — a wrong
43
- suppression costs one manual re-ask. Acceptance authorizes an *action* with the user absent — a wrong
44
- grant has side effects. Symmetric machinery over asymmetric stakes is itself the error (cross-family
45
- review, 2026-07-29). So the accept side carries three things the reject side does not: an **explicitly
46
- scoped** class, an **expiry**, and a **separate confirmation** that is not the same "yes" that approved
47
- the action in front of the user.
48
-
49
- **Classes are declared, never minted at runtime** (this is what makes the trigger mechanical). A class
50
- is promotion-eligible only if it exists in the **consent-class registry**, `tracks/_meta/consent_classes.yaml`,
51
- declaring: `owner` (gate/skill) · `mode` · `target scope` · `capabilities` (read / local-write /
52
- network / dispatch / repo-mutation) · `sinks` (what downstream surface the action can reach). A session
53
- may **propose** a registry entry (HITL, like any asset) but may **not** invent one mid-run and start
54
- counting against it. *Origin: without a registry the guard below did not bind — the class could simply
55
- be recorded over-broad at birth (`sim-conductor` rather than `dispatch read-only sim on a local
56
- artifact`), and no after-the-fact widening rule can catch a class that was born wide.*
57
-
58
- **Registry authorship is itself a capture surface** (cross-family round 2): closing runtime minting
59
- still leaves the agent drafting a broad entry, framing it as housekeeping, and later enjoying the
60
- approved breadth. So a proposed entry is promotion-eligible only after it carries (a) an explicit
61
- `excludes:` list of neighbouring actions the class must **not** cover, (b) 2+ **adversarial examples** —
62
- concrete actions a reader might assume are inside and that the author asserts are outside — and (c) a
63
- review by something other than the proposing session (the human, or a cross-family auditor). A class
64
- definition is reviewed as a **grant of future autonomy**, not as a config row.
65
-
66
- **Mechanical floor**: `scripts/consent_registry_check.sh` — joins `standing_consent` against the
67
- registry and enforces schema, eligibility soundness (a class naming an irreversible or unlisted sink
68
- **cannot** declare itself promotable), registration, expiry, and recorded scope. Missing registry → N/A
69
- + promotion disabled; unparseable → fail-closed. Run it before trusting any grant; the prose above is
70
- the salience layer over this check, not the enforcement.
71
-
72
- **Trigger**: the same registered class recorded `accepted` **3 consecutive times**, counted across
73
- sessions from the UAP outcome log. Refinements that keep the count honest:
74
- - *Consecutive* means consecutive **within that class's own entries**; other classes interleaved do not
75
- break the streak, a single `rejected` or `modified` does. An approval the user altered before granting
76
- is logged `modified`, never `accepted`.
77
- - **Only a promotion-eligible approval prompt counts** — one user gesture, one entry. **Retries of the
78
- same operation count once**, and one "yes, do those three" is **one** approval, not three. Ordinary
79
- supervised retry ("응, 다시 해봐" ×3) is not durable consent and must never reach the threshold.
80
- - The running count is **visible to the user at each approval** (`1/3` · `2/3` · `3/3`), so the offer is
81
- never the first time they learn a streak was being tallied.
82
-
83
- **Action — offer once, with the evidence in the offer**:
84
-
85
- > "`<class>` 을 3번 연속 승인했다 (`<date1>`, `<date2>`, `<date3>` — 각각 `<one-line what was approved>`).
86
- > 범위: `<mode · target · capabilities · sinks>`. 앞으로 `<N>`일간 안 묻고 진행할까?
87
- > (언제든 '다시 물어봐')"
88
-
89
- The offer **quotes the three approvals and the exact scope**; a grant the user cannot audit is not
90
- consent. Then:
91
-
92
- - **granted** → write `standing_consent: <class>: {granted: <date>, expires: <date+N>, effects: [...]}`.
93
- Later instances run unprompted, each **states in one line what it did**, and each **appends a durable
94
- entry to `tracks/_meta/consent_runs.log`**. *Post-action chat notice is not a control* (cross-family
95
- round 2): a line the user scrolls past has stopped the prompt without replacing it. The chat line is
96
- courtesy; the log is the audit surface, and it is the reason standing consent may cover only actions
97
- that are **recoverable and locally reviewable** — an unrecoverable action was already excluded by the
98
- floor, and an unreviewable one is excluded here.
99
- **Expiry is not optional** — at expiry the consent lapses to `unset` and the class is asked again;
100
- standing consent is a renewable lease, not a transfer of the decision.
101
- - **declined** → write `declined`. **Never ask again for that class version** — the same no-re-nag rule
102
- as muted cadence reminders. *Scoped to the version, not forever*: a user may decline because the
103
- timing was wrong, and permanent suppression with no renewal path is its own defect. A re-offer is
104
- allowed only when the class is **materially narrowed** (a new registry version with strictly smaller
105
- scope) or the user asks. Re-offering the same scope is a nag.
106
- - Revocation is always available and never negotiated: "다시 물어봐" / "revoke" → `unset`.
107
-
108
- **Floor — what never promotes (규약; this is the whole constraint)**: promotion is available only where
109
- the *protocol still passes*. Applicability is decided **mechanically, from the registry entry — never by
110
- the running session's judgment**, because the session that wants to stop being asked is the worst
111
- possible arbiter of whether it may. A class never promotes, at any count, when:
112
-
113
- 1. its `sinks` include an **irreversible surface** — publish / go-public, delete, history-rewrite, or any
114
- surface guarded by a fail-closed Irreversibility gate (`CLAUDE.md §Irreversibility Gates`); **or**
115
- 2. it **feeds, queues, or enables** such a sink even though it does not itself cross one — **sink
116
- tainting**. Reversible steps compose into irreversible outcomes: "edit workflow config" + "write
117
- release notes" + "create local commit" are each locally reversible, and a CI workflow publishes the
118
- result. Taint propagates along that chain; the floor is inherited, not re-derived per step; **or**
119
- 3. its irreversibility is **unknown or not yet classified** → **no promotion, ask**. Unknown is not
120
- reversible. (An unregistered class is by definition unknown, which is why 1–3 collapse to a single
121
- mechanical test against the registry.)
122
-
123
- Their cost is not the prompt, it is that the thing cannot be undone — grain-independent by construction.
124
- **The promotion offer itself is HITL**: this section grants no path to auto-approval the user did not
125
- answer "yes" to once, knowingly and in scope, which is why it does not contradict the "acceptance alone
126
- never auto-runs" rule above.
127
-
128
- **Degrade direction (fail-closed), three ways**: no UAP (ephemeral/cloud session, wiped profile) → **no
129
- promotion, keep asking**. No registry entry → **no promotion, keep asking**. Expired or unparseable
130
- record → **`unset`, keep asking**. A missing consent record is never `granted` — an absent measurement is
131
- not a yes.
132
-
133
- **Named residual — the ledger is self-attested (cross-family, 2026-07-29, HIGH, NOT closed).** The UAP
134
- outcome log is written by the same agent that benefits from fewer prompts, and it is gitignored, so there
135
- is no ordinary review trail. Nothing in this section fixes that; the honest mitigations only *narrow* it:
136
- consent events are **append-only** (an entry is superseded, never edited or deleted), the offer must
137
- **quote the three approvals verbatim** so a user can catch a fabricated streak at the one moment it
138
- matters, and every unprompted run **announces itself**. A real fix requires the consent record to be
139
- produced by the approval channel rather than by the consuming agent — outside this rule's reach. **Until
140
- then, treat every standing consent as auditable-by-the-user-only, and never widen the mechanism's scope
141
- on the assumption the ledger is trustworthy.**
142
-
143
- **Consent binds to the action's SHAPE, not its label** (found by blind target-tier sim, 2026-07-29):
144
- a class name is a string, and the action behind it can change after consent is granted. A sim that
145
- merely returned a report when you said "stop asking" may, ten sessions later, write into shared memory
146
- and trigger a downstream commit — same label, different blast radius, HITL skipped. So a grant records
147
- **what it was granted for**: the owning gate/skill, and the set of **effect classes** the action had at
148
- grant time (reads · local writes · network · dispatch · repo-mutation) **plus the `target` scope and
149
- the `sinks` fingerprint**. On any later run whose fingerprint is **not a subset** of the granted one,
150
- standing consent **reverts to `unset` and asks again**, naming what widened. Effect classes alone are
151
- too coarse to be the whole test (cross-family round 2): "local write" stays "local write" whether the
152
- target is a scratch report or a policy file — the *target* is where that drift shows, which is why it
153
- is part of the fingerprint and not merely descriptive. Widening is the trigger; narrowing is not. This is the same discipline as the
154
- byte-identity anchors used elsewhere: consent is pinned to a fingerprint, not to a name, because
155
- **the name is exactly what does not change when the danger does.**
156
-
157
- **Guard against class inflation**: the class recorded is the *narrow* one that was actually approved
158
- 3×, never a widened parent. Three approvals of "dispatch a Sonnet sim" do not grant "dispatch any
159
- agent" (`feedback_scope_widening_needs_grounding` — widening judgments get no free pass).
160
-
161
34
  ## Generalization gate → reverse-PR funnel
162
35
 
163
36
  This is the operator's **"원본 반영 가치"** criterion made mechanical. Split each UAP learning:
@@ -173,8 +46,6 @@ This is the operator's **"원본 반영 가치"** criterion made mechanical. Spl
173
46
 
174
47
  - **UAP WRITE ran** at field-session close (or was correctly skipped — absent profile / ephemeral session). *Check class: mandatory-pass (binary — did Step 5-B.1 execute or log a skip reason).*
175
48
  - **UAP READ applied** at session start / proposal time when a profile exists (preferred tier defaulted, 3×-rejected proposals suppressed, declined cadence nags muted). *Check class: judged, pair: the target-tier blind sim below.*
176
- - **A class at 3 consecutive `accepted`** was either offered promotion once, or correctly not offered with the reason recorded (irreversible surface · already `declined` · no UAP). *Check class: measured — the consecutive count is read off the UAP outcome log, not recalled.*
177
- - **Every `standing_consent` key resolves to a registry entry whose `sinks` are irreversible-free, and is unexpired.** *Check class: mandatory-pass (binary — join `standing_consent` keys against `consent_classes.yaml`, reject any key that is unregistered, taint-reachable to an irreversible sink, or past `expires`; any hit is a defect, not a judgment call).*
178
49
  - **No domain content** entered the UAP this session. *Check class: judged, pair: phantom/content scan of the UAP diff.*
179
50
 
180
51
  ## Guards
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chrono-meta/fh-gate",
3
- "version": "1.4.77",
3
+ "version": "1.4.78",
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": [
@@ -107,9 +107,6 @@
107
107
  "scripts/memory_nearcheck.py",
108
108
  "scripts/sidecar_wait.sh",
109
109
  "scripts/test_sidecar_wait_stdin.sh",
110
- "scripts/consent_registry_check.sh",
111
- "scripts/test_consent_registry.sh",
112
- "templates/consent_classes.yaml.example",
113
110
  "scripts/test_session_close_lanes.sh",
114
111
  "scripts/test_card_drift_probe.sh",
115
112
  "scripts/universal_guard_check.sh",
@@ -128,6 +125,11 @@
128
125
  "README.md",
129
126
  "README.ko.md",
130
127
  "README.zh.md",
131
- "README.ja.md"
128
+ "README.ja.md",
129
+ "scripts/fh_node_check.sh",
130
+ "templates/settings.SessionStart.snippet.json",
131
+ "scripts/test_node_check_lanes.sh",
132
+ "scripts/sidecar_calibrate.sh",
133
+ "scripts/test_sidecar_calibrate_lanes.sh"
132
134
  ]
133
135
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fh-commons",
3
- "version": "1.4.77",
3
+ "version": "1.4.78",
4
4
  "engines": {
5
5
  "claudeCode": ">=1.0.0"
6
6
  },
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fh-meta",
3
- "version": "1.4.77",
3
+ "version": "1.4.78",
4
4
  "engines": {
5
5
  "claudeCode": ">=1.0.0"
6
6
  },
@@ -48,6 +48,94 @@ claude --version 2>/dev/null || echo "Claude Code not installed"
48
48
 
49
49
  **⚠️ When Bedrock routing confirmed**: Request AWS quota increase (TPM/RPM increase) or raise LiteLLM `max_input_tokens`, then retry. Fundamental solution is switching to direct Anthropic API access.
50
50
 
51
+ ### Node floor check — run on EVERY machine, not once per user
52
+
53
+ A user's context (companion store, memory, session card) travels between machines; **the machine's
54
+ own setup does not**. A rich context makes a fresh laptop read as "already configured", so the two
55
+ mechanical floors below must be checked per node — this is the check `scripts/fh_node_check.sh`
56
+ points at when it reports a missing floor at turn 0.
57
+
58
+ ```bash
59
+ # HUB_DIR — the same variable the hook scripts use. Do NOT invent a second name (an earlier draft
60
+ # read FH_DIR, which nothing sets, so an operator with HUB_DIR set would have been silently told
61
+ # about a different repo).
62
+ FH="${HUB_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"
63
+
64
+ # ① git-side floor. Probe the EXECUTABLE HOOK, not the config key — `core.hooksPath` unset is a
65
+ # normal, working install when hooks sit in .git/hooks, and a set-but-empty path is a broken
66
+ # install that the key alone reports as fine. Both directions are wrong; the file is the truth.
67
+ # Resolve the directory with `git rev-parse --git-path` rather than assembling "$FH/.git/hooks":
68
+ # in a LINKED WORKTREE `.git` is a file, so the assembled path does not exist and every hook
69
+ # reads as missing (verified: a worktree with working hooks reported ❌❌ under the old form).
70
+ # Same resolution the script uses — one predicate, not two.
71
+ # --path-format needs git >= 2.31; on older git fall back to the RELATIVE --git-path form resolved
72
+ # against the toplevel, never to a hand-built "$FH/.git/hooks" (which is the worktree bug above).
73
+ HD="$(git -C "$FH" rev-parse --path-format=absolute --git-path hooks 2>/dev/null)"
74
+ if [ -z "$HD" ]; then
75
+ _rel="$(git -C "$FH" rev-parse --git-path hooks 2>/dev/null || echo .git/hooks)"
76
+ case "$_rel" in /*) HD="$_rel" ;; *) HD="$(git -C "$FH" rev-parse --show-toplevel 2>/dev/null || echo "$FH")/$_rel" ;; esac
77
+ fi
78
+ for h in pre-commit pre-push; do
79
+ if [ -x "$HD/$h" ]; then echo "✅ $h executable ($HD)"
80
+ else echo "❌ $h MISSING or not executable at $HD — that gate is not running on this node"; fi
81
+ done
82
+
83
+ # ② turn-0 load floor (Mode D only): the companion-load hook. Its registration lives in the
84
+ # GITIGNORED settings.local.json, so it does NOT survive a re-clone — hence a per-node check.
85
+ # Parse the JSON and match the SCRIPT NAME inside hooks.SessionStart — not a bare grep for the
86
+ # key. settings.json also carries a SessionStart entry (fh_node_check), so keying on the key
87
+ # alone passes on a machine where the companion load is absent; and a plain substring grep also
88
+ # hits commented-out lines, other hook events, and permission strings. Same predicate the script
89
+ # uses — two predicates reading one state differently is how a result leaks silently.
90
+ # Applicability gate FIRST, and keyed the same way the script keys it: a Mode D user is one with
91
+ # an exported BE_DIR, or a CLAUDE.local.md that MENTIONS a companion binding — NOT one who merely
92
+ # HAS a CLAUDE.local.md (that is Claude Code's standard local-override file; anyone may keep one),
93
+ # and NOT one who has a settings.local.json (gitignored, so a fresh clone lacks it — and a fresh
94
+ # clone with a full companion store is the exact case this must not silence). The vocabulary
95
+ # covers every backend the wizard documents (vault · gbrain · *-be repo), because an FH-flavoured
96
+ # regex would silence two first-class backends. It is a MENTION test, not semantic: "I do not use
97
+ # a companion store" also matches, and that over-match costs one informational line — the cheap
98
+ # direction, since the expensive direction is silence.
99
+ if { [ -n "${BE_DIR:-}" ] && [ -d "$BE_DIR" ]; } \
100
+ || { [ -f "$FH/CLAUDE.local.md" ] && grep -qiE 'BE_DIR|companion[ -]store|컴패니언|vault|gbrain|obsidian' "$FH/CLAUDE.local.md"; }; then
101
+ python3 - "$FH" <<'PY'
102
+ import json, os, sys
103
+ hub = sys.argv[1]
104
+ for p in (os.path.join(hub, ".claude", "settings.local.json"),
105
+ os.path.expanduser("~/.claude/settings.json")):
106
+ try: groups = json.load(open(p)).get("hooks", {}).get("SessionStart", [])
107
+ except Exception: continue
108
+ if any("fh_session_load" in h.get("command", "")
109
+ for g in groups for h in g.get("hooks", [])):
110
+ print("✅ companion-load SessionStart registered"); sys.exit(0)
111
+ print("❌ companion-load SessionStart MISSING — freshness + env-delta do not fire at turn 0")
112
+ PY
113
+ else
114
+ echo "N/A companion-load SessionStart (not a Mode D setup — no companion store configured)"
115
+ fi
116
+ ```
117
+
118
+ **Bootstrap note — honest scope**: `scripts/fh_node_check.sh` (the check that *reports* a missing
119
+ floor at turn 0) ships with the clone, but its **registration does not**: every
120
+ `.claude/settings*.json` path in this repo is gitignored (confirm with `git check-ignore -v
121
+ .claude/settings.json` — don't trust a line number, they move), so no SessionStart entry can be
122
+ tracked. The tracked artifact is `templates/settings.SessionStart.snippet.json`; `/install-wizard`
123
+ merges it. A user who never runs the wizard still gets no turn-0 signal — that residual is reduced,
124
+ not closed, and this check item is the backstop for it.
125
+
126
+ **Emission model** (so the output is read correctly): a **missing floor is reported every session**
127
+ until it is fixed — it is a persistent condition, not an event. A healthy machine is silent. So
128
+ seeing this banner twice is not a bug, and seeing it once then never again means it was an *event*
129
+ line (identity change / infra delta), not a floor complaint.
130
+
131
+ **Fix for either ❌**: re-run `/install-wizard` (it registers both — `install-wizard/SKILL_detail.md`
132
+ §Mode-D-Companion-Setup step 3 and the 4-axis gate block), or apply the two commands it uses directly.
133
+
134
+ **Why this is a check item and not a note** (measured 2026-07-30): a second machine held the full
135
+ companion store and memory, yet ran with **both** SessionStart hooks absent. Nothing surfaced it —
136
+ the miss was found by accident. A weak tier is where this bites hardest: a mechanized floor is
137
+ tier-independent, while the prose it replaces is exactly what a weaker model drops first.
138
+
51
139
  ---
52
140
 
53
141
  ## Step 1. Existing Asset Inventory
@@ -88,7 +88,7 @@ All 3 → Proceed in order: Step 0-B (token injection) → Step 0 (environment c
88
88
 
89
89
  **Mode D detected (FH developer/researcher)**: Guide companion-store setup before Step 1. **Ask the backend first** — the store is a role (durable private home for artifacts), not a fixed `*-be` git repo: Obsidian vault / gbrain-ingest / `*-be` git repo (default) all qualify. Do not assume the repo path.
90
90
 
91
- **Set the store up as a queryable WIKI, not an empty folder** — the store earns its value when the agent can *read it well*, so the scaffold includes: an `INDEX.md` wiki-home (**the `CATALOG.md` read-first pattern applied to the store** — named INDEX to avoid two CATALOG.md across the public + private repos; section map + **read-time-derived** pointers, never hand-maintained placeholders that rot) · session-start read wiring the AI **idempotently adds** to `CLAUDE.local.md` (grep-guarded against duplication: read INDEX → open result/signal files newer than the session card, not only handoffs) · the Raw/Wiki/Conversation ingest axis (`sync_push_protocols.md`). The git `*-be` form is the **default within the no-existing-store branch** (it stays an ask-first choice — see the backend question; SKILL.md does not override the detail file's neutral ordering), recommended *there* because for a non-visual user *observability is the agent querying the wiki* (INDEX + session-start read) — git-versioned, agent-native (grep/Read), no cloud egress; FH's answer to its weak Observability layer. Obsidian (graph-view = the visual observability surface) and cloud are equal options when the user already runs one. **Salience caveat**: the session-start read is prose in `CLAUDE.local.md` (no SessionStart hook) on a weak tier it may silently not fire; accepted, revisit if a target-tier sim measures a miss.
91
+ **Set the store up as a queryable WIKI, not an empty folder** — the store earns its value when the agent can *read it well*, so the scaffold includes: an `INDEX.md` wiki-home (**the `CATALOG.md` read-first pattern applied to the store** — named INDEX to avoid two CATALOG.md across the public + private repos; section map + **read-time-derived** pointers, never hand-maintained placeholders that rot) · session-start read wiring the AI **idempotently adds** to `CLAUDE.local.md` (grep-guarded against duplication: read INDEX → open result/signal files newer than the session card, not only handoffs) · the Raw/Wiki/Conversation ingest axis (`sync_push_protocols.md`). The git `*-be` form is the **default within the no-existing-store branch** (it stays an ask-first choice — see the backend question; SKILL.md does not override the detail file's neutral ordering), recommended *there* because for a non-visual user *observability is the agent querying the wiki* (INDEX + session-start read) — git-versioned, agent-native (grep/Read), no cloud egress; FH's answer to its weak Observability layer. Obsidian (graph-view = the visual observability surface) and cloud are equal options when the user already runs one. **Mechanical floor (corrected 2026-07-30)**: the `CLAUDE.local.md` read is prose, so the wizard **also registers the `SessionStart` hooks** (`fh_session_load.sh` companion freshness + `fh_env_delta_scan.sh` env-delta) into the gitignored `settings.local.json` — see `SKILL_detail.md §Mode-D-Companion-Setup` step 3. The earlier text here claimed no such hook existed and marked the gap "accepted"; both were wrong, and a clean second-machine install measured the miss (turn-0 load skipped on a task-first entry, on Opus). Prose is the layer **over** the floor, never the floor.
92
92
 
93
93
  **Beyond Mode D (not forced)**: a non-developer user who accumulates their own context into FH and keeps no separate local store benefits too — but since accumulation is unobservable at *first* install, surface this offer only on a **re-run where FH-written artifacts already exist** (e.g. non-empty `tracks/*/session_*.md`), not preemptively at first setup. A user with a local store, or with no FH artifacts, is not prompted.
94
94