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
|
@@ -2,13 +2,16 @@
|
|
|
2
2
|
* How long a harness hook waits, and how long the question it opened outlives
|
|
3
3
|
* that wait (APRV-287).
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
* rather than beside
|
|
7
|
-
* request it opened has been abandoned; `cli/channel-telegram.ts` uses the wait
|
|
5
|
+
* Three numbers with two readers each, which is the whole reason they live
|
|
6
|
+
* here rather than beside any one of them. `cli/hook.ts` uses them to decide
|
|
7
|
+
* when a request it opened has been abandoned; `cli/channel-telegram.ts` uses the wait
|
|
8
8
|
* to decide which pending requests on a listener's first cycle are stale enough
|
|
9
9
|
* to collapse into one message. Two copies of the same duration would be two
|
|
10
10
|
* answers to "is anybody still waiting on this", and the channel would collapse
|
|
11
11
|
* questions the hook was still holding open, or leave a dead queue on a phone.
|
|
12
|
+
* The third, APRV-423's margin, is read by the hook (which refuses a harness
|
|
13
|
+
* ceiling too short to hold a human's answer) and by `core/state.ts` (which
|
|
14
|
+
* judges every request against the window that ceiling leaves).
|
|
12
15
|
*
|
|
13
16
|
* Nothing here reads a clock, a policy or the log: these are durations, and
|
|
14
17
|
* every judgment made with them is made by a caller that already holds the
|
|
@@ -53,3 +56,79 @@ export declare const HOOK_RETRY_GRACE_MS: number;
|
|
|
53
56
|
* caller states about when it started waiting.
|
|
54
57
|
*/
|
|
55
58
|
export declare function abandonedAfterMs(waitMs: number, graceMs: number): number;
|
|
59
|
+
/**
|
|
60
|
+
* How far below a harness's own timeout a hook-originated request must lapse
|
|
61
|
+
* (APRV-423).
|
|
62
|
+
*
|
|
63
|
+
* A harness bounds the hook process: Hermes caps a `pre_tool_call` entry at
|
|
64
|
+
* 300 s, Claude Code kills the hook at the `timeout` in its settings file, and
|
|
65
|
+
* every one of them treats the killed process as a NON-BLOCKING error and runs
|
|
66
|
+
* the tool call. So when the harness gives up first the hook is already gone,
|
|
67
|
+
* the request is still pending on somebody's phone, and the tap that arrives
|
|
68
|
+
* afterwards is recorded as a grant on a call nobody holds (APRV-410, observed
|
|
69
|
+
* 2026-09-20). The two records then disagree about the same moment: one says
|
|
70
|
+
* denied, the other says approved.
|
|
71
|
+
*
|
|
72
|
+
* The fix is to make the question end BEFORE the process that asked it does,
|
|
73
|
+
* and this is the distance between the two. It is 60 s because the daemon's
|
|
74
|
+
* TTL sweep runs on a 30 s interval (`daemon/daemon.ts`'s
|
|
75
|
+
* `DEFAULT_INTERVAL_MS`): a request that lapses one millisecond after a sweep
|
|
76
|
+
* waits a whole interval for the next one, so a margin of two intervals leaves
|
|
77
|
+
* the `approval.expired` append a full tick of room in the common case, and it
|
|
78
|
+
* lands inside the cap. Anything smaller would make the ordering this task is
|
|
79
|
+
* about depend on where in the sweep cycle the lapse fell.
|
|
80
|
+
*
|
|
81
|
+
* The common case, and not a guarantee. `Daemon.tick` skips a sweep when the
|
|
82
|
+
* previous tick is still running, and the request record's `ts` is stamped at
|
|
83
|
+
* the write boundary, some intake latency after the harness spawned the hook,
|
|
84
|
+
* so the cap runs from an instant slightly before the one the margin counts
|
|
85
|
+
* from. An overrunning tick plus a slow intake can put the `approval.expired`
|
|
86
|
+
* record after the kill. What keeps a late tap safe regardless is not this
|
|
87
|
+
* margin but the lazy refusal: `core/state.ts` judges the lapse by arithmetic
|
|
88
|
+
* whether or not the record exists, so `decide` refuses the tap `expired` and
|
|
89
|
+
* materialises the record then. The margin buys the ORDERING in the common
|
|
90
|
+
* case, so the two records agree without a human ever seeing the disagreement;
|
|
91
|
+
* the refusal is what makes the grant impossible in every case.
|
|
92
|
+
*
|
|
93
|
+
* It is a floor on the cap as well as a subtraction from it. A cap at or below
|
|
94
|
+
* the margin leaves no window in which a human could answer at all: `cli/hook.ts`
|
|
95
|
+
* refuses such a configuration rather than opening a question that is already
|
|
96
|
+
* dead, and `schema/event.schema.json` refuses a record carrying one at the
|
|
97
|
+
* write boundary (its `minimum` is this constant plus one, pinned equal by
|
|
98
|
+
* `tests/harness-cap-ttl.test.ts`).
|
|
99
|
+
*/
|
|
100
|
+
export declare const HARNESS_CAP_MARGIN_MS = 60000;
|
|
101
|
+
/**
|
|
102
|
+
* The deadline a hook-originated request is judged by: the SHORTER of the
|
|
103
|
+
* policy's TTL and the harness's cap minus {@link HARNESS_CAP_MARGIN_MS}
|
|
104
|
+
* (APRV-423).
|
|
105
|
+
*
|
|
106
|
+
* `null` means no deadline, exactly as `defaults.approval_ttl`'s absence means
|
|
107
|
+
* it (SPEC.md §6): nothing lapses, and no duration is invented for a policy
|
|
108
|
+
* that declared none.
|
|
109
|
+
*
|
|
110
|
+
* **The cap may only shorten.** It is a self-reported field (the hook states
|
|
111
|
+
* the ceiling its own harness runs it under), and SPEC.md §11.1 invariant 4
|
|
112
|
+
* says such a field never reduces scrutiny. Recording it raw and taking the
|
|
113
|
+
* minimum HERE is what makes that structural: a cap larger than the policy TTL
|
|
114
|
+
* changes nothing, a cap of an hour under a one-minute TTL changes nothing, and
|
|
115
|
+
* the only direction a requester can move its own deadline is earlier. The same
|
|
116
|
+
* shape as `core/gate-window.ts`'s `expires_at`, which clamps a claimed window
|
|
117
|
+
* against the derived one for the same reason.
|
|
118
|
+
*
|
|
119
|
+
* A cap that does not clear the margin yields `0`, which reads as lapsed from
|
|
120
|
+
* the instant the request is written. That is the fail-closed answer for a
|
|
121
|
+
* harness whose ceiling leaves no room for a human, and it is unreachable
|
|
122
|
+
* through the write path: the hook refuses the configuration before it
|
|
123
|
+
* appends, and the event schema refuses the record for any caller that did
|
|
124
|
+
* not come through the hook.
|
|
125
|
+
*/
|
|
126
|
+
export declare function harnessCappedTtlMs(policyTtlMs: number | null, capMs: number | null): number | null;
|
|
127
|
+
/**
|
|
128
|
+
* Does `capMs` leave a window a human could answer in?
|
|
129
|
+
*
|
|
130
|
+
* The one question `cli/hook.ts` asks before it opens a request under a cap.
|
|
131
|
+
* Strictly greater: a cap EQUAL to the margin yields a zero-length window,
|
|
132
|
+
* which is a question that expires as it is asked.
|
|
133
|
+
*/
|
|
134
|
+
export declare function harnessCapFitsMargin(capMs: number): boolean;
|
|
@@ -2,13 +2,16 @@
|
|
|
2
2
|
* How long a harness hook waits, and how long the question it opened outlives
|
|
3
3
|
* that wait (APRV-287).
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
* rather than beside
|
|
7
|
-
* request it opened has been abandoned; `cli/channel-telegram.ts` uses the wait
|
|
5
|
+
* Three numbers with two readers each, which is the whole reason they live
|
|
6
|
+
* here rather than beside any one of them. `cli/hook.ts` uses them to decide
|
|
7
|
+
* when a request it opened has been abandoned; `cli/channel-telegram.ts` uses the wait
|
|
8
8
|
* to decide which pending requests on a listener's first cycle are stale enough
|
|
9
9
|
* to collapse into one message. Two copies of the same duration would be two
|
|
10
10
|
* answers to "is anybody still waiting on this", and the channel would collapse
|
|
11
11
|
* questions the hook was still holding open, or leave a dead queue on a phone.
|
|
12
|
+
* The third, APRV-423's margin, is read by the hook (which refuses a harness
|
|
13
|
+
* ceiling too short to hold a human's answer) and by `core/state.ts` (which
|
|
14
|
+
* judges every request against the window that ceiling leaves).
|
|
12
15
|
*
|
|
13
16
|
* Nothing here reads a clock, a policy or the log: these are durations, and
|
|
14
17
|
* every judgment made with them is made by a caller that already holds the
|
|
@@ -55,4 +58,87 @@ export const HOOK_RETRY_GRACE_MS = 5 * 60_000;
|
|
|
55
58
|
export function abandonedAfterMs(waitMs, graceMs) {
|
|
56
59
|
return Math.max(0, waitMs) + Math.max(0, graceMs);
|
|
57
60
|
}
|
|
61
|
+
/**
|
|
62
|
+
* How far below a harness's own timeout a hook-originated request must lapse
|
|
63
|
+
* (APRV-423).
|
|
64
|
+
*
|
|
65
|
+
* A harness bounds the hook process: Hermes caps a `pre_tool_call` entry at
|
|
66
|
+
* 300 s, Claude Code kills the hook at the `timeout` in its settings file, and
|
|
67
|
+
* every one of them treats the killed process as a NON-BLOCKING error and runs
|
|
68
|
+
* the tool call. So when the harness gives up first the hook is already gone,
|
|
69
|
+
* the request is still pending on somebody's phone, and the tap that arrives
|
|
70
|
+
* afterwards is recorded as a grant on a call nobody holds (APRV-410, observed
|
|
71
|
+
* 2026-09-20). The two records then disagree about the same moment: one says
|
|
72
|
+
* denied, the other says approved.
|
|
73
|
+
*
|
|
74
|
+
* The fix is to make the question end BEFORE the process that asked it does,
|
|
75
|
+
* and this is the distance between the two. It is 60 s because the daemon's
|
|
76
|
+
* TTL sweep runs on a 30 s interval (`daemon/daemon.ts`'s
|
|
77
|
+
* `DEFAULT_INTERVAL_MS`): a request that lapses one millisecond after a sweep
|
|
78
|
+
* waits a whole interval for the next one, so a margin of two intervals leaves
|
|
79
|
+
* the `approval.expired` append a full tick of room in the common case, and it
|
|
80
|
+
* lands inside the cap. Anything smaller would make the ordering this task is
|
|
81
|
+
* about depend on where in the sweep cycle the lapse fell.
|
|
82
|
+
*
|
|
83
|
+
* The common case, and not a guarantee. `Daemon.tick` skips a sweep when the
|
|
84
|
+
* previous tick is still running, and the request record's `ts` is stamped at
|
|
85
|
+
* the write boundary, some intake latency after the harness spawned the hook,
|
|
86
|
+
* so the cap runs from an instant slightly before the one the margin counts
|
|
87
|
+
* from. An overrunning tick plus a slow intake can put the `approval.expired`
|
|
88
|
+
* record after the kill. What keeps a late tap safe regardless is not this
|
|
89
|
+
* margin but the lazy refusal: `core/state.ts` judges the lapse by arithmetic
|
|
90
|
+
* whether or not the record exists, so `decide` refuses the tap `expired` and
|
|
91
|
+
* materialises the record then. The margin buys the ORDERING in the common
|
|
92
|
+
* case, so the two records agree without a human ever seeing the disagreement;
|
|
93
|
+
* the refusal is what makes the grant impossible in every case.
|
|
94
|
+
*
|
|
95
|
+
* It is a floor on the cap as well as a subtraction from it. A cap at or below
|
|
96
|
+
* the margin leaves no window in which a human could answer at all: `cli/hook.ts`
|
|
97
|
+
* refuses such a configuration rather than opening a question that is already
|
|
98
|
+
* dead, and `schema/event.schema.json` refuses a record carrying one at the
|
|
99
|
+
* write boundary (its `minimum` is this constant plus one, pinned equal by
|
|
100
|
+
* `tests/harness-cap-ttl.test.ts`).
|
|
101
|
+
*/
|
|
102
|
+
export const HARNESS_CAP_MARGIN_MS = 60_000;
|
|
103
|
+
/**
|
|
104
|
+
* The deadline a hook-originated request is judged by: the SHORTER of the
|
|
105
|
+
* policy's TTL and the harness's cap minus {@link HARNESS_CAP_MARGIN_MS}
|
|
106
|
+
* (APRV-423).
|
|
107
|
+
*
|
|
108
|
+
* `null` means no deadline, exactly as `defaults.approval_ttl`'s absence means
|
|
109
|
+
* it (SPEC.md §6): nothing lapses, and no duration is invented for a policy
|
|
110
|
+
* that declared none.
|
|
111
|
+
*
|
|
112
|
+
* **The cap may only shorten.** It is a self-reported field (the hook states
|
|
113
|
+
* the ceiling its own harness runs it under), and SPEC.md §11.1 invariant 4
|
|
114
|
+
* says such a field never reduces scrutiny. Recording it raw and taking the
|
|
115
|
+
* minimum HERE is what makes that structural: a cap larger than the policy TTL
|
|
116
|
+
* changes nothing, a cap of an hour under a one-minute TTL changes nothing, and
|
|
117
|
+
* the only direction a requester can move its own deadline is earlier. The same
|
|
118
|
+
* shape as `core/gate-window.ts`'s `expires_at`, which clamps a claimed window
|
|
119
|
+
* against the derived one for the same reason.
|
|
120
|
+
*
|
|
121
|
+
* A cap that does not clear the margin yields `0`, which reads as lapsed from
|
|
122
|
+
* the instant the request is written. That is the fail-closed answer for a
|
|
123
|
+
* harness whose ceiling leaves no room for a human, and it is unreachable
|
|
124
|
+
* through the write path: the hook refuses the configuration before it
|
|
125
|
+
* appends, and the event schema refuses the record for any caller that did
|
|
126
|
+
* not come through the hook.
|
|
127
|
+
*/
|
|
128
|
+
export function harnessCappedTtlMs(policyTtlMs, capMs) {
|
|
129
|
+
if (capMs === null)
|
|
130
|
+
return policyTtlMs;
|
|
131
|
+
const fromCap = Math.max(0, capMs - HARNESS_CAP_MARGIN_MS);
|
|
132
|
+
return policyTtlMs === null ? fromCap : Math.min(policyTtlMs, fromCap);
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Does `capMs` leave a window a human could answer in?
|
|
136
|
+
*
|
|
137
|
+
* The one question `cli/hook.ts` asks before it opens a request under a cap.
|
|
138
|
+
* Strictly greater: a cap EQUAL to the margin yields a zero-length window,
|
|
139
|
+
* which is a question that expires as it is asked.
|
|
140
|
+
*/
|
|
141
|
+
export function harnessCapFitsMargin(capMs) {
|
|
142
|
+
return capMs > HARNESS_CAP_MARGIN_MS;
|
|
143
|
+
}
|
|
58
144
|
//# sourceMappingURL=harness-wait.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"harness-wait.js","sourceRoot":"","sources":["../../../src/core/harness-wait.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"harness-wait.js","sourceRoot":"","sources":["../../../src/core/harness-wait.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH;;;;GAIG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,KAAK,CAAC;AAEvC,iDAAiD;AACjD,MAAM,CAAC,MAAM,oBAAoB,GAAG,MAAM,CAAC;AAE3C;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,GAAG,MAAM,CAAC;AAE9C;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAAC,MAAc,EAAE,OAAe;IAC9D,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;AACpD,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,MAAM,CAAC;AAE5C;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,UAAU,kBAAkB,CAChC,WAA0B,EAC1B,KAAoB;IAEpB,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,WAAW,CAAC;IACvC,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,GAAG,qBAAqB,CAAC,CAAC;IAC3D,OAAO,WAAW,KAAK,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;AACzE,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,oBAAoB,CAAC,KAAa;IAChD,OAAO,KAAK,GAAG,qBAAqB,CAAC;AACvC,CAAC"}
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
* secret and is not derived from one — it is a hash of a directory path, which
|
|
48
48
|
* `.approval/env` already carries in the open.
|
|
49
49
|
*/
|
|
50
|
-
import { type ResolvedVariable } from "./env-file.js";
|
|
50
|
+
import { type ResolvedVariable, type SourceRunner } from "./env-file.js";
|
|
51
51
|
import type { PolicyLoadResult } from "./policy-load.js";
|
|
52
52
|
/**
|
|
53
53
|
* The unscoped item names this project used before APRV-178.
|
|
@@ -59,6 +59,12 @@ import type { PolicyLoadResult } from "./policy-load.js";
|
|
|
59
59
|
export declare const LEGACY_SERVICE_TELEGRAM_TOKEN = "approval-tg-token";
|
|
60
60
|
export declare const LEGACY_SERVICE_VAULT_PASSPHRASE = "approval-vault-passphrase";
|
|
61
61
|
export declare const LEGACY_SERVICE_SAMPLING_SECRET = "approval-sampling-secret";
|
|
62
|
+
/**
|
|
63
|
+
* The sender key (APRV-370). Born scoped, so it has no legacy instance to be a
|
|
64
|
+
* fallback for; it sits in this list because the naming rule is the list's, and
|
|
65
|
+
* a name minted outside it is a name `approval env --check` reports as unknown.
|
|
66
|
+
*/
|
|
67
|
+
export declare const SERVICE_SENDER_KEY = "approval-sender-key";
|
|
62
68
|
/** Every unscoped name, for the "is this the legacy one?" test. */
|
|
63
69
|
export declare const LEGACY_SERVICES: readonly string[];
|
|
64
70
|
/** How many hex digits of the digest a scoped name carries. */
|
|
@@ -196,8 +202,16 @@ export declare function parseEnvProvenance(raw: string | undefined): EnvProvenan
|
|
|
196
202
|
* half of the incident that survived fixing the file: the operator's rc
|
|
197
203
|
* exported the production token, so every fresh terminal kept using the
|
|
198
204
|
* production bot.
|
|
205
|
+
* - `stale-export` — the value in this shell is NOT what this instance's file
|
|
206
|
+
* resolves to today (APRV-390). The name-only rules above cannot see this at
|
|
207
|
+
* all, and it is the case that actually cost an evening: a token re-stored in
|
|
208
|
+
* the keychain does not reach a shell that exported the old one, because
|
|
209
|
+
* `approval env` never overrides a variable that is already set. The daemon
|
|
210
|
+
* then answered 401 while the keychain item behind the same line passed
|
|
211
|
+
* `getMe` by hand. Only {@link valueFindings} produces it, because only that
|
|
212
|
+
* function is allowed to read a value.
|
|
199
213
|
*/
|
|
200
|
-
export type InstanceFindingKind = "foreign-instance" | "legacy-shared" | "ambient-bleed";
|
|
214
|
+
export type InstanceFindingKind = "foreign-instance" | "legacy-shared" | "ambient-bleed" | "stale-export";
|
|
201
215
|
export interface InstanceFinding {
|
|
202
216
|
kind: InstanceFindingKind;
|
|
203
217
|
/** The environment variable the finding is about. */
|
|
@@ -249,5 +263,48 @@ export interface FindingsContext {
|
|
|
249
263
|
* env --check` and `approval up` to say different things about one shell.
|
|
250
264
|
*/
|
|
251
265
|
export declare function ownEnvExports(logPath: string, context?: FindingsContext): ReadonlySet<string>;
|
|
266
|
+
/**
|
|
267
|
+
* The same question, answered by COMPARING VALUES (APRV-390).
|
|
268
|
+
*
|
|
269
|
+
* ## Why a second function, and why only start-up may call it
|
|
270
|
+
*
|
|
271
|
+
* Everything above answers from names, so that a diagnostic never blocks on a
|
|
272
|
+
* keystore-unlock dialog ({@link NON_RESOLVING_RUNNER}). That constraint is
|
|
273
|
+
* right for `approval doctor` and wrong for the thing `approval up` is about to
|
|
274
|
+
* do, which is USE the value. Two cases observed on 2026-09-19 are invisible to
|
|
275
|
+
* every name-only rule:
|
|
276
|
+
*
|
|
277
|
+
* 1. **A stale export.** `approval env` never overrides a variable already set
|
|
278
|
+
* in the shell — that is invariant 7 working as designed — so a token
|
|
279
|
+
* re-stored in the keychain never reaches a terminal that exported the old
|
|
280
|
+
* one. The names agree, the file is this instance's, the provenance claim is
|
|
281
|
+
* this instance's own, and the value is wrong. The daemon got 401 from a
|
|
282
|
+
* token that passed `getMe` when read from the keychain by hand.
|
|
283
|
+
* 2. **A false positive on the correct ritual.** `APPROVAL_HUMAN` was reported
|
|
284
|
+
* cross-instance when the export came from this instance's own `eval
|
|
285
|
+
* "$(approval env)"` seconds earlier, because the provenance claim did not
|
|
286
|
+
* match. A finding about a value that IS the file's value is noise, and
|
|
287
|
+
* noise in a refusal is how a refusal gets overridden by habit.
|
|
288
|
+
*
|
|
289
|
+
* Comparing answers both: a value equal to what the file resolves to is
|
|
290
|
+
* correct however it got there, and a value that differs is wrong however
|
|
291
|
+
* honest its provenance looks.
|
|
292
|
+
*
|
|
293
|
+
* ## What it does with the values
|
|
294
|
+
*
|
|
295
|
+
* It compares them and drops them. Nothing is printed, returned, logged or put
|
|
296
|
+
* in a message on any path — every finding this produces carries a variable
|
|
297
|
+
* name, a file path and a line number, exactly like the name-only ones. The
|
|
298
|
+
* only new capability is answering "same or different".
|
|
299
|
+
*
|
|
300
|
+
* ## Where it fails soft, and in which direction
|
|
301
|
+
*
|
|
302
|
+
* A file entry that cannot be resolved (a locked keychain, a helper that is not
|
|
303
|
+
* installed, a `secret-tool` with no D-Bus) yields no comparison, and the
|
|
304
|
+
* name-only finding for that variable stands unchanged. That is the safe
|
|
305
|
+
* direction: an unverifiable value keeps whatever scrutiny it already had, and
|
|
306
|
+
* a caller that cannot resolve anything gets exactly the pre-APRV-390 report.
|
|
307
|
+
*/
|
|
308
|
+
export declare function valueFindings(logPath: string, load: PolicyLoadResult, runner: SourceRunner, ambientEnv?: NodeJS.ProcessEnv): InstanceFinding[];
|
|
252
309
|
/** The name-only rules, over an already-resolved variable set. */
|
|
253
310
|
export declare function findingsFor(logPath: string, variables: readonly ResolvedVariable[], context?: FindingsContext): InstanceFinding[];
|
|
@@ -60,11 +60,18 @@ import { NON_RESOLVING_RUNNER, envFilePathFor, resolveEnvironment, } from "./env
|
|
|
60
60
|
export const LEGACY_SERVICE_TELEGRAM_TOKEN = "approval-tg-token";
|
|
61
61
|
export const LEGACY_SERVICE_VAULT_PASSPHRASE = "approval-vault-passphrase";
|
|
62
62
|
export const LEGACY_SERVICE_SAMPLING_SECRET = "approval-sampling-secret";
|
|
63
|
+
/**
|
|
64
|
+
* The sender key (APRV-370). Born scoped, so it has no legacy instance to be a
|
|
65
|
+
* fallback for; it sits in this list because the naming rule is the list's, and
|
|
66
|
+
* a name minted outside it is a name `approval env --check` reports as unknown.
|
|
67
|
+
*/
|
|
68
|
+
export const SERVICE_SENDER_KEY = "approval-sender-key";
|
|
63
69
|
/** Every unscoped name, for the "is this the legacy one?" test. */
|
|
64
70
|
export const LEGACY_SERVICES = [
|
|
65
71
|
LEGACY_SERVICE_TELEGRAM_TOKEN,
|
|
66
72
|
LEGACY_SERVICE_VAULT_PASSPHRASE,
|
|
67
73
|
LEGACY_SERVICE_SAMPLING_SECRET,
|
|
74
|
+
SERVICE_SENDER_KEY,
|
|
68
75
|
];
|
|
69
76
|
/** How many hex digits of the digest a scoped name carries. */
|
|
70
77
|
export const INSTANCE_ID_LENGTH = 8;
|
|
@@ -265,6 +272,112 @@ export function ownEnvExports(logPath, context = {}) {
|
|
|
265
272
|
const view = provenanceView(logPath, context);
|
|
266
273
|
return view.claimsThisFile ? view.names : new Set();
|
|
267
274
|
}
|
|
275
|
+
/**
|
|
276
|
+
* The same question, answered by COMPARING VALUES (APRV-390).
|
|
277
|
+
*
|
|
278
|
+
* ## Why a second function, and why only start-up may call it
|
|
279
|
+
*
|
|
280
|
+
* Everything above answers from names, so that a diagnostic never blocks on a
|
|
281
|
+
* keystore-unlock dialog ({@link NON_RESOLVING_RUNNER}). That constraint is
|
|
282
|
+
* right for `approval doctor` and wrong for the thing `approval up` is about to
|
|
283
|
+
* do, which is USE the value. Two cases observed on 2026-09-19 are invisible to
|
|
284
|
+
* every name-only rule:
|
|
285
|
+
*
|
|
286
|
+
* 1. **A stale export.** `approval env` never overrides a variable already set
|
|
287
|
+
* in the shell — that is invariant 7 working as designed — so a token
|
|
288
|
+
* re-stored in the keychain never reaches a terminal that exported the old
|
|
289
|
+
* one. The names agree, the file is this instance's, the provenance claim is
|
|
290
|
+
* this instance's own, and the value is wrong. The daemon got 401 from a
|
|
291
|
+
* token that passed `getMe` when read from the keychain by hand.
|
|
292
|
+
* 2. **A false positive on the correct ritual.** `APPROVAL_HUMAN` was reported
|
|
293
|
+
* cross-instance when the export came from this instance's own `eval
|
|
294
|
+
* "$(approval env)"` seconds earlier, because the provenance claim did not
|
|
295
|
+
* match. A finding about a value that IS the file's value is noise, and
|
|
296
|
+
* noise in a refusal is how a refusal gets overridden by habit.
|
|
297
|
+
*
|
|
298
|
+
* Comparing answers both: a value equal to what the file resolves to is
|
|
299
|
+
* correct however it got there, and a value that differs is wrong however
|
|
300
|
+
* honest its provenance looks.
|
|
301
|
+
*
|
|
302
|
+
* ## What it does with the values
|
|
303
|
+
*
|
|
304
|
+
* It compares them and drops them. Nothing is printed, returned, logged or put
|
|
305
|
+
* in a message on any path — every finding this produces carries a variable
|
|
306
|
+
* name, a file path and a line number, exactly like the name-only ones. The
|
|
307
|
+
* only new capability is answering "same or different".
|
|
308
|
+
*
|
|
309
|
+
* ## Where it fails soft, and in which direction
|
|
310
|
+
*
|
|
311
|
+
* A file entry that cannot be resolved (a locked keychain, a helper that is not
|
|
312
|
+
* installed, a `secret-tool` with no D-Bus) yields no comparison, and the
|
|
313
|
+
* name-only finding for that variable stands unchanged. That is the safe
|
|
314
|
+
* direction: an unverifiable value keeps whatever scrutiny it already had, and
|
|
315
|
+
* a caller that cannot resolve anything gets exactly the pre-APRV-390 report.
|
|
316
|
+
*/
|
|
317
|
+
export function valueFindings(logPath, load, runner, ambientEnv = process.env) {
|
|
318
|
+
const envPath = envFilePathFor(logPath);
|
|
319
|
+
const named = instanceFindings(logPath, load, ambientEnv);
|
|
320
|
+
// EVERY variable whose value came from the shell and whose file has a line
|
|
321
|
+
// for it — not only the ones the name-only rule complained about.
|
|
322
|
+
//
|
|
323
|
+
// This is the correction that matters, and the reason the set is not taken
|
|
324
|
+
// from `named`. The provenance claim (APRV-278) vouches for an export by
|
|
325
|
+
// naming this instance, this file's DIGEST and this variable, and all three
|
|
326
|
+
// stay true when the file is untouched and the KEYSTORE ITEM behind one of
|
|
327
|
+
// its lines is re-stored. The claim is honest and the value is stale, so the
|
|
328
|
+
// vouched variables are exactly the ones this check must still look at.
|
|
329
|
+
const nameOnly = resolveEnvironment(load, envPath, NON_RESOLVING_RUNNER, ambientEnv);
|
|
330
|
+
if (!nameOnly.ok)
|
|
331
|
+
return named;
|
|
332
|
+
const ambientNames = new Set(nameOnly.variables
|
|
333
|
+
.filter((variable) => variable.status === "set-in-environment" && variable.fileSource !== undefined)
|
|
334
|
+
.map((variable) => variable.name));
|
|
335
|
+
if (ambientNames.size === 0)
|
|
336
|
+
return named;
|
|
337
|
+
// Resolve the FILE, by hiding exactly those names from the resolution: the
|
|
338
|
+
// ambient environment wins otherwise, and the answer would be the value we
|
|
339
|
+
// are trying to check. Nothing here is exported — this process's own
|
|
340
|
+
// environment is untouched, so invariant 7 is as true after this call as
|
|
341
|
+
// before it. The file cannot SET anything; it can only fail to match.
|
|
342
|
+
const hidden = { ...ambientEnv };
|
|
343
|
+
for (const name of ambientNames)
|
|
344
|
+
delete hidden[name];
|
|
345
|
+
const fromFile = resolveEnvironment(load, envPath, runner, hidden);
|
|
346
|
+
if (!fromFile.ok)
|
|
347
|
+
return named;
|
|
348
|
+
const fileValues = new Map();
|
|
349
|
+
for (const variable of fromFile.variables) {
|
|
350
|
+
if (variable.value !== undefined && variable.status !== "set-in-environment") {
|
|
351
|
+
fileValues.set(variable.name, variable.value);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
// Everything the name-only rule found that is NOT about an ambient value: a
|
|
355
|
+
// foreign item name and a legacy shared one are facts about names, and no
|
|
356
|
+
// comparison of values can make either of them go away.
|
|
357
|
+
const findings = named.filter((finding) => finding.kind !== "ambient-bleed");
|
|
358
|
+
for (const name of ambientNames) {
|
|
359
|
+
const fileValue = fileValues.get(name);
|
|
360
|
+
const ambientValue = ambientEnv[name];
|
|
361
|
+
// Unresolvable (a locked keychain, a missing helper, a missing item): no
|
|
362
|
+
// comparison was made, so whatever the name-only rule said still stands.
|
|
363
|
+
if (fileValue === undefined) {
|
|
364
|
+
const bleed = named.find((finding) => finding.kind === "ambient-bleed" && finding.variable === name);
|
|
365
|
+
if (bleed !== undefined)
|
|
366
|
+
findings.push(bleed);
|
|
367
|
+
continue;
|
|
368
|
+
}
|
|
369
|
+
// The shell holds exactly what the file configures. Correct, however it got
|
|
370
|
+
// there, and whatever the provenance claim did or did not say.
|
|
371
|
+
if (ambientValue === fileValue)
|
|
372
|
+
continue;
|
|
373
|
+
findings.push({
|
|
374
|
+
kind: "stale-export",
|
|
375
|
+
variable: name,
|
|
376
|
+
detail: `${name} is exported in this shell with a value that is NOT what ${envPath} resolves to today, and an export always wins over the file, so this process would use the stale one`,
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
return findings;
|
|
380
|
+
}
|
|
268
381
|
/** The name-only rules, over an already-resolved variable set. */
|
|
269
382
|
export function findingsFor(logPath, variables, context = {}) {
|
|
270
383
|
const home = instanceHomeFor(logPath);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"instance.js","sourceRoot":"","sources":["../../../src/core/instance.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,OAAO,EACL,oBAAoB,EACpB,cAAc,EACd,kBAAkB,
|
|
1
|
+
{"version":3,"file":"instance.js","sourceRoot":"","sources":["../../../src/core/instance.ts"],"names":[],"mappings":"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"}
|
|
@@ -25,6 +25,27 @@ export interface LogSubscriptionOptions {
|
|
|
25
25
|
signal?: AbortSignal;
|
|
26
26
|
/** Bounded fallback when filesystem notifications are missing. */
|
|
27
27
|
pollIntervalMs?: number;
|
|
28
|
+
/**
|
|
29
|
+
* Drain the currently verified snapshot and RETURN, rather than waiting for
|
|
30
|
+
* appends (APRV-421).
|
|
31
|
+
*
|
|
32
|
+
* The streaming form is what `approval log follow` wants: a foreground
|
|
33
|
+
* process that stays. A PAGED reader wants the opposite and has nowhere to
|
|
34
|
+
* put a wait. `approval serve`'s `log/follow` answers one request with the
|
|
35
|
+
* records after a cursor and the cursor they end at, and the caller comes
|
|
36
|
+
* back with that cursor when it wants more. Expressing "we are caught up" as
|
|
37
|
+
* a race between the iterator and a timer is a timing heuristic; expressing
|
|
38
|
+
* it here is one flag, because this module already knows exactly when the
|
|
39
|
+
* verified snapshot is exhausted.
|
|
40
|
+
*
|
|
41
|
+
* It changes nothing about what is emitted or what is refused. The
|
|
42
|
+
* genesis-to-head verification, the cursor binding, the retained hash and
|
|
43
|
+
* every terminal failure are the ones below; this decides only whether the
|
|
44
|
+
* generator waits after the drain or ends. No watcher is established either,
|
|
45
|
+
* which matters for the caller this exists for: a server that left one file
|
|
46
|
+
* watcher behind per request would leak handles at the rate it is polled.
|
|
47
|
+
*/
|
|
48
|
+
once?: boolean;
|
|
28
49
|
}
|
|
29
50
|
/**
|
|
30
51
|
* Yield verified records after an exclusive cursor, then wait for appends.
|
|
@@ -86,12 +86,16 @@ export async function* subscribeVerifiedLog(logPath, options = {}) {
|
|
|
86
86
|
options.signal?.addEventListener("abort", abort, { once: false });
|
|
87
87
|
// Watch the directory so creation of an initially absent log is visible.
|
|
88
88
|
// Failure to establish a watch is harmless: bounded polling remains active.
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
89
|
+
// A `once` subscription never reaches the wait below, so it establishes no
|
|
90
|
+
// watch at all rather than opening and closing one per call.
|
|
91
|
+
if (options.once !== true) {
|
|
92
|
+
try {
|
|
93
|
+
watcher = watch(dirname(logPath), { persistent: false }, hint);
|
|
94
|
+
watcher.on("error", hint);
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
watcher = null;
|
|
98
|
+
}
|
|
95
99
|
}
|
|
96
100
|
try {
|
|
97
101
|
while (!options.signal?.aborted) {
|
|
@@ -131,6 +135,12 @@ export async function* subscribeVerifiedLog(logPath, options = {}) {
|
|
|
131
135
|
}
|
|
132
136
|
if (options.signal?.aborted)
|
|
133
137
|
return;
|
|
138
|
+
// The verified snapshot is exhausted and this caller asked for exactly
|
|
139
|
+
// that much (APRV-421). Everything above ran unchanged: the chain was
|
|
140
|
+
// verified from genesis, the cursor was bound, and a mismatch already
|
|
141
|
+
// threw. What is skipped is only the waiting.
|
|
142
|
+
if (options.once === true)
|
|
143
|
+
return;
|
|
134
144
|
// An append between watcher setup/read/drain and this point changes the
|
|
135
145
|
// version and causes an immediate verification instead of a lost wakeup.
|
|
136
146
|
if (wakeVersion !== versionBeforeRead)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"log-subscribe.js","sourceRoot":"","sources":["../../../src/core/log-subscribe.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,YAAY,EAAE,KAAK,EAAkB,MAAM,SAAS,CAAC;AAC9D,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAGpC,OAAO,EAAE,UAAU,EAA4B,MAAM,aAAa,CAAC;AAInE,gFAAgF;AAChF,MAAM,OAAO,oBAAqB,SAAQ,KAAK;IACpC,IAAI,CAA6B;IACjC,MAAM,CAAiD;IAEhE,YACE,IAAgC,EAChC,OAAe,EACf,MAAM,GAAmD,IAAI;QAE7D,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,sBAAsB,CAAC;QACnC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;CACF;
|
|
1
|
+
{"version":3,"file":"log-subscribe.js","sourceRoot":"","sources":["../../../src/core/log-subscribe.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The filesystem calls that put durable bytes on disk: `core/log.ts`'s append
|
|
3
|
+
* (APRV-440) and `core/payload-store.ts`'s atomic write (APRV-457). They are
|
|
4
|
+
* kept in their own module so that `core/log.ts`'s public surface stays what
|
|
5
|
+
* `tests/log.test.ts` pins: no mutation, no reorder, no truncate, and no way to
|
|
6
|
+
* change how its bytes reach the disk.
|
|
7
|
+
*
|
|
8
|
+
* Why a seam at all. The durability claim is "fsync after the write and before
|
|
9
|
+
* ok", and a page cache that was flushed reads back exactly like one that was
|
|
10
|
+
* not until the machine dies. Only a layer that records the ORDER of the calls
|
|
11
|
+
* can prove the claim, so `tests/log-fsync.test.ts` and
|
|
12
|
+
* `tests/payload-fsync.test.ts` install one that records every call and then
|
|
13
|
+
* delegates to the real one, which keeps each log they build a real log built
|
|
14
|
+
* through the real append path.
|
|
15
|
+
*
|
|
16
|
+
* One seam for both writers, deliberately. A log record is only as durable as
|
|
17
|
+
* the payload it binds: a record that survives a crash while its payload comes
|
|
18
|
+
* back NUL-filled verifies and points at nothing (APRV-457). Proving the two
|
|
19
|
+
* through one layer keeps one model of "what reached the disk" for both.
|
|
20
|
+
*
|
|
21
|
+
* Nothing in `src/` calls {@link setAppendWriteLayerForTests}. It changes how
|
|
22
|
+
* the bytes reach the disk and nothing about which bytes, so every check before
|
|
23
|
+
* the write (lock, tail, compare-and-append, schema, payload hashing) runs as in
|
|
24
|
+
* production.
|
|
25
|
+
*/
|
|
26
|
+
export interface AppendWriteLayer {
|
|
27
|
+
/** `open(2)` with numeric flags; throws like `fs.openSync`. */
|
|
28
|
+
open(path: string, flags: number, mode: number): number;
|
|
29
|
+
/** One `write(2)` of the whole buffer; returns the bytes written. */
|
|
30
|
+
write(fd: number, data: Buffer): number;
|
|
31
|
+
/** `fsync(2)` of a file or directory descriptor; throws on failure. */
|
|
32
|
+
fsync(fd: number): void;
|
|
33
|
+
close(fd: number): void;
|
|
34
|
+
/**
|
|
35
|
+
* `rename(2)`; throws like `fs.renameSync`. Used by the payload store's
|
|
36
|
+
* temp-then-rename write (APRV-457); the log append never renames.
|
|
37
|
+
*/
|
|
38
|
+
rename(from: string, to: string): void;
|
|
39
|
+
}
|
|
40
|
+
/** The layer the next append (or payload store write) uses. */
|
|
41
|
+
export declare function appendWriteLayer(): AppendWriteLayer;
|
|
42
|
+
/**
|
|
43
|
+
* Replace the append's write layer, or restore Node's with `null`. For tests
|
|
44
|
+
* and the opt-in benchmarks only.
|
|
45
|
+
*/
|
|
46
|
+
export declare function setAppendWriteLayerForTests(layer: AppendWriteLayer | null): void;
|
|
47
|
+
/**
|
|
48
|
+
* fsync a directory through the current layer, so a name created or renamed
|
|
49
|
+
* into it survives a crash. Skipped on Windows (a directory cannot be opened
|
|
50
|
+
* for this there, and its filesystems journal names themselves) and tolerated
|
|
51
|
+
* where the filesystem reports directory fsync as unsupported; every other
|
|
52
|
+
* failure throws for the caller to report. The read-only handle is closed even
|
|
53
|
+
* when the fsync throws.
|
|
54
|
+
*/
|
|
55
|
+
export declare function fsyncDirectory(dir: string): void;
|