@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.
- package/.claude/rules/fh_4axis_gate.md +63 -0
- package/.claude-plugin/marketplace.json +2 -2
- package/AGENTS.md +96 -260
- package/CLAUDE.md +2 -7
- package/docs/codex-compat.md +4 -1
- package/knowledge/shared/harness-core/agents_md_runtime_details.md +233 -0
- package/knowledge/shared/harness-core/multi_model_sidecar_strategy.md +1 -1
- package/knowledge/shared/learnings/subagent_invocations_log.yaml +14 -0
- package/knowledge/shared/rules/operational_adaptation.md +1 -130
- package/package.json +7 -5
- package/plugins/fh-commons/.claude-plugin/plugin.json +1 -1
- package/plugins/fh-meta/.claude-plugin/plugin.json +1 -1
- package/plugins/fh-meta/skills/install-doctor/SKILL.md +88 -0
- package/plugins/fh-meta/skills/install-wizard/SKILL.md +1 -1
- package/plugins/fh-meta/skills/install-wizard/SKILL_detail.md +117 -3
- package/scripts/fh_node_check.sh +184 -0
- package/scripts/fh_session_load.sh +59 -28
- package/scripts/package_coverage_check.sh +22 -0
- package/scripts/selfcheck.sh +30 -15
- package/scripts/sidecar_calibrate.sh +190 -0
- package/scripts/test_node_check_lanes.sh +179 -0
- package/scripts/test_sidecar_calibrate_lanes.sh +218 -0
- package/templates/settings.SessionStart.snippet.json +54 -0
- package/scripts/consent_registry_check.sh +0 -390
- package/scripts/test_consent_registry.sh +0 -255
- package/templates/consent_classes.yaml.example +0 -75
|
@@ -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
|
-
- `
|
|
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
|
|
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.
|
|
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
|
}
|
|
@@ -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. **
|
|
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
|
|