@chrono-meta/fh-gate 1.4.75 → 1.4.77
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/AGENTS.md +18 -0
- package/CLAUDE.md +7 -2
- package/knowledge/shared/harness-core/multi_model_sidecar_strategy.md +6 -0
- package/knowledge/shared/learnings/subagent_invocations_log.yaml +21 -2
- package/knowledge/shared/rules/operational_adaptation.md +130 -1
- package/package.json +5 -1
- 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/auto-decorrelation/SKILL.md +38 -3
- package/plugins/fh-meta/skills/sim-conductor/SKILL_detail.md +6 -0
- package/plugins/fh-meta/skills/steel-quench/SKILL_detail.md +6 -0
- package/scripts/consent_registry_check.sh +390 -0
- package/scripts/selfcheck.sh +30 -0
- package/scripts/sidecar_wait.sh +55 -5
- package/scripts/test_consent_registry.sh +255 -0
- package/scripts/test_sidecar_wait_stdin.sh +169 -0
- package/templates/consent_classes.yaml.example +75 -0
|
@@ -11,13 +11,13 @@
|
|
|
11
11
|
"plugins": [
|
|
12
12
|
{
|
|
13
13
|
"name": "fh-meta",
|
|
14
|
-
"version": "1.4.
|
|
14
|
+
"version": "1.4.77",
|
|
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.77",
|
|
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/AGENTS.md
CHANGED
|
@@ -70,6 +70,24 @@ Agents in this registry belong to the **Automation layer**. Skills (in `plugins/
|
|
|
70
70
|
|
|
71
71
|
> **Multi-model sidecar (validated)**: Any FH user can delegate to other models via sidecar — Gemini CLI, OpenAI/Codex CLI, or Copilot CLI's model catalog — invoked with `Bash` from within the Claude Code session. FH is the orchestrating harness; the sidecar is a routing/access layer (not a second harness — different layer entirely). Validated empirically: `echo "prompt" | gemini` works inside a CC session and produces usable output. Sidecar calls are Bash invocations, not agent dispatches — they bypass this registry and are coordinated inline by the skill. Capability routing matters too: Gemini/Antigravity is the natural breadth/multimodal sidecar, while Codex's primary cast is the **repo-grounded audit** sidecar (file reads · grep/source-close · diff & patch · gate execution · phantom/backtrace) — **not** discovery/design-depth; a Codex session with Browser/Chrome connectors mounted can additionally take live web-flow automation as a capability-routed handoff. In a local FH workspace that pairs the public methodology mirror with a private companion store (the `*-be` pattern), route by workspace capability while preserving each repository's ownership boundary. See `knowledge/shared/harness-core/multi_model_sidecar_strategy.md §Runtime Authority` for the authority model and the full pattern.
|
|
72
72
|
|
|
73
|
+
> **Waiting on a sidecar — mechanical, not by eye (2026-07-29).** A sidecar you dispatched is judged
|
|
74
|
+
> ONLY by the typed verdict line from `scripts/sidecar_wait.sh`:
|
|
75
|
+
>
|
|
76
|
+
> ```bash
|
|
77
|
+
> printf '%s' "$prompt" | bash scripts/sidecar_wait.sh out.txt 900 -- codex exec -m gpt-5.5 -
|
|
78
|
+
> # SIDECAR_VERDICT=COMPLETE exit=0 bytes=48489 → read out.txt
|
|
79
|
+
> # SIDECAR_VERDICT=TIMEOUT waited=900s bytes=0 → STILL ALIVE, not a result
|
|
80
|
+
> # SIDECAR_VERDICT=EMPTY exit=0 → the only state meaning "it said nothing"
|
|
81
|
+
> ```
|
|
82
|
+
>
|
|
83
|
+
> **Never judge a sidecar by looking at its output file.** A live process and a dead one produce the
|
|
84
|
+
> same zero bytes, and only process state separates them. Measured here: a session backgrounded two
|
|
85
|
+
> sidecars, read their files after 1 s and 30 s, recorded *"both returned 0-output"* into five
|
|
86
|
+
> records — and both had answered, with four real findings, one of which showed the change under
|
|
87
|
+
> review was over-applied. The mis-read nearly retired a working mechanism. This rule is repeated in
|
|
88
|
+
> this file because line 71 tells you to invoke sidecars with `Bash`; a runtime reading only that
|
|
89
|
+
> would dispatch with no waiting discipline at all. Canonical: `auto-decorrelation` SKILL.md §S-1b.
|
|
90
|
+
|
|
73
91
|
> **Runtime authority — hard stop line (Codex / non-Claude runtimes):** your findings are **evidence candidates, not terminal verdicts**. They are not final until the governor source-closes them against a **mechanical anchor** (a local file hit · a literal source span · a passing check) — **never governor agreement alone**. You are a capability-routed **sidecar**, not a co-governor: there is one explicit governor per context. Full doctrine: `knowledge/shared/harness-core/multi_model_sidecar_strategy.md §Runtime Authority`.
|
|
74
92
|
|
|
75
93
|
---
|
package/CLAUDE.md
CHANGED
|
@@ -531,7 +531,7 @@ Proposal format: `"I see [X]. Want me to run /[skill] to [one-line description]?
|
|
|
531
531
|
| **"진단해줘", "개선해줘", "diagnose this", "improve this harness", "check this project", "audit this project"** — said while working **in a mapped project** (not a single-file ask) | **Field-Harness Diagnostic** (see §Field-Harness Diagnostic above → compose existing checks into one ranked M/S/R list → HITL approval per item, nothing auto-fixed) |
|
|
532
532
|
| **"새 프로젝트", "하네스 작성해줘", "이 프로젝트 가속화", "harness-ify this", "accelerate this project"** — an onboarding/acceleration door (returning-menu ①②③) | **Onboarding / Acceleration Autopilot** (see §Onboarding / Acceleration Autopilot above → Phase 0 auto-discover + branch → innovator-centered recommend → ranked install plan → HITL per item, non-overwriting; "끝까지 자율로" → full-autonomy under /goal-quench gate) |
|
|
533
533
|
|
|
534
|
-
**Guard**: Do not propose a skill that is already running. One signal = one-line proposal (no pressure). Before proposing, consult the UAP (§Operational Adaptation Loop): a skill the user has rejected 3+ times is **suppressed**, not re-proposed.
|
|
534
|
+
**Guard**: Do not propose a skill that is already running. One signal = one-line proposal (no pressure). Before proposing, consult the UAP (§Operational Adaptation Loop): a skill the user has rejected 3+ times is **suppressed**, not re-proposed — and, symmetrically, a class **accepted 3× consecutively** earns a one-time "stop asking?" offer (§Consent promotion; never on irreversible surfaces).
|
|
535
535
|
For per-skill utterance patterns, see the relevant `SKILL.md §Trigger Phrases` section.
|
|
536
536
|
|
|
537
537
|
### Cadence Rules — Check at Session Start
|
|
@@ -564,7 +564,8 @@ Some proposals are not *time*-overdue — they fire **once when a specific work
|
|
|
564
564
|
Self-healing is not only FH-self-dev (Mode D 4-axis) and reactive (`verify-bidirectional`). A **standing, per-user operational loop** tunes FH behavior to the individual during normal field use, and escalates **only generalizable** learnings to the `field-harvest` → FH-origin PR funnel — idiosyncratic taste stays local (drift guard).
|
|
565
565
|
|
|
566
566
|
- **User Adaptation Profile (UAP)** — `tracks/_meta/user_adaptation_profile.md` (local/gitignored; **behavioral prefs only, never domain content**). Records skill-proposal outcomes (`accepted`/`rejected`/`sustained` — same vocabulary as `operations.md`), preferred tier/language/cadence, recurring friction, muted nags.
|
|
567
|
-
- **Pass** — rides `field-harvest` Mode B at field-session close (no new trigger, one per session): READ to apply (suppress a 3×-rejected proposal, default to preferred tier, mute declined cadence nags), WRITE to update outcomes.
|
|
567
|
+
- **Pass** — rides `field-harvest` Mode B at field-session close (no new trigger, one per session): READ to apply (suppress a 3×-rejected proposal, **offer standing consent on a 3×-accepted class**, default to preferred tier, mute declined cadence nags), WRITE to update outcomes.
|
|
568
|
+
- **Consent promotion (accept-side)** — repeated approval must offer to stop asking, not bill the same prompt forever: 3 consecutive `accepted` on a **registered** class (`tracks/_meta/consent_classes.yaml` — classes are declared, never minted mid-run) → **offer once, quoting the three approvals and the exact scope** → granted = a **time-limited lease**, revocable, and every unprompted run announces itself. **Not symmetric with suppression**: a bad suppression costs a re-ask, a bad grant has side effects. **Floor, decided mechanically from the registry — never by the session's own judgment**: a class never promotes if its sinks are irreversible (publish · delete · history-rewrite), if it *feeds* such a sink (**taint propagates through reversible steps**), or if that is **unknown** — unknown is not reversible. No UAP / no registry entry / expired → keep asking (absent ≠ granted). **Named residual: the ledger is self-attested** — mitigated (append-only, quoted evidence), not closed.
|
|
568
569
|
- **Generalization gate** — idiosyncratic → UAP local; generalizable (any user benefits; `≥40%` reject = redefine candidate / `≥60%` accept = reinforce, per `operations.md` gate) → `field-harvest` Mode A → FH PR (HITL).
|
|
569
570
|
- **Ephemeral guard** — UAP is gitignored, wiped on cloud reclaim; in ephemeral sessions operate from defaults, do not fabricate it.
|
|
570
571
|
|
|
@@ -699,6 +700,10 @@ Closing phrase detected ("wrap up", "done", "good work", "end session", etc.)
|
|
|
699
700
|
outcomes. ⑤ is ATOMIC and owns BOTH writes: (a) append any close-time finding to
|
|
700
701
|
`fh_completed_{date}.md` FIRST, (b) then write the card. Once ⑤ starts, `fh_completed`
|
|
701
702
|
is CLOSED — a later append re-opens the violation ⑤ exists to prevent.
|
|
703
|
+
**Late finding (named case)**: a finding that surfaces AFTER (b) — including while writing
|
|
704
|
+
the final message to the operator — means ⑤ is **not done**. Re-run ⑤ **whole**: append,
|
|
705
|
+
then **rewrite the card**. Appending alone is the violation; the card must never be older
|
|
706
|
+
than `fh_completed`.
|
|
702
707
|
→ ⑥ Commit card + push
|
|
703
708
|
```
|
|
704
709
|
|
|
@@ -9,6 +9,12 @@ status: mechanism-validated (cross-provider grader confirmed 2026-06-02)
|
|
|
9
9
|
> **Validation status** (updated 2026-06-02): mechanism validated by cross-provider grader.
|
|
10
10
|
>
|
|
11
11
|
> 2026-06-01 steel-quench (Issue #47): mechanism confirmed runnable — implementation shipped in PR #36/#37. Original empirical claims (Experiment 1·2) were an internal same-session self-report; raw transcripts not retained, codex grader blocked by network policy.
|
|
12
|
+
|
|
13
|
+
> **사이드카 대기는 기계로 한다** — `scripts/sidecar_wait.sh` 경유가 필수 형식이고, 타입 verdict
|
|
14
|
+
> (`COMPLETE`/`TIMEOUT`/`EMPTY`)만 읽는다. **`EMPTY` 만이 "사이드카가 아무 말 안 했다"** 이다.
|
|
15
|
+
> 출력 파일을 눈으로 보고 판정하지 마라 — 살아있는 프로세스와 죽은 프로세스가 같은 0바이트를
|
|
16
|
+
> 낸다(2026-07-29 실측: 1초/30초 만에 읽고 정상 동작 중인 사이드카 2종을 '실패'로 기록,
|
|
17
|
+
> 실제로는 4건의 진짜 finding 이 나왔다). 정본 = `auto-decorrelation` SKILL.md §S-1b.
|
|
12
18
|
>
|
|
13
19
|
> 2026-06-02 update: Gemini 0.41.2 cross-provider grader run on `pipeline-conductor/SKILL.md` (retained transcript: `tracks/_meta/grader_gemini_pipeline_conductor_2026_06_02.txt`). Gemini found 3 S-grade findings (interaction deadlock, PR-approval deadlock, cadence-lock deadlock); Claude Sonnet-4.6 previously found 3 different S-grade findings (model conflict, invocation contradiction, self-referential sweep). **Zero overlap across 6 S-grade findings** — validates the non-overlapping failure modes claim and perspective diversity mechanism. Provider-identity diversity is empirically confirmed; specific Experiment 2 finding counts on goal-quench (original target) are not directly re-run. Record: `tracks/_meta/grader_gemini_pipeline_conductor_2026_06_02.txt`.
|
|
14
20
|
|
|
@@ -550,7 +550,7 @@
|
|
|
550
550
|
prompt_summary: "Would have been: attack the S1~S5 probes + the FP scoping; verify no default-toward-PASS class is hidden by the scoping itself."
|
|
551
551
|
outcome: sustained
|
|
552
552
|
finding: "Ran the adversarial pass INLINE instead. It did find a HIGH (S1 scope-exclusion swallowed `[ -f lib ] || exit 0` dependency guards — the fail-open class), closed with a regression anchor. But inline review is same-context by construction, so the isolation property the gate asks for was NOT obtained."
|
|
553
|
-
note: "Recorded as `sustained` (decided NOT to invoke) because the session carries a
|
|
553
|
+
note: "Recorded as `sustained` (decided NOT to invoke) because the session carries a session-level system configuration, not an operator instruction. FIRST `sustained` entry in this log — and directly relevant to fh_signal_2026-07-28_decorrelation-log-uncalibrated, which measured 0 rejected / 0 sustained across 74 entries and argued the log is written selectively toward optimistic outcomes. Operator decision pending on dispatching before merge."
|
|
554
554
|
|
|
555
555
|
|
|
556
556
|
- date: 2026-07-28
|
|
@@ -560,4 +560,23 @@
|
|
|
560
560
|
prompt_summary: "Would have been: cold-read the rewritten Session Wrap-up ⑤ at Sonnet and close a session with a finding arriving mid-close — does the atomic ordering actually fire, or does the fh_completed append still happen after the card?"
|
|
561
561
|
outcome: sustained
|
|
562
562
|
finding: "Not run. The four other worklist items are mechanical (scripts/hooks — tier-independent, exempt by the gate's own enforcement-column test) and were verified by known-pair + mutation + positive control. The CLAUDE.md ⑤ change is the one item the sim gate actually targets, and it is therefore UNVERIFIED at floor tier — recorded as a residual in the Axes 2-3 marker rather than absorbed silently."
|
|
563
|
-
note: "Second `sustained` in this log, same cause as the first:
|
|
563
|
+
note: "Second `sustained` in this log, same cause as the first: session-level system configuration withheld the Agent tool absent an explicit user request (a configured default, NOT something the operator said — the earlier wording blurred the source). The pattern is now n=2 — the gate's near-mandatory sim is structurally unreachable in sessions carrying that instruction, which is a governance question (whose exception is it?) rather than a per-session judgment call. Candidate for fh_signal."
|
|
564
|
+
|
|
565
|
+
- date: 2026-07-29
|
|
566
|
+
agent: general-purpose (Sonnet) — blind behavioral sim
|
|
567
|
+
model: sonnet
|
|
568
|
+
purpose: "Target-tier sim gate on the salience-dependent auto-decorrelation §S-1b change (sidecar_wait as required form)"
|
|
569
|
+
prompt_summary: "Play out a real turn: dispatch a sidecar, check it 40s later, find 0 bytes — write the exact commands and the exact sentence you'd record. Then say what you'd skim past."
|
|
570
|
+
outcome: accepted
|
|
571
|
+
finding: >-
|
|
572
|
+
The rule FIRES at Sonnet tier — the sim reproduced the required command verbatim, refused to write
|
|
573
|
+
"0-output" at the 40s mark, and cited the exact lines that drove each answer. The value was in the
|
|
574
|
+
two honesty questions: it named the incident narrative as skimmable ("reads like color commentary,
|
|
575
|
+
and it comes AFTER the command, so a fast reader treats the command as the content"), and named
|
|
576
|
+
peeking at the output file as the FIRST thing it would do wrong in a hurry. Acted on: the operative
|
|
577
|
+
one-line rule was hoisted ABOVE the command block.
|
|
578
|
+
note: >-
|
|
579
|
+
First Agent-tool dispatch of this session. The two earlier `sustained` entries recorded the tool as
|
|
580
|
+
withheld by a "standing session instruction"; the operator pointed out they never said that — it is
|
|
581
|
+
session-level SYSTEM configuration, and the wording blurred the source. Corrected in the markers and
|
|
582
|
+
here. The operator then explicitly requested the agent, which satisfies the configured exception.
|
|
@@ -28,9 +28,136 @@ 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 (an `accepted` record carries **no** positive auto-action —
|
|
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.)
|
|
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
|
+
|
|
34
161
|
## Generalization gate → reverse-PR funnel
|
|
35
162
|
|
|
36
163
|
This is the operator's **"원본 반영 가치"** criterion made mechanical. Split each UAP learning:
|
|
@@ -46,6 +173,8 @@ This is the operator's **"원본 반영 가치"** criterion made mechanical. Spl
|
|
|
46
173
|
|
|
47
174
|
- **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).*
|
|
48
175
|
- **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).*
|
|
49
178
|
- **No domain content** entered the UAP this session. *Check class: judged, pair: phantom/content scan of the UAP diff.*
|
|
50
179
|
|
|
51
180
|
## 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.77",
|
|
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": [
|
|
@@ -106,6 +106,10 @@
|
|
|
106
106
|
"scripts/test_memory_link_check.sh",
|
|
107
107
|
"scripts/memory_nearcheck.py",
|
|
108
108
|
"scripts/sidecar_wait.sh",
|
|
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",
|
|
109
113
|
"scripts/test_session_close_lanes.sh",
|
|
110
114
|
"scripts/test_card_drift_probe.sh",
|
|
111
115
|
"scripts/universal_guard_check.sh",
|
|
@@ -129,13 +129,48 @@ diversity vs the orchestrator** (orchestrator = Claude/opus → recruit GPT or G
|
|
|
129
129
|
surfaces a one-line `token-budget-gate` ask per run (*"recruiting codex (~N) — proceed?"*) unless the
|
|
130
130
|
operator has set `paid_auto: true` in the UAP. One-time feature-consent ≠ consent to this spend now.
|
|
131
131
|
- Dispatch via `agent-composer` (no re-implementation of dispatch).
|
|
132
|
-
- **
|
|
132
|
+
- **Wait mechanically — `scripts/sidecar_wait.sh` is the required form, not a suggestion (S-1b).**
|
|
133
|
+
|
|
134
|
+
**The rule, before the command, because a Sonnet-tier blind sim of this section said the command
|
|
135
|
+
reads as "the content" and everything after it as "color commentary" — and named peeking at the
|
|
136
|
+
output file as the first thing it would do wrong in a hurry:**
|
|
137
|
+
|
|
138
|
+
> **Never judge a sidecar by looking at its output file.** A live process and a dead one produce
|
|
139
|
+
> the same zero bytes. The only readable verdict is the typed `SIDECAR_VERDICT=` line, and it
|
|
140
|
+
> does not exist until the process has exited.
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
printf '%s' "$prompt" | bash scripts/sidecar_wait.sh out.txt 900 -- codex exec -m gpt-5.5 -
|
|
144
|
+
# → SIDECAR_VERDICT=COMPLETE exit=0 bytes=48489 (read out.txt)
|
|
145
|
+
# → SIDECAR_VERDICT=TIMEOUT waited=900s bytes=0 (still alive — NOT a result)
|
|
146
|
+
# → SIDECAR_VERDICT=EMPTY exit=0 (the only state that means "it said nothing")
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
The runner **refuses to emit a verdict while the process is alive**, so "the sidecar returned
|
|
150
|
+
nothing" becomes unsayable until it has actually exited. Grep the typed `SIDECAR_VERDICT=` line;
|
|
151
|
+
never judge by looking at the output file.
|
|
152
|
+
|
|
153
|
+
**Why this is mechanical rather than a habit** — the bullets below already described bounding a
|
|
154
|
+
sidecar, and a session that had them loaded still got it wrong on 2026-07-29: it backgrounded
|
|
155
|
+
`codex exec` and `agy -p`, read the output files after **1 s and 30 s**, found them empty, and
|
|
156
|
+
recorded *"both sidecars returned 0-output"* into a gate marker, a PR body, a session card, a
|
|
157
|
+
memory file and a handoff. Both were running normally and both answered — codex with 48 KB and
|
|
158
|
+
three findings, agy with a further HIGH, and **all four were real**; one of them showed the change
|
|
159
|
+
under review was over-applied. So the measurement error nearly retired a working mechanism.
|
|
160
|
+
|
|
161
|
+
**That is a second failure mode this section did not cover.** The bullets below describe a *hung*
|
|
162
|
+
sidecar. An impatient read of a *healthy* one produces the identical observation — zero bytes —
|
|
163
|
+
and only one of the two is a fault. Distinguishing them requires process state, which is exactly
|
|
164
|
+
what a human eye on an output file cannot see and the runner always reports.
|
|
165
|
+
|
|
166
|
+
- **Liveness / hang-catch (the runner does the waiting; this is how to read a `TIMEOUT`).** A backgrounded CLI that hangs
|
|
133
167
|
(stuck on a sandbox/file-tool prompt, auth, or network) **does not exit**, so the background-completion
|
|
134
168
|
signal *never fires* — passive waiting is the wrong model and silently stalls the run (observed
|
|
135
169
|
2026-06-27: a `codex exec` that asked to read repo files hung at 0-output with no session log, and the
|
|
136
170
|
turn waited on a notification that could not come). So **bound it actively, never wait open-endedly**:
|
|
137
|
-
-
|
|
138
|
-
|
|
171
|
+
- The timeout is the runner's second argument; pick it from the model's real latency, not from
|
|
172
|
+
impatience (a reasoning model can be silent for minutes and still be working).
|
|
173
|
+
- On `TIMEOUT`, watch a **progress signal**, not just process-alive: output bytes growing **and** the CLI's own
|
|
139
174
|
session/log advancing (e.g. `~/.codex/sessions/<today>`). 0 output **and** no session created after a
|
|
140
175
|
short bound (≈2–3 min for codex/agy) = **hung, not slow** → kill and recover, do not keep waiting.
|
|
141
176
|
- **Recover, don't stall**: kill → diagnose (a file-tool/sandbox hang is the common cause) → retry with
|
|
@@ -172,6 +172,12 @@ Run multi-team? (a) Full panel (b) Claude sub-agents only (c) Skip to Area B
|
|
|
172
172
|
| T2 Copilot | `gh copilot suggest` | challenger · expert | `gh copilot suggest -t shell` |
|
|
173
173
|
| T3 Ollama | `ollama run` | challenger | `ollama run llama3 PROMPT` |
|
|
174
174
|
| T4 Codex | `npx @openai/codex exec` | challenger · edge-case-hunter | `echo PROMPT \| npx @openai/codex exec -m gpt-5 -` |
|
|
175
|
+
|
|
176
|
+
> **사이드카 대기는 기계로 한다** — `scripts/sidecar_wait.sh` 경유가 필수 형식이고, 타입 verdict
|
|
177
|
+
> (`COMPLETE`/`TIMEOUT`/`EMPTY`)만 읽는다. **`EMPTY` 만이 "사이드카가 아무 말 안 했다"** 이다.
|
|
178
|
+
> 출력 파일을 눈으로 보고 판정하지 마라 — 살아있는 프로세스와 죽은 프로세스가 같은 0바이트를
|
|
179
|
+
> 낸다(2026-07-29 실측: 1초/30초 만에 읽고 정상 동작 중인 사이드카 2종을 '실패'로 기록,
|
|
180
|
+
> 실제로는 4건의 진짜 finding 이 나왔다). 정본 = `auto-decorrelation` SKILL.md §S-1b.
|
|
175
181
|
| T5 agy | `agy -p` (gemini successor) | challenger · beginner | `agy -p "PROMPT"` — argument form only (stdin pipe prints help); timebox+retry hard rule (intermittent hang class); -p auto-approves tools → trusted artifacts only |
|
|
176
182
|
|
|
177
183
|
### CLI detection bash
|
|
@@ -285,6 +285,12 @@ Default team-persona assignments:
|
|
|
285
285
|
| **T2 Copilot** | `gh copilot suggest` | devil · expert |
|
|
286
286
|
| **T3 Ollama** | `ollama run {model}` | devil |
|
|
287
287
|
| **T4 Codex** | `npx @openai/codex exec` | devil · edge-case-hunter |
|
|
288
|
+
|
|
289
|
+
> **사이드카 대기는 기계로 한다** — `scripts/sidecar_wait.sh` 경유가 필수 형식이고, 타입 verdict
|
|
290
|
+
> (`COMPLETE`/`TIMEOUT`/`EMPTY`)만 읽는다. **`EMPTY` 만이 "사이드카가 아무 말 안 했다"** 이다.
|
|
291
|
+
> 출력 파일을 눈으로 보고 판정하지 마라 — 살아있는 프로세스와 죽은 프로세스가 같은 0바이트를
|
|
292
|
+
> 낸다(2026-07-29 실측: 1초/30초 만에 읽고 정상 동작 중인 사이드카 2종을 '실패'로 기록,
|
|
293
|
+
> 실제로는 4건의 진짜 finding 이 나왔다). 정본 = `auto-decorrelation` SKILL.md §S-1b.
|
|
288
294
|
| **T5 agy** | `agy -p "PROMPT"` (argument form only — stdin pipe prints help, measured 2026-06-13) | devil · beginner · alternatives (gemini successor) |
|
|
289
295
|
|
|
290
296
|
**Step 1 — Parallel Team Dispatch**:
|