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
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WHICH daemon wrote this record, and the check the write boundary runs on it
|
|
3
|
+
* (APRV-383).
|
|
4
|
+
*
|
|
5
|
+
* ## The fact this module is about
|
|
6
|
+
*
|
|
7
|
+
* `core/daemon-actor.ts` answers "is this process the daemon". Every record the
|
|
8
|
+
* daemon appends carried that and nothing more: a generic `system:daemon` actor,
|
|
9
|
+
* one string, identical on every machine that ever ran one. That is enough while
|
|
10
|
+
* a gate's daemon is the operator's own process in the operator's own checkout,
|
|
11
|
+
* and it stops being enough the moment one party HOSTS a daemon for another.
|
|
12
|
+
* Three properties of that arrangement already hold: authority comes from the
|
|
13
|
+
* tenant's attested policy and never from the host (§5.2, APRV-324 and 356),
|
|
14
|
+
* credentials come from the launch environment so one process holds one tenant's
|
|
15
|
+
* token, and a decision arrives only through the tenant's own sender mapping so
|
|
16
|
+
* the host cannot mint one. What was missing is the smallest of the four and the
|
|
17
|
+
* one a tenant reads first: opening their own log and seeing which daemon acted
|
|
18
|
+
* on their behalf.
|
|
19
|
+
*
|
|
20
|
+
* So a daemon-written record carries a `daemon` field naming the instance that
|
|
21
|
+
* wrote it, and an attested policy MAY list the ids permitted to write
|
|
22
|
+
* (`daemons`, read by `core/daemon-host.ts`). An id the list does not name is
|
|
23
|
+
* refused at the write boundary.
|
|
24
|
+
*
|
|
25
|
+
* ## What the id is NOT, and this is the important half
|
|
26
|
+
*
|
|
27
|
+
* It is a SELF-REPORTED field, so SPEC.md §11.1 invariant 4 governs it
|
|
28
|
+
* completely: it may raise scrutiny and may never lower it. Concretely, in this
|
|
29
|
+
* runtime:
|
|
30
|
+
*
|
|
31
|
+
* - a listed id gains NOTHING. No verdict, no autonomy, no budget, no floor, no
|
|
32
|
+
* sampling draw, no token and no TTL reads this field or the list. Being named
|
|
33
|
+
* in `daemons` buys a daemon exactly the ability it had before the key existed;
|
|
34
|
+
* - an unlisted id is REFUSED, which is the only direction the field moves
|
|
35
|
+
* anything, and it moves it strictly towards writing less;
|
|
36
|
+
* - the id never enters a payload hash and never enters a token. It is a
|
|
37
|
+
* top-level record field, so the record's own chain hash covers it the way it
|
|
38
|
+
* covers `actor` (a record's provenance has to be part of what the chain
|
|
39
|
+
* protects, or it could be edited without breaking the chain), and no bound
|
|
40
|
+
* payload, no `payload_hash` and no `token_sha256` is computed over it.
|
|
41
|
+
*
|
|
42
|
+
* And it is ATTRIBUTION rather than authorization. A hostile host process holds
|
|
43
|
+
* the tenant's log handle and can call anything in this module the daemon calls,
|
|
44
|
+
* exactly as it can call {@link markDaemonProcess}; `core/daemon-actor.ts` states
|
|
45
|
+
* that boundary and this module inherits it word for word. What the allowlist
|
|
46
|
+
* catches is the likelier failure and the one nothing caught before: a daemon
|
|
47
|
+
* started against the wrong tenant's log, a second daemon nobody meant to leave
|
|
48
|
+
* running, a container rebuilt with a new id, each of which used to write records
|
|
49
|
+
* indistinguishable from the intended daemon's.
|
|
50
|
+
*
|
|
51
|
+
* ## Why the state is module state and not an argument
|
|
52
|
+
*
|
|
53
|
+
* For the reason `core/daemon-actor.ts` gives for taking no arguments: a field a
|
|
54
|
+
* caller passes is a field a caller chooses, and an `appendEvent` option naming
|
|
55
|
+
* the writing daemon would let any process in this runtime claim to be one. The
|
|
56
|
+
* daemon runtime declares its own identity once, at construction, through
|
|
57
|
+
* `core/daemon-host.ts`, and `core/log.ts` READS it. `EventInput` deliberately
|
|
58
|
+
* has no `daemon` member, and `buildRecord` composes a record field by field, so
|
|
59
|
+
* a caller that puts the property on its input object anyway is ignored rather
|
|
60
|
+
* than obeyed.
|
|
61
|
+
*
|
|
62
|
+
* ## Which processes declare, and why the stamp does not ask for the mark
|
|
63
|
+
*
|
|
64
|
+
* Three processes append on a gate's behalf without being a session, and since
|
|
65
|
+
* APRV-448 all three declare the same identity through the same two calls in
|
|
66
|
+
* `core/daemon-host.ts` (`declareDaemonIdentityFor` or
|
|
67
|
+
* `declareResolvedDaemonIdentity`, then `refreshDaemonAllowlist*`): the daemon
|
|
68
|
+
* loop (`daemon/daemon.ts`, at construction), `approval serve` (its verbs on the
|
|
69
|
+
* listener's thread and its hook calls on their worker threads), and `approval
|
|
70
|
+
* channel telegram webhook`. In a co-located deployment, where a facade, a
|
|
71
|
+
* daemon and a channel run in one process per tenant, a tenant reading their
|
|
72
|
+
* own log therefore sees one id on the sweeps and on the actions.
|
|
73
|
+
*
|
|
74
|
+
* The stamp is keyed on the DECLARATION, not on `core/daemon-actor.ts`'s mark.
|
|
75
|
+
* The two answer different questions: the mark says "this process is the daemon
|
|
76
|
+
* loop" and is what routes an advance under the autonomous
|
|
77
|
+
* `log.advance.daemon` class (APRV-382), while the declaration says "records
|
|
78
|
+
* this process writes name this instance". `approval serve` dispatches verbs an
|
|
79
|
+
* agent asked for, so marking it the daemon to get the stamp would hand its
|
|
80
|
+
* process the daemon's advance route, a widening nothing here needs. Declaring
|
|
81
|
+
* without marking stamps and restricts, and widens nothing.
|
|
82
|
+
*
|
|
83
|
+
* ## Why this module imports almost nothing
|
|
84
|
+
*
|
|
85
|
+
* `core/log.ts` imports it, and `core/log.ts` is the bottom of this codebase's
|
|
86
|
+
* import graph: `core/instance.ts` (where the derived id comes from) reaches
|
|
87
|
+
* `core/attest.ts` through `core/env-file.ts`, and `core/attest.ts` spreads
|
|
88
|
+
* `APPEND_ERROR_CODES` at module scope, so importing the
|
|
89
|
+
* derivation here would close a cycle whose evaluation order throws before any
|
|
90
|
+
* test ran. The split is also the right seam on its own terms: what the write
|
|
91
|
+
* boundary needs is a grammar, a state and one question, and everything that
|
|
92
|
+
* reads a policy or a filesystem to ANSWER that question lives in
|
|
93
|
+
* `core/daemon-host.ts`.
|
|
94
|
+
*/
|
|
95
|
+
/**
|
|
96
|
+
* The environment variable a launch may declare the daemon's id in.
|
|
97
|
+
*
|
|
98
|
+
* Beside `APPROVAL_HUMAN` and the credential variables, and in the launch
|
|
99
|
+
* environment for the same reason they are: SPEC.md §11.1 invariant 7 keeps this
|
|
100
|
+
* runtime from reading configuration out of the working tree, and a hosted
|
|
101
|
+
* daemon's id is configuration its OPERATOR sets, never something the tenant's
|
|
102
|
+
* repository tells the host about itself.
|
|
103
|
+
*/
|
|
104
|
+
export const DAEMON_ID_ENV = "APPROVAL_DAEMON_ID";
|
|
105
|
+
/** The longest id this runtime writes or admits. */
|
|
106
|
+
export const DAEMON_ID_MAX_LENGTH = 64;
|
|
107
|
+
/**
|
|
108
|
+
* The shape an id takes: lowercase alphanumerics, dots, dashes and underscores,
|
|
109
|
+
* starting with an alphanumeric.
|
|
110
|
+
*
|
|
111
|
+
* Narrow on purpose, and for the reason `payload.harness_version` is narrow
|
|
112
|
+
* (APRV-227): the value is a string from a launch environment being written into
|
|
113
|
+
* an append-only log, so a banner, a path, a newline or a shell fragment must not
|
|
114
|
+
* be able to arrive through it. It is also a value an operator COMPARES by eye
|
|
115
|
+
* against `approval doctor` and against the `daemons` list in their policy, and
|
|
116
|
+
* two ids differing only in case are two ids nobody can tell apart on a phone.
|
|
117
|
+
*/
|
|
118
|
+
const DAEMON_ID_PATTERN = /^[a-z0-9][a-z0-9._-]*$/u;
|
|
119
|
+
/** Is `value` a well-formed daemon id? */
|
|
120
|
+
export function isDaemonId(value) {
|
|
121
|
+
return value.length <= DAEMON_ID_MAX_LENGTH && DAEMON_ID_PATTERN.test(value);
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* The two ways an append can be refused for the identity of the daemon making
|
|
125
|
+
* it, spread into `APPEND_ERROR_CODES` by `core/log.ts`.
|
|
126
|
+
*
|
|
127
|
+
* Two codes rather than one, because the repairs have nothing in common and
|
|
128
|
+
* SPEC.md §11.1 invariant 6 is about a caller being able to act on the answer:
|
|
129
|
+
* one is a line to add to an attested policy, the other is a variable to fix in
|
|
130
|
+
* the launch environment of a process that should not have started.
|
|
131
|
+
*
|
|
132
|
+
* Declared here rather than in `core/log.ts`'s literal list so that the codes and
|
|
133
|
+
* the logic that emits them cannot drift apart; the union there spreads this one,
|
|
134
|
+
* exactly as `ATTEST_ERROR_CODES` spreads it.
|
|
135
|
+
*/
|
|
136
|
+
export const DAEMON_APPEND_REFUSAL_CODES = [
|
|
137
|
+
/**
|
|
138
|
+
* The daemon declared an id through {@link DAEMON_ID_ENV} that is not a
|
|
139
|
+
* well-formed id at all, so no record it wrote could be attributed to anything.
|
|
140
|
+
*
|
|
141
|
+
* Refused rather than silently falling back to the derived id, which is the
|
|
142
|
+
* fail-closed direction twice over: an operator who set the variable meant the
|
|
143
|
+
* value they set, and a runtime that quietly used a different one would write
|
|
144
|
+
* records under a name nobody chose, against an allowlist nobody wrote for it.
|
|
145
|
+
*/
|
|
146
|
+
"daemon-id-invalid",
|
|
147
|
+
/**
|
|
148
|
+
* The attested policy lists the daemon ids that may write to this log, and this
|
|
149
|
+
* daemon's id is not among them.
|
|
150
|
+
*
|
|
151
|
+
* An ABSENT list is no restriction, not an empty one: a policy that never
|
|
152
|
+
* declares the key behaves exactly as every policy written before it existed.
|
|
153
|
+
*/
|
|
154
|
+
"daemon-not-allowed",
|
|
155
|
+
];
|
|
156
|
+
/** The declaring process's own state. Never set on another process's behalf. */
|
|
157
|
+
let identity = null;
|
|
158
|
+
/**
|
|
159
|
+
* The pid that made the declaration, for the reason `core/daemon-actor.ts` keeps
|
|
160
|
+
* one: the stamp answers only while it is still this process's own. Kept beside
|
|
161
|
+
* {@link identity} rather than inside it, so the reported state is unchanged.
|
|
162
|
+
*/
|
|
163
|
+
let declaredPid = null;
|
|
164
|
+
/**
|
|
165
|
+
* Declare the id this process's records carry. Called through
|
|
166
|
+
* `core/daemon-host.ts` by the processes the header names (the daemon loop,
|
|
167
|
+
* `approval serve` and its hook threads, the Telegram webhook listener), and by
|
|
168
|
+
* nothing else.
|
|
169
|
+
*
|
|
170
|
+
* Takes the RESOLVED values rather than resolving them, because resolution reads
|
|
171
|
+
* the environment and the filesystem and this module is the one `core/log.ts`
|
|
172
|
+
* imports (see the header). Idempotent, and a second call replaces the first: a
|
|
173
|
+
* runtime that re-resolves its identity is reporting a fact about itself, and the
|
|
174
|
+
* newest reading is the one in force.
|
|
175
|
+
*/
|
|
176
|
+
export function declareDaemonIdentity(state) {
|
|
177
|
+
identity = {
|
|
178
|
+
id: state.id,
|
|
179
|
+
declared: state.declared ?? null,
|
|
180
|
+
source: state.source,
|
|
181
|
+
// A fresh declaration carries no allowlist. The daemon resolves one from its
|
|
182
|
+
// attested policy and sets it separately, so an identity re-declared mid-run
|
|
183
|
+
// cannot silently drop a restriction that was in force.
|
|
184
|
+
allowed: identity === null ? null : identity.allowed,
|
|
185
|
+
};
|
|
186
|
+
declaredPid = process.pid;
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Record which daemon ids this log's ATTESTED policy admits.
|
|
190
|
+
*
|
|
191
|
+
* Called only after a successful resolution: the daemon loaded its policy, read
|
|
192
|
+
* the verified log, and the attestation matched (`core/daemon-host.ts`). A
|
|
193
|
+
* resolution that FAILED calls nothing, so the restriction last resolved stays in
|
|
194
|
+
* force rather than lapsing — a policy that becomes unreadable must not be a way
|
|
195
|
+
* to escape the list it carried a moment ago.
|
|
196
|
+
*
|
|
197
|
+
* `null` means the attested policy declares none, which is no restriction.
|
|
198
|
+
*/
|
|
199
|
+
export function setDaemonAllowlist(allowed) {
|
|
200
|
+
if (identity === null)
|
|
201
|
+
return;
|
|
202
|
+
identity = { ...identity, allowed: allowed === null ? null : [...allowed] };
|
|
203
|
+
}
|
|
204
|
+
/** Forget the declaration. For tests, and for a runtime that stops being one. */
|
|
205
|
+
export function clearDaemonIdentity() {
|
|
206
|
+
identity = null;
|
|
207
|
+
declaredPid = null;
|
|
208
|
+
}
|
|
209
|
+
/** What this process has declared about itself, or `null`. */
|
|
210
|
+
export function daemonIdentity() {
|
|
211
|
+
return identity;
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* The write boundary's question, answered from this process's own state
|
|
215
|
+
* (APRV-383).
|
|
216
|
+
*
|
|
217
|
+
* Both halves of the answer are here rather than in two functions, because they
|
|
218
|
+
* are one decision made in one order: a process that cannot name itself is
|
|
219
|
+
* refused before anything asks whether its name is allowed.
|
|
220
|
+
*
|
|
221
|
+
* A process that declared NO identity gets `absent`, whether or not it marked
|
|
222
|
+
* itself the daemon. That is every session, every hook process and the
|
|
223
|
+
* pre-APRV-383 daemon, and the records they write are the records they always
|
|
224
|
+
* wrote; turning an undeclared mark into a dead log would make an unrelated
|
|
225
|
+
* module's `markDaemonProcess` call a stop-the-world bug. The answer is keyed on
|
|
226
|
+
* the declaration alone (APRV-448, see the header): the daemon loop marks and
|
|
227
|
+
* declares together, while `approval serve` and the webhook listener declare
|
|
228
|
+
* without marking, because the mark carries the daemon's advance route and the
|
|
229
|
+
* declaration carries nothing but a name and a restriction.
|
|
230
|
+
*/
|
|
231
|
+
export function daemonStampForAppend() {
|
|
232
|
+
const state = identity;
|
|
233
|
+
if (state === null || declaredPid !== process.pid)
|
|
234
|
+
return { kind: "absent" };
|
|
235
|
+
if (state.id === null) {
|
|
236
|
+
return {
|
|
237
|
+
kind: "refuse",
|
|
238
|
+
code: "daemon-id-invalid",
|
|
239
|
+
message: `this daemon declared ${DAEMON_ID_ENV}=${JSON.stringify(state.declared ?? "")}, which is not a usable daemon id (lowercase letters, digits, \`.\`, \`-\` and \`_\`, starting with a letter or digit, at most ${String(DAEMON_ID_MAX_LENGTH)} characters), so no record it wrote could name which daemon wrote it. Nothing was written: fix ${DAEMON_ID_ENV} in this process's launch environment, or unset it and let the id derive from this instance`,
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
const allowed = state.allowed;
|
|
243
|
+
if (allowed !== null && !allowed.includes(state.id)) {
|
|
244
|
+
return {
|
|
245
|
+
kind: "refuse",
|
|
246
|
+
code: "daemon-not-allowed",
|
|
247
|
+
message: `this daemon's id is ${state.id} and the attested policy's \`daemons\` list admits ${allowed.length === 0 ? "no daemon at all" : allowed.join(", ")}, so it may not write to this log. Nothing was written: add ${state.id} to \`daemons\` and re-attest the policy, or run the daemon whose id the list already names`,
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
return { kind: "stamp", id: state.id };
|
|
251
|
+
}
|
|
252
|
+
//# sourceMappingURL=daemon-identity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"daemon-identity.js","sourceRoot":"","sources":["../../../src/core/daemon-identity.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6FG;AAEH;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,oBAAoB,CAAC;AAElD,oDAAoD;AACpD,MAAM,CAAC,MAAM,oBAAoB,GAAG,EAAE,CAAC;AAEvC;;;;;;;;;;GAUG;AACH,MAAM,iBAAiB,GAAG,yBAAyB,CAAC;AAEpD,0CAA0C;AAC1C,MAAM,UAAU,UAAU,CAAC,KAAa;IACtC,OAAO,KAAK,CAAC,MAAM,IAAI,oBAAoB,IAAI,iBAAiB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AAC/E,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,MAAM,2BAA2B,GAAG;IACzC;;;;;;;;OAQG;IACH,mBAAmB;IACnB;;;;;;OAMG;IACH,oBAAoB;CACZ,CAAC;AAkCX,gFAAgF;AAChF,IAAI,QAAQ,GAA0B,IAAI,CAAC;AAE3C;;;;GAIG;AACH,IAAI,WAAW,GAAkB,IAAI,CAAC;AAEtC;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,qBAAqB,CAAC,KAIrC;IACC,QAAQ,GAAG;QACT,EAAE,EAAE,KAAK,CAAC,EAAE;QACZ,QAAQ,EAAE,KAAK,CAAC,QAAQ,IAAI,IAAI;QAChC,MAAM,EAAE,KAAK,CAAC,MAAM;QACpB,6EAA6E;QAC7E,6EAA6E;QAC7E,wDAAwD;QACxD,OAAO,EAAE,QAAQ,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO;KACrD,CAAC;IACF,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC;AAC5B,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,kBAAkB,CAAC,OAAiC;IAClE,IAAI,QAAQ,KAAK,IAAI;QAAE,OAAO;IAC9B,QAAQ,GAAG,EAAE,GAAG,QAAQ,EAAE,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,EAAE,CAAC;AAC9E,CAAC;AAED,iFAAiF;AACjF,MAAM,UAAU,mBAAmB;IACjC,QAAQ,GAAG,IAAI,CAAC;IAChB,WAAW,GAAG,IAAI,CAAC;AACrB,CAAC;AAED,8DAA8D;AAC9D,MAAM,UAAU,cAAc;IAC5B,OAAO,QAAQ,CAAC;AAClB,CAAC;AAWD;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,oBAAoB;IAClC,MAAM,KAAK,GAAG,QAAQ,CAAC;IACvB,IAAI,KAAK,KAAK,IAAI,IAAI,WAAW,KAAK,OAAO,CAAC,GAAG;QAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;IAE7E,IAAI,KAAK,CAAC,EAAE,KAAK,IAAI,EAAE,CAAC;QACtB,OAAO;YACL,IAAI,EAAE,QAAQ;YACd,IAAI,EAAE,mBAAmB;YACzB,OAAO,EAAE,wBAAwB,aAAa,IAAI,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,QAAQ,IAAI,EAAE,CAAC,kIAAkI,MAAM,CAAC,oBAAoB,CAAC,kGAAkG,aAAa,6FAA6F;SACjc,CAAC;IACJ,CAAC;IAED,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC;IAC9B,IAAI,OAAO,KAAK,IAAI,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC;QACpD,OAAO;YACL,IAAI,EAAE,QAAQ;YACd,IAAI,EAAE,oBAAoB;YAC1B,OAAO,EAAE,uBAAuB,KAAK,CAAC,EAAE,sDAAsD,OAAO,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,+DAA+D,KAAK,CAAC,EAAE,6FAA6F;SACjU,CAAC;IACJ,CAAC;IAED,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC;AACzC,CAAC"}
|
|
@@ -59,6 +59,35 @@
|
|
|
59
59
|
* cost of the alternative (no branch key at all) is an alarm on every worktree
|
|
60
60
|
* whose session worked entirely outside the payload store's reach.
|
|
61
61
|
*
|
|
62
|
+
* ## What unit arm A judges, merges included (APRV-374)
|
|
63
|
+
*
|
|
64
|
+
* One commit, base to commit, which is the unit a grant binds and the unit the
|
|
65
|
+
* CI guard judges since APRV-375. The inputs are built by
|
|
66
|
+
* `core/commit-guard.ts`, so the two sides cannot drift apart.
|
|
67
|
+
*
|
|
68
|
+
* A MERGE commit is judged on what it invented and on nothing else. `git log
|
|
69
|
+
* --name-only` reports no path at all for a merge, so until APRV-374 every
|
|
70
|
+
* merge dropped out of arm A, which is right for an ordinary merge and wrong
|
|
71
|
+
* for the evil one: a conflict resolution, or a hand edit made while resolving,
|
|
72
|
+
* is the merge commit's own work, made in the checkout that made the merge, and
|
|
73
|
+
* no other commit carries those bytes. So a merge is judged for the paths whose
|
|
74
|
+
* DENSE COMBINED PATCH (`git log --cc`, {@link denseCombinedPaths}) is
|
|
75
|
+
* non-empty, against its FIRST PARENT, which is the state the checkout was in
|
|
76
|
+
* before the merge and therefore the before-state a grant for the resolution
|
|
77
|
+
* would have bound.
|
|
78
|
+
*
|
|
79
|
+
* The dense patch and not `--cc --name-only`, which over-reports: a merge blob
|
|
80
|
+
* differs from every parent blob whenever both sides edited one file, so
|
|
81
|
+
* `--name-only` lists a file two lanes happened to edit concurrently and git
|
|
82
|
+
* merged without a human in the room. Replaying that as the merge's own change
|
|
83
|
+
* would report dark for work every side of it had evidence for, which is the
|
|
84
|
+
* class of false alarm APRV-369 and APRV-375 exist to remove. The dense patch
|
|
85
|
+
* is empty for exactly those merges and non-empty for the resolutions.
|
|
86
|
+
*
|
|
87
|
+
* Scope is the guard's scope: guarded paths. A merge that resolved a conflict
|
|
88
|
+
* in a file no policy protects is not this detector's business, so the dense
|
|
89
|
+
* question is only ever asked about paths {@link isGuardedPath} accepts.
|
|
90
|
+
*
|
|
62
91
|
* ## Fail closed in the report
|
|
63
92
|
*
|
|
64
93
|
* Uncertainty is never "fine". A log that does not verify, a payload whose
|
|
@@ -90,9 +119,13 @@
|
|
|
90
119
|
*
|
|
91
120
|
* ## Layout
|
|
92
121
|
*
|
|
93
|
-
* The evaluator ({@link evaluateDarkSessions})
|
|
94
|
-
*
|
|
95
|
-
*
|
|
122
|
+
* The evaluator ({@link evaluateDarkSessions}) takes no clock and reads no
|
|
123
|
+
* file, so a fixture drives everything but arm A's replay: the ONE thing it
|
|
124
|
+
* runs git for is reading the blobs of a commit it is judging, which cannot be
|
|
125
|
+
* carried in the observation because which blobs are wanted is not known until
|
|
126
|
+
* the verdict is being computed. A fixture whose commits change no guarded path
|
|
127
|
+
* needs no repository at all. The observing half below the fold runs the rest
|
|
128
|
+
* of the git and reads the payload store,
|
|
96
129
|
* and it lives here rather than under `daemon/` because `approval doctor`
|
|
97
130
|
* reports the same findings and `src/cli/` may not import `src/daemon/`
|
|
98
131
|
* (APRV-59). The one thing that stays in `daemon/dark-session.ts` is the
|
|
@@ -127,6 +160,16 @@ export declare const DARK_SESSION_CODES: readonly [
|
|
|
127
160
|
"no-records",
|
|
128
161
|
/** Arm A: a guarded path changed and the log carries no evidence for it. */
|
|
129
162
|
"no-evidence",
|
|
163
|
+
/**
|
|
164
|
+
* Arm A, and EVERY failing commit arrived here through a merge (APRV-369).
|
|
165
|
+
*
|
|
166
|
+
* The same failure as `no-evidence` and the same `dark` verdict — nothing is
|
|
167
|
+
* excused by it — reported under its own code because the checkout it names
|
|
168
|
+
* is the one that SYNCED the change rather than the one that made it. The
|
|
169
|
+
* repair is in whatever branch or worktree authored the commit, which the
|
|
170
|
+
* detail names.
|
|
171
|
+
*/
|
|
172
|
+
"no-evidence-merged",
|
|
130
173
|
/** Exempt: the commits touch only the daemon's own append surface. */
|
|
131
174
|
"evidence-surface",
|
|
132
175
|
/** Exempt: authored by the daemon's own git identity. */
|
|
@@ -142,6 +185,18 @@ export declare const DARK_SESSION_CODES: readonly [
|
|
|
142
185
|
/** Undetermined: git named no usable date for the activity. */
|
|
143
186
|
"activity-undated"];
|
|
144
187
|
export type DarkSessionCode = (typeof DARK_SESSION_CODES)[number];
|
|
188
|
+
/**
|
|
189
|
+
* The codes a `dark` verdict can carry, and therefore the ones that reach the
|
|
190
|
+
* log on an `audit.dark_session` record (APRV-369).
|
|
191
|
+
*
|
|
192
|
+
* A separate, smaller list than {@link DARK_SESSION_CODES} because it is the
|
|
193
|
+
* one the event schema constrains: an exempt or undetermined subject is never
|
|
194
|
+
* recorded, so its code has no business in a write-boundary enum. Pinned equal
|
|
195
|
+
* to that enum by a test, for the reason APRV-358 gave: a code the sweep can
|
|
196
|
+
* produce and the schema refuses is an observation that never reaches a human,
|
|
197
|
+
* and nothing fails loudly when it happens.
|
|
198
|
+
*/
|
|
199
|
+
export declare const DARK_VERDICT_CODES: readonly ["no-records", "no-evidence", "no-evidence-merged"];
|
|
145
200
|
/** One commit the observer saw, as git reported it. */
|
|
146
201
|
export interface ObservedCommit {
|
|
147
202
|
/** The full sha. */
|
|
@@ -152,10 +207,42 @@ export interface ObservedCommit {
|
|
|
152
207
|
author: string;
|
|
153
208
|
/** The author's email alone, lowercased, or `""` when git named none. */
|
|
154
209
|
authorEmail: string;
|
|
155
|
-
/**
|
|
210
|
+
/**
|
|
211
|
+
* Repository-relative, `/`-separated paths this commit changed.
|
|
212
|
+
*
|
|
213
|
+
* For a MERGE commit these are the guarded paths whose result differs from
|
|
214
|
+
* every parent hunk by hunk — the merge's own resolution work, and nothing
|
|
215
|
+
* its parents did (APRV-374). An ordinary merge has none.
|
|
216
|
+
*/
|
|
156
217
|
changedPaths: readonly string[];
|
|
218
|
+
/**
|
|
219
|
+
* The commit's parents in git's own order, when the observer asked.
|
|
220
|
+
*
|
|
221
|
+
* OPTIONAL, and absence means "unknown", which the per-commit input builder
|
|
222
|
+
* reads as "assume it has a parent": the modest direction, since the base it
|
|
223
|
+
* then judges against is the first parent rather than nothing at all.
|
|
224
|
+
* Two or more parents is what makes a commit a merge here.
|
|
225
|
+
*/
|
|
226
|
+
parents?: readonly string[];
|
|
157
227
|
/** The branch or worktree this commit was observed on, for the message. */
|
|
158
228
|
ref: string;
|
|
229
|
+
/**
|
|
230
|
+
* Did this commit reach the checkout through a merge rather than being made
|
|
231
|
+
* on it? (APRV-369.)
|
|
232
|
+
*
|
|
233
|
+
* True when the commit is reachable from the observed HEAD but is NOT on its
|
|
234
|
+
* first-parent history, which is what "somebody else's branch was merged in"
|
|
235
|
+
* looks like in git: a lane's commits, a contributor's, or anything that
|
|
236
|
+
* arrived by fast-forward from the trunk. A commit made in this checkout and
|
|
237
|
+
* committed on the branch it is standing on is on the first-parent path and
|
|
238
|
+
* is `false`.
|
|
239
|
+
*
|
|
240
|
+
* OPTIONAL, and absence means `false` — attributed to this checkout. That is
|
|
241
|
+
* the direction that keeps a caller written before this field existed
|
|
242
|
+
* honest: an observer that cannot say where a commit came from must not be
|
|
243
|
+
* read as saying it came from somewhere else.
|
|
244
|
+
*/
|
|
245
|
+
arrivedByMerge?: boolean;
|
|
159
246
|
}
|
|
160
247
|
/** One checkout the observer saw: the primary, or a linked worktree. */
|
|
161
248
|
export interface ObservedCheckout {
|
|
@@ -298,8 +385,16 @@ export interface DarkSessionWatch {
|
|
|
298
385
|
/** How often a sweep runs. The tick interval is the floor. */
|
|
299
386
|
intervalMs: number;
|
|
300
387
|
}
|
|
301
|
-
/**
|
|
302
|
-
|
|
388
|
+
/**
|
|
389
|
+
* Everything git could tell the sweep about a repository in one window.
|
|
390
|
+
*
|
|
391
|
+
* `protectedPaths` is the policy's `protected_paths`, and omitting it asks the
|
|
392
|
+
* merge question about the BUILT-IN guarded set only. That is the fail-closed
|
|
393
|
+
* direction for a caller that has no policy to hand: it narrows which merges
|
|
394
|
+
* are examined and never which evidence is required, and
|
|
395
|
+
* {@link reportDarkSessions} always passes the loaded list.
|
|
396
|
+
*/
|
|
397
|
+
export declare function observeGitActivity(root: string, from: string, to: string, protectedPaths?: readonly ProtectedPathEntry[]): GitActivity;
|
|
303
398
|
export interface DarkSessionSweepOptions {
|
|
304
399
|
logPath: string;
|
|
305
400
|
/** The checkout to observe. The daemon's own `cwd`; never an agent worktree. */
|
|
@@ -316,8 +411,14 @@ export interface DarkSessionSweepOptions {
|
|
|
316
411
|
/** Verified records. The caller has already read them; nothing re-reads. */
|
|
317
412
|
records: readonly EventRecord[] | null;
|
|
318
413
|
logDetail?: string;
|
|
319
|
-
/**
|
|
320
|
-
|
|
414
|
+
/**
|
|
415
|
+
* Test seam: an observer that answers without running git.
|
|
416
|
+
*
|
|
417
|
+
* It is offered the policy's protected paths as a fourth argument, which the
|
|
418
|
+
* real observer needs to know which merges to ask git about (APRV-374); a
|
|
419
|
+
* seam that ignores it answers exactly as it did before.
|
|
420
|
+
*/
|
|
421
|
+
observe?: (root: string, from: string, to: string, protectedPaths: readonly ProtectedPathEntry[]) => GitActivity;
|
|
321
422
|
}
|
|
322
423
|
/**
|
|
323
424
|
* Observe and judge, appending nothing.
|