approval-md 0.2.0 → 0.4.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/README.md +85 -877
- package/SPEC.md +150 -19
- package/cli.js +66 -7
- package/dist/src/adapters/smtp.d.ts +18 -0
- package/dist/src/adapters/smtp.js +24 -3
- package/dist/src/adapters/smtp.js.map +1 -1
- package/dist/src/channels/contract.d.ts +53 -1
- package/dist/src/channels/contract.js +304 -7
- package/dist/src/channels/contract.js.map +1 -1
- package/dist/src/channels/relay-server.d.ts +90 -0
- package/dist/src/channels/relay-server.js +295 -0
- package/dist/src/channels/relay-server.js.map +1 -0
- package/dist/src/channels/relay.d.ts +273 -0
- package/dist/src/channels/relay.js +564 -0
- package/dist/src/channels/relay.js.map +1 -0
- package/dist/src/channels/tagging.js +6 -1
- package/dist/src/channels/tagging.js.map +1 -1
- package/dist/src/channels/telegram-webhook.d.ts +273 -0
- package/dist/src/channels/telegram-webhook.js +536 -0
- package/dist/src/channels/telegram-webhook.js.map +1 -0
- package/dist/src/channels/telegram.d.ts +394 -12
- package/dist/src/channels/telegram.js +621 -68
- package/dist/src/channels/telegram.js.map +1 -1
- package/dist/src/channels/web.d.ts +9 -0
- package/dist/src/channels/web.js +17 -0
- package/dist/src/channels/web.js.map +1 -1
- package/dist/src/cli/amend.js +564 -34
- package/dist/src/cli/amend.js.map +1 -1
- package/dist/src/cli/attest.d.ts +9 -0
- package/dist/src/cli/attest.js +177 -13
- package/dist/src/cli/attest.js.map +1 -1
- package/dist/src/cli/channel-relay.d.ts +108 -0
- package/dist/src/cli/channel-relay.js +334 -0
- package/dist/src/cli/channel-relay.js.map +1 -0
- package/dist/src/cli/channel-telegram-webhook.d.ts +273 -0
- package/dist/src/cli/channel-telegram-webhook.js +709 -0
- package/dist/src/cli/channel-telegram-webhook.js.map +1 -0
- package/dist/src/cli/channel-telegram.d.ts +347 -28
- package/dist/src/cli/channel-telegram.js +763 -28
- package/dist/src/cli/channel-telegram.js.map +1 -1
- package/dist/src/cli/channel.d.ts +10 -1
- package/dist/src/cli/channel.js +14 -1
- package/dist/src/cli/channel.js.map +1 -1
- package/dist/src/cli/codex-bridge.d.ts +819 -0
- package/dist/src/cli/codex-bridge.js +1607 -0
- package/dist/src/cli/codex-bridge.js.map +1 -0
- package/dist/src/cli/codex.d.ts +1 -1
- package/dist/src/cli/codex.js +304 -7
- package/dist/src/cli/codex.js.map +1 -1
- package/dist/src/cli/daemon.js +41 -6
- package/dist/src/cli/daemon.js.map +1 -1
- package/dist/src/cli/doctor.d.ts +63 -0
- package/dist/src/cli/doctor.js +1322 -122
- package/dist/src/cli/doctor.js.map +1 -1
- package/dist/src/cli/execute.d.ts +17 -1
- package/dist/src/cli/execute.js +366 -22
- package/dist/src/cli/execute.js.map +1 -1
- package/dist/src/cli/exit-codes.d.ts +12 -0
- package/dist/src/cli/exit-codes.js +13 -0
- package/dist/src/cli/exit-codes.js.map +1 -1
- package/dist/src/cli/gate.d.ts +2 -0
- package/dist/src/cli/gate.js +233 -2
- package/dist/src/cli/gate.js.map +1 -1
- package/dist/src/cli/help.d.ts +14 -2
- package/dist/src/cli/help.js +395 -82
- package/dist/src/cli/help.js.map +1 -1
- package/dist/src/cli/hook-codex.d.ts +49 -1
- package/dist/src/cli/hook-codex.js +60 -1
- package/dist/src/cli/hook-codex.js.map +1 -1
- package/dist/src/cli/hook.d.ts +828 -4
- package/dist/src/cli/hook.js +2972 -142
- package/dist/src/cli/hook.js.map +1 -1
- package/dist/src/cli/import.js +1 -1
- package/dist/src/cli/import.js.map +1 -1
- package/dist/src/cli/init.js +16 -7
- package/dist/src/cli/init.js.map +1 -1
- package/dist/src/cli/instructions.js +24 -3
- package/dist/src/cli/instructions.js.map +1 -1
- package/dist/src/cli/log-anchor.d.ts +55 -0
- package/dist/src/cli/log-anchor.js +199 -60
- package/dist/src/cli/log-anchor.js.map +1 -1
- package/dist/src/cli/main.d.ts +0 -12
- package/dist/src/cli/main.js +56 -3
- package/dist/src/cli/main.js.map +1 -1
- package/dist/src/cli/muse.d.ts +2 -0
- package/dist/src/cli/muse.js +62 -0
- package/dist/src/cli/muse.js.map +1 -0
- package/dist/src/cli/payload.js +79 -1
- package/dist/src/cli/payload.js.map +1 -1
- package/dist/src/cli/policy-apply.d.ts +195 -0
- package/dist/src/cli/policy-apply.js +573 -0
- package/dist/src/cli/policy-apply.js.map +1 -0
- package/dist/src/cli/policy.js +14 -1
- package/dist/src/cli/policy.js.map +1 -1
- package/dist/src/cli/preflight.d.ts +151 -13
- package/dist/src/cli/preflight.js +398 -41
- package/dist/src/cli/preflight.js.map +1 -1
- package/dist/src/cli/sandbox.js +17 -1
- package/dist/src/cli/sandbox.js.map +1 -1
- package/dist/src/cli/scaffold.d.ts +1 -1
- package/dist/src/cli/scaffold.js +7 -1
- package/dist/src/cli/scaffold.js.map +1 -1
- package/dist/src/cli/serve.d.ts +53 -0
- package/dist/src/cli/serve.js +270 -0
- package/dist/src/cli/serve.js.map +1 -0
- package/dist/src/cli/setup-channel.d.ts +9 -0
- package/dist/src/cli/setup-channel.js +28 -1
- package/dist/src/cli/setup-channel.js.map +1 -1
- package/dist/src/cli/setup-common.d.ts +3 -1
- package/dist/src/cli/setup-common.js +3 -2
- package/dist/src/cli/setup-common.js.map +1 -1
- package/dist/src/cli/setup.d.ts +2 -0
- package/dist/src/cli/setup.js +94 -2
- package/dist/src/cli/setup.js.map +1 -1
- package/dist/src/cli/up.js +134 -51
- package/dist/src/cli/up.js.map +1 -1
- package/dist/src/cli/values.js +3 -4
- package/dist/src/cli/values.js.map +1 -1
- package/dist/src/cli/verb-registry.d.ts +2 -0
- package/dist/src/cli/verb-registry.js +510 -47
- package/dist/src/cli/verb-registry.js.map +1 -1
- package/dist/src/cli/wordmark.d.ts +2 -2
- package/dist/src/cli/wordmark.js +2 -2
- package/dist/src/codex/broker.d.ts +229 -0
- package/dist/src/codex/broker.js +548 -0
- package/dist/src/codex/broker.js.map +1 -0
- package/dist/src/codex/runner.d.ts +178 -0
- package/dist/src/codex/runner.js +231 -0
- package/dist/src/codex/runner.js.map +1 -0
- package/dist/src/codex/serve.d.ts +56 -0
- package/dist/src/codex/serve.js +98 -0
- package/dist/src/codex/serve.js.map +1 -0
- package/dist/src/codex/workspace-commit.d.ts +219 -0
- package/dist/src/codex/workspace-commit.js +549 -0
- package/dist/src/codex/workspace-commit.js.map +1 -0
- package/dist/src/core/advance-cycle.d.ts +51 -0
- package/dist/src/core/advance-cycle.js +66 -2
- package/dist/src/core/advance-cycle.js.map +1 -1
- package/dist/src/core/agents-md.d.ts +20 -18
- package/dist/src/core/agents-md.js +33 -31
- package/dist/src/core/agents-md.js.map +1 -1
- package/dist/src/core/attest-bootstrap.d.ts +95 -0
- package/dist/src/core/attest-bootstrap.js +135 -0
- package/dist/src/core/attest-bootstrap.js.map +1 -0
- package/dist/src/core/attest.d.ts +234 -2
- package/dist/src/core/attest.js +342 -7
- package/dist/src/core/attest.js.map +1 -1
- package/dist/src/core/audit.d.ts +73 -3
- package/dist/src/core/audit.js +65 -5
- package/dist/src/core/audit.js.map +1 -1
- package/dist/src/core/channel-lease.d.ts +322 -0
- package/dist/src/core/channel-lease.js +663 -0
- package/dist/src/core/channel-lease.js.map +1 -0
- package/dist/src/core/channel-owner.d.ts +213 -0
- package/dist/src/core/channel-owner.js +358 -0
- package/dist/src/core/channel-owner.js.map +1 -0
- package/dist/src/core/command-class.d.ts +205 -0
- package/dist/src/core/command-class.js +1433 -28
- package/dist/src/core/command-class.js.map +1 -1
- package/dist/src/core/commit-guard.d.ts +272 -0
- package/dist/src/core/commit-guard.js +424 -0
- package/dist/src/core/commit-guard.js.map +1 -0
- package/dist/src/core/daemon-actor.d.ts +45 -0
- package/dist/src/core/daemon-actor.js +54 -0
- package/dist/src/core/daemon-actor.js.map +1 -0
- package/dist/src/core/daemon-host.d.ts +178 -0
- package/dist/src/core/daemon-host.js +228 -0
- package/dist/src/core/daemon-host.js.map +1 -0
- package/dist/src/core/daemon-identity.d.ts +238 -0
- package/dist/src/core/daemon-identity.js +252 -0
- package/dist/src/core/daemon-identity.js.map +1 -0
- package/dist/src/core/dark-session.d.ts +109 -8
- package/dist/src/core/dark-session.js +266 -82
- package/dist/src/core/dark-session.js.map +1 -1
- package/dist/src/core/decision-refusal.d.ts +23 -2
- package/dist/src/core/decision-refusal.js +24 -2
- package/dist/src/core/decision-refusal.js.map +1 -1
- package/dist/src/core/env-file.d.ts +5 -0
- package/dist/src/core/env-file.js +60 -1
- package/dist/src/core/env-file.js.map +1 -1
- package/dist/src/core/execute.d.ts +15 -2
- package/dist/src/core/execute.js +15 -2
- package/dist/src/core/execute.js.map +1 -1
- package/dist/src/core/gate.d.ts +392 -6
- package/dist/src/core/gate.js +852 -24
- package/dist/src/core/gate.js.map +1 -1
- package/dist/src/core/gesture-refusal.d.ts +166 -0
- package/dist/src/core/gesture-refusal.js +188 -0
- package/dist/src/core/gesture-refusal.js.map +1 -0
- package/dist/src/core/harness-version.d.ts +128 -4
- package/dist/src/core/harness-version.js +181 -12
- package/dist/src/core/harness-version.js.map +1 -1
- package/dist/src/core/harness-wait.d.ts +82 -3
- package/dist/src/core/harness-wait.js +89 -3
- package/dist/src/core/harness-wait.js.map +1 -1
- package/dist/src/core/instance.d.ts +59 -2
- package/dist/src/core/instance.js +113 -0
- package/dist/src/core/instance.js.map +1 -1
- package/dist/src/core/log-subscribe.d.ts +21 -0
- package/dist/src/core/log-subscribe.js +16 -6
- package/dist/src/core/log-subscribe.js.map +1 -1
- package/dist/src/core/log-write-layer.d.ts +55 -0
- package/dist/src/core/log-write-layer.js +91 -0
- package/dist/src/core/log-write-layer.js.map +1 -0
- package/dist/src/core/log.d.ts +89 -22
- package/dist/src/core/log.js +197 -19
- package/dist/src/core/log.js.map +1 -1
- package/dist/src/core/payload-census.d.ts +51 -0
- package/dist/src/core/payload-census.js +125 -2
- package/dist/src/core/payload-census.js.map +1 -1
- package/dist/src/core/payload-store.d.ts +31 -0
- package/dist/src/core/payload-store.js +113 -8
- package/dist/src/core/payload-store.js.map +1 -1
- package/dist/src/core/payload.d.ts +18 -3
- package/dist/src/core/payload.js +20 -4
- package/dist/src/core/payload.js.map +1 -1
- package/dist/src/core/policy-diff.d.ts +10 -0
- package/dist/src/core/policy-diff.js +10 -2
- package/dist/src/core/policy-diff.js.map +1 -1
- package/dist/src/core/policy-explain.d.ts +17 -1
- package/dist/src/core/policy-explain.js +48 -1
- package/dist/src/core/policy-explain.js.map +1 -1
- package/dist/src/core/policy-load.d.ts +65 -1
- package/dist/src/core/policy-load.js +21 -3
- package/dist/src/core/policy-load.js.map +1 -1
- package/dist/src/core/policy-match.d.ts +81 -0
- package/dist/src/core/policy-match.js +87 -0
- package/dist/src/core/policy-match.js.map +1 -1
- package/dist/src/core/policy-proposal.d.ts +75 -0
- package/dist/src/core/policy-proposal.js +110 -4
- package/dist/src/core/policy-proposal.js.map +1 -1
- package/dist/src/core/protected-path-guard.d.ts +117 -4
- package/dist/src/core/protected-path-guard.js +362 -48
- package/dist/src/core/protected-path-guard.js.map +1 -1
- package/dist/src/core/question-preempted.d.ts +141 -0
- package/dist/src/core/question-preempted.js +152 -0
- package/dist/src/core/question-preempted.js.map +1 -0
- package/dist/src/core/read-scope.d.ts +182 -0
- package/dist/src/core/read-scope.js +256 -0
- package/dist/src/core/read-scope.js.map +1 -0
- package/dist/src/core/run-payload.d.ts +160 -0
- package/dist/src/core/run-payload.js +304 -0
- package/dist/src/core/run-payload.js.map +1 -0
- package/dist/src/core/sandbox.d.ts +81 -0
- package/dist/src/core/sandbox.js +190 -1
- package/dist/src/core/sandbox.js.map +1 -1
- package/dist/src/core/sender-identity.d.ts +501 -0
- package/dist/src/core/sender-identity.js +609 -0
- package/dist/src/core/sender-identity.js.map +1 -0
- package/dist/src/core/shlex.d.ts +102 -0
- package/dist/src/core/shlex.js +159 -0
- package/dist/src/core/shlex.js.map +1 -0
- package/dist/src/core/state.d.ts +52 -1
- package/dist/src/core/state.js +52 -6
- package/dist/src/core/state.js.map +1 -1
- package/dist/src/core/telegram-config.d.ts +79 -0
- package/dist/src/core/telegram-config.js +108 -0
- package/dist/src/core/telegram-config.js.map +1 -1
- package/dist/src/core/values.d.ts +18 -8
- package/dist/src/core/values.js +36 -1
- package/dist/src/core/values.js.map +1 -1
- package/dist/src/core/verify.d.ts +23 -1
- package/dist/src/core/verify.js +20 -2
- package/dist/src/core/verify.js.map +1 -1
- package/dist/src/daemon/advance.d.ts +10 -0
- package/dist/src/daemon/advance.js +25 -4
- package/dist/src/daemon/advance.js.map +1 -1
- package/dist/src/daemon/audit.d.ts +80 -5
- package/dist/src/daemon/audit.js +126 -6
- package/dist/src/daemon/audit.js.map +1 -1
- package/dist/src/daemon/daemon.d.ts +174 -2
- package/dist/src/daemon/daemon.js +307 -7
- package/dist/src/daemon/daemon.js.map +1 -1
- package/dist/src/daemon/git-evidence.d.ts +2 -2
- package/dist/src/daemon/git-evidence.js +1 -1
- package/dist/src/daemon/projection.d.ts +13 -2
- package/dist/src/daemon/projection.js +39 -6
- package/dist/src/daemon/projection.js.map +1 -1
- package/dist/src/mcp/server.d.ts +39 -3
- package/dist/src/mcp/server.js +30 -3
- package/dist/src/mcp/server.js.map +1 -1
- package/dist/src/muse/server.d.ts +16 -0
- package/dist/src/muse/server.js +238 -0
- package/dist/src/muse/server.js.map +1 -0
- package/dist/src/serve/archive.d.ts +158 -0
- package/dist/src/serve/archive.js +354 -0
- package/dist/src/serve/archive.js.map +1 -0
- package/dist/src/serve/arguments.d.ts +93 -0
- package/dist/src/serve/arguments.js +228 -0
- package/dist/src/serve/arguments.js.map +1 -0
- package/dist/src/serve/credentials.d.ts +91 -0
- package/dist/src/serve/credentials.js +143 -0
- package/dist/src/serve/credentials.js.map +1 -0
- package/dist/src/serve/follow.d.ts +86 -0
- package/dist/src/serve/follow.js +101 -0
- package/dist/src/serve/follow.js.map +1 -0
- package/dist/src/serve/hook-thread.d.ts +134 -0
- package/dist/src/serve/hook-thread.js +333 -0
- package/dist/src/serve/hook-thread.js.map +1 -0
- package/dist/src/serve/hook-worker.d.ts +134 -0
- package/dist/src/serve/hook-worker.js +173 -0
- package/dist/src/serve/hook-worker.js.map +1 -0
- package/dist/src/serve/server.d.ts +412 -0
- package/dist/src/serve/server.js +1251 -0
- package/dist/src/serve/server.js.map +1 -0
- package/docs/README-extended.md +766 -0
- package/docs/cli-reference.md +2563 -73
- package/docs/codex-enforced-session.md +75 -2
- package/docs/codex-workspace-broker.md +118 -0
- package/package.json +6 -2
- package/schema/event.schema.json +595 -9
- package/schema/fixtures/event/invalid/approval-granted-sender-hashed-false.json +20 -0
- package/schema/fixtures/event/invalid/approval-granted-sender-hashed-raw-id.json +20 -0
- package/schema/fixtures/event/invalid/audit-gesture-refused-human-actor.json +16 -0
- package/schema/fixtures/event/invalid/audit-gesture-refused-no-actor-no-sender.json +15 -0
- package/schema/fixtures/event/invalid/audit-gesture-refused-unknown-gesture.json +16 -0
- package/schema/fixtures/event/invalid/audit-question-preempted-agent-actor.json +16 -0
- package/schema/fixtures/event/invalid/audit-question-preempted-no-question-id.json +16 -0
- package/schema/fixtures/event/invalid/audit-question-preempted-unknown-source.json +15 -0
- package/schema/fixtures/event/invalid/daemon-id-malformed.json +15 -0
- package/schema/fixtures/event/invalid/execution-started-truncated-policy-sha256.json +18 -0
- package/schema/fixtures/event/invalid/gate-path-signed-off-absolute-path.json +14 -0
- package/schema/fixtures/event/invalid/gate-path-signed-off-agent-actor.json +14 -0
- package/schema/fixtures/event/invalid/gate-path-signed-off-missing-path.json +13 -0
- package/schema/fixtures/event/invalid/harness-cap-at-margin.json +16 -0
- package/schema/fixtures/event/invalid/harness-cap-fractional.json +16 -0
- package/schema/fixtures/event/invalid/harness-cap-negative.json +16 -0
- package/schema/fixtures/event/invalid/harness-cap-string.json +16 -0
- package/schema/fixtures/event/invalid/harness-cap-without-harness-execution.json +16 -0
- package/schema/fixtures/event/invalid/harness-cap-zero.json +16 -0
- package/schema/fixtures/event/valid/approval-granted-sender-hashed.json +20 -0
- package/schema/fixtures/event/valid/approval-requested-harness-cap.json +18 -0
- package/schema/fixtures/event/valid/audit-gesture-refused-review-note.json +21 -0
- package/schema/fixtures/event/valid/audit-gesture-refused-sender-key-unavailable.json +19 -0
- package/schema/fixtures/event/valid/audit-gesture-refused.json +19 -0
- package/schema/fixtures/event/valid/audit-question-preempted-no-verdict.json +16 -0
- package/schema/fixtures/event/valid/audit-question-preempted.json +20 -0
- package/schema/fixtures/event/valid/daemon-identity-declared.json +17 -0
- package/schema/fixtures/event/valid/daemon-identity.json +15 -0
- package/schema/fixtures/event/valid/execution-started-policy-sha256.json +18 -0
- package/schema/fixtures/event/valid/gate-path-signed-off.json +14 -0
- package/schema/fixtures/event/valid/harness-kind-claude-code.json +23 -0
- package/schema/fixtures/event/valid/harness-kind-codex.json +23 -0
- package/schema/fixtures/event/valid/harness-kind-cursor.json +23 -0
- package/schema/fixtures/event/valid/harness-kind-grok.json +23 -0
- package/schema/fixtures/event/valid/harness-kind-hermes.json +23 -0
- package/schema/fixtures/event/valid/harness-kind-muse.json +23 -0
- package/schema/fixtures/policy/invalid/daemons-malformed-id.json +10 -0
- package/schema/fixtures/policy/invalid/senders-half-keyed.json +20 -0
- package/schema/fixtures/policy/valid/canonical.json +1 -1
- package/schema/fixtures/policy/valid/daemons-allowlist.json +19 -0
- package/schema/fixtures/policy/valid/senders-keyed.json +24 -0
- package/schema/fixtures/policy-md/invalid/schema-invalid-agent-may-request.md +21 -0
- package/schema/fixtures/policy-md/valid/agent-may-request.md +24 -0
- package/schema/fixtures/policy-md/valid/canonical.md +1 -1
- package/schema/fixtures/policy-md/valid/with-values.md +5 -7
- package/schema/fixtures/values/invalid/class-shaped.json +1 -1
- package/schema/fixtures/values/invalid/duplicate-entry.json +1 -1
- package/schema/fixtures/values/invalid/non-string-item.json +1 -1
- package/schema/fixtures/values/invalid/over-cap.json +1 -1
- package/schema/fixtures/values/invalid/unknown-key.json +1 -1
- package/schema/fixtures/values/invalid/version-float.json +1 -0
- package/schema/fixtures/values/invalid/version-integer.json +1 -0
- package/schema/fixtures/values/invalid/version-wrong-string.json +1 -0
- package/schema/fixtures/values/valid/empty-lists.json +2 -3
- package/schema/fixtures/values/valid/full.json +5 -7
- package/schema/fixtures/values/valid/minimal.json +1 -1
- package/schema/fixtures/values-md/invalid/schema-invalid.md +5 -3
- package/schema/fixtures/values-md/invalid/two-blocks.md +3 -3
- package/schema/fixtures/values-md/invalid/unterminated.md +2 -2
- package/schema/fixtures/values-md/invalid/version-1.md +69 -0
- package/schema/fixtures/values-md/invalid/version-unquoted.md +64 -0
- package/schema/fixtures/values-md/invalid/yaml-error.md +2 -2
- package/schema/fixtures/values-md/valid/absent.md +1 -1
- package/schema/fixtures/values-md/valid/with-values.md +5 -7
- package/schema/policy.schema.json +100 -4
- package/schema/values.schema.json +7 -11
- package/schema/fixtures/values/invalid/version-string.json +0 -1
package/dist/src/cli/hook.d.ts
CHANGED
|
@@ -72,8 +72,29 @@
|
|
|
72
72
|
* auditor holding the log alone. See `docs/claude-code-hook.md`.
|
|
73
73
|
*/
|
|
74
74
|
import { type ClassifiedSegment, type CommandClassification, type ProtectedPathEntry } from "../core/command-class.js";
|
|
75
|
+
import { type GateOptions } from "../core/gate.js";
|
|
76
|
+
import { type HarnessKind } from "../core/harness-version.js";
|
|
77
|
+
import { type HarnessLoopState } from "../core/loop.js";
|
|
78
|
+
import type { EventRecord } from "../core/log.js";
|
|
79
|
+
import { type ReadRecordsResult } from "../core/state.js";
|
|
75
80
|
import type { Streams } from "./main.js";
|
|
76
81
|
import { type Style } from "./style.js";
|
|
82
|
+
/**
|
|
83
|
+
* How many CONSECUTIVE torn-tail reads the decision wait reads past before it
|
|
84
|
+
* treats the log as unreadable (APRV-427 review).
|
|
85
|
+
*
|
|
86
|
+
* The poll reads the log while other writers append to it: the daemon beside
|
|
87
|
+
* a CLI hook, and every other hook call beside one run by `approval serve`. An
|
|
88
|
+
* append is one write on an O_APPEND handle, but a reader on a filesystem that
|
|
89
|
+
* grows a file a page at a time can see the front of a line before its end, and
|
|
90
|
+
* the verified read calls that a torn tail. It is a moment in someone else's
|
|
91
|
+
* write, and the next tick reads the whole line. Treating it as terminal
|
|
92
|
+
* withdrew a live question and denied `hook-io` because a DIFFERENT call was
|
|
93
|
+
* mid-append. So a torn read is read again next tick, like a view that lags;
|
|
94
|
+
* only a tail that stays torn for this many ticks in a row (a crashed write,
|
|
95
|
+
* which nobody is going to finish) takes the old withdraw-and-deny path.
|
|
96
|
+
*/
|
|
97
|
+
export declare const TORN_TAIL_RETRIES = 5;
|
|
77
98
|
/**
|
|
78
99
|
* How much of the command line goes in the (claimed) summary field.
|
|
79
100
|
*
|
|
@@ -115,6 +136,29 @@ export declare const HOOK_DENY_CODES: readonly [
|
|
|
115
136
|
* rejection: nobody decided anything, so there is nothing to ask again.
|
|
116
137
|
*/
|
|
117
138
|
"hook-class-human-only",
|
|
139
|
+
/**
|
|
140
|
+
* A `harness.launch.*` class that no rule of this policy names (APRV-354).
|
|
141
|
+
*
|
|
142
|
+
* SPEC.md §7 says the family is never inferred autonomous; this is the
|
|
143
|
+
* stronger reading the family needs, which is that it is never inferred at
|
|
144
|
+
* all. A launch resolves only under a rule an operator wrote, and a policy
|
|
145
|
+
* that names neither `harness.launch.*` nor the specific member refuses.
|
|
146
|
+
*
|
|
147
|
+
* It exists because of the window the softer reading opens. Before the family
|
|
148
|
+
* existed, `codex …` and `muse …` were `hook-unclassified`: refused outright.
|
|
149
|
+
* Letting the new class fall to `defaults.autonomy` would have made every
|
|
150
|
+
* harness launch grantable by one approval in every project whose defaults
|
|
151
|
+
* are manual, the moment they upgraded — a capability arriving by upgrade
|
|
152
|
+
* rather than by decision. What that approval would cover is a whole second
|
|
153
|
+
* agent whose own actions this gate never sees.
|
|
154
|
+
*
|
|
155
|
+
* Distinct from `hook-unclassified`, which says the CLASSIFIER has nothing to
|
|
156
|
+
* say about the command; here the classifier was clear and the POLICY is
|
|
157
|
+
* silent. Distinct from `hook-class-human-only`, which is a policy that has
|
|
158
|
+
* spoken and reserved the class: the repair there is for a person to run the
|
|
159
|
+
* command, and the repair here is to write a line.
|
|
160
|
+
*/
|
|
161
|
+
"hook-harness-launch-unruled",
|
|
118
162
|
/** A construct whose effect cannot be read off the text (`bash -c`, `eval`). */
|
|
119
163
|
"hook-opaque",
|
|
120
164
|
/** The command line could not be tokenized at all. */
|
|
@@ -139,8 +183,36 @@ export declare const HOOK_DENY_CODES: readonly [
|
|
|
139
183
|
* directory, once. Past the grace the hook withdraws it (reason `timeout`),
|
|
140
184
|
* because a question nothing will adopt is a message on a phone that decides
|
|
141
185
|
* nothing.
|
|
186
|
+
*
|
|
187
|
+
* **Who withdraws, stated because the deny text used to imply a scheduler
|
|
188
|
+
* that does not exist (APRV-410).** This process is over when it denies, so
|
|
189
|
+
* the withdrawal is made by the NEXT gated tool call this actor makes, from
|
|
190
|
+
* the sweep at the top of every gated invocation. It cannot be anyone else: a
|
|
191
|
+
* withdrawal is the requester's own (APRV-106 rule 1, `not-requester`), the
|
|
192
|
+
* gate refuses a `system:` withdrawal outright, and the event schema binds
|
|
193
|
+
* the one `system:` withdrawal that exists to `policy-drift` and to nothing
|
|
194
|
+
* else (APRV-235). So a session that denies here and then makes no further
|
|
195
|
+
* gated call leaves the question pending until the TTL, and a decision on it
|
|
196
|
+
* lands as an ordinary grant. What stops that grant authorizing anything is
|
|
197
|
+
* the other half of APRV-410: a retry past the grace does not carry it
|
|
198
|
+
* (`findHarnessCarry`), so it asks again.
|
|
142
199
|
*/
|
|
143
200
|
"hook-timeout",
|
|
201
|
+
/**
|
|
202
|
+
* The harness ceiling this hook was told it runs under leaves no window a
|
|
203
|
+
* human could answer in (APRV-423).
|
|
204
|
+
*
|
|
205
|
+
* `--harness-cap` (or the harness's own documented maximum) at or below
|
|
206
|
+
* `HARNESS_CAP_MARGIN_MS` means the question would lapse at the instant it
|
|
207
|
+
* was asked: the request must end a margin before the harness kills the
|
|
208
|
+
* process that opened it, and there is no room left for the asking. Nothing
|
|
209
|
+
* is registered, nothing is requested and nobody's phone lights up. The
|
|
210
|
+
* repair is a configuration change — raise the harness entry's timeout, or
|
|
211
|
+
* state the real one — and it is a refusal rather than a silent widening
|
|
212
|
+
* because a hook that quietly ignored its own ceiling would be back to
|
|
213
|
+
* recording grants on calls nobody holds.
|
|
214
|
+
*/
|
|
215
|
+
"hook-harness-cap-too-short",
|
|
144
216
|
/** The gate refused intake; the gate's own code follows a colon. */
|
|
145
217
|
"hook-gate-refused",
|
|
146
218
|
/**
|
|
@@ -183,9 +255,357 @@ export declare const HOOK_DENY_CODES: readonly [
|
|
|
183
255
|
* merges do not reconcile hash chains (APRV-101).
|
|
184
256
|
*/
|
|
185
257
|
"hook-log-unreachable",
|
|
258
|
+
/**
|
|
259
|
+
* The harness does not tell this hook where the call will run, so no verdict
|
|
260
|
+
* over the visible bytes can bind the action (APRV-311, native evidence in
|
|
261
|
+
* APRV-310 v6/v7).
|
|
262
|
+
*
|
|
263
|
+
* Native Codex 0.152.1 honours a per-call Bash working directory that appears
|
|
264
|
+
* in no field of the event: `tool_input` carries `command` alone, and the
|
|
265
|
+
* event cwd and the hook process cwd both stay at the session root. A
|
|
266
|
+
* decision over `{command, session root}` would therefore authorize different
|
|
267
|
+
* bytes from the `{command, effective directory}` the harness executes, and a
|
|
268
|
+
* relative path in an approved command can name a protected organ in a
|
|
269
|
+
* directory the classifier never saw.
|
|
270
|
+
*
|
|
271
|
+
* Distinct from `hook-io`, which this used to borrow, and the distinction is
|
|
272
|
+
* the repair. `hook-io` says THIS event was malformed and a well-formed one
|
|
273
|
+
* would be answered; this says every event of this shape is refused on this
|
|
274
|
+
* harness version, and the fix is a harness contract that exposes the
|
|
275
|
+
* effective execution directory, not a retry, a policy edit, or an open
|
|
276
|
+
* window. Nothing appends on this path and no gate lifecycle opens.
|
|
277
|
+
*/
|
|
278
|
+
"hook-unsupported-execution-context",
|
|
279
|
+
/**
|
|
280
|
+
* The session names a Contributor-tier model, so every tool call is refused
|
|
281
|
+
* (APRV-350).
|
|
282
|
+
*
|
|
283
|
+
* Meta sells a Contributor variant of the Muse Spark family that "trades a
|
|
284
|
+
* lower price for permission to train on your prompts and completions". A
|
|
285
|
+
* session on one discloses every byte it reads, so the refusal is above the
|
|
286
|
+
* policy: no class resolution and no grant widens it, and an absent or
|
|
287
|
+
* unrecognised `model` is refused for the same reason an unparseable event is.
|
|
288
|
+
*
|
|
289
|
+
* Distinct from `hook-class-human-only`, which says a HUMAN must do this
|
|
290
|
+
* action; this says nothing may do it in this session, and the repair is to
|
|
291
|
+
* change the model in Muse's picker rather than to ask anybody. Distinct from
|
|
292
|
+
* `hook-io` because the event was perfectly well formed.
|
|
293
|
+
*
|
|
294
|
+
* What it cannot do is stated wherever it is documented: it stops tool calls,
|
|
295
|
+
* and it cannot recall a prompt the model has already been sent.
|
|
296
|
+
*/
|
|
297
|
+
"hook-muse-contributor-model",
|
|
298
|
+
/**
|
|
299
|
+
* A Hermes Agent `execute_code` call, refused before anything else looks at
|
|
300
|
+
* it (APRV-398).
|
|
301
|
+
*
|
|
302
|
+
* The tool takes a `code` string and runs it in-process. There is no path, no
|
|
303
|
+
* argv and no working directory: nothing the classifier reads exists in the
|
|
304
|
+
* call, so no class can be resolved and no payload can bind what will happen.
|
|
305
|
+
* A verdict over `{code}` would authorize a program this runtime never
|
|
306
|
+
* parsed, which is the same hole `hook-opaque` closes for `bash -c` and the
|
|
307
|
+
* same one `hook-unsupported-execution-context` closes for a command whose
|
|
308
|
+
* directory is invisible. SPEC.md §11.1 says ambiguity resolves to the
|
|
309
|
+
* stricter path, and the strictest honest answer to "may I run this arbitrary
|
|
310
|
+
* program" is no.
|
|
311
|
+
*
|
|
312
|
+
* Distinct from `hook-opaque`, and the distinction is the repair. That one
|
|
313
|
+
* says a command line carried a construct the classifier could not read, and
|
|
314
|
+
* the fix is to write the command differently. This one says the TOOL has no
|
|
315
|
+
* readable surface at all on this harness, so there is no spelling of an
|
|
316
|
+
* `execute_code` call that would be answered differently: the repair is to do
|
|
317
|
+
* the work through the shell tool, where the words are visible and the
|
|
318
|
+
* classifier can price them, or through `approval run` with a granted token.
|
|
319
|
+
*
|
|
320
|
+
* Distinct from `hook-unclassified`, which says the classifier had nothing to
|
|
321
|
+
* say about bytes it could see. Here there are no bytes to see.
|
|
322
|
+
*/
|
|
323
|
+
"hook-hermes-execute-code-unbound",
|
|
186
324
|
/** Malformed hook input, or a log/filesystem fact that stopped the check. */
|
|
187
325
|
"hook-io"];
|
|
188
326
|
export type HookDenyCode = (typeof HOOK_DENY_CODES)[number];
|
|
327
|
+
/** Where the hook reads policy from and appends to, resolved together. */
|
|
328
|
+
export interface HookScope {
|
|
329
|
+
logPath: string;
|
|
330
|
+
/** The directory `logPath` sits under, named in the unreachable-log detail. */
|
|
331
|
+
root: string;
|
|
332
|
+
options: GateOptions;
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Policy and log, resolved from the same root (APRV-101).
|
|
336
|
+
*
|
|
337
|
+
* Before this, `--dir` scoped only the policy and the log was resolved from the
|
|
338
|
+
* process cwd, so a hook invoked with `--dir <primary>` from an agent worktree
|
|
339
|
+
* read the primary's policy and wrote the worktree's copy of the log: a
|
|
340
|
+
* dead-end chain that forks from the real one. Explicit flags still win
|
|
341
|
+
* (`--policy` for the policy, `--log` for the log); otherwise both follow
|
|
342
|
+
* `--dir`, and with no flags at all both follow the primary checkout.
|
|
343
|
+
*/
|
|
344
|
+
export declare function hookScope(flags: Record<string, string | boolean>, cwd: string): HookScope;
|
|
345
|
+
/**
|
|
346
|
+
* Which harness JSON envelope to print. Never `ask`.
|
|
347
|
+
*
|
|
348
|
+
* One definition since APRV-227, in `core/harness-version.ts`: the set of
|
|
349
|
+
* harnesses this runtime speaks a protocol for is the same set it knows a
|
|
350
|
+
* binary name for, and two copies of it would be two lists to drift.
|
|
351
|
+
*/
|
|
352
|
+
/**
|
|
353
|
+
* What a harness's own non-shell, non-file tool asks for (APRV-445).
|
|
354
|
+
*
|
|
355
|
+
* `gated` names the class the call is judged under and the headline the
|
|
356
|
+
* approver reads; the payload is always the WHOLE call (`{tool, input}`), so a
|
|
357
|
+
* grant binds every argument the harness will act on. `read` is a call that
|
|
358
|
+
* touches nothing and is allowed without a record, exactly as an in-scope read
|
|
359
|
+
* tool is. A rule returns `null` for a tool it does not know, and that tool
|
|
360
|
+
* keeps the not-a-gated-tool allow every unknown tool has.
|
|
361
|
+
*/
|
|
362
|
+
type ToolRuleVerdict = {
|
|
363
|
+
kind: "gated";
|
|
364
|
+
cls: string;
|
|
365
|
+
headline: string;
|
|
366
|
+
} | {
|
|
367
|
+
kind: "read";
|
|
368
|
+
reason: string;
|
|
369
|
+
};
|
|
370
|
+
/**
|
|
371
|
+
* The ceiling a harness puts on its hook process, in milliseconds, or `null`
|
|
372
|
+
* (APRV-423): the operator's stated `--harness-cap` clamped to the adapter's
|
|
373
|
+
* contractual maximum, or, with nothing stated, the adapter's documented
|
|
374
|
+
* default, falling back to that maximum.
|
|
375
|
+
*
|
|
376
|
+
* Exported so `approval serve` computes the same number the hook will before
|
|
377
|
+
* it hands the call to a thread (APRV-427 review): the budget a queued call
|
|
378
|
+
* has left is this less the time it has waited.
|
|
379
|
+
*/
|
|
380
|
+
export declare function effectiveHarnessCapMs(adapter: Pick<HarnessAdapter, "capCeilingMs" | "capDefaultMs">, statedCapMs: number | null): number | null;
|
|
381
|
+
interface HarnessAdapter {
|
|
382
|
+
kind: HarnessKind;
|
|
383
|
+
originApp: string;
|
|
384
|
+
defaultActor: string;
|
|
385
|
+
shellTool: string;
|
|
386
|
+
fileTools: readonly string[];
|
|
387
|
+
/**
|
|
388
|
+
* The harness's own tools that carry a side effect of their own, by name and
|
|
389
|
+
* arguments (APRV-445). Consulted after the shell, file and read tools, for
|
|
390
|
+
* the calls none of those owns.
|
|
391
|
+
*/
|
|
392
|
+
toolRules?: (toolName: string, toolInput: Record<string, unknown>) => ToolRuleVerdict | null;
|
|
393
|
+
/**
|
|
394
|
+
* Tools that READ a named path (APRV-347).
|
|
395
|
+
*
|
|
396
|
+
* Parallel to `fileTools` and answered by a parallel gate. The two lists
|
|
397
|
+
* differ in what an empty entry means: a file tool with no path is a tool
|
|
398
|
+
* call this runtime does not understand, while a read tool with no path is
|
|
399
|
+
* the ordinary spelling of "read the workspace" and keeps the
|
|
400
|
+
* not-a-gated-tool `allow` it has always had.
|
|
401
|
+
*/
|
|
402
|
+
readTools: readonly string[];
|
|
403
|
+
/** Include the native tool name in the bytes a grant binds. */
|
|
404
|
+
bindToolName?: boolean;
|
|
405
|
+
/**
|
|
406
|
+
* Read `toolName`/`toolInput`/`sessionId` as well as the snake_case
|
|
407
|
+
* spellings (APRV-243).
|
|
408
|
+
*
|
|
409
|
+
* Grok Build's PreToolUse envelope is Claude Code's with camelCase keys.
|
|
410
|
+
* Opt-in per adapter rather than tolerated everywhere: a Claude Code event
|
|
411
|
+
* that arrived with the wrong spelling is a malformed event, and the strict
|
|
412
|
+
* answer to a malformed event is the deny that `parseHookInput` already
|
|
413
|
+
* produces.
|
|
414
|
+
*/
|
|
415
|
+
camelCaseEnvelope?: boolean;
|
|
416
|
+
/**
|
|
417
|
+
* Tools the harness fires for its OWN bookkeeping, answered and never gated
|
|
418
|
+
* (APRV-350).
|
|
419
|
+
*
|
|
420
|
+
* Muse Code fires `PreToolUse` and `PostToolUse` for `submit_reminder_decision`
|
|
421
|
+
* continuously: 100 of the 139 events in the live capture were that one tool.
|
|
422
|
+
* It records a self-assessment and touches nothing, so gating it would put a
|
|
423
|
+
* hundred questions a turn on an approver's phone to authorize the harness
|
|
424
|
+
* thinking. It is listed rather than inferred, because a tool this runtime
|
|
425
|
+
* does not recognise must keep falling through to the ordinary path.
|
|
426
|
+
*/
|
|
427
|
+
passThroughTools?: readonly string[];
|
|
428
|
+
/**
|
|
429
|
+
* The `tool_input` key carrying the PER-CALL working directory, when the
|
|
430
|
+
* harness sends one (APRV-350).
|
|
431
|
+
*
|
|
432
|
+
* Muse's `bash` tool carries `workdir`, and it is the directory the command
|
|
433
|
+
* will actually run in, which is the fact the classifier needs. The top-level
|
|
434
|
+
* `cwd` is the session's root and can differ. Codex has neither, which is why
|
|
435
|
+
* its shell arm refuses outright; Claude Code has only the top-level one.
|
|
436
|
+
*/
|
|
437
|
+
shellCwdKey?: string;
|
|
438
|
+
/**
|
|
439
|
+
* The hard ceiling this harness's own contract puts on one hook process, in
|
|
440
|
+
* milliseconds, or absent when it documents none (APRV-423).
|
|
441
|
+
*
|
|
442
|
+
* A CEILING, not a configuration. Hermes states that a `hooks:` entry's
|
|
443
|
+
* `timeout` may not exceed 300 s, so no Hermes entry can outlive that however
|
|
444
|
+
* it is written, and this runtime may rely on it. Claude Code, Cursor, Grok,
|
|
445
|
+
* Muse and Codex document a DEFAULT hook timeout and no maximum, so they
|
|
446
|
+
* carry none: an adapter that guessed a ceiling would either invent a
|
|
447
|
+
* deadline the harness never imposed, or — worse, and the direction that
|
|
448
|
+
* actually costs something — guess high and leave the gap APRV-410 falls
|
|
449
|
+
* through. The operator states the entry they configured with
|
|
450
|
+
* `--harness-cap`, and the effective cap is the smaller of the two.
|
|
451
|
+
*/
|
|
452
|
+
capCeilingMs?: number;
|
|
453
|
+
/**
|
|
454
|
+
* The ceiling this harness imposes on a hook process when the operator has
|
|
455
|
+
* stated none, in milliseconds, or absent when the harness's defaults leave
|
|
456
|
+
* the process unbounded (APRV-423, second review pass).
|
|
457
|
+
*
|
|
458
|
+
* Read ONLY when `--harness-cap` is absent, and then it stands in for the
|
|
459
|
+
* flag: the operator has said nothing, so the runtime assumes the harness is
|
|
460
|
+
* running as shipped. It is the STRICTEST bound the harness's own defaults
|
|
461
|
+
* put on the process, because assuming a looser one is APRV-410 with a
|
|
462
|
+
* fictional deadline: a request judged against a window the harness will not
|
|
463
|
+
* keep open. On Hermes that is `plugins.hook_callback_timeout`, which bounds
|
|
464
|
+
* the whole hook dispatch and defaults to 30 s, well under the margin, so a
|
|
465
|
+
* Hermes hook with no flag is refused `hook-harness-cap-too-short` until the
|
|
466
|
+
* operator raises that setting and says so. A stated flag replaces this
|
|
467
|
+
* value (the operator knows what they configured) and is still clamped by
|
|
468
|
+
* {@link capCeilingMs}, so a stated cap can only shorten the contractual
|
|
469
|
+
* maximum, never exceed it.
|
|
470
|
+
*/
|
|
471
|
+
capDefaultMs?: number;
|
|
472
|
+
/**
|
|
473
|
+
* How an operator lifts a `hook-harness-cap-too-short` refusal on this
|
|
474
|
+
* harness, in the harness's own vocabulary (APRV-423, second review pass).
|
|
475
|
+
* Appended to the deny so the repair names the setting to change rather than
|
|
476
|
+
* a generic "raise the timeout". Absent adapters get the generic sentence.
|
|
477
|
+
*/
|
|
478
|
+
capRepair?: string;
|
|
479
|
+
/**
|
|
480
|
+
* Refuse every tool call when the envelope names a Contributor-tier model
|
|
481
|
+
* (APRV-350).
|
|
482
|
+
*
|
|
483
|
+
* Meta sells a Contributor variant that "trades a lower price for permission
|
|
484
|
+
* to train on your prompts and completions". A session on one is a session
|
|
485
|
+
* whose every read is disclosed, so the adapter refuses regardless of what
|
|
486
|
+
* the policy would otherwise allow. See {@link contributorModelRefusal}.
|
|
487
|
+
*/
|
|
488
|
+
contributorModelGuard?: boolean;
|
|
489
|
+
}
|
|
490
|
+
/**
|
|
491
|
+
* Hermes Agent's event names, which are its own and not Claude Code's
|
|
492
|
+
* (APRV-398).
|
|
493
|
+
*
|
|
494
|
+
* snake_case, and the VERBS differ too: `pre_tool_call` where every other
|
|
495
|
+
* harness in this table says `PreToolUse`. That is why they are constants
|
|
496
|
+
* rather than two more entries in {@link POST_TOOL_EVENTS} — a harness whose
|
|
497
|
+
* post-event name appeared in that shared list would also accept
|
|
498
|
+
* `PostToolUse` from an event nobody sent, and the dispatch would answer a
|
|
499
|
+
* name this harness never uses.
|
|
500
|
+
*/
|
|
501
|
+
export declare const HERMES_PRE_TOOL_EVENT = "pre_tool_call";
|
|
502
|
+
export declare const HERMES_POST_TOOL_EVENT = "post_tool_call";
|
|
503
|
+
/**
|
|
504
|
+
* Every harness this runtime speaks a hook protocol for, by kind (APRV-358).
|
|
505
|
+
*
|
|
506
|
+
* The table is `Record<HarnessKind, HarnessAdapter>` rather than a list of
|
|
507
|
+
* consts and a switch, and the type is the point: a kind added to
|
|
508
|
+
* `HARNESS_KINDS` with no adapter beside it fails to compile, so the two lists
|
|
509
|
+
* cannot drift by forgetting. The subcommand dispatch below reads this map, so
|
|
510
|
+
* `approval hook <kind>` is answerable for exactly the kinds named here.
|
|
511
|
+
*
|
|
512
|
+
* The kinds that are enumerated OUTSIDE this module — the schema's
|
|
513
|
+
* `payload.harness` enum, the verb registry's `hook` subcommands, the MCP
|
|
514
|
+
* exclusions, the help — are pinned set-equal to `HARNESS_KINDS` by
|
|
515
|
+
* `tests/harness-enum.test.ts`, which exists because `grok` shipped an adapter
|
|
516
|
+
* in APRV-243 and reached none of them. A Grok session's manual-class
|
|
517
|
+
* registration was refused at the write boundary for eleven days and nothing
|
|
518
|
+
* failed.
|
|
519
|
+
*/
|
|
520
|
+
export declare const HARNESS_ADAPTERS: Readonly<Record<HarnessKind, HarnessAdapter>>;
|
|
521
|
+
/**
|
|
522
|
+
* The block directive a harness reads, as bytes, for a refusal raised OUTSIDE
|
|
523
|
+
* the hook (APRV-421).
|
|
524
|
+
*
|
|
525
|
+
* `approval serve` can refuse a hook request before `commandHook` is reached
|
|
526
|
+
* at all: an oversized body, a credential at the wrong door, a method it does
|
|
527
|
+
* not answer. Each of those is a refusal by the transport, and a transport
|
|
528
|
+
* refusal a harness reads as "no verdict" is a harness that runs the command.
|
|
529
|
+
* So the refusal has to be spoken in the harness's own dialect, and the only
|
|
530
|
+
* correct source of that dialect is the function that prints it here.
|
|
531
|
+
*
|
|
532
|
+
* It renders through {@link deny} for that reason: one construction site per
|
|
533
|
+
* harness, still never `ask`. The exit code returned is the one the verb would
|
|
534
|
+
* have carried; a caller may impose a stricter one, and the transport does.
|
|
535
|
+
*/
|
|
536
|
+
export declare function harnessBlockDirective(code: string, detail: string, harness: HarnessKind): {
|
|
537
|
+
stdout: string;
|
|
538
|
+
exitCode: number;
|
|
539
|
+
};
|
|
540
|
+
/** The machine-readable code a Hermes `execute_code` call is refused with. */
|
|
541
|
+
export declare const HERMES_EXECUTE_CODE_REFUSAL = "hook-hermes-execute-code-unbound";
|
|
542
|
+
/** The machine-readable code a Contributor-tier session is refused with. */
|
|
543
|
+
export declare const MUSE_CONTRIBUTOR_REFUSAL = "hook-muse-contributor-model";
|
|
544
|
+
export interface HookInput {
|
|
545
|
+
sessionId: string;
|
|
546
|
+
/** Whether the event supplied the session id, distinct from the strict unknown bucket. */
|
|
547
|
+
sessionIdPresent: boolean;
|
|
548
|
+
cwd: string;
|
|
549
|
+
toolName: string;
|
|
550
|
+
toolInput: Record<string, unknown>;
|
|
551
|
+
toolUseId: string | null;
|
|
552
|
+
/**
|
|
553
|
+
* `hook_event_name`, verbatim, or `null` when the event carries none
|
|
554
|
+
* (APRV-145).
|
|
555
|
+
*
|
|
556
|
+
* Read at last. Until this, nothing in this module looked at it and
|
|
557
|
+
* `runHarnessHook` assumed a pre-execution event unconditionally, so an
|
|
558
|
+
* operator who registered this same command for the post-execution event would
|
|
559
|
+
* have gated every command a second time and doubled every prompt on the
|
|
560
|
+
* approver's phone.
|
|
561
|
+
*/
|
|
562
|
+
hookEventName: string | null;
|
|
563
|
+
/**
|
|
564
|
+
* The model the session reports running, or `null` (APRV-350).
|
|
565
|
+
*
|
|
566
|
+
* Muse sends it on every event. Read for one purpose only, the contributor
|
|
567
|
+
* guard, and that guard can only ever refuse: a self-reported field raises
|
|
568
|
+
* scrutiny and never lowers it (SPEC §11.1). It is never logged, because it
|
|
569
|
+
* is untrusted third-party text and §11.1 invariant 3 has no provenance
|
|
570
|
+
* exception.
|
|
571
|
+
*/
|
|
572
|
+
model: string | null;
|
|
573
|
+
/**
|
|
574
|
+
* `tool_response`, when the event carries one as an object.
|
|
575
|
+
*
|
|
576
|
+
* Present only on a post-execution event; the pre-execution path never reads
|
|
577
|
+
* it, because the tool has not run. Its SHAPE is all that is ever read (see
|
|
578
|
+
* {@link readReportedOutcome}) — never the text inside it.
|
|
579
|
+
*/
|
|
580
|
+
toolResponse: Record<string, unknown> | null;
|
|
581
|
+
/** `tool_response` verbatim, including strings, for harness-specific readers. */
|
|
582
|
+
toolResponseRaw: unknown;
|
|
583
|
+
/**
|
|
584
|
+
* `is_interrupt`, the post-execution events' own word for "a person stopped
|
|
585
|
+
* this" (APRV-303).
|
|
586
|
+
*
|
|
587
|
+
* `PostToolUseFailure` carries it beside `error`; `PostToolUse` carries the
|
|
588
|
+
* same fact as `tool_response.interrupted`. Read only to make an outcome
|
|
589
|
+
* UNREADABLE, never to establish one, so nothing about it can lower scrutiny.
|
|
590
|
+
*/
|
|
591
|
+
interrupted: boolean;
|
|
592
|
+
/**
|
|
593
|
+
* `version`, when the harness states its own (APRV-227).
|
|
594
|
+
*
|
|
595
|
+
* Claude Code's event may carry it; Cursor's does not, and neither did any
|
|
596
|
+
* Claude Code release before it. So this is a preference and never a
|
|
597
|
+
* requirement: `core/harness-version.ts` falls back to `<binary> --version`
|
|
598
|
+
* and then to absence, and a hook that can establish nothing records nothing.
|
|
599
|
+
*
|
|
600
|
+
* SELF-REPORTED, and read at all only because it cannot buy the reporter
|
|
601
|
+
* anything. Nothing in this module branches on it; it reaches exactly one
|
|
602
|
+
* payload field whose one reader is a doctor row that can only ADD a red
|
|
603
|
+
* line, so §11.1 invariant 4 holds by construction rather than by care. A
|
|
604
|
+
* harness that states a false version defeats a check that would have asked a
|
|
605
|
+
* human to look, and gains no verdict it did not already have.
|
|
606
|
+
*/
|
|
607
|
+
harnessVersion: string | null;
|
|
608
|
+
}
|
|
189
609
|
/** A classification plus a human-readable note for every segment refined. */
|
|
190
610
|
export interface RefinedClassification {
|
|
191
611
|
result: CommandClassification;
|
|
@@ -240,14 +660,77 @@ export declare function resolveScratchRoots(cwd: string, env?: NodeJS.ProcessEnv
|
|
|
240
660
|
*/
|
|
241
661
|
export declare function refineScratchDelete(result: CommandClassification, roots: readonly string[]): RefinedClassification;
|
|
242
662
|
/**
|
|
243
|
-
* The
|
|
663
|
+
* The read roots this process may vouch for, resolved.
|
|
664
|
+
*
|
|
665
|
+
* The gate root is the directory the hook resolved its POLICY from, never the
|
|
666
|
+
* harness-supplied `cwd`: a scope the subject of the gate could choose is not a
|
|
667
|
+
* scope (SPEC.md §11.1, self-reported fields never reduce scrutiny). The
|
|
668
|
+
* scratchpad and temp roots are the ones `resolveScratchRoots` already computes
|
|
669
|
+
* and already guards, so the two rules cannot disagree about where the agent's
|
|
670
|
+
* own scratch is. `declared` is `read_scope.roots` out of the loaded policy,
|
|
671
|
+
* which may only widen this set.
|
|
672
|
+
*/
|
|
673
|
+
export declare function resolveReadRoots(cwd: string, gateRoot: string, declared?: readonly string[]): string[];
|
|
674
|
+
/**
|
|
675
|
+
* Tighten a `read.shell` segment to `read.file.out_of_scope` wherever the disk
|
|
676
|
+
* disagrees with the text.
|
|
677
|
+
*
|
|
678
|
+
* IMPURE by design and by contract. `roots` empty means the caller asked for no
|
|
679
|
+
* read scoping at all, and every segment is returned untouched — the same
|
|
680
|
+
* "absent yields today's answer" the classifier context promises.
|
|
681
|
+
*/
|
|
682
|
+
export declare function refineReadScope(result: CommandClassification, roots: readonly string[], cwd: string): RefinedClassification;
|
|
683
|
+
/**
|
|
684
|
+
* The roots a write may land in, resolved.
|
|
685
|
+
*
|
|
686
|
+
* The scratch roots are the ones `resolveScratchRoots` already computes and
|
|
687
|
+
* already guards, so this rule and the delete rule cannot disagree about where
|
|
688
|
+
* the agent's own scratch is. The working directory is the other one, and it is
|
|
689
|
+
* the root the classifier's own text rule already implies: "a relative
|
|
690
|
+
* destination is the workspace" means the workspace is wherever the command
|
|
691
|
+
* runs.
|
|
692
|
+
*
|
|
693
|
+
* `cwd` is harness-supplied, and reading a root out of it does not offend
|
|
694
|
+
* SPEC.md §11.1 invariant 4, because this pass only ever narrows. The baseline
|
|
695
|
+
* it narrows from is no check at all, so a poisoned `cwd` buys back exactly
|
|
696
|
+
* today's answer and never a looser one. Compare the read pass, which checks
|
|
697
|
+
* containment against the GATE root: that pass decides whether a read is in
|
|
698
|
+
* scope at all, so the scope has to be one the subject cannot choose. This one
|
|
699
|
+
* only takes an allowance away.
|
|
700
|
+
*/
|
|
701
|
+
export declare function resolveWriteRoots(cwd: string): string[];
|
|
702
|
+
/**
|
|
703
|
+
* Tighten a `files.write.workspace` segment to the out-of-scope write class
|
|
704
|
+
* wherever the disk disagrees with the text.
|
|
705
|
+
*
|
|
706
|
+
* IMPURE by design and by contract, exactly as the other two passes are. The
|
|
707
|
+
* class it tightens to is the one `core/command-class.ts` already answers for
|
|
708
|
+
* an out-of-scope packaging destination rather than a new name: a class no
|
|
709
|
+
* policy mentions resolves by `defaults.autonomy` (SPEC.md §7), so minting
|
|
710
|
+
* `files.write.out_of_scope` here would arrive AUTONOMOUS in every deployment
|
|
711
|
+
* with permissive defaults, which is the one way a tightening pass could
|
|
712
|
+
* loosen something.
|
|
713
|
+
*
|
|
714
|
+
* `roots` empty means the caller asked for no write scoping, and every segment
|
|
715
|
+
* comes back untouched: the same "absent yields today's answer" the read pass
|
|
716
|
+
* promises.
|
|
717
|
+
*/
|
|
718
|
+
export declare function refineWriteScope(result: CommandClassification, roots: readonly string[], cwd: string): RefinedClassification;
|
|
719
|
+
/**
|
|
720
|
+
* The classifier, its context, and all four impure refinements, in the one
|
|
244
721
|
* order every caller must use.
|
|
245
722
|
*
|
|
246
723
|
* `hook classify` printing a different class from the one `hook claude-code`
|
|
247
724
|
* decides would make the explainer a different program (APRV-108's note), and
|
|
248
|
-
* that stays true now there are
|
|
725
|
+
* that stays true now there are four refinements in the chain.
|
|
726
|
+
*
|
|
727
|
+
* `readRoots` is the one argument whose ABSENCE is the loose answer rather than
|
|
728
|
+
* the strict one (APRV-347), so it is passed explicitly at every call site: an
|
|
729
|
+
* empty list means "do not scope reads", which is what every caller outside a
|
|
730
|
+
* resolved gate scope wants and what this classifier did before the field
|
|
731
|
+
* existed.
|
|
249
732
|
*/
|
|
250
|
-
export declare function classifyForHook(command: string, protectedPaths: readonly ProtectedPathEntry[], cwd: string): RefinedClassification;
|
|
733
|
+
export declare function classifyForHook(command: string, protectedPaths: readonly ProtectedPathEntry[], cwd: string, readRoots?: readonly string[]): RefinedClassification;
|
|
251
734
|
/**
|
|
252
735
|
* What the classifier made of a command (APRV-91 #9).
|
|
253
736
|
*
|
|
@@ -257,6 +740,264 @@ export declare function classifyForHook(command: string, protectedPaths: readonl
|
|
|
257
740
|
* that the `json` veto on colour is the answer this process memoizes.
|
|
258
741
|
*/
|
|
259
742
|
export declare function renderClassification(result: CommandClassification, json: boolean, st?: Style): string;
|
|
743
|
+
/**
|
|
744
|
+
* Where a hook invocation stops appending and starts waiting, for a caller that
|
|
745
|
+
* shares one store with other work in the same process (APRV-427).
|
|
746
|
+
*
|
|
747
|
+
* `approval serve` runs many hook calls, verbs and log reads against one store
|
|
748
|
+
* from one process. Its per-store lock has to cover every stretch in which a
|
|
749
|
+
* hook call APPENDS (intake, the abandoned-question sweep, register, request,
|
|
750
|
+
* the spend, the withdrawals) so that no other in-process writer lands between
|
|
751
|
+
* a check and its append; and it must NOT cover the poll loop, where the hook
|
|
752
|
+
* only reads the verified log and sleeps, or one question waiting on a human
|
|
753
|
+
* holds the tenant's whole facade for as long as the human takes.
|
|
754
|
+
*
|
|
755
|
+
* So the loop says where it begins and ends. `enterWait` is called once, right
|
|
756
|
+
* before the first poll, and after it nothing is appended until `leaveWait`
|
|
757
|
+
* returns. `leaveWait` is called before every append the loop can reach (the
|
|
758
|
+
* spend on a grant, a withdrawal on a failed read, at the deadline, on a throw
|
|
759
|
+
* or a signal) and once more when the loop is left by any route, and it may
|
|
760
|
+
* BLOCK until the caller's lock is held again. Both are idempotent pairs: the
|
|
761
|
+
* hook never calls `leaveWait` without a matching `enterWait` before it.
|
|
762
|
+
*
|
|
763
|
+
* Absent (the CLI, the Codex bridge), both are no-ops and the invocation is
|
|
764
|
+
* byte-for-byte what it was. The seam decides nothing: no verdict, record or
|
|
765
|
+
* line depends on it, and the appends it brackets are compare-and-append
|
|
766
|
+
* either way (SPEC.md §11.1 invariant 5). It only tells an embedding caller
|
|
767
|
+
* which stretch of the call is safe to run beside other writers.
|
|
768
|
+
*/
|
|
769
|
+
export interface HookWaitSeam {
|
|
770
|
+
/** The poll loop is about to start: nothing is appended until {@link leaveWait}. */
|
|
771
|
+
enterWait(): void;
|
|
772
|
+
/** An append follows, or the loop is over. May block until the caller's lock is held. */
|
|
773
|
+
leaveWait(): void;
|
|
774
|
+
/**
|
|
775
|
+
* The read each poll tick makes. Omitted by every real caller, which gets
|
|
776
|
+
* `readVerifiedRecords`; it exists so a test can put a torn read in front of
|
|
777
|
+
* the loop without writing a torn line into a log.
|
|
778
|
+
*/
|
|
779
|
+
pollRead?: (logPath: string) => ReadRecordsResult;
|
|
780
|
+
/**
|
|
781
|
+
* Whether the party this invocation answers has gone away (APRV-427 review).
|
|
782
|
+
*
|
|
783
|
+
* `approval serve` sets it when the HTTP client that asked disconnects. Read
|
|
784
|
+
* on every poll tick, and once more after the lock is re-taken and before a
|
|
785
|
+
* grant is spent: while it is set, the invocation stops waiting WITHOUT
|
|
786
|
+
* spending and WITHOUT withdrawing, so the question (or its grant) is left
|
|
787
|
+
* exactly as a hook-timeout inside the retry grace leaves it, for the
|
|
788
|
+
* harness's retry to adopt. A grant already spent is not affected: the check
|
|
789
|
+
* precedes the spend, and nothing after it looks again.
|
|
790
|
+
*/
|
|
791
|
+
cancelled?: () => boolean;
|
|
792
|
+
/**
|
|
793
|
+
* What is LEFT of the harness's ceiling when this invocation begins, in
|
|
794
|
+
* milliseconds, as the embedding caller measured it (APRV-427 review).
|
|
795
|
+
*
|
|
796
|
+
* The harness's ceiling runs from the moment the harness sent the call, not
|
|
797
|
+
* from the moment this invocation began. `approval serve` may hold a call in
|
|
798
|
+
* line for a thread, and for the store lock, before the hook's first line
|
|
799
|
+
* runs; that time is spent out of the same budget. The caller computes the
|
|
800
|
+
* remainder ONCE, admits the call on it, and hands the same number here, so
|
|
801
|
+
* the hook judges, waits by and records on the request exactly what the
|
|
802
|
+
* caller admitted: a call the caller let through never meets
|
|
803
|
+
* `hook-harness-cap-too-short` a few milliseconds later. It can only
|
|
804
|
+
* shorten the ceiling the flags give (never lengthen it), and it is omitted
|
|
805
|
+
* by the CLI, where the process starts when the harness calls.
|
|
806
|
+
*/
|
|
807
|
+
remainingCapMs?: number;
|
|
808
|
+
}
|
|
809
|
+
interface HookRun {
|
|
810
|
+
/** APRV-427: the embedding caller's wait seam, or `null` from the CLI. */
|
|
811
|
+
waitSeam: HookWaitSeam | null;
|
|
812
|
+
logPath: string;
|
|
813
|
+
options: GateOptions;
|
|
814
|
+
actor: string;
|
|
815
|
+
timeoutMs: number;
|
|
816
|
+
intervalMs: number;
|
|
817
|
+
/**
|
|
818
|
+
* How long a request outlives the wait before this hook takes it back
|
|
819
|
+
* (APRV-287, `--retry-grace`).
|
|
820
|
+
*
|
|
821
|
+
* `core/harness-wait.ts` holds the default and the reasoning. Zero withdraws
|
|
822
|
+
* at the moment the wait expires, which is what the tests drive.
|
|
823
|
+
*/
|
|
824
|
+
graceMs: number;
|
|
825
|
+
/** `defaults.approval_ttl`, or `null` when the policy declares none. */
|
|
826
|
+
ttlMs: number | null;
|
|
827
|
+
/**
|
|
828
|
+
* The ceiling this harness puts on this process, in milliseconds, or `null`
|
|
829
|
+
* (APRV-423).
|
|
830
|
+
*
|
|
831
|
+
* The entry timeout the operator stated with `--harness-cap`, clamped to the
|
|
832
|
+
* harness's own documented maximum ({@link HarnessAdapter.capCeilingMs}); or,
|
|
833
|
+
* when no flag was passed, the harness's default bound
|
|
834
|
+
* ({@link HarnessAdapter.capDefaultMs}, falling back to the ceiling). It is
|
|
835
|
+
* written onto every request this invocation opens and read back by the gate,
|
|
836
|
+
* which judges the question against the shorter of it (minus the margin) and
|
|
837
|
+
* the policy's TTL — so the request ends while this process is still alive to
|
|
838
|
+
* be told, and the daemon's `approval.expired` lands before the harness gives
|
|
839
|
+
* up.
|
|
840
|
+
*/
|
|
841
|
+
harnessCapMs: number | null;
|
|
842
|
+
/**
|
|
843
|
+
* Whether {@link harnessCapMs} came from the operator's own `--harness-cap`
|
|
844
|
+
* (`true`) or was assumed from the adapter's documented defaults (`false`).
|
|
845
|
+
* A refusal for a cap with no room in it says which, because the repair
|
|
846
|
+
* differs: a stated cap is a real configuration to raise, an assumed one is
|
|
847
|
+
* a flag the operator has not passed yet.
|
|
848
|
+
*/
|
|
849
|
+
harnessCapStated: boolean;
|
|
850
|
+
/**
|
|
851
|
+
* The ceiling {@link harnessCapMs} was charged down from, or `null` where
|
|
852
|
+
* nothing was charged (APRV-427 review). Wording only.
|
|
853
|
+
*/
|
|
854
|
+
harnessCapCeilingMs: number | null;
|
|
855
|
+
/** {@link HarnessAdapter.capRepair}, or `null` for the generic sentence. */
|
|
856
|
+
harnessCapRepair: string | null;
|
|
857
|
+
/**
|
|
858
|
+
* How long THIS invocation actually waits, in milliseconds (APRV-423, third
|
|
859
|
+
* review pass): `timeoutMs`, clamped to what the harness ceiling leaves after
|
|
860
|
+
* the margin (`harnessCapMs - HARNESS_CAP_MARGIN_MS`) when there is one, and
|
|
861
|
+
* never below zero.
|
|
862
|
+
*
|
|
863
|
+
* The clamp is what closes the adopted-question gap. The too-short refusal
|
|
864
|
+
* fires only for a question this invocation would OPEN; an invocation whose
|
|
865
|
+
* every key is adopted (a retry of bytes an earlier, correctly-capped run
|
|
866
|
+
* asked about) opens nothing, so under a ceiling with no room in it (a Hermes
|
|
867
|
+
* hook with no `--harness-cap`, assumed 30 s) it would have sat in its poll
|
|
868
|
+
* loop for the configured wait, been killed at the ceiling, and let the call
|
|
869
|
+
* proceed on any harness without `fail_closed`. Bounding the wait itself means
|
|
870
|
+
* the hook always answers before the harness stops listening: a zero wait
|
|
871
|
+
* reads the verified log once (so a decision already recorded is still
|
|
872
|
+
* honoured) and then denies `hook-timeout`, leaving the adopted question open
|
|
873
|
+
* for the retry grace exactly as a wait that ran out would. The adopted
|
|
874
|
+
* question's OWN deadline is whatever was recorded when it was opened; this
|
|
875
|
+
* changes nothing about it.
|
|
876
|
+
*/
|
|
877
|
+
waitMs: number;
|
|
878
|
+
harness: HarnessKind;
|
|
879
|
+
originApp: string;
|
|
880
|
+
/** Exact native command bytes required in a Codex allow's identity update. */
|
|
881
|
+
codexCommand?: string;
|
|
882
|
+
/**
|
|
883
|
+
* The version the hook event stated, or `null` (APRV-227).
|
|
884
|
+
*
|
|
885
|
+
* Carried rather than resolved here: resolving it means a `spawnSync` of
|
|
886
|
+
* `<binary> --version`, and a hook process exists per gated tool call. The
|
|
887
|
+
* resolution happens at the one place that is about to WRITE a record and
|
|
888
|
+
* nowhere else, so the pass-through verdict and the autonomous verdict pay
|
|
889
|
+
* nothing for it. See {@link registrationProvenance}.
|
|
890
|
+
*/
|
|
891
|
+
eventVersion: string | null;
|
|
892
|
+
/**
|
|
893
|
+
* The channel names this policy configures, sorted (APRV-281).
|
|
894
|
+
*
|
|
895
|
+
* Read off the policy the caller already loaded, and used for ONE thing: the
|
|
896
|
+
* line this hook prints when it appends a request, so the agent and the
|
|
897
|
+
* operator watching its error stream are told where the question went. It
|
|
898
|
+
* resolves nothing and reaches no verdict. An empty list is a fact worth
|
|
899
|
+
* printing rather than a default to fill in: a request under a policy that
|
|
900
|
+
* configures no channel is a question nothing is delivering.
|
|
901
|
+
*/
|
|
902
|
+
channels: readonly string[];
|
|
903
|
+
}
|
|
904
|
+
/**
|
|
905
|
+
* The gated half: find what is already open for these bytes, request whatever
|
|
906
|
+
* is not, wait for the decisions, spend the grants. Returns the exit code of
|
|
907
|
+
* whatever verdict it printed.
|
|
908
|
+
*
|
|
909
|
+
* ## Requests are keyed by bytes, not by invocation (APRV-117)
|
|
910
|
+
*
|
|
911
|
+
* The action key is still `hook:<session>:<tool-use id>:<class>` and is still
|
|
912
|
+
* unique per invocation — what changed is that intake LOOKS for an earlier
|
|
913
|
+
* request about the same `{command, cwd}` before opening a new one, matching on
|
|
914
|
+
* the `payload_hash` recorded on `approval.requested`. Three outcomes per class,
|
|
915
|
+
* decided by `core/gate.ts`'s `findHarnessCarry`:
|
|
916
|
+
*
|
|
917
|
+
* - nothing to carry: register and request, exactly as before;
|
|
918
|
+
* - a pending request: **adopt** it — wait out the remainder of this
|
|
919
|
+
* invocation's window on somebody else's key, opening nothing. The approver's
|
|
920
|
+
* phone never shows two prompts for one command, because there is only ever
|
|
921
|
+
* one question;
|
|
922
|
+
* - an unspent grant inside the TTL: **carry** it — no wait, no prompt, and
|
|
923
|
+
* the grant is spent (once) before the allow is printed.
|
|
924
|
+
*
|
|
925
|
+
* ## Why the wait no longer ends in a withdrawal (APRV-106, revised)
|
|
926
|
+
*
|
|
927
|
+
* APRV-106 retracted the request when the wait elapsed, because a retried tool
|
|
928
|
+
* call was a new request with a new key and a late tap therefore authorized
|
|
929
|
+
* nothing: the human spent attention on a question whose asker had left. The
|
|
930
|
+
* carryover above removes the premise. A late tap now authorizes the retry, so
|
|
931
|
+
* the request stays open for the policy's TTL and the timeout says so.
|
|
932
|
+
*
|
|
933
|
+
* What still withdraws is every path where nothing can adopt the question: a
|
|
934
|
+
* SIGTERM or SIGINT (the session is going away), a thrown failure, and an intake
|
|
935
|
+
* refusal partway through a multi-class command (the command cannot proceed on
|
|
936
|
+
* any retry, so the classes already opened are noise in a human's queue). The
|
|
937
|
+
* signal handlers are installed for the duration of the wait ONLY, and removed
|
|
938
|
+
* in `finally`: a hook process is short-lived and borrowing the harness's
|
|
939
|
+
* signal disposition for longer than the loop would be a side effect nobody
|
|
940
|
+
* asked for.
|
|
941
|
+
*/
|
|
942
|
+
/**
|
|
943
|
+
* What the gate decided about one harness tool call, before anything is printed
|
|
944
|
+
* (APRV-361).
|
|
945
|
+
*
|
|
946
|
+
* {@link gateHarnessCall} produces it and {@link gateAndWait} renders it in the
|
|
947
|
+
* harness's own dialect. The split exists because a second caller answers in a
|
|
948
|
+
* protocol rather than on stdout: `cli/codex-bridge.ts` replies
|
|
949
|
+
* `{id, result: {decision}}` over the app-server's JSON-RPC connection, and it
|
|
950
|
+
* has to reach that decision through the SAME classify, register, request and
|
|
951
|
+
* wait this function runs. Two implementations of that sequence would be two
|
|
952
|
+
* gates, and the second one would be the one nobody reviewed.
|
|
953
|
+
*
|
|
954
|
+
* `code` and `detail` are kept apart rather than pre-joined, because the bridge
|
|
955
|
+
* records the code as a code (§11.1 invariant 7) where the hook prints the pair
|
|
956
|
+
* as one reason string.
|
|
957
|
+
*/
|
|
958
|
+
export type HarnessVerdict = {
|
|
959
|
+
permission: "allow";
|
|
960
|
+
reason: string;
|
|
961
|
+
} | {
|
|
962
|
+
permission: "deny";
|
|
963
|
+
code: string;
|
|
964
|
+
detail: string;
|
|
965
|
+
};
|
|
966
|
+
export declare function gateHarnessCall(streams: Streams, run: HookRun, classes: string[],
|
|
967
|
+
/**
|
|
968
|
+
* The bytes the grant binds to: `{command, cwd}` for a Bash call, the change
|
|
969
|
+
* itself for a file tool (APRV-124). Whatever this is, it is what reaches the
|
|
970
|
+
* approver's FULL PAYLOAD block, complete — the summary below is a headline
|
|
971
|
+
* and is the only thing here that may be shortened.
|
|
972
|
+
*/
|
|
973
|
+
payload: unknown, headline: string,
|
|
974
|
+
/**
|
|
975
|
+
* The task id this invocation acts under, minted once by the caller
|
|
976
|
+
* (APRV-139) so the loop-escalation check and the registration it may lead to
|
|
977
|
+
* name the same task. Deriving it twice would mint two ids whenever
|
|
978
|
+
* `tool_use_id` is absent and the random fallback runs.
|
|
979
|
+
*/
|
|
980
|
+
task: string,
|
|
981
|
+
/** The history-rewrite refinement's own words, or `""` (APRV-108). */
|
|
982
|
+
note?: string,
|
|
983
|
+
/**
|
|
984
|
+
* The harness streak that floors the SIDE-EFFECTING classes of this
|
|
985
|
+
* invocation to `manual` (APRV-145, narrowed by APRV-297), or `null` where
|
|
986
|
+
* policy alone sent it here.
|
|
987
|
+
*
|
|
988
|
+
* Passed into `request` as a boolean rather than acted on here, so the floored
|
|
989
|
+
* action takes the identical path a manual class takes — same records, same
|
|
990
|
+
* order, same wait — and nothing below knows how it got there. What the STATE
|
|
991
|
+
* adds (APRV-280) is the deny text: an agent whose commands are all suddenly
|
|
992
|
+
* on the phone is owed the reason and the way out in the same breath, and
|
|
993
|
+
* before APRV-280 the nine-minute wait ended in a bare `hook-timeout` that
|
|
994
|
+
* said neither.
|
|
995
|
+
*
|
|
996
|
+
* Since APRV-297 the caller passes `null` for a command whose classes are all
|
|
997
|
+
* reads, and {@link floorApplies} below carves the read classes out of a mixed
|
|
998
|
+
* one, so a floor never puts a question about looking on a human's phone.
|
|
999
|
+
*/
|
|
1000
|
+
floor?: HarnessLoopState | null): HarnessVerdict;
|
|
260
1001
|
/**
|
|
261
1002
|
* Every line the counterpart can print, closed and machine-readable (§11.1
|
|
262
1003
|
* invariant 7).
|
|
@@ -328,4 +1069,87 @@ export declare const REQUIRE_SANDBOX_ENV = "APPROVAL_HOOK_REQUIRE_SANDBOX";
|
|
|
328
1069
|
* invariant 4).
|
|
329
1070
|
*/
|
|
330
1071
|
export declare function sandboxRequirement(segments: readonly ClassifiedSegment[] | undefined, autonomies: readonly string[], env?: NodeJS.ProcessEnv): string | null;
|
|
331
|
-
|
|
1072
|
+
/**
|
|
1073
|
+
* What {@link decideHarnessCall} needs to reach a verdict (APRV-361).
|
|
1074
|
+
*
|
|
1075
|
+
* Everything here is established by the CALLER: which harness asked, where the
|
|
1076
|
+
* policy and the log are, who is acting, and how long this decision may take.
|
|
1077
|
+
* Nothing in it is authored by the party under oversight beyond `input`, which
|
|
1078
|
+
* is the harness's own event and is treated as such throughout.
|
|
1079
|
+
*/
|
|
1080
|
+
export interface DecideInput {
|
|
1081
|
+
streams: Streams;
|
|
1082
|
+
input: HookInput;
|
|
1083
|
+
adapter: HarnessAdapter;
|
|
1084
|
+
/** The directory a relative path in the call resolves against. */
|
|
1085
|
+
cwd: string;
|
|
1086
|
+
logPath: string;
|
|
1087
|
+
/** The scope root, named in the unreachable-log detail. */
|
|
1088
|
+
root: string;
|
|
1089
|
+
options: GateOptions;
|
|
1090
|
+
actor: string;
|
|
1091
|
+
timeoutMs: number;
|
|
1092
|
+
intervalMs: number;
|
|
1093
|
+
graceMs: number;
|
|
1094
|
+
/**
|
|
1095
|
+
* The harness's ceiling on this process, in milliseconds, or `null`
|
|
1096
|
+
* (APRV-423). See {@link HookRun.harnessCapMs}. A caller that is not run by a
|
|
1097
|
+
* harness on a timer — `cli/codex-bridge.ts` holds its own connection open —
|
|
1098
|
+
* passes `null` and the policy's TTL governs alone.
|
|
1099
|
+
*/
|
|
1100
|
+
harnessCapMs?: number | null;
|
|
1101
|
+
/**
|
|
1102
|
+
* Whether `harnessCapMs` was stated by the operator (`--harness-cap`) rather
|
|
1103
|
+
* than assumed from the adapter's documented defaults (APRV-423, second
|
|
1104
|
+
* review pass). Defaults to `false`; see {@link HookRun.harnessCapStated}.
|
|
1105
|
+
*/
|
|
1106
|
+
harnessCapStated?: boolean;
|
|
1107
|
+
/**
|
|
1108
|
+
* The ceiling `harnessCapMs` was charged down from, where an embedding
|
|
1109
|
+
* caller charged time spent before this invocation began (APRV-427 review).
|
|
1110
|
+
* Wording only: a refusal names both, so the operator's stated number is
|
|
1111
|
+
* not reported as the remainder.
|
|
1112
|
+
*/
|
|
1113
|
+
harnessCapCeilingMs?: number;
|
|
1114
|
+
/** Exact native command bytes a Codex allow must carry back, where there are any. */
|
|
1115
|
+
codexCommand?: string | undefined;
|
|
1116
|
+
/**
|
|
1117
|
+
* The verified records an open-window lookup already read, or `null`.
|
|
1118
|
+
*
|
|
1119
|
+
* Passed rather than re-read so the floor and the unattended guard are
|
|
1120
|
+
* decided from the same read the window was. A caller that performed no
|
|
1121
|
+
* lookup passes `null`, and both of them read the log themselves.
|
|
1122
|
+
*/
|
|
1123
|
+
windowRecords: EventRecord[] | null;
|
|
1124
|
+
/**
|
|
1125
|
+
* Where the wait begins and ends, for a caller that runs this beside other
|
|
1126
|
+
* writers in one process (APRV-427; see {@link HookWaitSeam}). Omitted by
|
|
1127
|
+
* every CLI route, where it is a no-op.
|
|
1128
|
+
*/
|
|
1129
|
+
waitSeam?: HookWaitSeam | null;
|
|
1130
|
+
}
|
|
1131
|
+
/**
|
|
1132
|
+
* Classify, resolve, gate and wait: one harness tool call, from the event to a
|
|
1133
|
+
* verdict (APRV-361).
|
|
1134
|
+
*
|
|
1135
|
+
* Extracted from the hook's own verb so a SECOND caller can reach a decision
|
|
1136
|
+
* through exactly this sequence. `cli/codex-bridge.ts` answers Codex's
|
|
1137
|
+
* app-server approval requests over JSON-RPC rather than on stdout, and the
|
|
1138
|
+
* thing it must not do is re-implement any of what is below: the human-only
|
|
1139
|
+
* refusal, the unruled `harness.launch.*` refusal, the sandbox requirement, the
|
|
1140
|
+
* loop floor, the unattended guard, the autonomous charge, and the register,
|
|
1141
|
+
* request and wait that follow. Two implementations of that sequence would be
|
|
1142
|
+
* two gates, and the second one would be the one nobody reviewed.
|
|
1143
|
+
*
|
|
1144
|
+
* It returns a verdict and prints none. `streams.err` still carries the
|
|
1145
|
+
* progress and withdrawal lines, which are a report rather than a decision.
|
|
1146
|
+
*/
|
|
1147
|
+
export declare function decideHarnessCall(decide: DecideInput): HarnessVerdict;
|
|
1148
|
+
export declare function commandHook(argv: string[], streams: Streams, cwd: string, readStdin?: () => string,
|
|
1149
|
+
/**
|
|
1150
|
+
* APRV-427: where a harness hook's wait begins and ends, for `approval serve`,
|
|
1151
|
+
* which runs the call off its main thread and releases its store lock while
|
|
1152
|
+
* the call polls. Every CLI route omits it. See {@link HookWaitSeam}.
|
|
1153
|
+
*/
|
|
1154
|
+
waitSeam?: HookWaitSeam | null): number;
|
|
1155
|
+
export {};
|