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
|
@@ -14,7 +14,10 @@
|
|
|
14
14
|
* and subtracts the subjects already sampled. A tick that samples nothing new is
|
|
15
15
|
* the normal case; a restarted daemon, a second daemon, and an operator running
|
|
16
16
|
* a sweep by hand all converge on exactly the same set. This module therefore
|
|
17
|
-
* holds no state
|
|
17
|
+
* holds no state that any APPEND depends on: no seen-set, no cursor, no cache.
|
|
18
|
+
* The two process-lifetime sets below are output bookkeeping (which notice has
|
|
19
|
+
* been printed, which deferral was promised), and every decision about what to
|
|
20
|
+
* append is made from the log on every call.
|
|
18
21
|
*
|
|
19
22
|
* ## Reporting, and why it goes through `warning`
|
|
20
23
|
*
|
|
@@ -27,6 +30,58 @@
|
|
|
27
30
|
* its `sampled` line. Both channels remain the caller's: this module names no
|
|
28
31
|
* event and writes to no stream.
|
|
29
32
|
*
|
|
33
|
+
* ## A deferred sample is not a lost one (APRV-381)
|
|
34
|
+
*
|
|
35
|
+
* On 2026-09-19 a tick printed `append-refused audit sampling: append-failed:
|
|
36
|
+
* audit.sampled for hook:…:vcs.push.main was not appended (lock-timeout)` while a
|
|
37
|
+
* lane's hook append and a records advance were writing the same log. Nothing was
|
|
38
|
+
* lost, and the next ticks appended the sample; the operator reading the window
|
|
39
|
+
* had no way to know that, because the line said an audit record had not been
|
|
40
|
+
* written and stopped there.
|
|
41
|
+
*
|
|
42
|
+
* So the two outcomes are now said differently. `core/audit.ts` classifies
|
|
43
|
+
* `lock-timeout` and `head-moved` as transient (see its `TRANSIENT_APPEND_CODES`)
|
|
44
|
+
* and hands them back on `deferred`; this module reports each on {@link
|
|
45
|
+
* AuditSweepOptions.defer} with the action key and the fact that the next tick
|
|
46
|
+
* retries it, and reports everything else on {@link AuditSweepOptions.warn} in
|
|
47
|
+
* the form it always had. When the caller passes no `defer` sink the deferral
|
|
48
|
+
* goes to `warn` instead: a deferral nobody prints is indistinguishable, to the
|
|
49
|
+
* person who has to trust this log, from a sample that was dropped.
|
|
50
|
+
*
|
|
51
|
+
* The retry is then named as one. `pendingSamples` re-derives pendency from the
|
|
52
|
+
* verified log, so the retry needs no memory to HAPPEN; saying "this is the retry
|
|
53
|
+
* of what I deferred" is a fact about this process's own output, and it is kept
|
|
54
|
+
* here as such (see {@link rememberDeferral}).
|
|
55
|
+
*
|
|
56
|
+
* ## The lock wait: skip and retry, at `core/log.ts`'s own 2000 ms (APRV-381)
|
|
57
|
+
*
|
|
58
|
+
* The daemon is the one writer that could afford to wait longer, and it
|
|
59
|
+
* deliberately does not. Four reasons, recorded here because the number itself is
|
|
60
|
+
* a one-line change and the reasoning is not:
|
|
61
|
+
*
|
|
62
|
+
* 1. A tick is serial. This sweep sits between the TTL sweep and the queue
|
|
63
|
+
* render, so every millisecond it blocks is a millisecond an expiry line, a
|
|
64
|
+
* `state:` write-back and `QUEUE.md` wait. A longer wait spends latency a
|
|
65
|
+
* person notices on a record that costs nothing to postpone.
|
|
66
|
+
* 2. The contenders hold the lock for spans no polite wait covers. `approval log
|
|
67
|
+
* advance` holds it across a whole verify-and-commit, `approval log sync`
|
|
68
|
+
* across a baseline move. A wait long enough to win those races reliably is a
|
|
69
|
+
* wait long enough to stall the loop for seconds, and it would still time out
|
|
70
|
+
* sometimes, so the transient path has to exist either way.
|
|
71
|
+
* 3. Deferring loses nothing, provably. The sample's pendency is a property of
|
|
72
|
+
* the log (eligible, selected, no `audit.sampled` yet), not of this process, so
|
|
73
|
+
* the retry bound is the tick interval, and an append by anyone else also wakes
|
|
74
|
+
* the log watcher, which usually makes it the next debounce.
|
|
75
|
+
* 4. The appends this sweep contends with are the ones somebody is waiting on: a
|
|
76
|
+
* hook's gate verdict, a lane's execution record, an advance's commit. The
|
|
77
|
+
* writer with nobody blocked on it is the writer that should yield.
|
|
78
|
+
*
|
|
79
|
+
* "Skip" here means letting the append refuse and classifying the refusal. It
|
|
80
|
+
* explicitly does NOT mean peeking at `events.jsonl.lock` before sweeping: that
|
|
81
|
+
* read can be wrong in both directions (a lock released a microsecond later, a
|
|
82
|
+
* lock taken a microsecond after the peek), and `appendEvent`'s own acquisition
|
|
83
|
+
* under the lock is the only authority on whether an append can be made.
|
|
84
|
+
*
|
|
30
85
|
* ## A disabled sampler is not a failure
|
|
31
86
|
*
|
|
32
87
|
* When no sampling secret is configured, `core/sampler.ts` returns a disabled
|
|
@@ -58,10 +113,21 @@ export interface AuditSweepOptions {
|
|
|
58
113
|
/** Environment the sampling secret is read from. Injected by tests. */
|
|
59
114
|
env?: NodeJS.ProcessEnv;
|
|
60
115
|
/**
|
|
61
|
-
* One FAILURE line: an append the sampler could not make
|
|
62
|
-
* not read. Mapped by the caller onto its own
|
|
116
|
+
* One FAILURE line: an append the sampler could not make for a reason retrying
|
|
117
|
+
* cannot fix, or a log it could not read. Mapped by the caller onto its own
|
|
118
|
+
* warning vocabulary.
|
|
63
119
|
*/
|
|
64
120
|
warn(message: string): void;
|
|
121
|
+
/**
|
|
122
|
+
* One TRANSIENT line: an append that did not happen because another writer held
|
|
123
|
+
* the lock or moved the head, and that the next sweep will make (APRV-381).
|
|
124
|
+
*
|
|
125
|
+
* Optional, and a caller that passes none gets the same text on {@link
|
|
126
|
+
* AuditSweepOptions.warn}: the one thing this module will not do is let a
|
|
127
|
+
* deferral go unsaid. Callers that have a quieter channel for "later" should
|
|
128
|
+
* pass one; the daemon maps it onto its own `sample-deferred` warning code.
|
|
129
|
+
*/
|
|
130
|
+
defer?(message: string): void;
|
|
65
131
|
/**
|
|
66
132
|
* One CONFIGURATION line: sampling is switched off and here is why. Optional,
|
|
67
133
|
* and the daemon does not pass it — see the module header. `approval status`
|
|
@@ -72,8 +138,13 @@ export interface AuditSweepOptions {
|
|
|
72
138
|
* One SUCCESS line per `audit.sampled` appended (APRV-57). Optional, and
|
|
73
139
|
* injected exactly as {@link AuditSweepOptions.warn} is, so the sweep still
|
|
74
140
|
* reports in the caller's vocabulary and owns no output of its own.
|
|
141
|
+
*
|
|
142
|
+
* `retry` is true when THIS process deferred this same sample earlier and is
|
|
143
|
+
* now making the append it promised (APRV-381), so the caller can say so. It is
|
|
144
|
+
* a statement about output, never about the record: the appended event is
|
|
145
|
+
* byte-identical either way.
|
|
75
146
|
*/
|
|
76
|
-
sampled?(sample: SampleAppended): void;
|
|
147
|
+
sampled?(sample: SampleAppended, retry: boolean): void;
|
|
77
148
|
}
|
|
78
149
|
export interface AuditSweepSummary {
|
|
79
150
|
/** How many `audit.sampled` events this sweep appended. */
|
|
@@ -81,7 +152,11 @@ export interface AuditSweepSummary {
|
|
|
81
152
|
/** `null` when sampling ran; the reason when it did not. */
|
|
82
153
|
disabled: SamplerDisabledReason | null;
|
|
83
154
|
}
|
|
84
|
-
/**
|
|
155
|
+
/**
|
|
156
|
+
* Reset the once-per-process notice and the deferral memory. Exported for tests,
|
|
157
|
+
* used nowhere else: both are output bookkeeping, so a suite that shares a
|
|
158
|
+
* process needs to be able to start from nothing.
|
|
159
|
+
*/
|
|
85
160
|
export declare function resetAuditSweepNotices(): void;
|
|
86
161
|
/** One sampling sweep. Appends through `core/audit.ts` and decides nothing. */
|
|
87
162
|
export declare function sweepAuditSampling(options: AuditSweepOptions): AuditSweepSummary;
|
package/dist/src/daemon/audit.js
CHANGED
|
@@ -14,7 +14,10 @@
|
|
|
14
14
|
* and subtracts the subjects already sampled. A tick that samples nothing new is
|
|
15
15
|
* the normal case; a restarted daemon, a second daemon, and an operator running
|
|
16
16
|
* a sweep by hand all converge on exactly the same set. This module therefore
|
|
17
|
-
* holds no state
|
|
17
|
+
* holds no state that any APPEND depends on: no seen-set, no cursor, no cache.
|
|
18
|
+
* The two process-lifetime sets below are output bookkeeping (which notice has
|
|
19
|
+
* been printed, which deferral was promised), and every decision about what to
|
|
20
|
+
* append is made from the log on every call.
|
|
18
21
|
*
|
|
19
22
|
* ## Reporting, and why it goes through `warning`
|
|
20
23
|
*
|
|
@@ -27,6 +30,58 @@
|
|
|
27
30
|
* its `sampled` line. Both channels remain the caller's: this module names no
|
|
28
31
|
* event and writes to no stream.
|
|
29
32
|
*
|
|
33
|
+
* ## A deferred sample is not a lost one (APRV-381)
|
|
34
|
+
*
|
|
35
|
+
* On 2026-09-19 a tick printed `append-refused audit sampling: append-failed:
|
|
36
|
+
* audit.sampled for hook:…:vcs.push.main was not appended (lock-timeout)` while a
|
|
37
|
+
* lane's hook append and a records advance were writing the same log. Nothing was
|
|
38
|
+
* lost, and the next ticks appended the sample; the operator reading the window
|
|
39
|
+
* had no way to know that, because the line said an audit record had not been
|
|
40
|
+
* written and stopped there.
|
|
41
|
+
*
|
|
42
|
+
* So the two outcomes are now said differently. `core/audit.ts` classifies
|
|
43
|
+
* `lock-timeout` and `head-moved` as transient (see its `TRANSIENT_APPEND_CODES`)
|
|
44
|
+
* and hands them back on `deferred`; this module reports each on {@link
|
|
45
|
+
* AuditSweepOptions.defer} with the action key and the fact that the next tick
|
|
46
|
+
* retries it, and reports everything else on {@link AuditSweepOptions.warn} in
|
|
47
|
+
* the form it always had. When the caller passes no `defer` sink the deferral
|
|
48
|
+
* goes to `warn` instead: a deferral nobody prints is indistinguishable, to the
|
|
49
|
+
* person who has to trust this log, from a sample that was dropped.
|
|
50
|
+
*
|
|
51
|
+
* The retry is then named as one. `pendingSamples` re-derives pendency from the
|
|
52
|
+
* verified log, so the retry needs no memory to HAPPEN; saying "this is the retry
|
|
53
|
+
* of what I deferred" is a fact about this process's own output, and it is kept
|
|
54
|
+
* here as such (see {@link rememberDeferral}).
|
|
55
|
+
*
|
|
56
|
+
* ## The lock wait: skip and retry, at `core/log.ts`'s own 2000 ms (APRV-381)
|
|
57
|
+
*
|
|
58
|
+
* The daemon is the one writer that could afford to wait longer, and it
|
|
59
|
+
* deliberately does not. Four reasons, recorded here because the number itself is
|
|
60
|
+
* a one-line change and the reasoning is not:
|
|
61
|
+
*
|
|
62
|
+
* 1. A tick is serial. This sweep sits between the TTL sweep and the queue
|
|
63
|
+
* render, so every millisecond it blocks is a millisecond an expiry line, a
|
|
64
|
+
* `state:` write-back and `QUEUE.md` wait. A longer wait spends latency a
|
|
65
|
+
* person notices on a record that costs nothing to postpone.
|
|
66
|
+
* 2. The contenders hold the lock for spans no polite wait covers. `approval log
|
|
67
|
+
* advance` holds it across a whole verify-and-commit, `approval log sync`
|
|
68
|
+
* across a baseline move. A wait long enough to win those races reliably is a
|
|
69
|
+
* wait long enough to stall the loop for seconds, and it would still time out
|
|
70
|
+
* sometimes, so the transient path has to exist either way.
|
|
71
|
+
* 3. Deferring loses nothing, provably. The sample's pendency is a property of
|
|
72
|
+
* the log (eligible, selected, no `audit.sampled` yet), not of this process, so
|
|
73
|
+
* the retry bound is the tick interval, and an append by anyone else also wakes
|
|
74
|
+
* the log watcher, which usually makes it the next debounce.
|
|
75
|
+
* 4. The appends this sweep contends with are the ones somebody is waiting on: a
|
|
76
|
+
* hook's gate verdict, a lane's execution record, an advance's commit. The
|
|
77
|
+
* writer with nobody blocked on it is the writer that should yield.
|
|
78
|
+
*
|
|
79
|
+
* "Skip" here means letting the append refuse and classifying the refusal. It
|
|
80
|
+
* explicitly does NOT mean peeking at `events.jsonl.lock` before sweeping: that
|
|
81
|
+
* read can be wrong in both directions (a lock released a microsecond later, a
|
|
82
|
+
* lock taken a microsecond after the peek), and `appendEvent`'s own acquisition
|
|
83
|
+
* under the lock is the only authority on whether an append can be made.
|
|
84
|
+
*
|
|
30
85
|
* ## A disabled sampler is not a failure
|
|
31
86
|
*
|
|
32
87
|
* When no sampling secret is configured, `core/sampler.ts` returns a disabled
|
|
@@ -39,7 +94,7 @@
|
|
|
39
94
|
* operator goes to ask what is switched on. See `core/sampler.ts` for why an
|
|
40
95
|
* unconfigured sampler disables sampling rather than escalating everything.
|
|
41
96
|
*/
|
|
42
|
-
import { sampleSupervised } from "../core/audit.js";
|
|
97
|
+
import { sampleSupervised, } from "../core/audit.js";
|
|
43
98
|
/**
|
|
44
99
|
* Disablement reasons already reported in this process, so a standing
|
|
45
100
|
* configuration fact is stated once rather than every tick. Process-lifetime and
|
|
@@ -47,9 +102,52 @@ import { sampleSupervised } from "../core/audit.js";
|
|
|
47
102
|
* losing it costs one extra warning line and nothing else.
|
|
48
103
|
*/
|
|
49
104
|
const reported = new Set();
|
|
50
|
-
/**
|
|
105
|
+
/**
|
|
106
|
+
* Samples this process deferred and has not yet seen appended (APRV-381), keyed
|
|
107
|
+
* by log path and subject hash.
|
|
108
|
+
*
|
|
109
|
+
* Exactly the same standing as {@link reported}: process-lifetime, memory-only,
|
|
110
|
+
* and a de-duplicator for OUTPUT. Nothing here is consulted before an append,
|
|
111
|
+
* gates nothing, and suppresses no line. Losing it (a restart, a second daemon,
|
|
112
|
+
* an operator sweeping by hand) costs the word "retry" on one line and nothing
|
|
113
|
+
* else, which is why the retry itself is derived from the log and never from
|
|
114
|
+
* here.
|
|
115
|
+
*
|
|
116
|
+
* Bounded so a long run cannot accumulate: an entry is dropped when its sample
|
|
117
|
+
* lands, and the oldest is evicted past {@link DEFERRAL_MEMORY_LIMIT} for the
|
|
118
|
+
* candidate that is deferred and then stops being a candidate (a policy edit that
|
|
119
|
+
* drops its class, a rate that no longer selects it). A `Set` iterates in
|
|
120
|
+
* insertion order, so "the oldest" needs no timestamp.
|
|
121
|
+
*/
|
|
122
|
+
const deferrals = new Set();
|
|
123
|
+
/** How many deferred samples this process will remember for the retry wording. */
|
|
124
|
+
const DEFERRAL_MEMORY_LIMIT = 256;
|
|
125
|
+
function deferralKey(logPath, subjectHash) {
|
|
126
|
+
return JSON.stringify([logPath, subjectHash]);
|
|
127
|
+
}
|
|
128
|
+
function rememberDeferral(logPath, subjectHash) {
|
|
129
|
+
const key = deferralKey(logPath, subjectHash);
|
|
130
|
+
if (deferrals.has(key))
|
|
131
|
+
return;
|
|
132
|
+
if (deferrals.size >= DEFERRAL_MEMORY_LIMIT) {
|
|
133
|
+
const oldest = deferrals.values().next();
|
|
134
|
+
if (!oldest.done)
|
|
135
|
+
deferrals.delete(oldest.value);
|
|
136
|
+
}
|
|
137
|
+
deferrals.add(key);
|
|
138
|
+
}
|
|
139
|
+
/** Whether this process deferred this sample, consuming the memory of it. */
|
|
140
|
+
function takeDeferral(logPath, subjectHash) {
|
|
141
|
+
return deferrals.delete(deferralKey(logPath, subjectHash));
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Reset the once-per-process notice and the deferral memory. Exported for tests,
|
|
145
|
+
* used nowhere else: both are output bookkeeping, so a suite that shares a
|
|
146
|
+
* process needs to be able to start from nothing.
|
|
147
|
+
*/
|
|
51
148
|
export function resetAuditSweepNotices() {
|
|
52
149
|
reported.clear();
|
|
150
|
+
deferrals.clear();
|
|
53
151
|
}
|
|
54
152
|
/** One sampling sweep. Appends through `core/audit.ts` and decides nothing. */
|
|
55
153
|
export function sweepAuditSampling(options) {
|
|
@@ -80,11 +178,33 @@ export function sweepAuditSampling(options) {
|
|
|
80
178
|
for (const refusal of result.refusals) {
|
|
81
179
|
options.warn(`audit sampling: ${refusal.code}: ${refusal.message}`);
|
|
82
180
|
}
|
|
181
|
+
// The transient half (APRV-381). Said BEFORE the successes of this same sweep
|
|
182
|
+
// are said, because the sweep stops at the first deferral: anything it did
|
|
183
|
+
// append, it appended before the contention, and the reading order should match
|
|
184
|
+
// the order it happened in.
|
|
185
|
+
for (const entry of result.deferred) {
|
|
186
|
+
rememberDeferral(options.logPath, entry.candidate.hash);
|
|
187
|
+
(options.defer ?? options.warn)(deferralLine(entry));
|
|
188
|
+
}
|
|
83
189
|
const sampled = options.sampled;
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
190
|
+
for (const entry of result.appended) {
|
|
191
|
+
// Consumed whether or not anyone is listening, so a caller with no `sampled`
|
|
192
|
+
// sink cannot leave a stale deferral behind to mislabel a later sample.
|
|
193
|
+
const retry = takeDeferral(options.logPath, entry.candidate.hash);
|
|
194
|
+
if (sampled !== undefined)
|
|
195
|
+
sampled(entry, retry);
|
|
87
196
|
}
|
|
88
197
|
return { sampled: result.appended.length, disabled: null };
|
|
89
198
|
}
|
|
199
|
+
/**
|
|
200
|
+
* The deferral line: what was not appended, why, and that it is coming.
|
|
201
|
+
*
|
|
202
|
+
* "retries it on the next tick" is load-bearing wording. The line an
|
|
203
|
+
* operator read before APRV-381 ended at "was not appended", which is true and
|
|
204
|
+
* reads as a loss; this one has to answer the question that sentence provokes
|
|
205
|
+
* before they go looking for a missing audit record.
|
|
206
|
+
*/
|
|
207
|
+
function deferralLine(entry) {
|
|
208
|
+
return `audit sampling deferred ${entry.candidate.actionKey} (${entry.append.code}): ${entry.append.message} The sample is NOT lost: it is still pending in the log's own terms (eligible, drawn, no audit.sampled yet), and the sweep retries it on the next tick.`;
|
|
209
|
+
}
|
|
90
210
|
//# sourceMappingURL=audit.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"audit.js","sourceRoot":"","sources":["../../../src/daemon/audit.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"audit.js","sourceRoot":"","sources":["../../../src/daemon/audit.ts"],"names":[],"mappings":"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"}
|
|
@@ -104,6 +104,12 @@ import { type PruneReason } from "./prune.js";
|
|
|
104
104
|
export declare const DAEMON_ACTOR = "system:daemon";
|
|
105
105
|
/** Backlog.md's conventional task folder, relative to the working directory. */
|
|
106
106
|
export { DEFAULT_TASKS_DIR } from "../core/registration.js";
|
|
107
|
+
/**
|
|
108
|
+
* Forget every deferral. Exported for tests, used nowhere else: the memory is
|
|
109
|
+
* output bookkeeping, so a suite that shares a process needs to be able to start
|
|
110
|
+
* from nothing and to exercise what a restarted daemon does.
|
|
111
|
+
*/
|
|
112
|
+
export declare function resetDriftDeferrals(): void;
|
|
107
113
|
/** How often the daemon looks, absent any watcher event. */
|
|
108
114
|
export declare const DEFAULT_INTERVAL_MS = 30000;
|
|
109
115
|
/** How long a burst of watcher events is allowed to settle before a tick. */
|
|
@@ -203,6 +209,18 @@ export type DaemonEvent = {
|
|
|
203
209
|
* a person.
|
|
204
210
|
*/
|
|
205
211
|
dangling_advances?: string[];
|
|
212
|
+
/**
|
|
213
|
+
* Which daemon instance this run is (APRV-383), and `null` when the id it
|
|
214
|
+
* was launched with is unusable. Additive, like every other growth of this
|
|
215
|
+
* union.
|
|
216
|
+
*
|
|
217
|
+
* On the FIRST line for the reason `read_proof` and `draw` are: it is the
|
|
218
|
+
* name every record this run appends will carry, a tenant reading a hosted
|
|
219
|
+
* log identifies the process by it, and it is not something an operator
|
|
220
|
+
* should have to ask the process about. `null` is the state in which this
|
|
221
|
+
* run will refuse every append, which the warning beside it says in words.
|
|
222
|
+
*/
|
|
223
|
+
daemon?: string | null;
|
|
206
224
|
} | {
|
|
207
225
|
event: "drift";
|
|
208
226
|
task: string;
|
|
@@ -216,6 +234,18 @@ export type DaemonEvent = {
|
|
|
216
234
|
* appeared on all of them would change a shape supervisors already parse.
|
|
217
235
|
*/
|
|
218
236
|
reason?: "envelope-missing";
|
|
237
|
+
/**
|
|
238
|
+
* This append is the one an earlier tick of THIS run deferred (APRV-403).
|
|
239
|
+
* Present only when it is true, so the shape a supervisor already parses is
|
|
240
|
+
* unchanged for every other drift record.
|
|
241
|
+
*
|
|
242
|
+
* It closes a `drift-deferred` warning, exactly as `sampled.retry` closes a
|
|
243
|
+
* `sample-deferred` one: the pair is what tells an operator that the
|
|
244
|
+
* transient failure they read resolved, and which record resolved it. A
|
|
245
|
+
* fresh process makes the same append and claims no retry, because this
|
|
246
|
+
* loop says only what it witnessed.
|
|
247
|
+
*/
|
|
248
|
+
retry?: true;
|
|
219
249
|
} | {
|
|
220
250
|
/**
|
|
221
251
|
* A task file's `state:` was rewritten to match the log (SPEC.md §6.3).
|
|
@@ -252,6 +282,18 @@ export type DaemonEvent = {
|
|
|
252
282
|
seq: number;
|
|
253
283
|
/** `seq` of the `execution.started` record the sample named. */
|
|
254
284
|
subject_seq: number;
|
|
285
|
+
/**
|
|
286
|
+
* This append is the one an earlier tick of THIS run deferred (APRV-381).
|
|
287
|
+
* Present only when it is true, so the shape a supervisor already parses is
|
|
288
|
+
* unchanged for every other sample.
|
|
289
|
+
*
|
|
290
|
+
* It closes a `sample-deferred` warning: the pair is what tells an operator
|
|
291
|
+
* that the transient failure they read resolved, and which sample resolved
|
|
292
|
+
* it. Absent on a run that did not itself defer the sample, including a
|
|
293
|
+
* fresh process picking up a deferral the previous one printed, because this
|
|
294
|
+
* loop says only what it witnessed.
|
|
295
|
+
*/
|
|
296
|
+
retry?: true;
|
|
255
297
|
} | {
|
|
256
298
|
/**
|
|
257
299
|
* A payload's bytes were removed under `payload_retention` (APRV-41,
|
|
@@ -551,6 +593,19 @@ export declare const DAEMON_WARNING_CODES: readonly [
|
|
|
551
593
|
* repair, and the next tick asks again.
|
|
552
594
|
*/
|
|
553
595
|
"anchor-behind",
|
|
596
|
+
/**
|
|
597
|
+
* The working log changed between this tick's opening read and the anchor
|
|
598
|
+
* check's own read of the file, so the check read it again (APRV-389).
|
|
599
|
+
*
|
|
600
|
+
* A line rather than a stop, and rather than a silence. `approval log sync`
|
|
601
|
+
* and a records pull both rewrite `events.jsonl` under a running daemon
|
|
602
|
+
* (baseline, fast-forward, restore), and this loop reads without the append
|
|
603
|
+
* lock, so a tick can hold a view the file has already moved past. That is a
|
|
604
|
+
* fact about reads, never a fork: the anchor comparison is made again from the
|
|
605
|
+
* file and the verdict beside this line is the second read's. The line is the
|
|
606
|
+
* one an operator acts on when the writer was not a sync they ran.
|
|
607
|
+
*/
|
|
608
|
+
"anchor-reread",
|
|
554
609
|
/**
|
|
555
610
|
* `audit.checkpoint_every` says a human-signed checkpoint is due and the log
|
|
556
611
|
* carries none that recent (APRV-220). A WARNING and never a stop, at every
|
|
@@ -566,7 +621,54 @@ export declare const DAEMON_WARNING_CODES: readonly [
|
|
|
566
621
|
* while it stands, and that is a thing to know rather than to discover from a
|
|
567
622
|
* month of taps.
|
|
568
623
|
*/
|
|
569
|
-
"draw-unavailable"
|
|
624
|
+
"draw-unavailable",
|
|
625
|
+
/**
|
|
626
|
+
* This daemon's own id is not one the attested policy's `daemons` list admits,
|
|
627
|
+
* or the id it was launched with is not a usable id at all (APRV-383).
|
|
628
|
+
*
|
|
629
|
+
* A warning rather than a stop, and the distinction matters: the refusal itself
|
|
630
|
+
* happens at the write boundary, one append at a time, so the loop keeps
|
|
631
|
+
* reading, keeps rendering the queue and keeps reporting, and writes nothing.
|
|
632
|
+
* The line exists because an operator should learn that from the first tick
|
|
633
|
+
* rather than from a day of `append-refused` messages. The repair is a line in
|
|
634
|
+
* a policy a human re-attests, or a launch environment fixed, and neither is
|
|
635
|
+
* something this process can do for itself.
|
|
636
|
+
*/
|
|
637
|
+
"daemon-identity-refused",
|
|
638
|
+
/**
|
|
639
|
+
* An `audit.sampled` append met a held lock or a moved head, so the sample was
|
|
640
|
+
* postponed to the next tick (APRV-381). Appended to this union, so no existing
|
|
641
|
+
* entry changed meaning.
|
|
642
|
+
*
|
|
643
|
+
* Distinct from `append-refused` because the operator's next move is different:
|
|
644
|
+
* `append-refused` on a sample is a fact about the record or the file and wants
|
|
645
|
+
* a person, and this is contention between writers that the next sweep resolves
|
|
646
|
+
* by re-deriving the sample from the log. It is still a warning rather than a
|
|
647
|
+
* silence, because a tick that keeps deferring is a log under contention and
|
|
648
|
+
* that is a thing to know, and the `sampled` line that closes it carries
|
|
649
|
+
* `retry`.
|
|
650
|
+
*/
|
|
651
|
+
"sample-deferred",
|
|
652
|
+
/**
|
|
653
|
+
* An `envelope.drift` append met a held lock or a moved head, so the record was
|
|
654
|
+
* postponed to the next tick (APRV-403). Appended to this union, so no existing
|
|
655
|
+
* entry changed meaning.
|
|
656
|
+
*
|
|
657
|
+
* The same split APRV-381 made for `audit.sampled`, for the same reason and
|
|
658
|
+
* from the same closed list of transient codes. Found while landing that task:
|
|
659
|
+
* the end-to-end test that holds the append lockfile for a whole tick showed
|
|
660
|
+
* the drift scan printing `append-refused … (lock-timeout)`, which is true and
|
|
661
|
+
* reads as a lost record, while the next tick re-derived the drift from the
|
|
662
|
+
* verified log and appended it. An operator's next move differs between the
|
|
663
|
+
* two: `append-refused` on a drift record is a fact about the record or the
|
|
664
|
+
* file and wants a person, and this is contention between writers that the
|
|
665
|
+
* scan resolves by itself.
|
|
666
|
+
*
|
|
667
|
+
* Still a warning rather than a silence: a scan that keeps deferring is a log
|
|
668
|
+
* under contention, which is a thing to know. The `drift` line that closes it
|
|
669
|
+
* carries `retry`.
|
|
670
|
+
*/
|
|
671
|
+
"drift-deferred"];
|
|
570
672
|
export type DaemonWarningCode = (typeof DAEMON_WARNING_CODES)[number];
|
|
571
673
|
/** Where daemon output goes. Injected, so the loop itself writes to nothing. */
|
|
572
674
|
export interface DaemonSink {
|
|
@@ -838,6 +940,16 @@ export declare class Daemon {
|
|
|
838
940
|
private previousSelfWrites;
|
|
839
941
|
private settle;
|
|
840
942
|
private finished;
|
|
943
|
+
/**
|
|
944
|
+
* What this process resolved as its own daemon id (APRV-383), kept so the
|
|
945
|
+
* `started` line can name it and the startup check can say what is wrong with
|
|
946
|
+
* it. The id in FORCE lives in `core/daemon-identity.ts`'s process state, which
|
|
947
|
+
* is what the write boundary reads; this field is the resolution that produced
|
|
948
|
+
* it, and nothing reads it to decide anything.
|
|
949
|
+
*/
|
|
950
|
+
private readonly identity;
|
|
951
|
+
/** Has the unlisted-id warning already been said this run? */
|
|
952
|
+
private reportedIdentityRefusal;
|
|
841
953
|
/** Whether {@link DaemonOptions.draw} actually bound (APRV-208). */
|
|
842
954
|
private drawServing;
|
|
843
955
|
constructor(options: DaemonOptions);
|
|
@@ -1025,6 +1137,23 @@ export declare class Daemon {
|
|
|
1025
1137
|
*/
|
|
1026
1138
|
private read;
|
|
1027
1139
|
private readOnce;
|
|
1140
|
+
/** Where this daemon's policy is, in `loadPolicy`'s own vocabulary. */
|
|
1141
|
+
private policyWhere;
|
|
1142
|
+
/**
|
|
1143
|
+
* Put the attested policy's `daemons` allowlist in force for this tick
|
|
1144
|
+
* (APRV-383).
|
|
1145
|
+
*
|
|
1146
|
+
* Called once per tick, immediately after the opening verified read and before
|
|
1147
|
+
* any sweep that appends. It never widens: a resolution that fails sets nothing,
|
|
1148
|
+
* so whatever was last resolved stays in force, and a run whose policy was never
|
|
1149
|
+
* attested is unrestricted exactly as every run before this key existed was.
|
|
1150
|
+
*
|
|
1151
|
+
* The warning is said ONCE per run. A line every thirty seconds forever stops
|
|
1152
|
+
* being read (the `reportedDangling` set above exists for the same reason), and
|
|
1153
|
+
* the write boundary is where the refusal actually happens: every refused append
|
|
1154
|
+
* carries its own `append-refused` line with the reason in it.
|
|
1155
|
+
*/
|
|
1156
|
+
private refreshIdentity;
|
|
1028
1157
|
/** The TTL in force right now, re-read every pass: policy files change. */
|
|
1029
1158
|
private ttlMs;
|
|
1030
1159
|
private gateOptions;
|
|
@@ -1045,8 +1174,46 @@ export declare class Daemon {
|
|
|
1045
1174
|
* malformed file is not a *contradiction* of the log, it is a file the runtime
|
|
1046
1175
|
* cannot read a claim out of at all, and inventing a `declared_state` for it
|
|
1047
1176
|
* would put a fact in the log that nobody wrote.
|
|
1177
|
+
*
|
|
1178
|
+
* ## A deferred drift record is not a lost one (APRV-403)
|
|
1179
|
+
*
|
|
1180
|
+
* An append here can fail two ways that call for different things. A held
|
|
1181
|
+
* lockfile or a head that moved under the compare-and-append means the log was
|
|
1182
|
+
* not written and nothing about the file changed; the next tick re-reads the
|
|
1183
|
+
* folder, re-derives the disagreement from the verified log, and appends it.
|
|
1184
|
+
* A validation refusal, a canonicalization failure, a corrupt tail or an I/O
|
|
1185
|
+
* error is a fact about the record or the file that no retry repairs.
|
|
1186
|
+
*
|
|
1187
|
+
* Both used to read as `append-refused … was not appended`, which for the
|
|
1188
|
+
* first is true and reads as a loss. So the transient half now goes out as
|
|
1189
|
+
* {@link deferDrift}'s own `drift-deferred` line, naming the task and saying
|
|
1190
|
+
* that the scan retries, and the `drift` record that closes it carries
|
|
1191
|
+
* `retry`. Everything else keeps the form it had.
|
|
1192
|
+
*
|
|
1193
|
+
* Nothing about the APPEND changed, which is the part that matters for
|
|
1194
|
+
* SPEC.md §11.1 invariant 5: the record is still placed by `appendEvent`
|
|
1195
|
+
* against the `expectedHead` of a read taken immediately before it, and the
|
|
1196
|
+
* whole decision is still remade against that read. A `head-moved` here IS
|
|
1197
|
+
* compare-and-append working — another writer landed a record between this
|
|
1198
|
+
* scan's read and its append — and deferring is the only answer that does not
|
|
1199
|
+
* either write against a head this scan never saw or lose the observation.
|
|
1048
1200
|
*/
|
|
1049
1201
|
private scanForDrift;
|
|
1202
|
+
/**
|
|
1203
|
+
* Say that an `envelope.drift` append did not happen YET, and remember it
|
|
1204
|
+
* (APRV-403).
|
|
1205
|
+
*
|
|
1206
|
+
* Two things this line has to do that `append-refused` did not. It names the
|
|
1207
|
+
* TASK KEY, so an operator can match the deferral to the record that closes
|
|
1208
|
+
* it, and it says in words that the next tick retries — because the sentence
|
|
1209
|
+
* it replaced ended at "was not appended", which is true and reads as a loss.
|
|
1210
|
+
*
|
|
1211
|
+
* The claim is safe to make because the scan carries nothing between ticks:
|
|
1212
|
+
* the next one re-reads the folder, re-derives the state from the verified
|
|
1213
|
+
* log, and finds the same disagreement, so the retry is a property of how the
|
|
1214
|
+
* scan is written rather than a promise this method is keeping.
|
|
1215
|
+
*/
|
|
1216
|
+
private deferDrift;
|
|
1050
1217
|
/** Every `*.md` under the task folder, sorted, non-recursive. */
|
|
1051
1218
|
private taskFiles;
|
|
1052
1219
|
private checkOneFile;
|
|
@@ -1116,7 +1283,7 @@ export declare class Daemon {
|
|
|
1116
1283
|
* log, and produces bytes only through `core/task-file.ts`. A file can no more
|
|
1117
1284
|
* teach the log a state than a screenshot can teach a database a row.
|
|
1118
1285
|
*
|
|
1119
|
-
*
|
|
1286
|
+
* Five rules, each of which is a way of not making things worse:
|
|
1120
1287
|
*
|
|
1121
1288
|
* 1. **Only files that already have an envelope.** `set-state` refuses
|
|
1122
1289
|
* `no-envelope`, and that refusal is honoured silently: a task with no
|
|
@@ -1138,6 +1305,11 @@ export declare class Daemon {
|
|
|
1138
1305
|
* envelope: each was warned about a few milliseconds ago by
|
|
1139
1306
|
* {@link scanForDrift} over the same folder. Repeating it here would double
|
|
1140
1307
|
* every line an operator reads without adding a fact.
|
|
1308
|
+
* 5. **No repair for a drift record that was deferred** (APRV-403). See the
|
|
1309
|
+
* check inside the loop: a file whose `envelope.drift` this tick could not
|
|
1310
|
+
* append is left exactly as it is, because repairing it would correct the
|
|
1311
|
+
* disagreement off the record and erase what the retry is supposed to
|
|
1312
|
+
* re-derive.
|
|
1141
1313
|
*
|
|
1142
1314
|
* Loop safety comes from the comparison, not from a remembered flag: the next
|
|
1143
1315
|
* tick derives the same state from the same log, finds the file already
|