@lannguyensi/harness 0.31.0 → 0.33.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.
Files changed (46) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/dist/cli/approve/branch-protection.d.ts +69 -0
  3. package/dist/cli/approve/branch-protection.js +157 -0
  4. package/dist/cli/approve/branch-protection.js.map +1 -0
  5. package/dist/cli/index.js +101 -1
  6. package/dist/cli/index.js.map +1 -1
  7. package/dist/cli/init/composer.js +11 -5
  8. package/dist/cli/init/composer.js.map +1 -1
  9. package/dist/cli/init/profiles.d.ts +2 -2
  10. package/dist/cli/init/profiles.js +2 -2
  11. package/dist/cli/init/templates.d.ts +1 -1
  12. package/dist/cli/init/templates.js +23 -4
  13. package/dist/cli/init/templates.js.map +1 -1
  14. package/dist/cli/pack/hook-branch-protection.d.ts +8 -0
  15. package/dist/cli/pack/hook-branch-protection.js +59 -15
  16. package/dist/cli/pack/hook-branch-protection.js.map +1 -1
  17. package/dist/cli/pack/hook-pre-tool-use.js +31 -2
  18. package/dist/cli/pack/hook-pre-tool-use.js.map +1 -1
  19. package/dist/cli/pack/hook-solution-acceptance-writeguard.d.ts +26 -0
  20. package/dist/cli/pack/hook-solution-acceptance-writeguard.js +187 -0
  21. package/dist/cli/pack/hook-solution-acceptance-writeguard.js.map +1 -0
  22. package/dist/cli/pack/hook-solution-acceptance.d.ts +28 -0
  23. package/dist/cli/pack/hook-solution-acceptance.js +251 -0
  24. package/dist/cli/pack/hook-solution-acceptance.js.map +1 -0
  25. package/dist/cli/pack/read-only-bash.js +127 -4
  26. package/dist/cli/pack/read-only-bash.js.map +1 -1
  27. package/dist/cli/validate/checks.js +38 -0
  28. package/dist/cli/validate/checks.js.map +1 -1
  29. package/dist/policy-packs/builtin/branch-protection-runtime.d.ts +47 -6
  30. package/dist/policy-packs/builtin/branch-protection-runtime.js +53 -6
  31. package/dist/policy-packs/builtin/branch-protection-runtime.js.map +1 -1
  32. package/dist/policy-packs/builtin/branch-protection.js +21 -11
  33. package/dist/policy-packs/builtin/branch-protection.js.map +1 -1
  34. package/dist/policy-packs/builtin/solution-acceptance-runtime.d.ts +137 -0
  35. package/dist/policy-packs/builtin/solution-acceptance-runtime.js +321 -0
  36. package/dist/policy-packs/builtin/solution-acceptance-runtime.js.map +1 -0
  37. package/dist/policy-packs/builtin/solution-acceptance.d.ts +44 -0
  38. package/dist/policy-packs/builtin/solution-acceptance.js +185 -0
  39. package/dist/policy-packs/builtin/solution-acceptance.js.map +1 -0
  40. package/dist/policy-packs/builtin/understanding-before-execution.d.ts +11 -0
  41. package/dist/policy-packs/builtin/understanding-before-execution.js +15 -0
  42. package/dist/policy-packs/builtin/understanding-before-execution.js.map +1 -1
  43. package/dist/policy-packs/registry.d.ts +1 -1
  44. package/dist/policy-packs/registry.js +10 -0
  45. package/dist/policy-packs/registry.js.map +1 -1
  46. package/package.json +3 -3
