@chrono-meta/fh-gate 1.4.78 → 1.4.80

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,75 @@
1
+ # consent_classes.yaml — the consent-class registry.
2
+ #
3
+ # Copy to `tracks/_meta/consent_classes.yaml` (gitignored, per-environment) to enable
4
+ # accept-side consent promotion. See `knowledge/shared/rules/operational_adaptation.md`
5
+ # §Consent promotion.
6
+ #
7
+ # WHY A REGISTRY AT ALL (cross-family review, 2026-07-29)
8
+ # The first draft let the running session name the class it was counting approvals against.
9
+ # That defeats the class-inflation guard before it fires: the class does not need to be WIDENED
10
+ # later if it was born wide. Recording three approvals as `sim-conductor` instead of
11
+ # `dispatch read-only sim on a local artifact` silently grants every future sim-conductor mode.
12
+ # So classes are DECLARED here, reviewed by a human, and the session may only count against
13
+ # an entry that already exists. A session may propose a new entry; it may not mint one mid-run.
14
+ #
15
+ # THE FLOOR IS READ FROM THIS FILE, NOT JUDGED AT RUNTIME
16
+ # `sinks` is the field the irreversibility floor is decided from. A session asking itself
17
+ # "is this reversible?" is the wrong arbiter — it is the party that wants to stop being asked.
18
+ # Absent entry / absent file / unparseable entry => NO promotion, keep asking. Unknown is not
19
+ # reversible.
20
+ #
21
+ # TAINT
22
+ # A class inherits the floor if it FEEDS an irreversible sink, not only if it crosses one.
23
+ # `local-commit` below is the worked example: committing is locally reversible, but if a
24
+ # post-commit hook, sync daemon, or CI workflow publishes from that commit, the chain crossed
25
+ # go-public and the class must be marked tainted. Declare `feeds:` honestly — the whole point
26
+ # is that no single step in a bad chain looks dangerous on its own.
27
+
28
+ classes:
29
+ # ---- promotion-eligible example -------------------------------------------------
30
+ - name: dispatch-readonly-sim-local-artifact
31
+ owner: fh-meta:sim-conductor # the gate/skill that does the asking
32
+ mode: blind-target-tier-sim
33
+ target: a single local file under the current repo
34
+ capabilities: [read, dispatch] # read · local-write · network · dispatch · repo-mutation
35
+ sinks: [] # crosses nothing irreversible
36
+ feeds: [] # enables/queues nothing irreversible
37
+ promotion_eligible: true
38
+ lease_days: 30 # standing consent is a renewable lease, never permanent
39
+
40
+ # ---- NOT promotion-eligible: sink is irreversible --------------------------------
41
+ - name: npm-publish
42
+ owner: Pre-Publish Surface Gate
43
+ mode: registry-publish
44
+ target: the package tarball file set
45
+ capabilities: [read, network]
46
+ sinks: [go-public] # irreversible => never promotes, at any count
47
+ feeds: []
48
+ promotion_eligible: false
49
+ reason: "publish is effectively irreversible — a package is live the instant it ships and may
50
+ be cached or forked before any scrub (CLAUDE.md §Pre-Publish Surface Gate)."
51
+
52
+ # ---- NOT promotion-eligible: TAINTED (this is the one that looks safe) -----------
53
+ - name: local-commit
54
+ owner: FH 4-axis commit gate
55
+ mode: git-commit
56
+ target: the working tree
57
+ capabilities: [local-write, repo-mutation]
58
+ sinks: [] # committing itself is reversible
59
+ feeds: [go-public] # ...but CI/sync publishes from it
60
+ promotion_eligible: false
61
+ reason: "taint — reversible in isolation, feeds an irreversible sink. Every step in the
62
+ workflow-edit -> release-notes -> commit -> CI-publish chain is individually
63
+ reversible; the chain is not."
64
+
65
+ # ---- NOT promotion-eligible: unclassified ---------------------------------------
66
+ - name: run-arbitrary-shell
67
+ owner: (none — catch-all)
68
+ mode: unrestricted
69
+ target: unbounded
70
+ capabilities: [read, local-write, network, dispatch, repo-mutation]
71
+ sinks: [unknown] # unknown is NOT reversible
72
+ feeds: [unknown]
73
+ promotion_eligible: false
74
+ reason: "sinks cannot be enumerated, so the floor cannot be decided mechanically. A class
75
+ whose blast radius is unbounded is never a candidate, regardless of approval count."
@@ -0,0 +1,49 @@
1
+ {
2
+ "_README": [
3
+ "PreToolUse(Bash) hook snippet — TRACKED, so it arrives with a clone. Every .claude/settings*.json",
4
+ "path in this repo is gitignored (confirm with `git check-ignore -v .claude/settings.json`), so a",
5
+ "hook registration cannot itself be tracked. This is the tracked SOURCE that install-wizard merges",
6
+ "into the user's local settings. Shipping the file is not the same proposition as wiring it — that",
7
+ "gap is exactly what #204 found (the SessionStart hook existed from 07-05 and the wizard had never",
8
+ "installed it, for every new user).",
9
+ "",
10
+ "WHAT IT GUARDS: reading a verdict from the wrong end of a pipe. `cmd | tail -5; echo $?` reports",
11
+ "TAIL's status, so a FAILED gate reads as 0 — a degrade toward PASS, the direction that never",
12
+ "announces itself. Measured 6x in this project 2026-07-29..07-31.",
13
+ "",
14
+ "WHY A HOOK AND NOT A FILE LINTER: the plan of record was an S6 class in degrade_direction_scan.sh.",
15
+ "Hand-verifying every `pipe + $?` in this repo's scripts returned 7 hits, 7 of them CORRECT. True",
16
+ "positives in shipped files: 0. All 6 recurrences were in interactively-composed commands, which no",
17
+ "repo scanner reads. The guard belongs where the defect occurs — the Bash call itself.",
18
+ "",
19
+ "ADVISORY BY DESIGN: it warns and exits 0. It never blocks a Bash call, because a mis-read verdict",
20
+ "is re-runnable, and a false block on a developer's own shell trains the --no-verify reflex on the",
21
+ "hooks that DO guard irreversible surfaces (pre-push destructive-op, pre-commit confidentiality).",
22
+ "Escalate deliberately with FH_PIPE_VERDICT_BLOCK=1 if a project wants it to bite.",
23
+ "",
24
+ "Carries no private path, so it belongs in .claude/settings.json (project-local), not settings.local.json.",
25
+ "Merge, do not overwrite: preserve any PreToolUse entries the user already has; replace only the FH",
26
+ "one, keyed by script name.",
27
+ "",
28
+ "Verify after wiring (known pair, both directions):",
29
+ " bash scripts/test_pipe_verdict_guard_lanes.sh # 15 pairs, expect all hold",
30
+ " printf '%s' '{\"tool_name\":\"Bash\",\"tool_input\":{\"command\":\"g.sh | tail; rc=$?\"}}' \\",
31
+ " | bash scripts/pipe_verdict_guard.sh # expect a PIPE-VERDICT R2 warning"
32
+ ],
33
+ "project_settings_json": {
34
+ "hooks": {
35
+ "PreToolUse": [
36
+ {
37
+ "matcher": "Bash",
38
+ "hooks": [
39
+ {
40
+ "type": "command",
41
+ "command": "bash \"$CLAUDE_PROJECT_DIR/scripts/pipe_verdict_guard.sh\"",
42
+ "timeout": 5
43
+ }
44
+ ]
45
+ }
46
+ ]
47
+ }
48
+ }
49
+ }