@lannguyensi/harness 0.64.0 → 1.0.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 +64 -0
- package/README.md +46 -42
- package/dist/cli/add/index.js +17 -21
- package/dist/cli/add/index.js.map +1 -1
- package/dist/cli/adopt/derive.d.ts +2 -2
- package/dist/cli/adopt/derive.js +2 -19
- package/dist/cli/adopt/derive.js.map +1 -1
- package/dist/cli/adopt/index.d.ts +1 -1
- package/dist/cli/adopt/index.js +1 -12
- package/dist/cli/adopt/index.js.map +1 -1
- package/dist/cli/apply/apply.d.ts +20 -35
- package/dist/cli/apply/apply.js +61 -136
- package/dist/cli/apply/apply.js.map +1 -1
- package/dist/cli/apply/generate-codex-config.js +10 -58
- package/dist/cli/apply/generate-codex-config.js.map +1 -1
- package/dist/cli/apply/generate-memory-index.js +1 -2
- package/dist/cli/apply/generate-memory-index.js.map +1 -1
- package/dist/cli/apply/generate-opencode-config.d.ts +2 -10
- package/dist/cli/apply/generate-opencode-config.js +74 -113
- package/dist/cli/apply/generate-opencode-config.js.map +1 -1
- package/dist/cli/apply/generate-settings.d.ts +11 -64
- package/dist/cli/apply/generate-settings.js +27 -132
- package/dist/cli/apply/generate-settings.js.map +1 -1
- package/dist/cli/apply/index.d.ts +1 -1
- package/dist/cli/apply/index.js +1 -1
- package/dist/cli/apply/index.js.map +1 -1
- package/dist/cli/apply/install-codex-config.d.ts +153 -1
- package/dist/cli/apply/install-codex-config.js +445 -70
- package/dist/cli/apply/install-codex-config.js.map +1 -1
- package/dist/cli/apply/next-steps.d.ts +7 -0
- package/dist/cli/apply/next-steps.js +13 -1
- package/dist/cli/apply/next-steps.js.map +1 -1
- package/dist/cli/bounded-stdin.d.ts +0 -23
- package/dist/cli/bounded-stdin.js +2 -49
- package/dist/cli/bounded-stdin.js.map +1 -1
- package/dist/cli/describe.d.ts +1 -1
- package/dist/cli/describe.js +0 -3
- package/dist/cli/describe.js.map +1 -1
- package/dist/cli/diff/engine.d.ts +0 -11
- package/dist/cli/diff/engine.js +1 -17
- package/dist/cli/diff/engine.js.map +1 -1
- package/dist/cli/diff/index.js +14 -25
- package/dist/cli/diff/index.js.map +1 -1
- package/dist/cli/doctor/claude-mcp.d.ts +4 -19
- package/dist/cli/doctor/claude-mcp.js +11 -22
- package/dist/cli/doctor/claude-mcp.js.map +1 -1
- package/dist/cli/doctor/codex.d.ts +12 -14
- package/dist/cli/doctor/codex.js +31 -144
- package/dist/cli/doctor/codex.js.map +1 -1
- package/dist/cli/doctor/format.js +23 -345
- package/dist/cli/doctor/format.js.map +1 -1
- package/dist/cli/doctor/hook-liveness.d.ts +56 -0
- package/dist/cli/doctor/hook-liveness.js +455 -0
- package/dist/cli/doctor/hook-liveness.js.map +1 -0
- package/dist/cli/doctor/index.d.ts +13 -87
- package/dist/cli/doctor/index.js +57 -713
- package/dist/cli/doctor/index.js.map +1 -1
- package/dist/cli/doctor/opencode.d.ts +2 -1
- package/dist/cli/doctor/opencode.js.map +1 -1
- package/dist/cli/doctor/types.d.ts +21 -387
- package/dist/cli/doctor/types.js.map +1 -1
- package/dist/cli/drain-stdio.d.ts +31 -0
- package/dist/cli/drain-stdio.js +76 -0
- package/dist/cli/drain-stdio.js.map +1 -0
- package/dist/cli/export.d.ts +3 -1
- package/dist/cli/export.js +7 -16
- package/dist/cli/export.js.map +1 -1
- package/dist/cli/gate/codex-disable.d.ts +29 -0
- package/dist/cli/gate/codex-disable.js +195 -0
- package/dist/cli/gate/codex-disable.js.map +1 -0
- package/dist/cli/gate/codex-enable.d.ts +41 -0
- package/dist/cli/gate/codex-enable.js +127 -0
- package/dist/cli/gate/codex-enable.js.map +1 -0
- package/dist/cli/gate/codex-snapshot.d.ts +208 -0
- package/dist/cli/gate/codex-snapshot.js +402 -0
- package/dist/cli/gate/codex-snapshot.js.map +1 -0
- package/dist/cli/gate/disable.d.ts +9 -0
- package/dist/cli/gate/disable.js +263 -28
- package/dist/cli/gate/disable.js.map +1 -1
- package/dist/cli/gate/enable.d.ts +26 -6
- package/dist/cli/gate/enable.js +152 -80
- package/dist/cli/gate/enable.js.map +1 -1
- package/dist/cli/gate/link-chain.d.ts +53 -0
- package/dist/cli/gate/link-chain.js +122 -0
- package/dist/cli/gate/link-chain.js.map +1 -0
- package/dist/cli/gate/snapshot.d.ts +289 -12
- package/dist/cli/gate/snapshot.js +481 -22
- package/dist/cli/gate/snapshot.js.map +1 -1
- package/dist/cli/index.d.ts +0 -9
- package/dist/cli/index.js +9 -2131
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/init/composer.d.ts +3 -6
- package/dist/cli/init/composer.js +6 -370
- package/dist/cli/init/composer.js.map +1 -1
- package/dist/cli/init/dependencies.d.ts +1 -2
- package/dist/cli/init/dependencies.js +4 -80
- package/dist/cli/init/dependencies.js.map +1 -1
- package/dist/cli/init/detect.js +5 -4
- package/dist/cli/init/detect.js.map +1 -1
- package/dist/cli/init/interactive.d.ts +1 -23
- package/dist/cli/init/interactive.js +33 -247
- package/dist/cli/init/interactive.js.map +1 -1
- package/dist/cli/init/profiles.d.ts +2 -2
- package/dist/cli/init/profiles.js +51 -245
- package/dist/cli/init/profiles.js.map +1 -1
- package/dist/cli/init/templates.d.ts +2 -37
- package/dist/cli/init/templates.js +25 -1237
- package/dist/cli/init/templates.js.map +1 -1
- package/dist/cli/list.d.ts +1 -1
- package/dist/cli/list.js +1 -38
- package/dist/cli/list.js.map +1 -1
- package/dist/cli/loader.d.ts +15 -1
- package/dist/cli/loader.js +56 -39
- package/dist/cli/loader.js.map +1 -1
- package/dist/cli/main.js +7 -2
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/migrate-home/index.js +2 -2
- package/dist/cli/migrate-home/index.js.map +1 -1
- package/dist/cli/pack/add.js +1 -1
- package/dist/cli/pack/add.js.map +1 -1
- package/dist/cli/pack/hook-bootstrap.d.ts +5 -164
- package/dist/cli/pack/hook-bootstrap.js +15 -291
- package/dist/cli/pack/hook-bootstrap.js.map +1 -1
- package/dist/cli/pack/hook-branch-protection.d.ts +28 -21
- package/dist/cli/pack/hook-branch-protection.js +365 -340
- package/dist/cli/pack/hook-branch-protection.js.map +1 -1
- package/dist/cli/pack/index.d.ts +0 -1
- package/dist/cli/pack/index.js +0 -1
- package/dist/cli/pack/index.js.map +1 -1
- package/dist/cli/pack/list.js +1 -6
- package/dist/cli/pack/list.js.map +1 -1
- package/dist/cli/pack/mutate.d.ts +7 -9
- package/dist/cli/pack/mutate.js +6 -9
- package/dist/cli/pack/mutate.js.map +1 -1
- package/dist/cli/pack/reseed.d.ts +2 -2
- package/dist/cli/pack/reseed.js +16 -22
- package/dist/cli/pack/reseed.js.map +1 -1
- package/dist/cli/{delegate/register.d.ts → register-gate-group.d.ts} +1 -1
- package/dist/cli/register-gate-group.js +348 -0
- package/dist/cli/register-gate-group.js.map +1 -0
- package/dist/cli/register-inspect-group.d.ts +6 -0
- package/dist/cli/register-inspect-group.js +189 -0
- package/dist/cli/register-inspect-group.js.map +1 -0
- package/dist/cli/register-operator-lifecycle.d.ts +1 -1
- package/dist/cli/register-operator-lifecycle.js +3 -100
- package/dist/cli/register-operator-lifecycle.js.map +1 -1
- package/dist/cli/register-pack-group.d.ts +5 -0
- package/dist/cli/register-pack-group.js +134 -0
- package/dist/cli/register-pack-group.js.map +1 -0
- package/dist/cli/{register-audit-group.d.ts → register-setup-group.d.ts} +1 -1
- package/dist/cli/register-setup-group.js +495 -0
- package/dist/cli/register-setup-group.js.map +1 -0
- package/dist/cli/remove/index.d.ts +0 -14
- package/dist/cli/remove/index.js +2 -115
- package/dist/cli/remove/index.js.map +1 -1
- package/dist/cli/remove/mutate.d.ts +0 -2
- package/dist/cli/remove/mutate.js +1 -19
- package/dist/cli/remove/mutate.js.map +1 -1
- package/dist/cli/remove/register.js +2 -19
- package/dist/cli/remove/register.js.map +1 -1
- package/dist/cli/shell-quote.d.ts +7 -0
- package/dist/cli/shell-quote.js +17 -0
- package/dist/cli/shell-quote.js.map +1 -0
- package/dist/cli/uninstall/index.d.ts +4 -2
- package/dist/cli/uninstall/index.js +15 -7
- package/dist/cli/uninstall/index.js.map +1 -1
- package/dist/cli/uninstall/register.js +6 -4
- package/dist/cli/uninstall/register.js.map +1 -1
- package/dist/cli/uninstall/snapshot.d.ts +15 -0
- package/dist/cli/uninstall/snapshot.js +38 -0
- package/dist/cli/uninstall/snapshot.js.map +1 -1
- package/dist/cli/validate/checks.d.ts +0 -77
- package/dist/cli/validate/checks.js +3 -1049
- package/dist/cli/validate/checks.js.map +1 -1
- package/dist/cli/validate/index.js +19 -15
- package/dist/cli/validate/index.js.map +1 -1
- package/dist/index.d.ts +0 -3
- package/dist/index.js +0 -3
- package/dist/index.js.map +1 -1
- package/dist/io/claude-mcp.d.ts +1 -1
- package/dist/io/claude-mcp.js +27 -14
- package/dist/io/claude-mcp.js.map +1 -1
- package/dist/io/harness-lock.js +2 -2
- package/dist/io/harness-lock.js.map +1 -1
- package/dist/io/lock.d.ts +0 -26
- package/dist/io/lock.js +10 -27
- package/dist/io/lock.js.map +1 -1
- package/dist/io/merge-settings.js +6 -4
- package/dist/io/merge-settings.js.map +1 -1
- package/dist/io/project-name.d.ts +22 -39
- package/dist/io/project-name.js +25 -42
- package/dist/io/project-name.js.map +1 -1
- package/dist/io/read-regular-file.d.ts +103 -40
- package/dist/io/read-regular-file.js +196 -35
- package/dist/io/read-regular-file.js.map +1 -1
- package/dist/io/restart-hints.js +3 -6
- package/dist/io/restart-hints.js.map +1 -1
- package/dist/io/version-compare.d.ts +1 -3
- package/dist/io/version-compare.js +1 -3
- package/dist/io/version-compare.js.map +1 -1
- package/dist/io/write-regular-file.d.ts +30 -0
- package/dist/io/write-regular-file.js +85 -0
- package/dist/io/write-regular-file.js.map +1 -0
- package/dist/overrides/index.d.ts +1 -0
- package/dist/overrides/index.js +1 -0
- package/dist/overrides/index.js.map +1 -1
- package/dist/overrides/merge-layers.d.ts +43 -0
- package/dist/overrides/merge-layers.js +76 -0
- package/dist/overrides/merge-layers.js.map +1 -0
- package/dist/overrides/merge.d.ts +16 -0
- package/dist/overrides/merge.js +21 -8
- package/dist/overrides/merge.js.map +1 -1
- package/dist/policy-packs/builtin/branch-protection-runtime.d.ts +4 -69
- package/dist/policy-packs/builtin/branch-protection-runtime.js +12 -84
- package/dist/policy-packs/builtin/branch-protection-runtime.js.map +1 -1
- package/dist/policy-packs/builtin/branch-protection.d.ts +7 -30
- package/dist/policy-packs/builtin/branch-protection.js +56 -89
- package/dist/policy-packs/builtin/branch-protection.js.map +1 -1
- package/dist/policy-packs/config-check.d.ts +7 -5
- package/dist/policy-packs/config-check.js +2 -2
- package/dist/policy-packs/config-check.js.map +1 -1
- package/dist/policy-packs/expand.d.ts +1 -3
- package/dist/policy-packs/expand.js +5 -42
- package/dist/policy-packs/expand.js.map +1 -1
- package/dist/policy-packs/index.d.ts +3 -3
- package/dist/policy-packs/index.js +2 -2
- package/dist/policy-packs/index.js.map +1 -1
- package/dist/policy-packs/registry.d.ts +9 -38
- package/dist/policy-packs/registry.js +5 -65
- package/dist/policy-packs/registry.js.map +1 -1
- package/dist/policy-packs/runtime.d.ts +0 -4
- package/dist/policy-packs/runtime.js +9 -22
- package/dist/policy-packs/runtime.js.map +1 -1
- package/dist/policy-packs/types.d.ts +0 -19
- package/dist/policy-packs/ux-compare.d.ts +1 -11
- package/dist/policy-packs/ux-compare.js +6 -42
- package/dist/policy-packs/ux-compare.js.map +1 -1
- package/dist/policy-packs/ux-drift-check.d.ts +5 -5
- package/dist/policy-packs/ux-drift-check.js +17 -22
- package/dist/policy-packs/ux-drift-check.js.map +1 -1
- package/dist/policy-packs/version-check.d.ts +5 -26
- package/dist/policy-packs/version-check.js +19 -74
- package/dist/policy-packs/version-check.js.map +1 -1
- package/dist/probes/memory.d.ts +1 -1
- package/dist/probes/memory.js +1 -1
- package/dist/probes/memory.js.map +1 -1
- package/dist/runtime/agent-facing.d.ts +9 -0
- package/dist/runtime/agent-facing.js +28 -14
- package/dist/runtime/agent-facing.js.map +1 -1
- package/dist/runtime/bounded-text.d.ts +15 -0
- package/dist/runtime/bounded-text.js +27 -0
- package/dist/runtime/bounded-text.js.map +1 -0
- package/dist/runtime/git-branch.d.ts +125 -0
- package/dist/runtime/git-branch.js +381 -0
- package/dist/runtime/git-branch.js.map +1 -0
- package/dist/runtime/hermetic-spawn-guard.d.ts +2 -2
- package/dist/runtime/hermetic-spawn-guard.js +24 -34
- package/dist/runtime/hermetic-spawn-guard.js.map +1 -1
- package/dist/runtime/home-dir.js +3 -2
- package/dist/runtime/home-dir.js.map +1 -1
- package/dist/runtime/tool-name-aliases.d.ts +0 -6
- package/dist/runtime/tool-name-aliases.js +5 -18
- package/dist/runtime/tool-name-aliases.js.map +1 -1
- package/dist/schema/grounding.d.ts +7 -54
- package/dist/schema/grounding.js +10 -12
- package/dist/schema/grounding.js.map +1 -1
- package/dist/schema/hooks.d.ts +39 -136
- package/dist/schema/hooks.js +3 -6
- package/dist/schema/hooks.js.map +1 -1
- package/dist/schema/index.d.ts +114 -3828
- package/dist/schema/index.js +41 -80
- package/dist/schema/index.js.map +1 -1
- package/dist/schema/memory.d.ts +54 -131
- package/dist/schema/memory.js +2 -2
- package/dist/schema/memory.js.map +1 -1
- package/dist/schema/policy-packs.d.ts +3 -45
- package/dist/schema/policy-packs.js +7 -13
- package/dist/schema/policy-packs.js.map +1 -1
- package/dist/schema/policy-ux.d.ts +7 -0
- package/dist/schema/policy-ux.js +18 -0
- package/dist/schema/policy-ux.js.map +1 -0
- package/dist/schema/removed-keys.d.ts +185 -0
- package/dist/schema/removed-keys.js +531 -0
- package/dist/schema/removed-keys.js.map +1 -0
- package/dist/schema/tools.d.ts +30 -268
- package/dist/schema/tools.js +4 -4
- package/dist/schema/tools.js.map +1 -1
- package/package.json +5 -16
- package/dist/cli/approve/branch-protection.d.ts +0 -69
- package/dist/cli/approve/branch-protection.js +0 -144
- package/dist/cli/approve/branch-protection.js.map +0 -1
- package/dist/cli/approve/risk.d.ts +0 -112
- package/dist/cli/approve/risk.js +0 -197
- package/dist/cli/approve/risk.js.map +0 -1
- package/dist/cli/approve/stdin-report.d.ts +0 -57
- package/dist/cli/approve/stdin-report.js +0 -145
- package/dist/cli/approve/stdin-report.js.map +0 -1
- package/dist/cli/approve/understanding.d.ts +0 -410
- package/dist/cli/approve/understanding.js +0 -996
- package/dist/cli/approve/understanding.js.map +0 -1
- package/dist/cli/audit.d.ts +0 -80
- package/dist/cli/audit.js +0 -252
- package/dist/cli/audit.js.map +0 -1
- package/dist/cli/delegate/index.d.ts +0 -76
- package/dist/cli/delegate/index.js +0 -444
- package/dist/cli/delegate/index.js.map +0 -1
- package/dist/cli/delegate/register.js +0 -90
- package/dist/cli/delegate/register.js.map +0 -1
- package/dist/cli/doctor/auto-approve-mode.d.ts +0 -37
- package/dist/cli/doctor/auto-approve-mode.js +0 -80
- package/dist/cli/doctor/auto-approve-mode.js.map +0 -1
- package/dist/cli/doctor/bypass-without-auto-approve.d.ts +0 -38
- package/dist/cli/doctor/bypass-without-auto-approve.js +0 -95
- package/dist/cli/doctor/bypass-without-auto-approve.js.map +0 -1
- package/dist/cli/doctor/codex-config-drift.d.ts +0 -28
- package/dist/cli/doctor/codex-config-drift.js +0 -240
- package/dist/cli/doctor/codex-config-drift.js.map +0 -1
- package/dist/cli/doctor/expire-on-tool-match.d.ts +0 -15
- package/dist/cli/doctor/expire-on-tool-match.js +0 -58
- package/dist/cli/doctor/expire-on-tool-match.js.map +0 -1
- package/dist/cli/doctor/rogue-ledger.d.ts +0 -53
- package/dist/cli/doctor/rogue-ledger.js +0 -153
- package/dist/cli/doctor/rogue-ledger.js.map +0 -1
- package/dist/cli/doctor/session-start-preflight-setup-version.d.ts +0 -83
- package/dist/cli/doctor/session-start-preflight-setup-version.js +0 -114
- package/dist/cli/doctor/session-start-preflight-setup-version.js.map +0 -1
- package/dist/cli/doctor/settings-drift.d.ts +0 -25
- package/dist/cli/doctor/settings-drift.js +0 -213
- package/dist/cli/doctor/settings-drift.js.map +0 -1
- package/dist/cli/doctor/toolchain-parity.d.ts +0 -63
- package/dist/cli/doctor/toolchain-parity.js +0 -154
- package/dist/cli/doctor/toolchain-parity.js.map +0 -1
- package/dist/cli/doctor/ug-auto-approvals.d.ts +0 -40
- package/dist/cli/doctor/ug-auto-approvals.js +0 -91
- package/dist/cli/doctor/ug-auto-approvals.js.map +0 -1
- package/dist/cli/doctor/ug-delegations.d.ts +0 -26
- package/dist/cli/doctor/ug-delegations.js +0 -121
- package/dist/cli/doctor/ug-delegations.js.map +0 -1
- package/dist/cli/doctor/ug-inflight.d.ts +0 -35
- package/dist/cli/doctor/ug-inflight.js +0 -57
- package/dist/cli/doctor/ug-inflight.js.map +0 -1
- package/dist/cli/doctor/understanding-mode-env.d.ts +0 -51
- package/dist/cli/doctor/understanding-mode-env.js +0 -98
- package/dist/cli/doctor/understanding-mode-env.js.map +0 -1
- package/dist/cli/dry-run.d.ts +0 -58
- package/dist/cli/dry-run.js +0 -283
- package/dist/cli/dry-run.js.map +0 -1
- package/dist/cli/enriched-event.d.ts +0 -35
- package/dist/cli/enriched-event.js +0 -60
- package/dist/cli/enriched-event.js.map +0 -1
- package/dist/cli/event-input.d.ts +0 -28
- package/dist/cli/event-input.js +0 -73
- package/dist/cli/event-input.js.map +0 -1
- package/dist/cli/explain-action.d.ts +0 -25
- package/dist/cli/explain-action.js +0 -57
- package/dist/cli/explain-action.js.map +0 -1
- package/dist/cli/explain-policy.d.ts +0 -147
- package/dist/cli/explain-policy.js +0 -248
- package/dist/cli/explain-policy.js.map +0 -1
- package/dist/cli/explain.d.ts +0 -31
- package/dist/cli/explain.js +0 -159
- package/dist/cli/explain.js.map +0 -1
- package/dist/cli/gc/index.d.ts +0 -63
- package/dist/cli/gc/index.js +0 -566
- package/dist/cli/gc/index.js.map +0 -1
- package/dist/cli/pack/approve-escape.d.ts +0 -19
- package/dist/cli/pack/approve-escape.js +0 -190
- package/dist/cli/pack/approve-escape.js.map +0 -1
- package/dist/cli/pack/auto-approve-path.d.ts +0 -146
- package/dist/cli/pack/auto-approve-path.js +0 -462
- package/dist/cli/pack/auto-approve-path.js.map +0 -1
- package/dist/cli/pack/hook-codex-post-tool-use.d.ts +0 -25
- package/dist/cli/pack/hook-codex-post-tool-use.js +0 -160
- package/dist/cli/pack/hook-codex-post-tool-use.js.map +0 -1
- package/dist/cli/pack/hook-codex-pre-tool-use.d.ts +0 -48
- package/dist/cli/pack/hook-codex-pre-tool-use.js +0 -474
- package/dist/cli/pack/hook-codex-pre-tool-use.js.map +0 -1
- package/dist/cli/pack/hook-codex-stop.d.ts +0 -35
- package/dist/cli/pack/hook-codex-stop.js +0 -329
- package/dist/cli/pack/hook-codex-stop.js.map +0 -1
- package/dist/cli/pack/hook-codex-user-prompt-submit.d.ts +0 -24
- package/dist/cli/pack/hook-codex-user-prompt-submit.js +0 -93
- package/dist/cli/pack/hook-codex-user-prompt-submit.js.map +0 -1
- package/dist/cli/pack/hook-post-merge-gate-record.d.ts +0 -37
- package/dist/cli/pack/hook-post-merge-gate-record.js +0 -190
- package/dist/cli/pack/hook-post-merge-gate-record.js.map +0 -1
- package/dist/cli/pack/hook-post-merge-gate.d.ts +0 -34
- package/dist/cli/pack/hook-post-merge-gate.js +0 -277
- package/dist/cli/pack/hook-post-merge-gate.js.map +0 -1
- package/dist/cli/pack/hook-post-tool-use.d.ts +0 -45
- package/dist/cli/pack/hook-post-tool-use.js +0 -137
- package/dist/cli/pack/hook-post-tool-use.js.map +0 -1
- package/dist/cli/pack/hook-pre-tool-use.d.ts +0 -92
- package/dist/cli/pack/hook-pre-tool-use.js +0 -1328
- package/dist/cli/pack/hook-pre-tool-use.js.map +0 -1
- package/dist/cli/pack/hook-runtime-reality.d.ts +0 -63
- package/dist/cli/pack/hook-runtime-reality.js +0 -210
- package/dist/cli/pack/hook-runtime-reality.js.map +0 -1
- package/dist/cli/pack/hook-solution-acceptance-writeguard.d.ts +0 -32
- package/dist/cli/pack/hook-solution-acceptance-writeguard.js +0 -438
- package/dist/cli/pack/hook-solution-acceptance-writeguard.js.map +0 -1
- package/dist/cli/pack/hook-solution-acceptance.d.ts +0 -157
- package/dist/cli/pack/hook-solution-acceptance.js +0 -530
- package/dist/cli/pack/hook-solution-acceptance.js.map +0 -1
- package/dist/cli/pack/hook-stay-in-scope.d.ts +0 -37
- package/dist/cli/pack/hook-stay-in-scope.js +0 -128
- package/dist/cli/pack/hook-stay-in-scope.js.map +0 -1
- package/dist/cli/pack/hook-subagent-start.d.ts +0 -23
- package/dist/cli/pack/hook-subagent-start.js +0 -88
- package/dist/cli/pack/hook-subagent-start.js.map +0 -1
- package/dist/cli/pack/hook-subagent-stop.d.ts +0 -19
- package/dist/cli/pack/hook-subagent-stop.js +0 -41
- package/dist/cli/pack/hook-subagent-stop.js.map +0 -1
- package/dist/cli/pack/hook-track-active-claim.d.ts +0 -27
- package/dist/cli/pack/hook-track-active-claim.js +0 -275
- package/dist/cli/pack/hook-track-active-claim.js.map +0 -1
- package/dist/cli/pack/transcript-report-scan.d.ts +0 -71
- package/dist/cli/pack/transcript-report-scan.js +0 -359
- package/dist/cli/pack/transcript-report-scan.js.map +0 -1
- package/dist/cli/pack/understanding-report-schema-hint.d.ts +0 -26
- package/dist/cli/pack/understanding-report-schema-hint.js +0 -120
- package/dist/cli/pack/understanding-report-schema-hint.js.map +0 -1
- package/dist/cli/pack/upgrade.d.ts +0 -32
- package/dist/cli/pack/upgrade.js +0 -273
- package/dist/cli/pack/upgrade.js.map +0 -1
- package/dist/cli/pause/index.d.ts +0 -113
- package/dist/cli/pause/index.js +0 -251
- package/dist/cli/pause/index.js.map +0 -1
- package/dist/cli/pause-check.d.ts +0 -22
- package/dist/cli/pause-check.js +0 -54
- package/dist/cli/pause-check.js.map +0 -1
- package/dist/cli/policy/intercept.d.ts +0 -233
- package/dist/cli/policy/intercept.js +0 -1022
- package/dist/cli/policy/intercept.js.map +0 -1
- package/dist/cli/policy/risk-envelope-enrichment.d.ts +0 -40
- package/dist/cli/policy/risk-envelope-enrichment.js +0 -110
- package/dist/cli/policy/risk-envelope-enrichment.js.map +0 -1
- package/dist/cli/record/index.d.ts +0 -81
- package/dist/cli/record/index.js +0 -348
- package/dist/cli/record/index.js.map +0 -1
- package/dist/cli/register-audit-group.js +0 -91
- package/dist/cli/register-audit-group.js.map +0 -1
- package/dist/cli/resolve-env.d.ts +0 -32
- package/dist/cli/resolve-env.js +0 -46
- package/dist/cli/resolve-env.js.map +0 -1
- package/dist/cli/session-export/index.d.ts +0 -46
- package/dist/cli/session-export/index.js +0 -169
- package/dist/cli/session-export/index.js.map +0 -1
- package/dist/cli/session-export/redact.d.ts +0 -22
- package/dist/cli/session-export/redact.js +0 -47
- package/dist/cli/session-export/redact.js.map +0 -1
- package/dist/cli/session-export/transcript.d.ts +0 -24
- package/dist/cli/session-export/transcript.js +0 -164
- package/dist/cli/session-export/transcript.js.map +0 -1
- package/dist/cli/session-start/branch-check.d.ts +0 -18
- package/dist/cli/session-start/branch-check.js +0 -140
- package/dist/cli/session-start/branch-check.js.map +0 -1
- package/dist/cli/session-start/index.d.ts +0 -121
- package/dist/cli/session-start/index.js +0 -591
- package/dist/cli/session-start/index.js.map +0 -1
- package/dist/cli/session-start/shared-options.d.ts +0 -103
- package/dist/cli/session-start/shared-options.js +0 -107
- package/dist/cli/session-start/shared-options.js.map +0 -1
- package/dist/cli/session-start/stale-base-check.d.ts +0 -46
- package/dist/cli/session-start/stale-base-check.js +0 -352
- package/dist/cli/session-start/stale-base-check.js.map +0 -1
- package/dist/cli/session-start/toolchain-parity.d.ts +0 -267
- package/dist/cli/session-start/toolchain-parity.js +0 -797
- package/dist/cli/session-start/toolchain-parity.js.map +0 -1
- package/dist/cli/smoke/assertions.d.ts +0 -46
- package/dist/cli/smoke/assertions.js +0 -170
- package/dist/cli/smoke/assertions.js.map +0 -1
- package/dist/cli/smoke/index.d.ts +0 -76
- package/dist/cli/smoke/index.js +0 -245
- package/dist/cli/smoke/index.js.map +0 -1
- package/dist/cli/smoke/runner.d.ts +0 -55
- package/dist/cli/smoke/runner.js +0 -134
- package/dist/cli/smoke/runner.js.map +0 -1
- package/dist/cli/smoke/stream-parser.d.ts +0 -65
- package/dist/cli/smoke/stream-parser.js +0 -115
- package/dist/cli/smoke/stream-parser.js.map +0 -1
- package/dist/cli/test-risk.d.ts +0 -27
- package/dist/cli/test-risk.js +0 -38
- package/dist/cli/test-risk.js.map +0 -1
- package/dist/io/display-path.d.ts +0 -11
- package/dist/io/display-path.js +0 -29
- package/dist/io/display-path.js.map +0 -1
- package/dist/io/duration.d.ts +0 -5
- package/dist/io/duration.js +0 -50
- package/dist/io/duration.js.map +0 -1
- package/dist/io/extract.d.ts +0 -79
- package/dist/io/extract.js +0 -226
- package/dist/io/extract.js.map +0 -1
- package/dist/io/ledger-record.d.ts +0 -83
- package/dist/io/ledger-record.js +0 -301
- package/dist/io/ledger-record.js.map +0 -1
- package/dist/io/read-json-dir-entries.d.ts +0 -36
- package/dist/io/read-json-dir-entries.js +0 -48
- package/dist/io/read-json-dir-entries.js.map +0 -1
- package/dist/io/safe-json-parse.d.ts +0 -5
- package/dist/io/safe-json-parse.js +0 -21
- package/dist/io/safe-json-parse.js.map +0 -1
- package/dist/policies/index.d.ts +0 -5
- package/dist/policies/index.js +0 -6
- package/dist/policies/index.js.map +0 -1
- package/dist/policies/ledger-client.d.ts +0 -113
- package/dist/policies/ledger-client.js +0 -500
- package/dist/policies/ledger-client.js.map +0 -1
- package/dist/policies/producers.d.ts +0 -12
- package/dist/policies/producers.js +0 -61
- package/dist/policies/producers.js.map +0 -1
- package/dist/policies/requires.d.ts +0 -75
- package/dist/policies/requires.js +0 -240
- package/dist/policies/requires.js.map +0 -1
- package/dist/policies/timestamp.d.ts +0 -14
- package/dist/policies/timestamp.js +0 -36
- package/dist/policies/timestamp.js.map +0 -1
- package/dist/policy-packs/builtin/permission-profiles.d.ts +0 -11
- package/dist/policy-packs/builtin/permission-profiles.js +0 -74
- package/dist/policy-packs/builtin/permission-profiles.js.map +0 -1
- package/dist/policy-packs/builtin/post-merge-gate-runtime.d.ts +0 -286
- package/dist/policy-packs/builtin/post-merge-gate-runtime.js +0 -397
- package/dist/policy-packs/builtin/post-merge-gate-runtime.js.map +0 -1
- package/dist/policy-packs/builtin/post-merge-gate.d.ts +0 -56
- package/dist/policy-packs/builtin/post-merge-gate.js +0 -347
- package/dist/policy-packs/builtin/post-merge-gate.js.map +0 -1
- package/dist/policy-packs/builtin/solution-acceptance-reconnect.d.ts +0 -50
- package/dist/policy-packs/builtin/solution-acceptance-reconnect.js +0 -151
- package/dist/policy-packs/builtin/solution-acceptance-reconnect.js.map +0 -1
- package/dist/policy-packs/builtin/solution-acceptance-runtime.d.ts +0 -323
- package/dist/policy-packs/builtin/solution-acceptance-runtime.js +0 -672
- package/dist/policy-packs/builtin/solution-acceptance-runtime.js.map +0 -1
- package/dist/policy-packs/builtin/solution-acceptance.d.ts +0 -45
- package/dist/policy-packs/builtin/solution-acceptance.js +0 -226
- package/dist/policy-packs/builtin/solution-acceptance.js.map +0 -1
- package/dist/policy-packs/builtin/understanding-before-execution/active-claim.d.ts +0 -25
- package/dist/policy-packs/builtin/understanding-before-execution/active-claim.js +0 -94
- package/dist/policy-packs/builtin/understanding-before-execution/active-claim.js.map +0 -1
- package/dist/policy-packs/builtin/understanding-before-execution/auto-approve-default.d.ts +0 -35
- package/dist/policy-packs/builtin/understanding-before-execution/auto-approve-default.js +0 -86
- package/dist/policy-packs/builtin/understanding-before-execution/auto-approve-default.js.map +0 -1
- package/dist/policy-packs/builtin/understanding-before-execution/auto-approve.d.ts +0 -210
- package/dist/policy-packs/builtin/understanding-before-execution/auto-approve.js +0 -361
- package/dist/policy-packs/builtin/understanding-before-execution/auto-approve.js.map +0 -1
- package/dist/policy-packs/builtin/understanding-before-execution/delegation-markers.d.ts +0 -304
- package/dist/policy-packs/builtin/understanding-before-execution/delegation-markers.js +0 -734
- package/dist/policy-packs/builtin/understanding-before-execution/delegation-markers.js.map +0 -1
- package/dist/policy-packs/builtin/understanding-before-execution/expiry-reason.d.ts +0 -36
- package/dist/policy-packs/builtin/understanding-before-execution/expiry-reason.js +0 -73
- package/dist/policy-packs/builtin/understanding-before-execution/expiry-reason.js.map +0 -1
- package/dist/policy-packs/builtin/understanding-before-execution/index.d.ts +0 -19
- package/dist/policy-packs/builtin/understanding-before-execution/index.js +0 -83
- package/dist/policy-packs/builtin/understanding-before-execution/index.js.map +0 -1
- package/dist/policy-packs/builtin/understanding-before-execution/inflight-records.d.ts +0 -135
- package/dist/policy-packs/builtin/understanding-before-execution/inflight-records.js +0 -643
- package/dist/policy-packs/builtin/understanding-before-execution/inflight-records.js.map +0 -1
- package/dist/policy-packs/builtin/understanding-before-execution/ledger.d.ts +0 -25
- package/dist/policy-packs/builtin/understanding-before-execution/ledger.js +0 -56
- package/dist/policy-packs/builtin/understanding-before-execution/ledger.js.map +0 -1
- package/dist/policy-packs/builtin/understanding-before-execution/lifecycle.d.ts +0 -20
- package/dist/policy-packs/builtin/understanding-before-execution/lifecycle.js +0 -106
- package/dist/policy-packs/builtin/understanding-before-execution/lifecycle.js.map +0 -1
- package/dist/policy-packs/builtin/understanding-before-execution/markers.d.ts +0 -126
- package/dist/policy-packs/builtin/understanding-before-execution/markers.js +0 -245
- package/dist/policy-packs/builtin/understanding-before-execution/markers.js.map +0 -1
- package/dist/policy-packs/builtin/understanding-before-execution/measured-permission-modes.d.ts +0 -29
- package/dist/policy-packs/builtin/understanding-before-execution/measured-permission-modes.js +0 -82
- package/dist/policy-packs/builtin/understanding-before-execution/measured-permission-modes.js.map +0 -1
- package/dist/policy-packs/builtin/understanding-before-execution/permission-mode-observations.d.ts +0 -71
- package/dist/policy-packs/builtin/understanding-before-execution/permission-mode-observations.js +0 -175
- package/dist/policy-packs/builtin/understanding-before-execution/permission-mode-observations.js.map +0 -1
- package/dist/policy-packs/builtin/understanding-before-execution/persisted-reports.d.ts +0 -625
- package/dist/policy-packs/builtin/understanding-before-execution/persisted-reports.js +0 -1076
- package/dist/policy-packs/builtin/understanding-before-execution/persisted-reports.js.map +0 -1
- package/dist/policy-packs/builtin/understanding-before-execution/post-tool-use-boundary.d.ts +0 -128
- package/dist/policy-packs/builtin/understanding-before-execution/post-tool-use-boundary.js +0 -213
- package/dist/policy-packs/builtin/understanding-before-execution/post-tool-use-boundary.js.map +0 -1
- package/dist/policy-packs/builtin/understanding-before-execution/stay-in-scope-config.d.ts +0 -129
- package/dist/policy-packs/builtin/understanding-before-execution/stay-in-scope-config.js +0 -166
- package/dist/policy-packs/builtin/understanding-before-execution/stay-in-scope-config.js.map +0 -1
- package/dist/policy-packs/builtin/understanding-before-execution/task-markers.d.ts +0 -164
- package/dist/policy-packs/builtin/understanding-before-execution/task-markers.js +0 -275
- package/dist/policy-packs/builtin/understanding-before-execution/task-markers.js.map +0 -1
- package/dist/policy-packs/builtin/understanding-before-execution-runtime.d.ts +0 -1
- package/dist/policy-packs/builtin/understanding-before-execution-runtime.js +0 -10
- package/dist/policy-packs/builtin/understanding-before-execution-runtime.js.map +0 -1
- package/dist/policy-packs/builtin/understanding-before-execution.d.ts +0 -526
- package/dist/policy-packs/builtin/understanding-before-execution.js +0 -1078
- package/dist/policy-packs/builtin/understanding-before-execution.js.map +0 -1
- package/dist/policy-packs/permission-translator.d.ts +0 -9
- package/dist/policy-packs/permission-translator.js +0 -76
- package/dist/policy-packs/permission-translator.js.map +0 -1
- package/dist/runtime/action-envelope.d.ts +0 -64
- package/dist/runtime/action-envelope.js +0 -46
- package/dist/runtime/action-envelope.js.map +0 -1
- package/dist/runtime/approval-signing.d.ts +0 -111
- package/dist/runtime/approval-signing.js +0 -384
- package/dist/runtime/approval-signing.js.map +0 -1
- package/dist/runtime/bash-match-facts.d.ts +0 -60
- package/dist/runtime/bash-match-facts.js +0 -196
- package/dist/runtime/bash-match-facts.js.map +0 -1
- package/dist/runtime/bash-match-registry.d.ts +0 -112
- package/dist/runtime/bash-match-registry.js +0 -406
- package/dist/runtime/bash-match-registry.js.map +0 -1
- package/dist/runtime/bash-prefix-parse.d.ts +0 -31
- package/dist/runtime/bash-prefix-parse.js +0 -576
- package/dist/runtime/bash-prefix-parse.js.map +0 -1
- package/dist/runtime/command-normalize.d.ts +0 -658
- package/dist/runtime/command-normalize.js +0 -2410
- package/dist/runtime/command-normalize.js.map +0 -1
- package/dist/runtime/deletion-target-resolve.d.ts +0 -28
- package/dist/runtime/deletion-target-resolve.js +0 -1078
- package/dist/runtime/deletion-target-resolve.js.map +0 -1
- package/dist/runtime/destructive-shell-floor.d.ts +0 -19
- package/dist/runtime/destructive-shell-floor.js +0 -611
- package/dist/runtime/destructive-shell-floor.js.map +0 -1
- package/dist/runtime/environment-resolver.d.ts +0 -36
- package/dist/runtime/environment-resolver.js +0 -138
- package/dist/runtime/environment-resolver.js.map +0 -1
- package/dist/runtime/git-context.d.ts +0 -201
- package/dist/runtime/git-context.js +0 -422
- package/dist/runtime/git-context.js.map +0 -1
- package/dist/runtime/index.d.ts +0 -12
- package/dist/runtime/index.js +0 -13
- package/dist/runtime/index.js.map +0 -1
- package/dist/runtime/intercept.d.ts +0 -623
- package/dist/runtime/intercept.js +0 -1384
- package/dist/runtime/intercept.js.map +0 -1
- package/dist/runtime/kube-context.d.ts +0 -16
- package/dist/runtime/kube-context.js +0 -63
- package/dist/runtime/kube-context.js.map +0 -1
- package/dist/runtime/kubectl-target-parse.d.ts +0 -31
- package/dist/runtime/kubectl-target-parse.js +0 -249
- package/dist/runtime/kubectl-target-parse.js.map +0 -1
- package/dist/runtime/ledger-add.d.ts +0 -16
- package/dist/runtime/ledger-add.js +0 -150
- package/dist/runtime/ledger-add.js.map +0 -1
- package/dist/runtime/ledger-writer.d.ts +0 -27
- package/dist/runtime/ledger-writer.js +0 -46
- package/dist/runtime/ledger-writer.js.map +0 -1
- package/dist/runtime/pause-sentinel.d.ts +0 -60
- package/dist/runtime/pause-sentinel.js +0 -156
- package/dist/runtime/pause-sentinel.js.map +0 -1
- package/dist/runtime/pending-approval.d.ts +0 -23
- package/dist/runtime/pending-approval.js +0 -67
- package/dist/runtime/pending-approval.js.map +0 -1
- package/dist/runtime/read-only-bash.d.ts +0 -141
- package/dist/runtime/read-only-bash.js +0 -2204
- package/dist/runtime/read-only-bash.js.map +0 -1
- package/dist/runtime/recovery-git-commit.d.ts +0 -22
- package/dist/runtime/recovery-git-commit.js +0 -284
- package/dist/runtime/recovery-git-commit.js.map +0 -1
- package/dist/runtime/reject-malformed-session-id.d.ts +0 -8
- package/dist/runtime/reject-malformed-session-id.js +0 -16
- package/dist/runtime/reject-malformed-session-id.js.map +0 -1
- package/dist/runtime/risk-classifier.d.ts +0 -38
- package/dist/runtime/risk-classifier.js +0 -311
- package/dist/runtime/risk-classifier.js.map +0 -1
- package/dist/runtime/session-id.d.ts +0 -113
- package/dist/runtime/session-id.js +0 -193
- package/dist/runtime/session-id.js.map +0 -1
- package/dist/runtime/shell-word.d.ts +0 -62
- package/dist/runtime/shell-word.js +0 -410
- package/dist/runtime/shell-word.js.map +0 -1
- package/dist/runtime/task-providers/agent-tasks.d.ts +0 -105
- package/dist/runtime/task-providers/agent-tasks.js +0 -247
- package/dist/runtime/task-providers/agent-tasks.js.map +0 -1
- package/dist/runtime/when-eval.d.ts +0 -62
- package/dist/runtime/when-eval.js +0 -212
- package/dist/runtime/when-eval.js.map +0 -1
- package/dist/runtime/workflow-policies.d.ts +0 -207
- package/dist/runtime/workflow-policies.js +0 -596
- package/dist/runtime/workflow-policies.js.map +0 -1
- package/dist/schema/audit.d.ts +0 -71
- package/dist/schema/audit.js +0 -32
- package/dist/schema/audit.js.map +0 -1
- package/dist/schema/doctor.d.ts +0 -25
- package/dist/schema/doctor.js +0 -23
- package/dist/schema/doctor.js.map +0 -1
- package/dist/schema/environments.d.ts +0 -215
- package/dist/schema/environments.js +0 -101
- package/dist/schema/environments.js.map +0 -1
- package/dist/schema/extract.d.ts +0 -9
- package/dist/schema/extract.js +0 -86
- package/dist/schema/extract.js.map +0 -1
- package/dist/schema/permission-profiles.d.ts +0 -2292
- package/dist/schema/permission-profiles.js +0 -60
- package/dist/schema/permission-profiles.js.map +0 -1
- package/dist/schema/policies.d.ts +0 -974
- package/dist/schema/policies.js +0 -298
- package/dist/schema/policies.js.map +0 -1
- package/dist/schema/requires.d.ts +0 -131
- package/dist/schema/requires.js +0 -88
- package/dist/schema/requires.js.map +0 -1
- package/dist/schema/risk.d.ts +0 -144
- package/dist/schema/risk.js +0 -194
- package/dist/schema/risk.js.map +0 -1
- package/dist/schema/session-start-preflight.d.ts +0 -10
- package/dist/schema/session-start-preflight.js +0 -106
- package/dist/schema/session-start-preflight.js.map +0 -1
- package/dist/schema/stale-base-check.d.ts +0 -18
- package/dist/schema/stale-base-check.js +0 -37
- package/dist/schema/stale-base-check.js.map +0 -1
- package/dist/schema/toolchain-parity.d.ts +0 -21
- package/dist/schema/toolchain-parity.js +0 -48
- package/dist/schema/toolchain-parity.js.map +0 -1
- package/dist/schema/workflows.d.ts +0 -519
- package/dist/schema/workflows.js +0 -81
- package/dist/schema/workflows.js.map +0 -1
- package/scripts/runtime-reality-docker-probe.mjs +0 -71
|
@@ -1,17 +1,9 @@
|
|
|
1
|
-
import { spawnSync } from "node:child_process";
|
|
2
1
|
import * as fs from "node:fs";
|
|
3
2
|
import * as os from "node:os";
|
|
4
3
|
import * as path from "node:path";
|
|
5
|
-
import { checkPolicyPackConfigs, checkPolicyPackSources
|
|
4
|
+
import { checkPolicyPackConfigs, checkPolicyPackSources } from "../../policy-packs/index.js";
|
|
6
5
|
import { expandHome } from "../../io/expand-home.js";
|
|
7
6
|
import { parseProbedVersion, compareVersionFloor } from "../../io/version-compare.js";
|
|
8
|
-
import { extractBashMatchBoundary, shippedBashMatchBoundaries, shippedOperatorOnlyPolicyNames, } from "../init/templates.js";
|
|
9
|
-
import { isPolicyInterceptCommand, requiredHookBudgetMs } from "../policy/intercept.js";
|
|
10
|
-
import { PACK_NAME as UNDERSTANDING_BEFORE_EXECUTION_PACK_NAME } from "../../policy-packs/builtin/understanding-before-execution.js";
|
|
11
|
-
import { isMeasuredPermissionModeFor, measuredPermissionModeLiteralsFor, } from "../../policy-packs/builtin/understanding-before-execution/measured-permission-modes.js";
|
|
12
|
-
import { AUTO_APPROVE_HARNESS_VALUES, DEFAULT_AUTO_APPROVE_HARNESSES, } from "../../policy-packs/builtin/understanding-before-execution/auto-approve.js";
|
|
13
|
-
import { DEFAULT_SAFE_DELETION_ROOTS } from "../../schema/risk.js";
|
|
14
|
-
import { deriveWorkflowGatePolicies, findWeakGatePolicyOverlaps, handAuthoredPolicies, MERGE_BASH_MATCH, MERGE_MCP_MATCH, REVIEW_EVIDENCE_HOOK_BASH, REVIEW_EVIDENCE_HOOK_MCP, REVIEW_EVIDENCE_HOOK_TASK_FINISH, REVIEW_EVIDENCE_HOOK_TASK_MERGE, workflowRequiresMergeGate, } from "../../runtime/workflow-policies.js";
|
|
15
7
|
const DEFAULT_RUNTIME_BUILTINS = [
|
|
16
8
|
"Read",
|
|
17
9
|
"Edit",
|
|
@@ -220,958 +212,6 @@ function checkBuiltinDrift(manifest, opts) {
|
|
|
220
212
|
}
|
|
221
213
|
return diags;
|
|
222
214
|
}
|
|
223
|
-
export function checkPolicyGroundingMcp(manifest) {
|
|
224
|
-
if (!manifest.policies.some((policy) => policy.requires !== undefined))
|
|
225
|
-
return [];
|
|
226
|
-
const wired = manifest.tools.mcp.some((m) => m.name === "grounding-mcp");
|
|
227
|
-
if (wired)
|
|
228
|
-
return [];
|
|
229
|
-
// This only applies to policies that consume `requires:` evidence.
|
|
230
|
-
// Tier-aware wording since task f1aea826: this is the LAST surface
|
|
231
|
-
// before an operator ships a manifest whose evidence-consuming
|
|
232
|
-
// block/require_approval policies will hard-deny every matching event
|
|
233
|
-
// (deny-degraded), the inverse of the pre-0.45 silent non-blocking
|
|
234
|
-
// fallback this message used to describe. operator_only policies deny
|
|
235
|
-
// without a ledger query and need no grounding-mcp provider.
|
|
236
|
-
return [
|
|
237
|
-
{
|
|
238
|
-
severity: "warning",
|
|
239
|
-
path: "policies",
|
|
240
|
-
message: "evidence-consuming policies declared but grounding-mcp not wired: warn policies degrade non-blocking (warn-degraded), but block/require_approval policies will DENY every matching event (deny-degraded) until the producer is wired; risk.degraded_fail_posture: fail_open restores the availability-first behaviour — see docs/okf/gate-fail-posture-matrix.md",
|
|
241
|
-
},
|
|
242
|
-
];
|
|
243
|
-
}
|
|
244
|
-
// solution-acceptance is a pure CONSUMER: it reads the verdict marker the
|
|
245
|
-
// grounding-mcp producer writes. Misconfigurations can silently turn the
|
|
246
|
-
// completion-gate into a permanent deny (a No-Op that LOOKS protective):
|
|
247
|
-
// 1. grounding-mcp absent from tools.mcp -> the producer (solution_evaluate)
|
|
248
|
-
// is unreachable, so no verdict can ever be written -> deadlock.
|
|
249
|
-
// 2. grounding-mcp declares a RELATIVE SOLUTION_VERDICT_DIR -> harness now
|
|
250
|
-
// projects the value into the hook command, but a relative path resolves
|
|
251
|
-
// against each process's cwd, which harness cannot reconcile (the
|
|
252
|
-
// producer's cwd is unknown), so producer and consumer can still diverge.
|
|
253
|
-
// An ABSOLUTE non-default SOLUTION_VERDICT_DIR previously also denied (harness
|
|
254
|
-
// did not project the env override into the hook); `harness apply` now projects
|
|
255
|
-
// it (see `buildExpectedFiles` in apply.ts), so the absolute case is handled
|
|
256
|
-
// correctly and no longer warn-worthy. Condition #1 (grounding-mcp not wired)
|
|
257
|
-
// is an ERROR: solution-acceptance without a reachable producer deadlocks the
|
|
258
|
-
// completion-gate on a permanent deny, so it is a hard misconfiguration rather
|
|
259
|
-
// than a warning (task e3af6388). Condition #2 (a relative SOLUTION_VERDICT_DIR)
|
|
260
|
-
// stays a warning: it only bites on cwd divergence between producer and hook.
|
|
261
|
-
export function checkSolutionAcceptanceProducer(manifest) {
|
|
262
|
-
const pack = manifest.policy_packs.find((p) => p.name === "solution-acceptance");
|
|
263
|
-
if (!pack || !pack.enabled)
|
|
264
|
-
return [];
|
|
265
|
-
const grounding = manifest.tools.mcp.find((m) => m.name === "grounding-mcp");
|
|
266
|
-
if (!grounding) {
|
|
267
|
-
return [
|
|
268
|
-
{
|
|
269
|
-
severity: "error",
|
|
270
|
-
path: "policy_packs",
|
|
271
|
-
message: "solution-acceptance is enabled but grounding-mcp is not wired under tools.mcp: the producer (solution_evaluate) is unreachable, so the completion-gate can never see a verdict and will deadlock on a permanent deny. Add grounding-mcp (>= 0.3.2) to tools.mcp.",
|
|
272
|
-
},
|
|
273
|
-
];
|
|
274
|
-
}
|
|
275
|
-
// Condition #2: an absolute non-default SOLUTION_VERDICT_DIR is now projected
|
|
276
|
-
// into the hook at apply time, so it is handled and silent. A relative
|
|
277
|
-
// override cannot be reconciled (cwd divergence between producer and hook),
|
|
278
|
-
// so warn only for that unfixable case.
|
|
279
|
-
const env = (grounding.env ?? {});
|
|
280
|
-
const dir = env["SOLUTION_VERDICT_DIR"];
|
|
281
|
-
if (typeof dir === "string" && dir.trim().length > 0 && !path.isAbsolute(dir.trim())) {
|
|
282
|
-
return [
|
|
283
|
-
{
|
|
284
|
-
severity: "warning",
|
|
285
|
-
path: "tools.mcp",
|
|
286
|
-
message: "solution-acceptance: grounding-mcp declares a relative SOLUTION_VERDICT_DIR; harness projects this value into the completion-gate hook, but a relative path resolves against each process's working directory, so the producer (grounding-mcp) and the hook can still land on different dirs and the gate would deny. Use an absolute path.",
|
|
287
|
-
},
|
|
288
|
-
];
|
|
289
|
-
}
|
|
290
|
-
return [];
|
|
291
|
-
}
|
|
292
|
-
// checkWorkflowGateWiring closes the exact gap deriveWorkflowGatePolicies
|
|
293
|
-
// (src/runtime/workflow-policies.ts) leaves deliberately open: a
|
|
294
|
-
// `workflows:` entry that declares a `review_subagent` step with
|
|
295
|
-
// `spawn: "required"` followed by a `merge` step LOOKS like an
|
|
296
|
-
// enforced gate, but `deriveWorkflowGatePolicies` only derives the
|
|
297
|
-
// runtime policy pair when BOTH `require-review-evidence` and
|
|
298
|
-
// `require-review-evidence-bash` are declared in `manifest.hooks[]`.
|
|
299
|
-
// Without them the derivation quietly returns `[]` (no policy, so no
|
|
300
|
-
// hook-reference error either, since there is nothing referencing a
|
|
301
|
-
// hook to validate against) and the merge is never actually blocked, a
|
|
302
|
-
// No-Op that LOOKS protective. This check makes that specific
|
|
303
|
-
// misconfiguration a loud `error` instead of a silent non-enforcement.
|
|
304
|
-
//
|
|
305
|
-
// F5 (review round 2): a hook declared under the RIGHT name but wired to
|
|
306
|
-
// the WRONG surface (a stale `match`/`bash_match` that no longer covers
|
|
307
|
-
// the merge tool call, or a `command` that isn't the policy-intercept
|
|
308
|
-
// engine — `isPolicyInterceptCommand`) is just as unenforced as a
|
|
309
|
-
// missing hook, but the earlier name-only check reported it as fine.
|
|
310
|
-
// `isMergeGateHookProperlyWired` below checks the actual trigger surface
|
|
311
|
-
// + command, not just presence of the name.
|
|
312
|
-
function isMergeGateHookProperlyWired(hook, surface) {
|
|
313
|
-
if (hook.event !== "PreToolUse")
|
|
314
|
-
return false;
|
|
315
|
-
if (!isPolicyInterceptCommand(hook.command))
|
|
316
|
-
return false;
|
|
317
|
-
if (surface === "mcp")
|
|
318
|
-
return hook.match === MERGE_MCP_MATCH;
|
|
319
|
-
return hook.match === "Bash" && hook.bash_match === MERGE_BASH_MATCH;
|
|
320
|
-
}
|
|
321
|
-
export function checkWorkflowGateWiring(manifest) {
|
|
322
|
-
const offending = manifest.workflows.filter((wf) => workflowRequiresMergeGate(wf));
|
|
323
|
-
if (offending.length === 0)
|
|
324
|
-
return [];
|
|
325
|
-
const mcpHook = manifest.hooks.find((h) => h.name === REVIEW_EVIDENCE_HOOK_MCP);
|
|
326
|
-
const bashHook = manifest.hooks.find((h) => h.name === REVIEW_EVIDENCE_HOOK_BASH);
|
|
327
|
-
const problems = [];
|
|
328
|
-
const missing = [];
|
|
329
|
-
if (!mcpHook) {
|
|
330
|
-
missing.push(REVIEW_EVIDENCE_HOOK_MCP);
|
|
331
|
-
}
|
|
332
|
-
else if (!isMergeGateHookProperlyWired(mcpHook, "mcp")) {
|
|
333
|
-
problems.push(`hook "${REVIEW_EVIDENCE_HOOK_MCP}" is declared but not wired to intercept the merge ` +
|
|
334
|
-
`gate surface (expects event: PreToolUse, match: "${MERGE_MCP_MATCH}", command running ` +
|
|
335
|
-
"`harness policy intercept`)");
|
|
336
|
-
}
|
|
337
|
-
if (!bashHook) {
|
|
338
|
-
missing.push(REVIEW_EVIDENCE_HOOK_BASH);
|
|
339
|
-
}
|
|
340
|
-
else if (!isMergeGateHookProperlyWired(bashHook, "bash")) {
|
|
341
|
-
problems.push(`hook "${REVIEW_EVIDENCE_HOOK_BASH}" is declared but not wired to intercept the merge ` +
|
|
342
|
-
`gate surface (expects event: PreToolUse, match: "Bash", bash_match: "${MERGE_BASH_MATCH}", ` +
|
|
343
|
-
"command running `harness policy intercept`)");
|
|
344
|
-
}
|
|
345
|
-
if (missing.length > 0) {
|
|
346
|
-
problems.unshift(`hooks[] is missing ${missing.join(" and ")}`);
|
|
347
|
-
}
|
|
348
|
-
const errors = problems.length === 0
|
|
349
|
-
? []
|
|
350
|
-
: offending.map((wf) => ({
|
|
351
|
-
severity: "error",
|
|
352
|
-
path: "workflows",
|
|
353
|
-
message: `workflow "${wf.name}" declares a review_subagent step with spawn: "required" ` +
|
|
354
|
-
`followed by a merge step, but the runtime merge gate is not wired: ${problems.join("; ")}. ` +
|
|
355
|
-
"Without both hooks correctly wired, harness policy intercept never derives this " +
|
|
356
|
-
"workflow's merge-gate policy and the merge is NOT blocked (silent non-enforcement). " +
|
|
357
|
-
"See docs/for-agents.md, or src/cli/init/templates.ts's require-review-evidence / " +
|
|
358
|
-
'require-review-evidence-bash entries, or drop spawn: "required".',
|
|
359
|
-
}));
|
|
360
|
-
return [...errors, ...checkTaskVerbGateWiring(manifest, offending)];
|
|
361
|
-
}
|
|
362
|
-
/**
|
|
363
|
-
* MEDIUM security (review round 1, task 2699b476 round 2): a manifest can
|
|
364
|
-
* wire the ORIGINAL pair of review-evidence hooks
|
|
365
|
-
* (`require-review-evidence` / `require-review-evidence-bash`, gating
|
|
366
|
-
* `mcp__agent-tasks__pull_requests_merge` and `gh pr merge`) without ever
|
|
367
|
-
* adding the two task-scoped ones task 2699b476 introduced
|
|
368
|
-
* (`require-review-evidence-task-merge` / `require-review-evidence-task-finish`,
|
|
369
|
-
* gating `mcp__agent-tasks__task_merge` and `task_finish { autoMerge: true }`).
|
|
370
|
-
* `deriveWorkflowGatePolicies` derives the task-scoped gate ONLY when its
|
|
371
|
-
* own hook is present (same "quietly returns nothing" shape
|
|
372
|
-
* `checkWorkflowGateWiring` already guards for the original pair), so a
|
|
373
|
-
* manifest predating that task keeps the two newer verbs completely
|
|
374
|
-
* uncovered: a PR can be merged via `task_merge` or an auto-merging
|
|
375
|
-
* `task_finish` with no recorded review at all, while `harness validate`
|
|
376
|
-
* stayed silent about it. This is a `warning`, not an `error`, because the
|
|
377
|
-
* ORIGINAL surfaces (`pull_requests_merge`, `gh pr merge`) are still
|
|
378
|
-
* enforced when this fires: the gap is narrower than the fully-unwired
|
|
379
|
-
* case above, but still silent non-enforcement for the two verbs it names.
|
|
380
|
-
*/
|
|
381
|
-
function checkTaskVerbGateWiring(manifest, offending) {
|
|
382
|
-
const mcpHook = manifest.hooks.find((h) => h.name === REVIEW_EVIDENCE_HOOK_MCP);
|
|
383
|
-
const bashHook = manifest.hooks.find((h) => h.name === REVIEW_EVIDENCE_HOOK_BASH);
|
|
384
|
-
if (!mcpHook || !bashHook)
|
|
385
|
-
return [];
|
|
386
|
-
const taskMergeHook = manifest.hooks.find((h) => h.name === REVIEW_EVIDENCE_HOOK_TASK_MERGE);
|
|
387
|
-
const taskFinishHook = manifest.hooks.find((h) => h.name === REVIEW_EVIDENCE_HOOK_TASK_FINISH);
|
|
388
|
-
const uncoveredVerbs = [];
|
|
389
|
-
const missingHookNames = [];
|
|
390
|
-
if (!taskMergeHook) {
|
|
391
|
-
uncoveredVerbs.push("mcp__agent-tasks__task_merge");
|
|
392
|
-
missingHookNames.push(`"${REVIEW_EVIDENCE_HOOK_TASK_MERGE}"`);
|
|
393
|
-
}
|
|
394
|
-
if (!taskFinishHook) {
|
|
395
|
-
uncoveredVerbs.push("mcp__agent-tasks__task_finish (autoMerge: true)");
|
|
396
|
-
missingHookNames.push(`"${REVIEW_EVIDENCE_HOOK_TASK_FINISH}"`);
|
|
397
|
-
}
|
|
398
|
-
if (uncoveredVerbs.length === 0)
|
|
399
|
-
return [];
|
|
400
|
-
return offending.map((wf) => ({
|
|
401
|
-
severity: "warning",
|
|
402
|
-
path: "workflows",
|
|
403
|
-
message: `workflow "${wf.name}" wires the review-evidence gate for mcp__agent-tasks__pull_requests_merge ` +
|
|
404
|
-
`and gh pr merge, but not for ${uncoveredVerbs.join(" or ")}: ` +
|
|
405
|
-
`hooks[] is missing ${missingHookNames.join(" and ")}. ` +
|
|
406
|
-
"Without them harness policy intercept never derives the task-scoped merge gate (task 2699b476) " +
|
|
407
|
-
"and a PR can be merged through those verbs with no recorded review, even though the original " +
|
|
408
|
-
"pull_requests_merge / gh pr merge surfaces are still gated. See docs/for-agents.md, or " +
|
|
409
|
-
"src/cli/init/templates.ts's require-review-evidence-task-merge / " +
|
|
410
|
-
"require-review-evidence-task-finish entries.",
|
|
411
|
-
}));
|
|
412
|
-
}
|
|
413
|
-
/**
|
|
414
|
-
* F1 (review round 2): a hand-authored policy on the identical trigger
|
|
415
|
-
* surface + ledger_tag as a derived block gate, but weaker than it
|
|
416
|
-
* (`enforcement: "warn"`/`"require_approval"`, or `when:`-scoped), no
|
|
417
|
-
* longer suppresses the derived gate (see `isAtLeastAsStrongAsDerivedGate`
|
|
418
|
-
* in workflow-policies.ts) — both apply. This check surfaces that overlap
|
|
419
|
-
* as a warning so an operator reading the weaker policy does not mistake
|
|
420
|
-
* it for the ONLY gate on the surface.
|
|
421
|
-
*/
|
|
422
|
-
export function checkWorkflowGateWeakOverlap(manifest) {
|
|
423
|
-
return findWeakGatePolicyOverlaps(manifest).map((overlap) => ({
|
|
424
|
-
severity: "warning",
|
|
425
|
-
path: "workflows",
|
|
426
|
-
message: `workflow "${overlap.workflowName}" derives a block gate on ${overlap.surface}; ` +
|
|
427
|
-
`hand-authored policy "${overlap.handPolicyName}" on the same surface is weaker ` +
|
|
428
|
-
`(${overlap.reason}). Both policies apply: the derived block gate ` +
|
|
429
|
-
`("${overlap.derivedPolicyName}") still enforces review evidence independently, so this ` +
|
|
430
|
-
"is informational, not a gap, but double-check the weaker policy is intentional. Note " +
|
|
431
|
-
"also that this overlap is not suppressed on purpose, so the same event now round-trips " +
|
|
432
|
-
"the ledger twice (once per policy); if that hook's budget_ms was sized for one policy, " +
|
|
433
|
-
"check it against two, since requiredHookBudgetMs does not scale with the policy count.",
|
|
434
|
-
}));
|
|
435
|
-
}
|
|
436
|
-
/**
|
|
437
|
-
* F6 (review round 2): a workflow that declares BOTH a `merge` step and a
|
|
438
|
-
* `review_subagent` step with `spawn: "required"`, but with the review
|
|
439
|
-
* step coming AFTER the merge step, derives no gate at all
|
|
440
|
-
* (`workflowRequiresMergeGate` only looks for review-then-merge). That
|
|
441
|
-
* ordering is likely a mistake (a review that runs after the PR already
|
|
442
|
-
* merged cannot gate it), but step-ordering validation in general is out
|
|
443
|
-
* of scope for this slice (module doc, src/runtime/workflow-policies.ts).
|
|
444
|
-
* This warns instead of silently doing nothing.
|
|
445
|
-
*/
|
|
446
|
-
export function checkWorkflowMergeBeforeReview(manifest) {
|
|
447
|
-
const out = [];
|
|
448
|
-
for (const wf of manifest.workflows) {
|
|
449
|
-
if (workflowRequiresMergeGate(wf))
|
|
450
|
-
continue;
|
|
451
|
-
let sawMerge = false;
|
|
452
|
-
let requiredReviewAfterMerge = false;
|
|
453
|
-
for (const step of wf.steps) {
|
|
454
|
-
if (step.kind === "merge") {
|
|
455
|
-
sawMerge = true;
|
|
456
|
-
}
|
|
457
|
-
else if (step.kind === "review_subagent" && step.spawn === "required" && sawMerge) {
|
|
458
|
-
requiredReviewAfterMerge = true;
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
if (requiredReviewAfterMerge) {
|
|
462
|
-
out.push({
|
|
463
|
-
severity: "warning",
|
|
464
|
-
path: "workflows",
|
|
465
|
-
message: `workflow "${wf.name}" declares a required review step after its merge step; no ` +
|
|
466
|
-
"merge gate is derived (step ordering validation is a later slice).",
|
|
467
|
-
});
|
|
468
|
-
}
|
|
469
|
-
}
|
|
470
|
-
return out;
|
|
471
|
-
}
|
|
472
|
-
/**
|
|
473
|
-
* Review round 3 (99f47307 Slice 1): a hand-authored policy whose name
|
|
474
|
-
* equals a derived policy's name (`workflow:<name>:review-before-merge[-
|
|
475
|
-
* bash]`) is not deduped by name (dedupe keys on surface, not name), so
|
|
476
|
-
* the derived view carries two policies with one name. This fires both
|
|
477
|
-
* when the hand-authored policy sits on a DIFFERENT surface (dedupe never
|
|
478
|
-
* even compares them), and when it sits on the SAME surface but is not
|
|
479
|
-
* `isAtLeastAsStrongAsDerivedGate` (weaker enforcement or a `when:` scope
|
|
480
|
-
* — that case IS a surface match, so `findWeakGatePolicyOverlaps` also
|
|
481
|
-
* reports it as an overlap; the two checks are not mutually exclusive).
|
|
482
|
-
* Either way the runtime evaluates both policies (fail-safe), but every
|
|
483
|
-
* by-name reader (`explain`, `explain-policy`, `audit`, `diff`'s
|
|
484
|
-
* name-keyed policy list) resolves the name to the hand-authored one and
|
|
485
|
-
* silently hides the derived gate. The schema's duplicate-name refinement
|
|
486
|
-
* cannot see this (it runs on the hand-authored view), so it is an error
|
|
487
|
-
* here.
|
|
488
|
-
*/
|
|
489
|
-
export function checkWorkflowDerivedNameCollision(manifest) {
|
|
490
|
-
const handNames = new Set(handAuthoredPolicies(manifest).map((p) => p.name));
|
|
491
|
-
return deriveWorkflowGatePolicies(manifest)
|
|
492
|
-
.filter((derived) => handNames.has(derived.name))
|
|
493
|
-
.map((derived) => ({
|
|
494
|
-
severity: "error",
|
|
495
|
-
path: "policies",
|
|
496
|
-
message: `hand-authored policy "${derived.name}" collides with the policy of the same name ` +
|
|
497
|
-
"derived from workflows[] (it does not stand in for the derived gate: either a " +
|
|
498
|
-
"different trigger surface, or weaker / differently-extracting on the same one); " +
|
|
499
|
-
"both are enforced, but explain/explain-policy/audit/diff resolve the name to the " +
|
|
500
|
-
"hand-authored one. Rename the hand-authored policy.",
|
|
501
|
-
}));
|
|
502
|
-
}
|
|
503
|
-
/**
|
|
504
|
-
* Every `workflows[]` check in one list, so `harness validate`
|
|
505
|
-
* (`runAssetChecks`) and `harness doctor`'s Workflows section run the
|
|
506
|
-
* SAME set (review round 3, 99f47307 Slice 1: doctor previously picked
|
|
507
|
-
* two of the three by hand and was missing `checkWorkflowMergeBeforeReview`).
|
|
508
|
-
*/
|
|
509
|
-
export function checkWorkflows(manifest) {
|
|
510
|
-
return [
|
|
511
|
-
...checkWorkflowGateWiring(manifest),
|
|
512
|
-
...checkWorkflowGateWeakOverlap(manifest),
|
|
513
|
-
...checkWorkflowMergeBeforeReview(manifest),
|
|
514
|
-
...checkWorkflowDerivedNameCollision(manifest),
|
|
515
|
-
];
|
|
516
|
-
}
|
|
517
|
-
/**
|
|
518
|
-
* The orchestrator-workflow knob the grounding-mcp producer reads
|
|
519
|
-
* (`resolveOwKnob`). Repo-relative on purpose: the knob belongs to the
|
|
520
|
-
* repository whose completions the OW arm gates.
|
|
521
|
-
*/
|
|
522
|
-
export const OW_KNOB_REL_PATH = ".ai/solution-acceptance.json";
|
|
523
|
-
export function createDefaultGitIgnoreProbe(cwd) {
|
|
524
|
-
return (relPath) => {
|
|
525
|
-
const res = spawnSync("git", ["check-ignore", "-q", "--", relPath], {
|
|
526
|
-
cwd: cwd ?? process.cwd(),
|
|
527
|
-
stdio: "ignore",
|
|
528
|
-
});
|
|
529
|
-
if (res.error)
|
|
530
|
-
return null;
|
|
531
|
-
if (res.status === 0)
|
|
532
|
-
return true;
|
|
533
|
-
if (res.status === 1)
|
|
534
|
-
return false;
|
|
535
|
-
return null; // 128: not a git repository (or another fatal git error)
|
|
536
|
-
};
|
|
537
|
-
}
|
|
538
|
-
// Knob-reachability lint (task 24f6ceb9, ow-review-2026-07-01). The OW arm
|
|
539
|
-
// of solution-acceptance reads repo state: the knob above plus run
|
|
540
|
-
// completeness under `.ai/runs/`. When the knob path is git-ignored the
|
|
541
|
-
// repo CANNOT commit its enforcement posture, so in a fresh clone or a git
|
|
542
|
-
// worktree `.ai/runs/` is absent, the default `auto` knob silently skips
|
|
543
|
-
// the OW arm, and the gate that exists to prevent process skipping is
|
|
544
|
-
// itself skipped exactly where process skipping happens. Warn (not error):
|
|
545
|
-
// the preflight floor still gates every completion; only the OW arm is
|
|
546
|
-
// affected. A `null` probe answer (non-repo cwd, git missing) skips the
|
|
547
|
-
// check — validate must stay usable for pure home-config linting.
|
|
548
|
-
export function checkSolutionAcceptanceKnobIgnored(manifest, probe) {
|
|
549
|
-
const pack = manifest.policy_packs.find((p) => p.name === "solution-acceptance");
|
|
550
|
-
if (!pack || !pack.enabled)
|
|
551
|
-
return [];
|
|
552
|
-
if (probe(OW_KNOB_REL_PATH) !== true)
|
|
553
|
-
return [];
|
|
554
|
-
return [
|
|
555
|
-
{
|
|
556
|
-
severity: "warning",
|
|
557
|
-
path: "policy_packs",
|
|
558
|
-
message: `solution-acceptance: the orchestrator-workflow knob ${OW_KNOB_REL_PATH} ` +
|
|
559
|
-
`is git-ignored in this repository, so the OW enforcement posture cannot ` +
|
|
560
|
-
`be committed. In a fresh clone or git worktree .ai/runs/ is absent and ` +
|
|
561
|
-
`the default "auto" knob silently skips the OW arm — exactly where ` +
|
|
562
|
-
`process skipping happens. Narrow the ignore to .ai/runs/ (run state ` +
|
|
563
|
-
`stays local) and commit ${OW_KNOB_REL_PATH}; see ` +
|
|
564
|
-
`docs/policy-packs/solution-acceptance.md ("Repo state and gitignore").`,
|
|
565
|
-
},
|
|
566
|
-
];
|
|
567
|
-
}
|
|
568
|
-
// Self-attestation disclosure (task 43b107f2, harness-review-2026-07-01).
|
|
569
|
-
// The generic `requires:`/`ledger_tag` engine matches substrings in ledger
|
|
570
|
-
// content that the GATED AGENT can write directly via
|
|
571
|
-
// `mcp__grounding-mcp__ledger_add` — whoever can write the ledger can open
|
|
572
|
-
// the gate. The two builtin packs were hardened to filesystem markers after
|
|
573
|
-
// exactly this class of self-approval bug (docs/CLI.md, branch-protection),
|
|
574
|
-
// but a custom block-policy is only as strong as its evidence producer.
|
|
575
|
-
//
|
|
576
|
-
// Heuristic: warn only when a `block` policy declares NO `producers:` at
|
|
577
|
-
// all — the evidence source is then undocumented and the operator has made
|
|
578
|
-
// no visible trust decision. A declared producer, even an agent-executable
|
|
579
|
-
// `mcp`/`bash` one, IS the schema's way of stating the intended evidence
|
|
580
|
-
// flow (same philosophy as the doctor producer-gap refinement, task
|
|
581
|
-
// f97e152f): the full/team templates deliberately ship mcp-producer
|
|
582
|
-
// process-gates whose purpose is forcing a review-subagent step, and
|
|
583
|
-
// warning on every one of them would train operators to ignore warnings.
|
|
584
|
-
// What an agent-executable producer MEANS for the trust model (advisory
|
|
585
|
-
// against the gated agent) is taught by the tripwire in
|
|
586
|
-
// docs/writing-custom-policies.md, which the producer docs link to.
|
|
587
|
-
export function checkPolicySelfAttestation(manifest) {
|
|
588
|
-
const diags = [];
|
|
589
|
-
for (let i = 0; i < manifest.policies.length; i++) {
|
|
590
|
-
const p = manifest.policies[i];
|
|
591
|
-
// block-only on purpose: a require_approval policy's canonical unblock
|
|
592
|
-
// path is the operator verb (`harness approve risk`), an ask-semantics
|
|
593
|
-
// flow that exists independent of producers:, so absence of producers
|
|
594
|
-
// there does not mean the evidence source is undocumented.
|
|
595
|
-
if (p === undefined || p.enforcement !== "block")
|
|
596
|
-
continue;
|
|
597
|
-
// operator_only: true (task 2cc73f55) is the schema-level unconditional
|
|
598
|
-
// operator-only deny: no requires:, so there is no self-satisfiable
|
|
599
|
-
// evidence source to leave undocumented, and no producers: array could
|
|
600
|
-
// ever name a legitimate one (an unconditional deny is never satisfied
|
|
601
|
-
// from inside the session, by design). Correct-by-construction: skip
|
|
602
|
-
// both this warning and the --strict error it would become.
|
|
603
|
-
if (p.operator_only === true)
|
|
604
|
-
continue;
|
|
605
|
-
if (p.producers !== undefined && p.producers.length > 0)
|
|
606
|
-
continue;
|
|
607
|
-
diags.push({
|
|
608
|
-
severity: "warning",
|
|
609
|
-
path: `policies[${i}]`,
|
|
610
|
-
message: `policy "${p.name}" blocks on requires.ledger_tag but declares no ` +
|
|
611
|
-
`producers: — the evidence source is undocumented, and the tag is ` +
|
|
612
|
-
`satisfied by ANY ledger writer, including the gated agent itself ` +
|
|
613
|
-
`via mcp__grounding-mcp__ledger_add (advisory against the agent ` +
|
|
614
|
-
`it gates). Declare a producers: entry naming the intended evidence ` +
|
|
615
|
-
`flow — an ask-kind producer for operator-in-the-loop approval ` +
|
|
616
|
-
`(alongside the mcp recovery producer the schema requires), or an ` +
|
|
617
|
-
`agent recipe if the gate is a deliberate process gate. See ` +
|
|
618
|
-
`docs/writing-custom-policies.md ("The trust model").`,
|
|
619
|
-
});
|
|
620
|
-
}
|
|
621
|
-
return diags;
|
|
622
|
-
}
|
|
623
|
-
// M7 validate lint: a policy that gates on risk.* / action.reversible clauses
|
|
624
|
-
// WITHOUT an environment.name clause fires on EVERY unclassified command in
|
|
625
|
-
// EVERY environment because those three clauses fail-closed to matched=true
|
|
626
|
-
// when the action is unclassified ("unknown is not safe"). This is almost
|
|
627
|
-
// never what the operator intends: an unscoped risk policy becomes a blanket
|
|
628
|
-
// gate on any command the classifier does not recognise. See docs/risk-gate.md.
|
|
629
|
-
export function checkPolicyRiskWithoutEnvScope(manifest) {
|
|
630
|
-
const diags = [];
|
|
631
|
-
for (let i = 0; i < manifest.policies.length; i++) {
|
|
632
|
-
const p = manifest.policies[i];
|
|
633
|
-
if (!p?.when)
|
|
634
|
-
continue;
|
|
635
|
-
const when = p.when;
|
|
636
|
-
// The three clauses that fail-closed to matched=true for an unclassified
|
|
637
|
-
// action. An environment.name clause constrains the scope, so we only
|
|
638
|
-
// warn when it is absent.
|
|
639
|
-
const hasUnclassifiedFallbackClause = when["risk.severity_at_least"] !== undefined ||
|
|
640
|
-
when["risk.category_in"] !== undefined ||
|
|
641
|
-
when["action.reversible"] !== undefined;
|
|
642
|
-
const hasEnvNameScope = when["environment.name"] !== undefined;
|
|
643
|
-
if (hasUnclassifiedFallbackClause && !hasEnvNameScope) {
|
|
644
|
-
diags.push({
|
|
645
|
-
severity: "warning",
|
|
646
|
-
path: `policies[${i}]`,
|
|
647
|
-
message: `policy "${p.name}" declares a when: block with ` +
|
|
648
|
-
`risk.severity_at_least / risk.category_in / action.reversible ` +
|
|
649
|
-
`but no environment.name scope: those clauses fail-closed to ` +
|
|
650
|
-
`matched=true for any unclassified command, so this policy fires ` +
|
|
651
|
-
`on every unclassified action in every environment. ` +
|
|
652
|
-
`Add an environment.name clause to scope the policy to a specific ` +
|
|
653
|
-
`environment. See docs/risk-gate.md.`,
|
|
654
|
-
});
|
|
655
|
-
}
|
|
656
|
-
}
|
|
657
|
-
return diags;
|
|
658
|
-
}
|
|
659
|
-
// Safe-deletion-root syntax lint (task d03af8f6, review round 2, LOW (a)).
|
|
660
|
-
// `resolveDeletionTarget` (`src/runtime/deletion-target-resolve.ts`) only
|
|
661
|
-
// ever treats an ABSOLUTE, plain-literal `risk.safe_deletion_roots` entry
|
|
662
|
-
// as an allowlist member — a relative entry can never match any target
|
|
663
|
-
// (every target the resolver considers absolute-checks against is itself
|
|
664
|
-
// required to be absolute first, so a relative root is silently
|
|
665
|
-
// dead weight), and an entry containing `$` or `~` reads as a LITERAL
|
|
666
|
-
// dollar-sign/tilde character (this resolver never expands either), not
|
|
667
|
-
// the shell construct an operator likely intended when writing it. Both
|
|
668
|
-
// shapes are a config mistake the operator would otherwise discover only
|
|
669
|
-
// by noticing a deletion that should have been allowed still got gated.
|
|
670
|
-
// Warning-severity (not an error): unlike the bare-`/` case in
|
|
671
|
-
// `RiskSchema`'s own `superRefine` (which defeats the allowlist in the
|
|
672
|
-
// DANGEROUS direction — matching too much), a malformed entry here only
|
|
673
|
-
// fails to widen the allowlist — the resolver still fails CLOSED
|
|
674
|
-
// (unresolvable) for a target that entry was meant to cover, so it is a
|
|
675
|
-
// usability lint, not a security gap needing a parse-time refusal.
|
|
676
|
-
export function checkSafeDeletionRootsSyntax(manifest) {
|
|
677
|
-
const diags = [];
|
|
678
|
-
// Guarded (task d03af8f6, review round 3, LOW (e)) the same way
|
|
679
|
-
// `src/runtime/intercept.ts` and `src/cli/explain-policy.ts` already
|
|
680
|
-
// guard this same field: a hand-built `Manifest` that bypasses
|
|
681
|
-
// `RiskSchema.parse` (every test fixture that constructs
|
|
682
|
-
// `{ risk: { classifiers: [...] } }` directly, per that schema's own
|
|
683
|
-
// comment) can carry a `risk` with no `safe_deletion_roots` at all, or
|
|
684
|
-
// no `risk` object whatsoever — `manifest.risk.safe_deletion_roots`
|
|
685
|
-
// would throw for either shape instead of degrading to the same
|
|
686
|
-
// default the runtime resolver itself falls back to.
|
|
687
|
-
const safeDeletionRoots = manifest.risk?.safe_deletion_roots ?? DEFAULT_SAFE_DELETION_ROOTS;
|
|
688
|
-
safeDeletionRoots.forEach((root, i) => {
|
|
689
|
-
const trimmed = root.trim();
|
|
690
|
-
if (!trimmed.startsWith("/")) {
|
|
691
|
-
diags.push({
|
|
692
|
-
severity: "warning",
|
|
693
|
-
path: `risk.safe_deletion_roots[${i}]`,
|
|
694
|
-
message: `risk.safe_deletion_roots entry "${root}" is not an absolute path — ` +
|
|
695
|
-
`resolveDeletionTarget only ever matches an absolute target against this list, ` +
|
|
696
|
-
`so a relative entry can never allow anything. See docs/risk-gate.md.`,
|
|
697
|
-
});
|
|
698
|
-
return;
|
|
699
|
-
}
|
|
700
|
-
if (trimmed.includes("$") || trimmed.includes("~")) {
|
|
701
|
-
diags.push({
|
|
702
|
-
severity: "warning",
|
|
703
|
-
path: `risk.safe_deletion_roots[${i}]`,
|
|
704
|
-
message: `risk.safe_deletion_roots entry "${root}" contains "$" or "~" — this resolver never ` +
|
|
705
|
-
`expands a shell variable or home-directory reference, so the entry is matched as a ` +
|
|
706
|
-
`LITERAL "$"/"~" character, almost certainly not what was intended. Write the fully ` +
|
|
707
|
-
`expanded absolute path instead. See docs/risk-gate.md.`,
|
|
708
|
-
});
|
|
709
|
-
}
|
|
710
|
-
});
|
|
711
|
-
return diags;
|
|
712
|
-
}
|
|
713
|
-
// Template-policy drift (task adf037c1): an installed harness.yaml ages in
|
|
714
|
-
// place — `harness apply` never retroactively adds newly-shipped default
|
|
715
|
-
// policies to an already-materialized manifest, so security policies
|
|
716
|
-
// introduced after install reach only fresh installs. The measured
|
|
717
|
-
// incident: a 0.44.0 machine whose manifest predated the kill-switch
|
|
718
|
-
// defenses (deny-kill-switch-bypass / deny-session-env-strip /
|
|
719
|
-
// deny-pause-sentinel-forgery) had the documented `harness pause` bypass
|
|
720
|
-
// live as ALLOW, with nothing surfacing the gap.
|
|
721
|
-
//
|
|
722
|
-
// Scope (operator decision 2026-08-08): compare only the shipped
|
|
723
|
-
// `operator_only` (kill-switch / security) policy names — the
|
|
724
|
-
// profile-independent security floor — against the installed manifest.
|
|
725
|
-
// A missing one is an ERROR (this is a real, exploitable defense gap),
|
|
726
|
-
// distinct from a merely-cosmetic drift; non-operator_only policies are
|
|
727
|
-
// intentionally not compared so solo/team installs are not nagged for
|
|
728
|
-
// full-only convenience policies they never carried.
|
|
729
|
-
//
|
|
730
|
-
// Two drift shapes are reported, both real aged-manifest bypasses:
|
|
731
|
-
// - MISSING: the shipped operator_only policy name is absent entirely.
|
|
732
|
-
// - DOWNGRADED: a policy of that name IS present but is no longer
|
|
733
|
-
// operator_only (task 2cc73f55's history: these exact policies once
|
|
734
|
-
// shipped with a `requires.ledger_tag` shape a ledger write could
|
|
735
|
-
// satisfy; a manifest that kept the name but not operator_only:true
|
|
736
|
-
// has a bypassable kill-switch). Name-presence alone would pass it as
|
|
737
|
-
// no-drift, which is exactly the class this check exists to catch
|
|
738
|
-
// (review finding 2026-08-08). operator_only:true is the single
|
|
739
|
-
// sufficient predicate: the schema's superRefine forces enforcement
|
|
740
|
-
// block for operator_only policies, so any downgrade (warn, a
|
|
741
|
-
// requires: shape, operator_only dropped) fails this test.
|
|
742
|
-
//
|
|
743
|
-
// Deliberate opt-out (operator decision 2026-08-08): a name listed in
|
|
744
|
-
// `doctor.ignore_template_drift` is skipped ENTIRELY (both shapes). This
|
|
745
|
-
// is NOT a `policies[].enabled` flag — such a flag would be read here but
|
|
746
|
-
// ignored by the runtime engine, so an operator would believe a policy
|
|
747
|
-
// disabled while it still fired. The ignore-list only ever silences THIS
|
|
748
|
-
// report and changes no enforcement, so its meaning is honest. A
|
|
749
|
-
// stale/typo'd ignore entry (matching no shipped name) is itself
|
|
750
|
-
// surfaced as a warning so a dead opt-out cannot silently stop
|
|
751
|
-
// suppressing after a future rename.
|
|
752
|
-
export function checkTemplatePolicyDrift(manifest) {
|
|
753
|
-
const byName = new Map(manifest.policies.map((p) => [p.name, p]));
|
|
754
|
-
const ignored = new Set(manifest.doctor.ignore_template_drift);
|
|
755
|
-
const shipped = shippedOperatorOnlyPolicyNames();
|
|
756
|
-
const diags = [];
|
|
757
|
-
for (const name of shipped) {
|
|
758
|
-
if (ignored.has(name))
|
|
759
|
-
continue;
|
|
760
|
-
const installed = byName.get(name);
|
|
761
|
-
if (installed === undefined) {
|
|
762
|
-
diags.push({
|
|
763
|
-
severity: "error",
|
|
764
|
-
path: "policies",
|
|
765
|
-
message: `shipped operator_only security policy "${name}" is missing from ` +
|
|
766
|
-
`this manifest, a defense the current template ships but this ` +
|
|
767
|
-
`(older) install never received, so the gate it enforces is silently ` +
|
|
768
|
-
`absent. Re-add the "${name}" policy + its hook from the full ` +
|
|
769
|
-
`template (\`harness init --template full\` in a scratch dir and copy ` +
|
|
770
|
-
`the block, or hand-add per docs/okf/pause-vs-gate-kill-switch.md), ` +
|
|
771
|
-
`or, if you deliberately do not want it, list "${name}" under ` +
|
|
772
|
-
`doctor.ignore_template_drift to acknowledge the opt-out.`,
|
|
773
|
-
});
|
|
774
|
-
}
|
|
775
|
-
else if (installed.operator_only !== true) {
|
|
776
|
-
diags.push({
|
|
777
|
-
severity: "error",
|
|
778
|
-
path: "policies",
|
|
779
|
-
message: `security policy "${name}" is present but DOWNGRADED: the shipped ` +
|
|
780
|
-
`template makes it \`operator_only: true\` (an unconditional deny no ` +
|
|
781
|
-
`in-session evidence can satisfy), but this manifest's copy is not, ` +
|
|
782
|
-
`so its kill-switch is bypassable (e.g. a \`requires:\` shape a ledger ` +
|
|
783
|
-
`write satisfies, or \`enforcement: warn\`). Restore \`operator_only: ` +
|
|
784
|
-
`true\` from the full template, or list "${name}" under ` +
|
|
785
|
-
`doctor.ignore_template_drift if this weakening is deliberate.`,
|
|
786
|
-
});
|
|
787
|
-
}
|
|
788
|
-
}
|
|
789
|
-
// Stale/typo'd opt-out entries: named in ignore_template_drift but not a
|
|
790
|
-
// shipped operator_only policy AND not a shipped bash_match trigger name
|
|
791
|
-
// (task 037cfb7c's checkTriggerBoundaryDrift shares this same opt-out
|
|
792
|
-
// field, see that function's header), so they suppress nothing. Warn
|
|
793
|
-
// (not error), fail-safe already (the operator keeps seeing any real
|
|
794
|
-
// drift), this only surfaces the dead config so a rename doesn't
|
|
795
|
-
// silently strand an acknowledgement.
|
|
796
|
-
const shippedSet = new Set(shipped);
|
|
797
|
-
const knownBashMatchNames = new Set(shippedBashMatchBoundaries().map((e) => e.name));
|
|
798
|
-
for (const name of manifest.doctor.ignore_template_drift) {
|
|
799
|
-
if (!shippedSet.has(name) && !knownBashMatchNames.has(name)) {
|
|
800
|
-
diags.push({
|
|
801
|
-
severity: "warning",
|
|
802
|
-
path: "doctor.ignore_template_drift",
|
|
803
|
-
message: `doctor.ignore_template_drift lists "${name}", which is not a ` +
|
|
804
|
-
`shipped operator_only policy name or a shipped bash_match trigger ` +
|
|
805
|
-
`name, so it suppresses nothing. Remove the entry, or fix the name ` +
|
|
806
|
-
`(a policy/hook rename can strand an acknowledgement here).`,
|
|
807
|
-
});
|
|
808
|
-
}
|
|
809
|
-
}
|
|
810
|
-
return diags;
|
|
811
|
-
}
|
|
812
|
-
// Trigger-boundary drift (task 037cfb7c, follow-up to adf037c1): a
|
|
813
|
-
// bash_match trigger's own drift check, parallel to
|
|
814
|
-
// checkTemplatePolicyDrift's missing/downgraded-policy check above.
|
|
815
|
-
// Compares, by exact name, every shipped-by-name bash_match trigger
|
|
816
|
-
// (hook-level hooks[].bash_match and policy-level
|
|
817
|
-
// policies[].trigger.bash_match) against shippedBashMatchBoundaries(),
|
|
818
|
-
// but ONLY the leading boundary-alternation group, never the rest of
|
|
819
|
-
// the regex; an operator's own edits to the command-shape match after
|
|
820
|
-
// the boundary group are legitimate and must not be flagged.
|
|
821
|
-
//
|
|
822
|
-
// Comparison is set-based, not string-equal (splitBoundaryAlternatives
|
|
823
|
-
// below splits the `|`-separated alternatives, escape-aware so `\|`
|
|
824
|
-
// inside an alternative is not itself treated as a separator, and
|
|
825
|
-
// trims each one). Only an alternative the template has that the
|
|
826
|
-
// installed regex is MISSING is reported by name; reordering the same
|
|
827
|
-
// alternatives, or an installed regex that is a superset of the
|
|
828
|
-
// template's, is not a finding (a superset can only widen what the
|
|
829
|
-
// trigger catches, never narrow it). An installed bash_match under a
|
|
830
|
-
// shipped name that has no recognizable boundary group at all is its
|
|
831
|
-
// own finding: it matches no command separator, so the trigger is
|
|
832
|
-
// unreachable for anything but a bare command at the very start of the
|
|
833
|
-
// string.
|
|
834
|
-
//
|
|
835
|
-
// Scope (mirrors checkTemplatePolicyDrift's operator decision
|
|
836
|
-
// 2026-08-08): an entry the template doesn't know by that name is out
|
|
837
|
-
// of scope for THIS check (a missing shipped hook/policy is
|
|
838
|
-
// checkTemplatePolicyDrift's concern, not this one's).
|
|
839
|
-
//
|
|
840
|
-
// Exit-code choice: ERROR, not warn, same rationale as
|
|
841
|
-
// checkTemplatePolicyDrift: a missing boundary alternative is a real,
|
|
842
|
-
// measured gate bypass, not cosmetic drift. See the `CHANGELOG.md:#0.49.0`
|
|
843
|
-
// entry for task 037cfb7c for the measured incident and reproduction.
|
|
844
|
-
// Severity is pinned directly (not just via message
|
|
845
|
-
// content) by tests/cli/doctor-trigger-boundary-drift.test.ts.
|
|
846
|
-
//
|
|
847
|
-
// Deliberate opt-out: a name listed under `doctor.ignore_template_drift`
|
|
848
|
-
// is skipped entirely, same field and same "silences only this report,
|
|
849
|
-
// never enforcement semantics" contract as checkTemplatePolicyDrift (see
|
|
850
|
-
// that function's header), NOT a `policies[].enabled` flag, which the
|
|
851
|
-
// runtime would still enforce while the operator believed it disabled.
|
|
852
|
-
/**
|
|
853
|
-
* Splits a boundary-alternation group's inner content on top-level `|`
|
|
854
|
-
* alternation separators. A backslash-escaped character (e.g. `\|`,
|
|
855
|
-
* `\n`, `\(`) is treated as one atomic unit and is never itself a
|
|
856
|
-
* separator, so an escaped pipe inside an alternative does not split
|
|
857
|
-
* it. Each alternative is trimmed, so whitespace padding around a `|`
|
|
858
|
-
* does not change the comparison.
|
|
859
|
-
*/
|
|
860
|
-
function splitBoundaryAlternatives(boundary) {
|
|
861
|
-
const parts = [];
|
|
862
|
-
let current = "";
|
|
863
|
-
for (let i = 0; i < boundary.length; i++) {
|
|
864
|
-
const ch = boundary[i];
|
|
865
|
-
if (ch === "\\" && i + 1 < boundary.length) {
|
|
866
|
-
current += ch + boundary[i + 1];
|
|
867
|
-
i++;
|
|
868
|
-
continue;
|
|
869
|
-
}
|
|
870
|
-
if (ch === "|") {
|
|
871
|
-
parts.push(current.trim());
|
|
872
|
-
current = "";
|
|
873
|
-
continue;
|
|
874
|
-
}
|
|
875
|
-
current += ch;
|
|
876
|
-
}
|
|
877
|
-
parts.push(current.trim());
|
|
878
|
-
return parts;
|
|
879
|
-
}
|
|
880
|
-
/**
|
|
881
|
-
* The template's boundary alternatives that are absent from the
|
|
882
|
-
* installed boundary's alternative set. Order and duplicate extras in
|
|
883
|
-
* `installedBoundary` never matter: only a missing alternative narrows
|
|
884
|
-
* what the trigger can match relative to the template, so only a
|
|
885
|
-
* missing alternative is reported.
|
|
886
|
-
*/
|
|
887
|
-
function missingBoundaryAlternatives(templateBoundary, installedBoundary) {
|
|
888
|
-
const installedSet = new Set(splitBoundaryAlternatives(installedBoundary));
|
|
889
|
-
return splitBoundaryAlternatives(templateBoundary).filter((alt) => !installedSet.has(alt));
|
|
890
|
-
}
|
|
891
|
-
// Shared rehydration guidance appended to every finding message. `init`
|
|
892
|
-
// resolves its target manifest via `--config` (or `~/.harness/harness.yaml`
|
|
893
|
-
// by default, task adf037c1's original wording pointed at "a scratch dir",
|
|
894
|
-
// which `init` does not honor: it always resolves against `--config` or the
|
|
895
|
-
// home directory, never `cwd`); writing to a throwaway `--config` path is
|
|
896
|
-
// the only way to get the shipped regex to compare against without risking
|
|
897
|
-
// `--force` overwriting the live manifest.
|
|
898
|
-
function triggerBoundaryRehydrationGuidance(entryName) {
|
|
899
|
-
return (`Rehydrate the boundary: \`harness init --template full --config ` +
|
|
900
|
-
`/tmp/harness-full.yaml\` and copy the boundary from there (or hand-edit ` +
|
|
901
|
-
`per docs/okf/pause-vs-gate-kill-switch.md), or, if this is a deliberate ` +
|
|
902
|
-
`custom boundary, list "${entryName}" under doctor.ignore_template_drift ` +
|
|
903
|
-
`to acknowledge the opt-out.`);
|
|
904
|
-
}
|
|
905
|
-
// Shared "not a boundary at all" wording (review round 3, item 1). Used
|
|
906
|
-
// both when the leading group is syntactically absent (no parenthesized
|
|
907
|
-
// group at all) AND when one IS present but shares zero alternatives
|
|
908
|
-
// with the template's, e.g. a shipped-named trigger whose leading group
|
|
909
|
-
// serves an entirely different purpose such as `(gh|git)\s+pr merge\b`.
|
|
910
|
-
// In both cases the fix is the same (replace the group with the
|
|
911
|
-
// template's, not widen it with one more alternative), so the two cases
|
|
912
|
-
// share a message instead of the zero-overlap case being misdescribed as
|
|
913
|
-
// "missing" every single alternative.
|
|
914
|
-
function noRecognizableBoundaryMessage(entryLevel, entryName, installedDescription) {
|
|
915
|
-
return (`${entryLevel} "${entryName}"'s bash_match has no recognizable ` +
|
|
916
|
-
`leading boundary alternation (${installedDescription}). ` +
|
|
917
|
-
`The trigger matches no command separator at all, so it only fires ` +
|
|
918
|
-
`for a command that is literally the very first thing in the string, ` +
|
|
919
|
-
`every other position (after \`;\`, \`&\`, a newline, a pipe, an open ` +
|
|
920
|
-
`paren) is silently bypassable. ${triggerBoundaryRehydrationGuidance(entryName)}`);
|
|
921
|
-
}
|
|
922
|
-
export function checkTriggerBoundaryDrift(manifest) {
|
|
923
|
-
const ignored = new Set(manifest.doctor.ignore_template_drift);
|
|
924
|
-
const shipped = shippedBashMatchBoundaries();
|
|
925
|
-
const hooksByName = new Map(manifest.hooks.map((h) => [h.name, h]));
|
|
926
|
-
const policiesByName = new Map(manifest.policies.map((p) => [p.name, p]));
|
|
927
|
-
const diags = [];
|
|
928
|
-
for (const entry of shipped) {
|
|
929
|
-
if (ignored.has(entry.name))
|
|
930
|
-
continue;
|
|
931
|
-
const installedBashMatch = entry.level === "hook"
|
|
932
|
-
? hooksByName.get(entry.name)?.bash_match
|
|
933
|
-
: policiesByName.get(entry.name)?.trigger.bash_match;
|
|
934
|
-
// Not present by that name at that level (missing entirely, or no
|
|
935
|
-
// longer carries a bash_match at all): out of this check's scope,
|
|
936
|
-
// see the "Scope" note above.
|
|
937
|
-
if (installedBashMatch === undefined)
|
|
938
|
-
continue;
|
|
939
|
-
const diagPath = entry.level === "hook" ? "hooks" : "policies";
|
|
940
|
-
const actualBoundary = extractBashMatchBoundary(installedBashMatch);
|
|
941
|
-
if (actualBoundary === undefined) {
|
|
942
|
-
diags.push({
|
|
943
|
-
severity: "error",
|
|
944
|
-
path: diagPath,
|
|
945
|
-
message: noRecognizableBoundaryMessage(entry.level, entry.name, `installed value: "${installedBashMatch}"`),
|
|
946
|
-
});
|
|
947
|
-
continue;
|
|
948
|
-
}
|
|
949
|
-
const templateAlternatives = splitBoundaryAlternatives(entry.boundary);
|
|
950
|
-
const missing = missingBoundaryAlternatives(entry.boundary, actualBoundary);
|
|
951
|
-
if (missing.length === 0)
|
|
952
|
-
continue;
|
|
953
|
-
if (missing.length === templateAlternatives.length) {
|
|
954
|
-
// Zero overlap: the leading group is a syntactically valid
|
|
955
|
-
// parenthesized alternation, but it shares no alternative at all
|
|
956
|
-
// with the template's boundary, so it is not the boundary group
|
|
957
|
-
// (an unrelated command-shape alternation like `(gh|git)` that
|
|
958
|
-
// happens to sit first, or a fully custom, unrelated set). Report
|
|
959
|
-
// it as "no recognizable boundary", not as "missing" every single
|
|
960
|
-
// template alternative, since the fix is to replace this group,
|
|
961
|
-
// not extend it.
|
|
962
|
-
diags.push({
|
|
963
|
-
severity: "error",
|
|
964
|
-
path: diagPath,
|
|
965
|
-
message: noRecognizableBoundaryMessage(entry.level, entry.name, `installed leading group: "(${actualBoundary})", which shares no ` +
|
|
966
|
-
`boundary token with the shipped template's "(${entry.boundary})"`),
|
|
967
|
-
});
|
|
968
|
-
continue;
|
|
969
|
-
}
|
|
970
|
-
const missingList = missing.map((m) => `"${m}"`).join(", ");
|
|
971
|
-
diags.push({
|
|
972
|
-
severity: "error",
|
|
973
|
-
path: diagPath,
|
|
974
|
-
message: `${entry.level} "${entry.name}"'s bash_match boundary is missing ` +
|
|
975
|
-
`${missing.length === 1 ? "an alternative" : "alternatives"} the shipped ` +
|
|
976
|
-
`template has: ${missingList} (installed: "(${actualBoundary})", template: ` +
|
|
977
|
-
`"(${entry.boundary})"). A command that only opens with ` +
|
|
978
|
-
`${missing.length === 1 ? "that boundary token" : "one of those boundary tokens"} ` +
|
|
979
|
-
`(e.g. a backgrounded \`cmd & gh pr merge 1\`) is not matched, so the gate ` +
|
|
980
|
-
`this trigger guards is silently bypassable. ${triggerBoundaryRehydrationGuidance(entry.name)}`,
|
|
981
|
-
});
|
|
982
|
-
}
|
|
983
|
-
return diags;
|
|
984
|
-
}
|
|
985
|
-
// Hook-budget-vs-ledger-timeout margin (task d20a7e0c, follow-up to
|
|
986
|
-
// f1aea826/7bf47554). A blocking (`blocking: "hard"`) hook that consults
|
|
987
|
-
// the evidence ledger before it can write its own decision is bounded
|
|
988
|
-
// TWICE: once by its own `budget_ms` (the runtime's outer kill-timeout —
|
|
989
|
-
// Claude Code and Codex both treat a KILLED hook as ALLOW, never as its
|
|
990
|
-
// own pending verdict) and once by the ledger round-trip it is waiting
|
|
991
|
-
// on. `budget_ms` below `requiredHookBudgetMs(health.timeout_ms)`
|
|
992
|
-
// (src/cli/policy/intercept.ts — see that function's doc comment for the
|
|
993
|
-
// full derivation from `realLedgerClient`'s own two round-trip shapes,
|
|
994
|
-
// INCLUDING the "KNOWN RESIDUAL" paragraph there: this check guarantees
|
|
995
|
-
// delivery on the pure-timeout hang shape only, not on a query() that
|
|
996
|
-
// degrades via a non-timeout ledger error and then hangs on record())
|
|
997
|
-
// means a merely SLOW (not even hard-down) ledger can get the hook
|
|
998
|
-
// killed before its fail-closed `deny` / `deny-degraded` JSON reaches
|
|
999
|
-
// stdout — silently turning the verdict into an unintended allow,
|
|
1000
|
-
// defeating the deny-degraded fix (task f1aea826) on exactly the hang
|
|
1001
|
-
// shape it exists to close.
|
|
1002
|
-
//
|
|
1003
|
-
// Two hook populations are checked, both GENERICALLY — unlike
|
|
1004
|
-
// tests/runtime/hook-budget-ledger-margin.test.ts's pre-d20a7e0c version,
|
|
1005
|
-
// which hand-imported three specific pack modules and pinned a hardcoded
|
|
1006
|
-
// 15000ms floor instead of scaling with the manifest's own
|
|
1007
|
-
// health.timeout_ms:
|
|
1008
|
-
// 1. `manifest.hooks[]` entries that invoke `harness policy intercept`
|
|
1009
|
-
// (recognised via `isPolicyInterceptCommand`, robust to how the
|
|
1010
|
-
// operator or a local build spells the leading token — see that
|
|
1011
|
-
// function's own doc comment for why a verbatim string compare
|
|
1012
|
-
// under-recognises real manifests).
|
|
1013
|
-
// 2. Every hook an ENABLED `manifest.policy_packs[]` entry resolves to,
|
|
1014
|
-
// for every runtime `harness apply` can target (`KNOWN_RUNTIMES`) —
|
|
1015
|
-
// iterating whichever packs the operator actually has enabled
|
|
1016
|
-
// through the shared `resolveBuiltin` registry lookup, not a
|
|
1017
|
-
// hand-maintained list of specific pack modules. Only the subset
|
|
1018
|
-
// whose command names one of the LEDGER_CONSULTING_PACK_SUBCOMMANDS
|
|
1019
|
-
// below is checked: `solution-acceptance` / `solution-acceptance-
|
|
1020
|
-
// writeguard` are DELIBERATELY excluded — they gate on a filesystem
|
|
1021
|
-
// verdict marker the producer writes, never a live ledger
|
|
1022
|
-
// round-trip (see solution-acceptance.ts's own header comment), so
|
|
1023
|
-
// flagging them here would be a false positive.
|
|
1024
|
-
const LEDGER_CONSULTING_PACK_SUBCOMMANDS = [
|
|
1025
|
-
"pack hook branch-protection",
|
|
1026
|
-
"pack hook pre-tool-use",
|
|
1027
|
-
"pack hook codex-pre-tool-use",
|
|
1028
|
-
"pack hook post-merge-gate",
|
|
1029
|
-
];
|
|
1030
|
-
function escapeRegExp(s) {
|
|
1031
|
-
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1032
|
-
}
|
|
1033
|
-
/**
|
|
1034
|
-
* Does `command` invoke `subcommand` as adjacent, whitespace-separated
|
|
1035
|
-
* tokens, regardless of a leading interpreter/env-var prefix or a
|
|
1036
|
-
* trailing flag? Mirrors `isPolicyInterceptCommand`'s own reasoning
|
|
1037
|
-
* (src/cli/policy/intercept.ts) for the pack-hook subcommand names,
|
|
1038
|
-
* which that function does not itself cover (it only recognises `policy
|
|
1039
|
-
* intercept`).
|
|
1040
|
-
*
|
|
1041
|
-
* Trailing-boundary note (review 2026-08-09, fix round 1, finding 3):
|
|
1042
|
-
* `isPolicyInterceptCommand` widened ITS trailing boundary to also accept
|
|
1043
|
-
* a semicolon/quote glued directly onto the subcommand word, because it
|
|
1044
|
-
* classifies OPERATOR-authored `manifest.hooks[]` commands. This
|
|
1045
|
-
* function's callers only ever see the exact, harness-generated pack
|
|
1046
|
-
* command strings (`BLOCKER_COMMAND` constants in the
|
|
1047
|
-
* `src/policy-packs/builtin/*.ts` sources) — a fixed, known set this
|
|
1048
|
-
* codebase controls byte-for-byte — so the narrower whitespace-or-end
|
|
1049
|
-
* boundary here is deliberately left as is rather than mirrored; there is
|
|
1050
|
-
* no operator-authored input on this path for a glued shell metacharacter
|
|
1051
|
-
* to appear in.
|
|
1052
|
-
*/
|
|
1053
|
-
function commandInvokesSubcommand(command, subcommand) {
|
|
1054
|
-
const pattern = subcommand.split(/\s+/).map(escapeRegExp).join("\\s+");
|
|
1055
|
-
return new RegExp(`(?:^|[\\s/\\\\])${pattern}(?:\\s|$)`).test(command);
|
|
1056
|
-
}
|
|
1057
|
-
function isLedgerConsultingPackCommand(command) {
|
|
1058
|
-
return LEDGER_CONSULTING_PACK_SUBCOMMANDS.some((s) => commandInvokesSubcommand(command, s));
|
|
1059
|
-
}
|
|
1060
|
-
// The one pack subcommand among LEDGER_CONSULTING_PACK_SUBCOMMANDS whose
|
|
1061
|
-
// OWN degraded-ledger handling fails OPEN (allow) rather than closed —
|
|
1062
|
-
// see hook-post-merge-gate.ts's explicit "Fail posture: OPEN" header
|
|
1063
|
-
// comment and its `post-merge-gate fails open, allowing` diagnostics.
|
|
1064
|
-
// `branch-protection` / `pre-tool-use` (understanding-before-execution) /
|
|
1065
|
-
// `codex-pre-tool-use` all fail CLOSED absent ledger evidence (a missing
|
|
1066
|
-
// or degraded query reads as "no evidence", which those hooks block or
|
|
1067
|
-
// ask on, not allow). Tracked separately so the diagnostic below can
|
|
1068
|
-
// stop attributing a fail-closed verdict this hook never produces to it
|
|
1069
|
-
// (review 2026-08-09, fix round 1, finding 2).
|
|
1070
|
-
const FAIL_OPEN_ON_DEGRADED_PACK_SUBCOMMAND = "pack hook post-merge-gate";
|
|
1071
|
-
function collectLedgerConsultingBlockingHooks(manifest) {
|
|
1072
|
-
const direct = manifest.hooks
|
|
1073
|
-
.filter((h) => h.blocking === "hard" && isPolicyInterceptCommand(h.command))
|
|
1074
|
-
.map((hook) => ({ hook, isPolicyInterceptHook: true, isFailOpenOnDegraded: false }));
|
|
1075
|
-
const fromPacks = [];
|
|
1076
|
-
for (const pack of manifest.policy_packs) {
|
|
1077
|
-
if (!pack.enabled)
|
|
1078
|
-
continue;
|
|
1079
|
-
for (const runtime of KNOWN_RUNTIMES) {
|
|
1080
|
-
const resolved = resolveBuiltin(pack, runtime);
|
|
1081
|
-
// Unresolvable packs (unknown source / unknown builtin name) are
|
|
1082
|
-
// already flagged separately by checkPolicyPacks; nothing to
|
|
1083
|
-
// classify here.
|
|
1084
|
-
if (!resolved)
|
|
1085
|
-
continue;
|
|
1086
|
-
for (const hook of resolved.contribution.hooks) {
|
|
1087
|
-
if (hook.blocking === "hard" && isLedgerConsultingPackCommand(hook.command)) {
|
|
1088
|
-
fromPacks.push({
|
|
1089
|
-
hook,
|
|
1090
|
-
isPolicyInterceptHook: false,
|
|
1091
|
-
isFailOpenOnDegraded: commandInvokesSubcommand(hook.command, FAIL_OPEN_ON_DEGRADED_PACK_SUBCOMMAND),
|
|
1092
|
-
});
|
|
1093
|
-
}
|
|
1094
|
-
}
|
|
1095
|
-
}
|
|
1096
|
-
}
|
|
1097
|
-
return [...direct, ...fromPacks];
|
|
1098
|
-
}
|
|
1099
|
-
export function checkHookBudgetLedgerMargin(manifest) {
|
|
1100
|
-
const grounding = manifest.tools.mcp.find((m) => m.name === "grounding-mcp" && m.enabled !== false);
|
|
1101
|
-
// No wired producer: `harness policy intercept` falls back to the
|
|
1102
|
-
// instant `degradedLedgerClient` (no subprocess, no wait), and a pack
|
|
1103
|
-
// blocker with no grounding-mcp entry to query is a separate,
|
|
1104
|
-
// already-reported misconfiguration (checkPolicyGroundingMcp). No live
|
|
1105
|
-
// ledger round-trip exists here for a margin to protect.
|
|
1106
|
-
if (!grounding)
|
|
1107
|
-
return [];
|
|
1108
|
-
const ledgerTimeoutMs = grounding.health?.timeout_ms ?? 5000;
|
|
1109
|
-
const required = requiredHookBudgetMs(ledgerTimeoutMs);
|
|
1110
|
-
const seen = new Set();
|
|
1111
|
-
const diags = [];
|
|
1112
|
-
for (const entry of collectLedgerConsultingBlockingHooks(manifest)) {
|
|
1113
|
-
const { hook, isPolicyInterceptHook, isFailOpenOnDegraded } = entry;
|
|
1114
|
-
// Both KNOWN_RUNTIMES resolutions of an enabled pack commonly yield a
|
|
1115
|
-
// hook with the same (name, budget_ms) pair — only the match/command
|
|
1116
|
-
// wording differs per runtime. De-dupe so one misconfigured budget
|
|
1117
|
-
// is reported once, not twice.
|
|
1118
|
-
const key = `${hook.name}:${hook.budget_ms}`;
|
|
1119
|
-
if (seen.has(key))
|
|
1120
|
-
continue;
|
|
1121
|
-
seen.add(key);
|
|
1122
|
-
if (hook.budget_ms >= required)
|
|
1123
|
-
continue;
|
|
1124
|
-
const preamble = `hook "${hook.name}" carries budget_ms=${hook.budget_ms}, below the ${required}ms this ` +
|
|
1125
|
-
`manifest's grounding-mcp health.timeout_ms=${ledgerTimeoutMs}ms requires (2×timeout_ms ` +
|
|
1126
|
-
`+ 3× the deny-degraded audit-retry budget — see requiredHookBudgetMs in ` +
|
|
1127
|
-
`src/cli/policy/intercept.ts for the derivation`;
|
|
1128
|
-
let message;
|
|
1129
|
-
if (isPolicyInterceptHook) {
|
|
1130
|
-
message =
|
|
1131
|
-
`${preamble}, INCLUDING that function's "KNOWN RESIDUAL" paragraph: clearing ${required}ms ` +
|
|
1132
|
-
`only guarantees the fail-closed verdict on the pure-timeout hang shape, not on a ledger ` +
|
|
1133
|
-
`query that errors non-timeout and then hangs on the audit write). A merely SLOW (not even ` +
|
|
1134
|
-
`hard-down) ledger can get this blocking hook killed by the runtime's outer hook timeout ` +
|
|
1135
|
-
`before its fail-closed deny JSON reaches stdout — both Claude Code and Codex then read the ` +
|
|
1136
|
-
`kill as allow, defeating the deny-degraded fix (task f1aea826) on exactly this hang shape. ` +
|
|
1137
|
-
`Raise budget_ms to at least ${required}, or lower tools.mcp.grounding-mcp.health.timeout_ms ` +
|
|
1138
|
-
`(which lowers this requirement too, at the cost of a stricter ledger-latency budget); see ` +
|
|
1139
|
-
`docs/okf/gate-fail-posture-matrix.md.`;
|
|
1140
|
-
}
|
|
1141
|
-
else {
|
|
1142
|
-
// Pack-contributed blocker: only queries the ledger (no ledger_add,
|
|
1143
|
-
// no deny-degraded audit retry of its own), so the 2T+3R floor
|
|
1144
|
-
// above is a deliberately conservative carryover from the direct
|
|
1145
|
-
// `harness policy intercept` threshold, not this hook's own
|
|
1146
|
-
// derived worst case (which is bounded at up to 2×timeout_ms for
|
|
1147
|
-
// the query alone) — kept uniform rather than a separately-tested,
|
|
1148
|
-
// lower per-kind bound (review 2026-08-09, fix round 1, finding 2).
|
|
1149
|
-
const consequence = isFailOpenOnDegraded
|
|
1150
|
-
? `this hook's OWN decision on a degraded or unreachable ledger is to fail OPEN (allow), ` +
|
|
1151
|
-
`not deny — see hook-post-merge-gate.ts's "Fail posture: OPEN" note — so a hook killed by ` +
|
|
1152
|
-
`the runtime's outer timeout here reaches the same allow outcome its own degraded-handling ` +
|
|
1153
|
-
`would already choose. It is still flagged here so a merely SLOW (not even hard-down) ` +
|
|
1154
|
-
`ledger cannot needlessly stall the gate up to its outer timeout, not because a fail-closed ` +
|
|
1155
|
-
`verdict is at risk`
|
|
1156
|
-
: `a merely SLOW (not even hard-down) ledger can still get this hook killed by the runtime's ` +
|
|
1157
|
-
`outer hook timeout before it can even complete that query, defeating its own fail-closed ` +
|
|
1158
|
-
`default on exactly this hang shape`;
|
|
1159
|
-
message =
|
|
1160
|
-
`${preamble}). This pack-contributed hook only QUERIES the ledger (queryLedgerByTag: open ` +
|
|
1161
|
-
`session, one querySummary, dispose) — it never calls ledger_add, so it has no ` +
|
|
1162
|
-
`deny-degraded audit-retry step of its own; ${consequence}. Raise budget_ms to at least ` +
|
|
1163
|
-
`${required}, or lower tools.mcp.grounding-mcp.health.timeout_ms (which lowers this ` +
|
|
1164
|
-
`requirement too, at the cost of a stricter ledger-latency budget); see ` +
|
|
1165
|
-
`docs/okf/gate-fail-posture-matrix.md.`;
|
|
1166
|
-
}
|
|
1167
|
-
diags.push({
|
|
1168
|
-
severity: "error",
|
|
1169
|
-
path: `hooks[${hook.name}].budget_ms`,
|
|
1170
|
-
message,
|
|
1171
|
-
});
|
|
1172
|
-
}
|
|
1173
|
-
return diags;
|
|
1174
|
-
}
|
|
1175
215
|
// Phase 6 #2: surface pack-resolution problems at lint time, not at
|
|
1176
216
|
// `harness apply` time. Delegates to the shared `checkPolicyPackSources`
|
|
1177
217
|
// so the apply path (which now also fails loudly on these conditions)
|
|
@@ -1187,8 +227,8 @@ function checkPolicyPacks(manifest) {
|
|
|
1187
227
|
// Phase 6 follow-up (task d78fb3c7): per-pack `config:` shape check.
|
|
1188
228
|
// Each builtin pack registers a zod `configSchema` consumed via
|
|
1189
229
|
// `checkPolicyPackConfigs`; this turns the strict-mode issues into
|
|
1190
|
-
// validate Diagnostics so typo'd keys (`
|
|
1191
|
-
//
|
|
230
|
+
// validate Diagnostics so typo'd keys (`protected_brnches`) and bad
|
|
231
|
+
// values (`protected_branches: "master"`) fail loud at lint time. Runs
|
|
1192
232
|
// AFTER the source / name check above; an unknown pack name has no
|
|
1193
233
|
// registered schema and would be skipped silently here even without
|
|
1194
234
|
// the source check, but emitting both diagnostics in one run is the
|
|
@@ -1205,83 +245,6 @@ function checkPolicyPackConfigsAsDiagnostics(manifest) {
|
|
|
1205
245
|
};
|
|
1206
246
|
});
|
|
1207
247
|
}
|
|
1208
|
-
// Slice 2 AC 3 (docs/decisions/2026-08-27-ug-auto-mode-approval.md): every
|
|
1209
|
-
// literal in `understanding-before-execution`'s `auto_approve.when` must be
|
|
1210
|
-
// backed by a checked-in dogfood fixture that shows a LISTED harness
|
|
1211
|
-
// emitting that exact `permission_mode` value; see
|
|
1212
|
-
// `src/policy-packs/builtin/understanding-before-execution/measured-permission-modes.ts`
|
|
1213
|
-
// for the registry and the sync test that keeps it honest against the
|
|
1214
|
-
// fixtures. `auto_approve.harnesses` (round-2 review finding) scopes WHICH
|
|
1215
|
-
// harnesses' evidence counts: a literal measured only for Claude Code is
|
|
1216
|
-
// not evidence Codex ever emits it, so this check is per-listed-harness,
|
|
1217
|
-
// not "any harness anywhere". Runs independently of
|
|
1218
|
-
// `checkPolicyPackConfigsAsDiagnostics` (the zod schema check above
|
|
1219
|
-
// already reports shape errors for a malformed `auto_approve` block): it
|
|
1220
|
-
// only inspects `when` when it is an array whose entries are all strings,
|
|
1221
|
-
// it leaves empty-string entries to the schema check (`min(1)`), and it
|
|
1222
|
-
// leaves a malformed `harnesses` (not an array, non-string/unknown/
|
|
1223
|
-
// duplicate entries) to the same schema check rather than reporting from
|
|
1224
|
-
// here, so the two never report the same literal twice and this check
|
|
1225
|
-
// never throws on a malformed config. Disabled packs (`enabled: false`)
|
|
1226
|
-
// are skipped on purpose, the same way `checkPolicyPacks` skips them: a
|
|
1227
|
-
// pack that contributes no hooks has no allowlist to enforce.
|
|
1228
|
-
function listedAutoApproveHarnesses(autoApprove) {
|
|
1229
|
-
const raw = autoApprove["harnesses"];
|
|
1230
|
-
if (raw === undefined) {
|
|
1231
|
-
return [...DEFAULT_AUTO_APPROVE_HARNESSES];
|
|
1232
|
-
}
|
|
1233
|
-
if (!Array.isArray(raw) || raw.length === 0)
|
|
1234
|
-
return null;
|
|
1235
|
-
const harnesses = [];
|
|
1236
|
-
for (const entry of raw) {
|
|
1237
|
-
if (typeof entry !== "string" ||
|
|
1238
|
-
!AUTO_APPROVE_HARNESS_VALUES.includes(entry) ||
|
|
1239
|
-
harnesses.includes(entry)) {
|
|
1240
|
-
return null;
|
|
1241
|
-
}
|
|
1242
|
-
harnesses.push(entry);
|
|
1243
|
-
}
|
|
1244
|
-
return harnesses;
|
|
1245
|
-
}
|
|
1246
|
-
function checkUnderstandingBeforeExecutionAutoApproveMeasured(manifest) {
|
|
1247
|
-
const diags = [];
|
|
1248
|
-
manifest.policy_packs.forEach((pack, packIndex) => {
|
|
1249
|
-
if (!pack.enabled)
|
|
1250
|
-
return;
|
|
1251
|
-
if (pack.name !== UNDERSTANDING_BEFORE_EXECUTION_PACK_NAME)
|
|
1252
|
-
return;
|
|
1253
|
-
const config = pack.config;
|
|
1254
|
-
const autoApprove = config?.["auto_approve"];
|
|
1255
|
-
if (typeof autoApprove !== "object" || autoApprove === null)
|
|
1256
|
-
return;
|
|
1257
|
-
const autoApproveObj = autoApprove;
|
|
1258
|
-
const when = autoApproveObj["when"];
|
|
1259
|
-
if (!Array.isArray(when))
|
|
1260
|
-
return;
|
|
1261
|
-
if (!when.every((literal) => typeof literal === "string"))
|
|
1262
|
-
return;
|
|
1263
|
-
const listedHarnesses = listedAutoApproveHarnesses(autoApproveObj);
|
|
1264
|
-
if (listedHarnesses === null)
|
|
1265
|
-
return;
|
|
1266
|
-
when.forEach((literal, literalIndex) => {
|
|
1267
|
-
if (literal.length === 0)
|
|
1268
|
-
return;
|
|
1269
|
-
if (listedHarnesses.some((harness) => isMeasuredPermissionModeFor(literal, harness)))
|
|
1270
|
-
return;
|
|
1271
|
-
const measuredParts = listedHarnesses
|
|
1272
|
-
.map((harness) => `measured for ${harness}: ${measuredPermissionModeLiteralsFor(harness).join(", ")}`)
|
|
1273
|
-
.join("; ");
|
|
1274
|
-
diags.push({
|
|
1275
|
-
severity: "error",
|
|
1276
|
-
path: `policy_packs[${packIndex}].config.auto_approve.when[${literalIndex}]`,
|
|
1277
|
-
message: `auto_approve.when literal "${literal}" has no checked-in dogfood fixture showing ` +
|
|
1278
|
-
`${listedHarnesses.join(" or ")} emitting it (${measuredParts}); see ` +
|
|
1279
|
-
`docs/decisions/2026-08-27-ug-auto-mode-approval.md, Slice 2`,
|
|
1280
|
-
});
|
|
1281
|
-
});
|
|
1282
|
-
});
|
|
1283
|
-
return diags;
|
|
1284
|
-
}
|
|
1285
248
|
export function runAssetChecks(manifest, opts = {}) {
|
|
1286
249
|
const home = opts.homeDir ?? os.homedir();
|
|
1287
250
|
return [
|
|
@@ -1290,17 +253,8 @@ export function runAssetChecks(manifest, opts = {}) {
|
|
|
1290
253
|
...checkSkills(manifest, home),
|
|
1291
254
|
...checkHooks(manifest, home),
|
|
1292
255
|
...checkBuiltinDrift(manifest, opts),
|
|
1293
|
-
...checkPolicyGroundingMcp(manifest),
|
|
1294
|
-
...checkSolutionAcceptanceProducer(manifest),
|
|
1295
|
-
...checkSolutionAcceptanceKnobIgnored(manifest, opts.gitIgnoreProbe ?? createDefaultGitIgnoreProbe()),
|
|
1296
256
|
...checkPolicyPacks(manifest),
|
|
1297
257
|
...checkPolicyPackConfigsAsDiagnostics(manifest),
|
|
1298
|
-
...checkUnderstandingBeforeExecutionAutoApproveMeasured(manifest),
|
|
1299
|
-
...checkPolicyRiskWithoutEnvScope(manifest),
|
|
1300
|
-
...checkSafeDeletionRootsSyntax(manifest),
|
|
1301
|
-
...checkPolicySelfAttestation(manifest),
|
|
1302
|
-
...checkHookBudgetLedgerMargin(manifest),
|
|
1303
|
-
...checkWorkflows(manifest),
|
|
1304
258
|
];
|
|
1305
259
|
}
|
|
1306
260
|
export const __testables = {
|