@lannguyensi/harness 0.43.0 → 0.45.0
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/CHANGELOG.md +1248 -0
- package/dist/cli/adopt/derive.d.ts +16 -7
- package/dist/cli/adopt/derive.js +19 -2
- package/dist/cli/adopt/derive.js.map +1 -1
- package/dist/cli/adopt/index.js +18 -3
- package/dist/cli/adopt/index.js.map +1 -1
- package/dist/cli/apply/apply.d.ts +1 -0
- package/dist/cli/apply/apply.js +62 -6
- package/dist/cli/apply/apply.js.map +1 -1
- package/dist/cli/apply/generate-codex-config.js +5 -6
- package/dist/cli/apply/generate-codex-config.js.map +1 -1
- package/dist/cli/apply/generate-opencode-config.d.ts +58 -0
- package/dist/cli/apply/generate-opencode-config.js +210 -0
- package/dist/cli/apply/generate-opencode-config.js.map +1 -0
- package/dist/cli/apply/generate-settings.d.ts +90 -32
- package/dist/cli/apply/generate-settings.js +133 -10
- package/dist/cli/apply/generate-settings.js.map +1 -1
- package/dist/cli/apply/index.d.ts +2 -1
- package/dist/cli/apply/index.js +2 -1
- package/dist/cli/apply/index.js.map +1 -1
- package/dist/cli/approve/stdin-report.d.ts +15 -0
- package/dist/cli/approve/stdin-report.js +1 -1
- package/dist/cli/approve/stdin-report.js.map +1 -1
- package/dist/cli/approve/understanding.d.ts +75 -0
- package/dist/cli/approve/understanding.js +150 -2
- package/dist/cli/approve/understanding.js.map +1 -1
- package/dist/cli/audit.d.ts +1 -1
- package/dist/cli/audit.js +3 -2
- package/dist/cli/audit.js.map +1 -1
- package/dist/cli/doctor/claude-mcp.d.ts +19 -1
- package/dist/cli/doctor/claude-mcp.js +36 -10
- package/dist/cli/doctor/claude-mcp.js.map +1 -1
- package/dist/cli/doctor/codex.d.ts +3 -1
- package/dist/cli/doctor/codex.js +6 -9
- package/dist/cli/doctor/codex.js.map +1 -1
- package/dist/cli/doctor/format.js +94 -8
- package/dist/cli/doctor/format.js.map +1 -1
- package/dist/cli/doctor/index.d.ts +23 -0
- package/dist/cli/doctor/index.js +106 -2
- package/dist/cli/doctor/index.js.map +1 -1
- package/dist/cli/doctor/opencode.d.ts +24 -0
- package/dist/cli/doctor/opencode.js +107 -0
- package/dist/cli/doctor/opencode.js.map +1 -0
- package/dist/cli/doctor/target-checks.d.ts +11 -0
- package/dist/cli/doctor/target-checks.js +25 -0
- package/dist/cli/doctor/target-checks.js.map +1 -0
- package/dist/cli/doctor/toolchain-parity.d.ts +63 -0
- package/dist/cli/doctor/toolchain-parity.js +154 -0
- package/dist/cli/doctor/toolchain-parity.js.map +1 -0
- package/dist/cli/doctor/types.d.ts +85 -1
- package/dist/cli/doctor/types.js +1 -1
- package/dist/cli/doctor/types.js.map +1 -1
- package/dist/cli/doctor/understanding-mode-env.d.ts +39 -0
- package/dist/cli/doctor/understanding-mode-env.js +83 -0
- package/dist/cli/doctor/understanding-mode-env.js.map +1 -0
- package/dist/cli/dry-run.js +34 -13
- package/dist/cli/dry-run.js.map +1 -1
- package/dist/cli/explain.d.ts +1 -1
- package/dist/cli/explain.js +1 -1
- package/dist/cli/explain.js.map +1 -1
- package/dist/cli/index.js +62 -7
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/init/composer.js +36 -13
- package/dist/cli/init/composer.js.map +1 -1
- package/dist/cli/init/interactive.d.ts +7 -4
- package/dist/cli/init/interactive.js +79 -27
- package/dist/cli/init/interactive.js.map +1 -1
- package/dist/cli/init/profiles.d.ts +2 -2
- package/dist/cli/init/profiles.js +32 -1
- package/dist/cli/init/profiles.js.map +1 -1
- package/dist/cli/init/templates.d.ts +2 -1
- package/dist/cli/init/templates.js +136 -24
- package/dist/cli/init/templates.js.map +1 -1
- package/dist/cli/pack/approve-escape.js +69 -8
- package/dist/cli/pack/approve-escape.js.map +1 -1
- package/dist/cli/pack/hook-branch-protection.js +1 -1
- package/dist/cli/pack/hook-branch-protection.js.map +1 -1
- package/dist/cli/pack/hook-codex-pre-tool-use.js +27 -1
- package/dist/cli/pack/hook-codex-pre-tool-use.js.map +1 -1
- package/dist/cli/pack/hook-post-merge-gate.js +44 -26
- package/dist/cli/pack/hook-post-merge-gate.js.map +1 -1
- package/dist/cli/pack/hook-pre-tool-use.js +91 -4
- package/dist/cli/pack/hook-pre-tool-use.js.map +1 -1
- package/dist/cli/pack/hook-solution-acceptance.js +19 -2
- package/dist/cli/pack/hook-solution-acceptance.js.map +1 -1
- package/dist/cli/pack/understanding-report-schema-hint.d.ts +13 -0
- package/dist/cli/pack/understanding-report-schema-hint.js +52 -3
- package/dist/cli/pack/understanding-report-schema-hint.js.map +1 -1
- package/dist/cli/pause/index.js +1 -1
- package/dist/cli/pause/index.js.map +1 -1
- package/dist/cli/policy/intercept.d.ts +148 -0
- package/dist/cli/policy/intercept.js +538 -79
- package/dist/cli/policy/intercept.js.map +1 -1
- package/dist/cli/session-start/index.d.ts +12 -3
- package/dist/cli/session-start/index.js +67 -11
- package/dist/cli/session-start/index.js.map +1 -1
- package/dist/cli/session-start/stale-base-check.d.ts +71 -0
- package/dist/cli/session-start/stale-base-check.js +379 -0
- package/dist/cli/session-start/stale-base-check.js.map +1 -0
- package/dist/cli/session-start/toolchain-parity.d.ts +142 -8
- package/dist/cli/session-start/toolchain-parity.js +244 -36
- package/dist/cli/session-start/toolchain-parity.js.map +1 -1
- package/dist/cli/smoke/assertions.d.ts +9 -2
- package/dist/cli/smoke/assertions.js +9 -2
- package/dist/cli/smoke/assertions.js.map +1 -1
- package/dist/cli/uninstall/index.js +18 -2
- package/dist/cli/uninstall/index.js.map +1 -1
- package/dist/cli/validate/checks.d.ts +3 -1
- package/dist/cli/validate/checks.js +298 -3
- package/dist/cli/validate/checks.js.map +1 -1
- package/dist/io/claude-mcp.d.ts +122 -1
- package/dist/io/claude-mcp.js +214 -12
- package/dist/io/claude-mcp.js.map +1 -1
- package/dist/io/duration.js.map +1 -0
- package/dist/io/expand-home.js.map +1 -0
- package/dist/io/extract.js.map +1 -0
- package/dist/io/harness-lock.js +1 -1
- package/dist/io/harness-lock.js.map +1 -1
- package/dist/{runtime → io}/ledger-record.d.ts +1 -1
- package/dist/io/ledger-record.js.map +1 -0
- package/dist/policies/index.d.ts +2 -2
- package/dist/policies/index.js +2 -2
- package/dist/policies/index.js.map +1 -1
- package/dist/policies/ledger-client.js +10 -6
- package/dist/policies/ledger-client.js.map +1 -1
- package/dist/policies/producers.js +1 -1
- package/dist/policies/producers.js.map +1 -1
- package/dist/policies/requires.js +2 -2
- package/dist/policies/requires.js.map +1 -1
- package/dist/policy-packs/builtin/branch-protection.js +28 -3
- package/dist/policy-packs/builtin/branch-protection.js.map +1 -1
- package/dist/policy-packs/builtin/post-merge-gate-runtime.d.ts +90 -10
- package/dist/policy-packs/builtin/post-merge-gate-runtime.js +112 -16
- package/dist/policy-packs/builtin/post-merge-gate-runtime.js.map +1 -1
- package/dist/policy-packs/builtin/post-merge-gate.js +84 -31
- package/dist/policy-packs/builtin/post-merge-gate.js.map +1 -1
- package/dist/policy-packs/builtin/solution-acceptance-runtime.d.ts +152 -8
- package/dist/policy-packs/builtin/solution-acceptance-runtime.js +260 -13
- package/dist/policy-packs/builtin/solution-acceptance-runtime.js.map +1 -1
- package/dist/policy-packs/builtin/solution-acceptance.js +17 -3
- package/dist/policy-packs/builtin/solution-acceptance.js.map +1 -1
- package/dist/policy-packs/builtin/understanding-before-execution/active-claim.d.ts +25 -0
- package/dist/policy-packs/builtin/understanding-before-execution/active-claim.js +89 -0
- package/dist/policy-packs/builtin/understanding-before-execution/active-claim.js.map +1 -0
- package/dist/policy-packs/builtin/understanding-before-execution/index.d.ts +13 -0
- package/dist/policy-packs/builtin/understanding-before-execution/index.js +39 -0
- package/dist/policy-packs/builtin/understanding-before-execution/index.js.map +1 -0
- package/dist/policy-packs/builtin/understanding-before-execution/ledger.d.ts +25 -0
- package/dist/policy-packs/builtin/understanding-before-execution/ledger.js +56 -0
- package/dist/policy-packs/builtin/understanding-before-execution/ledger.js.map +1 -0
- package/dist/policy-packs/builtin/understanding-before-execution/lifecycle.d.ts +20 -0
- package/dist/policy-packs/builtin/understanding-before-execution/lifecycle.js +79 -0
- package/dist/policy-packs/builtin/understanding-before-execution/lifecycle.js.map +1 -0
- package/dist/policy-packs/builtin/understanding-before-execution/markers.d.ts +103 -0
- package/dist/policy-packs/builtin/understanding-before-execution/markers.js +203 -0
- package/dist/policy-packs/builtin/understanding-before-execution/markers.js.map +1 -0
- package/dist/policy-packs/builtin/understanding-before-execution/persisted-reports.d.ts +177 -0
- package/dist/policy-packs/builtin/understanding-before-execution/persisted-reports.js +309 -0
- package/dist/policy-packs/builtin/understanding-before-execution/persisted-reports.js.map +1 -0
- package/dist/policy-packs/builtin/understanding-before-execution/post-tool-use-boundary.d.ts +106 -0
- package/dist/policy-packs/builtin/understanding-before-execution/post-tool-use-boundary.js +209 -0
- package/dist/policy-packs/builtin/understanding-before-execution/post-tool-use-boundary.js.map +1 -0
- package/dist/policy-packs/builtin/understanding-before-execution/task-markers.d.ts +78 -0
- package/dist/policy-packs/builtin/understanding-before-execution/task-markers.js +185 -0
- package/dist/policy-packs/builtin/understanding-before-execution/task-markers.js.map +1 -0
- package/dist/policy-packs/builtin/understanding-before-execution-runtime.d.ts +1 -536
- package/dist/policy-packs/builtin/understanding-before-execution-runtime.js +9 -1095
- package/dist/policy-packs/builtin/understanding-before-execution-runtime.js.map +1 -1
- package/dist/policy-packs/builtin/understanding-before-execution.d.ts +109 -1
- package/dist/policy-packs/builtin/understanding-before-execution.js +183 -15
- package/dist/policy-packs/builtin/understanding-before-execution.js.map +1 -1
- package/dist/policy-packs/registry.d.ts +11 -0
- package/dist/policy-packs/registry.js +1 -1
- package/dist/policy-packs/registry.js.map +1 -1
- package/dist/policy-packs/runtime.d.ts +1 -1
- package/dist/policy-packs/runtime.js +10 -1
- package/dist/policy-packs/runtime.js.map +1 -1
- package/dist/runtime/agent-facing.js +1 -1
- package/dist/runtime/agent-facing.js.map +1 -1
- package/dist/runtime/bash-match-facts.d.ts +9 -7
- package/dist/runtime/bash-match-facts.js +9 -7
- package/dist/runtime/bash-match-facts.js.map +1 -1
- package/dist/runtime/bash-match-registry.d.ts +75 -38
- package/dist/runtime/bash-match-registry.js +279 -232
- package/dist/runtime/bash-match-registry.js.map +1 -1
- package/dist/runtime/bash-prefix-parse.d.ts +7 -0
- package/dist/runtime/bash-prefix-parse.js +205 -17
- package/dist/runtime/bash-prefix-parse.js.map +1 -1
- package/dist/runtime/command-normalize.d.ts +429 -4
- package/dist/runtime/command-normalize.js +1146 -67
- package/dist/runtime/command-normalize.js.map +1 -1
- package/dist/runtime/hermetic-spawn-guard.js +6 -0
- package/dist/runtime/hermetic-spawn-guard.js.map +1 -1
- package/dist/runtime/index.d.ts +2 -2
- package/dist/runtime/index.js +2 -2
- package/dist/runtime/index.js.map +1 -1
- package/dist/runtime/intercept.d.ts +247 -4
- package/dist/runtime/intercept.js +553 -61
- package/dist/runtime/intercept.js.map +1 -1
- package/dist/runtime/ledger-add.js +1 -1
- package/dist/runtime/ledger-add.js.map +1 -1
- package/dist/runtime/read-only-bash.js +356 -26
- package/dist/runtime/read-only-bash.js.map +1 -1
- package/dist/runtime/shell-word.d.ts +21 -0
- package/dist/runtime/shell-word.js +242 -0
- package/dist/runtime/shell-word.js.map +1 -0
- package/dist/schema/doctor.d.ts +25 -0
- package/dist/schema/doctor.js +23 -0
- package/dist/schema/doctor.js.map +1 -0
- package/dist/schema/extract.js +1 -1
- package/dist/schema/extract.js.map +1 -1
- package/dist/schema/index.d.ts +76 -0
- package/dist/schema/index.js +9 -0
- package/dist/schema/index.js.map +1 -1
- package/dist/schema/requires.js +2 -2
- package/dist/schema/requires.js.map +1 -1
- package/dist/schema/risk.d.ts +7 -0
- package/dist/schema/risk.js +23 -0
- package/dist/schema/risk.js.map +1 -1
- package/dist/schema/stale-base-check.d.ts +18 -0
- package/dist/schema/stale-base-check.js +37 -0
- package/dist/schema/stale-base-check.js.map +1 -0
- package/dist/schema/toolchain-parity.d.ts +3 -0
- package/dist/schema/toolchain-parity.js +13 -0
- package/dist/schema/toolchain-parity.js.map +1 -1
- package/package.json +5 -2
- package/dist/policies/duration.js.map +0 -1
- package/dist/policies/extract.js.map +0 -1
- package/dist/runtime/expand-home.js.map +0 -1
- package/dist/runtime/ledger-record.js.map +0 -1
- /package/dist/{policies → io}/duration.d.ts +0 -0
- /package/dist/{policies → io}/duration.js +0 -0
- /package/dist/{runtime → io}/expand-home.d.ts +0 -0
- /package/dist/{runtime → io}/expand-home.js +0 -0
- /package/dist/{policies → io}/extract.d.ts +0 -0
- /package/dist/{policies → io}/extract.js +0 -0
- /package/dist/{runtime → io}/ledger-record.js +0 -0
|
@@ -29,10 +29,24 @@
|
|
|
29
29
|
// scope" section). Read-only git (status/log/diff/branch) and
|
|
30
30
|
// unrelated shell are never touched.
|
|
31
31
|
//
|
|
32
|
-
// -
|
|
33
|
-
//
|
|
34
|
-
//
|
|
35
|
-
//
|
|
32
|
+
// - DENY WINS over the escape allowlist (task 19356be7, REVERSES the
|
|
33
|
+
// original escape-first decision): the blocker classifies a command as
|
|
34
|
+
// gate-eligible iff the curated mutation matcher fires
|
|
35
|
+
// (`isGateEligibleCommand` below). The escape allowlist no longer
|
|
36
|
+
// short-circuits the whole command — under escape-first, any chained
|
|
37
|
+
// command containing an escape verb anywhere (`harness preflight &&
|
|
38
|
+
// git push origin master`, the documented normal workflow) skipped the
|
|
39
|
+
// gate entirely. Measured before changing, over a 66-row corpus with
|
|
40
|
+
// bash ground truth: per-segment evaluation and deny-wins are
|
|
41
|
+
// 0-divergent — a consequence of the pinned verb disjointness between
|
|
42
|
+
// the escape and deny lists — so the simplest (deny-wins) is
|
|
43
|
+
// implemented. The third candidate, escape-at-start-only, was measured
|
|
44
|
+
// and REJECTED: it diverges on 26 rows and still exempts three of the
|
|
45
|
+
// four lines this task exists to close (`git switch master && git push
|
|
46
|
+
// origin master` and friends). The recovery-never-starved property is preserved
|
|
47
|
+
// structurally: a non-eligible command is allowed before any manifest
|
|
48
|
+
// load or ledger query, exactly where the escape short-circuit used to
|
|
49
|
+
// sit (see hook-post-merge-gate.ts for the ordering).
|
|
36
50
|
//
|
|
37
51
|
// - Fails OPEN (allows) when the ledger is unreachable, unlike
|
|
38
52
|
// branch-protection's fail-closed posture: without the ledger,
|
|
@@ -70,33 +84,80 @@ export function buildMergedTagContent(args) {
|
|
|
70
84
|
* indirection defeats this match before the producer ever runs — not
|
|
71
85
|
* attempted to close here (the MCP merge path, `pull_requests_merge`, is
|
|
72
86
|
* the other documented gap; see the pack's instructions.md).
|
|
87
|
+
*
|
|
88
|
+
* Task 76671e5a: bare `&` added to the boundary alternation (`&&` kept to
|
|
89
|
+
* its left only to mirror `src/runtime/command-normalize.ts`'s alternation
|
|
90
|
+
* order for readability — NOT because order is load-bearing here, unlike
|
|
91
|
+
* that module). Same fix as `d834a065` applied to every policy trigger —
|
|
92
|
+
* bash starts a new command after a single `&`, so `A=x&gh pr merge` used
|
|
93
|
+
* to miss this trigger entirely and the producer silently never recorded
|
|
94
|
+
* the merged-tip fact for that spelling. Broadening a TRIGGER only widens
|
|
95
|
+
* when the producer fires (a strictly safer direction, mirrors the
|
|
96
|
+
* deny-side reasoning below), unlike the escape allowlist further down.
|
|
97
|
+
* Measured: swapping to `&|&&` produces a byte-identical match set for this
|
|
98
|
+
* matcher (a `RegExp.test` existential check over every start offset, not a
|
|
99
|
+
* segmenter — the reasoning that makes order matter in
|
|
100
|
+
* `command-normalize.ts`'s `BOUNDARY_RE`/`AMP_BOUNDARY_RE` does not transfer
|
|
101
|
+
* to a plain `.test()` matcher like this one).
|
|
73
102
|
*/
|
|
74
|
-
export const GH_PR_MERGE_BASH_RE = /(?:^|\n
|
|
103
|
+
export const GH_PR_MERGE_BASH_RE = /(?:^|\n|;|\||&&|&|\()\s*(?:\w+=\S+\s+)*gh\s+pr\s+merge\b/;
|
|
75
104
|
/**
|
|
76
105
|
* Blocker deny-scope v1 (03-decisions.md): the curated mutation command
|
|
77
106
|
* list. Deliberately NOT "every Bash command" — read-only git
|
|
78
107
|
* (status/log/diff/branch) and unrelated shell stay unaffected. Same
|
|
79
108
|
* anchoring convention as `GH_PR_MERGE_BASH_RE`.
|
|
109
|
+
*
|
|
110
|
+
* Task 76671e5a: bare `&` added to the boundary alternation, same reasoning
|
|
111
|
+
* as `GH_PR_MERGE_BASH_RE` above — this is a DENY-scope matcher, so
|
|
112
|
+
* broadening it is the STRICTER direction (more commands recognized as
|
|
113
|
+
* curated mutations, none dropped). Verified disjoint from the escape
|
|
114
|
+
* allowlist below on a single-command corpus (no verb overlap), so this
|
|
115
|
+
* broadening cannot lock out the documented recovery path.
|
|
80
116
|
*/
|
|
81
|
-
export const CURATED_MUTATION_BASH_RE = /(?:^|\n
|
|
117
|
+
export const CURATED_MUTATION_BASH_RE = /(?:^|\n|;|\||&&|&|\()\s*(?:\w+=\S+\s+)*(?:git(?:\s+-C\s+\S+)?\s+(?:commit|add|push|merge|rebase|cherry-pick|revert|reset|stash\s+(?:pop|apply))\b|gh\s+pr\s+(?:create|merge)\b)/;
|
|
82
118
|
/**
|
|
83
119
|
* Escape allowlist, git verbs (03-decisions.md): the exact recovery path
|
|
84
|
-
* the deny message recommends, plus read-only stash inspection.
|
|
85
|
-
*
|
|
86
|
-
*
|
|
120
|
+
* the deny message recommends, plus read-only stash inspection. Since task
|
|
121
|
+
* 19356be7 this list no longer short-circuits the blocker — the deny
|
|
122
|
+
* match decides gate-eligibility, and every one of these verbs stays free
|
|
123
|
+
* simply because it is not a curated mutation (verb disjointness, pinned).
|
|
124
|
+
* The list remains the deny message's recovery vocabulary and a
|
|
125
|
+
* diagnostics aid.
|
|
126
|
+
*
|
|
127
|
+
* Task 76671e5a, DELIBERATELY LEFT on the old (no bare-`&`) boundary
|
|
128
|
+
* alphabet: this is an ALLOW-side matcher, so broadening it is the LOOSER
|
|
129
|
+
* direction. Its remaining consumers (diagnostics, docs interpolation)
|
|
130
|
+
* still must not silently widen what reads as "recovery". Pinned by a
|
|
131
|
+
* regression test asserting this regex's source never gains the `&|`
|
|
132
|
+
* boundary token.
|
|
87
133
|
*/
|
|
88
134
|
export const ESCAPE_GIT_BASH_RE = /(?:^|\n|;|\||&&|\()\s*(?:\w+=\S+\s+)*git(?:\s+-C\s+\S+)?\s+(?:switch\b|checkout\b|pull\b|fetch\b|branch\s+-(?:d|D)\b|stash\s+(?:list|show)\b)/;
|
|
89
135
|
/**
|
|
90
|
-
* Escape allowlist, harness verbs (03-decisions.md): any invocation
|
|
91
|
-
* harness's own CLI (recovery commands like `harness session-start
|
|
92
|
-
* branch-check`, diagnostics, a future self-check)
|
|
93
|
-
*
|
|
94
|
-
*
|
|
136
|
+
* Escape allowlist, harness verbs (03-decisions.md): names any invocation
|
|
137
|
+
* of harness's own CLI (recovery commands like `harness session-start
|
|
138
|
+
* branch-check`, diagnostics, a future self-check), regardless of
|
|
139
|
+
* spelling — mirrors the `npx` / absolute-path / `./node_modules/.bin`
|
|
140
|
+
* robustness the `deny-kill-switch-bash` regex in
|
|
95
141
|
* `src/cli/init/templates.ts` already established for the same class of
|
|
96
|
-
* bypass concern.
|
|
142
|
+
* bypass concern. Since task 19356be7 its only job is the deny message's
|
|
143
|
+
* / diagnostics' vocabulary: a bare harness command stays free through
|
|
144
|
+
* verb disjointness with the curated deny list, not through a
|
|
145
|
+
* short-circuit.
|
|
146
|
+
*
|
|
147
|
+
* Task 76671e5a: DELIBERATELY LEFT narrow, same reasoning as
|
|
148
|
+
* `ESCAPE_GIT_BASH_RE` immediately above — an allow-side matcher, so
|
|
149
|
+
* broadening its boundary alphabet is the loosening direction.
|
|
97
150
|
*/
|
|
98
151
|
export const ESCAPE_HARNESS_BASH_RE = /(?:^|\n|;|\||&&|\()\s*(?:\w+=\S+\s+)*(?:npx\s+|\S*\/)?harness\b/;
|
|
99
|
-
/**
|
|
152
|
+
/**
|
|
153
|
+
* True when `command` matches any escape pattern. Since task 19356be7
|
|
154
|
+
* this is DIAGNOSTICS ONLY — the blocker's decision is
|
|
155
|
+
* `isGateEligibleCommand` below; when escape and deny both match, deny
|
|
156
|
+
* wins. (Under the original escape-first ordering this function was the
|
|
157
|
+
* blocker's first, unconditional check, which let `harness preflight &&
|
|
158
|
+
* git push origin master` — the documented normal workflow — skip the
|
|
159
|
+
* gate entirely.)
|
|
160
|
+
*/
|
|
100
161
|
export function isEscapeCommand(command) {
|
|
101
162
|
return ESCAPE_GIT_BASH_RE.test(command) || ESCAPE_HARNESS_BASH_RE.test(command);
|
|
102
163
|
}
|
|
@@ -104,6 +165,41 @@ export function isEscapeCommand(command) {
|
|
|
104
165
|
export function isCuratedMutationCommand(command) {
|
|
105
166
|
return CURATED_MUTATION_BASH_RE.test(command);
|
|
106
167
|
}
|
|
168
|
+
/**
|
|
169
|
+
* The blocker's single classification entry point (task 19356be7): a
|
|
170
|
+
* command is gate-eligible iff the curated mutation matcher fires.
|
|
171
|
+
* Everything else — the whole recovery vocabulary, read-only git,
|
|
172
|
+
* unrelated shell — is allowed by the hook BEFORE any manifest load or
|
|
173
|
+
* ledger query, preserving the recovery-never-starved property the
|
|
174
|
+
* escape-first ordering used to provide.
|
|
175
|
+
*
|
|
176
|
+
* A thin wrapper over `isCuratedMutationCommand` by DELIBERATE choice,
|
|
177
|
+
* not by accident. An earlier iteration of this task pre-processed the
|
|
178
|
+
* command here, stripping the bodies of quoted-delimiter heredocs fed to
|
|
179
|
+
* a harness invocation, so that an `harness approve understanding <<'…'`
|
|
180
|
+
* report body mentioning a mutation verb would not become gate-eligible.
|
|
181
|
+
* Two review rounds found two successive families of UNDER-blocks in that
|
|
182
|
+
* pre-processing (bodies consumed that bash really executes), both rooted
|
|
183
|
+
* in the same cause: deciding where a heredoc body BEGINS requires
|
|
184
|
+
* re-implementing bash's quote and word grammar on the permissive side of
|
|
185
|
+
* a security boundary. Tasks `dbc6d303` and `5b1b24fb` had already been
|
|
186
|
+
* halted for that same reason. It was therefore removed rather than
|
|
187
|
+
* patched a third time; see
|
|
188
|
+
* `.ai/runs/2026-08-02-post-merge-gate-escape-precedence/03-decisions.md`
|
|
189
|
+
* D5/D6 for the measurement history, and task `5b1b24fb` for the real
|
|
190
|
+
* fix, which belongs at the `bash_match` layer for every surface at once.
|
|
191
|
+
*
|
|
192
|
+
* Accepted consequence, OVER-block direction: a harness report heredoc
|
|
193
|
+
* whose body carries a boundary-anchored mutation verb IS gate-eligible.
|
|
194
|
+
* It only denies while the branch tip equals a recorded merged tip, and
|
|
195
|
+
* `git switch <default>` — the gate's own recommended recovery — stays
|
|
196
|
+
* free. Same residual class as the quoted-text false positives this
|
|
197
|
+
* change already accepts. Do NOT reintroduce a strip here without a
|
|
198
|
+
* design that does not re-derive bash's grammar.
|
|
199
|
+
*/
|
|
200
|
+
export function isGateEligibleCommand(command) {
|
|
201
|
+
return isCuratedMutationCommand(command);
|
|
202
|
+
}
|
|
107
203
|
/**
|
|
108
204
|
* Defensive `tool_output.exit_code` reader for the PostToolUse Bash
|
|
109
205
|
* payload — Contract A. Returns `null` on ANY shape other than a plain
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"post-merge-gate-runtime.js","sourceRoot":"","sources":["../../../src/policy-packs/builtin/post-merge-gate-runtime.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,EAAE;AACF,2EAA2E;AAC3E,yEAAyE;AACzE,yEAAyE;AACzE,0EAA0E;AAC1E,sEAAsE;AACtE,yEAAyE;AACzE,iDAAiD;AACjD,EAAE;AACF,6CAA6C;AAC7C,iEAAiE;AACjE,EAAE;AACF,qEAAqE;AACrE,wEAAwE;AACxE,wEAAwE;AACxE,yEAAyE;AACzE,wEAAwE;AACxE,0EAA0E;AAC1E,oEAAoE;AACpE,yEAAyE;AACzE,kEAAkE;AAClE,qEAAqE;AACrE,EAAE;AACF,yEAAyE;AACzE,0EAA0E;AAC1E,0DAA0D;AAC1D,sEAAsE;AACtE,kEAAkE;AAClE,yCAAyC;AACzC,EAAE;AACF,
|
|
1
|
+
{"version":3,"file":"post-merge-gate-runtime.js","sourceRoot":"","sources":["../../../src/policy-packs/builtin/post-merge-gate-runtime.ts"],"names":[],"mappings":"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"}
|
|
@@ -38,11 +38,14 @@
|
|
|
38
38
|
// when the CURRENT branch tip still equals a recorded merged tip
|
|
39
39
|
// for this repo+branch — an exact sha match, not ancestry, so a new
|
|
40
40
|
// commit (moving the tip) or a recycled branch name (a different
|
|
41
|
-
// tip) both fall silently outside the gate.
|
|
42
|
-
//
|
|
43
|
-
//
|
|
44
|
-
//
|
|
45
|
-
// be starved by an unrelated failure
|
|
41
|
+
// tip) both fall silently outside the gate. GATE-ELIGIBILITY is
|
|
42
|
+
// classified FIRST, before any manifest load or ledger query, so
|
|
43
|
+
// the recovery vocabulary (git switch/checkout/pull/fetch, git
|
|
44
|
+
// branch -d/-D, git stash list/show, any `harness ...` command) can
|
|
45
|
+
// never be starved by an unrelated failure — none of those verbs is
|
|
46
|
+
// a curated mutation. Task 19356be7: DENY WINS when one command
|
|
47
|
+
// chains recovery vocabulary with a curated mutation; the escape
|
|
48
|
+
// list no longer short-circuits the whole command string.
|
|
46
49
|
//
|
|
47
50
|
// Fails OPEN when the ledger is unreachable (unlike branch-protection's
|
|
48
51
|
// fail-closed posture): without the ledger, "merged" and "not merged"
|
|
@@ -60,11 +63,14 @@
|
|
|
60
63
|
// higher-risk gate; see templates.ts for the rationale), unlike
|
|
61
64
|
// branch-protection's default-enabled precedent.
|
|
62
65
|
//
|
|
63
|
-
// Claude Code only in v1 (no Codex adapter): both hooks
|
|
64
|
-
// `Bash` tool name and classify by command text, a
|
|
65
|
-
// no established Codex-adapter parity yet (mirrors
|
|
66
|
-
// which ships with no Codex variant either)
|
|
67
|
-
// under
|
|
66
|
+
// Claude Code only in v1 (no Codex or opencode adapter): both hooks
|
|
67
|
+
// match on the `Bash` tool name and classify by command text, a
|
|
68
|
+
// mechanism that has no established Codex-adapter parity yet (mirrors
|
|
69
|
+
// `solution-acceptance`, which ships with no Codex variant either) and
|
|
70
|
+
// no meaning at all under opencode (no declarative hook/event field to
|
|
71
|
+
// match against). `resolve()` warns whenever `runtime !== "claude-code"`
|
|
72
|
+
// (batch18 fix-round, task f34eb233 — widened from a codex-only check
|
|
73
|
+
// so opencode gets the same warning).
|
|
68
74
|
import { z } from "zod";
|
|
69
75
|
import { PolicyUxSchema } from "../../schema/policies.js";
|
|
70
76
|
import { DEFAULT_RUNTIME } from "../runtime.js";
|
|
@@ -126,8 +132,19 @@ function buildHooks() {
|
|
|
126
132
|
match: "Bash",
|
|
127
133
|
command: BLOCKER_COMMAND,
|
|
128
134
|
blocking: "hard",
|
|
129
|
-
|
|
130
|
-
|
|
135
|
+
// 15000 (task 7bf47554): the blocker's own ledger query
|
|
136
|
+
// (queryLedgerByTag, src/cli/pack/hook-post-merge-gate.ts) is bounded
|
|
137
|
+
// by the grounding-mcp server's own `health.timeout_ms` (default
|
|
138
|
+
// 5000ms) — the same number the old budget_ms=5000 used for the
|
|
139
|
+
// outer Claude Code kill-timeout, leaving no margin. This gate
|
|
140
|
+
// already fails OPEN by design on a genuinely unreachable ledger
|
|
141
|
+
// (unlike branch-protection); raising the outer timeout does not
|
|
142
|
+
// change that posture, it only gives a merely-SLOW (not dead) ledger
|
|
143
|
+
// room to answer before the outer timeout could fire first and kill
|
|
144
|
+
// the subprocess mid-query, consistent with the other ledger-backed
|
|
145
|
+
// pack blockers.
|
|
146
|
+
budget_ms: 15000,
|
|
147
|
+
description: "Blocker: deny curated history-mutating Bash commands (git commit/add/push/merge/rebase/cherry-pick/revert/reset/stash pop|apply, gh pr create/merge) when the current branch tip matches a recorded merged tip. The recovery vocabulary (git switch/checkout/pull/fetch, git branch -d/-D, git stash list/show, any `harness ...` command) always passes as its own command, classified before any manifest or ledger access; chaining one of those with a curated mutation does NOT exempt the mutation. Fails open when the ledger is unreachable.",
|
|
131
148
|
},
|
|
132
149
|
];
|
|
133
150
|
}
|
|
@@ -142,7 +159,11 @@ function buildInstructions(pack, runtime) {
|
|
|
142
159
|
|
|
143
160
|
## Runtime
|
|
144
161
|
|
|
145
|
-
${runtime}${runtime === "codex"
|
|
162
|
+
${runtime}${runtime === "codex"
|
|
163
|
+
? " (UNSUPPORTED — see \"Known gaps\" below; both hooks assume the Claude Code Bash tool surface)"
|
|
164
|
+
: runtime === "opencode"
|
|
165
|
+
? " (UNSUPPORTED — see \"Known gaps\" below; opencode has no declarative hook/event wiring, so neither hook is projected into any opencode artefact)"
|
|
166
|
+
: ""}
|
|
146
167
|
|
|
147
168
|
## Trigger + signals
|
|
148
169
|
|
|
@@ -185,12 +206,17 @@ ${runtime}${runtime === "codex" ? " (UNSUPPORTED — see \"Known gaps\" below; b
|
|
|
185
206
|
|
|
186
207
|
2. \`PreToolUse\` blocker (\`${BLOCKER_COMMAND}\`, blocking: hard) on
|
|
187
208
|
\`Bash\`: checked in this order —
|
|
188
|
-
a. **
|
|
189
|
-
load or ledger query):
|
|
190
|
-
\`${
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
209
|
+
a. **Gate-eligibility classification first, unconditionally** (before
|
|
210
|
+
any manifest load or ledger query): the curated mutation matcher
|
|
211
|
+
\`${CURATED_MUTATION_BASH_RE.source}\`. Commands it
|
|
212
|
+
does not match pass through untouched — that unconditionally covers the whole recovery
|
|
213
|
+
vocabulary (\`${ESCAPE_GIT_BASH_RE.source}\` and
|
|
214
|
+
\`${ESCAPE_HARNESS_BASH_RE.source}\`), since none of those verbs are
|
|
215
|
+
curated mutations. **Deny wins over the recovery vocabulary** when
|
|
216
|
+
one command chains both (task 19356be7): \`harness preflight &&
|
|
217
|
+
git push\` is judged by its push half, not exempted by its
|
|
218
|
+
preflight half.
|
|
219
|
+
b. **Ledger check**: denies only when the current branch tip exactly
|
|
194
220
|
equals a recorded \`${MERGED_TAG_PREFIX}:<repo>:<branch>:<sha>\` fact
|
|
195
221
|
for this repo+branch. A new commit moves the tip (gate falls
|
|
196
222
|
silent — that's legitimate continued work); a recycled branch name
|
|
@@ -204,9 +230,12 @@ ${runtime}${runtime === "codex" ? " (UNSUPPORTED — see \"Known gaps\" below; b
|
|
|
204
230
|
\`git branch -d <branch>\` to clean up the merged local branch.
|
|
205
231
|
- **Read-only / inspection commands** (\`git switch\`/\`checkout\`,
|
|
206
232
|
\`pull\`/\`fetch\`, \`branch -d\`/\`-D\`, \`stash list\`/\`show\`, and any
|
|
207
|
-
\`harness ...\` invocation) always pass
|
|
208
|
-
reachability — the gate can never itself become
|
|
209
|
-
recovery path is unavailable.
|
|
233
|
+
\`harness ...\` invocation) always pass **as their own commands**,
|
|
234
|
+
independent of ledger reachability — the gate can never itself become
|
|
235
|
+
the reason the recovery path is unavailable. Chaining one of them with
|
|
236
|
+
a curated mutation does NOT exempt the mutation (the pre-19356be7
|
|
237
|
+
escape-first ordering did, which let the documented
|
|
238
|
+
preflight-then-push workflow skip the gate entirely).
|
|
210
239
|
|
|
211
240
|
## Known gaps (documented, not attempted in v1)
|
|
212
241
|
|
|
@@ -225,13 +254,19 @@ ${runtime}${runtime === "codex" ? " (UNSUPPORTED — see \"Known gaps\" below; b
|
|
|
225
254
|
commands on a just-merged branch (e.g. hand-editing files outside
|
|
226
255
|
git's own history primitives) are out of v1 scope, mirroring
|
|
227
256
|
branch-protection's documented scope-cut.
|
|
228
|
-
- **Chained-escape bypass
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
257
|
+
- **Chained-escape bypass — CLOSED (task 19356be7)**: the original
|
|
258
|
+
escape-first ordering matched the escape list against the WHOLE
|
|
259
|
+
command string, so a curated mutation chained with an escape verb
|
|
260
|
+
(\`harness preflight && git push\`, \`git commit -am x && git switch
|
|
261
|
+
main\`) skipped the gate entirely. The blocker now classifies by the
|
|
262
|
+
curated mutation match alone (deny wins; measured 0-divergent from
|
|
263
|
+
per-clause evaluation thanks to the escape/deny verb disjointness —
|
|
264
|
+
escape-at-start-only was measured and rejected, it still exempts three
|
|
265
|
+
of the four lines this closed).
|
|
266
|
+
Residual, measured and accepted: a deny verb as quoted TEXT in a
|
|
267
|
+
chain (\`git pull && echo 'a && git push'\`) is gate-eligible even
|
|
268
|
+
though bash never runs it — the existing accepted false-positive
|
|
269
|
+
class, no longer rescued by a chained escape verb.
|
|
235
270
|
- **Inverted trust-boundary residual**: a merged fact written by any
|
|
236
271
|
means OTHER than the producer (e.g. directly via
|
|
237
272
|
\`mcp__grounding-mcp__ledger_add\`) that happens to exactly match the
|
|
@@ -255,6 +290,11 @@ ${runtime}${runtime === "codex" ? " (UNSUPPORTED — see \"Known gaps\" below; b
|
|
|
255
290
|
throwaway PR and check for a fresh \`${MERGED_TAG_PREFIX}\` fact in the
|
|
256
291
|
evidence ledger.
|
|
257
292
|
- **No Codex adapter**: see "Runtime" above.
|
|
293
|
+
- **No opencode adapter**: see "Runtime" above; opencode has no
|
|
294
|
+
declarative hook/event field (only a JS/TS plugin API), so neither
|
|
295
|
+
the producer nor the blocker hook above is ever projected into the
|
|
296
|
+
generated opencode artefact (\`harness.generated/opencode/opencode.json\`)
|
|
297
|
+
— this pack has no effect at all under \`--runtime opencode\`.
|
|
258
298
|
|
|
259
299
|
## Fail posture
|
|
260
300
|
|
|
@@ -283,8 +323,21 @@ export function resolve(pack, runtime = DEFAULT_RUNTIME) {
|
|
|
283
323
|
},
|
|
284
324
|
];
|
|
285
325
|
const warnings = [];
|
|
286
|
-
|
|
287
|
-
|
|
326
|
+
// HIGH-F1 (batch18 fix-round, task f34eb233): widened from
|
|
327
|
+
// `runtime === "codex"` to `runtime !== "claude-code"` so opencode
|
|
328
|
+
// gets the same pack-level warning codex already got — before this,
|
|
329
|
+
// opencode silently fell through this check (it is neither
|
|
330
|
+
// "claude-code" nor, before the widening, matched by
|
|
331
|
+
// `runtime === "codex"`), losing both the UNSUPPORTED marker above
|
|
332
|
+
// AND this warning even though opencode has no hook wiring at all
|
|
333
|
+
// (strictly worse than codex, whose hooks are at least PROJECTED,
|
|
334
|
+
// just against the wrong tool-name surface).
|
|
335
|
+
if (runtime !== "claude-code") {
|
|
336
|
+
const detail = runtime === "codex"
|
|
337
|
+
? "hooks are wired assuming the Claude Code Bash tool surface and will not fire correctly under a Codex runtime"
|
|
338
|
+
: "opencode has no declarative hook/event wiring, so neither hook is projected into any opencode artefact and this pack has no effect";
|
|
339
|
+
const adapterName = runtime === "codex" ? "Codex" : "opencode";
|
|
340
|
+
warnings.push(`policy_packs[${pack.name}]: post-merge-gate has no ${adapterName} adapter in v1; ${detail}.`);
|
|
288
341
|
}
|
|
289
342
|
return { contribution: { hooks, files }, warnings };
|
|
290
343
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"post-merge-gate.js","sourceRoot":"","sources":["../../../src/policy-packs/builtin/post-merge-gate.ts"],"names":[],"mappings":"AAAA,0CAA0C;AAC1C,EAAE;AACF,qEAAqE;AACrE,sEAAsE;AACtE,mEAAmE;AACnE,gEAAgE;AAChE,uEAAuE;AACvE,wEAAwE;AACxE,0CAA0C;AAC1C,EAAE;AACF,uEAAuE;AACvE,iCAAiC;AACjC,EAAE;AACF,yEAAyE;AACzE,kEAAkE;AAClE,0DAA0D;AAC1D,oEAAoE;AACpE,wEAAwE;AACxE,wEAAwE;AACxE,wEAAwE;AACxE,qEAAqE;AACrE,2EAA2E;AAC3E,oEAAoE;AACpE,yEAAyE;AACzE,2EAA2E;AAC3E,qEAAqE;AACrE,2EAA2E;AAC3E,uEAAuE;AACvE,2DAA2D;AAC3D,wEAAwE;AACxE,2EAA2E;AAC3E,2EAA2E;AAC3E,gBAAgB;AAChB,EAAE;AACF,yEAAyE;AACzE,sDAAsD;AACtD,sEAAsE;AACtE,sEAAsE;AACtE,yEAAyE;AACzE,sEAAsE;AACtE,qEAAqE;AACrE,oEAAoE;AACpE,
|
|
1
|
+
{"version":3,"file":"post-merge-gate.js","sourceRoot":"","sources":["../../../src/policy-packs/builtin/post-merge-gate.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type SignatureVerification } from "../../runtime/approval-signing.js";
|
|
1
2
|
import type { PolicyPack } from "../../schema/index.js";
|
|
2
3
|
export declare const PACK_NAME = "solution-acceptance";
|
|
3
4
|
/**
|
|
@@ -14,6 +15,19 @@ export declare const DEFAULT_PROTECTED_COMPLETION_TOOLS: readonly ["task_finish"
|
|
|
14
15
|
* load-bearing choke points; hardening this is follow-up `7207d8f9`.
|
|
15
16
|
* Tolerates a leading `cd … &&`, inline `VAR=val` assignments, and `git -C
|
|
16
17
|
* <path> push`.
|
|
18
|
+
*
|
|
19
|
+
* Task 76671e5a: bare `&` added to the boundary alternation (`&&` kept to
|
|
20
|
+
* its left only to mirror `src/runtime/command-normalize.ts`'s alternation
|
|
21
|
+
* order for readability — NOT because order is load-bearing here, unlike
|
|
22
|
+
* that module). Same fix as `d834a065` applied to every policy trigger —
|
|
23
|
+
* bash starts a new command after a single `&`, so `A=x&git push` used to
|
|
24
|
+
* slip past this DENY matcher entirely. Broadening is the stricter direction
|
|
25
|
+
* here (this gates completion actions, it is not an allow-list). Measured:
|
|
26
|
+
* swapping to `&|&&` produces a byte-identical match set for this matcher
|
|
27
|
+
* (a `RegExp.test` existential check over every start offset, not a
|
|
28
|
+
* segmenter — the reasoning that makes order matter in
|
|
29
|
+
* `command-normalize.ts`'s `BOUNDARY_RE`/`AMP_BOUNDARY_RE` does not transfer
|
|
30
|
+
* to a plain `.test()` matcher like this one).
|
|
17
31
|
*/
|
|
18
32
|
export declare const DEFAULT_PUSH_BASH_RE: RegExp;
|
|
19
33
|
/**
|
|
@@ -24,7 +38,18 @@ export declare const DEFAULT_PUSH_BASH_RE: RegExp;
|
|
|
24
38
|
* surface (mirrors `resolveProtectedBranches` in branch-protection-runtime).
|
|
25
39
|
*/
|
|
26
40
|
export declare function resolveProtectedCompletionTools(pack: PolicyPack): string[];
|
|
27
|
-
/**
|
|
41
|
+
/**
|
|
42
|
+
* The verdict marker the producer writes. Keep field-for-field with
|
|
43
|
+
* grounding-mcp for the first 7 keys (golden-fixture-pinned below).
|
|
44
|
+
*
|
|
45
|
+
* `alg` / `signature` (harness/c7c3f606) are ADDITIVE and OPTIONAL: a
|
|
46
|
+
* legacy / not-yet-updated producer omits them entirely (parsed as
|
|
47
|
+
* `undefined`, never `null` — mirrors how `ApprovalMarker.reportContentHash`
|
|
48
|
+
* distinguishes "absent" from an explicit empty value), and such a verdict
|
|
49
|
+
* is REJECTED by `evaluateGate` (missing signature = forged/unsigned, fail
|
|
50
|
+
* closed) rather than silently accepted. See the module doc for the
|
|
51
|
+
* consumer-only / cross-repo-follow-up honesty.
|
|
52
|
+
*/
|
|
28
53
|
export interface Verdict {
|
|
29
54
|
id: string;
|
|
30
55
|
head: string;
|
|
@@ -33,7 +58,73 @@ export interface Verdict {
|
|
|
33
58
|
blockers: string[];
|
|
34
59
|
timestamp: string;
|
|
35
60
|
source: string;
|
|
61
|
+
/** Signature algorithm tag; only `SIGNING_ALG` (`src/runtime/approval-signing.ts`) verifies. */
|
|
62
|
+
alg?: string;
|
|
63
|
+
/** HMAC-SHA256 hex signature over the canonical identifying tuple (see `verifyVerdictSignature`). */
|
|
64
|
+
signature?: string;
|
|
36
65
|
}
|
|
66
|
+
/**
|
|
67
|
+
* Marker-id namespace the verdict's HMAC signature is bound to (mirrors
|
|
68
|
+
* `BRANCH_PROTECTION_MARKER_PREFIX` in branch-protection-runtime.ts): a
|
|
69
|
+
* validly-signed verdict for one id can never be replayed as a validly-
|
|
70
|
+
* signed verdict — or a validly-signed understanding-gate / branch-
|
|
71
|
+
* protection marker — for a different id, because `markerId` is bound into
|
|
72
|
+
* the signed payload (`approval-signing.ts` `canonicalPayload`) and this
|
|
73
|
+
* prefix keeps the three id spaces disjoint even where the raw ids
|
|
74
|
+
* (task ids, session ids) could otherwise collide.
|
|
75
|
+
*/
|
|
76
|
+
export declare const VERDICT_MARKER_ID_PREFIX = "solution-verdict-";
|
|
77
|
+
/** The HMAC markerId for verdict `id` (NOT a filesystem path — see `verdictPathFor` for that). */
|
|
78
|
+
export declare function verdictMarkerId(id: string): string;
|
|
79
|
+
/**
|
|
80
|
+
* Sign `verdict`, reusing `signMarker` (`src/runtime/approval-signing.ts`)
|
|
81
|
+
* unmodified — same crypto as the understanding-gate / branch-protection
|
|
82
|
+
* markers (harness/f9485cc7). Payload mapping mirrors the approval
|
|
83
|
+
* marker's shape (`approvedAt`/`approvedBy`/`reportContentHash`) onto the
|
|
84
|
+
* verdict's own fields:
|
|
85
|
+
* - `approvedAt` <- `verdict.timestamp` (when the attestation was made)
|
|
86
|
+
* - `approvedBy` <- `verdict.source` (what attests it, e.g. "preflight")
|
|
87
|
+
* - `reportContentHash` <- `verdictContentHash(verdict)` (binds
|
|
88
|
+
* head/ready/confidence/blockers, the fields signMarker's fixed shape
|
|
89
|
+
* has no dedicated slot for)
|
|
90
|
+
*
|
|
91
|
+
* NOT wired to any production write path in harness (harness is a pure
|
|
92
|
+
* CONSUMER of this marker, see module doc) — exported as the pattern +
|
|
93
|
+
* exemplar a grounding-mcp-side producer change reuses, and used directly
|
|
94
|
+
* by this module's own tests to construct validly-signed fixtures.
|
|
95
|
+
*
|
|
96
|
+
* Load-bearing on the producer side: `signMarker` is called with
|
|
97
|
+
* `verdictMarkerId(verdict.id)`, while the consumer (`verifyVerdictSignature`
|
|
98
|
+
* below) verifies with `verdictMarkerId(id)` where `id` is the CALLER's id,
|
|
99
|
+
* not `verdict.id`. Those two only agree when a mirroring producer sets
|
|
100
|
+
* `verdict.id` to the EXACT id string the consumer looks the marker up by —
|
|
101
|
+
* byte-identical, no trimming or case normalization on either side — and the
|
|
102
|
+
* consumer additionally rejects outright when `verdict.id !== id` even if
|
|
103
|
+
* the signature itself still verifies (belt-and-braces, see `evaluateGate`).
|
|
104
|
+
*/
|
|
105
|
+
export declare function signVerdict(generatedDir: string, verdict: Verdict): Verdict;
|
|
106
|
+
/**
|
|
107
|
+
* Verify `verdict`'s signature, reusing `verifyMarkerSignature` unmodified.
|
|
108
|
+
* Recomputes `verdictContentHash` from the (possibly tampered) verdict
|
|
109
|
+
* being checked — the SAME name-mapping `signVerdict` uses — so any
|
|
110
|
+
* post-signing edit to `head` / `ready` / `confidence` / `blockers` /
|
|
111
|
+
* `timestamp` / `source` fails verification, not just an edit to
|
|
112
|
+
* `signature` itself.
|
|
113
|
+
*
|
|
114
|
+
* `id` is the CALLER's id — the one `evaluateGate` is actually checking
|
|
115
|
+
* (`readVerdict`'s lookup key) — NOT `verdict.id` (a field read back out of
|
|
116
|
+
* the marker BODY, which is exactly the bytes an attacker controls when
|
|
117
|
+
* copying a validly-signed marker verbatim onto a different id's path).
|
|
118
|
+
* This mirrors the load-bearing invariant `approval-signing.ts`'s
|
|
119
|
+
* `signMarker` doc states explicitly: "verification always recomputes the
|
|
120
|
+
* HMAC using the id the caller is checking, NOT one read back out of the
|
|
121
|
+
* marker body." Trusting `verdict.id` here would let a byte-for-byte copy
|
|
122
|
+
* of one id's signed verdict pass verification under a DIFFERENT id's
|
|
123
|
+
* request, because the copy's `signature` and its own (also-copied)
|
|
124
|
+
* `verdict.id` field would always agree with each other regardless of which
|
|
125
|
+
* id actually asked.
|
|
126
|
+
*/
|
|
127
|
+
export declare function verifyVerdictSignature(generatedDir: string, id: string, verdict: Verdict): SignatureVerification;
|
|
37
128
|
/** Env knob that overrides the verdict directory (mirrors the producer). */
|
|
38
129
|
export declare const VERDICT_DIR_ENV = "SOLUTION_VERDICT_DIR";
|
|
39
130
|
/**
|
|
@@ -91,16 +182,69 @@ export interface GateResult {
|
|
|
91
182
|
allowed: boolean;
|
|
92
183
|
reason: string;
|
|
93
184
|
verdict: Verdict | null;
|
|
185
|
+
/**
|
|
186
|
+
* True when `allowed` is false SPECIFICALLY because a verdict file
|
|
187
|
+
* existed and parsed (the 7 producer keys), but carried a missing or
|
|
188
|
+
* invalid HMAC signature (harness/c7c3f606), or a body that identifies a
|
|
189
|
+
* different id than the one being checked (cross-id replay, see the
|
|
190
|
+
* `verdict.id !== id` check below) — a legacy/not-yet-signing producer, a
|
|
191
|
+
* marker planted through some write primitive the write-guard doesn't
|
|
192
|
+
* enumerate, or content tampered/copied post-signing. Distinct from
|
|
193
|
+
* `verdict === null` (no marker at all, `forged: false`), from a
|
|
194
|
+
* signing-key I/O failure (fail-closed but NOT classified as forged,
|
|
195
|
+
* mirroring `SignatureVerification`'s `kind: "key-unavailable"` — a
|
|
196
|
+
* broken key file must not read as an active forgery attempt in audit
|
|
197
|
+
* output), and from a verdict with NO `alg`/`signature` fields at all
|
|
198
|
+
* whose `timestamp` / `source` also reads blank (fail-closed but NOT
|
|
199
|
+
* classified as forged either — a legitimately malformed, never-signed
|
|
200
|
+
* marker, not evidence of an attack; see the `MISSING_*_REASON` carve-out
|
|
201
|
+
* below). That last carve-out is scoped tightly: a verdict that DOES carry
|
|
202
|
+
* `alg`/`signature` but still hits the same blank-field reason IS
|
|
203
|
+
* classified `forged: true` — a signed field reading blank only happens
|
|
204
|
+
* via post-signing tampering, so it does not get the "legitimately
|
|
205
|
+
* malformed" benefit of the doubt (review R2, closes an audit-fidelity
|
|
206
|
+
* gap where a forger could blank a real signed verdict's `timestamp` to
|
|
207
|
+
* suppress this tag while the block itself still held). `allowed` is
|
|
208
|
+
* always false when `forged` is true.
|
|
209
|
+
*/
|
|
210
|
+
forged: boolean;
|
|
94
211
|
}
|
|
95
212
|
/**
|
|
96
|
-
* Evaluate the gate for `id` at `currentHead`.
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
213
|
+
* Evaluate the gate for `id` at `currentHead`. First verifies the verdict's
|
|
214
|
+
* HMAC signature (harness/c7c3f606, fail-closed — see `verifyVerdictSignature`);
|
|
215
|
+
* a verdict that fails this is REJECTED before its `ready` / `head` fields
|
|
216
|
+
* are ever trusted, with a distinct forged/unsigned reason so an
|
|
217
|
+
* operator/auditor can tell that apart from the routine "not ready yet" or
|
|
218
|
+
* "no verdict" cases. Signature verification needs `generatedDir` (the
|
|
219
|
+
* harness `.generated/` dir holding the shared signing key, NOT the verdict
|
|
220
|
+
* dir) — `undefined` when it could not be resolved, which fails closed with
|
|
221
|
+
* its own distinct (non-forged) reason.
|
|
222
|
+
*
|
|
223
|
+
* `verifyVerdictSignature` is called with `id` — the CALLER's id, i.e. the
|
|
224
|
+
* one this function is asked to check — not `verdict.id`. A SECOND,
|
|
225
|
+
* belt-and-braces check below independently rejects `verdict.id !== id`:
|
|
226
|
+
* even though the signature check alone already closes the cross-id-replay
|
|
227
|
+
* hole (a byte-for-byte copy of a validly-signed verdict onto a different
|
|
228
|
+
* id's path fails signature verification, because the markerId the
|
|
229
|
+
* signature is bound to no longer matches `id`), the verdict body's `id`
|
|
230
|
+
* field itself is NOT part of the signed payload (only `timestamp` /
|
|
231
|
+
* `source` / the head-ready-confidence-blockers content hash are), so it
|
|
232
|
+
* can drift from the id that was actually verified without invalidating an
|
|
233
|
+
* otherwise-valid signature. This check makes that drift fail loudly too,
|
|
234
|
+
* rather than silently ignoring an inconsistency in the body of a verdict
|
|
235
|
+
* that otherwise passed.
|
|
236
|
+
*
|
|
237
|
+
* Once signed and identity-checked, mirrors grounding-mcp `evaluateGate`:
|
|
238
|
+
* allow iff `verdict.ready === true` AND `verdict.head === currentHead`.
|
|
239
|
+
* `confidence` is INFORMATIONAL ONLY and never gates — a `ready:true
|
|
240
|
+
* confidence:0` verdict at HEAD passes — so the harness consumer stays
|
|
241
|
+
* byte-parity with the producer's `solution_gate` for THAT decision.
|
|
242
|
+
* Signature verification itself is NOT (yet) mirrored on the producer side
|
|
243
|
+
* — see module doc, "HONEST RESIDUAL": grounding-mcp's own `solution_gate`
|
|
244
|
+
* does not enforce this signature, only this harness consumer does, until a
|
|
245
|
+
* matching producer release ships.
|
|
102
246
|
*/
|
|
103
|
-
export declare function evaluateGate(verdict: Verdict | null, currentHead: string | null, id: string): GateResult;
|
|
247
|
+
export declare function evaluateGate(verdict: Verdict | null, currentHead: string | null, id: string, generatedDir: string | undefined): GateResult;
|
|
104
248
|
/**
|
|
105
249
|
* Is `target` inside `dir` after resolution? Used for the path-tool arm
|
|
106
250
|
* (Write/Edit/MultiEdit/NotebookEdit `file_path`) and for a Bash shell whose
|