@@ -0,0 +1,321 @@
1
+ // Builtin Policy Pack runtime: `solution-acceptance` (consumer half).
2
+ //
3
+ // The PRODUCER lives in grounding-mcp (`solution_evaluate` / `solution_gate`,
4
+ // @lannguyensi/grounding-mcp >= 0.3.2): it runs a real `preflight run --json`
5
+ // and records a HEAD-pinned verdict marker outside the agent-writable
6
+ // evidence ledger. harness is the CONSUMER: it reimplements the marker read +
7
+ // gate decision here so it carries NO grounding-mcp runtime dependency (the
8
+ // same precedent as understanding-before-execution reimplementing its
9
+ // approval-marker read). Schema parity with the producer is pinned by a
10
+ // golden-fixture test against a real 0.3.2 marker; if grounding-mcp changes
11
+ // the Verdict shape, that test fails loudly.
12
+ //
13
+ // Two enforcement surfaces are built on top of these helpers (see the pack
14
+ // definition + the two hook verbs):
15
+ //
16
+ // 1. completion-gate — denies the task-finishing tools (agent-tasks MCP
17
+ // task_finish / task_submit_pr / task_merge / pull_requests_merge, plus
18
+ // a belt-and-suspenders bash match for `git push` / `gh pr merge`)
19
+ // unless a ready verdict exists at the CURRENT git HEAD.
20
+ //
21
+ // 2. write-guard — the actual anti-forgery closure. Relocating the verdict
22
+ // dir is NOT sufficient: understanding-gate allows all Bash post-approval
23
+ // (it does no write-target inspection in the allow path), so an approved
24
+ // agent could otherwise hand-write a `ready:true` marker. The write-guard
25
+ // blocks the agent's enumerated write paths into the verdict dir.
26
+ //
27
+ // Anti-forgery honesty (operator decision, 2026-05-30): v1 closes the
28
+ // ENUMERATED-WRITE-PATH residual (the Bash/Edit/Write spellings below), NOT
29
+ // arbitrary same-uid forgery. A `0500` chmod on the dir would be incoherent
30
+ // here because the producer (grounding-mcp) and the agent share a uid, so it
31
+ // would block the producer too. Content-authenticity against an unguarded
32
+ // write primitive is closed by marker signing, a cross-repo follow-up that
33
+ // already touches the producer. This module must NOT claim the residual is
34
+ // fully closed.
35
+ import * as fs from "node:fs";
36
+ import * as os from "node:os";
37
+ import * as path from "node:path";
38
+ export const PACK_NAME = "solution-acceptance";
39
+ /**
40
+ * agent-tasks MCP verbs that mark a completion boundary. The gate fires on
41
+ * these (matched by exact tool name `mcp__agent-tasks__<verb>`). These MCP
42
+ * choke points are reliable: unlike the bash matcher they cannot be evaded
43
+ * by shell indirection.
44
+ */
45
+ export const DEFAULT_PROTECTED_COMPLETION_TOOLS = [
46
+ "task_finish",
47
+ "task_submit_pr",
48
+ "task_merge",
49
+ "pull_requests_merge",
50
+ ];
51
+ /**
52
+ * Belt-and-suspenders bash matcher for `git push` / `gh pr merge`. Regex on
53
+ * the typed command, so an env-var indirection
54
+ * (`B=main && git push origin $B`) evades it — the MCP verbs above are the
55
+ * load-bearing choke points; hardening this is follow-up `7207d8f9`.
56
+ * Tolerates a leading `cd … &&`, inline `VAR=val` assignments, and `git -C
57
+ * <path> push`.
58
+ */
59
+ export const DEFAULT_PUSH_BASH_RE = /(?:^|\n|;|\||&&|\()\s*(?:\w+=\S+\s+)*(?:git(?:\s+-C\s+\S+)?\s+push|gh\s+pr\s+merge)\b/;
60
+ /**
61
+ * Resolve the completion verbs the gate fires on: the pack's
62
+ * `config.protected_completion_tools` override, else the default set.
63
+ * Always non-empty. Lives here (not in the pack module) so the
64
+ * completion-gate hook can share it without importing the pack's zod
65
+ * surface (mirrors `resolveProtectedBranches` in branch-protection-runtime).
66
+ */
67
+ export function resolveProtectedCompletionTools(pack) {
68
+ const raw = pack.config["protected_completion_tools"];
69
+ if (Array.isArray(raw) &&
70
+ raw.length > 0 &&
71
+ raw.every((t) => typeof t === "string" && t.length > 0)) {
72
+ return raw;
73
+ }
74
+ return [...DEFAULT_PROTECTED_COMPLETION_TOOLS];
75
+ }
76
+ /** Env knob that overrides the verdict directory (mirrors the producer). */
77
+ export const VERDICT_DIR_ENV = "SOLUTION_VERDICT_DIR";
78
+ /**
79
+ * Env knob that supplies the verdict id for SOLO / non-agent-tasks sessions.
80
+ * The completion-gate consults it ONLY when no agent-tasks `active-claim` is
81
+ * recorded (resolution order: active-claim first, then this env, then
82
+ * fail-closed), so a claimed session's id stays authoritative and cannot be
83
+ * redirected by an env var. A sessionId fallback is intentionally still NOT a
84
+ * source (the wrong-scope bug class understanding-gate closed).
85
+ */
86
+ export const VERDICT_ID_ENV = "SOLUTION_VERDICT_ID";
87
+ /**
88
+ * Stable tail of the default verdict dir. The write-guard's reference
89
+ * detection matches on this so ANY spelling of the home prefix is caught
90
+ * (`~/.local/state/...`, `$HOME/...`, `$XDG_STATE_HOME/...`, the literal
91
+ * absolute path).
92
+ */
93
+ export const VERDICT_DIR_TAIL = path.join("agent-grounding", "solution-verdicts");
94
+ /**
95
+ * Resolve the verdict directory. Resolution order MUST match grounding-mcp's
96
+ * `verdictDir()` so the consumer reads exactly where the producer writes
97
+ * (operator decision B: both sides use the producer default; no apply-time
98
+ * env threading, no divergence risk):
99
+ * 1. SOLUTION_VERDICT_DIR
100
+ * 2. $XDG_STATE_HOME/agent-grounding/solution-verdicts
101
+ * 3. ~/.local/state/agent-grounding/solution-verdicts
102
+ */
103
+ export function verdictDir(env = process.env, homedir = os.homedir) {
104
+ const override = env[VERDICT_DIR_ENV];
105
+ if (override && override.trim().length > 0)
106
+ return override;
107
+ const xdg = env["XDG_STATE_HOME"];
108
+ const base = xdg && xdg.trim().length > 0 ? xdg : path.join(homedir(), ".local", "state");
109
+ return path.join(base, "agent-grounding", "solution-verdicts");
110
+ }
111
+ /**
112
+ * Reduce a verdict id to a single safe path segment. Mirrors the producer's
113
+ * `sanitizeVerdictId`: non-portable chars collapse to `_`, `path.basename`
114
+ * strips any residual separator (path-traversal guard), empty / dot-only ids
115
+ * are rejected.
116
+ */
117
+ export function sanitizeVerdictId(id) {
118
+ const cleaned = id.replace(/[^A-Za-z0-9._-]/g, "_");
119
+ const base = path.basename(cleaned);
120
+ if (base === "" || base === "." || base === "..") {
121
+ throw new Error(`invalid verdict id: ${JSON.stringify(id)}`);
122
+ }
123
+ return base;
124
+ }
125
+ export function verdictPathFor(dir, id) {
126
+ return path.join(dir, `${sanitizeVerdictId(id)}.json`);
127
+ }
128
+ /**
129
+ * Resolve the explicit verdict id from `SOLUTION_VERDICT_ID`, or null when it
130
+ * is unset, blank, or not a safe single path segment. Validated through
131
+ * `sanitizeVerdictId` so a traversal-y or empty value fails closed here
132
+ * (returns null -> the gate denies) rather than reaching the marker read. This
133
+ * is the solo / non-agent-tasks fallback the completion-gate uses only when no
134
+ * active-claim is present.
135
+ */
136
+ export function resolveExplicitVerdictId(env = process.env) {
137
+ const raw = env[VERDICT_ID_ENV];
138
+ if (typeof raw !== "string")
139
+ return null;
140
+ const trimmed = raw.trim();
141
+ if (trimmed.length === 0)
142
+ return null;
143
+ try {
144
+ sanitizeVerdictId(trimmed);
145
+ }
146
+ catch {
147
+ return null;
148
+ }
149
+ return trimmed;
150
+ }
151
+ /**
152
+ * Read + validate the verdict marker for `id`, or null when it is absent,
153
+ * unparseable, a symlink, or not a regular file. The lstat + symlink reject
154
+ * mirrors `checkApprovalMarker`: defense-in-depth against a symlink planted
155
+ * at the marker path pointing at agent-controlled content.
156
+ */
157
+ export function readVerdict(dir, id) {
158
+ let p;
159
+ try {
160
+ p = verdictPathFor(dir, id);
161
+ }
162
+ catch {
163
+ return null; // invalid id
164
+ }
165
+ let stat;
166
+ try {
167
+ stat = fs.lstatSync(p);
168
+ }
169
+ catch {
170
+ return null;
171
+ }
172
+ if (stat.isSymbolicLink() || !stat.isFile())
173
+ return null;
174
+ let raw;
175
+ try {
176
+ raw = fs.readFileSync(p, "utf8");
177
+ }
178
+ catch {
179
+ return null;
180
+ }
181
+ try {
182
+ const parsed = JSON.parse(raw);
183
+ if (typeof parsed.id !== "string" ||
184
+ typeof parsed.head !== "string" ||
185
+ typeof parsed.ready !== "boolean") {
186
+ return null;
187
+ }
188
+ return {
189
+ id: parsed.id,
190
+ head: parsed.head,
191
+ ready: parsed.ready,
192
+ confidence: typeof parsed.confidence === "number" ? parsed.confidence : 0,
193
+ blockers: Array.isArray(parsed.blockers) ? parsed.blockers : [],
194
+ timestamp: typeof parsed.timestamp === "string" ? parsed.timestamp : "",
195
+ source: typeof parsed.source === "string" ? parsed.source : "",
196
+ };
197
+ }
198
+ catch {
199
+ return null;
200
+ }
201
+ }
202
+ /**
203
+ * Evaluate the gate for `id` at `currentHead`. Mirrors grounding-mcp
204
+ * `evaluateGate` EXACTLY: allow iff `verdict.ready === true` AND
205
+ * `verdict.head === currentHead`. `confidence` is INFORMATIONAL ONLY and
206
+ * never gates — a `ready:true confidence:0` verdict at HEAD passes — so the
207
+ * harness consumer stays byte-parity with the producer's `solution_gate`
208
+ * (an operator running `solution_gate` and the harness gate must agree).
209
+ */
210
+ export function evaluateGate(verdict, currentHead, id) {
211
+ if (!verdict) {
212
+ return {
213
+ allowed: false,
214
+ reason: `no solution-acceptance verdict recorded for "${id}" (run mcp__agent-grounding__solution_evaluate first)`,
215
+ verdict: null,
216
+ };
217
+ }
218
+ if (!verdict.ready) {
219
+ const why = verdict.blockers.length > 0 ? `: ${verdict.blockers.join("; ")}` : "";
220
+ return {
221
+ allowed: false,
222
+ reason: `solution-acceptance verdict for "${id}" is not ready${why} (fix, then re-run solution_evaluate)`,
223
+ verdict,
224
+ };
225
+ }
226
+ if (!currentHead) {
227
+ return {
228
+ allowed: false,
229
+ reason: `cannot resolve the current git HEAD to confirm the verdict for "${id}" is at HEAD`,
230
+ verdict,
231
+ };
232
+ }
233
+ if (verdict.head !== currentHead) {
234
+ return {
235
+ allowed: false,
236
+ reason: `stale solution-acceptance verdict for "${id}": recorded at ${verdict.head.slice(0, 7)}, current HEAD ${currentHead.slice(0, 7)} (re-run solution_evaluate after the rework)`,
237
+ verdict,
238
+ };
239
+ }
240
+ return {
241
+ allowed: true,
242
+ reason: `solution-acceptance verdict for "${id}" is ready at HEAD ${currentHead.slice(0, 7)} (confidence ${Math.round(verdict.confidence * 100)}%)`,
243
+ verdict,
244
+ };
245
+ }
246
+ // ── Write-guard target detection ──
247
+ /**
248
+ * Is `target` inside `dir` after resolution? Used for the path-tool arm
249
+ * (Write/Edit/MultiEdit/NotebookEdit `file_path`) and for a Bash shell whose
250
+ * cwd is the protected dir. A relative `target` resolves against `cwd`
251
+ * (falling back to process.cwd()).
252
+ */
253
+ export function isInsideDir(target, dir, cwd) {
254
+ if (typeof target !== "string" || target.length === 0)
255
+ return false;
256
+ const absDir = path.resolve(dir);
257
+ const absTarget = path.isAbsolute(target)
258
+ ? path.resolve(target)
259
+ : path.resolve(cwd ?? process.cwd(), target);
260
+ const rel = path.relative(absDir, absTarget);
261
+ return rel === "" || (!rel.startsWith("..") && !path.isAbsolute(rel));
262
+ }
263
+ /**
264
+ * Does a Bash command TEXTUALLY reference the verdict dir? Catches the
265
+ * enumerated spellings without shell-evaluating (same contract as
266
+ * read-only-bash):
267
+ * - the literal absolute dir,
268
+ * - the `$SOLUTION_VERDICT_DIR` env token,
269
+ * - the stable tail `agent-grounding/solution-verdicts` (covers `~/...`,
270
+ * `$HOME/...`, `$XDG_STATE_HOME/...`, and absolute spellings), and
271
+ * - the dir's LEAF segment (`solution-verdicts` for the default).
272
+ *
273
+ * The leaf segment closes the `cd <parent> && write <relative-into-dir>`
274
+ * descent (where the parent path and the child redirect never form the
275
+ * contiguous tail): ANY relative write into the dir from a cwd that is not
276
+ * the dir itself must name the leaf somewhere in the command, and a
277
+ * `cd <…/leaf>` to first make cwd==dir would itself contain the leaf. The
278
+ * write-guard's cwd-inside check covers the only remaining case (cwd already
279
+ * inside the dir). The leaf needle is length-guarded so a short custom
280
+ * basename does not over-block; the default leaf is distinctive, and a
281
+ * non-default dir already warns at validate time.
282
+ *
283
+ * `chmod`/`chattr` that target the dir are caught the same way, so the
284
+ * FS-perm-loosening attack is covered.
285
+ *
286
+ * Honest residual: a path constructed at runtime inside an interpreter with
287
+ * no textual reference (e.g. base64-decoded inside `python3 -c`) is NOT
288
+ * caught. That is what marker signing (follow-up) closes.
289
+ */
290
+ export function bashReferencesVerdictDir(command, dir) {
291
+ if (typeof command !== "string" || command.length === 0)
292
+ return false;
293
+ const leaf = path.basename(dir);
294
+ // Direct literal references + the distinctive leaf segment.
295
+ if (command.includes(dir) ||
296
+ command.includes(VERDICT_DIR_ENV) ||
297
+ command.includes(VERDICT_DIR_TAIL) ||
298
+ (leaf.length >= 6 && command.includes(leaf))) {
299
+ return true;
300
+ }
301
+ // Glob-obscured references. bash expands `*?[` against EXISTING paths at
302
+ // runtime, so a glob like `solution-ver*/<id>.json` reaches the dir
303
+ // without the literal leaf ever appearing in the command text, and a
304
+ // matching glob can OVERWRITE an existing marker (flipping ready:false ->
305
+ // true). We cannot safely expand globs (that is the shell-eval surface
306
+ // read-only-bash refuses), so when a glob metachar is present we match the
307
+ // leaf's distinctive sub-words: a single glob can split the hyphenated
308
+ // leaf but not erase every >=6-char word of it (`solution-ver*` keeps
309
+ // "solution"; `solu*verdicts` keeps "verdicts"). The leaf words, not the
310
+ // parent segment, are used on purpose: the parent here is `agent-grounding`,
311
+ // which is also a repo name and would over-block legitimate work. A command
312
+ // that globs EVERY path segment is the residual the marker-signing
313
+ // follow-up closes.
314
+ if (/[*?[]/.test(command)) {
315
+ const leafWords = leaf.split(/[^A-Za-z0-9]+/).filter((w) => w.length >= 6);
316
+ if (leafWords.some((w) => command.includes(w)))
317
+ return true;
318
+ }
319
+ return false;
320
+ }
321
+ //# sourceMappingURL=solution-acceptance-runtime.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"solution-acceptance-runtime.js","sourceRoot":"","sources":["../../../src/policy-packs/builtin/solution-acceptance-runtime.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,44 @@
1
+ import { z } from "zod";
2
+ import type { PolicyPack } from "../../schema/index.js";
3
+ import { type Runtime } from "../runtime.js";
4
+ import type { PackContribution } from "../types.js";
5
+ import { PACK_NAME } from "./solution-acceptance-runtime.js";
6
+ export { PACK_NAME };
7
+ /**
8
+ * Zod schema for this pack's `config:` block. Strict by design so typo'd
9
+ * keys fail loud at lint time (mirrors sibling packs).
10
+ */
11
+ export declare const configSchema: z.ZodObject<{
12
+ protected_completion_tools: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
13
+ ux: z.ZodOptional<z.ZodObject<{
14
+ cannot: z.ZodString;
15
+ required: z.ZodArray<z.ZodString, "many">;
16
+ run: z.ZodArray<z.ZodString, "many">;
17
+ }, "strict", z.ZodTypeAny, {
18
+ cannot: string;
19
+ required: string[];
20
+ run: string[];
21
+ }, {
22
+ cannot: string;
23
+ required: string[];
24
+ run: string[];
25
+ }>>;
26
+ }, "strict", z.ZodTypeAny, {
27
+ ux?: {
28
+ cannot: string;
29
+ required: string[];
30
+ run: string[];
31
+ } | undefined;
32
+ protected_completion_tools?: string[] | undefined;
33
+ }, {
34
+ ux?: {
35
+ cannot: string;
36
+ required: string[];
37
+ run: string[];
38
+ } | undefined;
39
+ protected_completion_tools?: string[] | undefined;
40
+ }>;
41
+ export declare function resolve(pack: PolicyPack, runtime?: Runtime): {
42
+ contribution: PackContribution;
43
+ warnings: string[];
44
+ };
@@ -0,0 +1,185 @@
1
+ // Builtin Policy Pack: `solution-acceptance`.
2
+ //
3
+ // Consumer half of the solution-acceptance gate (the "Verifier-gated Done"
4
+ // anti-lazyness mechanism). Makes a task's completion EARNED from a real
5
+ // preflight run, not self-attested. The producer is grounding-mcp's
6
+ // `solution_evaluate` (>= 0.3.2); harness reads the HEAD-pinned verdict
7
+ // marker it writes and gates the task-finishing tools on it.
8
+ //
9
+ // Two PreToolUse hooks, both `blocking: hard`:
10
+ //
11
+ // 1. completion-gate (`harness pack hook solution-acceptance`): denies the
12
+ // agent-tasks completion verbs (task_finish / task_submit_pr /
13
+ // task_merge / pull_requests_merge) — and, belt-and-suspenders, a
14
+ // `git push` / `gh pr merge` bash command — unless a READY verdict
15
+ // exists at the CURRENT git HEAD. The verdict id is the active-claim
16
+ // task id; with no active claim the gate fails CLOSED.
17
+ //
18
+ // 2. write-guard (`harness pack hook solution-acceptance-writeguard`): the
19
+ // actual anti-forgery closure. Relocating the verdict dir is NOT enough
20
+ // — understanding-gate allows all Bash post-approval — so this hook
21
+ // blocks the agent's enumerated write paths (Bash redirects/`tee`/`mv`/
22
+ // `cp`/`ln`/interpreter writes that reference the dir, `chmod`/`chattr`
23
+ // on the dir, and Write/Edit/MultiEdit/NotebookEdit whose `file_path`
24
+ // lands inside the dir). The ONLY remaining writer is the producer
25
+ // (the operator-launched grounding-mcp MCP server, which runs real
26
+ // preflight and does not flow through the agent's gated tool surface).
27
+ //
28
+ // Anti-forgery scope (operator decision, 2026-05-30): v1 closes the
29
+ // ENUMERATED-WRITE-PATH residual, NOT arbitrary same-uid forgery. Content
30
+ // signing (a cross-repo follow-up that also touches the producer) is what
31
+ // closes an unguarded write primitive. See solution-acceptance-runtime.ts.
32
+ //
33
+ // Pack is OFF by default; enable per-installation via
34
+ // `harness pack add solution-acceptance`. The `full` init template wires it
35
+ // as a disabled-by-policy exemplar... (see templates.ts). It REQUIRES
36
+ // grounding-mcp under `tools.mcp` (the producer) and the `preflight` binary
37
+ // on PATH; `harness validate` / `harness doctor` warn when the producer is
38
+ // absent (a missing producer means the gate can never see a verdict and
39
+ // would deadlock).
40
+ import { z } from "zod";
41
+ import { PolicyUxSchema } from "../../schema/policies.js";
42
+ import { DEFAULT_RUNTIME } from "../runtime.js";
43
+ import { PACK_NAME, resolveProtectedCompletionTools, } from "./solution-acceptance-runtime.js";
44
+ export { PACK_NAME };
45
+ /**
46
+ * Zod schema for this pack's `config:` block. Strict by design so typo'd
47
+ * keys fail loud at lint time (mirrors sibling packs).
48
+ */
49
+ export const configSchema = z
50
+ .object({
51
+ // Override the agent-tasks completion verbs the gate fires on. The
52
+ // names are bare verbs (e.g. "task_finish"); the matcher prefixes
53
+ // `mcp__agent-tasks__`. Defaults to DEFAULT_PROTECTED_COMPLETION_TOOLS.
54
+ protected_completion_tools: z.array(z.string().min(1)).min(1).optional(),
55
+ // `ux` renders the agent-facing remediation block when a gate trips.
56
+ ux: PolicyUxSchema.optional(),
57
+ })
58
+ .strict();
59
+ const HOOK_NAME_PREFIX = `policy-pack:${PACK_NAME}`;
60
+ const COMPLETION_BLOCKER_COMMAND = "harness pack hook solution-acceptance";
61
+ const WRITEGUARD_BLOCKER_COMMAND = "harness pack hook solution-acceptance-writeguard";
62
+ const WRITEGUARD_MATCH_CLAUDE = "Edit|Write|MultiEdit|NotebookEdit|Bash";
63
+ const WRITEGUARD_MATCH_CODEX = "apply_patch|Bash";
64
+ /**
65
+ * PreToolUse `match` for the completion-gate. `Bash` is always included for
66
+ * the `git push` / `gh pr merge` belt-and-suspenders arm; on the Claude
67
+ * runtime the agent-tasks MCP verbs are appended (the reliable choke
68
+ * points). Codex has no agent-tasks MCP surface here, so it gets the Bash
69
+ * arm only (documented limitation).
70
+ */
71
+ function completionMatch(runtime, tools) {
72
+ if (runtime === "codex")
73
+ return "Bash";
74
+ const mcp = tools.map((t) => `mcp__agent-tasks__${t}`).join("|");
75
+ return `Bash|${mcp}`;
76
+ }
77
+ function buildHooks(runtime, tools) {
78
+ const writeGuardMatch = runtime === "codex" ? WRITEGUARD_MATCH_CODEX : WRITEGUARD_MATCH_CLAUDE;
79
+ return [
80
+ {
81
+ name: `${HOOK_NAME_PREFIX}:completion-gate`,
82
+ event: "PreToolUse",
83
+ match: completionMatch(runtime, tools),
84
+ command: COMPLETION_BLOCKER_COMMAND,
85
+ blocking: "hard",
86
+ budget_ms: 5000,
87
+ description: "Blocker: deny the task-finishing tools (agent-tasks completion verbs + `git push` / `gh pr merge`) unless a ready solution-acceptance verdict exists at the current git HEAD. Fail-closed.",
88
+ },
89
+ {
90
+ name: `${HOOK_NAME_PREFIX}:write-guard`,
91
+ event: "PreToolUse",
92
+ match: writeGuardMatch,
93
+ command: WRITEGUARD_BLOCKER_COMMAND,
94
+ blocking: "hard",
95
+ budget_ms: 5000,
96
+ description: "Anti-forgery write-guard: deny any agent write into the solution-verdict dir (redirects/tee/mv/cp/ln/interpreter writes that reference it, chmod/chattr on it, and path-tool file_path inside it). The producer (grounding-mcp MCP server) is the only legitimate writer.",
97
+ },
98
+ ];
99
+ }
100
+ function buildInstructions(pack, tools, runtime) {
101
+ const description = pack.description?.trim() ?? "";
102
+ return `# Policy Pack: ${PACK_NAME}
103
+
104
+ > Operator audit copy. This pack makes task completion EARNED from a real
105
+ > preflight run (the producer, grounding-mcp \`solution_evaluate\`) rather
106
+ > than self-attested, and closes the enumerated-write-path forgery residual
107
+ > on the verdict marker.
108
+
109
+ ## Runtime
110
+
111
+ ${runtime}
112
+
113
+ ## Producer (required)
114
+
115
+ This pack is a pure CONSUMER. It needs the producer wired:
116
+
117
+ - \`grounding-mcp\` (>= 0.3.2) declared under \`tools.mcp\`, exposing
118
+ \`solution_evaluate\` / \`solution_gate\`.
119
+ - The \`preflight\` binary (\`@lannguyensi/agent-preflight\`) on PATH (or
120
+ \`SOLUTION_PREFLIGHT_BIN\`), which \`solution_evaluate\` shells out to.
121
+
122
+ If the producer is absent the gate can never see a verdict and would
123
+ deadlock; \`harness validate\` / \`harness doctor\` warn about this.
124
+
125
+ ## Effect
126
+
127
+ Two \`PreToolUse\` hooks (both blocking: hard) are wired into the
128
+ harness-managed settings:
129
+
130
+ 1. \`completion-gate\` (\`${COMPLETION_BLOCKER_COMMAND}\`): denies the
131
+ completion verbs unless a READY verdict exists at the CURRENT git HEAD.
132
+ Gated tools: ${tools.map((t) => `\`${t}\``).join(", ")} (matched as
133
+ \`mcp__agent-tasks__<verb>\`), plus a \`git push\` / \`gh pr merge\` bash
134
+ match. The verdict id is the active-claim task id; with no active claim
135
+ the gate fails CLOSED.
136
+
137
+ 2. \`write-guard\` (\`${WRITEGUARD_BLOCKER_COMMAND}\`): denies any agent write
138
+ into the verdict dir. The only legitimate writer is the producer.
139
+
140
+ ## Earning a verdict
141
+
142
+ \`\`\`
143
+ mcp__agent-grounding__solution_evaluate({ id: "<active-task-id>" })
144
+ \`\`\`
145
+
146
+ This runs \`preflight run --json\` and records a HEAD-pinned verdict. A
147
+ not-ready run (failing checks, dirty tree) records a not-ready verdict; any
148
+ commit after a green run shifts HEAD and invalidates it, so re-run after
149
+ every change.
150
+
151
+ ## Anti-forgery scope (v1)
152
+
153
+ v1 closes the ENUMERATED-WRITE-PATH residual: the write-guard blocks the
154
+ agent's Bash/Edit/Write spellings that target the verdict dir. It does NOT
155
+ close arbitrary same-uid forgery (an unguarded write primitive). Marker
156
+ signing — a follow-up that also changes the producer — is what closes
157
+ content-authenticity. A \`0500\` chmod on the dir is deliberately NOT used:
158
+ producer and agent share a uid, so it would block the producer too.
159
+
160
+ ## Out of scope (v1)
161
+
162
+ - Goodhart test-count-delta guard (producer side).
163
+ - LLM-judge layer / relative best-of-N ranking.
164
+ - Explicit verdict-id source for untasked sessions (follow-up; today an
165
+ untasked session fails closed).
166
+
167
+ ## Pack metadata
168
+ ${description ? `\n> ${description.replace(/\n/g, "\n> ")}\n` : ""}
169
+ - Source: \`builtin\`
170
+ - Pack: \`${PACK_NAME}\`
171
+ - Runtime: \`${runtime}\`
172
+ `;
173
+ }
174
+ export function resolve(pack, runtime = DEFAULT_RUNTIME) {
175
+ const tools = resolveProtectedCompletionTools(pack);
176
+ const hooks = buildHooks(runtime, tools);
177
+ const files = [
178
+ {
179
+ relativePath: `policy-packs/${PACK_NAME}/instructions.md`,
180
+ content: buildInstructions(pack, tools, runtime),
181
+ },
182
+ ];
183
+ return { contribution: { hooks, files }, warnings: [] };
184
+ }
185
+ //# sourceMappingURL=solution-acceptance.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"solution-acceptance.js","sourceRoot":"","sources":["../../../src/policy-packs/builtin/solution-acceptance.ts"],"names":[],"mappings":"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"}
@@ -6,6 +6,17 @@ export declare const PACK_NAME = "understanding-before-execution";
6
6
  export declare const VERSION_COMMAND: readonly [string, string];
7
7
  export type Mode = "fast_confirm" | "grill_me" | "strict";
8
8
  export declare const DEFAULT_MODE: Mode;
9
+ /**
10
+ * The agent-facing `required:` phrase for the understanding-gate deny
11
+ * envelope, derived from the configured mode. Only `strict` forces
12
+ * `requiresHumanApproval` (see {@link modeFriction}); in `fast_confirm`
13
+ * and `grill_me` the agent self-attests and a structural validator
14
+ * checks the report, so naming it "human-approved" there would overstate
15
+ * what the gate actually enforces. Generation surfaces (the Custom
16
+ * composer, the init templates) call this so the wording can never drift
17
+ * from the mode it is paired with.
18
+ */
19
+ export declare function understandingApprovalRequirement(mode: Mode): string;
9
20
  export declare function isMode(value: unknown): value is Mode;
10
21
  /**
11
22
  * Zod schema for this pack's `config:` block. Surfaced via
@@ -29,6 +29,21 @@ export const VERSION_COMMAND = [
29
29
  ];
30
30
  const MODES = ["fast_confirm", "grill_me", "strict"];
31
31
  export const DEFAULT_MODE = "grill_me";
32
+ /**
33
+ * The agent-facing `required:` phrase for the understanding-gate deny
34
+ * envelope, derived from the configured mode. Only `strict` forces
35
+ * `requiresHumanApproval` (see {@link modeFriction}); in `fast_confirm`
36
+ * and `grill_me` the agent self-attests and a structural validator
37
+ * checks the report, so naming it "human-approved" there would overstate
38
+ * what the gate actually enforces. Generation surfaces (the Custom
39
+ * composer, the init templates) call this so the wording can never drift
40
+ * from the mode it is paired with.
41
+ */
42
+ export function understandingApprovalRequirement(mode) {
43
+ return mode === "strict"
44
+ ? "a human-approved Understanding Report for this session"
45
+ : "an approved Understanding Report for this session";
46
+ }
32
47
  const HOOK_NAME_PREFIX = `policy-pack:${PACK_NAME}`;
33
48
  // Per-runtime hook surface. Claude Code keys on tool name (Edit|Write|Bash);
34
49
  // Codex's write surface is `apply_patch` plus shell tool aliases used by