@lannguyensi/harness 0.49.0 → 0.50.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 (88) hide show
  1. package/CHANGELOG.md +683 -0
  2. package/dist/cli/add/index.js +15 -3
  3. package/dist/cli/add/index.js.map +1 -1
  4. package/dist/cli/apply/apply.js +19 -3
  5. package/dist/cli/apply/apply.js.map +1 -1
  6. package/dist/cli/approve/risk.d.ts +28 -2
  7. package/dist/cli/approve/risk.js +14 -4
  8. package/dist/cli/approve/risk.js.map +1 -1
  9. package/dist/cli/diff/index.js +22 -1
  10. package/dist/cli/diff/index.js.map +1 -1
  11. package/dist/cli/doctor/format.js +12 -2
  12. package/dist/cli/doctor/format.js.map +1 -1
  13. package/dist/cli/doctor/index.js +26 -1
  14. package/dist/cli/doctor/index.js.map +1 -1
  15. package/dist/cli/doctor/types.d.ts +26 -0
  16. package/dist/cli/explain-policy.d.ts +4 -1
  17. package/dist/cli/explain-policy.js +12 -2
  18. package/dist/cli/explain-policy.js.map +1 -1
  19. package/dist/cli/export.js +12 -1
  20. package/dist/cli/export.js.map +1 -1
  21. package/dist/cli/index.js +29 -7
  22. package/dist/cli/index.js.map +1 -1
  23. package/dist/cli/init/templates.d.ts +1 -1
  24. package/dist/cli/init/templates.js +65 -1
  25. package/dist/cli/init/templates.js.map +1 -1
  26. package/dist/cli/list.js +6 -0
  27. package/dist/cli/list.js.map +1 -1
  28. package/dist/cli/loader.js +15 -0
  29. package/dist/cli/loader.js.map +1 -1
  30. package/dist/cli/pack/hook-codex-pre-tool-use.js +11 -6
  31. package/dist/cli/pack/hook-codex-pre-tool-use.js.map +1 -1
  32. package/dist/cli/pack/hook-post-tool-use.d.ts +6 -4
  33. package/dist/cli/pack/hook-post-tool-use.js.map +1 -1
  34. package/dist/cli/pack/hook-pre-tool-use.js +23 -22
  35. package/dist/cli/pack/hook-pre-tool-use.js.map +1 -1
  36. package/dist/cli/remove/index.d.ts +12 -0
  37. package/dist/cli/remove/index.js +89 -0
  38. package/dist/cli/remove/index.js.map +1 -1
  39. package/dist/cli/validate/checks.d.ts +48 -0
  40. package/dist/cli/validate/checks.js +231 -0
  41. package/dist/cli/validate/checks.js.map +1 -1
  42. package/dist/cli/validate/index.js +11 -0
  43. package/dist/cli/validate/index.js.map +1 -1
  44. package/dist/policy-packs/builtin/understanding-before-execution/index.d.ts +2 -2
  45. package/dist/policy-packs/builtin/understanding-before-execution/index.js +33 -27
  46. package/dist/policy-packs/builtin/understanding-before-execution/index.js.map +1 -1
  47. package/dist/policy-packs/builtin/understanding-before-execution/markers.js +2 -2
  48. package/dist/policy-packs/builtin/understanding-before-execution/markers.js.map +1 -1
  49. package/dist/policy-packs/builtin/understanding-before-execution/persisted-reports.d.ts +46 -8
  50. package/dist/policy-packs/builtin/understanding-before-execution/persisted-reports.js +71 -12
  51. package/dist/policy-packs/builtin/understanding-before-execution/persisted-reports.js.map +1 -1
  52. package/dist/policy-packs/builtin/understanding-before-execution/post-tool-use-boundary.js +6 -3
  53. package/dist/policy-packs/builtin/understanding-before-execution/post-tool-use-boundary.js.map +1 -1
  54. package/dist/policy-packs/builtin/understanding-before-execution.js +9 -8
  55. package/dist/policy-packs/builtin/understanding-before-execution.js.map +1 -1
  56. package/dist/runtime/command-normalize.d.ts +112 -0
  57. package/dist/runtime/command-normalize.js +169 -55
  58. package/dist/runtime/command-normalize.js.map +1 -1
  59. package/dist/runtime/deletion-target-resolve.d.ts +28 -0
  60. package/dist/runtime/deletion-target-resolve.js +1055 -0
  61. package/dist/runtime/deletion-target-resolve.js.map +1 -0
  62. package/dist/runtime/index.d.ts +1 -0
  63. package/dist/runtime/index.js +1 -0
  64. package/dist/runtime/index.js.map +1 -1
  65. package/dist/runtime/intercept.js +17 -1
  66. package/dist/runtime/intercept.js.map +1 -1
  67. package/dist/runtime/kubectl-target-parse.d.ts +16 -0
  68. package/dist/runtime/kubectl-target-parse.js +9 -1
  69. package/dist/runtime/kubectl-target-parse.js.map +1 -1
  70. package/dist/runtime/read-only-bash.d.ts +11 -0
  71. package/dist/runtime/read-only-bash.js +288 -7
  72. package/dist/runtime/read-only-bash.js.map +1 -1
  73. package/dist/runtime/risk-classifier.js +14 -1
  74. package/dist/runtime/risk-classifier.js.map +1 -1
  75. package/dist/runtime/when-eval.d.ts +11 -2
  76. package/dist/runtime/when-eval.js +25 -0
  77. package/dist/runtime/when-eval.js.map +1 -1
  78. package/dist/runtime/workflow-policies.d.ts +164 -0
  79. package/dist/runtime/workflow-policies.js +439 -0
  80. package/dist/runtime/workflow-policies.js.map +1 -0
  81. package/dist/schema/index.d.ts +24 -0
  82. package/dist/schema/policies.d.ts +25 -0
  83. package/dist/schema/policies.js +29 -0
  84. package/dist/schema/policies.js.map +1 -1
  85. package/dist/schema/risk.d.ts +6 -0
  86. package/dist/schema/risk.js +80 -0
  87. package/dist/schema/risk.js.map +1 -1
  88. package/package.json +1 -1
