approval-md 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +85 -877
- package/SPEC.md +150 -19
- package/cli.js +66 -7
- package/dist/src/adapters/smtp.d.ts +18 -0
- package/dist/src/adapters/smtp.js +24 -3
- package/dist/src/adapters/smtp.js.map +1 -1
- package/dist/src/channels/contract.d.ts +53 -1
- package/dist/src/channels/contract.js +304 -7
- package/dist/src/channels/contract.js.map +1 -1
- package/dist/src/channels/relay-server.d.ts +90 -0
- package/dist/src/channels/relay-server.js +295 -0
- package/dist/src/channels/relay-server.js.map +1 -0
- package/dist/src/channels/relay.d.ts +273 -0
- package/dist/src/channels/relay.js +564 -0
- package/dist/src/channels/relay.js.map +1 -0
- package/dist/src/channels/tagging.js +6 -1
- package/dist/src/channels/tagging.js.map +1 -1
- package/dist/src/channels/telegram-webhook.d.ts +273 -0
- package/dist/src/channels/telegram-webhook.js +536 -0
- package/dist/src/channels/telegram-webhook.js.map +1 -0
- package/dist/src/channels/telegram.d.ts +394 -12
- package/dist/src/channels/telegram.js +621 -68
- package/dist/src/channels/telegram.js.map +1 -1
- package/dist/src/channels/web.d.ts +9 -0
- package/dist/src/channels/web.js +17 -0
- package/dist/src/channels/web.js.map +1 -1
- package/dist/src/cli/amend.js +564 -34
- package/dist/src/cli/amend.js.map +1 -1
- package/dist/src/cli/attest.d.ts +9 -0
- package/dist/src/cli/attest.js +177 -13
- package/dist/src/cli/attest.js.map +1 -1
- package/dist/src/cli/channel-relay.d.ts +108 -0
- package/dist/src/cli/channel-relay.js +334 -0
- package/dist/src/cli/channel-relay.js.map +1 -0
- package/dist/src/cli/channel-telegram-webhook.d.ts +273 -0
- package/dist/src/cli/channel-telegram-webhook.js +709 -0
- package/dist/src/cli/channel-telegram-webhook.js.map +1 -0
- package/dist/src/cli/channel-telegram.d.ts +347 -28
- package/dist/src/cli/channel-telegram.js +763 -28
- package/dist/src/cli/channel-telegram.js.map +1 -1
- package/dist/src/cli/channel.d.ts +10 -1
- package/dist/src/cli/channel.js +14 -1
- package/dist/src/cli/channel.js.map +1 -1
- package/dist/src/cli/codex-bridge.d.ts +819 -0
- package/dist/src/cli/codex-bridge.js +1607 -0
- package/dist/src/cli/codex-bridge.js.map +1 -0
- package/dist/src/cli/codex.d.ts +1 -1
- package/dist/src/cli/codex.js +304 -7
- package/dist/src/cli/codex.js.map +1 -1
- package/dist/src/cli/daemon.js +41 -6
- package/dist/src/cli/daemon.js.map +1 -1
- package/dist/src/cli/doctor.d.ts +63 -0
- package/dist/src/cli/doctor.js +1322 -122
- package/dist/src/cli/doctor.js.map +1 -1
- package/dist/src/cli/execute.d.ts +17 -1
- package/dist/src/cli/execute.js +366 -22
- package/dist/src/cli/execute.js.map +1 -1
- package/dist/src/cli/exit-codes.d.ts +12 -0
- package/dist/src/cli/exit-codes.js +13 -0
- package/dist/src/cli/exit-codes.js.map +1 -1
- package/dist/src/cli/gate.d.ts +2 -0
- package/dist/src/cli/gate.js +233 -2
- package/dist/src/cli/gate.js.map +1 -1
- package/dist/src/cli/help.d.ts +14 -2
- package/dist/src/cli/help.js +395 -82
- package/dist/src/cli/help.js.map +1 -1
- package/dist/src/cli/hook-codex.d.ts +49 -1
- package/dist/src/cli/hook-codex.js +60 -1
- package/dist/src/cli/hook-codex.js.map +1 -1
- package/dist/src/cli/hook.d.ts +828 -4
- package/dist/src/cli/hook.js +2972 -142
- package/dist/src/cli/hook.js.map +1 -1
- package/dist/src/cli/import.js +1 -1
- package/dist/src/cli/import.js.map +1 -1
- package/dist/src/cli/init.js +16 -7
- package/dist/src/cli/init.js.map +1 -1
- package/dist/src/cli/instructions.js +24 -3
- package/dist/src/cli/instructions.js.map +1 -1
- package/dist/src/cli/log-anchor.d.ts +55 -0
- package/dist/src/cli/log-anchor.js +199 -60
- package/dist/src/cli/log-anchor.js.map +1 -1
- package/dist/src/cli/main.d.ts +0 -12
- package/dist/src/cli/main.js +56 -3
- package/dist/src/cli/main.js.map +1 -1
- package/dist/src/cli/muse.d.ts +2 -0
- package/dist/src/cli/muse.js +62 -0
- package/dist/src/cli/muse.js.map +1 -0
- package/dist/src/cli/payload.js +79 -1
- package/dist/src/cli/payload.js.map +1 -1
- package/dist/src/cli/policy-apply.d.ts +195 -0
- package/dist/src/cli/policy-apply.js +573 -0
- package/dist/src/cli/policy-apply.js.map +1 -0
- package/dist/src/cli/policy.js +14 -1
- package/dist/src/cli/policy.js.map +1 -1
- package/dist/src/cli/preflight.d.ts +151 -13
- package/dist/src/cli/preflight.js +398 -41
- package/dist/src/cli/preflight.js.map +1 -1
- package/dist/src/cli/sandbox.js +17 -1
- package/dist/src/cli/sandbox.js.map +1 -1
- package/dist/src/cli/scaffold.d.ts +1 -1
- package/dist/src/cli/scaffold.js +7 -1
- package/dist/src/cli/scaffold.js.map +1 -1
- package/dist/src/cli/serve.d.ts +53 -0
- package/dist/src/cli/serve.js +270 -0
- package/dist/src/cli/serve.js.map +1 -0
- package/dist/src/cli/setup-channel.d.ts +9 -0
- package/dist/src/cli/setup-channel.js +28 -1
- package/dist/src/cli/setup-channel.js.map +1 -1
- package/dist/src/cli/setup-common.d.ts +3 -1
- package/dist/src/cli/setup-common.js +3 -2
- package/dist/src/cli/setup-common.js.map +1 -1
- package/dist/src/cli/setup.d.ts +2 -0
- package/dist/src/cli/setup.js +94 -2
- package/dist/src/cli/setup.js.map +1 -1
- package/dist/src/cli/up.js +134 -51
- package/dist/src/cli/up.js.map +1 -1
- package/dist/src/cli/values.js +3 -4
- package/dist/src/cli/values.js.map +1 -1
- package/dist/src/cli/verb-registry.d.ts +2 -0
- package/dist/src/cli/verb-registry.js +510 -47
- package/dist/src/cli/verb-registry.js.map +1 -1
- package/dist/src/cli/wordmark.d.ts +2 -2
- package/dist/src/cli/wordmark.js +2 -2
- package/dist/src/codex/broker.d.ts +229 -0
- package/dist/src/codex/broker.js +548 -0
- package/dist/src/codex/broker.js.map +1 -0
- package/dist/src/codex/runner.d.ts +178 -0
- package/dist/src/codex/runner.js +231 -0
- package/dist/src/codex/runner.js.map +1 -0
- package/dist/src/codex/serve.d.ts +56 -0
- package/dist/src/codex/serve.js +98 -0
- package/dist/src/codex/serve.js.map +1 -0
- package/dist/src/codex/workspace-commit.d.ts +219 -0
- package/dist/src/codex/workspace-commit.js +549 -0
- package/dist/src/codex/workspace-commit.js.map +1 -0
- package/dist/src/core/advance-cycle.d.ts +51 -0
- package/dist/src/core/advance-cycle.js +66 -2
- package/dist/src/core/advance-cycle.js.map +1 -1
- package/dist/src/core/agents-md.d.ts +20 -18
- package/dist/src/core/agents-md.js +33 -31
- package/dist/src/core/agents-md.js.map +1 -1
- package/dist/src/core/attest-bootstrap.d.ts +95 -0
- package/dist/src/core/attest-bootstrap.js +135 -0
- package/dist/src/core/attest-bootstrap.js.map +1 -0
- package/dist/src/core/attest.d.ts +234 -2
- package/dist/src/core/attest.js +342 -7
- package/dist/src/core/attest.js.map +1 -1
- package/dist/src/core/audit.d.ts +73 -3
- package/dist/src/core/audit.js +65 -5
- package/dist/src/core/audit.js.map +1 -1
- package/dist/src/core/channel-lease.d.ts +322 -0
- package/dist/src/core/channel-lease.js +663 -0
- package/dist/src/core/channel-lease.js.map +1 -0
- package/dist/src/core/channel-owner.d.ts +213 -0
- package/dist/src/core/channel-owner.js +358 -0
- package/dist/src/core/channel-owner.js.map +1 -0
- package/dist/src/core/command-class.d.ts +205 -0
- package/dist/src/core/command-class.js +1433 -28
- package/dist/src/core/command-class.js.map +1 -1
- package/dist/src/core/commit-guard.d.ts +272 -0
- package/dist/src/core/commit-guard.js +424 -0
- package/dist/src/core/commit-guard.js.map +1 -0
- package/dist/src/core/daemon-actor.d.ts +45 -0
- package/dist/src/core/daemon-actor.js +54 -0
- package/dist/src/core/daemon-actor.js.map +1 -0
- package/dist/src/core/daemon-host.d.ts +178 -0
- package/dist/src/core/daemon-host.js +228 -0
- package/dist/src/core/daemon-host.js.map +1 -0
- package/dist/src/core/daemon-identity.d.ts +238 -0
- package/dist/src/core/daemon-identity.js +252 -0
- package/dist/src/core/daemon-identity.js.map +1 -0
- package/dist/src/core/dark-session.d.ts +109 -8
- package/dist/src/core/dark-session.js +266 -82
- package/dist/src/core/dark-session.js.map +1 -1
- package/dist/src/core/decision-refusal.d.ts +23 -2
- package/dist/src/core/decision-refusal.js +24 -2
- package/dist/src/core/decision-refusal.js.map +1 -1
- package/dist/src/core/env-file.d.ts +5 -0
- package/dist/src/core/env-file.js +60 -1
- package/dist/src/core/env-file.js.map +1 -1
- package/dist/src/core/execute.d.ts +15 -2
- package/dist/src/core/execute.js +15 -2
- package/dist/src/core/execute.js.map +1 -1
- package/dist/src/core/gate.d.ts +392 -6
- package/dist/src/core/gate.js +852 -24
- package/dist/src/core/gate.js.map +1 -1
- package/dist/src/core/gesture-refusal.d.ts +166 -0
- package/dist/src/core/gesture-refusal.js +188 -0
- package/dist/src/core/gesture-refusal.js.map +1 -0
- package/dist/src/core/harness-version.d.ts +128 -4
- package/dist/src/core/harness-version.js +181 -12
- package/dist/src/core/harness-version.js.map +1 -1
- package/dist/src/core/harness-wait.d.ts +82 -3
- package/dist/src/core/harness-wait.js +89 -3
- package/dist/src/core/harness-wait.js.map +1 -1
- package/dist/src/core/instance.d.ts +59 -2
- package/dist/src/core/instance.js +113 -0
- package/dist/src/core/instance.js.map +1 -1
- package/dist/src/core/log-subscribe.d.ts +21 -0
- package/dist/src/core/log-subscribe.js +16 -6
- package/dist/src/core/log-subscribe.js.map +1 -1
- package/dist/src/core/log-write-layer.d.ts +55 -0
- package/dist/src/core/log-write-layer.js +91 -0
- package/dist/src/core/log-write-layer.js.map +1 -0
- package/dist/src/core/log.d.ts +89 -22
- package/dist/src/core/log.js +197 -19
- package/dist/src/core/log.js.map +1 -1
- package/dist/src/core/payload-census.d.ts +51 -0
- package/dist/src/core/payload-census.js +125 -2
- package/dist/src/core/payload-census.js.map +1 -1
- package/dist/src/core/payload-store.d.ts +31 -0
- package/dist/src/core/payload-store.js +113 -8
- package/dist/src/core/payload-store.js.map +1 -1
- package/dist/src/core/payload.d.ts +18 -3
- package/dist/src/core/payload.js +20 -4
- package/dist/src/core/payload.js.map +1 -1
- package/dist/src/core/policy-diff.d.ts +10 -0
- package/dist/src/core/policy-diff.js +10 -2
- package/dist/src/core/policy-diff.js.map +1 -1
- package/dist/src/core/policy-explain.d.ts +17 -1
- package/dist/src/core/policy-explain.js +48 -1
- package/dist/src/core/policy-explain.js.map +1 -1
- package/dist/src/core/policy-load.d.ts +65 -1
- package/dist/src/core/policy-load.js +21 -3
- package/dist/src/core/policy-load.js.map +1 -1
- package/dist/src/core/policy-match.d.ts +81 -0
- package/dist/src/core/policy-match.js +87 -0
- package/dist/src/core/policy-match.js.map +1 -1
- package/dist/src/core/policy-proposal.d.ts +75 -0
- package/dist/src/core/policy-proposal.js +110 -4
- package/dist/src/core/policy-proposal.js.map +1 -1
- package/dist/src/core/protected-path-guard.d.ts +117 -4
- package/dist/src/core/protected-path-guard.js +362 -48
- package/dist/src/core/protected-path-guard.js.map +1 -1
- package/dist/src/core/question-preempted.d.ts +141 -0
- package/dist/src/core/question-preempted.js +152 -0
- package/dist/src/core/question-preempted.js.map +1 -0
- package/dist/src/core/read-scope.d.ts +182 -0
- package/dist/src/core/read-scope.js +256 -0
- package/dist/src/core/read-scope.js.map +1 -0
- package/dist/src/core/run-payload.d.ts +160 -0
- package/dist/src/core/run-payload.js +304 -0
- package/dist/src/core/run-payload.js.map +1 -0
- package/dist/src/core/sandbox.d.ts +81 -0
- package/dist/src/core/sandbox.js +190 -1
- package/dist/src/core/sandbox.js.map +1 -1
- package/dist/src/core/sender-identity.d.ts +501 -0
- package/dist/src/core/sender-identity.js +609 -0
- package/dist/src/core/sender-identity.js.map +1 -0
- package/dist/src/core/shlex.d.ts +102 -0
- package/dist/src/core/shlex.js +159 -0
- package/dist/src/core/shlex.js.map +1 -0
- package/dist/src/core/state.d.ts +52 -1
- package/dist/src/core/state.js +52 -6
- package/dist/src/core/state.js.map +1 -1
- package/dist/src/core/telegram-config.d.ts +79 -0
- package/dist/src/core/telegram-config.js +108 -0
- package/dist/src/core/telegram-config.js.map +1 -1
- package/dist/src/core/values.d.ts +18 -8
- package/dist/src/core/values.js +36 -1
- package/dist/src/core/values.js.map +1 -1
- package/dist/src/core/verify.d.ts +23 -1
- package/dist/src/core/verify.js +20 -2
- package/dist/src/core/verify.js.map +1 -1
- package/dist/src/daemon/advance.d.ts +10 -0
- package/dist/src/daemon/advance.js +25 -4
- package/dist/src/daemon/advance.js.map +1 -1
- package/dist/src/daemon/audit.d.ts +80 -5
- package/dist/src/daemon/audit.js +126 -6
- package/dist/src/daemon/audit.js.map +1 -1
- package/dist/src/daemon/daemon.d.ts +174 -2
- package/dist/src/daemon/daemon.js +307 -7
- package/dist/src/daemon/daemon.js.map +1 -1
- package/dist/src/daemon/git-evidence.d.ts +2 -2
- package/dist/src/daemon/git-evidence.js +1 -1
- package/dist/src/daemon/projection.d.ts +13 -2
- package/dist/src/daemon/projection.js +39 -6
- package/dist/src/daemon/projection.js.map +1 -1
- package/dist/src/mcp/server.d.ts +39 -3
- package/dist/src/mcp/server.js +30 -3
- package/dist/src/mcp/server.js.map +1 -1
- package/dist/src/muse/server.d.ts +16 -0
- package/dist/src/muse/server.js +238 -0
- package/dist/src/muse/server.js.map +1 -0
- package/dist/src/serve/archive.d.ts +158 -0
- package/dist/src/serve/archive.js +354 -0
- package/dist/src/serve/archive.js.map +1 -0
- package/dist/src/serve/arguments.d.ts +93 -0
- package/dist/src/serve/arguments.js +228 -0
- package/dist/src/serve/arguments.js.map +1 -0
- package/dist/src/serve/credentials.d.ts +91 -0
- package/dist/src/serve/credentials.js +143 -0
- package/dist/src/serve/credentials.js.map +1 -0
- package/dist/src/serve/follow.d.ts +86 -0
- package/dist/src/serve/follow.js +101 -0
- package/dist/src/serve/follow.js.map +1 -0
- package/dist/src/serve/hook-thread.d.ts +134 -0
- package/dist/src/serve/hook-thread.js +333 -0
- package/dist/src/serve/hook-thread.js.map +1 -0
- package/dist/src/serve/hook-worker.d.ts +134 -0
- package/dist/src/serve/hook-worker.js +173 -0
- package/dist/src/serve/hook-worker.js.map +1 -0
- package/dist/src/serve/server.d.ts +412 -0
- package/dist/src/serve/server.js +1251 -0
- package/dist/src/serve/server.js.map +1 -0
- package/docs/README-extended.md +766 -0
- package/docs/cli-reference.md +2563 -73
- package/docs/codex-enforced-session.md +75 -2
- package/docs/codex-workspace-broker.md +118 -0
- package/package.json +6 -2
- package/schema/event.schema.json +595 -9
- package/schema/fixtures/event/invalid/approval-granted-sender-hashed-false.json +20 -0
- package/schema/fixtures/event/invalid/approval-granted-sender-hashed-raw-id.json +20 -0
- package/schema/fixtures/event/invalid/audit-gesture-refused-human-actor.json +16 -0
- package/schema/fixtures/event/invalid/audit-gesture-refused-no-actor-no-sender.json +15 -0
- package/schema/fixtures/event/invalid/audit-gesture-refused-unknown-gesture.json +16 -0
- package/schema/fixtures/event/invalid/audit-question-preempted-agent-actor.json +16 -0
- package/schema/fixtures/event/invalid/audit-question-preempted-no-question-id.json +16 -0
- package/schema/fixtures/event/invalid/audit-question-preempted-unknown-source.json +15 -0
- package/schema/fixtures/event/invalid/daemon-id-malformed.json +15 -0
- package/schema/fixtures/event/invalid/execution-started-truncated-policy-sha256.json +18 -0
- package/schema/fixtures/event/invalid/gate-path-signed-off-absolute-path.json +14 -0
- package/schema/fixtures/event/invalid/gate-path-signed-off-agent-actor.json +14 -0
- package/schema/fixtures/event/invalid/gate-path-signed-off-missing-path.json +13 -0
- package/schema/fixtures/event/invalid/harness-cap-at-margin.json +16 -0
- package/schema/fixtures/event/invalid/harness-cap-fractional.json +16 -0
- package/schema/fixtures/event/invalid/harness-cap-negative.json +16 -0
- package/schema/fixtures/event/invalid/harness-cap-string.json +16 -0
- package/schema/fixtures/event/invalid/harness-cap-without-harness-execution.json +16 -0
- package/schema/fixtures/event/invalid/harness-cap-zero.json +16 -0
- package/schema/fixtures/event/valid/approval-granted-sender-hashed.json +20 -0
- package/schema/fixtures/event/valid/approval-requested-harness-cap.json +18 -0
- package/schema/fixtures/event/valid/audit-gesture-refused-review-note.json +21 -0
- package/schema/fixtures/event/valid/audit-gesture-refused-sender-key-unavailable.json +19 -0
- package/schema/fixtures/event/valid/audit-gesture-refused.json +19 -0
- package/schema/fixtures/event/valid/audit-question-preempted-no-verdict.json +16 -0
- package/schema/fixtures/event/valid/audit-question-preempted.json +20 -0
- package/schema/fixtures/event/valid/daemon-identity-declared.json +17 -0
- package/schema/fixtures/event/valid/daemon-identity.json +15 -0
- package/schema/fixtures/event/valid/execution-started-policy-sha256.json +18 -0
- package/schema/fixtures/event/valid/gate-path-signed-off.json +14 -0
- package/schema/fixtures/event/valid/harness-kind-claude-code.json +23 -0
- package/schema/fixtures/event/valid/harness-kind-codex.json +23 -0
- package/schema/fixtures/event/valid/harness-kind-cursor.json +23 -0
- package/schema/fixtures/event/valid/harness-kind-grok.json +23 -0
- package/schema/fixtures/event/valid/harness-kind-hermes.json +23 -0
- package/schema/fixtures/event/valid/harness-kind-muse.json +23 -0
- package/schema/fixtures/policy/invalid/daemons-malformed-id.json +10 -0
- package/schema/fixtures/policy/invalid/senders-half-keyed.json +20 -0
- package/schema/fixtures/policy/valid/canonical.json +1 -1
- package/schema/fixtures/policy/valid/daemons-allowlist.json +19 -0
- package/schema/fixtures/policy/valid/senders-keyed.json +24 -0
- package/schema/fixtures/policy-md/invalid/schema-invalid-agent-may-request.md +21 -0
- package/schema/fixtures/policy-md/valid/agent-may-request.md +24 -0
- package/schema/fixtures/policy-md/valid/canonical.md +1 -1
- package/schema/fixtures/policy-md/valid/with-values.md +5 -7
- package/schema/fixtures/values/invalid/class-shaped.json +1 -1
- package/schema/fixtures/values/invalid/duplicate-entry.json +1 -1
- package/schema/fixtures/values/invalid/non-string-item.json +1 -1
- package/schema/fixtures/values/invalid/over-cap.json +1 -1
- package/schema/fixtures/values/invalid/unknown-key.json +1 -1
- package/schema/fixtures/values/invalid/version-float.json +1 -0
- package/schema/fixtures/values/invalid/version-integer.json +1 -0
- package/schema/fixtures/values/invalid/version-wrong-string.json +1 -0
- package/schema/fixtures/values/valid/empty-lists.json +2 -3
- package/schema/fixtures/values/valid/full.json +5 -7
- package/schema/fixtures/values/valid/minimal.json +1 -1
- package/schema/fixtures/values-md/invalid/schema-invalid.md +5 -3
- package/schema/fixtures/values-md/invalid/two-blocks.md +3 -3
- package/schema/fixtures/values-md/invalid/unterminated.md +2 -2
- package/schema/fixtures/values-md/invalid/version-1.md +69 -0
- package/schema/fixtures/values-md/invalid/version-unquoted.md +64 -0
- package/schema/fixtures/values-md/invalid/yaml-error.md +2 -2
- package/schema/fixtures/values-md/valid/absent.md +1 -1
- package/schema/fixtures/values-md/valid/with-values.md +5 -7
- package/schema/policy.schema.json +100 -4
- package/schema/values.schema.json +7 -11
- package/schema/fixtures/values/invalid/version-string.json +0 -1
package/schema/event.schema.json
CHANGED
|
@@ -52,6 +52,8 @@
|
|
|
52
52
|
"audit.reviewed",
|
|
53
53
|
"audit.dark_session",
|
|
54
54
|
"audit.decision_refused",
|
|
55
|
+
"audit.gesture_refused",
|
|
56
|
+
"audit.question_preempted",
|
|
55
57
|
"reconciliation.required",
|
|
56
58
|
"reconciliation.satisfied",
|
|
57
59
|
"payload.pruned",
|
|
@@ -59,9 +61,10 @@
|
|
|
59
61
|
"gate.closed",
|
|
60
62
|
"gate.bypassed",
|
|
61
63
|
"gate.organ.attested",
|
|
64
|
+
"gate.path.signed_off",
|
|
62
65
|
"log.checkpoint"
|
|
63
66
|
],
|
|
64
|
-
"description": "SPEC.md §8 'Event types (v0.1)': the closed set of thirty-
|
|
67
|
+
"description": "SPEC.md §8 'Event types (v0.1)': the closed set of thirty-four event types. Closed by design — an unrecognized type fails closed at the write boundary rather than entering the log unvalidated. `payload.pruned` (APRV-38) is the first addition after the v0.1 draft set of sixteen, `approval.withdrawn` (APRV-106) the second, `execution.indeterminate` with `execution.reconciled` (APRV-120) the third and fourth, `reconciliation.required` / `reconciliation.satisfied` (APRV-127) the fifth and sixth, `policy.proposed` / `policy.declined` (APRV-109) the seventh and eighth, `audit.dark_session` (APRV-192) the ninth, `gate.opened` / `gate.closed` / `gate.bypassed` (APRV-214) the tenth, eleventh and twelfth, `log.checkpoint` (APRV-220) the thirteenth, `audit.decision_refused` (APRV-235) the fourteenth, `gate.organ.attested` (APRV-272) the fifteenth, `gate.path.signed_off` (APRV-338) the sixteenth, `audit.gesture_refused` (APRV-355) the seventeenth, and `audit.question_preempted` (APRV-378) the eighteenth: a reader of a v0.1 log may encounter any of them, and a verifier written against the draft enum must be updated to accept them all."
|
|
65
68
|
},
|
|
66
69
|
"actor": {
|
|
67
70
|
"type": "string",
|
|
@@ -89,6 +92,13 @@
|
|
|
89
92
|
}
|
|
90
93
|
]
|
|
91
94
|
},
|
|
95
|
+
"daemon": {
|
|
96
|
+
"type": "string",
|
|
97
|
+
"minLength": 1,
|
|
98
|
+
"maxLength": 64,
|
|
99
|
+
"pattern": "^[a-z0-9][a-z0-9._-]*$",
|
|
100
|
+
"description": "SPEC.md §8 (amended, APRV-383): WHICH daemon instance appended this record. Present exactly on the records a daemon wrote, so a tenant whose daemon is hosted by another party can open their own log and tell which host process acted on their behalf — before this field every daemon-written record carried the same generic `system:daemon` actor and nothing else. The value is either the id declared in the daemon's launch environment (`APPROVAL_DAEMON_ID`) or `daemon-` followed by the eight hex digits of the instance id `approval doctor` prints as its `keychain-scope` suffix, so it is stable across restarts on one machine and keystore without anything being stored.\n\nOPTIONAL and additive: every record written before the field existed still validates and still verifies, and the ABSENCE of the field is absence — it is never read as a claim that no daemon wrote the record, because until this task no daemon could say.\n\nSELF-REPORTED, and bounded by SPEC.md §11.1 invariant 4 in the only way a self-reported field may be: a policy MAY list the ids permitted to write (`daemons` in `policy.schema.json`), an id the list does not name is REFUSED at the write boundary (`daemon-not-allowed`), and an id the list DOES name gains nothing at all — no verdict, autonomy, budget, floor, sampling draw, token or TTL reads this field or that list. Its SHAPE is constrained here for the reason `payload.harness_version`'s is: the value comes from a launch environment and is being written into an append-only log, so a banner, a path, a newline or a shell fragment cannot arrive through it, and two ids differing only in case would be two ids nobody can tell apart on a phone."
|
|
101
|
+
},
|
|
92
102
|
"task": {
|
|
93
103
|
"type": "string",
|
|
94
104
|
"minLength": 1,
|
|
@@ -113,9 +123,12 @@
|
|
|
113
123
|
"enum": [
|
|
114
124
|
"claude-code",
|
|
115
125
|
"cursor",
|
|
116
|
-
"codex"
|
|
126
|
+
"codex",
|
|
127
|
+
"grok",
|
|
128
|
+
"muse",
|
|
129
|
+
"hermes"
|
|
117
130
|
],
|
|
118
|
-
"description": "SPEC.md §10.1 (amended, APRV-227): which harness hook wrote this record. A closed set, extended only by a task that adds the case, because it is the discriminator a reader scopes `harness_version` by: one log holds the records of every harness that ever wrote to it, and a version with no binary named beside it is a string nobody can compare against anything. OPTIONAL and additive — every record written before the field existed still validates and still verifies, and the two fields travel together or not at all."
|
|
131
|
+
"description": "SPEC.md §10.1 (amended, APRV-227): which harness hook wrote this record. A closed set, extended only by a task that adds the case, because it is the discriminator a reader scopes `harness_version` by: one log holds the records of every harness that ever wrote to it, and a version with no binary named beside it is a string nobody can compare against anything. OPTIONAL and additive — every record written before the field existed still validates and still verifies, and the two fields travel together or not at all. APRV-358 added `grok`, which APRV-243 shipped an adapter for and never added here: a Grok session's manual-class registration failed validation at the write boundary, so the request never reached a human. Closed does not mean hand-maintained any more. This list is pinned set-equal to `HARNESS_KINDS` in `src/core/harness-version.ts` by `tests/harness-enum.test.ts`, along with every other place a harness name is enumerated, so the next adapter cannot land with the enum left behind. APRV-398 added `hermes`, the Hermes Agent adapter, through that pinning rather than after it."
|
|
119
132
|
},
|
|
120
133
|
"harness_version": {
|
|
121
134
|
"type": "string",
|
|
@@ -184,6 +197,34 @@
|
|
|
184
197
|
}
|
|
185
198
|
},
|
|
186
199
|
"$defs": {
|
|
200
|
+
"senderHashed": {
|
|
201
|
+
"const": true,
|
|
202
|
+
"description": "SPEC.md §5.2/§10.3 (amended, APRV-370): the sibling `id` is the operator's KEYED digest of the account, `hmac-sha256:<64 lowercase hex>` — HMAC-SHA-256 of the transport's own account id under a secret named by `APPROVAL_SENDER_KEY` — rather than the account id itself. It appears exactly when the attested `approvers[id].senders` mapping for that channel is written in the keyed form, which is what a deployment that PUBLISHES its policy and its log uses so that neither discloses the account.\n\n`true` or ABSENT, never `false`, and that asymmetry is deliberate: absent is the raw form, which is the record every build since APRV-324 has written, and a `false` here would make all of those read afterwards as though they were missing something. The digest is not a credential either — it is published in the policy — and it is not an authenticator: nothing in the gate's safety rests on the key's secrecy, and a process that loses the key loses the ability to RESOLVE accounts, never the ability to refuse.",
|
|
203
|
+
"examples": [true]
|
|
204
|
+
},
|
|
205
|
+
"senderHashedImpliesDigest": {
|
|
206
|
+
"$comment": "APRV-370. `hashed` and `id` describe the same fact in two shapes, and two shapes of one fact can disagree. They cannot here: whenever the flag is present the id must be the digest form, so a record claiming to be hashed while carrying a bare account id does not validate, and a reader may branch on the boolean without parsing the string. The converse is deliberately NOT required — an id that merely looks like a digest is not asserted to be one — because the assertion is the flag's to make.",
|
|
207
|
+
"if": {
|
|
208
|
+
"type": "object",
|
|
209
|
+
"properties": {
|
|
210
|
+
"hashed": {
|
|
211
|
+
"const": true
|
|
212
|
+
}
|
|
213
|
+
},
|
|
214
|
+
"required": [
|
|
215
|
+
"hashed"
|
|
216
|
+
]
|
|
217
|
+
},
|
|
218
|
+
"then": {
|
|
219
|
+
"type": "object",
|
|
220
|
+
"properties": {
|
|
221
|
+
"id": {
|
|
222
|
+
"type": "string",
|
|
223
|
+
"pattern": "^hmac-sha256:[0-9a-f]{64}$"
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
},
|
|
187
228
|
"usd_amount_string": {
|
|
188
229
|
"type": "string",
|
|
189
230
|
"pattern": "^(0|[1-9][0-9]*)(\\.[0-9]{0,5}[1-9])?$",
|
|
@@ -719,6 +760,11 @@
|
|
|
719
760
|
"type": "integer",
|
|
720
761
|
"minimum": 1,
|
|
721
762
|
"description": "The `policy.proposed` record this attestation answers."
|
|
763
|
+
},
|
|
764
|
+
"payload_hash": {
|
|
765
|
+
"type": "string",
|
|
766
|
+
"pattern": "^[a-f0-9]{64}$",
|
|
767
|
+
"description": "SPEC.md §5.2/§10.4 (APRV-356): the binding for the ATTESTED policy text, stored beside the log, so the policy in force is recoverable and not only nameable. A digest alone cannot produce the file it names, and the privileged-gesture rule of §10.3 has to be decided against the policy in force rather than the one being proposed. Optional and additive: records written before this existed still validate and verify, and a reader treats absence as the pre-amendment state, where the in-force bytes are unrecoverable and the fail-closed fallback applies. A reader that recovers the text MUST re-hash it against `sha256` from the verified log, so the store is checked rather than trusted."
|
|
722
768
|
}
|
|
723
769
|
}
|
|
724
770
|
}
|
|
@@ -795,6 +841,36 @@
|
|
|
795
841
|
}
|
|
796
842
|
}
|
|
797
843
|
},
|
|
844
|
+
{
|
|
845
|
+
"$comment": "SPEC.md §8 (amended, APRV-447, pending sign-off): a harness `execution.started` carries `policy_sha256`, the SHA-256 of the attested policy the write boundary re-checked the action's class against when it recorded the start (on the hook route the verdict was drawn from the hook's own earlier read, so a re-attest landing between the two leaves the stamp naming the policy in force at the append). Before it, a policy-authorized start (autonomous, supervised, an unselected supervised-live draw) named no policy at all, and a reader had to reconstruct the rules in force from the latest `policy.updated` before the record's seq; a granted start could reach its policy through `grant_seq` and carries the stamp too, written by the same code. The runtime assigns the value at the write boundary from its own attestation check, exactly as it assigns `policy_sha256` on `approval.requested` and `ts` on every gate-typed event: no caller parameter carries it, and a caller-supplied value of the same name is ignored. No verdict, budget, draw or refusal reads it back, so §11.1 invariant 4 is untouched. It is covered by the record hash like every other payload field. OPTIONAL and additive: every record written before the field existed still validates and still verifies, an `execution.started` written by any other path records none, and only the SHAPE is constrained here, a 64-character lowercase hex digest. Whether the hash names an attestation the log actually carries is not expressible in a schema that sees one record. The field name is a stable contract: downstream followers of the log use it to join a start to the rules it ran under.",
|
|
846
|
+
"if": {
|
|
847
|
+
"type": "object",
|
|
848
|
+
"properties": {
|
|
849
|
+
"event": {
|
|
850
|
+
"const": "execution.started"
|
|
851
|
+
}
|
|
852
|
+
},
|
|
853
|
+
"required": [
|
|
854
|
+
"event"
|
|
855
|
+
]
|
|
856
|
+
},
|
|
857
|
+
"then": {
|
|
858
|
+
"type": "object",
|
|
859
|
+
"properties": {
|
|
860
|
+
"payload": {
|
|
861
|
+
"type": "object",
|
|
862
|
+
"properties": {
|
|
863
|
+
"policy_sha256": {
|
|
864
|
+
"type": "string",
|
|
865
|
+
"pattern": "^[a-f0-9]{64}$",
|
|
866
|
+
"description": "SPEC.md §8 (amended, APRV-447): SHA-256 (lowercase hex) of the attested policy file's exact bytes the write boundary re-checked this start's class against, stamped by the runtime at the write boundary. Never a caller's parameter and never an input to a verdict.",
|
|
867
|
+
"examples": ["5f1c0de9a3b7e2d4c6f8091a2b3c4d5e6f708192a3b4c5d6e7f8091a2b3c4d5e"]
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
},
|
|
798
874
|
{
|
|
799
875
|
"$comment": "SPEC.md §10.3 (amended, APRV-38): a decision collected as part of a batch gesture carries the batch's channel delivery id as a first-class payload field. Optional — a unit decision has no batch — and constrained only when present, so the field can never arrive as an empty string or a number that audit would have to guess at.",
|
|
800
876
|
"if": {
|
|
@@ -828,6 +904,140 @@
|
|
|
828
904
|
}
|
|
829
905
|
}
|
|
830
906
|
},
|
|
907
|
+
{
|
|
908
|
+
"$comment": "SPEC.md §6.3/§10.3 (amended, APRV-324): who decided, as evidence rather than as configuration. `sender` is the account the channel's own transport attributed the gesture to — for Telegram the numeric `callback_query.from.id` — and the record's `actor` is the human the attested `approvers[id].senders` mapping binds that account to. Both optional and both additive: a decision made on a surface that authenticates no sender, or under a policy that maps none, carries neither key and is byte-identical to a decision written before they existed. That ABSENCE is meaningful and is why the fields are not defaulted: it says the attribution came from the identity the deciding process was launched with, which is the whole of what every build before APRV-324 could say. What may never appear here is anything the sender says about themselves: the mapping key is the transport's attribution, never a name, handle or id a message body claims (§11.1 invariant 4), and `from.username` is deliberately absent from the vocabulary because it is mutable and reusable and a log carrying it would grow a field that reads like identity and decays into a lie. `sender_source` names the kind of evidence, as a closed vocabulary, so a later source is distinguishable in a log rather than silently mixed in with policy-attested ones.",
|
|
909
|
+
"if": {
|
|
910
|
+
"type": "object",
|
|
911
|
+
"properties": {
|
|
912
|
+
"event": {
|
|
913
|
+
"enum": [
|
|
914
|
+
"approval.granted",
|
|
915
|
+
"approval.rejected",
|
|
916
|
+
"approval.revoked"
|
|
917
|
+
]
|
|
918
|
+
}
|
|
919
|
+
},
|
|
920
|
+
"required": [
|
|
921
|
+
"event"
|
|
922
|
+
]
|
|
923
|
+
},
|
|
924
|
+
"then": {
|
|
925
|
+
"type": "object",
|
|
926
|
+
"properties": {
|
|
927
|
+
"payload": {
|
|
928
|
+
"type": "object",
|
|
929
|
+
"properties": {
|
|
930
|
+
"sender": {
|
|
931
|
+
"type": "object",
|
|
932
|
+
"additionalProperties": false,
|
|
933
|
+
"required": [
|
|
934
|
+
"channel",
|
|
935
|
+
"id"
|
|
936
|
+
],
|
|
937
|
+
"description": "The authenticated sender the `actor` was resolved from. Present only where a transport authenticated one and the attested policy mapped it.",
|
|
938
|
+
"properties": {
|
|
939
|
+
"channel": {
|
|
940
|
+
"type": "string",
|
|
941
|
+
"minLength": 1,
|
|
942
|
+
"description": "The surface that observed the sender: `telegram`. Named separately from the record's own `channel` because a sender id is only meaningful inside the namespace that issued it."
|
|
943
|
+
},
|
|
944
|
+
"id": {
|
|
945
|
+
"type": "string",
|
|
946
|
+
"minLength": 1,
|
|
947
|
+
"description": "The transport's own account id, as a string, OR the operator's keyed digest of it when `hashed` is true. Raw, it is not a credential — a Telegram user id is visible to everyone in a chat — and it is recorded so an operator investigating a decision has the number rather than a name they cannot check."
|
|
948
|
+
},
|
|
949
|
+
"hashed": {
|
|
950
|
+
"$ref": "#/$defs/senderHashed"
|
|
951
|
+
}
|
|
952
|
+
},
|
|
953
|
+
"allOf": [
|
|
954
|
+
{
|
|
955
|
+
"$ref": "#/$defs/senderHashedImpliesDigest"
|
|
956
|
+
}
|
|
957
|
+
],
|
|
958
|
+
"examples": [
|
|
959
|
+
{ "channel": "telegram", "id": "12345678" },
|
|
960
|
+
{
|
|
961
|
+
"channel": "telegram",
|
|
962
|
+
"id": "hmac-sha256:3f2a9c11b4d7e6085a1c2f9d8e7b6a5c4d3e2f1a0b9c8d7e6f5a4b3c2d1e0f9a",
|
|
963
|
+
"hashed": true
|
|
964
|
+
}
|
|
965
|
+
]
|
|
966
|
+
},
|
|
967
|
+
"sender_source": {
|
|
968
|
+
"type": "string",
|
|
969
|
+
"enum": [
|
|
970
|
+
"policy"
|
|
971
|
+
],
|
|
972
|
+
"description": "How the sender became the actor. `policy` is the attested `approvers[id].senders` mapping of SPEC.md §5.2, and it is the only member today. A closed vocabulary on purpose: a future source of the same shape must be distinguishable from an operator's attestation rather than read back as one."
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
},
|
|
979
|
+
{
|
|
980
|
+
"$comment": "SPEC.md §10.3 (amended, APRV-324): the same two fields on the other human acts a channel collects. An attestation decides which rules are in force and a retrospective review is handed to agents as human-authored guidance, so both are recorded as a person's and both are worth attributing to an account rather than to whichever process was listening. Optional and additive on exactly the terms the decision events carry them: absent means the attribution came from the deciding process's configuration. `log.checkpoint` is deliberately NOT in this list — its payload is a signed structure, and an unsigned field sitting beside a signature invites a reader to treat it as covered by one; a checkpoint tap resolves its ACTOR from the sender exactly as these do, and the account it came from is not written next to the signature.",
|
|
981
|
+
"if": {
|
|
982
|
+
"type": "object",
|
|
983
|
+
"properties": {
|
|
984
|
+
"event": {
|
|
985
|
+
"enum": [
|
|
986
|
+
"policy.updated",
|
|
987
|
+
"policy.declined",
|
|
988
|
+
"audit.reviewed"
|
|
989
|
+
]
|
|
990
|
+
}
|
|
991
|
+
},
|
|
992
|
+
"required": [
|
|
993
|
+
"event"
|
|
994
|
+
]
|
|
995
|
+
},
|
|
996
|
+
"then": {
|
|
997
|
+
"type": "object",
|
|
998
|
+
"properties": {
|
|
999
|
+
"payload": {
|
|
1000
|
+
"type": "object",
|
|
1001
|
+
"properties": {
|
|
1002
|
+
"sender": {
|
|
1003
|
+
"type": "object",
|
|
1004
|
+
"additionalProperties": false,
|
|
1005
|
+
"required": [
|
|
1006
|
+
"channel",
|
|
1007
|
+
"id"
|
|
1008
|
+
],
|
|
1009
|
+
"description": "The authenticated sender the `actor` was resolved from, on the same terms as a decision's.",
|
|
1010
|
+
"properties": {
|
|
1011
|
+
"channel": {
|
|
1012
|
+
"type": "string",
|
|
1013
|
+
"minLength": 1
|
|
1014
|
+
},
|
|
1015
|
+
"id": {
|
|
1016
|
+
"type": "string",
|
|
1017
|
+
"minLength": 1
|
|
1018
|
+
},
|
|
1019
|
+
"hashed": {
|
|
1020
|
+
"$ref": "#/$defs/senderHashed"
|
|
1021
|
+
}
|
|
1022
|
+
},
|
|
1023
|
+
"allOf": [
|
|
1024
|
+
{
|
|
1025
|
+
"$ref": "#/$defs/senderHashedImpliesDigest"
|
|
1026
|
+
}
|
|
1027
|
+
]
|
|
1028
|
+
},
|
|
1029
|
+
"sender_source": {
|
|
1030
|
+
"type": "string",
|
|
1031
|
+
"enum": [
|
|
1032
|
+
"policy"
|
|
1033
|
+
],
|
|
1034
|
+
"description": "How the sender became the actor. `policy` is the attested `approvers[id].senders` mapping of SPEC.md §5.2 — and for an attestation specifically, the mapping of the policy IN FORCE, never the one being attested."
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
},
|
|
831
1041
|
{
|
|
832
1042
|
"$comment": "SPEC.md §5.2/§8 (amended, APRV-118): a request and the grant that answers it each pin the SHA-256 of the attested policy the runtime evaluated them under, so a reader can see whether the approver decided by the rules the requester was routed by. The runtime assigns the value at the write boundary from its own attestation check, exactly as it assigns `ts` to a gate-typed event; a grant whose hash differs from its request's is refused `policy-drift` and never reaches this schema. Optional, and deliberately: the field is additive, so records written before it existed still validate and still verify, and only its SHAPE is constrained here — a 64-character lowercase hex digest, never an empty string or a truncated one that audit would have to guess at. Whether the hash names an attestation the log actually carries is not expressible in a schema that sees one record, and is the gate's check.",
|
|
833
1043
|
"if": {
|
|
@@ -954,6 +1164,25 @@
|
|
|
954
1164
|
"type": "string",
|
|
955
1165
|
"pattern": "^[a-f0-9]{64}$",
|
|
956
1166
|
"description": "SPEC.md §9/§10.3 (APRV-119): SHA-256 (lowercase hex) over the UTF-8 text of the canonical rendering of this request's payload, as produced by the versioned deterministic renderer named inside that text."
|
|
1167
|
+
},
|
|
1168
|
+
"harness_cap_ms": {
|
|
1169
|
+
"type": "integer",
|
|
1170
|
+
"minimum": 60001,
|
|
1171
|
+
"maximum": 86400000,
|
|
1172
|
+
"description": "SPEC.md §6 (APRV-423): the ceiling, in milliseconds, that the harness running the requesting hook imposes on that hook's own process — Hermes caps a `pre_tool_call` entry at 300 s, Claude Code kills the hook at the `timeout` in its settings file. A BOUNDED INPUT and never a deadline of its own: the runtime judges the request against the SHORTER of this minus a fixed margin and the policy's `defaults.approval_ttl`, so a stated cap can only move the lapse EARLIER (§11.1 invariant 4). The minimum is HARNESS_CAP_MARGIN_MS + 1 (src/core/harness-wait.ts, 60 000 ms; tests/harness-cap-ttl.test.ts pins the two equal): a cap at or below the margin leaves a zero-length window, a question that lapses as it is asked, and the hook refuses such a configuration before it appends, so a record carrying one could only come from a caller that skipped the hook and is refused here. Above 24 h is a number no harness contract states."
|
|
1173
|
+
}
|
|
1174
|
+
},
|
|
1175
|
+
"dependentSchemas": {
|
|
1176
|
+
"harness_cap_ms": {
|
|
1177
|
+
"$comment": "APRV-423. A record carrying a harness ceiling MUST also declare `execution: \"harness\"`. The ceiling shortens the window in which this request may be decided, and a request that mints an execution token would otherwise hold an authorization with a longer shelf life than the question that produced it. A schema sees one record, which is exactly enough to see both fields, so the pairing is refused at the write boundary rather than left to a caller's discipline.",
|
|
1178
|
+
"properties": {
|
|
1179
|
+
"execution": {
|
|
1180
|
+
"const": "harness"
|
|
1181
|
+
}
|
|
1182
|
+
},
|
|
1183
|
+
"required": [
|
|
1184
|
+
"execution"
|
|
1185
|
+
]
|
|
957
1186
|
}
|
|
958
1187
|
}
|
|
959
1188
|
}
|
|
@@ -1245,9 +1474,10 @@
|
|
|
1245
1474
|
"reason": {
|
|
1246
1475
|
"type": "string",
|
|
1247
1476
|
"enum": [
|
|
1248
|
-
"act-threw"
|
|
1477
|
+
"act-threw",
|
|
1478
|
+
"workspace-commit-unknown"
|
|
1249
1479
|
],
|
|
1250
|
-
"description": "Where the unknowing began. `act-threw`: the adapter's `act` was entered and raised, so the provider call may or may not have committed. Closed, and extended only by a task that adds the case."
|
|
1480
|
+
"description": "Where the unknowing began. `act-threw`: the adapter's `act` was entered and raised, so the provider call may or may not have committed. `workspace-commit-unknown` (APRV-325.2): a multi-file workspace transaction was applied and the workspace then read back as neither the approved before-state nor the approved after-state, because POSIX offers no atomic multi-file rename and a crash, a permission error or a failed rollback can land between two of them; the transaction journal naming both states is retained for a person. Closed, and extended only by a task that adds the case."
|
|
1251
1481
|
},
|
|
1252
1482
|
"exit_code": {
|
|
1253
1483
|
"type": "null",
|
|
@@ -1390,9 +1620,10 @@
|
|
|
1390
1620
|
"type": "string",
|
|
1391
1621
|
"enum": [
|
|
1392
1622
|
"no-records",
|
|
1393
|
-
"no-evidence"
|
|
1623
|
+
"no-evidence",
|
|
1624
|
+
"no-evidence-merged"
|
|
1394
1625
|
],
|
|
1395
|
-
"description": "Which arm found it: `no-records` is git activity with no attributable hook record at all, `no-evidence` is a guarded path changed with no evidence a human decided it (the spelling APRV-151's CI guard already uses)."
|
|
1626
|
+
"description": "Which arm found it: `no-records` is git activity with no attributable hook record at all, `no-evidence` is a guarded path changed with no evidence a human decided it (the spelling APRV-151's CI guard already uses), and `no-evidence-merged` (APRV-369) is that same finding where every failing commit reached the checkout through a merge rather than being authored on it — the same `dark` verdict, reported under its own code because the subject named it the checkout that SYNCED the change and the repair is in the branch the commit came from. A closed set, and `tests/dark-session.test.ts` pins it equal to the codes a `dark` verdict can carry, so a code the sweep can produce cannot be missing here: a record refused at the write boundary is an observation that never reaches a human."
|
|
1396
1627
|
},
|
|
1397
1628
|
"observation_key": {
|
|
1398
1629
|
"type": "string",
|
|
@@ -1464,7 +1695,6 @@
|
|
|
1464
1695
|
"payload": {
|
|
1465
1696
|
"type": "object",
|
|
1466
1697
|
"required": [
|
|
1467
|
-
"actor",
|
|
1468
1698
|
"decision",
|
|
1469
1699
|
"code"
|
|
1470
1700
|
],
|
|
@@ -1472,7 +1702,45 @@
|
|
|
1472
1702
|
"actor": {
|
|
1473
1703
|
"type": "string",
|
|
1474
1704
|
"pattern": "^human:",
|
|
1475
|
-
"description": "The person whose decision was refused, `human:<id>`. Distinct from the record's own `actor`, which is the runtime: this field is the SUBJECT of the observation, and the record's actor is its author. `^human:` because this event exists only for a human decision; a refusal handed to an agent is not recorded at all."
|
|
1705
|
+
"description": "The person whose decision was refused, `human:<id>`. Distinct from the record's own `actor`, which is the runtime: this field is the SUBJECT of the observation, and the record's actor is its author. `^human:` because this event exists only for a human decision; a refusal handed to an agent is not recorded at all. Required unless the refusal is precisely that the runtime could not say who the person was — see the `sender` field and the rule below it.",
|
|
1706
|
+
"$comment": "APRV-324 moved this out of the payload's `required` list and into the conditional rule below, which requires it on every record that does not carry a `sender`."
|
|
1707
|
+
},
|
|
1708
|
+
"sender": {
|
|
1709
|
+
"type": "object",
|
|
1710
|
+
"additionalProperties": false,
|
|
1711
|
+
"required": [
|
|
1712
|
+
"channel",
|
|
1713
|
+
"id"
|
|
1714
|
+
],
|
|
1715
|
+
"description": "SPEC.md §10.3 (amended, APRV-324): the authenticated sender whose decision was refused. Carried on a `sender-unmapped`, `sender-ambiguous` or `sender-key-unavailable` refusal, where it is the only thing the runtime knows about who tapped and the reason the record exists — an operator investigating who tried needs the account — and on any other refusal of a decision whose actor WAS resolved from a sender, where it says which account spent the attention.",
|
|
1716
|
+
"properties": {
|
|
1717
|
+
"channel": {
|
|
1718
|
+
"type": "string",
|
|
1719
|
+
"minLength": 1,
|
|
1720
|
+
"description": "The surface that observed the sender: `telegram`."
|
|
1721
|
+
},
|
|
1722
|
+
"id": {
|
|
1723
|
+
"type": "string",
|
|
1724
|
+
"minLength": 1,
|
|
1725
|
+
"description": "The transport's own account id, as a string, OR the operator's keyed digest of it when `hashed` is true. Raw, a Telegram user id is not a credential: it is visible to everyone in the chat the tap arrived from. Under a keyed mapping the digest is what the operator can act on, because it is exactly the string their `senders` block would carry."
|
|
1726
|
+
},
|
|
1727
|
+
"hashed": {
|
|
1728
|
+
"$ref": "#/$defs/senderHashed"
|
|
1729
|
+
}
|
|
1730
|
+
},
|
|
1731
|
+
"allOf": [
|
|
1732
|
+
{
|
|
1733
|
+
"$ref": "#/$defs/senderHashedImpliesDigest"
|
|
1734
|
+
}
|
|
1735
|
+
],
|
|
1736
|
+
"examples": [{ "channel": "telegram", "id": "12345678" }]
|
|
1737
|
+
},
|
|
1738
|
+
"sender_source": {
|
|
1739
|
+
"type": "string",
|
|
1740
|
+
"enum": [
|
|
1741
|
+
"policy"
|
|
1742
|
+
],
|
|
1743
|
+
"description": "How the sender became the actor, on a refusal whose actor was resolved. Absent where nothing resolved, which is what a `sender-unmapped` refusal is."
|
|
1476
1744
|
},
|
|
1477
1745
|
"decision": {
|
|
1478
1746
|
"type": "string",
|
|
@@ -1507,6 +1775,276 @@
|
|
|
1507
1775
|
}
|
|
1508
1776
|
}
|
|
1509
1777
|
},
|
|
1778
|
+
{
|
|
1779
|
+
"$comment": "SPEC.md §10.3/§11.1 (amended, APRV-324): a refused decision names the person it was refused for, UNLESS naming one is exactly what the runtime could not do. Before the sender mapping, every decision surface held one configured human identity and the answer was always available; `sender-unmapped` is the case where a transport authenticated an account the attested policy binds to nobody, and the honest record of it carries the account and no person. Writing the listener's own identity there would have been the false statement the refusal exists to prevent — that the operator's decision was refused, when the operator did not decide. So the requirement is conditional rather than dropped: a record with no `sender` MUST still name the human, which is every refusal the runtime wrote before this rule existed and every refusal on a surface that authenticates no sender.",
|
|
1780
|
+
"if": {
|
|
1781
|
+
"type": "object",
|
|
1782
|
+
"properties": {
|
|
1783
|
+
"event": {
|
|
1784
|
+
"const": "audit.decision_refused"
|
|
1785
|
+
},
|
|
1786
|
+
"payload": {
|
|
1787
|
+
"type": "object",
|
|
1788
|
+
"not": {
|
|
1789
|
+
"properties": {
|
|
1790
|
+
"sender": {}
|
|
1791
|
+
},
|
|
1792
|
+
"required": [
|
|
1793
|
+
"sender"
|
|
1794
|
+
]
|
|
1795
|
+
}
|
|
1796
|
+
}
|
|
1797
|
+
},
|
|
1798
|
+
"required": [
|
|
1799
|
+
"event",
|
|
1800
|
+
"payload"
|
|
1801
|
+
]
|
|
1802
|
+
},
|
|
1803
|
+
"then": {
|
|
1804
|
+
"type": "object",
|
|
1805
|
+
"properties": {
|
|
1806
|
+
"payload": {
|
|
1807
|
+
"type": "object",
|
|
1808
|
+
"properties": {
|
|
1809
|
+
"actor": {
|
|
1810
|
+
"type": "string",
|
|
1811
|
+
"pattern": "^human:"
|
|
1812
|
+
}
|
|
1813
|
+
},
|
|
1814
|
+
"required": [
|
|
1815
|
+
"actor"
|
|
1816
|
+
]
|
|
1817
|
+
}
|
|
1818
|
+
}
|
|
1819
|
+
}
|
|
1820
|
+
},
|
|
1821
|
+
{
|
|
1822
|
+
"$comment": "SPEC.md §8 (amended, APRV-355): a human made a gesture on a decision surface that is NOT a decision — a checkpoint signature, a review — and the surface refused it before any verb ran, so nothing else was appended. Found while landing APRV-324 (PR #427): `audit.decision_refused` requires an `action_key` and a `payload.decision` of grant, reject or revoke, and a signature or a review has neither, so recording one of these there would have meant manufacturing both. The cost of recording nothing is that attention spent, and an attempt made by an account the operator did not map, leave no trace in the log at all — only a terminal line and a chat answer.\n\nAUDIT TIER, in the strict sense `audit.decision_refused` set: it grants nothing, mints no token, settles no request, charges no budget, is not sampled, and NO ENFORCEMENT PATH READS IT. It is the runtime's statement about a refusal the runtime made.\n\nThe actor MUST be `^system:`, for the reason `audit.dark_session`'s and `audit.decision_refused`'s are: a record of a refusal authored by either party to it is a record neither party can be held to. The human, when the runtime could name one, is named in the payload; on the refusal where it could not — a transport authenticated an account the attested policy binds to nobody — the payload carries the observed `sender` and no `actor`, on the same conditional terms APRV-324 gave the decision record.\n\nThere is no `action_key` and no `decision`, deliberately. A signature answers for a chain head and a review answers for a sampled record; neither is an action the gate holds a key for, and a field invented to satisfy a schema would be a false statement in a record whose whole purpose is to be true about an attempt.",
|
|
1823
|
+
"if": {
|
|
1824
|
+
"type": "object",
|
|
1825
|
+
"properties": {
|
|
1826
|
+
"event": {
|
|
1827
|
+
"const": "audit.gesture_refused"
|
|
1828
|
+
}
|
|
1829
|
+
},
|
|
1830
|
+
"required": [
|
|
1831
|
+
"event"
|
|
1832
|
+
]
|
|
1833
|
+
},
|
|
1834
|
+
"then": {
|
|
1835
|
+
"type": "object",
|
|
1836
|
+
"required": [
|
|
1837
|
+
"actor",
|
|
1838
|
+
"channel",
|
|
1839
|
+
"payload"
|
|
1840
|
+
],
|
|
1841
|
+
"properties": {
|
|
1842
|
+
"actor": {
|
|
1843
|
+
"type": "string",
|
|
1844
|
+
"pattern": "^system:",
|
|
1845
|
+
"description": "The runtime. A `human:` or `agent:` actor is refused: the record states that a surface refused a gesture, and the parties to that refusal do not author it."
|
|
1846
|
+
},
|
|
1847
|
+
"channel": {
|
|
1848
|
+
"$ref": "#/properties/channel",
|
|
1849
|
+
"description": "Which surface collected the gesture — `telegram`, `web`, `cli`. Required here for the reason it is required on `audit.decision_refused`: a refusal a reader cannot attribute to a surface is one they cannot go and reproduce, and every decision surface knows its own name."
|
|
1850
|
+
},
|
|
1851
|
+
"payload": {
|
|
1852
|
+
"type": "object",
|
|
1853
|
+
"required": [
|
|
1854
|
+
"gesture",
|
|
1855
|
+
"code"
|
|
1856
|
+
],
|
|
1857
|
+
"properties": {
|
|
1858
|
+
"gesture": {
|
|
1859
|
+
"enum": [
|
|
1860
|
+
"checkpoint-signature",
|
|
1861
|
+
"review",
|
|
1862
|
+
"review-note"
|
|
1863
|
+
],
|
|
1864
|
+
"description": "What was attempted. `checkpoint-signature` is a signature over a chain head (§9, APRV-257); `review` is a retrospective verdict on a sampled record (§10.1, APRV-299); `review-note` is that same gesture carrying a human's own words, distinguished because a note is attention spent writing rather than attention spent tapping, and an operator reading this record to decide whether to map an account wants to know which. CLOSED, and additive-only like every other vocabulary here (§11.1 invariant 6): a kind the write boundary refuses is an observation that never reaches a human."
|
|
1865
|
+
},
|
|
1866
|
+
"code": {
|
|
1867
|
+
"enum": [
|
|
1868
|
+
"sender-unmapped",
|
|
1869
|
+
"sender-ambiguous",
|
|
1870
|
+
"sender-key-unavailable",
|
|
1871
|
+
"policy-not-attested"
|
|
1872
|
+
],
|
|
1873
|
+
"description": "Why the surface refused, verbatim from the resolution that refused. CLOSED, and exactly the refusals sender resolution can reach before a verb runs: the first three are `CHANNEL_DECISION_REFUSAL_CODES` (`core/sender-identity.ts`), and the last is `ATTESTATION_REFUSAL` (`core/attest.ts`), which fires when the policy declaring the mapping is not the attested one and is therefore not in force. `sender-key-unavailable` (APRV-370) is the policy mapping this channel's senders in the keyed form while the process holds no key: the runtime could resolve NO account, which is a statement about the listener rather than about the person who tapped. `attest-requires-terminal` is not here: it belongs to an attestation tap, which is a decision."
|
|
1874
|
+
},
|
|
1875
|
+
"actor": {
|
|
1876
|
+
"type": "string",
|
|
1877
|
+
"pattern": "^human:",
|
|
1878
|
+
"description": "The person whose gesture was refused, `human:<id>`, when the runtime could name one. Distinct from the record's own `actor`, which is the runtime: this field is the SUBJECT of the observation and the record's actor is its author. Required unless the refusal is precisely that the runtime could not say who the person was — see `sender` and the rule below it."
|
|
1879
|
+
},
|
|
1880
|
+
"sender": {
|
|
1881
|
+
"type": "object",
|
|
1882
|
+
"additionalProperties": false,
|
|
1883
|
+
"required": [
|
|
1884
|
+
"channel",
|
|
1885
|
+
"id"
|
|
1886
|
+
],
|
|
1887
|
+
"description": "SPEC.md §10.3: the authenticated sender the gesture arrived from. The transport's own attribution and nothing the message claimed about itself (§11.1 invariant 4). On `sender-unmapped` it is the whole of what is known about who tried, and the reason the record exists: an operator deciding whether to map an account needs the account.",
|
|
1888
|
+
"properties": {
|
|
1889
|
+
"channel": {
|
|
1890
|
+
"type": "string",
|
|
1891
|
+
"minLength": 1,
|
|
1892
|
+
"description": "The surface that observed it. `telegram` at v0.1."
|
|
1893
|
+
},
|
|
1894
|
+
"id": {
|
|
1895
|
+
"type": "string",
|
|
1896
|
+
"minLength": 1,
|
|
1897
|
+
"description": "The transport's account id, as a string, OR the operator's keyed digest of it when `hashed` is true."
|
|
1898
|
+
},
|
|
1899
|
+
"hashed": {
|
|
1900
|
+
"$ref": "#/$defs/senderHashed"
|
|
1901
|
+
}
|
|
1902
|
+
},
|
|
1903
|
+
"allOf": [
|
|
1904
|
+
{
|
|
1905
|
+
"$ref": "#/$defs/senderHashedImpliesDigest"
|
|
1906
|
+
}
|
|
1907
|
+
]
|
|
1908
|
+
},
|
|
1909
|
+
"sender_source": {
|
|
1910
|
+
"enum": [
|
|
1911
|
+
"policy"
|
|
1912
|
+
],
|
|
1913
|
+
"description": "How the sender became the actor, when one did. `policy` at v0.1: the attested policy's `senders` mapping."
|
|
1914
|
+
},
|
|
1915
|
+
"message": {
|
|
1916
|
+
"type": "string",
|
|
1917
|
+
"description": "The refusal message, as the person was shown it. A convenience for a reader; `code` is what anything machine-readable branches on."
|
|
1918
|
+
}
|
|
1919
|
+
}
|
|
1920
|
+
}
|
|
1921
|
+
}
|
|
1922
|
+
}
|
|
1923
|
+
},
|
|
1924
|
+
{
|
|
1925
|
+
"$comment": "SPEC.md §10.3/§11.1 (APRV-355), the same conditional APRV-324 gave `audit.decision_refused`: a refused gesture names the person it was refused for, UNLESS naming one is exactly what the runtime could not do. A record with no `sender` MUST still name the human, which is every refusal on a surface that authenticates no sender.",
|
|
1926
|
+
"if": {
|
|
1927
|
+
"type": "object",
|
|
1928
|
+
"properties": {
|
|
1929
|
+
"event": {
|
|
1930
|
+
"const": "audit.gesture_refused"
|
|
1931
|
+
},
|
|
1932
|
+
"payload": {
|
|
1933
|
+
"type": "object",
|
|
1934
|
+
"not": {
|
|
1935
|
+
"properties": {
|
|
1936
|
+
"sender": {}
|
|
1937
|
+
},
|
|
1938
|
+
"required": [
|
|
1939
|
+
"sender"
|
|
1940
|
+
]
|
|
1941
|
+
}
|
|
1942
|
+
}
|
|
1943
|
+
},
|
|
1944
|
+
"required": [
|
|
1945
|
+
"event",
|
|
1946
|
+
"payload"
|
|
1947
|
+
]
|
|
1948
|
+
},
|
|
1949
|
+
"then": {
|
|
1950
|
+
"type": "object",
|
|
1951
|
+
"properties": {
|
|
1952
|
+
"payload": {
|
|
1953
|
+
"type": "object",
|
|
1954
|
+
"properties": {
|
|
1955
|
+
"actor": {
|
|
1956
|
+
"type": "string",
|
|
1957
|
+
"pattern": "^human:"
|
|
1958
|
+
}
|
|
1959
|
+
},
|
|
1960
|
+
"required": [
|
|
1961
|
+
"actor"
|
|
1962
|
+
]
|
|
1963
|
+
}
|
|
1964
|
+
}
|
|
1965
|
+
}
|
|
1966
|
+
},
|
|
1967
|
+
{
|
|
1968
|
+
"$comment": "SPEC.md §8 (amended, APRV-378): something other than this gate answered a question this gate exists to ask. The first instance is Codex's server-side auto-reviewer (`docs/codex-app-server-bridge.md`, question 3): a model call can resolve an approval before `approval codex bridge` is asked, and the client is told afterwards through an `item/autoApprovalReview` notification. No existing type carries the fact. `audit.dark_session` is the sweep over activity with no records beside it; `audit.decision_refused` is a human gesture the gate would not take; `audit.gesture_refused` (APRV-355) is a gesture that is not a decision at all. This one is none of those: the question existed, the gate would have asked it, and somebody else answered first.\n\nThe NAME is general because the instance will not be the last. `source` is a closed enum whose first member names the Codex auto-reviewer, and a second party gains a member rather than a type.\n\nAUDIT TIER, on the strict terms `audit.decision_refused` and `audit.gesture_refused` set: it grants nothing, mints no token, settles no request, charges no budget, is not sampled, and NO ENFORCEMENT PATH READS IT. `approval doctor` reads it, which is a diagnosis rather than an enforcement: the row it feeds authorizes nothing and refuses nothing.\n\nThe actor MUST be `^system:`, for the reason `audit.dark_session`'s and `audit.gesture_refused`'s are: a record of an event authored by either party to it is a record neither party can be held to. Here both parties are machines, and neither the harness that pre-empted the question nor the agent whose action it was may author the note that says so.\n\n`verdict` is OPTIONAL and carries the other party's word verbatim when the notification stated one. It is not defaulted: a record that said `accept` because nothing said otherwise would be this runtime inventing another party's decision, which is the one thing a record about another party's decision may not do. Its absence is a fact about the notification.",
|
|
1969
|
+
"if": {
|
|
1970
|
+
"type": "object",
|
|
1971
|
+
"properties": {
|
|
1972
|
+
"event": {
|
|
1973
|
+
"const": "audit.question_preempted"
|
|
1974
|
+
}
|
|
1975
|
+
},
|
|
1976
|
+
"required": [
|
|
1977
|
+
"event"
|
|
1978
|
+
]
|
|
1979
|
+
},
|
|
1980
|
+
"then": {
|
|
1981
|
+
"type": "object",
|
|
1982
|
+
"required": [
|
|
1983
|
+
"actor",
|
|
1984
|
+
"payload"
|
|
1985
|
+
],
|
|
1986
|
+
"properties": {
|
|
1987
|
+
"actor": {
|
|
1988
|
+
"type": "string",
|
|
1989
|
+
"pattern": "^system:",
|
|
1990
|
+
"description": "The runtime. A `human:` or `agent:` actor is refused: the record states that something else answered a question, and neither party to that may author the statement."
|
|
1991
|
+
},
|
|
1992
|
+
"payload": {
|
|
1993
|
+
"type": "object",
|
|
1994
|
+
"required": [
|
|
1995
|
+
"source",
|
|
1996
|
+
"question"
|
|
1997
|
+
],
|
|
1998
|
+
"properties": {
|
|
1999
|
+
"source": {
|
|
2000
|
+
"enum": [
|
|
2001
|
+
"codex-auto-reviewer"
|
|
2002
|
+
],
|
|
2003
|
+
"description": "Who answered instead of the gate. CLOSED, and additive-only like every other vocabulary here (§11.1 invariant 6). `codex-auto-reviewer` is Codex's server-side guardian reviewer, whose answer is a genuine model call (`codex-rs/core/src/guardian/decision.rs`) and which runs before the client path. A second party gains a member here rather than an event type of its own."
|
|
2004
|
+
},
|
|
2005
|
+
"question": {
|
|
2006
|
+
"type": "object",
|
|
2007
|
+
"required": [
|
|
2008
|
+
"id"
|
|
2009
|
+
],
|
|
2010
|
+
"description": "The question that was answered, as the OTHER PARTY identified it. Its own identifiers and nothing this runtime minted: a record about somebody else's decision names it in their terms, or a reader cannot go and find it on their side.",
|
|
2011
|
+
"properties": {
|
|
2012
|
+
"id": {
|
|
2013
|
+
"type": "string",
|
|
2014
|
+
"minLength": 1,
|
|
2015
|
+
"description": "The other party's identifier for the question. Codex's `itemId` on the item-based API."
|
|
2016
|
+
},
|
|
2017
|
+
"method": {
|
|
2018
|
+
"type": "string",
|
|
2019
|
+
"minLength": 1,
|
|
2020
|
+
"description": "The notification that disclosed it, verbatim: `item/autoApprovalReview/started` or `item/autoApprovalReview/completed` at v0.1."
|
|
2021
|
+
},
|
|
2022
|
+
"thread": {
|
|
2023
|
+
"type": "string",
|
|
2024
|
+
"minLength": 1,
|
|
2025
|
+
"description": "The other party's session identifier, where the notification named one. Codex's `threadId`."
|
|
2026
|
+
},
|
|
2027
|
+
"turn": {
|
|
2028
|
+
"type": "string",
|
|
2029
|
+
"minLength": 1,
|
|
2030
|
+
"description": "The other party's turn identifier, where the notification named one. Codex's `turnId`."
|
|
2031
|
+
}
|
|
2032
|
+
}
|
|
2033
|
+
},
|
|
2034
|
+
"verdict": {
|
|
2035
|
+
"type": "string",
|
|
2036
|
+
"minLength": 1,
|
|
2037
|
+
"description": "The verdict the other party reached, verbatim in their own vocabulary and never translated into this project's. Absent when the notification stated none, which is a fact about the notification rather than a default."
|
|
2038
|
+
},
|
|
2039
|
+
"detail": {
|
|
2040
|
+
"type": "string",
|
|
2041
|
+
"description": "The runtime's own one line about what it did next. A convenience for a reader; nothing branches on it."
|
|
2042
|
+
}
|
|
2043
|
+
}
|
|
2044
|
+
}
|
|
2045
|
+
}
|
|
2046
|
+
}
|
|
2047
|
+
},
|
|
1510
2048
|
{
|
|
1511
2049
|
"$comment": "SPEC.md §5.2 (amended, APRV-214), 'The open window': a person opening the harness gate on themselves so the gate can be debugged from inside. The actor MUST be `^human:`, in the schema as well as in `core/gate-window.ts`, for the reason attestation refuses an agent: this is the one act that suspends the policy, and an agent able to author it could authorize its own next command. The window's state lives here and in no file, so what a reader derives is derived from a verified chain and nothing else. Expiry is `ts` plus `duration`, and a record claiming an `expires_at` beyond that reads as the shorter of the two: the field is a convenience for a human reading the log, never the authority.",
|
|
1512
2050
|
"if": {
|
|
@@ -1806,6 +2344,54 @@
|
|
|
1806
2344
|
}
|
|
1807
2345
|
}
|
|
1808
2346
|
}
|
|
2347
|
+
},
|
|
2348
|
+
{
|
|
2349
|
+
"$comment": "SPEC.md §5.2/§8/§10.1 (amended, APRV-338): a human's sign-off on the exact bytes of one PROTECTED PATH whose edits classify `policy.edit` or a `policy.edit.*` sub-class — a specification, an agent instruction file, CI configuration, a document a policy's `protected_paths` widened the surface with. The sibling of `gate.organ.attested` and deliberately a different type, because the two answer about different surfaces under different rules: an organ has no grant available to it at all, while a protected path does, and the after-the-fact checker of §10.1 prefers the grant's hunk evidence and reads this record only when no grant covers the change. Whole-file evidence, and weaker than a hunk grant for exactly that reason: it says a human read this file at these bytes and stands behind them, not that they saw a particular line change. The actor MUST be `^human:`, in the schema as well as in `core/attest.ts`: a record an agent could write would let the party under oversight ratify its own text. Both payload fields are computed by the runtime — the digest from the file's bytes, the path normalized — and the caller supplies neither. Gate-typed (`gate.` prefix), so `ts` is stamped at the write boundary.",
|
|
2350
|
+
"if": {
|
|
2351
|
+
"type": "object",
|
|
2352
|
+
"properties": {
|
|
2353
|
+
"event": {
|
|
2354
|
+
"const": "gate.path.signed_off"
|
|
2355
|
+
}
|
|
2356
|
+
},
|
|
2357
|
+
"required": [
|
|
2358
|
+
"event"
|
|
2359
|
+
]
|
|
2360
|
+
},
|
|
2361
|
+
"then": {
|
|
2362
|
+
"type": "object",
|
|
2363
|
+
"required": [
|
|
2364
|
+
"actor",
|
|
2365
|
+
"payload"
|
|
2366
|
+
],
|
|
2367
|
+
"properties": {
|
|
2368
|
+
"actor": {
|
|
2369
|
+
"type": "string",
|
|
2370
|
+
"pattern": "^human:"
|
|
2371
|
+
},
|
|
2372
|
+
"payload": {
|
|
2373
|
+
"type": "object",
|
|
2374
|
+
"required": [
|
|
2375
|
+
"path",
|
|
2376
|
+
"sha256"
|
|
2377
|
+
],
|
|
2378
|
+
"properties": {
|
|
2379
|
+
"path": {
|
|
2380
|
+
"type": "string",
|
|
2381
|
+
"minLength": 1,
|
|
2382
|
+
"maxLength": 512,
|
|
2383
|
+
"pattern": "^[^/\\\\][^\\\\]*$",
|
|
2384
|
+
"description": "The signed-off file's REPOSITORY-RELATIVE, `/`-separated path, for example `SPEC.md`. Relative and never absolute, for the reason `organ_path` is: a permanent record must not leak the writer's home directory and must name the same file on the machine that later reads it. That the path actually classifies `policy.edit` or a `policy.edit.*` sub-class, and is neither the policy file nor a `policy.core` surface, is the runtime's check (`core/attest.ts`), as is the refusal of `..`; a schema sees one record and constrains the shape."
|
|
2385
|
+
},
|
|
2386
|
+
"sha256": {
|
|
2387
|
+
"type": "string",
|
|
2388
|
+
"pattern": "^[a-f0-9]{64}$",
|
|
2389
|
+
"description": "SHA-256 (lowercase hex) of the file's exact bytes at the moment of sign-off, computed by the runtime. Bytes, not text, so a file differing by trailing whitespace is a different file and a record about it is not a record about this one. The caller has no parameter for this value — one who could supply it could sign off on bytes nobody read."
|
|
2390
|
+
}
|
|
2391
|
+
}
|
|
2392
|
+
}
|
|
2393
|
+
}
|
|
2394
|
+
}
|
|
1809
2395
|
}
|
|
1810
2396
|
]
|
|
1811
2397
|
}
|