approval-md 0.0.1 → 0.2.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/LICENSE +176 -0
- package/NOTICE +5 -0
- package/README.md +940 -4
- package/SPEC.md +476 -34
- package/cli.js +29 -3
- package/dist/src/adapters/agentmail.d.ts +426 -0
- package/dist/src/adapters/agentmail.js +1200 -0
- package/dist/src/adapters/agentmail.js.map +1 -0
- package/dist/src/adapters/conformance.d.ts +149 -0
- package/dist/src/adapters/conformance.js +461 -0
- package/dist/src/adapters/conformance.js.map +1 -0
- package/dist/src/adapters/contract.d.ts +628 -0
- package/dist/src/adapters/contract.js +1035 -0
- package/dist/src/adapters/contract.js.map +1 -0
- package/dist/src/adapters/email.d.ts +324 -0
- package/dist/src/adapters/email.js +749 -0
- package/dist/src/adapters/email.js.map +1 -0
- package/dist/src/adapters/env-passphrase.d.ts +93 -0
- package/dist/src/adapters/env-passphrase.js +132 -0
- package/dist/src/adapters/env-passphrase.js.map +1 -0
- package/dist/src/adapters/public.d.ts +11 -0
- package/dist/src/adapters/public.js +11 -0
- package/dist/src/adapters/public.js.map +1 -0
- package/dist/src/adapters/registry.d.ts +59 -0
- package/dist/src/adapters/registry.js +77 -0
- package/dist/src/adapters/registry.js.map +1 -0
- package/dist/src/adapters/smtp.d.ts +213 -0
- package/dist/src/adapters/smtp.js +499 -0
- package/dist/src/adapters/smtp.js.map +1 -0
- package/dist/src/adapters/vault-provider.d.ts +114 -0
- package/dist/src/adapters/vault-provider.js +161 -0
- package/dist/src/adapters/vault-provider.js.map +1 -0
- package/dist/src/adapters/zzz.d.ts +66 -0
- package/dist/src/adapters/zzz.js +299 -0
- package/dist/src/adapters/zzz.js.map +1 -0
- package/dist/src/channels/batch.d.ts +109 -0
- package/dist/src/channels/batch.js +121 -0
- package/dist/src/channels/batch.js.map +1 -0
- package/dist/src/channels/cli.d.ts +193 -0
- package/dist/src/channels/cli.js +468 -0
- package/dist/src/channels/cli.js.map +1 -0
- package/dist/src/channels/conformance.d.ts +92 -0
- package/dist/src/channels/conformance.js +445 -0
- package/dist/src/channels/conformance.js.map +1 -0
- package/dist/src/channels/contract.d.ts +623 -0
- package/dist/src/channels/contract.js +494 -0
- package/dist/src/channels/contract.js.map +1 -0
- package/dist/src/channels/payload-view.d.ts +35 -0
- package/dist/src/channels/payload-view.js +43 -0
- package/dist/src/channels/payload-view.js.map +1 -0
- package/dist/src/channels/render-queue.d.ts +149 -0
- package/dist/src/channels/render-queue.js +564 -0
- package/dist/src/channels/render-queue.js.map +1 -0
- package/dist/src/channels/tagging.d.ts +196 -0
- package/dist/src/channels/tagging.js +723 -0
- package/dist/src/channels/tagging.js.map +1 -0
- package/dist/src/channels/telegram.d.ts +1832 -0
- package/dist/src/channels/telegram.js +3190 -0
- package/dist/src/channels/telegram.js.map +1 -0
- package/dist/src/channels/web.d.ts +341 -0
- package/dist/src/channels/web.js +903 -0
- package/dist/src/channels/web.js.map +1 -0
- package/dist/src/cli/adapter.d.ts +90 -0
- package/dist/src/cli/adapter.js +288 -0
- package/dist/src/cli/adapter.js.map +1 -0
- package/dist/src/cli/amend.d.ts +59 -0
- package/dist/src/cli/amend.js +2171 -0
- package/dist/src/cli/amend.js.map +1 -0
- package/dist/src/cli/args.d.ts +43 -0
- package/dist/src/cli/args.js +86 -0
- package/dist/src/cli/args.js.map +1 -0
- package/dist/src/cli/attest.d.ts +41 -0
- package/dist/src/cli/attest.js +307 -0
- package/dist/src/cli/attest.js.map +1 -0
- package/dist/src/cli/audit-card.d.ts +62 -0
- package/dist/src/cli/audit-card.js +201 -0
- package/dist/src/cli/audit-card.js.map +1 -0
- package/dist/src/cli/audit.d.ts +59 -0
- package/dist/src/cli/audit.js +460 -0
- package/dist/src/cli/audit.js.map +1 -0
- package/dist/src/cli/channel-telegram.d.ts +806 -0
- package/dist/src/cli/channel-telegram.js +2063 -0
- package/dist/src/cli/channel-telegram.js.map +1 -0
- package/dist/src/cli/channel-web.d.ts +131 -0
- package/dist/src/cli/channel-web.js +357 -0
- package/dist/src/cli/channel-web.js.map +1 -0
- package/dist/src/cli/channel.d.ts +71 -0
- package/dist/src/cli/channel.js +438 -0
- package/dist/src/cli/channel.js.map +1 -0
- package/dist/src/cli/checkpoint-tap.d.ts +169 -0
- package/dist/src/cli/checkpoint-tap.js +238 -0
- package/dist/src/cli/checkpoint-tap.js.map +1 -0
- package/dist/src/cli/codex.d.ts +2 -0
- package/dist/src/cli/codex.js +172 -0
- package/dist/src/cli/codex.js.map +1 -0
- package/dist/src/cli/coverage.d.ts +61 -0
- package/dist/src/cli/coverage.js +343 -0
- package/dist/src/cli/coverage.js.map +1 -0
- package/dist/src/cli/daemon.d.ts +120 -0
- package/dist/src/cli/daemon.js +631 -0
- package/dist/src/cli/daemon.js.map +1 -0
- package/dist/src/cli/doctor.d.ts +129 -0
- package/dist/src/cli/doctor.js +2762 -0
- package/dist/src/cli/doctor.js.map +1 -0
- package/dist/src/cli/env.d.ts +65 -0
- package/dist/src/cli/env.js +302 -0
- package/dist/src/cli/env.js.map +1 -0
- package/dist/src/cli/execute.d.ts +202 -0
- package/dist/src/cli/execute.js +1682 -0
- package/dist/src/cli/execute.js.map +1 -0
- package/dist/src/cli/exit-codes.d.ts +73 -0
- package/dist/src/cli/exit-codes.js +82 -0
- package/dist/src/cli/exit-codes.js.map +1 -0
- package/dist/src/cli/feedback.d.ts +60 -0
- package/dist/src/cli/feedback.js +205 -0
- package/dist/src/cli/feedback.js.map +1 -0
- package/dist/src/cli/gate-window.d.ts +40 -0
- package/dist/src/cli/gate-window.js +294 -0
- package/dist/src/cli/gate-window.js.map +1 -0
- package/dist/src/cli/gate.d.ts +68 -0
- package/dist/src/cli/gate.js +557 -0
- package/dist/src/cli/gate.js.map +1 -0
- package/dist/src/cli/git-scope.d.ts +190 -0
- package/dist/src/cli/git-scope.js +295 -0
- package/dist/src/cli/git-scope.js.map +1 -0
- package/dist/src/cli/gloss-attach.d.ts +85 -0
- package/dist/src/cli/gloss-attach.js +107 -0
- package/dist/src/cli/gloss-attach.js.map +1 -0
- package/dist/src/cli/gloss-codex-child.d.ts +9 -0
- package/dist/src/cli/gloss-codex-child.js +149 -0
- package/dist/src/cli/gloss-codex-child.js.map +1 -0
- package/dist/src/cli/gloss-codex.d.ts +24 -0
- package/dist/src/cli/gloss-codex.js +255 -0
- package/dist/src/cli/gloss-codex.js.map +1 -0
- package/dist/src/cli/gloss-options.d.ts +42 -0
- package/dist/src/cli/gloss-options.js +79 -0
- package/dist/src/cli/gloss-options.js.map +1 -0
- package/dist/src/cli/gloss.d.ts +265 -0
- package/dist/src/cli/gloss.js +362 -0
- package/dist/src/cli/gloss.js.map +1 -0
- package/dist/src/cli/help.d.ts +103 -0
- package/dist/src/cli/help.js +2339 -0
- package/dist/src/cli/help.js.map +1 -0
- package/dist/src/cli/hook-codex.d.ts +78 -0
- package/dist/src/cli/hook-codex.js +167 -0
- package/dist/src/cli/hook-codex.js.map +1 -0
- package/dist/src/cli/hook.d.ts +331 -0
- package/dist/src/cli/hook.js +2849 -0
- package/dist/src/cli/hook.js.map +1 -0
- package/dist/src/cli/import.d.ts +35 -0
- package/dist/src/cli/import.js +175 -0
- package/dist/src/cli/import.js.map +1 -0
- package/dist/src/cli/init.d.ts +84 -0
- package/dist/src/cli/init.js +336 -0
- package/dist/src/cli/init.js.map +1 -0
- package/dist/src/cli/instructions.d.ts +23 -0
- package/dist/src/cli/instructions.js +262 -0
- package/dist/src/cli/instructions.js.map +1 -0
- package/dist/src/cli/journal.d.ts +41 -0
- package/dist/src/cli/journal.js +238 -0
- package/dist/src/cli/journal.js.map +1 -0
- package/dist/src/cli/log-advance.d.ts +287 -0
- package/dist/src/cli/log-advance.js +840 -0
- package/dist/src/cli/log-advance.js.map +1 -0
- package/dist/src/cli/log-anchor.d.ts +176 -0
- package/dist/src/cli/log-anchor.js +387 -0
- package/dist/src/cli/log-anchor.js.map +1 -0
- package/dist/src/cli/log-checkpoint.d.ts +22 -0
- package/dist/src/cli/log-checkpoint.js +128 -0
- package/dist/src/cli/log-checkpoint.js.map +1 -0
- package/dist/src/cli/log-sync.d.ts +243 -0
- package/dist/src/cli/log-sync.js +849 -0
- package/dist/src/cli/log-sync.js.map +1 -0
- package/dist/src/cli/log-verbs.d.ts +16 -0
- package/dist/src/cli/log-verbs.js +360 -0
- package/dist/src/cli/log-verbs.js.map +1 -0
- package/dist/src/cli/long-help.d.ts +70 -0
- package/dist/src/cli/long-help.js +148 -0
- package/dist/src/cli/long-help.js.map +1 -0
- package/dist/src/cli/main.d.ts +77 -0
- package/dist/src/cli/main.js +1206 -0
- package/dist/src/cli/main.js.map +1 -0
- package/dist/src/cli/mcp.d.ts +52 -0
- package/dist/src/cli/mcp.js +306 -0
- package/dist/src/cli/mcp.js.map +1 -0
- package/dist/src/cli/paths.d.ts +56 -0
- package/dist/src/cli/paths.js +79 -0
- package/dist/src/cli/paths.js.map +1 -0
- package/dist/src/cli/payload.d.ts +58 -0
- package/dist/src/cli/payload.js +253 -0
- package/dist/src/cli/payload.js.map +1 -0
- package/dist/src/cli/policy.d.ts +43 -0
- package/dist/src/cli/policy.js +229 -0
- package/dist/src/cli/policy.js.map +1 -0
- package/dist/src/cli/preflight.d.ts +363 -0
- package/dist/src/cli/preflight.js +1175 -0
- package/dist/src/cli/preflight.js.map +1 -0
- package/dist/src/cli/progress.d.ts +78 -0
- package/dist/src/cli/progress.js +112 -0
- package/dist/src/cli/progress.js.map +1 -0
- package/dist/src/cli/prompt.d.ts +209 -0
- package/dist/src/cli/prompt.js +312 -0
- package/dist/src/cli/prompt.js.map +1 -0
- package/dist/src/cli/quickstart.d.ts +46 -0
- package/dist/src/cli/quickstart.js +297 -0
- package/dist/src/cli/quickstart.js.map +1 -0
- package/dist/src/cli/records.d.ts +34 -0
- package/dist/src/cli/records.js +66 -0
- package/dist/src/cli/records.js.map +1 -0
- package/dist/src/cli/render.d.ts +22 -0
- package/dist/src/cli/render.js +132 -0
- package/dist/src/cli/render.js.map +1 -0
- package/dist/src/cli/sandbox.d.ts +51 -0
- package/dist/src/cli/sandbox.js +150 -0
- package/dist/src/cli/sandbox.js.map +1 -0
- package/dist/src/cli/scaffold.d.ts +79 -0
- package/dist/src/cli/scaffold.js +137 -0
- package/dist/src/cli/scaffold.js.map +1 -0
- package/dist/src/cli/setup-adapter.d.ts +137 -0
- package/dist/src/cli/setup-adapter.js +509 -0
- package/dist/src/cli/setup-adapter.js.map +1 -0
- package/dist/src/cli/setup-channel.d.ts +117 -0
- package/dist/src/cli/setup-channel.js +635 -0
- package/dist/src/cli/setup-channel.js.map +1 -0
- package/dist/src/cli/setup-checkpoint.d.ts +57 -0
- package/dist/src/cli/setup-checkpoint.js +196 -0
- package/dist/src/cli/setup-checkpoint.js.map +1 -0
- package/dist/src/cli/setup-common.d.ts +275 -0
- package/dist/src/cli/setup-common.js +376 -0
- package/dist/src/cli/setup-common.js.map +1 -0
- package/dist/src/cli/setup-flow.d.ts +287 -0
- package/dist/src/cli/setup-flow.js +476 -0
- package/dist/src/cli/setup-flow.js.map +1 -0
- package/dist/src/cli/setup-service.d.ts +96 -0
- package/dist/src/cli/setup-service.js +308 -0
- package/dist/src/cli/setup-service.js.map +1 -0
- package/dist/src/cli/setup.d.ts +202 -0
- package/dist/src/cli/setup.js +473 -0
- package/dist/src/cli/setup.js.map +1 -0
- package/dist/src/cli/style.d.ts +320 -0
- package/dist/src/cli/style.js +469 -0
- package/dist/src/cli/style.js.map +1 -0
- package/dist/src/cli/token.d.ts +39 -0
- package/dist/src/cli/token.js +274 -0
- package/dist/src/cli/token.js.map +1 -0
- package/dist/src/cli/up.d.ts +155 -0
- package/dist/src/cli/up.js +849 -0
- package/dist/src/cli/up.js.map +1 -0
- package/dist/src/cli/usage.d.ts +37 -0
- package/dist/src/cli/usage.js +91 -0
- package/dist/src/cli/usage.js.map +1 -0
- package/dist/src/cli/values.d.ts +40 -0
- package/dist/src/cli/values.js +189 -0
- package/dist/src/cli/values.js.map +1 -0
- package/dist/src/cli/vault.d.ts +59 -0
- package/dist/src/cli/vault.js +362 -0
- package/dist/src/cli/vault.js.map +1 -0
- package/dist/src/cli/verb-registry.d.ts +76 -0
- package/dist/src/cli/verb-registry.js +2341 -0
- package/dist/src/cli/verb-registry.js.map +1 -0
- package/dist/src/cli/wordmark.d.ts +31 -0
- package/dist/src/cli/wordmark.js +52 -0
- package/dist/src/cli/wordmark.js.map +1 -0
- package/dist/src/codex/doctor.d.ts +13 -0
- package/dist/src/codex/doctor.js +41 -0
- package/dist/src/codex/doctor.js.map +1 -0
- package/dist/src/codex/manifest.d.ts +49 -0
- package/dist/src/codex/manifest.js +103 -0
- package/dist/src/codex/manifest.js.map +1 -0
- package/dist/src/codex/templates.d.ts +41 -0
- package/dist/src/codex/templates.js +319 -0
- package/dist/src/codex/templates.js.map +1 -0
- package/dist/src/codex/trust.d.ts +19 -0
- package/dist/src/codex/trust.js +183 -0
- package/dist/src/codex/trust.js.map +1 -0
- package/dist/src/codex/workspace-plan.d.ts +131 -0
- package/dist/src/codex/workspace-plan.js +561 -0
- package/dist/src/codex/workspace-plan.js.map +1 -0
- package/dist/src/core/actor.d.ts +2 -0
- package/dist/src/core/actor.js +5 -0
- package/dist/src/core/actor.js.map +1 -0
- package/dist/src/core/advance-cycle.d.ts +170 -0
- package/dist/src/core/advance-cycle.js +200 -0
- package/dist/src/core/advance-cycle.js.map +1 -0
- package/dist/src/core/agents-md.d.ts +276 -0
- package/dist/src/core/agents-md.js +747 -0
- package/dist/src/core/agents-md.js.map +1 -0
- package/dist/src/core/apply-patch.d.ts +49 -0
- package/dist/src/core/apply-patch.js +266 -0
- package/dist/src/core/apply-patch.js.map +1 -0
- package/dist/src/core/attest.d.ts +420 -0
- package/dist/src/core/attest.js +589 -0
- package/dist/src/core/attest.js.map +1 -0
- package/dist/src/core/audit.d.ts +492 -0
- package/dist/src/core/audit.js +882 -0
- package/dist/src/core/audit.js.map +1 -0
- package/dist/src/core/budgets.d.ts +238 -0
- package/dist/src/core/budgets.js +449 -0
- package/dist/src/core/budgets.js.map +1 -0
- package/dist/src/core/checkpoint.d.ts +500 -0
- package/dist/src/core/checkpoint.js +738 -0
- package/dist/src/core/checkpoint.js.map +1 -0
- package/dist/src/core/child-env.d.ts +88 -0
- package/dist/src/core/child-env.js +86 -0
- package/dist/src/core/child-env.js.map +1 -0
- package/dist/src/core/clock.d.ts +52 -0
- package/dist/src/core/clock.js +43 -0
- package/dist/src/core/clock.js.map +1 -0
- package/dist/src/core/command-class.d.ts +543 -0
- package/dist/src/core/command-class.js +2356 -0
- package/dist/src/core/command-class.js.map +1 -0
- package/dist/src/core/coverage-sources/adapter.d.ts +40 -0
- package/dist/src/core/coverage-sources/adapter.js +71 -0
- package/dist/src/core/coverage-sources/adapter.js.map +1 -0
- package/dist/src/core/coverage-sources/gh.d.ts +48 -0
- package/dist/src/core/coverage-sources/gh.js +136 -0
- package/dist/src/core/coverage-sources/gh.js.map +1 -0
- package/dist/src/core/coverage-sources/git.d.ts +101 -0
- package/dist/src/core/coverage-sources/git.js +269 -0
- package/dist/src/core/coverage-sources/git.js.map +1 -0
- package/dist/src/core/coverage.d.ts +217 -0
- package/dist/src/core/coverage.js +337 -0
- package/dist/src/core/coverage.js.map +1 -0
- package/dist/src/core/credential-spec.d.ts +72 -0
- package/dist/src/core/credential-spec.js +23 -0
- package/dist/src/core/credential-spec.js.map +1 -0
- package/dist/src/core/dark-session.d.ts +331 -0
- package/dist/src/core/dark-session.js +714 -0
- package/dist/src/core/dark-session.js.map +1 -0
- package/dist/src/core/decision-refusal.d.ts +185 -0
- package/dist/src/core/decision-refusal.js +265 -0
- package/dist/src/core/decision-refusal.js.map +1 -0
- package/dist/src/core/env-file.d.ts +450 -0
- package/dist/src/core/env-file.js +837 -0
- package/dist/src/core/env-file.js.map +1 -0
- package/dist/src/core/execute.d.ts +858 -0
- package/dist/src/core/execute.js +1271 -0
- package/dist/src/core/execute.js.map +1 -0
- package/dist/src/core/frontmatter.d.ts +78 -0
- package/dist/src/core/frontmatter.js +100 -0
- package/dist/src/core/frontmatter.js.map +1 -0
- package/dist/src/core/gate-window.d.ts +312 -0
- package/dist/src/core/gate-window.js +506 -0
- package/dist/src/core/gate-window.js.map +1 -0
- package/dist/src/core/gate.d.ts +1364 -0
- package/dist/src/core/gate.js +3002 -0
- package/dist/src/core/gate.js.map +1 -0
- package/dist/src/core/git-run.d.ts +73 -0
- package/dist/src/core/git-run.js +93 -0
- package/dist/src/core/git-run.js.map +1 -0
- package/dist/src/core/harness-version.d.ts +157 -0
- package/dist/src/core/harness-version.js +211 -0
- package/dist/src/core/harness-version.js.map +1 -0
- package/dist/src/core/harness-wait.d.ts +55 -0
- package/dist/src/core/harness-wait.js +58 -0
- package/dist/src/core/harness-wait.js.map +1 -0
- package/dist/src/core/head-retry.d.ts +107 -0
- package/dist/src/core/head-retry.js +121 -0
- package/dist/src/core/head-retry.js.map +1 -0
- package/dist/src/core/instance.d.ts +253 -0
- package/dist/src/core/instance.js +319 -0
- package/dist/src/core/instance.js.map +1 -0
- package/dist/src/core/intake-limits.d.ts +247 -0
- package/dist/src/core/intake-limits.js +350 -0
- package/dist/src/core/intake-limits.js.map +1 -0
- package/dist/src/core/jcs.d.ts +52 -0
- package/dist/src/core/jcs.js +132 -0
- package/dist/src/core/jcs.js.map +1 -0
- package/dist/src/core/journal.d.ts +144 -0
- package/dist/src/core/journal.js +200 -0
- package/dist/src/core/journal.js.map +1 -0
- package/dist/src/core/live-draw.d.ts +436 -0
- package/dist/src/core/live-draw.js +703 -0
- package/dist/src/core/live-draw.js.map +1 -0
- package/dist/src/core/log-reconcile.d.ts +89 -0
- package/dist/src/core/log-reconcile.js +136 -0
- package/dist/src/core/log-reconcile.js.map +1 -0
- package/dist/src/core/log-subscribe.d.ts +36 -0
- package/dist/src/core/log-subscribe.js +162 -0
- package/dist/src/core/log-subscribe.js.map +1 -0
- package/dist/src/core/log.d.ts +278 -0
- package/dist/src/core/log.js +546 -0
- package/dist/src/core/log.js.map +1 -0
- package/dist/src/core/loop.d.ts +274 -0
- package/dist/src/core/loop.js +487 -0
- package/dist/src/core/loop.js.map +1 -0
- package/dist/src/core/md-fence.d.ts +41 -0
- package/dist/src/core/md-fence.js +74 -0
- package/dist/src/core/md-fence.js.map +1 -0
- package/dist/src/core/money.d.ts +147 -0
- package/dist/src/core/money.js +195 -0
- package/dist/src/core/money.js.map +1 -0
- package/dist/src/core/payload-census.d.ts +74 -0
- package/dist/src/core/payload-census.js +146 -0
- package/dist/src/core/payload-census.js.map +1 -0
- package/dist/src/core/payload-store.d.ts +175 -0
- package/dist/src/core/payload-store.js +340 -0
- package/dist/src/core/payload-store.js.map +1 -0
- package/dist/src/core/payload.d.ts +71 -0
- package/dist/src/core/payload.js +80 -0
- package/dist/src/core/payload.js.map +1 -0
- package/dist/src/core/policy-diff.d.ts +292 -0
- package/dist/src/core/policy-diff.js +588 -0
- package/dist/src/core/policy-diff.js.map +1 -0
- package/dist/src/core/policy-expectations.d.ts +199 -0
- package/dist/src/core/policy-expectations.js +394 -0
- package/dist/src/core/policy-expectations.js.map +1 -0
- package/dist/src/core/policy-explain.d.ts +150 -0
- package/dist/src/core/policy-explain.js +258 -0
- package/dist/src/core/policy-explain.js.map +1 -0
- package/dist/src/core/policy-load.d.ts +527 -0
- package/dist/src/core/policy-load.js +536 -0
- package/dist/src/core/policy-load.js.map +1 -0
- package/dist/src/core/policy-match.d.ts +281 -0
- package/dist/src/core/policy-match.js +478 -0
- package/dist/src/core/policy-match.js.map +1 -0
- package/dist/src/core/policy-proposal.d.ts +265 -0
- package/dist/src/core/policy-proposal.js +458 -0
- package/dist/src/core/policy-proposal.js.map +1 -0
- package/dist/src/core/prompt-layout.d.ts +221 -0
- package/dist/src/core/prompt-layout.js +422 -0
- package/dist/src/core/prompt-layout.js.map +1 -0
- package/dist/src/core/protected-path-guard.d.ts +453 -0
- package/dist/src/core/protected-path-guard.js +1566 -0
- package/dist/src/core/protected-path-guard.js.map +1 -0
- package/dist/src/core/registration.d.ts +25 -0
- package/dist/src/core/registration.js +39 -0
- package/dist/src/core/registration.js.map +1 -0
- package/dist/src/core/reindex.d.ts +99 -0
- package/dist/src/core/reindex.js +336 -0
- package/dist/src/core/reindex.js.map +1 -0
- package/dist/src/core/sampler.d.ts +313 -0
- package/dist/src/core/sampler.js +388 -0
- package/dist/src/core/sampler.js.map +1 -0
- package/dist/src/core/sandbox.d.ts +290 -0
- package/dist/src/core/sandbox.js +424 -0
- package/dist/src/core/sandbox.js.map +1 -0
- package/dist/src/core/seal.d.ts +165 -0
- package/dist/src/core/seal.js +290 -0
- package/dist/src/core/seal.js.map +1 -0
- package/dist/src/core/state.d.ts +505 -0
- package/dist/src/core/state.js +1009 -0
- package/dist/src/core/state.js.map +1 -0
- package/dist/src/core/task-file.d.ts +185 -0
- package/dist/src/core/task-file.js +464 -0
- package/dist/src/core/task-file.js.map +1 -0
- package/dist/src/core/telegram-config.d.ts +93 -0
- package/dist/src/core/telegram-config.js +114 -0
- package/dist/src/core/telegram-config.js.map +1 -0
- package/dist/src/core/token.d.ts +409 -0
- package/dist/src/core/token.js +561 -0
- package/dist/src/core/token.js.map +1 -0
- package/dist/src/core/validate.d.ts +138 -0
- package/dist/src/core/validate.js +0 -0
- package/dist/src/core/validate.js.map +1 -0
- package/dist/src/core/values.d.ts +137 -0
- package/dist/src/core/values.js +153 -0
- package/dist/src/core/values.js.map +1 -0
- package/dist/src/core/vault.d.ts +291 -0
- package/dist/src/core/vault.js +612 -0
- package/dist/src/core/vault.js.map +1 -0
- package/dist/src/core/verified-snapshot.d.ts +204 -0
- package/dist/src/core/verified-snapshot.js +506 -0
- package/dist/src/core/verified-snapshot.js.map +1 -0
- package/dist/src/core/verify.d.ts +336 -0
- package/dist/src/core/verify.js +549 -0
- package/dist/src/core/verify.js.map +1 -0
- package/dist/src/core/version.d.ts +8 -0
- package/dist/src/core/version.js +9 -0
- package/dist/src/core/version.js.map +1 -0
- package/dist/src/core/wysiwys.d.ts +370 -0
- package/dist/src/core/wysiwys.js +728 -0
- package/dist/src/core/wysiwys.js.map +1 -0
- package/dist/src/daemon/advance-child.d.ts +39 -0
- package/dist/src/daemon/advance-child.js +78 -0
- package/dist/src/daemon/advance-child.js.map +1 -0
- package/dist/src/daemon/advance.d.ts +466 -0
- package/dist/src/daemon/advance.js +849 -0
- package/dist/src/daemon/advance.js.map +1 -0
- package/dist/src/daemon/audit.d.ts +87 -0
- package/dist/src/daemon/audit.js +90 -0
- package/dist/src/daemon/audit.js.map +1 -0
- package/dist/src/daemon/daemon.d.ts +1180 -0
- package/dist/src/daemon/daemon.js +1988 -0
- package/dist/src/daemon/daemon.js.map +1 -0
- package/dist/src/daemon/dark-session.d.ts +64 -0
- package/dist/src/daemon/dark-session.js +119 -0
- package/dist/src/daemon/dark-session.js.map +1 -0
- package/dist/src/daemon/draw-child.d.ts +36 -0
- package/dist/src/daemon/draw-child.js +132 -0
- package/dist/src/daemon/draw-child.js.map +1 -0
- package/dist/src/daemon/draw.d.ts +154 -0
- package/dist/src/daemon/draw.js +458 -0
- package/dist/src/daemon/draw.js.map +1 -0
- package/dist/src/daemon/git-evidence.d.ts +173 -0
- package/dist/src/daemon/git-evidence.js +345 -0
- package/dist/src/daemon/git-evidence.js.map +1 -0
- package/dist/src/daemon/projection.d.ts +180 -0
- package/dist/src/daemon/projection.js +233 -0
- package/dist/src/daemon/projection.js.map +1 -0
- package/dist/src/daemon/prune.d.ts +207 -0
- package/dist/src/daemon/prune.js +376 -0
- package/dist/src/daemon/prune.js.map +1 -0
- package/dist/src/mcp/http.d.ts +113 -0
- package/dist/src/mcp/http.js +343 -0
- package/dist/src/mcp/http.js.map +1 -0
- package/dist/src/mcp/server.d.ts +265 -0
- package/dist/src/mcp/server.js +602 -0
- package/dist/src/mcp/server.js.map +1 -0
- package/docs/adapter-api.md +106 -0
- package/docs/cli-reference.md +5716 -0
- package/docs/codex-enforced-session.md +30 -0
- package/package.json +53 -4
- package/schema/.gitkeep +0 -0
- package/schema/LICENSE +117 -0
- package/schema/codex-instance.schema.json +82 -0
- package/schema/envelope.schema.json +137 -0
- package/schema/event.schema.json +1811 -0
- package/schema/fixtures/codex-instance/invalid/unpinned-codex-version.json +40 -0
- package/schema/fixtures/codex-instance/valid/canonical.json +40 -0
- package/schema/fixtures/envelope/invalid/action-missing-idempotency-key.json +15 -0
- package/schema/fixtures/envelope/invalid/action-unknown-class-format.json +14 -0
- package/schema/fixtures/envelope/invalid/confidence-out-of-range.json +11 -0
- package/schema/fixtures/envelope/invalid/est-cost-bare-number.json +14 -0
- package/schema/fixtures/envelope/invalid/est-cost-noncanonical-string.json +14 -0
- package/schema/fixtures/envelope/invalid/malformed-assignee.json +10 -0
- package/schema/fixtures/envelope/invalid/malformed-created-by.json +7 -0
- package/schema/fixtures/envelope/invalid/malformed-max-latency.json +11 -0
- package/schema/fixtures/envelope/invalid/malformed-payload-hash.json +14 -0
- package/schema/fixtures/envelope/invalid/max-cost-bare-number.json +11 -0
- package/schema/fixtures/envelope/invalid/missing-origin.json +3 -0
- package/schema/fixtures/envelope/invalid/negative-est-cost.json +14 -0
- package/schema/fixtures/envelope/invalid/unknown-state.json +7 -0
- package/schema/fixtures/envelope/invalid/unknown-top-level-field.json +8 -0
- package/schema/fixtures/envelope/valid/action-payload-hash.json +17 -0
- package/schema/fixtures/envelope/valid/actions-without-budget.json +18 -0
- package/schema/fixtures/envelope/valid/canonical.json +25 -0
- package/schema/fixtures/envelope/valid/minimal.json +7 -0
- package/schema/fixtures/envelope/valid/multi-action-executed.json +30 -0
- package/schema/fixtures/envelope/valid/record-write-stage.json +22 -0
- package/schema/fixtures/event/invalid/approval-granted-agent-actor.json +15 -0
- package/schema/fixtures/event/invalid/approval-granted-empty-batch-delivery-id.json +17 -0
- package/schema/fixtures/event/invalid/approval-granted-fifth-reaction.json +16 -0
- package/schema/fixtures/event/invalid/approval-granted-missing-actor.json +14 -0
- package/schema/fixtures/event/invalid/approval-requested-missing-action-key.json +14 -0
- package/schema/fixtures/event/invalid/approval-withdrawn-agent-policy-drift.json +15 -0
- package/schema/fixtures/event/invalid/approval-withdrawn-missing-reason.json +15 -0
- package/schema/fixtures/event/invalid/approval-withdrawn-system-actor.json +15 -0
- package/schema/fixtures/event/invalid/audit-decision-refused-human-actor.json +17 -0
- package/schema/fixtures/event/invalid/audit-decision-refused-missing-code.json +16 -0
- package/schema/fixtures/event/invalid/audit-reviewed-agent-actor.json +15 -0
- package/schema/fixtures/event/invalid/audit-reviewed-loved-no-note.json +16 -0
- package/schema/fixtures/event/invalid/audit-reviewed-system-actor.json +15 -0
- package/schema/fixtures/event/invalid/bad-actor-prefix.json +15 -0
- package/schema/fixtures/event/invalid/est-cost-bare-number.json +17 -0
- package/schema/fixtures/event/invalid/execution-completed-fabricated-exit-code.json +16 -0
- package/schema/fixtures/event/invalid/execution-completed-provider-ref-empty-id.json +18 -0
- package/schema/fixtures/event/invalid/execution-completed-provider-ref-extra-field.json +19 -0
- package/schema/fixtures/event/invalid/execution-completed-provider-ref-id-not-string.json +18 -0
- package/schema/fixtures/event/invalid/execution-completed-provider-ref-missing-adapter.json +17 -0
- package/schema/fixtures/event/invalid/execution-failed-open-reported-by.json +16 -0
- package/schema/fixtures/event/invalid/execution-indeterminate-open-reason.json +14 -0
- package/schema/fixtures/event/invalid/execution-reconciled-agent-actor.json +17 -0
- package/schema/fixtures/event/invalid/execution-started-negative-env-stripped.json +16 -0
- package/schema/fixtures/event/invalid/gate-bypassed-missing-opened-seq.json +15 -0
- package/schema/fixtures/event/invalid/gate-closed-non-integer-opened-seq.json +13 -0
- package/schema/fixtures/event/invalid/gate-opened-agent-actor.json +16 -0
- package/schema/fixtures/event/invalid/gate-organ-attested-absolute-path.json +14 -0
- package/schema/fixtures/event/invalid/gate-organ-attested-agent-actor.json +14 -0
- package/schema/fixtures/event/invalid/gate-organ-attested-missing-organ-path.json +13 -0
- package/schema/fixtures/event/invalid/harness-unknown-kind.json +18 -0
- package/schema/fixtures/event/invalid/harness-version-multiline.json +16 -0
- package/schema/fixtures/event/invalid/log-checkpoint-agent-actor.json +17 -0
- package/schema/fixtures/event/invalid/log-checkpoint-missing-signature.json +16 -0
- package/schema/fixtures/event/invalid/log-checkpoint-short-signed-hash.json +17 -0
- package/schema/fixtures/event/invalid/log-checkpoint-unknown-signature-alg.json +17 -0
- package/schema/fixtures/event/invalid/malformed-ts.json +15 -0
- package/schema/fixtures/event/invalid/missing-alg.json +14 -0
- package/schema/fixtures/event/invalid/missing-hash.json +14 -0
- package/schema/fixtures/event/invalid/non-integer-seq.json +15 -0
- package/schema/fixtures/event/invalid/payload-pruned-human-actor.json +14 -0
- package/schema/fixtures/event/invalid/payload-pruned-missing-hash.json +14 -0
- package/schema/fixtures/event/invalid/policy-declined-agent-actor.json +15 -0
- package/schema/fixtures/event/invalid/policy-proposed-missing-diff.json +19 -0
- package/schema/fixtures/event/invalid/policy-proposed-system-actor.json +26 -0
- package/schema/fixtures/event/invalid/short-hash.json +15 -0
- package/schema/fixtures/event/invalid/unknown-alg.json +15 -0
- package/schema/fixtures/event/invalid/unknown-event-type.json +15 -0
- package/schema/fixtures/event/invalid/unknown-top-level-field.json +16 -0
- package/schema/fixtures/event/valid/approval-expired.json +15 -0
- package/schema/fixtures/event/valid/approval-granted-batch.json +19 -0
- package/schema/fixtures/event/valid/approval-granted-reaction.json +16 -0
- package/schema/fixtures/event/valid/approval-granted.json +15 -0
- package/schema/fixtures/event/valid/approval-rejected.json +15 -0
- package/schema/fixtures/event/valid/approval-requested.json +19 -0
- package/schema/fixtures/event/valid/approval-revoked.json +15 -0
- package/schema/fixtures/event/valid/approval-withdrawn-policy-drift.json +17 -0
- package/schema/fixtures/event/valid/approval-withdrawn.json +16 -0
- package/schema/fixtures/event/valid/audit-decision-refused.json +20 -0
- package/schema/fixtures/event/valid/audit-reviewed-reaction.json +17 -0
- package/schema/fixtures/event/valid/audit-reviewed.json +15 -0
- package/schema/fixtures/event/valid/audit-sampled.json +14 -0
- package/schema/fixtures/event/valid/budget-exceeded.json +21 -0
- package/schema/fixtures/event/valid/envelope-drift.json +16 -0
- package/schema/fixtures/event/valid/execution-completed-harness-report.json +16 -0
- package/schema/fixtures/event/valid/execution-completed-provider-ref.json +18 -0
- package/schema/fixtures/event/valid/execution-completed.json +15 -0
- package/schema/fixtures/event/valid/execution-failed-harness-report.json +16 -0
- package/schema/fixtures/event/valid/execution-failed.json +15 -0
- package/schema/fixtures/event/valid/execution-indeterminate.json +15 -0
- package/schema/fixtures/event/valid/execution-reconciled.json +17 -0
- package/schema/fixtures/event/valid/execution-started-env-stripped.json +17 -0
- package/schema/fixtures/event/valid/execution-started.json +14 -0
- package/schema/fixtures/event/valid/gate-bypassed-harness-version.json +18 -0
- package/schema/fixtures/event/valid/gate-bypassed.json +19 -0
- package/schema/fixtures/event/valid/gate-closed.json +14 -0
- package/schema/fixtures/event/valid/gate-opened.json +16 -0
- package/schema/fixtures/event/valid/gate-organ-attested.json +14 -0
- package/schema/fixtures/event/valid/genesis-null-prev.json +14 -0
- package/schema/fixtures/event/valid/log-checkpoint.json +17 -0
- package/schema/fixtures/event/valid/payload-pruned-orphan.json +13 -0
- package/schema/fixtures/event/valid/payload-pruned.json +17 -0
- package/schema/fixtures/event/valid/policy-declined.json +16 -0
- package/schema/fixtures/event/valid/policy-proposed.json +35 -0
- package/schema/fixtures/event/valid/policy-updated.json +14 -0
- package/schema/fixtures/event/valid/reconciliation-required.json +18 -0
- package/schema/fixtures/event/valid/reconciliation-satisfied.json +17 -0
- package/schema/fixtures/event/valid/route-accepted.json +15 -0
- package/schema/fixtures/event/valid/route-proposed.json +16 -0
- package/schema/fixtures/event/valid/spec-example.json +15 -0
- package/schema/fixtures/event/valid/task-registered-harness-version.json +23 -0
- package/schema/fixtures/event/valid/task-registered.json +14 -0
- package/schema/fixtures/hash/known-answer-pre-121.json +74 -0
- package/schema/fixtures/hash/known-answer.json +74 -0
- package/schema/fixtures/policy/invalid/bad-approval-ttl.json +7 -0
- package/schema/fixtures/policy/invalid/bad-web-port.json +4 -0
- package/schema/fixtures/policy/invalid/checkpoint-key-not-base64.json +6 -0
- package/schema/fixtures/policy/invalid/class-rule-missing-autonomy.json +9 -0
- package/schema/fixtures/policy/invalid/empty-class-key.json +6 -0
- package/schema/fixtures/policy/invalid/live-rate-on-human-only.json +7 -0
- package/schema/fixtures/policy/invalid/malformed-class-key.json +6 -0
- package/schema/fixtures/policy/invalid/missing-version.json +8 -0
- package/schema/fixtures/policy/invalid/negative-limit.json +9 -0
- package/schema/fixtures/policy/invalid/non-numeric-limit.json +9 -0
- package/schema/fixtures/policy/invalid/non-positive-max-pending.json +9 -0
- package/schema/fixtures/policy/invalid/on-expiry-grant.json +8 -0
- package/schema/fixtures/policy/invalid/payload-retention-bare-number.json +4 -0
- package/schema/fixtures/policy/invalid/payload-retention-compound.json +4 -0
- package/schema/fixtures/policy/invalid/payload-retention-fractional.json +4 -0
- package/schema/fixtures/policy/invalid/payload-retention-zero.json +4 -0
- package/schema/fixtures/policy/invalid/protected-paths-escape.json +4 -0
- package/schema/fixtures/policy/invalid/protected-paths-glob.json +4 -0
- package/schema/fixtures/policy/invalid/retro-rate-on-human-only.json +7 -0
- package/schema/fixtures/policy/invalid/retro-rate-on-manual.json +7 -0
- package/schema/fixtures/policy/invalid/retro-rate-zero.json +7 -0
- package/schema/fixtures/policy/invalid/sample-rate-too-high.json +5 -0
- package/schema/fixtures/policy/invalid/sampling-secret-env-empty.json +7 -0
- package/schema/fixtures/policy/invalid/sampling-secret-env-not-string.json +6 -0
- package/schema/fixtures/policy/invalid/skew-tolerance-compound.json +6 -0
- package/schema/fixtures/policy/invalid/unknown-autonomy.json +7 -0
- package/schema/fixtures/policy/invalid/unknown-class-rule-key.json +6 -0
- package/schema/fixtures/policy/invalid/unknown-top-level-key.json +7 -0
- package/schema/fixtures/policy/invalid/vault-passphrase-env-empty.json +6 -0
- package/schema/fixtures/policy/invalid/vault-passphrase-literal.json +6 -0
- package/schema/fixtures/policy/invalid/version-not-string.json +4 -0
- package/schema/fixtures/policy/valid/canonical.json +47 -0
- package/schema/fixtures/policy/valid/checkpoint-keys.json +18 -0
- package/schema/fixtures/policy/valid/class-approvers-limits.json +25 -0
- package/schema/fixtures/policy/valid/class-retro-rate.json +17 -0
- package/schema/fixtures/policy/valid/global-budgets.json +19 -0
- package/schema/fixtures/policy/valid/human-only.json +9 -0
- package/schema/fixtures/policy/valid/minimal.json +6 -0
- package/schema/fixtures/policy/valid/protected-paths.json +10 -0
- package/schema/fixtures/policy/valid/record-namespace.json +13 -0
- package/schema/fixtures/policy/valid/request-volume-limits.json +26 -0
- package/schema/fixtures/policy/valid/retention-and-sampling-secret.json +16 -0
- package/schema/fixtures/policy/valid/skew-tolerance.json +15 -0
- package/schema/fixtures/policy/valid/vault-passphrase-env.json +14 -0
- package/schema/fixtures/policy/valid/wildcards.json +15 -0
- package/schema/fixtures/policy-md/invalid/alias-bomb.md +15 -0
- package/schema/fixtures/policy-md/invalid/no-fence.md +7 -0
- package/schema/fixtures/policy-md/invalid/protected-route-not-a-subclass.md +16 -0
- package/schema/fixtures/policy-md/invalid/schema-invalid-autonomy.md +16 -0
- package/schema/fixtures/policy-md/invalid/schema-invalid-read-proof.md +17 -0
- package/schema/fixtures/policy-md/invalid/two-fences.md +19 -0
- package/schema/fixtures/policy-md/invalid/unclosed-fence.md +11 -0
- package/schema/fixtures/policy-md/invalid/wrong-info-string.md +11 -0
- package/schema/fixtures/policy-md/invalid/yaml-syntax-error.md +13 -0
- package/schema/fixtures/policy-md/precedence/both/APPROVAL.md +7 -0
- package/schema/fixtures/policy-md/precedence/both/APPROVALS.md +7 -0
- package/schema/fixtures/policy-md/precedence/fallback-only/APPROVALS.md +7 -0
- package/schema/fixtures/policy-md/valid/canonical.md +50 -0
- package/schema/fixtures/policy-md/valid/daemon-read-proof.md +18 -0
- package/schema/fixtures/policy-md/valid/minimal.md +3 -0
- package/schema/fixtures/policy-md/valid/prose-lookalikes.md +54 -0
- package/schema/fixtures/policy-md/valid/routed-protected-paths.md +49 -0
- package/schema/fixtures/policy-md/valid/with-values.md +79 -0
- package/schema/fixtures/sample-record/invalid/bad-date-time.json +4 -0
- package/schema/fixtures/sample-record/invalid/missing-required-field.json +3 -0
- package/schema/fixtures/sample-record/invalid/unknown-top-level-field.json +5 -0
- package/schema/fixtures/sample-record/invalid/wrong-type.json +4 -0
- package/schema/fixtures/sample-record/valid/minimal.json +4 -0
- package/schema/fixtures/sample-record/valid/with-note.json +5 -0
- package/schema/fixtures/values/invalid/class-shaped.json +9 -0
- package/schema/fixtures/values/invalid/duplicate-entry.json +4 -0
- package/schema/fixtures/values/invalid/non-string-item.json +4 -0
- package/schema/fixtures/values/invalid/over-cap.json +26 -0
- package/schema/fixtures/values/invalid/unknown-key.json +5 -0
- package/schema/fixtures/values/invalid/version-string.json +1 -0
- package/schema/fixtures/values/valid/empty-lists.json +7 -0
- package/schema/fixtures/values/valid/full.json +20 -0
- package/schema/fixtures/values/valid/minimal.json +1 -0
- package/schema/fixtures/values-md/invalid/schema-invalid.md +62 -0
- package/schema/fixtures/values-md/invalid/two-blocks.md +69 -0
- package/schema/fixtures/values-md/invalid/unterminated.md +61 -0
- package/schema/fixtures/values-md/invalid/yaml-error.md +63 -0
- package/schema/fixtures/values-md/valid/absent.md +50 -0
- package/schema/fixtures/values-md/valid/with-values.md +79 -0
- package/schema/policy.schema.json +501 -0
- package/schema/sample-record.schema.json +26 -0
- package/schema/values.schema.json +55 -0
- package/templates/codex/README.md +9 -0
|
@@ -0,0 +1,1811 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://approval.md/schema/event.schema.json",
|
|
4
|
+
"$comment": "CC0 1.0. See schema/LICENSE.",
|
|
5
|
+
"title": "Event log record",
|
|
6
|
+
"description": "One line of `.approval/log/events.jsonl` (SPEC.md §8). The log is append-only and is the truth; projections are caches. Every record MUST validate against this schema before append (SPEC.md §8: validation at the write boundary is itself a control). Structural shape only: hash-chain linkage, first-record semantics, and monotonic `seq` are verified by the chain verifier (M1), not expressible here.",
|
|
7
|
+
"type": "object",
|
|
8
|
+
"additionalProperties": false,
|
|
9
|
+
"required": [
|
|
10
|
+
"seq",
|
|
11
|
+
"ts",
|
|
12
|
+
"event",
|
|
13
|
+
"actor",
|
|
14
|
+
"alg",
|
|
15
|
+
"hash",
|
|
16
|
+
"prev"
|
|
17
|
+
],
|
|
18
|
+
"properties": {
|
|
19
|
+
"seq": {
|
|
20
|
+
"type": "integer",
|
|
21
|
+
"minimum": 1,
|
|
22
|
+
"description": "SPEC.md §8: 1-based position of this record in the append-only log. Strict monotonic succession across records is enforced by the chain verifier (M1); this schema constrains a single record only."
|
|
23
|
+
},
|
|
24
|
+
"ts": {
|
|
25
|
+
"type": "string",
|
|
26
|
+
"format": "date-time",
|
|
27
|
+
"description": "SPEC.md §8: RFC 3339 timestamp at which the event was appended (the example record uses `2026-08-04T09:14:02Z`)."
|
|
28
|
+
},
|
|
29
|
+
"event": {
|
|
30
|
+
"type": "string",
|
|
31
|
+
"enum": [
|
|
32
|
+
"task.registered",
|
|
33
|
+
"route.proposed",
|
|
34
|
+
"route.accepted",
|
|
35
|
+
"approval.requested",
|
|
36
|
+
"approval.granted",
|
|
37
|
+
"approval.rejected",
|
|
38
|
+
"approval.expired",
|
|
39
|
+
"approval.revoked",
|
|
40
|
+
"approval.withdrawn",
|
|
41
|
+
"execution.started",
|
|
42
|
+
"execution.completed",
|
|
43
|
+
"execution.failed",
|
|
44
|
+
"execution.indeterminate",
|
|
45
|
+
"execution.reconciled",
|
|
46
|
+
"budget.exceeded",
|
|
47
|
+
"policy.updated",
|
|
48
|
+
"policy.proposed",
|
|
49
|
+
"policy.declined",
|
|
50
|
+
"envelope.drift",
|
|
51
|
+
"audit.sampled",
|
|
52
|
+
"audit.reviewed",
|
|
53
|
+
"audit.dark_session",
|
|
54
|
+
"audit.decision_refused",
|
|
55
|
+
"reconciliation.required",
|
|
56
|
+
"reconciliation.satisfied",
|
|
57
|
+
"payload.pruned",
|
|
58
|
+
"gate.opened",
|
|
59
|
+
"gate.closed",
|
|
60
|
+
"gate.bypassed",
|
|
61
|
+
"gate.organ.attested",
|
|
62
|
+
"log.checkpoint"
|
|
63
|
+
],
|
|
64
|
+
"description": "SPEC.md §8 'Event types (v0.1)': the closed set of thirty-one 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, and `gate.organ.attested` (APRV-272) the fifteenth: 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
|
+
},
|
|
66
|
+
"actor": {
|
|
67
|
+
"type": "string",
|
|
68
|
+
"pattern": "^(human|agent|system):.+",
|
|
69
|
+
"description": "SPEC.md §8: who caused the event, as `<kind>:<id>` (the example record uses `human:carter`). Three kinds: `human:` for a person acting through a channel or the CLI, `agent:` for an agent-originated event, and `system:` for events the daemon originates with no external prompt (for example `approval.expired`, which fires on TTL lapse rather than on any actor's action). Approval decisions are restricted to `human:` — see the per-type rules below."
|
|
70
|
+
},
|
|
71
|
+
"alg": {
|
|
72
|
+
"const": "sha256/jcs",
|
|
73
|
+
"description": "SPEC.md §8 (hash-scheme amendment): explicit hash-scheme identifier. Version 0.1 defines exactly one value, `sha256/jcs` — SHA-256 over the RFC 8785 (JCS) canonical serialization of the record with `prev` included. Missing or unrecognized `alg` MUST be rejected; differing `alg` values MAY coexist in one log so a scheme change is a migration, never a schism."
|
|
74
|
+
},
|
|
75
|
+
"hash": {
|
|
76
|
+
"type": "string",
|
|
77
|
+
"pattern": "^[a-f0-9]{64}$",
|
|
78
|
+
"description": "SPEC.md §8: this record's hash under `alg` — SHA-256 over the record's JCS canonical serialization with `prev` included, lowercase hex. That the digest actually matches the record contents is checked by `approval log verify` (M1); this schema constrains only its form."
|
|
79
|
+
},
|
|
80
|
+
"prev": {
|
|
81
|
+
"description": "SPEC.md §8: the previous record's `hash`, chaining the log so that `approval log verify` detects any mutation or truncation. `null` is legal only for the first record in a log; a schema can express the type union but not the position, so first-record semantics are enforced by the chain verifier (M1).",
|
|
82
|
+
"oneOf": [
|
|
83
|
+
{
|
|
84
|
+
"type": "string",
|
|
85
|
+
"pattern": "^[a-f0-9]{64}$"
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
"type": "null"
|
|
89
|
+
}
|
|
90
|
+
]
|
|
91
|
+
},
|
|
92
|
+
"task": {
|
|
93
|
+
"type": "string",
|
|
94
|
+
"minLength": 1,
|
|
95
|
+
"description": "SPEC.md §8: the Backlog.md task id the event concerns (the example record uses `task-042`). Optional in the base shape and required per event type below."
|
|
96
|
+
},
|
|
97
|
+
"action_key": {
|
|
98
|
+
"type": "string",
|
|
99
|
+
"minLength": 1,
|
|
100
|
+
"description": "SPEC.md §8: the action's idempotency key (the example record uses `task-042:chaser:2026-08-04`), identifying the specific side effect being approved or executed. Optional in the base shape and required per event type below."
|
|
101
|
+
},
|
|
102
|
+
"channel": {
|
|
103
|
+
"type": "string",
|
|
104
|
+
"minLength": 1,
|
|
105
|
+
"description": "SPEC.md §8: the channel through which the event reached the runtime (the example record uses `telegram`). Optional: CLI- and daemon-originated events need not name one."
|
|
106
|
+
},
|
|
107
|
+
"payload": {
|
|
108
|
+
"type": "object",
|
|
109
|
+
"description": "SPEC.md §8: event-type-specific detail (the example record carries an approver's note). Shape is deliberately open at v0.1; per-type payload schemas are a later, separate task. Four names are constrained regardless of event type. Two are monetary (APRV-121), because they are the fields the budgets contract reads and the whole payload is hashed material: `est_cost_usd`, and `budget.max_cost_usd` as copied onto `task.registered`. Two are harness provenance (APRV-227): `harness` and `harness_version`, which the hook copies onto the `task.registered` and `gate.bypassed` records it writes. The rest of the payload stays open.",
|
|
110
|
+
"properties": {
|
|
111
|
+
"harness": {
|
|
112
|
+
"type": "string",
|
|
113
|
+
"enum": [
|
|
114
|
+
"claude-code",
|
|
115
|
+
"cursor",
|
|
116
|
+
"codex"
|
|
117
|
+
],
|
|
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."
|
|
119
|
+
},
|
|
120
|
+
"harness_version": {
|
|
121
|
+
"type": "string",
|
|
122
|
+
"minLength": 1,
|
|
123
|
+
"maxLength": 64,
|
|
124
|
+
"pattern": "^[\\u0020-\\u007e]+$",
|
|
125
|
+
"description": "SPEC.md §10.1 (amended, APRV-227): the version of the harness binary that issued this record, taken from the hook event where the harness supplies one and otherwise from `<binary> --version`, normalized to its first line. SELF-REPORTED, and informational only: nothing in the runtime reads it back as an input to a verdict, a floor, a budget, a streak or a sampling draw, so it satisfies SPEC.md §11.1 invariant 4 by having no path along which it could reduce scrutiny — its single reader is a doctor row that can only ADD a red line. Its SHAPE is constrained here because the value is the output of a third-party process being written to an append-only log: one line, printable ASCII, capped at 64 characters, so a banner or a stack trace (and whatever a stack trace quotes) cannot arrive through it. §11.1 invariant 3 has no exception for provenance. OPTIONAL and additive."
|
|
126
|
+
},
|
|
127
|
+
"est_cost_usd": {
|
|
128
|
+
"$ref": "#/$defs/usd_amount",
|
|
129
|
+
"description": "SPEC.md §5.2 budgets contract (amended, APRV-121): the authorization's declared cost in USD as a decimal string. Recorded on `approval.requested`, `approval.granted`, `execution.started` and `budget.exceeded`; a JSON number is the pre-APRV-121 form and is accepted on read, never on write."
|
|
130
|
+
},
|
|
131
|
+
"live_draw": {
|
|
132
|
+
"type": "object",
|
|
133
|
+
"description": "SPEC.md §6 (amended, APRV-208): how a `supervised-live` action's draw was made, present on `approval.requested` ONLY when the draw was delegated to the daemon over the local draw socket because the deciding process held no sampling secret. Absent for an in-process draw, which is why a sampled request made where the secret resolves stays byte-for-byte identical to a manual one (APRV-127). A delegated verdict is an assertion by another process, so it is recorded with the MAC that makes it checkable — HMAC-SHA-256 over a domain separator, the canonical question and the verdict, keyed by the sampling secret, reconstructible from the record's own `action_key`, `payload_hash` and `policy_sha256` plus this object's `live_rate` — or, when no usable answer came back, with the distinct machine-readable reason the action gated instead. It never carries the secret, the selection value, or any clock: a self-reported field that reduces scrutiny must arrive with a proof its author could not forge (§11).",
|
|
134
|
+
"required": ["v", "source", "reason", "live_rate"],
|
|
135
|
+
"properties": {
|
|
136
|
+
"v": {
|
|
137
|
+
"type": "integer",
|
|
138
|
+
"const": 1,
|
|
139
|
+
"description": "The draw protocol version the answer was made under. A reader that does not know this version must not treat the record as an evidenced draw."
|
|
140
|
+
},
|
|
141
|
+
"source": {
|
|
142
|
+
"type": "string",
|
|
143
|
+
"enum": ["daemon", "unavailable"],
|
|
144
|
+
"description": "`daemon`: a MAC'd answer from the daemon holding the secret. `unavailable`: no usable answer, so the action gated; `reason` says which refusal."
|
|
145
|
+
},
|
|
146
|
+
"reason": {
|
|
147
|
+
"type": "string",
|
|
148
|
+
"enum": ["selected", "draw-daemon-absent", "draw-daemon-stale", "draw-answer-invalid"],
|
|
149
|
+
"description": "`selected`: the daemon's draw put this action in the live fraction. The three refusals are distinct because an operator's action differs — no socket at all (start the daemon), a socket that will not answer or whose daemon is gone (it is wedged or was killed), and an answer that could not be matched to the question asked (something answered and it was not this operator's daemon)."
|
|
150
|
+
},
|
|
151
|
+
"live_rate": {
|
|
152
|
+
"type": "number",
|
|
153
|
+
"exclusiveMinimum": 0,
|
|
154
|
+
"maximum": 1,
|
|
155
|
+
"description": "The rate the draw was made at, as the DAEMON resolved it from its own policy. Recorded so a verifier can reconstruct the question without re-resolving a policy that may have been amended since."
|
|
156
|
+
},
|
|
157
|
+
"selected": {
|
|
158
|
+
"type": "boolean",
|
|
159
|
+
"description": "The daemon's verdict, present only with a `mac` to check it against. Always `true` on a recorded request: an unselected action appends no `approval.requested` at all (§6.3)."
|
|
160
|
+
},
|
|
161
|
+
"mac": {
|
|
162
|
+
"type": "string",
|
|
163
|
+
"pattern": "^[a-f0-9]{64}$",
|
|
164
|
+
"description": "The MAC over the question and the verdict. A digest under a key no party to this record holds, which is what makes it evidence rather than a claim."
|
|
165
|
+
},
|
|
166
|
+
"daemon_pid": {
|
|
167
|
+
"type": "integer",
|
|
168
|
+
"minimum": 1,
|
|
169
|
+
"description": "The process that answered, for an operator correlating a draw with a daemon's own logs. Carries no authority: a pid is not a proof, and the MAC is."
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
},
|
|
173
|
+
"budget": {
|
|
174
|
+
"type": "object",
|
|
175
|
+
"description": "SPEC.md §6.2 `budget`, copied onto `task.registered` at registration so the task's own cap is read from the log rather than from a file that may have been edited since.",
|
|
176
|
+
"properties": {
|
|
177
|
+
"max_cost_usd": {
|
|
178
|
+
"$ref": "#/$defs/usd_amount",
|
|
179
|
+
"description": "SPEC.md §6.2 (amended, APRV-121): the task's lifetime cap in USD as a decimal string."
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
},
|
|
186
|
+
"$defs": {
|
|
187
|
+
"usd_amount_string": {
|
|
188
|
+
"type": "string",
|
|
189
|
+
"pattern": "^(0|[1-9][0-9]*)(\\.[0-9]{0,5}[1-9])?$",
|
|
190
|
+
"description": "SPEC.md §8 (amended, APRV-121): a USD amount as a canonical decimal string. No sign, no exponent, no leading zeros, at most six fractional digits, and no trailing zeros beyond the last significant digit — one value has exactly one spelling, so two records carrying the same amount always hash the same way, in any language."
|
|
191
|
+
},
|
|
192
|
+
"usd_amount": {
|
|
193
|
+
"$ref": "#/$defs/usd_amount_string",
|
|
194
|
+
"description": "The write-boundary form of a USD amount: the decimal string, and nothing else. `validate(..., { mode: \"historical\" })` replaces this definition with `usd_amount_historical` so that records written before APRV-121 keep validating and verifying; nothing else in the runtime relaxes it."
|
|
195
|
+
},
|
|
196
|
+
"usd_amount_historical": {
|
|
197
|
+
"anyOf": [
|
|
198
|
+
{ "$ref": "#/$defs/usd_amount_string" },
|
|
199
|
+
{ "type": "number", "minimum": 0 }
|
|
200
|
+
],
|
|
201
|
+
"description": "The read form: the decimal string, or the pre-APRV-121 JSON number. The log is append-only, so a verifier must accept every shape the write boundary ever admitted. Used only in historical validation mode."
|
|
202
|
+
}
|
|
203
|
+
},
|
|
204
|
+
"allOf": [
|
|
205
|
+
{
|
|
206
|
+
"$comment": "SPEC.md §8: approval lifecycle events name both the task and the specific action being decided.",
|
|
207
|
+
"if": {
|
|
208
|
+
"type": "object",
|
|
209
|
+
"properties": {
|
|
210
|
+
"event": {
|
|
211
|
+
"enum": [
|
|
212
|
+
"approval.requested",
|
|
213
|
+
"approval.granted",
|
|
214
|
+
"approval.rejected",
|
|
215
|
+
"approval.expired",
|
|
216
|
+
"approval.revoked",
|
|
217
|
+
"approval.withdrawn"
|
|
218
|
+
]
|
|
219
|
+
}
|
|
220
|
+
},
|
|
221
|
+
"required": [
|
|
222
|
+
"event"
|
|
223
|
+
]
|
|
224
|
+
},
|
|
225
|
+
"then": {
|
|
226
|
+
"type": "object",
|
|
227
|
+
"required": [
|
|
228
|
+
"task",
|
|
229
|
+
"action_key"
|
|
230
|
+
],
|
|
231
|
+
"properties": {
|
|
232
|
+
"task": {
|
|
233
|
+
"$ref": "#/properties/task"
|
|
234
|
+
},
|
|
235
|
+
"action_key": {
|
|
236
|
+
"$ref": "#/properties/action_key"
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
"$comment": "SPEC.md §10.1: granting and rejecting are human decisions made through the CLI or a channel. An agent- or system-originated grant would be the gate approving itself, so the actor MUST carry the `human:` prefix.",
|
|
243
|
+
"if": {
|
|
244
|
+
"type": "object",
|
|
245
|
+
"properties": {
|
|
246
|
+
"event": {
|
|
247
|
+
"enum": [
|
|
248
|
+
"approval.granted",
|
|
249
|
+
"approval.rejected"
|
|
250
|
+
]
|
|
251
|
+
}
|
|
252
|
+
},
|
|
253
|
+
"required": [
|
|
254
|
+
"event"
|
|
255
|
+
]
|
|
256
|
+
},
|
|
257
|
+
"then": {
|
|
258
|
+
"type": "object",
|
|
259
|
+
"properties": {
|
|
260
|
+
"actor": {
|
|
261
|
+
"type": "string",
|
|
262
|
+
"pattern": "^human:"
|
|
263
|
+
}
|
|
264
|
+
},
|
|
265
|
+
"required": [
|
|
266
|
+
"actor"
|
|
267
|
+
]
|
|
268
|
+
}
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
"$comment": "SPEC.md §5.2/§8 (APRV-56): reviewing a sampled action is a human decision, made through the CLI, and the runtime already refuses any other reviewer. A `system:` or `agent:` reviewer would be the party under oversight signing off on its own sample, which is the one reading of the record that must never be possible, so the actor MUST carry the `human:` prefix.",
|
|
272
|
+
"if": {
|
|
273
|
+
"type": "object",
|
|
274
|
+
"properties": {
|
|
275
|
+
"event": {
|
|
276
|
+
"const": "audit.reviewed"
|
|
277
|
+
}
|
|
278
|
+
},
|
|
279
|
+
"required": [
|
|
280
|
+
"event"
|
|
281
|
+
]
|
|
282
|
+
},
|
|
283
|
+
"then": {
|
|
284
|
+
"type": "object",
|
|
285
|
+
"properties": {
|
|
286
|
+
"actor": {
|
|
287
|
+
"type": "string",
|
|
288
|
+
"pattern": "^human:"
|
|
289
|
+
},
|
|
290
|
+
"payload": {
|
|
291
|
+
"type": "object",
|
|
292
|
+
"properties": {
|
|
293
|
+
"verdict": {
|
|
294
|
+
"type": "string",
|
|
295
|
+
"enum": [
|
|
296
|
+
"ok",
|
|
297
|
+
"denied"
|
|
298
|
+
],
|
|
299
|
+
"description": "SPEC.md §5.2 (amended, APRV-127): what the reviewer concluded. `ok`: the action should have happened — the spelling the v0.1 fixtures already used, kept rather than renamed, because a second word for a verdict already in logs is the drift this vocabulary exists to avoid. `denied`: it should not have, and the runtime appends a `reconciliation.required` naming the obligation the denial creates. Optional and additive — a review written before the split carries none and reads as the observation it was, which is why the ABSENCE of this field is never read as a denial."
|
|
300
|
+
},
|
|
301
|
+
"reaction": {
|
|
302
|
+
"type": "string",
|
|
303
|
+
"enum": [
|
|
304
|
+
"disliked",
|
|
305
|
+
"indifferent",
|
|
306
|
+
"liked",
|
|
307
|
+
"loved"
|
|
308
|
+
],
|
|
309
|
+
"description": "SPEC.md §5.2 (amended, APRV-237): what the human thought of the action, graded. `verdict` remains the ENFORCEMENT field and is the only one any enforcement path reads; `reaction` is guidance travelling the human-to-agent direction and no routing, matching, sampling, budgeting or token decision consults it (SPEC.md §11.1 invariant 10). Optional and additive: a review written before the field existed still validates and still verifies, and the ABSENCE of the field is absence — it is never read as `indifferent`, which is a thing a person had to actually say. The four words are the whole vocabulary; a fifth is refused here rather than left to accumulate synonyms across surfaces."
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
},
|
|
314
|
+
"required": [
|
|
315
|
+
"actor"
|
|
316
|
+
]
|
|
317
|
+
}
|
|
318
|
+
},
|
|
319
|
+
{
|
|
320
|
+
"$comment": "SPEC.md §5.2 (amended, APRV-237): a grant may carry the same graded `reaction` a review may, because a human answering the gate is already saying what they think of the action and this is where they can say it in one word rather than in prose nothing can read back. The constraint is the same four-word enum as on `audit.reviewed`, written here rather than shared by a `$ref` so the two events' descriptions can each say what the field means where it appears. Optional and additive, and never enforcement: `verdict` on a review and the grant record itself are what any enforcement path reads; nothing reads this (SPEC.md §11.1 invariant 10).",
|
|
321
|
+
"if": {
|
|
322
|
+
"type": "object",
|
|
323
|
+
"properties": {
|
|
324
|
+
"event": {
|
|
325
|
+
"const": "approval.granted"
|
|
326
|
+
}
|
|
327
|
+
},
|
|
328
|
+
"required": [
|
|
329
|
+
"event"
|
|
330
|
+
]
|
|
331
|
+
},
|
|
332
|
+
"then": {
|
|
333
|
+
"type": "object",
|
|
334
|
+
"properties": {
|
|
335
|
+
"payload": {
|
|
336
|
+
"type": "object",
|
|
337
|
+
"properties": {
|
|
338
|
+
"reaction": {
|
|
339
|
+
"type": "string",
|
|
340
|
+
"enum": [
|
|
341
|
+
"disliked",
|
|
342
|
+
"indifferent",
|
|
343
|
+
"liked",
|
|
344
|
+
"loved"
|
|
345
|
+
],
|
|
346
|
+
"description": "SPEC.md §5.2 (amended, APRV-237): what the human thought of the action they were approving, graded. The grant is the enforcement fact; this is the human-to-agent signal riding alongside it, and no enforcement path reads it (SPEC.md §11.1 invariant 10). Optional and additive, and its ABSENCE is absence rather than `indifferent`. The four words are the whole vocabulary here and on `audit.reviewed`; one field name, one enum, two surfaces."
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
},
|
|
353
|
+
{
|
|
354
|
+
"$comment": "SPEC.md §5.2 (amended, APRV-237): the two extreme grades require a note. `loved` and `disliked` are the reactions an agent is most likely to act on and least able to interpret alone — \"disliked\" with no words says something went wrong and nothing about what — so the grade that carries the most weight must carry a sentence with it. This is expressible in a schema (one record, two of its own fields) and so it is expressed HERE, at the write boundary SPEC.md §8 requires every event to pass, rather than left to the verbs alone. The verbs refuse it too, as `note-required`, because a refusal a person sees on their phone must name itself; the schema is what makes the rule true of every record regardless of which surface wrote it. `liked` and `indifferent` need no note: they are the ordinary readings, and demanding prose for them would turn a one-tap signal into a form and get the signal switched off.",
|
|
355
|
+
"if": {
|
|
356
|
+
"type": "object",
|
|
357
|
+
"properties": {
|
|
358
|
+
"event": {
|
|
359
|
+
"enum": [
|
|
360
|
+
"audit.reviewed",
|
|
361
|
+
"approval.granted"
|
|
362
|
+
]
|
|
363
|
+
},
|
|
364
|
+
"payload": {
|
|
365
|
+
"type": "object",
|
|
366
|
+
"properties": {
|
|
367
|
+
"reaction": {
|
|
368
|
+
"enum": [
|
|
369
|
+
"disliked",
|
|
370
|
+
"loved"
|
|
371
|
+
]
|
|
372
|
+
}
|
|
373
|
+
},
|
|
374
|
+
"required": [
|
|
375
|
+
"reaction"
|
|
376
|
+
]
|
|
377
|
+
}
|
|
378
|
+
},
|
|
379
|
+
"required": [
|
|
380
|
+
"event",
|
|
381
|
+
"payload"
|
|
382
|
+
]
|
|
383
|
+
},
|
|
384
|
+
"then": {
|
|
385
|
+
"type": "object",
|
|
386
|
+
"properties": {
|
|
387
|
+
"payload": {
|
|
388
|
+
"type": "object",
|
|
389
|
+
"required": [
|
|
390
|
+
"note"
|
|
391
|
+
],
|
|
392
|
+
"properties": {
|
|
393
|
+
"note": {
|
|
394
|
+
"type": "string",
|
|
395
|
+
"minLength": 1,
|
|
396
|
+
"description": "SPEC.md §5.2 (amended, APRV-237): the human's own words, REQUIRED when `reaction` is `loved` or `disliked`. Blank is not a note: an empty string would satisfy a presence check while telling an auditor and an agent exactly as much as the absent field would, so the minimum length is part of the rule."
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
},
|
|
403
|
+
{
|
|
404
|
+
"$comment": "SPEC.md §6.3/§8 (APRV-106): a withdrawal is the requester retracting their own pending request, so the actor is the party that opened it — `agent:` for an agent-originated request, `human:` for a person's. Never `system:`: the runtime already has a way to end a request it was not asked to end, and that is `approval.expired` on TTL lapse. A `system:` withdrawal would let the runtime cancel a question a human was about to answer, with no clock to justify it. That the actor is the SAME party that appended the matching `approval.requested` is not expressible here (a schema sees one record), and is enforced by the gate as `not-requester`; what the schema can rule out is the actor kind that must never appear. `reason` is required and closed: a withdrawal that does not say why is a decision nobody can audit, and the three values are the three things that actually happen — the requester's wait elapsed, the requester changed its mind, or a newer request replaced this one.",
|
|
405
|
+
"$comment": "SPEC.md §6.3/§8 (APRV-106, amended APRV-235): a withdrawal is normally the requester retracting their own pending request, so the actor is the party that opened it — `agent:` for an agent-originated request, `human:` for a person's. That the actor is the SAME party that appended the matching `approval.requested` is not expressible here (a schema sees one record), and is enforced by the gate as `not-requester`. `reason` is required and closed: a withdrawal that does not say why is a decision nobody can audit, and the first three values are the three things that happen to a requester — its wait elapsed, it changed its mind, or a newer request replaced this one.\n\nAPRV-235 admits the runtime as a fourth author, on ONE reason and no other, and the cross-rule below is bidirectional: `system:` requires `policy-drift`, and `policy-drift` requires `system:`. The original ban existed because a `system:` withdrawal on the requester's reasons would let the runtime cancel a question a human was about to answer with no clock to justify it. `policy-drift` is the case where the justification is not a clock but is every bit as objective, and it is IN THE RECORD: the policy hash the `approval.requested` pinned differs from the hash attested now, so the rules that routed the question are gone and the gate already refuses every decision on it (`policy-drift` in `core/gate.ts`). The request is void by the gate's own verdict; leaving it pending offers a human a tap that cannot be honoured. A reader checks the justification the same way the runtime derived it, from two hashes the log carries. The requester's three reasons stay closed to the runtime, and the runtime's one reason stays closed to the requester — an agent cannot spell `policy-drift`, so it cannot dress a cancellation as the gate's verdict.",
|
|
406
|
+
"if": {
|
|
407
|
+
"type": "object",
|
|
408
|
+
"properties": {
|
|
409
|
+
"event": {
|
|
410
|
+
"const": "approval.withdrawn"
|
|
411
|
+
}
|
|
412
|
+
},
|
|
413
|
+
"required": [
|
|
414
|
+
"event"
|
|
415
|
+
]
|
|
416
|
+
},
|
|
417
|
+
"then": {
|
|
418
|
+
"type": "object",
|
|
419
|
+
"required": [
|
|
420
|
+
"actor",
|
|
421
|
+
"payload"
|
|
422
|
+
],
|
|
423
|
+
"properties": {
|
|
424
|
+
"actor": {
|
|
425
|
+
"type": "string",
|
|
426
|
+
"pattern": "^(human|agent|system):",
|
|
427
|
+
"description": "SPEC.md §6.3: the requester, or (APRV-235) `system:` on the one runtime reason `policy-drift`. The bidirectional cross-rule under `allOf` is what keeps those two sets from mixing."
|
|
428
|
+
},
|
|
429
|
+
"payload": {
|
|
430
|
+
"type": "object",
|
|
431
|
+
"required": [
|
|
432
|
+
"action_key",
|
|
433
|
+
"reason"
|
|
434
|
+
],
|
|
435
|
+
"properties": {
|
|
436
|
+
"action_key": {
|
|
437
|
+
"type": "string",
|
|
438
|
+
"minLength": 1,
|
|
439
|
+
"description": "The withdrawn request's idempotency key, restated in the payload so the reason and the key it applies to travel together for a reader who has only the payload."
|
|
440
|
+
},
|
|
441
|
+
"reason": {
|
|
442
|
+
"type": "string",
|
|
443
|
+
"enum": [
|
|
444
|
+
"timeout",
|
|
445
|
+
"cancelled",
|
|
446
|
+
"superseded",
|
|
447
|
+
"policy-drift"
|
|
448
|
+
],
|
|
449
|
+
"description": "Why the request ended without a decision. The first three are the REQUESTER's: `timeout`, the requester stopped waiting and can no longer consume a decision; `cancelled`, the requester no longer wants the action; `superseded`, a newer request covers the same effect. `policy-drift` (APRV-235) is the RUNTIME's and is `system:`-only: the policy attested now is not the policy this request pinned, so the gate refuses every decision on it and the request is void. The two hashes belong in `note`, where a reader can check the verdict against the log rather than take it."
|
|
450
|
+
},
|
|
451
|
+
"note": {
|
|
452
|
+
"type": "string",
|
|
453
|
+
"description": "The requester's free-text elaboration. Claimed, never a control. On a `policy-drift` withdrawal it is the runtime's own, and names both policy hashes."
|
|
454
|
+
},
|
|
455
|
+
"refused_seq": {
|
|
456
|
+
"type": "integer",
|
|
457
|
+
"minimum": 1,
|
|
458
|
+
"description": "APRV-235, `policy-drift` only: the `seq` of the `audit.decision_refused` record that voided this request. The audit record is appended FIRST and this names it, so the pair is readable in one direction and an interrupted write leaves the explanation without the withdrawal rather than the withdrawal without the explanation."
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
},
|
|
463
|
+
"allOf": [
|
|
464
|
+
{
|
|
465
|
+
"$comment": "`policy-drift` is the runtime's reason and only the runtime's: a requester that could spell it would be dressing its own cancellation as the gate's verdict about the policy.",
|
|
466
|
+
"if": {
|
|
467
|
+
"type": "object",
|
|
468
|
+
"required": [
|
|
469
|
+
"payload"
|
|
470
|
+
],
|
|
471
|
+
"properties": {
|
|
472
|
+
"payload": {
|
|
473
|
+
"type": "object",
|
|
474
|
+
"required": [
|
|
475
|
+
"reason"
|
|
476
|
+
],
|
|
477
|
+
"properties": {
|
|
478
|
+
"reason": {
|
|
479
|
+
"const": "policy-drift"
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
},
|
|
485
|
+
"then": {
|
|
486
|
+
"type": "object",
|
|
487
|
+
"properties": {
|
|
488
|
+
"actor": {
|
|
489
|
+
"type": "string",
|
|
490
|
+
"pattern": "^system:"
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
},
|
|
495
|
+
{
|
|
496
|
+
"$comment": "And the converse: the runtime withdraws for drift or not at all. `approval.expired` remains its only other way to end a request nobody answered, and the pre-APRV-235 ban on a `system:` withdrawal for a requester's reason is unchanged.",
|
|
497
|
+
"if": {
|
|
498
|
+
"type": "object",
|
|
499
|
+
"required": [
|
|
500
|
+
"actor"
|
|
501
|
+
],
|
|
502
|
+
"properties": {
|
|
503
|
+
"actor": {
|
|
504
|
+
"type": "string",
|
|
505
|
+
"pattern": "^system:"
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
},
|
|
509
|
+
"then": {
|
|
510
|
+
"type": "object",
|
|
511
|
+
"properties": {
|
|
512
|
+
"payload": {
|
|
513
|
+
"type": "object",
|
|
514
|
+
"properties": {
|
|
515
|
+
"reason": {
|
|
516
|
+
"const": "policy-drift"
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
]
|
|
524
|
+
}
|
|
525
|
+
},
|
|
526
|
+
{
|
|
527
|
+
"$comment": "SPEC.md §10.1/§10.3/§11 (amended, APRV-109): an attestation prompt. An agent that has prepared a policy edit appends this to ask a human to attest the bytes; a channel renders it like a manual action prompt and the tap appends `policy.updated`. The actor is the party that prepared the edit, so `agent:` is expected and `human:` is allowed (a person can propose too); `system:` is refused, because the runtime has no policy edit of its own to ask about and a runtime-originated proposal would be the gate writing its own rules. Every payload field the schema requires is COMPUTED by the runtime from the policy bytes — the SHA-256, the semantic diff summary and the load advisory — and the proposing caller has no parameter for any of them, so a proposal cannot show an approver one story and attest a different file. `note` and `wait_until` are the proposer's own and are rendered claimed. A diff too large for a channel to show whole is refused (`diff-too-large`) before this record is written, so `diff.lines` is what a channel prints and never a truncation of it.",
|
|
528
|
+
"if": {
|
|
529
|
+
"type": "object",
|
|
530
|
+
"properties": {
|
|
531
|
+
"event": {
|
|
532
|
+
"const": "policy.proposed"
|
|
533
|
+
}
|
|
534
|
+
},
|
|
535
|
+
"required": [
|
|
536
|
+
"event"
|
|
537
|
+
]
|
|
538
|
+
},
|
|
539
|
+
"then": {
|
|
540
|
+
"type": "object",
|
|
541
|
+
"required": [
|
|
542
|
+
"actor",
|
|
543
|
+
"payload"
|
|
544
|
+
],
|
|
545
|
+
"properties": {
|
|
546
|
+
"actor": {
|
|
547
|
+
"type": "string",
|
|
548
|
+
"pattern": "^(human|agent):",
|
|
549
|
+
"description": "The party that prepared the policy edit. `system:` is refused: the runtime proposes no policy of its own."
|
|
550
|
+
},
|
|
551
|
+
"payload": {
|
|
552
|
+
"type": "object",
|
|
553
|
+
"required": [
|
|
554
|
+
"policy_path",
|
|
555
|
+
"sha256",
|
|
556
|
+
"diff",
|
|
557
|
+
"load"
|
|
558
|
+
],
|
|
559
|
+
"properties": {
|
|
560
|
+
"policy_path": {
|
|
561
|
+
"type": "string",
|
|
562
|
+
"minLength": 1,
|
|
563
|
+
"description": "The policy file's BASENAME, as `policy.updated` records it: the log is meant to be copied and read on other machines, and an absolute path would leak the writer's home directory while saying nothing a reader can use."
|
|
564
|
+
},
|
|
565
|
+
"sha256": {
|
|
566
|
+
"type": "string",
|
|
567
|
+
"pattern": "^[a-f0-9]{64}$",
|
|
568
|
+
"description": "SHA-256 (lowercase hex) of the proposed policy file's exact bytes, computed by the runtime. The hash a channel displays, and the only hash a tap can attest."
|
|
569
|
+
},
|
|
570
|
+
"payload_hash": {
|
|
571
|
+
"type": "string",
|
|
572
|
+
"pattern": "^[a-f0-9]{64}$",
|
|
573
|
+
"description": "SPEC.md §10.4: the binding for the full policy text stored beside the log, so an approver can read the whole file and not only the summary."
|
|
574
|
+
},
|
|
575
|
+
"diff": {
|
|
576
|
+
"type": "object",
|
|
577
|
+
"required": [
|
|
578
|
+
"available",
|
|
579
|
+
"lines",
|
|
580
|
+
"headline"
|
|
581
|
+
],
|
|
582
|
+
"description": "The semantic diff summary, computed from the proposed bytes against a baseline whose own SHA-256 equals the latest attestation. `available: false` is hash-only mode and carries the reason; it is never a truncated diff.",
|
|
583
|
+
"properties": {
|
|
584
|
+
"available": {
|
|
585
|
+
"type": "boolean"
|
|
586
|
+
},
|
|
587
|
+
"reason": {
|
|
588
|
+
"type": [
|
|
589
|
+
"string",
|
|
590
|
+
"null"
|
|
591
|
+
]
|
|
592
|
+
},
|
|
593
|
+
"lines": {
|
|
594
|
+
"type": "array",
|
|
595
|
+
"items": {
|
|
596
|
+
"type": "string"
|
|
597
|
+
}
|
|
598
|
+
},
|
|
599
|
+
"headline": {
|
|
600
|
+
"type": "string"
|
|
601
|
+
},
|
|
602
|
+
"baseline_sha256": {
|
|
603
|
+
"oneOf": [
|
|
604
|
+
{
|
|
605
|
+
"type": "string",
|
|
606
|
+
"pattern": "^[a-f0-9]{64}$"
|
|
607
|
+
},
|
|
608
|
+
{
|
|
609
|
+
"type": "null"
|
|
610
|
+
}
|
|
611
|
+
]
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
},
|
|
615
|
+
"load": {
|
|
616
|
+
"type": "object",
|
|
617
|
+
"required": [
|
|
618
|
+
"ok"
|
|
619
|
+
],
|
|
620
|
+
"description": "The load advisory for the proposed bytes. A policy that does not load fails closed to all-manual for every class; the prompt says so before the tap rather than after it.",
|
|
621
|
+
"properties": {
|
|
622
|
+
"ok": {
|
|
623
|
+
"type": "boolean"
|
|
624
|
+
},
|
|
625
|
+
"code": {
|
|
626
|
+
"type": [
|
|
627
|
+
"string",
|
|
628
|
+
"null"
|
|
629
|
+
]
|
|
630
|
+
},
|
|
631
|
+
"message": {
|
|
632
|
+
"type": [
|
|
633
|
+
"string",
|
|
634
|
+
"null"
|
|
635
|
+
]
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
},
|
|
639
|
+
"note": {
|
|
640
|
+
"type": "string",
|
|
641
|
+
"description": "The proposer's own words. Claimed, never a control."
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
},
|
|
648
|
+
{
|
|
649
|
+
"$comment": "SPEC.md §10.1/§11 (amended, APRV-109): a human refused to attest the proposed policy bytes. Human-only for the same reason `approval.rejected` is: the answer to an attestation prompt is the human act the whole ceremony exists to collect, and an agent-authored refusal would be the party under oversight closing its own question. `proposed_seq` names the prompt this answers, so a reader of the decline alone can find the hash, the diff and the advisory the approver was shown. Nothing about a decline attests anything: the policy stays exactly as unattested as it was.",
|
|
650
|
+
"if": {
|
|
651
|
+
"type": "object",
|
|
652
|
+
"properties": {
|
|
653
|
+
"event": {
|
|
654
|
+
"const": "policy.declined"
|
|
655
|
+
}
|
|
656
|
+
},
|
|
657
|
+
"required": [
|
|
658
|
+
"event"
|
|
659
|
+
]
|
|
660
|
+
},
|
|
661
|
+
"then": {
|
|
662
|
+
"type": "object",
|
|
663
|
+
"required": [
|
|
664
|
+
"actor",
|
|
665
|
+
"payload"
|
|
666
|
+
],
|
|
667
|
+
"properties": {
|
|
668
|
+
"actor": {
|
|
669
|
+
"type": "string",
|
|
670
|
+
"pattern": "^human:",
|
|
671
|
+
"description": "The approver. Answering an attestation prompt is human-only, in the schema as well as in the code."
|
|
672
|
+
},
|
|
673
|
+
"payload": {
|
|
674
|
+
"type": "object",
|
|
675
|
+
"required": [
|
|
676
|
+
"sha256",
|
|
677
|
+
"proposed_seq"
|
|
678
|
+
],
|
|
679
|
+
"properties": {
|
|
680
|
+
"sha256": {
|
|
681
|
+
"type": "string",
|
|
682
|
+
"pattern": "^[a-f0-9]{64}$",
|
|
683
|
+
"description": "The proposed policy hash that was refused."
|
|
684
|
+
},
|
|
685
|
+
"proposed_seq": {
|
|
686
|
+
"type": "integer",
|
|
687
|
+
"minimum": 1,
|
|
688
|
+
"description": "The `policy.proposed` record this answers."
|
|
689
|
+
},
|
|
690
|
+
"note": {
|
|
691
|
+
"type": "string",
|
|
692
|
+
"description": "The approver's free-text reason."
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
},
|
|
699
|
+
{
|
|
700
|
+
"$comment": "SPEC.md §5.2/§10.1 (amended, APRV-109): an attestation that answers a channel prompt names the prompt it answers. Optional and additive — `approval policy attest` at a terminal answers no prompt and carries none — and only its SHAPE is constrained here: that the seq names a `policy.proposed` record the log actually carries is not expressible in a schema that sees one record, and is the runtime's check.",
|
|
701
|
+
"if": {
|
|
702
|
+
"type": "object",
|
|
703
|
+
"properties": {
|
|
704
|
+
"event": {
|
|
705
|
+
"const": "policy.updated"
|
|
706
|
+
}
|
|
707
|
+
},
|
|
708
|
+
"required": [
|
|
709
|
+
"event"
|
|
710
|
+
]
|
|
711
|
+
},
|
|
712
|
+
"then": {
|
|
713
|
+
"type": "object",
|
|
714
|
+
"properties": {
|
|
715
|
+
"payload": {
|
|
716
|
+
"type": "object",
|
|
717
|
+
"properties": {
|
|
718
|
+
"proposed_seq": {
|
|
719
|
+
"type": "integer",
|
|
720
|
+
"minimum": 1,
|
|
721
|
+
"description": "The `policy.proposed` record this attestation answers."
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
},
|
|
728
|
+
{
|
|
729
|
+
"$comment": "SPEC.md §8: execution events report on one keyed action of one task.",
|
|
730
|
+
"if": {
|
|
731
|
+
"type": "object",
|
|
732
|
+
"properties": {
|
|
733
|
+
"event": {
|
|
734
|
+
"enum": [
|
|
735
|
+
"execution.started",
|
|
736
|
+
"execution.completed",
|
|
737
|
+
"execution.failed",
|
|
738
|
+
"execution.indeterminate",
|
|
739
|
+
"execution.reconciled"
|
|
740
|
+
]
|
|
741
|
+
}
|
|
742
|
+
},
|
|
743
|
+
"required": [
|
|
744
|
+
"event"
|
|
745
|
+
]
|
|
746
|
+
},
|
|
747
|
+
"then": {
|
|
748
|
+
"type": "object",
|
|
749
|
+
"required": [
|
|
750
|
+
"task",
|
|
751
|
+
"action_key"
|
|
752
|
+
],
|
|
753
|
+
"properties": {
|
|
754
|
+
"task": {
|
|
755
|
+
"$ref": "#/properties/task"
|
|
756
|
+
},
|
|
757
|
+
"action_key": {
|
|
758
|
+
"$ref": "#/properties/action_key"
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
},
|
|
763
|
+
{
|
|
764
|
+
"$comment": "SPEC.md §10.4 (amended, APRV-205): an executor that spawns a child builds that child's environment rather than inheriting one, withholding every variable under the credential-bearing prefixes (less the runtime's non-secret allowlist) and the variable the policy's `vault.passphrase_env` names. `env_stripped` records HOW MANY were withheld and nothing else: a variable's name is half of a credential, and §11.1's raw-secrets invariant is not satisfied by leaking the other half slowly. The field is informational — nothing in the gate reads it back and no decision turns on it, which is why it does not collide with §11.1's rule that self-reported fields never reduce scrutiny. OPTIONAL and additive: an execution with no child (an adapter's `act`, which runs in the runtime's own process) records no count, every record written before the field existed still validates and still verifies, and only the SHAPE is constrained here.",
|
|
765
|
+
"if": {
|
|
766
|
+
"type": "object",
|
|
767
|
+
"properties": {
|
|
768
|
+
"event": {
|
|
769
|
+
"const": "execution.started"
|
|
770
|
+
}
|
|
771
|
+
},
|
|
772
|
+
"required": [
|
|
773
|
+
"event"
|
|
774
|
+
]
|
|
775
|
+
},
|
|
776
|
+
"then": {
|
|
777
|
+
"type": "object",
|
|
778
|
+
"properties": {
|
|
779
|
+
"payload": {
|
|
780
|
+
"type": "object",
|
|
781
|
+
"properties": {
|
|
782
|
+
"env_stripped": {
|
|
783
|
+
"type": "integer",
|
|
784
|
+
"minimum": 0,
|
|
785
|
+
"description": "SPEC.md §10.4: the COUNT of credential-bearing environment variables withheld from the spawned child. Never a name and never a value.",
|
|
786
|
+
"examples": [3]
|
|
787
|
+
},
|
|
788
|
+
"grant_origin": {
|
|
789
|
+
"enum": ["direct", "carried"],
|
|
790
|
+
"description": "SPEC.md §10.4 (amended, APRV-200): how the authorization reached the process that spent it, on a harness `execution.started` that names a `grant_seq`. `direct` means the tool call that spent the grant is the tool call that asked for it, so one process observed the whole ordering and nothing this runtime authorized ran before the human answered. `carried` means a LATER tool call spent it under the carryover of §10.2's harness path: the asking invocation had already returned its verdict, and whether the harness honoured that verdict is a fact this runtime never observes, because it decides and the harness executes. The ordering the record implies is therefore guaranteed only for `direct`, and this field is what lets an auditor tell the two apart from the log alone. Derived at the write boundary from the task the request record carries, never from a caller's assertion of it: the value that adds scrutiny is the default and the one that removes it is reachable only by naming a fact already in the verified log, which is §11.1 invariant 4 applied to a marker. OPTIONAL and additive — an execution with no grant reports no origin, and every record written before the field existed still validates and still verifies.",
|
|
791
|
+
"examples": ["direct"]
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
},
|
|
798
|
+
{
|
|
799
|
+
"$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
|
+
"if": {
|
|
801
|
+
"type": "object",
|
|
802
|
+
"properties": {
|
|
803
|
+
"event": {
|
|
804
|
+
"enum": [
|
|
805
|
+
"approval.granted",
|
|
806
|
+
"approval.rejected"
|
|
807
|
+
]
|
|
808
|
+
}
|
|
809
|
+
},
|
|
810
|
+
"required": [
|
|
811
|
+
"event"
|
|
812
|
+
]
|
|
813
|
+
},
|
|
814
|
+
"then": {
|
|
815
|
+
"type": "object",
|
|
816
|
+
"properties": {
|
|
817
|
+
"payload": {
|
|
818
|
+
"type": "object",
|
|
819
|
+
"properties": {
|
|
820
|
+
"batch_delivery_id": {
|
|
821
|
+
"type": "string",
|
|
822
|
+
"minLength": 1,
|
|
823
|
+
"description": "SPEC.md §10.3: the channel delivery id of the batch this decision answered. The log never batches — each member gets its own event — so this field is what ties the separate events back to the one human gesture. Earlier v0.1 builds encoded the same id in the first line of `note` (`batch_delivery_id=<id>`); readers accept both encodings through v0.1 and prefer this one.",
|
|
824
|
+
"examples": ["tg-batch-7"]
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
},
|
|
831
|
+
{
|
|
832
|
+
"$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
|
+
"if": {
|
|
834
|
+
"type": "object",
|
|
835
|
+
"properties": {
|
|
836
|
+
"event": {
|
|
837
|
+
"enum": [
|
|
838
|
+
"approval.requested",
|
|
839
|
+
"approval.granted"
|
|
840
|
+
]
|
|
841
|
+
}
|
|
842
|
+
},
|
|
843
|
+
"required": [
|
|
844
|
+
"event"
|
|
845
|
+
]
|
|
846
|
+
},
|
|
847
|
+
"then": {
|
|
848
|
+
"type": "object",
|
|
849
|
+
"properties": {
|
|
850
|
+
"payload": {
|
|
851
|
+
"type": "object",
|
|
852
|
+
"properties": {
|
|
853
|
+
"policy_sha256": {
|
|
854
|
+
"type": "string",
|
|
855
|
+
"pattern": "^[a-f0-9]{64}$",
|
|
856
|
+
"description": "SPEC.md §5.2: SHA-256 (lowercase hex) of the attested policy file's exact bytes, as recorded by the `policy.updated` attestation the live file matched when this event was written."
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
},
|
|
863
|
+
{
|
|
864
|
+
"$comment": "SPEC.md §6.3/§10.4 (amended, APRV-105): sealed token delivery. `token_recipient_key` on `approval.requested` is the PUBLIC half of an ephemeral X25519 keypair the requester minted for this one request; the private half never leaves the requesting machine. `token_sealed` on `approval.granted` is the raw execution token encrypted to that key. Both are optional and additive: under the default `token_delivery: manual` neither appears and a record is byte-identical to one written before the field existed. Note what `token_sealed` does NOT weaken. §11.1 invariant 3 is reworded rather than relaxed: what appears in the log is a hash, or ciphertext sealed to a recipient key the log does not hold. The log carries no private key and no plaintext, so an attacker with a copy of the log — the one artifact this system deliberately makes durable, copyable and auditable — gains nothing, exactly as before. `token_sha256` remains present and remains what possession is proven against; the seal is a delivery mechanism, never an authorization.",
|
|
865
|
+
"if": {
|
|
866
|
+
"type": "object",
|
|
867
|
+
"properties": {
|
|
868
|
+
"event": {
|
|
869
|
+
"enum": [
|
|
870
|
+
"approval.requested",
|
|
871
|
+
"approval.granted"
|
|
872
|
+
]
|
|
873
|
+
}
|
|
874
|
+
},
|
|
875
|
+
"required": [
|
|
876
|
+
"event"
|
|
877
|
+
]
|
|
878
|
+
},
|
|
879
|
+
"then": {
|
|
880
|
+
"type": "object",
|
|
881
|
+
"properties": {
|
|
882
|
+
"payload": {
|
|
883
|
+
"type": "object",
|
|
884
|
+
"properties": {
|
|
885
|
+
"token_recipient_key": {
|
|
886
|
+
"type": "string",
|
|
887
|
+
"minLength": 20,
|
|
888
|
+
"maxLength": 600,
|
|
889
|
+
"pattern": "^[A-Za-z0-9+/]+={0,2}$",
|
|
890
|
+
"description": "The requester's ephemeral X25519 PUBLIC key, base64 DER SPKI. An ADDRESS, not a credential: it can receive a token and cannot mint, forge, rebind or respend one. Channels render it as a COMPUTED field so an approver can see that a delivery address was published and by whom."
|
|
891
|
+
},
|
|
892
|
+
"token_sealed": {
|
|
893
|
+
"type": "object",
|
|
894
|
+
"required": [
|
|
895
|
+
"alg",
|
|
896
|
+
"epk",
|
|
897
|
+
"nonce",
|
|
898
|
+
"ct",
|
|
899
|
+
"tag"
|
|
900
|
+
],
|
|
901
|
+
"description": "The raw token sealed to `token_recipient_key`: X25519 ECDH to a fresh ephemeral sender key, HKDF-SHA-256 to a 32-byte key, AES-256-GCM with a fresh nonce. The HKDF info binds the ciphertext to the scheme and to the action key, so a seal cannot be replayed as another action's.",
|
|
902
|
+
"properties": {
|
|
903
|
+
"alg": {
|
|
904
|
+
"const": "x25519-hkdf-sha256/aes-256-gcm",
|
|
905
|
+
"description": "The scheme identifier. One value in v0.1, present so no reader has to guess; a differing value MUST be refused rather than attempted."
|
|
906
|
+
},
|
|
907
|
+
"epk": {
|
|
908
|
+
"type": "string",
|
|
909
|
+
"minLength": 1,
|
|
910
|
+
"description": "The sender's ephemeral X25519 public key, base64 DER SPKI."
|
|
911
|
+
},
|
|
912
|
+
"nonce": {
|
|
913
|
+
"type": "string",
|
|
914
|
+
"minLength": 1,
|
|
915
|
+
"description": "The AES-GCM nonce, base64. Fresh for every seal."
|
|
916
|
+
},
|
|
917
|
+
"ct": {
|
|
918
|
+
"type": "string",
|
|
919
|
+
"minLength": 1,
|
|
920
|
+
"description": "Ciphertext, base64."
|
|
921
|
+
},
|
|
922
|
+
"tag": {
|
|
923
|
+
"type": "string",
|
|
924
|
+
"minLength": 1,
|
|
925
|
+
"description": "The GCM authentication tag, base64."
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
},
|
|
934
|
+
{
|
|
935
|
+
"$comment": "SPEC.md §9/§10.3 (amended, APRV-119, WYSIWYS): `display_hash` is the SHA-256 of the canonical rendering every channel MUST present for this request's payload — the digest of what the approver READ, beside `payload_hash`, the digest of what the approval BINDS to. The runtime assigns it at the write boundary from the deterministic renderer (`core/wysiwys.ts`), exactly as it assigns `ts` and `policy_sha256`; a requester cannot supply it, so it can never name a rendering that was not produced from the bound bytes. Optional, and deliberately: the field is additive, so records written before it existed still validate and still verify, and a request whose bytes this runtime does not hold records none rather than inventing one. Only its SHAPE is constrained here — a 64-character lowercase hex digest. Whether it equals a re-render of the stored payload is not expressible in a schema that sees one record, and is a check any reader with the payload store can make for itself.",
|
|
936
|
+
"if": {
|
|
937
|
+
"type": "object",
|
|
938
|
+
"properties": {
|
|
939
|
+
"event": {
|
|
940
|
+
"const": "approval.requested"
|
|
941
|
+
}
|
|
942
|
+
},
|
|
943
|
+
"required": [
|
|
944
|
+
"event"
|
|
945
|
+
]
|
|
946
|
+
},
|
|
947
|
+
"then": {
|
|
948
|
+
"type": "object",
|
|
949
|
+
"properties": {
|
|
950
|
+
"payload": {
|
|
951
|
+
"type": "object",
|
|
952
|
+
"properties": {
|
|
953
|
+
"display_hash": {
|
|
954
|
+
"type": "string",
|
|
955
|
+
"pattern": "^[a-f0-9]{64}$",
|
|
956
|
+
"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."
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
},
|
|
963
|
+
{
|
|
964
|
+
"$comment": "SPEC.md §5.2/§8 (amended, APRV-127): `reconciliation.required` records the obligation a RETROSPECTIVE DENIAL creates. The runtime cannot undo anything — the action already executed — so what it can do is oblige and record, and this is that record. The actor is `system:` because the obligation is mechanical: it follows from a human's denial by a rule, and an obligation an agent or a human could author by hand is an obligation the party under oversight could word into something it prefers. Every field named here is REQUIRED, because an obligation that does not say which action, under which class, denied by which review, and in which shape is an obligation nobody can discharge or audit.",
|
|
965
|
+
"if": {
|
|
966
|
+
"type": "object",
|
|
967
|
+
"properties": {
|
|
968
|
+
"event": {
|
|
969
|
+
"const": "reconciliation.required"
|
|
970
|
+
}
|
|
971
|
+
},
|
|
972
|
+
"required": [
|
|
973
|
+
"event"
|
|
974
|
+
]
|
|
975
|
+
},
|
|
976
|
+
"then": {
|
|
977
|
+
"type": "object",
|
|
978
|
+
"required": [
|
|
979
|
+
"action_key",
|
|
980
|
+
"actor",
|
|
981
|
+
"payload"
|
|
982
|
+
],
|
|
983
|
+
"properties": {
|
|
984
|
+
"action_key": {
|
|
985
|
+
"$ref": "#/properties/action_key"
|
|
986
|
+
},
|
|
987
|
+
"actor": {
|
|
988
|
+
"type": "string",
|
|
989
|
+
"pattern": "^system:"
|
|
990
|
+
},
|
|
991
|
+
"payload": {
|
|
992
|
+
"type": "object",
|
|
993
|
+
"required": [
|
|
994
|
+
"action_key",
|
|
995
|
+
"class",
|
|
996
|
+
"review_seq",
|
|
997
|
+
"obligation"
|
|
998
|
+
],
|
|
999
|
+
"properties": {
|
|
1000
|
+
"action_key": {
|
|
1001
|
+
"type": "string",
|
|
1002
|
+
"minLength": 1,
|
|
1003
|
+
"description": "The denied action's idempotency key, restated in the payload so the obligation and the action it concerns travel together for a reader who has only the payload."
|
|
1004
|
+
},
|
|
1005
|
+
"class": {
|
|
1006
|
+
"type": "string",
|
|
1007
|
+
"minLength": 1,
|
|
1008
|
+
"description": "The side-effect class the action was declared under. The class, not the action, is what a policy-was-wrong finding tightens."
|
|
1009
|
+
},
|
|
1010
|
+
"review_seq": {
|
|
1011
|
+
"type": "integer",
|
|
1012
|
+
"minimum": 1,
|
|
1013
|
+
"description": "The seq of the `audit.reviewed` record that denied. The denial is the fact; this obligation is its consequence, and the two are joined here rather than by proximity in the chain."
|
|
1014
|
+
},
|
|
1015
|
+
"obligation": {
|
|
1016
|
+
"type": "string",
|
|
1017
|
+
"enum": [
|
|
1018
|
+
"gated-revert",
|
|
1019
|
+
"policy-finding"
|
|
1020
|
+
],
|
|
1021
|
+
"description": "The shape the declared reversibility selects. `gated-revert`: the action can be undone, so the obligation is to undo it THROUGH THE GATE — the revert is itself a side-effecting action, and routing it through the gate is what closes the loop in the log. `policy-finding`: the action cannot be undone (or nobody declared that it could), so there is nothing to revert and the obligation is the review of the class that permitted it. Which one applies is decided by the runtime from the declaration the log already holds; see the reversibility floor note in `core/audit.ts` on why a self-reported `reversible: true` may select the SHAPE of an obligation and may never remove one."
|
|
1022
|
+
},
|
|
1023
|
+
"reversible": {
|
|
1024
|
+
"description": "The action's declared reversibility, as the `task.registered` record carried it, or `null` when the declaration named none. Recorded so a reader can see which claim selected the obligation shape. SELF-REPORTED: it is evidence about the claim, never a permission.",
|
|
1025
|
+
"oneOf": [
|
|
1026
|
+
{
|
|
1027
|
+
"type": "boolean"
|
|
1028
|
+
},
|
|
1029
|
+
{
|
|
1030
|
+
"type": "null"
|
|
1031
|
+
}
|
|
1032
|
+
]
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
},
|
|
1039
|
+
{
|
|
1040
|
+
"$comment": "SPEC.md §5.2/§8 (amended, APRV-127): `reconciliation.satisfied` closes one obligation, and it is HUMAN-ONLY by the same rule that governs grant, reject, revoke and `audit.reviewed`. The whole content of the record is that a person judged the obligation discharged; a runtime that could satisfy its own obligations would be a reconciliation backlog that empties itself, which is exactly the failure an unreconciled denial exists to make visible. `note` is required and not optional: `audit.reviewed` may record only \"seen\", but this record asserts that something was DONE, and an assertion with no statement of what was done is one no auditor can check.",
|
|
1041
|
+
"if": {
|
|
1042
|
+
"type": "object",
|
|
1043
|
+
"properties": {
|
|
1044
|
+
"event": {
|
|
1045
|
+
"const": "reconciliation.satisfied"
|
|
1046
|
+
}
|
|
1047
|
+
},
|
|
1048
|
+
"required": [
|
|
1049
|
+
"event"
|
|
1050
|
+
]
|
|
1051
|
+
},
|
|
1052
|
+
"then": {
|
|
1053
|
+
"type": "object",
|
|
1054
|
+
"required": [
|
|
1055
|
+
"actor",
|
|
1056
|
+
"payload"
|
|
1057
|
+
],
|
|
1058
|
+
"properties": {
|
|
1059
|
+
"actor": {
|
|
1060
|
+
"type": "string",
|
|
1061
|
+
"pattern": "^human:"
|
|
1062
|
+
},
|
|
1063
|
+
"payload": {
|
|
1064
|
+
"type": "object",
|
|
1065
|
+
"required": [
|
|
1066
|
+
"obligation_seq",
|
|
1067
|
+
"note"
|
|
1068
|
+
],
|
|
1069
|
+
"properties": {
|
|
1070
|
+
"obligation_seq": {
|
|
1071
|
+
"type": "integer",
|
|
1072
|
+
"minimum": 1,
|
|
1073
|
+
"description": "The seq of the `reconciliation.required` record being closed. Named by seq rather than by action key because one action may accumulate more than one obligation, and a satisfaction that could mean either would close the wrong one."
|
|
1074
|
+
},
|
|
1075
|
+
"note": {
|
|
1076
|
+
"type": "string",
|
|
1077
|
+
"minLength": 1,
|
|
1078
|
+
"description": "What the human did. Required: this record asserts a discharge, and a discharge nobody described is one nobody can check."
|
|
1079
|
+
},
|
|
1080
|
+
"revert_action_key": {
|
|
1081
|
+
"type": "string",
|
|
1082
|
+
"minLength": 1,
|
|
1083
|
+
"description": "For a `gated-revert` obligation, the idempotency key of the action that performed the revert. The runtime refuses the satisfaction unless the log carries an `execution.completed` for that key, so the loop closes in the chain rather than in a sentence."
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
},
|
|
1090
|
+
{
|
|
1091
|
+
"$comment": "SPEC.md §8/§9 (amended, APRV-38): `payload.pruned` records that the daemon removed a payload file from `.approval/payloads/` under `payload_retention`. Two constraints, both about who and what: the actor is `system:` because pruning is a retention rule executing on a schedule rather than anyone's decision (a `human:` or `agent:` pruner would be a party under oversight deleting the evidence its approval bound to), and the payload names the removed bytes by their SHA-256, which is the store's own key and the same value a grant binds to. The log therefore states exactly what the store no longer holds.",
|
|
1092
|
+
"if": {
|
|
1093
|
+
"type": "object",
|
|
1094
|
+
"properties": {
|
|
1095
|
+
"event": {
|
|
1096
|
+
"const": "payload.pruned"
|
|
1097
|
+
}
|
|
1098
|
+
},
|
|
1099
|
+
"required": [
|
|
1100
|
+
"event"
|
|
1101
|
+
]
|
|
1102
|
+
},
|
|
1103
|
+
"then": {
|
|
1104
|
+
"type": "object",
|
|
1105
|
+
"required": [
|
|
1106
|
+
"actor",
|
|
1107
|
+
"payload"
|
|
1108
|
+
],
|
|
1109
|
+
"properties": {
|
|
1110
|
+
"actor": {
|
|
1111
|
+
"type": "string",
|
|
1112
|
+
"pattern": "^system:"
|
|
1113
|
+
},
|
|
1114
|
+
"payload": {
|
|
1115
|
+
"type": "object",
|
|
1116
|
+
"required": [
|
|
1117
|
+
"payload_hash"
|
|
1118
|
+
],
|
|
1119
|
+
"properties": {
|
|
1120
|
+
"payload_hash": {
|
|
1121
|
+
"type": "string",
|
|
1122
|
+
"pattern": "^[a-f0-9]{64}$",
|
|
1123
|
+
"description": "SPEC.md §6.2/§9: SHA-256 (lowercase hex) of the pruned payload's bytes — the content address `.approval/payloads/` filed them under, and the value the action's grant bound to."
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
},
|
|
1130
|
+
{
|
|
1131
|
+
"$comment": "SPEC.md §10.2 (amended, APRV-145): the completion counterpart. A harness adapter answers before the tool runs and never observes an exit status, so the `execution.started` it writes is terminal by design; an implementation MAY close it from a post-execution harness event, and the record it appends is an ordinary `execution.completed` or `execution.failed` carrying two additive markers. `reported_by` names which untrusted reporter ASSERTED the outcome and is drawn from a closed set, extended only by a task that adds the case: it is a claimed field in the computed-versus-claimed vocabulary of §9 and reduces nothing, and a free-text value is where a tool's output (and the credential quoted inside it) would arrive. `execution` repeats the harness marker the start carries, so a reader can tell an asserted outcome from an observed one without walking back to the start record. Both are OPTIONAL and only their SHAPE is constrained: the field is additive, so every outcome written before it existed still validates and still verifies, and a record's payload is otherwise unchanged. `exit_code` is constrained to an integer or null — a harness that states no code records null rather than a number nobody measured, exactly as `execution.indeterminate` does. Whether the named start actually carries the marker is the gate's check (`not-delegated`); a schema sees one record.",
|
|
1132
|
+
"if": {
|
|
1133
|
+
"type": "object",
|
|
1134
|
+
"properties": {
|
|
1135
|
+
"event": {
|
|
1136
|
+
"enum": [
|
|
1137
|
+
"execution.completed",
|
|
1138
|
+
"execution.failed"
|
|
1139
|
+
]
|
|
1140
|
+
}
|
|
1141
|
+
},
|
|
1142
|
+
"required": [
|
|
1143
|
+
"event"
|
|
1144
|
+
]
|
|
1145
|
+
},
|
|
1146
|
+
"then": {
|
|
1147
|
+
"type": "object",
|
|
1148
|
+
"properties": {
|
|
1149
|
+
"payload": {
|
|
1150
|
+
"type": "object",
|
|
1151
|
+
"properties": {
|
|
1152
|
+
"reported_by": {
|
|
1153
|
+
"type": "string",
|
|
1154
|
+
"enum": [
|
|
1155
|
+
"post-tool-use"
|
|
1156
|
+
],
|
|
1157
|
+
"description": "Which untrusted reporter asserted this outcome. `post-tool-use`: a harness adapter's post-execution hook event. Closed, and extended only by a task that adds the case. Its presence marks the record as a REPORT rather than an observation; its absence means this runtime watched the process to its exit itself."
|
|
1158
|
+
},
|
|
1159
|
+
"execution": {
|
|
1160
|
+
"const": "harness",
|
|
1161
|
+
"description": "The same marker the `execution.started` carries: the harness ran the command and this runtime never observed the exit status. Present only on a counterpart appended from a harness report."
|
|
1162
|
+
},
|
|
1163
|
+
"exit_code": {
|
|
1164
|
+
"type": [
|
|
1165
|
+
"integer",
|
|
1166
|
+
"null"
|
|
1167
|
+
],
|
|
1168
|
+
"description": "The process exit code where one was observed or stated, and `null` where none was. A harness event exposes no exit code, so a counterpart records `null`: an invented number would be indistinguishable from a measured one."
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
},
|
|
1175
|
+
{
|
|
1176
|
+
"$comment": "SPEC.md §8 (amended, APRV-251): an `execution.completed` MAY carry `provider_ref`, the identifier the provider's own record files this effect under, so that `approval coverage` (§10.1) can join a witness outside this runtime to the log by id instead of by class and time window. Exactly two members and no others: `adapter`, the name of the adapter that executed the action, and `id`, the provider's identifier. `additionalProperties: false` is the whole point of the shape — a reference that could carry a third member is a place to put a message body, and the length and character bounds close the same door on the two members it does have. Both are written by the runtime: `adapter` from the registered adapter, `id` lifted from what the adapter returned, through the same redaction sweep as every other string it returned (§11.1 invariant 3). An id whose bytes that sweep touched is OMITTED by the write path rather than recorded, since a redacted identifier matches nothing and would read like one that does. The field authorizes nothing and no verdict reads it back, so §11.1 invariant 4 is untouched by a value that came in part from the far side. OPTIONAL and additive: an execution whose adapter names no reference records none, and every record written before the field existed still validates and still verifies. Constrained on `execution.completed` alone, because that is the only event a completed side effect's identifier belongs on; a failed execution produced no effect for a provider to file.",
|
|
1177
|
+
"if": {
|
|
1178
|
+
"type": "object",
|
|
1179
|
+
"properties": {
|
|
1180
|
+
"event": {
|
|
1181
|
+
"const": "execution.completed"
|
|
1182
|
+
}
|
|
1183
|
+
},
|
|
1184
|
+
"required": [
|
|
1185
|
+
"event"
|
|
1186
|
+
]
|
|
1187
|
+
},
|
|
1188
|
+
"then": {
|
|
1189
|
+
"type": "object",
|
|
1190
|
+
"properties": {
|
|
1191
|
+
"payload": {
|
|
1192
|
+
"type": "object",
|
|
1193
|
+
"properties": {
|
|
1194
|
+
"provider_ref": {
|
|
1195
|
+
"type": "object",
|
|
1196
|
+
"required": [
|
|
1197
|
+
"adapter",
|
|
1198
|
+
"id"
|
|
1199
|
+
],
|
|
1200
|
+
"additionalProperties": false,
|
|
1201
|
+
"properties": {
|
|
1202
|
+
"adapter": {
|
|
1203
|
+
"type": "string",
|
|
1204
|
+
"pattern": "^[\\x21-\\x7e]{1,64}$",
|
|
1205
|
+
"description": "The name of the adapter that executed the action, as the runtime holds it. Printable ASCII with no spaces, at most 64 characters."
|
|
1206
|
+
},
|
|
1207
|
+
"id": {
|
|
1208
|
+
"type": "string",
|
|
1209
|
+
"pattern": "^[\\x21-\\x7e]{1,256}$",
|
|
1210
|
+
"description": "The provider's own identifier for the effect (an AgentMail `message_id`). Printable ASCII with no spaces, at most 256 characters: an identifier is short, and the bound is what keeps this field from becoming somewhere to put a message."
|
|
1211
|
+
}
|
|
1212
|
+
},
|
|
1213
|
+
"examples": [{ "adapter": "agentmail", "id": "msg_01JQ2" }]
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
},
|
|
1220
|
+
{
|
|
1221
|
+
"$comment": "SPEC.md §6.3/§10.4 (amended, APRV-120): `execution.indeterminate` records that a side effect was ATTEMPTED and that its outcome is unknown. It is not a failure — a failure is a fact — and the difference is what stops a blind retry against an action that may already have happened. Two constraints, both about what may be written down. `reason` is required and closed: an unknown outcome that does not say where the unknowing began is not machine-readable, and a free-text reason is where an exception message (and the credential quoted inside it) would arrive. `exit_code` is `null` when present, never a number: nobody observed one, and a fabricated code would read exactly like a measured one. The bytes of the exception are deliberately absent from this record; the executing caller receives them, redacted, and the log holds the closed code.",
|
|
1222
|
+
"if": {
|
|
1223
|
+
"type": "object",
|
|
1224
|
+
"properties": {
|
|
1225
|
+
"event": {
|
|
1226
|
+
"const": "execution.indeterminate"
|
|
1227
|
+
}
|
|
1228
|
+
},
|
|
1229
|
+
"required": [
|
|
1230
|
+
"event"
|
|
1231
|
+
]
|
|
1232
|
+
},
|
|
1233
|
+
"then": {
|
|
1234
|
+
"type": "object",
|
|
1235
|
+
"required": [
|
|
1236
|
+
"payload"
|
|
1237
|
+
],
|
|
1238
|
+
"properties": {
|
|
1239
|
+
"payload": {
|
|
1240
|
+
"type": "object",
|
|
1241
|
+
"required": [
|
|
1242
|
+
"reason"
|
|
1243
|
+
],
|
|
1244
|
+
"properties": {
|
|
1245
|
+
"reason": {
|
|
1246
|
+
"type": "string",
|
|
1247
|
+
"enum": [
|
|
1248
|
+
"act-threw"
|
|
1249
|
+
],
|
|
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."
|
|
1251
|
+
},
|
|
1252
|
+
"exit_code": {
|
|
1253
|
+
"type": "null",
|
|
1254
|
+
"description": "Always `null`. No process was watched to an exit, and an invented number would be indistinguishable from an observed one."
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
},
|
|
1261
|
+
{
|
|
1262
|
+
"$comment": "SPEC.md §6.3/§10.4 (amended, APRV-120): `execution.reconciled` records what a person established about an indeterminate execution. It never rewrites the indeterminate record — the original observation survives its resolution, which is the point of an append-only log — and it names that record by seq so a reader can walk from one to the other. Human-only, for the reason `execution resolve` is: an agent reconciling its own indeterminate outcome is the executing party reporting on itself. The note is the evidence and is required non-empty, because an unexplained resolution cannot be told apart from a guess, and `resolution` is closed to two values so that \"it did happen\" and \"it did not\" are distinct in the log rather than a matter of reading prose.",
|
|
1263
|
+
"if": {
|
|
1264
|
+
"type": "object",
|
|
1265
|
+
"properties": {
|
|
1266
|
+
"event": {
|
|
1267
|
+
"const": "execution.reconciled"
|
|
1268
|
+
}
|
|
1269
|
+
},
|
|
1270
|
+
"required": [
|
|
1271
|
+
"event"
|
|
1272
|
+
]
|
|
1273
|
+
},
|
|
1274
|
+
"then": {
|
|
1275
|
+
"type": "object",
|
|
1276
|
+
"required": [
|
|
1277
|
+
"actor",
|
|
1278
|
+
"payload"
|
|
1279
|
+
],
|
|
1280
|
+
"properties": {
|
|
1281
|
+
"actor": {
|
|
1282
|
+
"type": "string",
|
|
1283
|
+
"pattern": "^human:"
|
|
1284
|
+
},
|
|
1285
|
+
"payload": {
|
|
1286
|
+
"type": "object",
|
|
1287
|
+
"required": [
|
|
1288
|
+
"indeterminate_seq",
|
|
1289
|
+
"resolution",
|
|
1290
|
+
"note",
|
|
1291
|
+
"attested_by_human"
|
|
1292
|
+
],
|
|
1293
|
+
"properties": {
|
|
1294
|
+
"indeterminate_seq": {
|
|
1295
|
+
"type": "integer",
|
|
1296
|
+
"minimum": 1,
|
|
1297
|
+
"description": "The `seq` of the `execution.indeterminate` record this resolves. That the named record exists, concerns this action key, and is not already reconciled is the gate's check; a schema sees one record and can only constrain the shape."
|
|
1298
|
+
},
|
|
1299
|
+
"resolution": {
|
|
1300
|
+
"type": "string",
|
|
1301
|
+
"enum": [
|
|
1302
|
+
"executed",
|
|
1303
|
+
"not-executed"
|
|
1304
|
+
],
|
|
1305
|
+
"description": "What the evidence established. `executed`: the side effect did happen. `not-executed`: it did not, which is what re-opens the possibility of a fresh request for the same effect under a new idempotency key. The key itself stays burned either way."
|
|
1306
|
+
},
|
|
1307
|
+
"note": {
|
|
1308
|
+
"type": "string",
|
|
1309
|
+
"minLength": 1,
|
|
1310
|
+
"description": "The evidence, in the reconciler's own words: which log, which provider console, which message id. The whole value of the record is this."
|
|
1311
|
+
},
|
|
1312
|
+
"attested_by_human": {
|
|
1313
|
+
"const": true,
|
|
1314
|
+
"description": "Marks the record as an observation rather than a machine's report, exactly as `execution resolve` marks its own."
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1320
|
+
},
|
|
1321
|
+
{
|
|
1322
|
+
"$comment": "SPEC.md §8: task-scoped events that precede or accompany a specific action key. Budget accounting is per task (SPEC.md §5), so `budget.exceeded` names the task but need not name an action.",
|
|
1323
|
+
"if": {
|
|
1324
|
+
"type": "object",
|
|
1325
|
+
"properties": {
|
|
1326
|
+
"event": {
|
|
1327
|
+
"enum": [
|
|
1328
|
+
"task.registered",
|
|
1329
|
+
"route.proposed",
|
|
1330
|
+
"route.accepted",
|
|
1331
|
+
"envelope.drift",
|
|
1332
|
+
"budget.exceeded"
|
|
1333
|
+
]
|
|
1334
|
+
}
|
|
1335
|
+
},
|
|
1336
|
+
"required": [
|
|
1337
|
+
"event"
|
|
1338
|
+
]
|
|
1339
|
+
},
|
|
1340
|
+
"then": {
|
|
1341
|
+
"type": "object",
|
|
1342
|
+
"required": [
|
|
1343
|
+
"task"
|
|
1344
|
+
],
|
|
1345
|
+
"properties": {
|
|
1346
|
+
"task": {
|
|
1347
|
+
"$ref": "#/properties/task"
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
},
|
|
1352
|
+
{
|
|
1353
|
+
"$comment": "SPEC.md §10.2 (amended, APRV-192): the dark-session observation. The daemon correlates the git activity it can see against the records the log carries, and appends this when a checkout produced gated-class git activity for which no hook record exists. It is a statement by the RUNTIME about what it observed, never a record written on a session's behalf, so the actor MUST be `system:`-prefixed: an agent- or human-authored dark-session record would be the party under oversight (or the party it reports to) authoring the detector's findings, and §11.1 invariant 4 forbids a self-reported value from lowering scrutiny. The payload names the subject checkout, the verdict's stable code, the observation key that makes the append idempotent across ticks, and the counts. No commit message, no diff, no command text: §11.1 invariant 3 has no exception for diagnostics, and what this record establishes is an ABSENCE, which needs no content to state.",
|
|
1354
|
+
"if": {
|
|
1355
|
+
"type": "object",
|
|
1356
|
+
"properties": {
|
|
1357
|
+
"event": {
|
|
1358
|
+
"const": "audit.dark_session"
|
|
1359
|
+
}
|
|
1360
|
+
},
|
|
1361
|
+
"required": [
|
|
1362
|
+
"event"
|
|
1363
|
+
]
|
|
1364
|
+
},
|
|
1365
|
+
"then": {
|
|
1366
|
+
"type": "object",
|
|
1367
|
+
"required": [
|
|
1368
|
+
"actor",
|
|
1369
|
+
"payload"
|
|
1370
|
+
],
|
|
1371
|
+
"properties": {
|
|
1372
|
+
"actor": {
|
|
1373
|
+
"type": "string",
|
|
1374
|
+
"pattern": "^system:"
|
|
1375
|
+
},
|
|
1376
|
+
"payload": {
|
|
1377
|
+
"type": "object",
|
|
1378
|
+
"required": [
|
|
1379
|
+
"subject",
|
|
1380
|
+
"code",
|
|
1381
|
+
"observation_key"
|
|
1382
|
+
],
|
|
1383
|
+
"properties": {
|
|
1384
|
+
"subject": {
|
|
1385
|
+
"type": "string",
|
|
1386
|
+
"minLength": 1,
|
|
1387
|
+
"description": "The checkout the observation is about, named as the worktree directory's base name."
|
|
1388
|
+
},
|
|
1389
|
+
"code": {
|
|
1390
|
+
"type": "string",
|
|
1391
|
+
"enum": [
|
|
1392
|
+
"no-records",
|
|
1393
|
+
"no-evidence"
|
|
1394
|
+
],
|
|
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)."
|
|
1396
|
+
},
|
|
1397
|
+
"observation_key": {
|
|
1398
|
+
"type": "string",
|
|
1399
|
+
"minLength": 1,
|
|
1400
|
+
"description": "Subject plus the state of the world it was observed in. The sweep re-derives it every tick and appends nothing for a key the verified log already carries, so a standing dark worktree produces one record rather than one per tick."
|
|
1401
|
+
},
|
|
1402
|
+
"commits": {
|
|
1403
|
+
"type": "integer",
|
|
1404
|
+
"minimum": 0
|
|
1405
|
+
},
|
|
1406
|
+
"guarded_paths": {
|
|
1407
|
+
"type": "array",
|
|
1408
|
+
"items": {
|
|
1409
|
+
"type": "string"
|
|
1410
|
+
}
|
|
1411
|
+
},
|
|
1412
|
+
"window": {
|
|
1413
|
+
"type": "object",
|
|
1414
|
+
"properties": {
|
|
1415
|
+
"from": {
|
|
1416
|
+
"type": "string",
|
|
1417
|
+
"format": "date-time"
|
|
1418
|
+
},
|
|
1419
|
+
"to": {
|
|
1420
|
+
"type": "string",
|
|
1421
|
+
"format": "date-time"
|
|
1422
|
+
}
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1426
|
+
}
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
1429
|
+
},
|
|
1430
|
+
{
|
|
1431
|
+
"$comment": "SPEC.md §5.2/§8 (amended, APRV-235): a human decided, through a channel or the CLI, and the gate refused to record the decision. Seen live on 2026-09-02: an approve tap on a request asked under the previous policy was refused `policy-drift`, correctly, and nothing was appended at all — the log, which is the truth, held no trace that a person had answered, and a later reader could not explain why a request sat unanswered.\n\nThis is an AUDIT-TIER record and grants nothing. No verdict, no budget, no streak and no sampling path reads it: `core/state.ts` does not settle on it, `core/budgets.ts` charges nothing for it, and it mints no token. What it establishes is that a human's attention was spent and the answer could not be taken — which is a fact about the gate, not an authorization by it.\n\nThe actor MUST be `^system:` for the same reason `audit.dark_session`'s is: this is the RUNTIME's statement about a refusal the runtime made, and a record of a refusal authored by either party to it is a record neither party can be held to. The human who decided is named in the payload, from the decision surface's configured identity — the same source the `actor` of a grant comes from — so nothing here is a self-report that lowers scrutiny (§11.1 invariant 4); an audit record can only ADD to what a reviewer sees.\n\nGate-side refusals to AGENTS stay unlogged, deliberately. An agent that is refused is told, retries or stops, and can be asked again; a human who is refused has already spent the thing SPEC.md §11 calls the audit budget, and cannot be asked to spend it again without a record saying the first spend happened.",
|
|
1432
|
+
"if": {
|
|
1433
|
+
"type": "object",
|
|
1434
|
+
"properties": {
|
|
1435
|
+
"event": {
|
|
1436
|
+
"const": "audit.decision_refused"
|
|
1437
|
+
}
|
|
1438
|
+
},
|
|
1439
|
+
"required": [
|
|
1440
|
+
"event"
|
|
1441
|
+
]
|
|
1442
|
+
},
|
|
1443
|
+
"then": {
|
|
1444
|
+
"type": "object",
|
|
1445
|
+
"required": [
|
|
1446
|
+
"actor",
|
|
1447
|
+
"action_key",
|
|
1448
|
+
"channel",
|
|
1449
|
+
"payload"
|
|
1450
|
+
],
|
|
1451
|
+
"properties": {
|
|
1452
|
+
"actor": {
|
|
1453
|
+
"type": "string",
|
|
1454
|
+
"pattern": "^system:",
|
|
1455
|
+
"description": "The runtime. A `human:` or `agent:` actor is refused: the record states that the gate refused a decision, and the parties to that refusal do not author it."
|
|
1456
|
+
},
|
|
1457
|
+
"action_key": {
|
|
1458
|
+
"$ref": "#/properties/action_key"
|
|
1459
|
+
},
|
|
1460
|
+
"channel": {
|
|
1461
|
+
"$ref": "#/properties/channel",
|
|
1462
|
+
"description": "Which surface collected the gesture — `telegram`, `web`, `cli`. Required here and optional in the base shape: a refused decision a reader cannot attribute to a surface is one they cannot go and reproduce, and every decision surface knows its own name."
|
|
1463
|
+
},
|
|
1464
|
+
"payload": {
|
|
1465
|
+
"type": "object",
|
|
1466
|
+
"required": [
|
|
1467
|
+
"actor",
|
|
1468
|
+
"decision",
|
|
1469
|
+
"code"
|
|
1470
|
+
],
|
|
1471
|
+
"properties": {
|
|
1472
|
+
"actor": {
|
|
1473
|
+
"type": "string",
|
|
1474
|
+
"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."
|
|
1476
|
+
},
|
|
1477
|
+
"decision": {
|
|
1478
|
+
"type": "string",
|
|
1479
|
+
"enum": [
|
|
1480
|
+
"grant",
|
|
1481
|
+
"reject",
|
|
1482
|
+
"revoke"
|
|
1483
|
+
],
|
|
1484
|
+
"description": "Which of `core/gate.ts`'s three human-only decisions was attempted. Recorded so a reader can tell an unrecordable YES from an unrecordable NO, which are very different facts about an unanswered request."
|
|
1485
|
+
},
|
|
1486
|
+
"code": {
|
|
1487
|
+
"type": "string",
|
|
1488
|
+
"minLength": 1,
|
|
1489
|
+
"description": "The gate's machine-readable refusal code, verbatim (§11.1 invariant 6: refusals are machine-readable and distinct). Not pinned to an enum here on purpose. The closed union is `GATE_REFUSAL_CODES` in `core/gate.ts`, pinned for conformance in `conformance/vectors/refusal-unions.v1.json`, and a log is append-only: a code retired from the union must still READ, so the schema constrains the shape and the union constrains what may be written."
|
|
1490
|
+
},
|
|
1491
|
+
"policy_sha256_requested": {
|
|
1492
|
+
"type": "string",
|
|
1493
|
+
"pattern": "^[a-f0-9]{64}$",
|
|
1494
|
+
"description": "The policy hash the `approval.requested` pinned, on a `policy-drift` refusal. Copied from the request record, never recomputed."
|
|
1495
|
+
},
|
|
1496
|
+
"policy_sha256_attested": {
|
|
1497
|
+
"type": "string",
|
|
1498
|
+
"pattern": "^[a-f0-9]{64}$",
|
|
1499
|
+
"description": "The policy hash in force at the moment of the refusal, as the runtime attested it. Assigned at the write boundary like `ts`; there is no parameter through which a caller could supply either hash."
|
|
1500
|
+
},
|
|
1501
|
+
"message": {
|
|
1502
|
+
"type": "string",
|
|
1503
|
+
"description": "The gate's own refusal message, as the approver was shown it. A convenience for a reader; `code` is what anything machine-readable branches on."
|
|
1504
|
+
}
|
|
1505
|
+
}
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1508
|
+
}
|
|
1509
|
+
},
|
|
1510
|
+
{
|
|
1511
|
+
"$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
|
+
"if": {
|
|
1513
|
+
"type": "object",
|
|
1514
|
+
"properties": {
|
|
1515
|
+
"event": {
|
|
1516
|
+
"const": "gate.opened"
|
|
1517
|
+
}
|
|
1518
|
+
},
|
|
1519
|
+
"required": [
|
|
1520
|
+
"event"
|
|
1521
|
+
]
|
|
1522
|
+
},
|
|
1523
|
+
"then": {
|
|
1524
|
+
"type": "object",
|
|
1525
|
+
"required": [
|
|
1526
|
+
"actor",
|
|
1527
|
+
"payload"
|
|
1528
|
+
],
|
|
1529
|
+
"properties": {
|
|
1530
|
+
"actor": {
|
|
1531
|
+
"type": "string",
|
|
1532
|
+
"pattern": "^human:"
|
|
1533
|
+
},
|
|
1534
|
+
"payload": {
|
|
1535
|
+
"type": "object",
|
|
1536
|
+
"required": [
|
|
1537
|
+
"expires_at",
|
|
1538
|
+
"duration",
|
|
1539
|
+
"reason",
|
|
1540
|
+
"scope"
|
|
1541
|
+
],
|
|
1542
|
+
"properties": {
|
|
1543
|
+
"expires_at": {
|
|
1544
|
+
"type": "string",
|
|
1545
|
+
"format": "date-time",
|
|
1546
|
+
"description": "When the window lapses, as the opener's runtime computed it from the record's own `ts`. Nothing is appended at lapse, so a reader derives closure from this and from the absence of a `gate.closed` naming this record's seq."
|
|
1547
|
+
},
|
|
1548
|
+
"duration": {
|
|
1549
|
+
"type": "string",
|
|
1550
|
+
"minLength": 1,
|
|
1551
|
+
"description": "The duration as it was asked for (`30m`, `2h`). The authority, alongside `ts`: a duration over the 24h cap is clamped at read time, so a hand-written record cannot buy a longer window than the runtime would have granted."
|
|
1552
|
+
},
|
|
1553
|
+
"reason": {
|
|
1554
|
+
"type": "string",
|
|
1555
|
+
"minLength": 1,
|
|
1556
|
+
"description": "Why the window was opened, in the opener's words. Required: a bypass nobody stated a reason for is a bypass nobody can review."
|
|
1557
|
+
},
|
|
1558
|
+
"scope": {
|
|
1559
|
+
"type": "string",
|
|
1560
|
+
"enum": [
|
|
1561
|
+
"hook"
|
|
1562
|
+
],
|
|
1563
|
+
"description": "What the window suspends. One value at v0.1: the harness hook's policy resolution. It is an enum rather than a free string so that a later scope is an explicit schema change and never a value an opener invents."
|
|
1564
|
+
}
|
|
1565
|
+
}
|
|
1566
|
+
}
|
|
1567
|
+
}
|
|
1568
|
+
}
|
|
1569
|
+
},
|
|
1570
|
+
{
|
|
1571
|
+
"$comment": "SPEC.md §5.2 (amended, APRV-214): the human closing an open window early. Human-actor for the same reason `gate.opened` is, though this one only ever TIGHTENS: an agent able to close a window could not thereby authorize anything, and the rule is kept uniform so the pair reads as one ceremony.",
|
|
1572
|
+
"if": {
|
|
1573
|
+
"type": "object",
|
|
1574
|
+
"properties": {
|
|
1575
|
+
"event": {
|
|
1576
|
+
"const": "gate.closed"
|
|
1577
|
+
}
|
|
1578
|
+
},
|
|
1579
|
+
"required": [
|
|
1580
|
+
"event"
|
|
1581
|
+
]
|
|
1582
|
+
},
|
|
1583
|
+
"then": {
|
|
1584
|
+
"type": "object",
|
|
1585
|
+
"required": [
|
|
1586
|
+
"actor",
|
|
1587
|
+
"payload"
|
|
1588
|
+
],
|
|
1589
|
+
"properties": {
|
|
1590
|
+
"actor": {
|
|
1591
|
+
"type": "string",
|
|
1592
|
+
"pattern": "^human:"
|
|
1593
|
+
},
|
|
1594
|
+
"payload": {
|
|
1595
|
+
"type": "object",
|
|
1596
|
+
"required": [
|
|
1597
|
+
"opened_seq"
|
|
1598
|
+
],
|
|
1599
|
+
"properties": {
|
|
1600
|
+
"opened_seq": {
|
|
1601
|
+
"type": "integer",
|
|
1602
|
+
"minimum": 1,
|
|
1603
|
+
"description": "The `seq` of the `gate.opened` this closes. That the named record exists and is the latest open one is the runtime's check; a schema sees one record and can only constrain the shape."
|
|
1604
|
+
},
|
|
1605
|
+
"note": {
|
|
1606
|
+
"type": "string",
|
|
1607
|
+
"minLength": 1,
|
|
1608
|
+
"description": "Optional: what was learned while the window was open."
|
|
1609
|
+
}
|
|
1610
|
+
}
|
|
1611
|
+
}
|
|
1612
|
+
}
|
|
1613
|
+
}
|
|
1614
|
+
},
|
|
1615
|
+
{
|
|
1616
|
+
"$comment": "SPEC.md §5.2 (amended, APRV-214): one gated tool call the hook allowed because a window was open. Appended BEFORE the allow is printed, so a bypassed command that ran and was not recorded is not a state this runtime can reach (§11.1 invariant 8). Either actor kind: the record says who the harness ran as, and it is a statement about the call rather than an authorization by the caller, so nothing here is a self-report that lowers scrutiny — the authority is the human's `gate.opened`, and this record can only ADD to what a reviewer sees. Summary and payload hash, never the raw command: §11.1 invariant 3 has no exception for a bypass, and the payload store already holds the bytes for every record that binds to them.",
|
|
1617
|
+
"if": {
|
|
1618
|
+
"type": "object",
|
|
1619
|
+
"properties": {
|
|
1620
|
+
"event": {
|
|
1621
|
+
"const": "gate.bypassed"
|
|
1622
|
+
}
|
|
1623
|
+
},
|
|
1624
|
+
"required": [
|
|
1625
|
+
"event"
|
|
1626
|
+
]
|
|
1627
|
+
},
|
|
1628
|
+
"then": {
|
|
1629
|
+
"type": "object",
|
|
1630
|
+
"required": [
|
|
1631
|
+
"actor",
|
|
1632
|
+
"payload"
|
|
1633
|
+
],
|
|
1634
|
+
"properties": {
|
|
1635
|
+
"actor": {
|
|
1636
|
+
"type": "string",
|
|
1637
|
+
"pattern": "^(human|agent):"
|
|
1638
|
+
},
|
|
1639
|
+
"payload": {
|
|
1640
|
+
"type": "object",
|
|
1641
|
+
"required": [
|
|
1642
|
+
"opened_seq",
|
|
1643
|
+
"tool",
|
|
1644
|
+
"summary",
|
|
1645
|
+
"classes",
|
|
1646
|
+
"payload_hash"
|
|
1647
|
+
],
|
|
1648
|
+
"properties": {
|
|
1649
|
+
"opened_seq": {
|
|
1650
|
+
"type": "integer",
|
|
1651
|
+
"minimum": 1,
|
|
1652
|
+
"description": "The `seq` of the `gate.opened` under which this call was allowed, so every bypassed call is attributable to one human's ceremony."
|
|
1653
|
+
},
|
|
1654
|
+
"tool": {
|
|
1655
|
+
"type": "string",
|
|
1656
|
+
"minLength": 1,
|
|
1657
|
+
"description": "The harness tool the call came through (`Bash`, `Edit`, `Write`)."
|
|
1658
|
+
},
|
|
1659
|
+
"summary": {
|
|
1660
|
+
"type": "string",
|
|
1661
|
+
"maxLength": 160,
|
|
1662
|
+
"description": "The one-line headline, capped at the hook's own `SUMMARY_LIMIT`. A headline and only that: what the bytes were is `payload_hash`."
|
|
1663
|
+
},
|
|
1664
|
+
"classes": {
|
|
1665
|
+
"type": "array",
|
|
1666
|
+
"minItems": 1,
|
|
1667
|
+
"items": {
|
|
1668
|
+
"type": "string",
|
|
1669
|
+
"minLength": 1
|
|
1670
|
+
},
|
|
1671
|
+
"description": "The classes the command resolved to. The window suspends the POLICY, so the classification still happens and is still recorded: a reviewer reads what would have needed approval."
|
|
1672
|
+
},
|
|
1673
|
+
"payload_hash": {
|
|
1674
|
+
"type": "string",
|
|
1675
|
+
"pattern": "^[0-9a-f]{64}$",
|
|
1676
|
+
"description": "The `payload_hash` of the binding bytes, computed exactly as the gated path computes it."
|
|
1677
|
+
},
|
|
1678
|
+
"session_id": {
|
|
1679
|
+
"type": "string",
|
|
1680
|
+
"minLength": 1
|
|
1681
|
+
},
|
|
1682
|
+
"tool_use_id": {
|
|
1683
|
+
"type": "string",
|
|
1684
|
+
"minLength": 1
|
|
1685
|
+
},
|
|
1686
|
+
"cwd": {
|
|
1687
|
+
"type": "string",
|
|
1688
|
+
"minLength": 1
|
|
1689
|
+
}
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
1692
|
+
}
|
|
1693
|
+
}
|
|
1694
|
+
},
|
|
1695
|
+
{
|
|
1696
|
+
"$comment": "SPEC.md §9 (amended, APRV-220): a human's signature over the chain head at a moment. The chain is unkeyed, so a party with write access to `events.jsonl` can truncate it and recompute a chain that walks clean from genesis (the boundary the conformance suite states as `chain-verification/truncation-unanchored`); what that party cannot do is reproduce a signature over the hashes they replaced. The actor MUST be `^human:`, in the schema as well as in `core/checkpoint.ts`, for the reason `gate.opened` carries the same rule: an agent able to author a checkpoint could vouch for a chain it had just written. `key_sha256` is a HINT and never the authority — a verifier looks the public key up in the policy's `audit.checkpoint_keys`, so a record naming a key nobody lists is a refusal and not a self-signed pass (§11.1: self-reported fields never reduce scrutiny). Gate-typed, so `ts` is stamped at the write boundary and never accepted from the caller: the whole content of this record is a claim about a moment.",
|
|
1697
|
+
"if": {
|
|
1698
|
+
"type": "object",
|
|
1699
|
+
"properties": {
|
|
1700
|
+
"event": {
|
|
1701
|
+
"const": "log.checkpoint"
|
|
1702
|
+
}
|
|
1703
|
+
},
|
|
1704
|
+
"required": [
|
|
1705
|
+
"event"
|
|
1706
|
+
]
|
|
1707
|
+
},
|
|
1708
|
+
"then": {
|
|
1709
|
+
"type": "object",
|
|
1710
|
+
"required": [
|
|
1711
|
+
"actor",
|
|
1712
|
+
"payload"
|
|
1713
|
+
],
|
|
1714
|
+
"properties": {
|
|
1715
|
+
"actor": {
|
|
1716
|
+
"type": "string",
|
|
1717
|
+
"pattern": "^human:"
|
|
1718
|
+
},
|
|
1719
|
+
"payload": {
|
|
1720
|
+
"type": "object",
|
|
1721
|
+
"required": [
|
|
1722
|
+
"seq",
|
|
1723
|
+
"hash",
|
|
1724
|
+
"alg",
|
|
1725
|
+
"key_sha256",
|
|
1726
|
+
"signature"
|
|
1727
|
+
],
|
|
1728
|
+
"properties": {
|
|
1729
|
+
"seq": {
|
|
1730
|
+
"type": "integer",
|
|
1731
|
+
"minimum": 1,
|
|
1732
|
+
"description": "The `seq` of the record whose hash was signed. That it is below this record's own seq, and that the log carries this hash there, are the runtime's checks; a schema sees one record and can only constrain the shape."
|
|
1733
|
+
},
|
|
1734
|
+
"hash": {
|
|
1735
|
+
"type": "string",
|
|
1736
|
+
"pattern": "^[0-9a-f]{64}$",
|
|
1737
|
+
"description": "The chain hash at that seq, as the signer read it. A chain recomputed after this record was written cannot produce a signature over this value, which is the whole of what a checkpoint proves."
|
|
1738
|
+
},
|
|
1739
|
+
"alg": {
|
|
1740
|
+
"type": "string",
|
|
1741
|
+
"enum": [
|
|
1742
|
+
"ed25519"
|
|
1743
|
+
],
|
|
1744
|
+
"description": "The signature scheme. One value at v0.1, and an enum rather than a free string so that a second scheme is an explicit schema change and never a value a signer invents."
|
|
1745
|
+
},
|
|
1746
|
+
"key_sha256": {
|
|
1747
|
+
"type": "string",
|
|
1748
|
+
"pattern": "^[0-9a-f]{64}$",
|
|
1749
|
+
"description": "SHA-256 of the signing key's DER SPKI bytes, hex. A pointer, not a credential and not an authority: the verifier resolves it against `audit.checkpoint_keys` in the attested policy, and a fingerprint naming a key the policy does not list is refused. Recording the fingerprint rather than the key itself is deliberate — a record carrying its own public key invites a reader to verify the signature against it, which any forger could satisfy."
|
|
1750
|
+
},
|
|
1751
|
+
"signature": {
|
|
1752
|
+
"type": "string",
|
|
1753
|
+
"minLength": 1,
|
|
1754
|
+
"pattern": "^[A-Za-z0-9+/]+={0,2}$",
|
|
1755
|
+
"description": "Base64 Ed25519 signature over `\"approval.md/log-checkpoint/v1\\n\"` followed by the RFC 8785 canonicalization of `{alg, hash, seq}`. Domain-separated, so a signature made here cannot be lifted into another use of the same key and one made elsewhere cannot be presented as a checkpoint."
|
|
1756
|
+
}
|
|
1757
|
+
}
|
|
1758
|
+
}
|
|
1759
|
+
}
|
|
1760
|
+
}
|
|
1761
|
+
},
|
|
1762
|
+
{
|
|
1763
|
+
"$comment": "SPEC.md §5.2/§8 (amended, APRV-272): a human's sign-off on the exact bytes of one of the gate's ORGANS — the harness files that install the hook (`.claude/settings*`, `.cursor/hooks.json`, `.cursor/hooks/`, `.cursor/agents/`). Those paths classify `policy.core`, which is human-only, so the gate mints no grant for them and no grant-shaped evidence for a hand edit to one can exist; content attestation is the only evidence there is. The actor MUST be `^human:`, in the schema as well as in `core/attest.ts`, for the reason `gate.opened` carries the same rule: an agent able to attest an organ could vouch for its own way out of the gate. This is NOT a policy attestation and no reader of one sees it: the policy attestation is `policy.updated`, every reader selects on that type, and the two must never be confused. 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.",
|
|
1764
|
+
"if": {
|
|
1765
|
+
"type": "object",
|
|
1766
|
+
"properties": {
|
|
1767
|
+
"event": {
|
|
1768
|
+
"const": "gate.organ.attested"
|
|
1769
|
+
}
|
|
1770
|
+
},
|
|
1771
|
+
"required": [
|
|
1772
|
+
"event"
|
|
1773
|
+
]
|
|
1774
|
+
},
|
|
1775
|
+
"then": {
|
|
1776
|
+
"type": "object",
|
|
1777
|
+
"required": [
|
|
1778
|
+
"actor",
|
|
1779
|
+
"payload"
|
|
1780
|
+
],
|
|
1781
|
+
"properties": {
|
|
1782
|
+
"actor": {
|
|
1783
|
+
"type": "string",
|
|
1784
|
+
"pattern": "^human:"
|
|
1785
|
+
},
|
|
1786
|
+
"payload": {
|
|
1787
|
+
"type": "object",
|
|
1788
|
+
"required": [
|
|
1789
|
+
"organ_path",
|
|
1790
|
+
"sha256"
|
|
1791
|
+
],
|
|
1792
|
+
"properties": {
|
|
1793
|
+
"organ_path": {
|
|
1794
|
+
"type": "string",
|
|
1795
|
+
"minLength": 1,
|
|
1796
|
+
"maxLength": 512,
|
|
1797
|
+
"pattern": "^[^/\\\\][^\\\\]*$",
|
|
1798
|
+
"description": "The organ's REPOSITORY-RELATIVE, `/`-separated path, for example `.claude/settings.json`. Relative and never absolute, because a permanent record must not leak the writer's home directory and must name the same file on the machine that later reads it. The whole path and not a basename, unlike `policy.updated`'s `policy_path`: there are several organs in several directories, and the guard's rule is that a digest attested for one path is not evidence for another. That the path actually classifies `policy.core` is the runtime's check (`core/attest.ts`), as is the refusal of `..`; a schema sees one record and constrains the shape."
|
|
1799
|
+
},
|
|
1800
|
+
"sha256": {
|
|
1801
|
+
"type": "string",
|
|
1802
|
+
"pattern": "^[a-f0-9]{64}$",
|
|
1803
|
+
"description": "SHA-256 (lowercase hex) of the organ file's exact bytes, computed by the runtime. Bytes, not text: no encoding round-trip and no normalization, so a file differing by trailing whitespace is a different file. The caller has no parameter for this value — one who could supply it could attest bytes nobody read."
|
|
1804
|
+
}
|
|
1805
|
+
}
|
|
1806
|
+
}
|
|
1807
|
+
}
|
|
1808
|
+
}
|
|
1809
|
+
}
|
|
1810
|
+
]
|
|
1811
|
+
}
|