@@ -0,0 +1,439 @@
1
+ /**
2
+ * Runtime enforcement for `workflows:` (99f47307, Slice 1).
3
+ *
4
+ * `manifest.workflows[]` describes a review-then-merge process (schema:
5
+ * `src/schema/workflows.ts`), but until now nothing read it at
6
+ * enforcement time: `src/runtime/intercept.ts` (the Risk Gate / policy
7
+ * engine `harness policy intercept` runs on every PreToolUse) only ever
8
+ * evaluates `manifest.policies[]`. A `workflows:` declaration alone was
9
+ * documentation, not a gate (`docs/for-agents.md`, pre-Slice-1 wording:
10
+ * "The schema cannot enforce that today").
11
+ *
12
+ * This module closes that gap for exactly ONE step pairing: a
13
+ * `review_subagent` step with `spawn: "required"` followed (at any
14
+ * later index in the same workflow) by a `merge` step. When BOTH
15
+ * `require-review-evidence` and `require-review-evidence-bash` are
16
+ * declared in `manifest.hooks[]`, `deriveWorkflowGatePolicies` returns
17
+ * the SAME policy pair `harness init --template full` would hand-author
18
+ * (`src/cli/init/templates.ts` `review-before-merge` /
19
+ * `review-before-merge-bash`), renamed per workflow so the provenance
20
+ * is visible in `harness list policies` / `harness explain`.
21
+ *
22
+ * Deliberately does NOT touch `src/runtime/intercept.ts`: the engine
23
+ * already knows how to evaluate a `Policy`, so the only thing missing
24
+ * was a `Policy` to hand it. `src/cli/loader.ts#loadManifest` calls
25
+ * `withDerivedPolicies` after parsing and appends the result to
26
+ * `manifest.policies` before any consumer (the CLI `policy intercept`
27
+ * entrypoint, `list`, `explain`, `explain-policy`) reads it.
28
+ *
29
+ * TWO VIEWS OF ONE MANIFEST (review round 3, 99f47307 Slice 1). Every
30
+ * reader of a parsed manifest sees exactly one of:
31
+ *
32
+ * - the HAND-AUTHORED view: `parseManifest(...)` as-is, only what the
33
+ * operator wrote under `policies:`. Right for anything that writes a
34
+ * manifest back to disk or serialises it for later comparison
35
+ * (`harness export`, the `.last-apply` manifest snapshot, `add` /
36
+ * `remove` / `adopt` / `pack reseed` file rewrites, the schema gate in
37
+ * `io/validate-before-write.ts`), because a derived policy written out
38
+ * as if hand-authored would be re-derived on top of itself next time.
39
+ * `withoutDerivedPolicies` turns a derived view back into this one.
40
+ * - the DERIVED view: `withDerivedPolicies(parseManifest(...))`, hand-
41
+ * authored policies plus the workflow gate pair. Right for enforcement
42
+ * (`policy intercept`, hook entrypoints), validation (`validate`,
43
+ * `doctor`, `add`'s asset gate), display (`list`, `explain[-policy]`,
44
+ * `describe`, `dry-run`), and every COMPARISON where the other side is
45
+ * also derived (`diff --since` ref side vs working side; the
46
+ * `.last-apply` snapshot vs the current manifest in restart hints).
47
+ * `loadManifest` hands out this view; the few readers that parse
48
+ * without it (`validate`, `diff`'s ref side, `apply`'s snapshot
49
+ * reader, `add`'s gate) call `withDerivedPolicies` themselves.
50
+ *
51
+ * Both helpers are idempotent and accept either view as input (they
52
+ * partition on `isDerivedPolicy`), so a reader can never "double derive"
53
+ * or strip a hand-authored policy by calling them on the wrong view.
54
+ * Review rounds 1 and 2 each found one reader on the wrong side of this
55
+ * line (`apply` vs `validate`; `diff --since`'s two sides); the table
56
+ * above plus tests/cli/manifest-view-parity.test.ts are the guard.
57
+ *
58
+ * Fail direction: when the two evidence hooks are NOT wired, this
59
+ * returns `[]` for every workflow rather than deriving an
60
+ * unenforceable policy (there would be no hook name to bind it to, and
61
+ * `manifest.policies[].hook` must reference a declared hook, see
62
+ * `ManifestSchema`'s superRefine in `src/schema/index.ts`). That silent
63
+ * non-enforcement case is exactly what `checkWorkflowGateWiring`
64
+ * (`src/cli/validate/checks.ts`) turns into a loud `validate` error, so
65
+ * "declared `spawn: required`, gate never wired" cannot pass silently.
66
+ */
67
+ /** The two hooks templates.ts wires for the hand-authored merge gate. */
68
+ export const REVIEW_EVIDENCE_HOOK_MCP = "require-review-evidence";
69
+ export const REVIEW_EVIDENCE_HOOK_BASH = "require-review-evidence-bash";
70
+ // Exported (review round 2, F5) so `src/cli/validate/checks.ts` can check a
71
+ // declared hook's `match`/`bash_match` against the EXACT surface the
72
+ // derivation binds to, instead of trusting the hook name alone. See
73
+ // `checkWorkflowGateWiring`'s header for the "right name, wrong trigger"
74
+ // gap this closes.
75
+ export const MERGE_MCP_MATCH = "mcp__agent-tasks__pull_requests_merge";
76
+ // Byte-identical to templates.ts's `review-before-merge-bash` trigger
77
+ // (verified against the parsed FULL_TEMPLATE at authoring time; pinned
78
+ // by tests/runtime/workflow-policies.test.ts's parity assertion so the
79
+ // two cannot silently drift apart).
80
+ export const MERGE_BASH_MATCH = "(^|\\n|;|\\||&|\\()\\s*(\\w+=\\S+\\s+)*gh pr merge\\b";
81
+ /**
82
+ * True when `workflow.steps` contains a `review_subagent` step with
83
+ * `spawn: "required"` at an index earlier than some `merge` step
84
+ * (not necessarily adjacent; a `ci_gate` or another step may sit
85
+ * between them). A workflow with no `merge` step at all, or whose only
86
+ * `review_subagent` steps are `spawn: "optional"` / `"skip"`, returns
87
+ * `false`.
88
+ */
89
+ export function workflowRequiresMergeGate(workflow) {
90
+ let sawRequiredReview = false;
91
+ for (const step of workflow.steps) {
92
+ if (step.kind === "review_subagent" && step.spawn === "required") {
93
+ sawRequiredReview = true;
94
+ }
95
+ else if (step.kind === "merge" && sawRequiredReview) {
96
+ return true;
97
+ }
98
+ }
99
+ return false;
100
+ }
101
+ /** True when both merge-gate evidence hooks are declared in `manifest.hooks[]`. */
102
+ export function hasWiredMergeGateHooks(manifest) {
103
+ const hookNames = new Set(manifest.hooks.map((h) => h.name));
104
+ return hookNames.has(REVIEW_EVIDENCE_HOOK_MCP) && hookNames.has(REVIEW_EVIDENCE_HOOK_BASH);
105
+ }
106
+ /**
107
+ * Canonical key for "does this policy intercept the same surface for the
108
+ * same evidence?": event + match + bash_match + requires.ledger_tag. Two
109
+ * policies sharing this key fire on the identical event for the identical
110
+ * ledger tag, so the derivation treats a hand-authored policy with this
111
+ * key as a CANDIDATE for standing in for the derived gate. Whether it
112
+ * actually does is decided by `isEquivalentToDerivedGate` below: it must
113
+ * also extract its template variables the same way (F4, review round 3)
114
+ * and be at least as strong (F1, review round 2).
115
+ */
116
+ function triggerSurfaceKey(policy) {
117
+ return JSON.stringify({
118
+ event: policy.trigger.event,
119
+ match: policy.trigger.match ?? null,
120
+ bash_match: policy.trigger.bash_match ?? null,
121
+ ledger_tag: policy.requires?.ledger_tag ?? null,
122
+ });
123
+ }
124
+ /**
125
+ * Canonical form of `trigger.extract` for equality: key-sorted so two
126
+ * maps that declare the same variables from the same paths compare equal
127
+ * regardless of authoring order. `null` when the policy extracts nothing.
128
+ *
129
+ * F4 (review round 3): `extract` was NOT part of the dedupe key before,
130
+ * so a hand-authored block policy on the derived surface that extracted
131
+ * `PR_NUMBER` from a WRONG path (say `toolArgs.pr` instead of
132
+ * `toolArgs.prNumber`) counted as equivalent, suppressed the derived
133
+ * gate, and then evaluated its own `review:${PR_NUMBER}` against an
134
+ * unresolved variable. Under `risk.degraded_fail_posture: fail_open` that
135
+ * is an allow with "template variables unresolved": the merge went
136
+ * through with no review evidence at all. A differently-extracting policy
137
+ * no longer dedupes: the derived gate is produced as well, both apply,
138
+ * and `findWeakGatePolicyOverlaps` names the mismatch.
139
+ */
140
+ function extractKey(policy) {
141
+ const extract = policy.trigger.extract;
142
+ if (extract === undefined)
143
+ return null;
144
+ const sorted = {};
145
+ for (const key of Object.keys(extract).sort()) {
146
+ const value = extract[key];
147
+ if (value !== undefined)
148
+ sorted[key] = value;
149
+ }
150
+ return JSON.stringify(sorted);
151
+ }
152
+ /**
153
+ * True when a hand-authored policy sharing the derived gate's trigger
154
+ * surface + ledger_tag is strong enough to stand in for it: `enforcement:
155
+ * "block"`, no `when:` risk/environment scoping, and not `operator_only:
156
+ * true` (which carries no `requires:`/ledger_tag at all, so it would not
157
+ * normally share a key, but the check is defensive since operator_only
158
+ * would otherwise read as "block" and pass the enforcement check alone).
159
+ *
160
+ * F1 (review round 2, 99f47307 Slice 1): before this, `triggerSurfaceKey`
161
+ * alone decided dedupe, so a hand-authored `enforcement: "warn"` policy
162
+ * (or a `block` policy scoped down via `when:` to only some environments)
163
+ * on the identical surface silently suppressed the derived BLOCK gate: a
164
+ * `spawn: "required"` workflow step that LOOKED enforced actually
165
+ * degraded to warn-only, or to unenforced outside the `when:` scope,
166
+ * with no diagnostic anywhere. A weaker match no longer dedupes: the
167
+ * derived block gate is ALSO produced (both apply), and
168
+ * `findWeakGatePolicyOverlaps` surfaces the overlap so validate/doctor
169
+ * can flag it instead of leaving it silent.
170
+ */
171
+ function isAtLeastAsStrongAsDerivedGate(policy) {
172
+ return (policy.enforcement === "block" &&
173
+ policy.when === undefined &&
174
+ policy.operator_only !== true);
175
+ }
176
+ /**
177
+ * Why `policy` does NOT qualify as at-least-as-strong (see
178
+ * `isAtLeastAsStrongAsDerivedGate`), in a form suitable for a diagnostic
179
+ * message. `null` when the policy DOES qualify (nothing weak to report).
180
+ */
181
+ function weaknessReason(policy) {
182
+ if (isAtLeastAsStrongAsDerivedGate(policy))
183
+ return null;
184
+ if (policy.enforcement !== "block")
185
+ return `enforcement: ${policy.enforcement}`;
186
+ if (policy.when !== undefined)
187
+ return "when: (risk/environment-scoped)";
188
+ if (policy.operator_only === true)
189
+ return "operator_only: true";
190
+ return "weaker than a plain block gate";
191
+ }
192
+ /**
193
+ * Why a hand-authored policy on the derived gate's surface does NOT stand
194
+ * in for it, or `null` when it does (same surface, same extract, at least
195
+ * as strong). Strength is reported before an extract mismatch: a warn
196
+ * policy with a wrong extract path is first and foremost a warn policy.
197
+ */
198
+ function nonEquivalenceReason(handPolicy, derivedGate) {
199
+ const weakness = weaknessReason(handPolicy);
200
+ if (weakness !== null)
201
+ return weakness;
202
+ const handExtract = extractKey(handPolicy);
203
+ const gateExtract = extractKey(derivedGate);
204
+ if (handExtract !== gateExtract) {
205
+ return `trigger.extract differs (${handExtract ?? "none"} vs derived ${gateExtract ?? "none"})`;
206
+ }
207
+ return null;
208
+ }
209
+ function buildMcpMergeGatePolicy(workflowName) {
210
+ return {
211
+ name: `workflow:${workflowName}:review-before-merge`,
212
+ description: `Derived from workflows[] (workflow "${workflowName}"): a review_subagent step ` +
213
+ `with spawn: "required" precedes a merge step, so block PR merges unless a ` +
214
+ "ledger entry tagged review:<pr-number> exists for this session.",
215
+ trigger: {
216
+ event: "PreToolUse",
217
+ match: MERGE_MCP_MATCH,
218
+ extract: { PR_NUMBER: "toolArgs.prNumber" },
219
+ },
220
+ requires: { ledger_tag: "review:${PR_NUMBER}" },
221
+ hook: REVIEW_EVIDENCE_HOOK_MCP,
222
+ enforcement: "block",
223
+ producers: [
224
+ {
225
+ kind: "mcp",
226
+ verb: "mcp__grounding-mcp__ledger_add",
227
+ example: '{sessionId:"${SESSION_ID}", type:"fact", content:"review:${PR_NUMBER}: <verdict + key findings + nits>", source:"Agent(general-purpose) review"}',
228
+ description: "Spawn a review subagent against the PR diff, capture its verdict, then persist a ledger entry tagged with the PR number. The content should be self-contained enough for an auditor to read without re-opening the chat.",
229
+ },
230
+ ],
231
+ ux: {
232
+ cannot: "You cannot merge PR #${PR_NUMBER} yet.",
233
+ required: ["a recorded review of PR #${PR_NUMBER}"],
234
+ run: ['harness record review --pr ${PR_NUMBER} "<summary>"'],
235
+ },
236
+ };
237
+ }
238
+ function buildBashMergeGatePolicy(workflowName) {
239
+ return {
240
+ name: `workflow:${workflowName}:review-before-merge-bash`,
241
+ description: `Derived from workflows[] (workflow "${workflowName}"): a review_subagent step ` +
242
+ 'with spawn: "required" precedes a merge step, so block `gh pr merge` unless a ' +
243
+ "ledger entry tagged review:<branch> exists for this session.",
244
+ trigger: {
245
+ event: "PreToolUse",
246
+ match: "Bash",
247
+ bash_match: MERGE_BASH_MATCH,
248
+ },
249
+ requires: { ledger_tag: "review:${BRANCH}" },
250
+ hook: REVIEW_EVIDENCE_HOOK_BASH,
251
+ enforcement: "block",
252
+ producers: [
253
+ {
254
+ kind: "mcp",
255
+ verb: "mcp__grounding-mcp__ledger_add",
256
+ example: '{sessionId:"${SESSION_ID}", type:"fact", content:"review:${BRANCH}: <verdict + key findings + nits>", source:"Agent(general-purpose) review"}',
257
+ description: "Spawn a review subagent against the branch diff, capture its verdict, then persist a ledger entry tagged with the branch name. Mirror of the review-before-merge producer for the gh-cli surface.",
258
+ },
259
+ ],
260
+ ux: {
261
+ cannot: "You cannot merge the PR for branch ${BRANCH} via `gh pr merge` yet.",
262
+ required: ["a recorded review of the PR for branch ${BRANCH}"],
263
+ run: ['harness record review --pr <pr> "<summary>"'],
264
+ },
265
+ };
266
+ }
267
+ // F7 (review round 2): a registry of every `Policy` object this module has
268
+ // ever handed back from `deriveWorkflowGatePolicies`. `WeakSet` keys on
269
+ // object identity, not value equality, deliberately: two DIFFERENT
270
+ // workflows can derive value-identical-looking policies with different
271
+ // names, and a hand-authored policy could theoretically share a derived
272
+ // policy's exact shape too (nothing stops an operator hand-copying one
273
+ // into `policies:`). Identity is the only unambiguous "did THIS module
274
+ // produce THIS object" test. `harness export`/`list`/`doctor` use
275
+ // `isDerivedPolicy` to distinguish "declared by the operator" from
276
+ // "derived from workflows[]" without re-deriving and re-comparing.
277
+ const derivedPolicyRegistry = new WeakSet();
278
+ /** True when `policy` is an object this module derived (see `withDerivedPolicies`). */
279
+ export function isDerivedPolicy(policy) {
280
+ return derivedPolicyRegistry.has(policy);
281
+ }
282
+ /**
283
+ * The hand-authored slice of `manifest.policies`: everything the operator
284
+ * wrote under `policies:`, none of the workflow-derived entries. Works on
285
+ * either view (see module doc); on a hand-authored view it is the
286
+ * identity.
287
+ */
288
+ export function handAuthoredPolicies(manifest) {
289
+ return manifest.policies.filter((p) => !isDerivedPolicy(p));
290
+ }
291
+ /**
292
+ * The ONE walk that decides, per qualifying workflow and per surface,
293
+ * whether a gate is derived and which hand-authored policies overlap it.
294
+ * `deriveWorkflowGatePolicies` and `findWeakGatePolicyOverlaps` are both
295
+ * projections of this result, so they cannot disagree about what was
296
+ * derived (F1, review round 3: the overlap finder used to re-implement
297
+ * the walk without the `seen` set, and warned about a "derived" gate that
298
+ * a strong hand-authored policy had in fact suppressed).
299
+ *
300
+ * Only HAND-authored policies feed the walk (`handAuthoredPolicies`), so
301
+ * the result is the same whether the input is the hand-authored or the
302
+ * derived view.
303
+ */
304
+ function deriveWorkflowGates(manifest) {
305
+ const empty = { policies: [], overlaps: [] };
306
+ if (!hasWiredMergeGateHooks(manifest))
307
+ return empty;
308
+ const hand = handAuthoredPolicies(manifest);
309
+ const bySurface = new Map();
310
+ for (const policy of hand) {
311
+ const key = triggerSurfaceKey(policy);
312
+ const bucket = bySurface.get(key);
313
+ if (bucket)
314
+ bucket.push(policy);
315
+ else
316
+ bySurface.set(key, [policy]);
317
+ }
318
+ // Surfaces already covered: by an equivalent hand-authored policy (F1 +
319
+ // F4: at least as strong AND extracting the same way), or by a gate an
320
+ // earlier workflow in this same manifest already derived.
321
+ const seen = new Set();
322
+ const derived = [];
323
+ const overlaps = [];
324
+ for (const workflow of manifest.workflows) {
325
+ if (!workflowRequiresMergeGate(workflow))
326
+ continue;
327
+ const candidates = [
328
+ { policy: buildMcpMergeGatePolicy(workflow.name), surface: MERGE_MCP_MATCH },
329
+ { policy: buildBashMergeGatePolicy(workflow.name), surface: "`gh pr merge` (Bash)" },
330
+ ];
331
+ for (const { policy: candidate, surface } of candidates) {
332
+ const key = triggerSurfaceKey(candidate);
333
+ if (seen.has(key))
334
+ continue;
335
+ const sharing = bySurface.get(key) ?? [];
336
+ const reasons = sharing.map((hp) => ({
337
+ handPolicy: hp,
338
+ reason: nonEquivalenceReason(hp, candidate),
339
+ }));
340
+ if (reasons.some((r) => r.reason === null)) {
341
+ // An equivalent hand-authored policy already gates this surface;
342
+ // nothing is derived, so there is nothing to overlap with either.
343
+ seen.add(key);
344
+ continue;
345
+ }
346
+ derived.push(candidate);
347
+ seen.add(key);
348
+ for (const { handPolicy, reason } of reasons) {
349
+ if (reason === null)
350
+ continue;
351
+ overlaps.push({
352
+ workflowName: workflow.name,
353
+ derivedPolicyName: candidate.name,
354
+ surface,
355
+ handPolicyName: handPolicy.name,
356
+ reason,
357
+ });
358
+ }
359
+ }
360
+ }
361
+ return { policies: derived, overlaps };
362
+ }
363
+ /**
364
+ * Derive the merge-gate `Policy` pair for every workflow that needs one
365
+ * and does not already have an equivalent policy (hand-authored, or
366
+ * derived from an earlier workflow in the same manifest) intercepting
367
+ * the same surface for the same evidence tag.
368
+ *
369
+ * Returns `[]` when `manifest.hooks[]` is missing either evidence hook
370
+ * (see module doc: that gap is a `validate` error, not silent
371
+ * derivation of an unenforceable policy) or when no workflow declares
372
+ * a `spawn: "required"` review step followed by a merge step.
373
+ */
374
+ export function deriveWorkflowGatePolicies(manifest) {
375
+ return deriveWorkflowGates(manifest).policies;
376
+ }
377
+ /**
378
+ * Every hand-authored policy that shares an ACTUALLY DERIVED gate's
379
+ * trigger surface + ledger_tag but does not stand in for it: weaker
380
+ * (F1, review round 2) or extracting its variables differently (F4,
381
+ * review round 3). Both policies apply in that case, and the overlap is
382
+ * worth flagging: an operator reading `enforcement: warn` on a
383
+ * `two-reviewers-required`-shaped policy might reasonably believe THAT
384
+ * is the only gate on the surface. `src/cli/validate/checks.ts` turns
385
+ * each entry into a warning Diagnostic; `src/cli/doctor/index.ts`
386
+ * renders the same list in the Workflows section.
387
+ *
388
+ * Mirrors `deriveWorkflowGatePolicies` exactly (same walk, same `seen`
389
+ * set): a surface an equivalent hand-authored policy already covers
390
+ * derives nothing and therefore reports nothing, even when a second,
391
+ * weaker policy sits on it too. Returns `[]` when the evidence hooks are
392
+ * not wired or no workflow needs a gate.
393
+ */
394
+ export function findWeakGatePolicyOverlaps(manifest) {
395
+ return deriveWorkflowGates(manifest).overlaps;
396
+ }
397
+ /**
398
+ * The ONE place `workflows[]` gets folded into `manifest.policies` (F2,
399
+ * review round 2). Before this, `src/cli/loader.ts#loadManifest` inlined
400
+ * the append, so every OTHER manifest consumer that parses the manifest
401
+ * a different way (`src/cli/validate/index.ts`'s `loadMergedRaw` +
402
+ * `parseManifest`, notably) saw a manifest with no derived policies at
403
+ * all, and `apply` and `validate` diverged. See the module doc for the
404
+ * full list of readers and which view each one takes.
405
+ *
406
+ * Idempotent and view-agnostic (review round 3): the derivation runs on
407
+ * `handAuthoredPolicies(manifest)` only and any derived entries already
408
+ * present are replaced, so calling this on an already-derived view yields
409
+ * the same policy names with no duplicates. Returns the input object
410
+ * unchanged when there is nothing to derive and nothing to replace.
411
+ *
412
+ * Registers each derived `Policy` object in the module-level
413
+ * `derivedPolicyRegistry` (F7) so `isDerivedPolicy` can later distinguish
414
+ * it from a hand-authored one (used by `harness export`'s filter and
415
+ * `list`/`doctor`'s "(derived from workflows[])" marker).
416
+ */
417
+ export function withDerivedPolicies(manifest) {
418
+ const hand = handAuthoredPolicies(manifest);
419
+ const derivedPolicies = deriveWorkflowGatePolicies(manifest);
420
+ if (derivedPolicies.length === 0 && hand.length === manifest.policies.length)
421
+ return manifest;
422
+ for (const policy of derivedPolicies)
423
+ derivedPolicyRegistry.add(policy);
424
+ return { ...manifest, policies: [...hand, ...derivedPolicies] };
425
+ }
426
+ /**
427
+ * The hand-authored view of a manifest that may carry derived policies:
428
+ * what `harness export` emits and what the `.last-apply` manifest
429
+ * snapshot stores, so nothing that gets written out or re-read later
430
+ * carries a derived policy as if the operator had declared it. Returns
431
+ * the input object unchanged when it carries no derived policy.
432
+ */
433
+ export function withoutDerivedPolicies(manifest) {
434
+ const hand = handAuthoredPolicies(manifest);
435
+ if (hand.length === manifest.policies.length)
436
+ return manifest;
437
+ return { ...manifest, policies: hand };
438
+ }
439
+ //# sourceMappingURL=workflow-policies.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workflow-policies.js","sourceRoot":"","sources":["../../src/runtime/workflow-policies.ts"],"names":[],"mappings":"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"}
@@ -574,26 +574,31 @@ export declare const ManifestSchema: z.ZodEffects<z.ZodObject<{
574
574
  "risk.category_in": z.ZodOptional<z.ZodArray<z.ZodEnum<["destructive", "data_loss", "production_mutation", "credential_access", "secret_exfiltration", "network_exfiltration", "deployment_change", "infrastructure_change", "privilege_escalation", "irreversible_action", "mass_update"]>, "many">>;
575
575
  "environment.name": z.ZodOptional<z.ZodEnum<["production", "staging", "dev", "local", "unknown"]>>;
576
576
  "action.reversible": z.ZodOptional<z.ZodBoolean>;
577
+ "action.deletion_target_unresolvable": z.ZodOptional<z.ZodLiteral<true>>;
577
578
  }, "strict", z.ZodTypeAny, {
578
579
  "risk.severity_at_least"?: "low" | "medium" | "high" | "critical" | undefined;
579
580
  "risk.category_in"?: ("destructive" | "data_loss" | "production_mutation" | "credential_access" | "secret_exfiltration" | "network_exfiltration" | "deployment_change" | "infrastructure_change" | "privilege_escalation" | "irreversible_action" | "mass_update")[] | undefined;
580
581
  "environment.name"?: "production" | "staging" | "dev" | "local" | "unknown" | undefined;
581
582
  "action.reversible"?: boolean | undefined;
583
+ "action.deletion_target_unresolvable"?: true | undefined;
582
584
  }, {
583
585
  "risk.severity_at_least"?: "low" | "medium" | "high" | "critical" | undefined;
584
586
  "risk.category_in"?: ("destructive" | "data_loss" | "production_mutation" | "credential_access" | "secret_exfiltration" | "network_exfiltration" | "deployment_change" | "infrastructure_change" | "privilege_escalation" | "irreversible_action" | "mass_update")[] | undefined;
585
587
  "environment.name"?: "production" | "staging" | "dev" | "local" | "unknown" | undefined;
586
588
  "action.reversible"?: boolean | undefined;
589
+ "action.deletion_target_unresolvable"?: true | undefined;
587
590
  }>, {
588
591
  "risk.severity_at_least"?: "low" | "medium" | "high" | "critical" | undefined;
589
592
  "risk.category_in"?: ("destructive" | "data_loss" | "production_mutation" | "credential_access" | "secret_exfiltration" | "network_exfiltration" | "deployment_change" | "infrastructure_change" | "privilege_escalation" | "irreversible_action" | "mass_update")[] | undefined;
590
593
  "environment.name"?: "production" | "staging" | "dev" | "local" | "unknown" | undefined;
591
594
  "action.reversible"?: boolean | undefined;
595
+ "action.deletion_target_unresolvable"?: true | undefined;
592
596
  }, {
593
597
  "risk.severity_at_least"?: "low" | "medium" | "high" | "critical" | undefined;
594
598
  "risk.category_in"?: ("destructive" | "data_loss" | "production_mutation" | "credential_access" | "secret_exfiltration" | "network_exfiltration" | "deployment_change" | "infrastructure_change" | "privilege_escalation" | "irreversible_action" | "mass_update")[] | undefined;
595
599
  "environment.name"?: "production" | "staging" | "dev" | "local" | "unknown" | undefined;
596
600
  "action.reversible"?: boolean | undefined;
601
+ "action.deletion_target_unresolvable"?: true | undefined;
597
602
  }>>;
598
603
  }, "strict", z.ZodTypeAny, {
599
604
  name: string;
@@ -642,6 +647,7 @@ export declare const ManifestSchema: z.ZodEffects<z.ZodObject<{
642
647
  "risk.category_in"?: ("destructive" | "data_loss" | "production_mutation" | "credential_access" | "secret_exfiltration" | "network_exfiltration" | "deployment_change" | "infrastructure_change" | "privilege_escalation" | "irreversible_action" | "mass_update")[] | undefined;
643
648
  "environment.name"?: "production" | "staging" | "dev" | "local" | "unknown" | undefined;
644
649
  "action.reversible"?: boolean | undefined;
650
+ "action.deletion_target_unresolvable"?: true | undefined;
645
651
  } | undefined;
646
652
  }, {
647
653
  name: string;
@@ -690,6 +696,7 @@ export declare const ManifestSchema: z.ZodEffects<z.ZodObject<{
690
696
  "risk.category_in"?: ("destructive" | "data_loss" | "production_mutation" | "credential_access" | "secret_exfiltration" | "network_exfiltration" | "deployment_change" | "infrastructure_change" | "privilege_escalation" | "irreversible_action" | "mass_update")[] | undefined;
691
697
  "environment.name"?: "production" | "staging" | "dev" | "local" | "unknown" | undefined;
692
698
  "action.reversible"?: boolean | undefined;
699
+ "action.deletion_target_unresolvable"?: true | undefined;
693
700
  } | undefined;
694
701
  }>, {
695
702
  name: string;
@@ -738,6 +745,7 @@ export declare const ManifestSchema: z.ZodEffects<z.ZodObject<{
738
745
  "risk.category_in"?: ("destructive" | "data_loss" | "production_mutation" | "credential_access" | "secret_exfiltration" | "network_exfiltration" | "deployment_change" | "infrastructure_change" | "privilege_escalation" | "irreversible_action" | "mass_update")[] | undefined;
739
746
  "environment.name"?: "production" | "staging" | "dev" | "local" | "unknown" | undefined;
740
747
  "action.reversible"?: boolean | undefined;
748
+ "action.deletion_target_unresolvable"?: true | undefined;
741
749
  } | undefined;
742
750
  }, {
743
751
  name: string;
@@ -786,6 +794,7 @@ export declare const ManifestSchema: z.ZodEffects<z.ZodObject<{
786
794
  "risk.category_in"?: ("destructive" | "data_loss" | "production_mutation" | "credential_access" | "secret_exfiltration" | "network_exfiltration" | "deployment_change" | "infrastructure_change" | "privilege_escalation" | "irreversible_action" | "mass_update")[] | undefined;
787
795
  "environment.name"?: "production" | "staging" | "dev" | "local" | "unknown" | undefined;
788
796
  "action.reversible"?: boolean | undefined;
797
+ "action.deletion_target_unresolvable"?: true | undefined;
789
798
  } | undefined;
790
799
  }>, "many">, {
791
800
  name: string;
@@ -834,6 +843,7 @@ export declare const ManifestSchema: z.ZodEffects<z.ZodObject<{
834
843
  "risk.category_in"?: ("destructive" | "data_loss" | "production_mutation" | "credential_access" | "secret_exfiltration" | "network_exfiltration" | "deployment_change" | "infrastructure_change" | "privilege_escalation" | "irreversible_action" | "mass_update")[] | undefined;
835
844
  "environment.name"?: "production" | "staging" | "dev" | "local" | "unknown" | undefined;
836
845
  "action.reversible"?: boolean | undefined;
846
+ "action.deletion_target_unresolvable"?: true | undefined;
837
847
  } | undefined;
838
848
  }[], {
839
849
  name: string;
@@ -882,6 +892,7 @@ export declare const ManifestSchema: z.ZodEffects<z.ZodObject<{
882
892
  "risk.category_in"?: ("destructive" | "data_loss" | "production_mutation" | "credential_access" | "secret_exfiltration" | "network_exfiltration" | "deployment_change" | "infrastructure_change" | "privilege_escalation" | "irreversible_action" | "mass_update")[] | undefined;
883
893
  "environment.name"?: "production" | "staging" | "dev" | "local" | "unknown" | undefined;
884
894
  "action.reversible"?: boolean | undefined;
895
+ "action.deletion_target_unresolvable"?: true | undefined;
885
896
  } | undefined;
886
897
  }[]>>;
887
898
  policy_packs: z.ZodDefault<z.ZodEffects<z.ZodArray<z.ZodObject<{
@@ -963,6 +974,7 @@ export declare const ManifestSchema: z.ZodEffects<z.ZodObject<{
963
974
  tool: string;
964
975
  }>, "many">>;
965
976
  degraded_fail_posture: z.ZodDefault<z.ZodEnum<["preserve_enforcement", "fail_open"]>>;
977
+ safe_deletion_roots: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
966
978
  }, "strict", z.ZodTypeAny, {
967
979
  classifiers: {
968
980
  patterns: {
@@ -974,6 +986,7 @@ export declare const ManifestSchema: z.ZodEffects<z.ZodObject<{
974
986
  tool: string;
975
987
  }[];
976
988
  degraded_fail_posture: "preserve_enforcement" | "fail_open";
989
+ safe_deletion_roots: string[];
977
990
  }, {
978
991
  classifiers?: {
979
992
  patterns: {
@@ -985,6 +998,7 @@ export declare const ManifestSchema: z.ZodEffects<z.ZodObject<{
985
998
  tool: string;
986
999
  }[] | undefined;
987
1000
  degraded_fail_posture?: "preserve_enforcement" | "fail_open" | undefined;
1001
+ safe_deletion_roots?: string[] | undefined;
988
1002
  }>, {
989
1003
  classifiers: {
990
1004
  patterns: {
@@ -996,6 +1010,7 @@ export declare const ManifestSchema: z.ZodEffects<z.ZodObject<{
996
1010
  tool: string;
997
1011
  }[];
998
1012
  degraded_fail_posture: "preserve_enforcement" | "fail_open";
1013
+ safe_deletion_roots: string[];
999
1014
  }, {
1000
1015
  classifiers?: {
1001
1016
  patterns: {
@@ -1007,6 +1022,7 @@ export declare const ManifestSchema: z.ZodEffects<z.ZodObject<{
1007
1022
  tool: string;
1008
1023
  }[] | undefined;
1009
1024
  degraded_fail_posture?: "preserve_enforcement" | "fail_open" | undefined;
1025
+ safe_deletion_roots?: string[] | undefined;
1010
1026
  }>>;
1011
1027
  environments: z.ZodDefault<z.ZodEffects<z.ZodObject<{
1012
1028
  resolvers: z.ZodDefault<z.ZodArray<z.ZodObject<{
@@ -2660,6 +2676,7 @@ export declare const ManifestSchema: z.ZodEffects<z.ZodObject<{
2660
2676
  "risk.category_in"?: ("destructive" | "data_loss" | "production_mutation" | "credential_access" | "secret_exfiltration" | "network_exfiltration" | "deployment_change" | "infrastructure_change" | "privilege_escalation" | "irreversible_action" | "mass_update")[] | undefined;
2661
2677
  "environment.name"?: "production" | "staging" | "dev" | "local" | "unknown" | undefined;
2662
2678
  "action.reversible"?: boolean | undefined;
2679
+ "action.deletion_target_unresolvable"?: true | undefined;
2663
2680
  } | undefined;
2664
2681
  }[];
2665
2682
  policy_packs: {
@@ -2681,6 +2698,7 @@ export declare const ManifestSchema: z.ZodEffects<z.ZodObject<{
2681
2698
  tool: string;
2682
2699
  }[];
2683
2700
  degraded_fail_posture: "preserve_enforcement" | "fail_open";
2701
+ safe_deletion_roots: string[];
2684
2702
  };
2685
2703
  environments: {
2686
2704
  resolvers: {
@@ -2974,6 +2992,7 @@ export declare const ManifestSchema: z.ZodEffects<z.ZodObject<{
2974
2992
  "risk.category_in"?: ("destructive" | "data_loss" | "production_mutation" | "credential_access" | "secret_exfiltration" | "network_exfiltration" | "deployment_change" | "infrastructure_change" | "privilege_escalation" | "irreversible_action" | "mass_update")[] | undefined;
2975
2993
  "environment.name"?: "production" | "staging" | "dev" | "local" | "unknown" | undefined;
2976
2994
  "action.reversible"?: boolean | undefined;
2995
+ "action.deletion_target_unresolvable"?: true | undefined;
2977
2996
  } | undefined;
2978
2997
  }[] | undefined;
2979
2998
  policy_packs?: {
@@ -2995,6 +3014,7 @@ export declare const ManifestSchema: z.ZodEffects<z.ZodObject<{
2995
3014
  tool: string;
2996
3015
  }[] | undefined;
2997
3016
  degraded_fail_posture?: "preserve_enforcement" | "fail_open" | undefined;
3017
+ safe_deletion_roots?: string[] | undefined;
2998
3018
  } | undefined;
2999
3019
  environments?: {
3000
3020
  resolvers?: {
@@ -3288,6 +3308,7 @@ export declare const ManifestSchema: z.ZodEffects<z.ZodObject<{
3288
3308
  "risk.category_in"?: ("destructive" | "data_loss" | "production_mutation" | "credential_access" | "secret_exfiltration" | "network_exfiltration" | "deployment_change" | "infrastructure_change" | "privilege_escalation" | "irreversible_action" | "mass_update")[] | undefined;
3289
3309
  "environment.name"?: "production" | "staging" | "dev" | "local" | "unknown" | undefined;
3290
3310
  "action.reversible"?: boolean | undefined;
3311
+ "action.deletion_target_unresolvable"?: true | undefined;
3291
3312
  } | undefined;
3292
3313
  }[];
3293
3314
  policy_packs: {
@@ -3309,6 +3330,7 @@ export declare const ManifestSchema: z.ZodEffects<z.ZodObject<{
3309
3330
  tool: string;
3310
3331
  }[];
3311
3332
  degraded_fail_posture: "preserve_enforcement" | "fail_open";
3333
+ safe_deletion_roots: string[];
3312
3334
  };
3313
3335
  environments: {
3314
3336
  resolvers: {
@@ -3602,6 +3624,7 @@ export declare const ManifestSchema: z.ZodEffects<z.ZodObject<{
3602
3624
  "risk.category_in"?: ("destructive" | "data_loss" | "production_mutation" | "credential_access" | "secret_exfiltration" | "network_exfiltration" | "deployment_change" | "infrastructure_change" | "privilege_escalation" | "irreversible_action" | "mass_update")[] | undefined;
3603
3625
  "environment.name"?: "production" | "staging" | "dev" | "local" | "unknown" | undefined;
3604
3626
  "action.reversible"?: boolean | undefined;
3627
+ "action.deletion_target_unresolvable"?: true | undefined;
3605
3628
  } | undefined;
3606
3629
  }[] | undefined;
3607
3630
  policy_packs?: {
@@ -3623,6 +3646,7 @@ export declare const ManifestSchema: z.ZodEffects<z.ZodObject<{
3623
3646
  tool: string;
3624
3647
  }[] | undefined;
3625
3648
  degraded_fail_posture?: "preserve_enforcement" | "fail_open" | undefined;
3649
+ safe_deletion_roots?: string[] | undefined;
3626
3650
  } | undefined;
3627
3651
  environments?: {
3628
3652
  resolvers?: {