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,1035 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The adapter contract (SPEC.md §4, §6.2, §10.4, §11).
|
|
3
|
+
*
|
|
4
|
+
* An **adapter** is a side-effect executor: the thing that actually sends the
|
|
5
|
+
* email, writes the calendar entry, moves the file. SPEC.md §10.4 makes it the
|
|
6
|
+
* hard boundary of the whole system: credentials reach an adapter only inside
|
|
7
|
+
* a runtime-authorized execution window.
|
|
8
|
+
*
|
|
9
|
+
* That boundary describes a *sequence*, not a property of any one function:
|
|
10
|
+
* recompute the payload hash, verify the applicable authority and consume a
|
|
11
|
+
* token where required, record that the execution started, act, record how it
|
|
12
|
+
* ended. An adapter that owned that
|
|
13
|
+
* sequence could skip a step — and the step it would skip is whichever one was
|
|
14
|
+
* inconvenient the week the adapter was written. So the sequence lives here,
|
|
15
|
+
* once, and an adapter implements exactly one method:
|
|
16
|
+
*
|
|
17
|
+
* ```ts
|
|
18
|
+
* act(input: ActInput): Promise<ActOutcome> | ActOutcome
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* {@link executeThroughAdapter} owns everything around that call. It is not a
|
|
22
|
+
* helper an adapter may choose; it is the only door, in the same sense that
|
|
23
|
+
* `core/token.ts`'s `consumeToken` is the only sanctioned way to append a
|
|
24
|
+
* manual `execution.started`.
|
|
25
|
+
*
|
|
26
|
+
* ## The five things the contract does that an adapter therefore cannot skip
|
|
27
|
+
*
|
|
28
|
+
* 1. **Recompute the hash.** Amended §10.4: "Adapters and `approval run` MUST
|
|
29
|
+
* recompute the hash of the payload they are about to execute and MUST
|
|
30
|
+
* refuse, with a distinct machine-readable reason (`payload-mismatch`), when
|
|
31
|
+
* it differs from the hash the grant recorded." The contract hashes
|
|
32
|
+
* `request.payload` with `core/payload.ts` — the same canonicalizer the log
|
|
33
|
+
* uses — and hands the digest to the token spend. An adapter is never asked
|
|
34
|
+
* what its payload hashes to, because an executor that could *state* its
|
|
35
|
+
* hash could state the approved one while holding different bytes.
|
|
36
|
+
* 2. **Check the class before touching the log.** An adapter declares the
|
|
37
|
+
* classes it serves. An adapter asked to execute an action declared under
|
|
38
|
+
* some other class is refused `adapter-class-mismatch` with the log
|
|
39
|
+
* untouched: the declaration is read from `task.registered` (the log, not the
|
|
40
|
+
* caller's claim), and nothing is appended, because nothing happened.
|
|
41
|
+
* 3. **Start before acting.** {@link startExecution} appends `execution.started`
|
|
42
|
+
* *before* `act` is called, and a refusal there means `act` is never called
|
|
43
|
+
* at all. A log that recorded an execution only once it succeeded could not
|
|
44
|
+
* tell you about the one that did not.
|
|
45
|
+
* 3b. **Resolve declared credentials before spending the token** (APRV-169). An
|
|
46
|
+
* adapter names the credentials it cannot act without, and the contract
|
|
47
|
+
* resolves them before `startExecution`. A missing one refuses
|
|
48
|
+
* `credential-unavailable` with the log untouched and the grant intact,
|
|
49
|
+
* because a configuration fault must not consume a human's single-use
|
|
50
|
+
* authority. The side effect's own ordering is unchanged: the token is still
|
|
51
|
+
* consumed and `execution.started` still appended before `act` runs.
|
|
52
|
+
* 3c. **Ask the adapter what it can learn before the spend** (APRV-276). An
|
|
53
|
+
* adapter MAY implement {@link Adapter.precheck}, and the contract calls it
|
|
54
|
+
* after the credentials resolve and before the token is consumed. It is the
|
|
55
|
+
* home of one specific refusal: the condition that makes the side effect
|
|
56
|
+
* impossible, that this runtime can learn without attempting it, and that
|
|
57
|
+
* would otherwise cost a human's single-use grant to discover. AgentMail's
|
|
58
|
+
* drift check is the worked case. A refusal appends nothing, spends nothing,
|
|
59
|
+
* and returns `adapter-precheck-refused`. It does not replace the same check
|
|
60
|
+
* inside `act`: the far side can move in between, and the check that binds
|
|
61
|
+
* the bytes actually sent is the later one.
|
|
62
|
+
* 4. **Scope the credentials.** The provider handed to `act` is a wrapper that
|
|
63
|
+
* closes when `act` returns. Inside the verified execution window it answers;
|
|
64
|
+
* outside it, every `get` refuses `credential-window-closed`. An adapter that
|
|
65
|
+
* stashes the provider and reads it later gets a refusal rather than a
|
|
66
|
+
* secret, so the execution window is a mechanism instead of an intention.
|
|
67
|
+
* 5. **Redact.** Every string the contract is about to return is scanned for
|
|
68
|
+
* each credential value the provider handed out during the window, and hits
|
|
69
|
+
* are replaced with {@link REDACTION_PLACEHOLDER} and counted. SPEC.md §11.1
|
|
70
|
+
* invariant 3 ("raw secrets never appear in the log") is the reason adapters
|
|
71
|
+
* exist; here it is a mechanical check rather than a convention. Note what
|
|
72
|
+
* reaches the log from an adapter: one bounded field. Since APRV-251 a
|
|
73
|
+
* completion may carry `provider_ref`, the identifier the provider files the
|
|
74
|
+
* effect under, and the sweep runs BEFORE that record is appended so the id
|
|
75
|
+
* is scanned exactly as the rest of the detail is. Everything else in the
|
|
76
|
+
* adapter's own vocabulary rides in the returned result, which is scanned
|
|
77
|
+
* before it is handed back.
|
|
78
|
+
*
|
|
79
|
+
* ## What is deliberately not here
|
|
80
|
+
*
|
|
81
|
+
* No vault. {@link CredentialProvider} is the seam a real vault implements
|
|
82
|
+
* (APRV-68); this module ships {@link inMemoryCredentials} for tests and
|
|
83
|
+
* {@link NO_CREDENTIALS}, which refuses everything, as the default. A runtime
|
|
84
|
+
* that wires no provider therefore fails closed: an adapter that needs a secret
|
|
85
|
+
* to act cannot act.
|
|
86
|
+
*
|
|
87
|
+
* No token verification, consumption, or append logic. Those are
|
|
88
|
+
* `core/token.ts` and `core/execute.ts`, called here and reimplemented nowhere.
|
|
89
|
+
*
|
|
90
|
+
* ## Two callers, one core path
|
|
91
|
+
*
|
|
92
|
+
* `approval run` (`src/cli/execute.ts`) is the other caller of the same core
|
|
93
|
+
* path: a command is an adapter whose `act` is `spawnSync`, whose payload is
|
|
94
|
+
* §6.2's `{argv, cwd}`, and whose credentials are the ambient environment. It
|
|
95
|
+
* calls `startExecution` and `finishExecution` directly rather than through this
|
|
96
|
+
* module, because its stdio, exit-code transparency, and `--` argv split are CLI
|
|
97
|
+
* concerns with nothing to do with adapters. The two callers share the core
|
|
98
|
+
* verbs, not this wrapper; anything that must hold for both belongs in
|
|
99
|
+
* `core/execute.ts`, and a rule added here alone protects adapters only.
|
|
100
|
+
*
|
|
101
|
+
* Deterministic and total: no clock of its own (it forwards
|
|
102
|
+
* {@link ExecuteOptions.clock}), no randomness, and nothing here throws — an
|
|
103
|
+
* adapter that throws is caught and recorded as a failed execution.
|
|
104
|
+
*/
|
|
105
|
+
import { EXECUTE_REFUSAL_CODES, checkExecutionEligibility, declaringTasks, finishExecution, findDeclaration, indeterminateExecution, providerRefRecordable, startExecution, hasApprovalCycle, } from "../core/execute.js";
|
|
106
|
+
import { request as requestApproval } from "../core/gate.js";
|
|
107
|
+
import { payloadHash } from "../core/payload.js";
|
|
108
|
+
import { loadPolicy } from "../core/policy-load.js";
|
|
109
|
+
import { resolve } from "../core/policy-match.js";
|
|
110
|
+
import { payloadOf, readVerifiedRecords } from "../core/state.js";
|
|
111
|
+
import { PAYLOAD_HASH_FIELD, TOKEN_HASH_FIELD, digestsEqual, tokenHash } from "../core/token.js";
|
|
112
|
+
// ---------------------------------------------------------------------------
|
|
113
|
+
// Credentials
|
|
114
|
+
// ---------------------------------------------------------------------------
|
|
115
|
+
/**
|
|
116
|
+
* Why a credential was not handed over. Frozen union, per SPEC.md §11.1(6).
|
|
117
|
+
*
|
|
118
|
+
* The three are distinguished because they call for three different responses:
|
|
119
|
+
* fix the configuration, ask a human, or fix the adapter.
|
|
120
|
+
*/
|
|
121
|
+
export const CREDENTIAL_REFUSAL_CODES = [
|
|
122
|
+
/** No such credential is configured. The repair is configuration. */
|
|
123
|
+
"credential-unavailable",
|
|
124
|
+
/** The provider knows it and declined: policy, a locked vault, a human's no. */
|
|
125
|
+
"credential-refused",
|
|
126
|
+
/**
|
|
127
|
+
* The verified execution window has closed: `act` has already returned, and the
|
|
128
|
+
* provider it was handed is no longer live. Distinct from the two above
|
|
129
|
+
* because nothing is wrong with the credential or the configuration — the
|
|
130
|
+
* adapter asked at the wrong time, which is a defect in the adapter and is
|
|
131
|
+
* reported as one.
|
|
132
|
+
*/
|
|
133
|
+
"credential-window-closed",
|
|
134
|
+
];
|
|
135
|
+
/** The default: no vault is wired, so nothing is handed out. Fails closed. */
|
|
136
|
+
export const NO_CREDENTIALS = {
|
|
137
|
+
get(name) {
|
|
138
|
+
return {
|
|
139
|
+
ok: false,
|
|
140
|
+
code: "credential-unavailable",
|
|
141
|
+
message: `no credential provider is configured, so ${JSON.stringify(name)} cannot be supplied. SPEC.md §10.4 puts the credentials behind the adapter boundary; wire a provider explicitly rather than reading the environment from inside an adapter.`,
|
|
142
|
+
};
|
|
143
|
+
},
|
|
144
|
+
};
|
|
145
|
+
/**
|
|
146
|
+
* A provider over a literal map. **Tests and fixtures only** — it holds secrets
|
|
147
|
+
* in process memory in the clear, which is precisely what the vault (APRV-68)
|
|
148
|
+
* exists to stop doing.
|
|
149
|
+
*/
|
|
150
|
+
export function inMemoryCredentials(entries) {
|
|
151
|
+
return {
|
|
152
|
+
get(name) {
|
|
153
|
+
const value = Object.prototype.hasOwnProperty.call(entries, name)
|
|
154
|
+
? entries[name]
|
|
155
|
+
: undefined;
|
|
156
|
+
if (typeof value !== "string") {
|
|
157
|
+
return {
|
|
158
|
+
ok: false,
|
|
159
|
+
code: "credential-unavailable",
|
|
160
|
+
message: `no credential named ${JSON.stringify(name)} is configured`,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
return { ok: true, value };
|
|
164
|
+
},
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Wrap `inner` in a window that closes.
|
|
169
|
+
*
|
|
170
|
+
* The mechanism is a closure over a boolean, not a revoked reference: the
|
|
171
|
+
* adapter may keep the object it was handed for as long as it likes, and the
|
|
172
|
+
* object will refuse. Values handed out are remembered so the contract can scan
|
|
173
|
+
* its own output for them; the *names* are not interesting and the values never
|
|
174
|
+
* leave this set.
|
|
175
|
+
*/
|
|
176
|
+
function scopeCredentials(inner) {
|
|
177
|
+
let open = true;
|
|
178
|
+
const issued = new Set();
|
|
179
|
+
return {
|
|
180
|
+
provider: {
|
|
181
|
+
get(name) {
|
|
182
|
+
if (!open) {
|
|
183
|
+
return {
|
|
184
|
+
ok: false,
|
|
185
|
+
code: "credential-window-closed",
|
|
186
|
+
message: `credential ${JSON.stringify(name)} was requested after act() returned. Credentials are reachable only inside the verified execution window: the execution has already been recorded, so a secret handed over now would be outside the authority that opened the call.`,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
const result = inner.get(name);
|
|
190
|
+
if (result.ok && result.value.length > 0)
|
|
191
|
+
issued.add(result.value);
|
|
192
|
+
return result;
|
|
193
|
+
},
|
|
194
|
+
},
|
|
195
|
+
issued,
|
|
196
|
+
close() {
|
|
197
|
+
open = false;
|
|
198
|
+
},
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
// ---------------------------------------------------------------------------
|
|
202
|
+
// Redaction (SPEC.md §11.1 invariant 3, mechanically)
|
|
203
|
+
// ---------------------------------------------------------------------------
|
|
204
|
+
/** What a redacted credential value is replaced with. */
|
|
205
|
+
export const REDACTION_PLACEHOLDER = "[redacted]";
|
|
206
|
+
/**
|
|
207
|
+
* Replace every occurrence of every secret in `text`.
|
|
208
|
+
*
|
|
209
|
+
* Empty secrets are skipped, because "replace every occurrence of the empty
|
|
210
|
+
* string" redacts a document into nothing and would hide the very message a
|
|
211
|
+
* reader needs. Everything else is replaced literally (no regex, no escaping
|
|
212
|
+
* question), including a secret that appears as a substring of a longer word:
|
|
213
|
+
* over-redaction is the safe direction, and a credential that happens to be a
|
|
214
|
+
* common word is a credential problem, not a scanner problem.
|
|
215
|
+
*/
|
|
216
|
+
export function redactSecrets(text, secrets) {
|
|
217
|
+
let out = text;
|
|
218
|
+
let hits = 0;
|
|
219
|
+
for (const secret of secrets) {
|
|
220
|
+
if (secret.length === 0)
|
|
221
|
+
continue;
|
|
222
|
+
let index = out.indexOf(secret);
|
|
223
|
+
while (index !== -1) {
|
|
224
|
+
hits += 1;
|
|
225
|
+
out = out.slice(0, index) + REDACTION_PLACEHOLDER + out.slice(index + secret.length);
|
|
226
|
+
index = out.indexOf(secret, index + REDACTION_PLACEHOLDER.length);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
return { text: out, hits };
|
|
230
|
+
}
|
|
231
|
+
/** Does `text` contain any of `secrets`? The assertion form of the guard. */
|
|
232
|
+
export function containsSecret(text, secrets) {
|
|
233
|
+
for (const secret of secrets) {
|
|
234
|
+
if (secret.length === 0)
|
|
235
|
+
continue;
|
|
236
|
+
if (text.includes(secret))
|
|
237
|
+
return true;
|
|
238
|
+
}
|
|
239
|
+
return false;
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Walk `value` and redact every string in it, keys included.
|
|
243
|
+
*
|
|
244
|
+
* Keys are scanned as well as values because a leak does not care which side of
|
|
245
|
+
* the colon it lands on: `{"sk-live-…": "used"}` publishes the secret exactly as
|
|
246
|
+
* effectively as the other arrangement.
|
|
247
|
+
*/
|
|
248
|
+
export function redactJson(value, secrets) {
|
|
249
|
+
const list = [...secrets].filter((secret) => secret.length > 0);
|
|
250
|
+
if (list.length === 0)
|
|
251
|
+
return { value, hits: 0 };
|
|
252
|
+
let hits = 0;
|
|
253
|
+
const walk = (node) => {
|
|
254
|
+
if (typeof node === "string") {
|
|
255
|
+
const redacted = redactSecrets(node, list);
|
|
256
|
+
hits += redacted.hits;
|
|
257
|
+
return redacted.text;
|
|
258
|
+
}
|
|
259
|
+
if (Array.isArray(node))
|
|
260
|
+
return node.map(walk);
|
|
261
|
+
if (typeof node === "object" && node !== null) {
|
|
262
|
+
const out = {};
|
|
263
|
+
for (const [key, member] of Object.entries(node)) {
|
|
264
|
+
const redactedKey = redactSecrets(key, list);
|
|
265
|
+
hits += redactedKey.hits;
|
|
266
|
+
out[redactedKey.text] = walk(member);
|
|
267
|
+
}
|
|
268
|
+
return out;
|
|
269
|
+
}
|
|
270
|
+
return node;
|
|
271
|
+
};
|
|
272
|
+
return { value: walk(value), hits };
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* The one key by which a success `detail` NAMES the provider's own identifier
|
|
276
|
+
* for the effect (APRV-251, SPEC.md §8).
|
|
277
|
+
*
|
|
278
|
+
* An adapter that wants its effect joinable by id puts a short printable string
|
|
279
|
+
* at the top level of its detail under this key, beside whatever else its
|
|
280
|
+
* receipt says. Everything after that is the contract's: the value passes the
|
|
281
|
+
* redaction sweep with the rest of the detail, the adapter half of the record
|
|
282
|
+
* is this runtime's own knowledge of which adapter it called, and the record is
|
|
283
|
+
* written by {@link finishExecution}.
|
|
284
|
+
*
|
|
285
|
+
* ONE conventional key rather than a guess across `message_id`, `sid`, `id` and
|
|
286
|
+
* whatever the next provider calls it. A contract that guessed would sooner or
|
|
287
|
+
* later lift the wrong field of some receipt onto a permanent log, and an
|
|
288
|
+
* adapter that says nothing under this key is treated as naming no reference,
|
|
289
|
+
* which is the pre-amendment behaviour and always valid.
|
|
290
|
+
*/
|
|
291
|
+
export const PROVIDER_REF_DETAIL_KEY = "provider_ref";
|
|
292
|
+
/**
|
|
293
|
+
* The reference to record for this call, or `null` for none.
|
|
294
|
+
*
|
|
295
|
+
* Given BOTH the raw detail the adapter returned and the redacted copy about to
|
|
296
|
+
* be handed back, because the interesting case is the one where they differ. A
|
|
297
|
+
* lifted id whose bytes the redaction sweep touched is dropped rather than
|
|
298
|
+
* recorded: `[redacted]` matches no provider's record, and writing it would put
|
|
299
|
+
* a value in the join column that reads exactly like one that means something.
|
|
300
|
+
* The credential itself never reaches the log either way, since what would be
|
|
301
|
+
* written is the redacted copy.
|
|
302
|
+
*
|
|
303
|
+
* Everything else it declines is declined for the same reason the schema would
|
|
304
|
+
* reject it (an absent key, a value that is not a string, a string that is
|
|
305
|
+
* empty, too long, or carries a space or a control character). Declining here
|
|
306
|
+
* rather than at the append is deliberate: a record the write boundary rejects
|
|
307
|
+
* would leave a side effect that already happened with no outcome in the log,
|
|
308
|
+
* and a completion carrying no reference is the better failure.
|
|
309
|
+
*/
|
|
310
|
+
export function providerRefFor(adapterName, rawDetail, redactedDetail) {
|
|
311
|
+
const read = (node) => {
|
|
312
|
+
if (typeof node !== "object" || node === null || Array.isArray(node))
|
|
313
|
+
return null;
|
|
314
|
+
const named = node[PROVIDER_REF_DETAIL_KEY];
|
|
315
|
+
return typeof named === "string" ? named : null;
|
|
316
|
+
};
|
|
317
|
+
const raw = read(rawDetail);
|
|
318
|
+
if (raw === null)
|
|
319
|
+
return null;
|
|
320
|
+
// The redacted copy is what a reader would get, so it is what is compared.
|
|
321
|
+
if (read(redactedDetail) !== raw)
|
|
322
|
+
return null;
|
|
323
|
+
const ref = { adapter: adapterName, id: raw };
|
|
324
|
+
return providerRefRecordable(ref) ? ref : null;
|
|
325
|
+
}
|
|
326
|
+
// ---------------------------------------------------------------------------
|
|
327
|
+
// Refusals
|
|
328
|
+
// ---------------------------------------------------------------------------
|
|
329
|
+
/**
|
|
330
|
+
* Everything {@link executeThroughAdapter} can refuse. Frozen public API, per
|
|
331
|
+
* SPEC.md §11.1(6), and a strict superset of {@link EXECUTE_REFUSAL_CODES}:
|
|
332
|
+
* every core refusal surfaces verbatim rather than being collapsed into an
|
|
333
|
+
* adapter-flavoured one, because `token-consumed`, `payload-mismatch` and
|
|
334
|
+
* `budget-exceeded` call for three different responses whether the caller is an
|
|
335
|
+
* adapter or `approval run`.
|
|
336
|
+
*/
|
|
337
|
+
export const ADAPTER_REFUSAL_CODES = [
|
|
338
|
+
...EXECUTE_REFUSAL_CODES,
|
|
339
|
+
/** Request intake could not find the task registration. */
|
|
340
|
+
"not-registered",
|
|
341
|
+
/** A concurrent caller opened the live-selected request first. */
|
|
342
|
+
"duplicate-request",
|
|
343
|
+
/** Live-selected intake reached the approver queue ceiling. */
|
|
344
|
+
"queue-full",
|
|
345
|
+
/** Live-selected intake reached the origin's request rate limit. */
|
|
346
|
+
"rate-limited",
|
|
347
|
+
/** A selected live action had no declaration hash to bind. */
|
|
348
|
+
"payload-hash-required",
|
|
349
|
+
/** A selected live self-delivery address could not be created. */
|
|
350
|
+
"token-delivery-unavailable",
|
|
351
|
+
/** Live intake could not retain the exact payload for human display or audit. */
|
|
352
|
+
"payload-store-failed",
|
|
353
|
+
/**
|
|
354
|
+
* The adapter does not serve the class this action was declared under.
|
|
355
|
+
* Refused before anything is appended: routing an action to the wrong
|
|
356
|
+
* executor is a wiring mistake, and a wiring mistake must not consume a
|
|
357
|
+
* single-use token or leave a dangling execution behind.
|
|
358
|
+
*/
|
|
359
|
+
"adapter-class-mismatch",
|
|
360
|
+
/**
|
|
361
|
+
* The payload has no RFC 8785 canonical form, so there is no hash to check
|
|
362
|
+
* against the grant. Nothing is appended and no token is spent.
|
|
363
|
+
*/
|
|
364
|
+
"payload-unhashable",
|
|
365
|
+
/**
|
|
366
|
+
* `act` ran and reported failure. `execution.failed` WAS appended: the side
|
|
367
|
+
* effect was attempted, and the log says so. `adapter_code` carries the
|
|
368
|
+
* adapter's own reason.
|
|
369
|
+
*/
|
|
370
|
+
"adapter-failed",
|
|
371
|
+
/**
|
|
372
|
+
* A throw on the way to `act`, before it was entered: assembling the
|
|
373
|
+
* credential window, or reading the adapter's own method. Nothing was
|
|
374
|
+
* attempted, so `execution.failed` is the honest record and the caller may
|
|
375
|
+
* fix its wiring and try again.
|
|
376
|
+
*
|
|
377
|
+
* A throw from INSIDE `act` is a different code (`execution-indeterminate`)
|
|
378
|
+
* and a different event, because the outcome is then unknown (APRV-120).
|
|
379
|
+
*
|
|
380
|
+
* Only the error's `message` is kept — never the stack, which routinely
|
|
381
|
+
* quotes arguments and would be a credential-shaped leak with a plausible
|
|
382
|
+
* excuse — and even that message passes the redaction guard.
|
|
383
|
+
*/
|
|
384
|
+
"adapter-act-threw",
|
|
385
|
+
/**
|
|
386
|
+
* A credential the adapter declared in {@link Adapter.requiredCredentials}
|
|
387
|
+
* could not be resolved (APRV-169). Refused BEFORE the token is consumed and
|
|
388
|
+
* before `execution.started` is appended, so the grant survives: the log is
|
|
389
|
+
* left exactly as it was found and the same token is spendable once the
|
|
390
|
+
* credential appears.
|
|
391
|
+
*
|
|
392
|
+
* The provider's own reason (`credential-unavailable`, `credential-refused`)
|
|
393
|
+
* rides in `adapter_code`, because "nobody stored an SMTP password" and "the
|
|
394
|
+
* vault would not open" are two different repairs.
|
|
395
|
+
*/
|
|
396
|
+
"credential-unavailable",
|
|
397
|
+
/**
|
|
398
|
+
* The adapter's own {@link Adapter.precheck} answered no (APRV-276). Refused
|
|
399
|
+
* BEFORE the token is consumed and before `execution.started` is appended, so
|
|
400
|
+
* the grant survives exactly as it does for `credential-unavailable`: the log
|
|
401
|
+
* is left as it was found and the same token is spendable once the condition
|
|
402
|
+
* the precheck named is repaired.
|
|
403
|
+
*
|
|
404
|
+
* The adapter's own reason rides in `adapter_code`
|
|
405
|
+
* (`agentmail-draft-drifted`, say), because the repair belongs to the far
|
|
406
|
+
* side's vocabulary and this union cannot enumerate it.
|
|
407
|
+
*
|
|
408
|
+
* A precheck that throws lands here too, under `adapter_code`
|
|
409
|
+
* `precheck-threw`: a check that could not be performed is not a check that
|
|
410
|
+
* passed, and the fail-closed answer costs nothing, since nothing was
|
|
411
|
+
* attempted.
|
|
412
|
+
*/
|
|
413
|
+
"adapter-precheck-refused",
|
|
414
|
+
];
|
|
415
|
+
/** The exit code recorded for an execution the adapter did not complete. */
|
|
416
|
+
const ADAPTER_FAILURE_EXIT_CODE = 1;
|
|
417
|
+
/**
|
|
418
|
+
* Does `token` match the digest the log's grant for `actionKey` recorded?
|
|
419
|
+
*
|
|
420
|
+
* A pure comparison over records this path has already read and verified, and
|
|
421
|
+
* deliberately NOT a second opinion about whether the token may be spent:
|
|
422
|
+
* `core/token.ts` owns TTL, revocation and the single-use check, and
|
|
423
|
+
* `startExecution` runs all three before anything is appended. What this answers
|
|
424
|
+
* is the narrower question the pre-token phase needs: does the caller hold the
|
|
425
|
+
* token a human's grant minted for this action?
|
|
426
|
+
*
|
|
427
|
+
* Constant-time, through `digestsEqual`. `false` for everything else: no grant,
|
|
428
|
+
* no recorded digest, no token.
|
|
429
|
+
*/
|
|
430
|
+
function tokenMatchesGrant(records, actionKey, token) {
|
|
431
|
+
if (token === undefined || token.length === 0)
|
|
432
|
+
return false;
|
|
433
|
+
let recorded;
|
|
434
|
+
for (const record of records) {
|
|
435
|
+
if (record.event !== "approval.granted")
|
|
436
|
+
continue;
|
|
437
|
+
if (record.action_key !== actionKey)
|
|
438
|
+
continue;
|
|
439
|
+
recorded = payloadOf(record)[TOKEN_HASH_FIELD];
|
|
440
|
+
}
|
|
441
|
+
if (typeof recorded !== "string" || recorded.length === 0)
|
|
442
|
+
return false;
|
|
443
|
+
return digestsEqual(tokenHash(token), recorded);
|
|
444
|
+
}
|
|
445
|
+
/**
|
|
446
|
+
* Does the log bind this action to exactly the bytes the caller presented?
|
|
447
|
+
*
|
|
448
|
+
* Read for one purpose (APRV-276): deciding whether {@link Adapter.precheck}
|
|
449
|
+
* may run at all. A precheck is handed the payload, and every word of its
|
|
450
|
+
* contract assumes those bytes are the approved ones — AgentMail compares the
|
|
451
|
+
* live draft against them and calls a difference drift. Handed bytes no human
|
|
452
|
+
* approved, the same comparison is a question about the wrong snapshot, and its
|
|
453
|
+
* answer is noise at best: a refusal naming the caller's own edit as the far
|
|
454
|
+
* side's drift, or a pass that says a tampered payload matches the draft.
|
|
455
|
+
*
|
|
456
|
+
* So this is a precondition and never a verdict. It answers no and the precheck
|
|
457
|
+
* is SKIPPED, not refused: `startExecution` is the authority on content binding
|
|
458
|
+
* and refuses `payload-mismatch` in its own words a few lines later, with
|
|
459
|
+
* nothing appended and the token still live. Two modules refusing the same fact
|
|
460
|
+
* is two places for the reason to drift.
|
|
461
|
+
*
|
|
462
|
+
* The binding it compares against is the one that path's authorization actually
|
|
463
|
+
* uses, which is why both are read here: the grant's recorded `payload_hash` on
|
|
464
|
+
* the manual path (`core/token.ts`), and the registered declaration's off it
|
|
465
|
+
* (`core/execute.ts`), with the declaration standing in for a grant that
|
|
466
|
+
* recorded none, exactly as `verifyToken` lets it. A binding that exists
|
|
467
|
+
* nowhere answers no, so the strict path is the default: an action whose bytes
|
|
468
|
+
* nothing in the log states is one no precheck may reason about.
|
|
469
|
+
*/
|
|
470
|
+
function logBindsPayload(records, actionKey, declared, hash) {
|
|
471
|
+
let bound = declared.payload_hash;
|
|
472
|
+
for (const record of records) {
|
|
473
|
+
if (record.event !== "approval.granted")
|
|
474
|
+
continue;
|
|
475
|
+
if (record.action_key !== actionKey)
|
|
476
|
+
continue;
|
|
477
|
+
const recorded = payloadOf(record)[PAYLOAD_HASH_FIELD];
|
|
478
|
+
bound = typeof recorded === "string" && recorded.length > 0 ? recorded : declared.payload_hash;
|
|
479
|
+
}
|
|
480
|
+
if (bound === null)
|
|
481
|
+
return false;
|
|
482
|
+
return digestsEqual(bound, hash);
|
|
483
|
+
}
|
|
484
|
+
/**
|
|
485
|
+
* Hand `grant` to a provider that asked to be told, and swallow whatever it does
|
|
486
|
+
* about it.
|
|
487
|
+
*
|
|
488
|
+
* A provider is not required to implement `grant`, and one that implements it
|
|
489
|
+
* badly must not be able to fail an execution: this call carries no secret, no
|
|
490
|
+
* decision, and nothing the contract needs back. Every path that leaves the
|
|
491
|
+
* execution passes `null` through here, so a capability opened for a window is
|
|
492
|
+
* closed on every exit, refusals included.
|
|
493
|
+
*/
|
|
494
|
+
function tell(provider, grant) {
|
|
495
|
+
try {
|
|
496
|
+
provider.grant?.(grant);
|
|
497
|
+
}
|
|
498
|
+
catch {
|
|
499
|
+
// Nothing to report and nothing to repair: the contract asked, not asked for.
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
/**
|
|
503
|
+
* Resolve every credential the adapter declared it cannot act without, before
|
|
504
|
+
* the token is consumed (APRV-169).
|
|
505
|
+
*
|
|
506
|
+
* The ordering this exists to establish, stated once: a credential the runtime
|
|
507
|
+
* cannot reach is a configuration fault, and a configuration fault must not
|
|
508
|
+
* spend a human's single-use grant. Resolving here means a
|
|
509
|
+
* `credential-unavailable` refusal costs no authority: nothing is appended, the
|
|
510
|
+
* token stays spendable, and the same token executes once the credential
|
|
511
|
+
* appears.
|
|
512
|
+
*
|
|
513
|
+
* What this does NOT reorder is the consume-then-execute sequence for the side
|
|
514
|
+
* effect itself. `startExecution` still consumes the token and appends
|
|
515
|
+
* `execution.started` before `act` is called, because that ordering is what
|
|
516
|
+
* makes a crash between the two an ambiguity the log can SEE rather than one it
|
|
517
|
+
* is silent about (APRV-120). Credentials move ahead of the token; the side
|
|
518
|
+
* effect does not.
|
|
519
|
+
*
|
|
520
|
+
* The reads happen in a window of their own that closes immediately, so a
|
|
521
|
+
* provider handed here is never left live outside a scope, and every value it
|
|
522
|
+
* hands over joins the redaction corpus of the refusal this may return. Nothing
|
|
523
|
+
* read here is kept or returned: what comes back is whether the name resolved,
|
|
524
|
+
* and `act` asks for the values itself inside the verified execution window.
|
|
525
|
+
*
|
|
526
|
+
* Note what an unauthorized caller learns by invoking this path: whether the
|
|
527
|
+
* names THIS ADAPTER statically declared are present in a provider the caller
|
|
528
|
+
* supplied in the first place. It is not a channel for asking about arbitrary
|
|
529
|
+
* names, and a caller holding the provider could ask it directly anyway.
|
|
530
|
+
*/
|
|
531
|
+
function resolveRequiredCredentials(adapter, actionKey, inner) {
|
|
532
|
+
const names = adapter.requiredCredentials ?? [];
|
|
533
|
+
if (names.length === 0)
|
|
534
|
+
return { ok: true, issued: new Set() };
|
|
535
|
+
const scope = scopeCredentials(inner);
|
|
536
|
+
try {
|
|
537
|
+
for (const name of names) {
|
|
538
|
+
let got;
|
|
539
|
+
try {
|
|
540
|
+
got = scope.provider.get(name);
|
|
541
|
+
}
|
|
542
|
+
catch (error) {
|
|
543
|
+
// A provider is required to be total. One that throws anyway is a
|
|
544
|
+
// configuration this runtime cannot use, and it is reported as one
|
|
545
|
+
// rather than allowed to escape: nothing here throws.
|
|
546
|
+
got = {
|
|
547
|
+
ok: false,
|
|
548
|
+
code: "credential-unavailable",
|
|
549
|
+
message: `the credential provider raised instead of answering: ${error instanceof Error ? error.message : String(error)}`,
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
if (got.ok)
|
|
553
|
+
continue;
|
|
554
|
+
const redacted = redactSecrets(`the ${adapter.name} adapter declares that it cannot act without the credential ${JSON.stringify(name)}, and it did not resolve (${got.code}): ${got.message}. Nothing was appended and no token was spent: credentials resolve before the token is consumed (SPEC.md §10.4, APRV-169), so this refusal costs no authority and the same token executes once the credential is reachable.`, scope.issued);
|
|
555
|
+
return {
|
|
556
|
+
ok: false,
|
|
557
|
+
credentialCode: got.code,
|
|
558
|
+
message: redacted.text,
|
|
559
|
+
redactions: redacted.hits,
|
|
560
|
+
};
|
|
561
|
+
}
|
|
562
|
+
return { ok: true, issued: scope.issued };
|
|
563
|
+
}
|
|
564
|
+
finally {
|
|
565
|
+
scope.close();
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
/**
|
|
569
|
+
* Ask the adapter what it can find out before the token is spent (APRV-276).
|
|
570
|
+
*
|
|
571
|
+
* The ordering this exists to establish, stated once and alongside
|
|
572
|
+
* {@link resolveRequiredCredentials}'s: a condition that makes the side effect
|
|
573
|
+
* impossible, and that this runtime can learn without attempting it, must not
|
|
574
|
+
* cost a human's single-use grant. A credential nobody stored was the first
|
|
575
|
+
* such condition (APRV-169); a draft the agent edited after the human read it
|
|
576
|
+
* is the second, and the difference between them is only whose fault it is.
|
|
577
|
+
* Both refuse with the log untouched and the token spendable.
|
|
578
|
+
*
|
|
579
|
+
* What this does NOT reorder is the consume-then-execute sequence for the side
|
|
580
|
+
* effect itself, for the reason APRV-120 gives: `startExecution` still consumes
|
|
581
|
+
* the token and appends `execution.started` before `act` is called, so a crash
|
|
582
|
+
* between the two is an ambiguity the log can SEE. Nor does it move a check OUT
|
|
583
|
+
* of `act`: an adapter that prechecks a condition checks it again inside the
|
|
584
|
+
* window, because the far side can move in between and the check that binds the
|
|
585
|
+
* bytes actually sent is the later one.
|
|
586
|
+
*
|
|
587
|
+
* The window is its own and closes immediately, exactly as the credential
|
|
588
|
+
* resolution's does, and every value the provider handed over joins the
|
|
589
|
+
* redaction corpus of whatever this returns. So does `inherited`, everything
|
|
590
|
+
* the resolution above already handed this adapter, and it has to: a precheck
|
|
591
|
+
* that throws typically throws holding a secret it was given a step earlier and
|
|
592
|
+
* never asked this window for, so scanning only what THIS window issued would
|
|
593
|
+
* scan an empty set and print the credential. An adapter that implements no
|
|
594
|
+
* precheck pays nothing: the function returns at its first line.
|
|
595
|
+
*
|
|
596
|
+
* Total: a precheck that throws is reported as a refusal rather than allowed to
|
|
597
|
+
* escape. A check that could not be performed is not a check that passed, and
|
|
598
|
+
* failing closed here is free, since nothing has been attempted.
|
|
599
|
+
*/
|
|
600
|
+
async function runPrecheck(adapter, actionKey, payload, inner, inherited, signal) {
|
|
601
|
+
const precheck = adapter.precheck;
|
|
602
|
+
if (precheck === undefined)
|
|
603
|
+
return { ok: true, issued: new Set() };
|
|
604
|
+
const scope = scopeCredentials(inner);
|
|
605
|
+
try {
|
|
606
|
+
let outcome;
|
|
607
|
+
try {
|
|
608
|
+
const input = {
|
|
609
|
+
actionKey,
|
|
610
|
+
payload,
|
|
611
|
+
credentials: scope.provider,
|
|
612
|
+
...(signal === undefined ? {} : { signal }),
|
|
613
|
+
};
|
|
614
|
+
outcome = await precheck.call(adapter, input);
|
|
615
|
+
}
|
|
616
|
+
catch (error) {
|
|
617
|
+
// The message and nothing else, for `act`'s reason: a stack trace quotes
|
|
618
|
+
// call arguments, and a credential passed as one would ride out inside an
|
|
619
|
+
// error report.
|
|
620
|
+
outcome = {
|
|
621
|
+
ok: false,
|
|
622
|
+
code: "precheck-threw",
|
|
623
|
+
message: `the ${adapter.name} adapter's pre-token check raised instead of answering: ${error instanceof Error ? error.message : String(error)}`,
|
|
624
|
+
};
|
|
625
|
+
}
|
|
626
|
+
if (outcome.ok)
|
|
627
|
+
return { ok: true, issued: scope.issued };
|
|
628
|
+
// Everything this window issued, plus everything the resolution above
|
|
629
|
+
// already handed over. A refusal is printed, and both sets are secrets it
|
|
630
|
+
// could be printing.
|
|
631
|
+
const secrets = new Set(inherited);
|
|
632
|
+
for (const secret of scope.issued)
|
|
633
|
+
secrets.add(secret);
|
|
634
|
+
const code = redactSecrets(outcome.code, secrets);
|
|
635
|
+
const message = redactSecrets(`${adapter.name} refused action ${actionKey} before the token was spent (${outcome.code}): ${outcome.message}. Nothing was appended and no token was spent: an adapter's pre-token check runs before the token is consumed (SPEC.md §10.4, APRV-276), so this refusal costs no authority and the same token executes once the condition it names is repaired.`, secrets);
|
|
636
|
+
return {
|
|
637
|
+
ok: false,
|
|
638
|
+
adapterCode: code.text,
|
|
639
|
+
message: message.text,
|
|
640
|
+
redactions: code.hits + message.hits,
|
|
641
|
+
};
|
|
642
|
+
}
|
|
643
|
+
finally {
|
|
644
|
+
scope.close();
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
/**
|
|
648
|
+
* Copy the core-relevant options across, and bind the hash the contract
|
|
649
|
+
* computed. Built explicitly rather than spread so that a future field added to
|
|
650
|
+
* {@link AdapterExecuteOptions} (a credential provider, a transport handle) is
|
|
651
|
+
* not silently forwarded into core.
|
|
652
|
+
*/
|
|
653
|
+
function executeOptionsFrom(options, presentedPayloadHash) {
|
|
654
|
+
return {
|
|
655
|
+
...(options.token === undefined ? {} : { token: options.token }),
|
|
656
|
+
...(options.policy === undefined ? {} : { policy: options.policy }),
|
|
657
|
+
...(options.schemaDir === undefined ? {} : { schemaDir: options.schemaDir }),
|
|
658
|
+
...(options.append === undefined ? {} : { append: options.append }),
|
|
659
|
+
...(options.clock === undefined ? {} : { clock: options.clock }),
|
|
660
|
+
...(options.keyStoreDir === undefined ? {} : { keyStoreDir: options.keyStoreDir }),
|
|
661
|
+
...(options.retryOnHeadMoved === undefined
|
|
662
|
+
? {}
|
|
663
|
+
: { retryOnHeadMoved: options.retryOnHeadMoved }),
|
|
664
|
+
presentedPayloadHash,
|
|
665
|
+
};
|
|
666
|
+
}
|
|
667
|
+
function liveIntakeOptionsFrom(options) {
|
|
668
|
+
return {
|
|
669
|
+
...(options.policy === undefined ? {} : { policy: options.policy }),
|
|
670
|
+
...(options.schemaDir === undefined ? {} : { schemaDir: options.schemaDir }),
|
|
671
|
+
...(options.append === undefined ? {} : { append: options.append }),
|
|
672
|
+
...(options.clock === undefined ? {} : { clock: options.clock }),
|
|
673
|
+
...(options.keyStoreDir === undefined ? {} : { keyStoreDir: options.keyStoreDir }),
|
|
674
|
+
...(options.retryOnHeadMoved === undefined
|
|
675
|
+
? {}
|
|
676
|
+
: { retryOnHeadMoved: options.retryOnHeadMoved }),
|
|
677
|
+
...(options.liveIntake?.env === undefined ? {} : { env: options.liveIntake.env }),
|
|
678
|
+
...(options.liveIntake?.drawAsk === undefined
|
|
679
|
+
? {}
|
|
680
|
+
: { drawAsk: options.liveIntake.drawAsk }),
|
|
681
|
+
};
|
|
682
|
+
}
|
|
683
|
+
function refuse(adapter, actionKey, code, message, extra = {}) {
|
|
684
|
+
return {
|
|
685
|
+
ok: false,
|
|
686
|
+
code,
|
|
687
|
+
message,
|
|
688
|
+
adapter: adapter.name,
|
|
689
|
+
action_key: actionKey,
|
|
690
|
+
acted: false,
|
|
691
|
+
redactions: 0,
|
|
692
|
+
...extra,
|
|
693
|
+
};
|
|
694
|
+
}
|
|
695
|
+
/**
|
|
696
|
+
* Execute one approved action through `adapter`, and own every step around the
|
|
697
|
+
* adapter's own.
|
|
698
|
+
*
|
|
699
|
+
* The order, and why it is this order:
|
|
700
|
+
*
|
|
701
|
+
* 1. **Hash the payload.** Before any log read, because a payload with no
|
|
702
|
+
* canonical form has nothing to check and nothing to execute.
|
|
703
|
+
* 2. **Read the declaration** from the verified log and check the class. Both
|
|
704
|
+
* before `startExecution`, so a misrouted action leaves the log exactly as it
|
|
705
|
+
* found it. (The log is read twice on this path — once here, once inside
|
|
706
|
+
* `startExecution`, which reads for itself and compare-and-appends against
|
|
707
|
+
* the head it read. That is not redundancy to remove: a routing check that
|
|
708
|
+
* handed its records to core would be core trusting a caller's snapshot.)
|
|
709
|
+
* 3. **Resolve the declared credentials** (APRV-169), in a window of their own
|
|
710
|
+
* that closes at once. A name the provider cannot answer refuses
|
|
711
|
+
* `credential-unavailable` here, with the log untouched and the token
|
|
712
|
+
* unspent, so a missing secret costs no authority and the same token works
|
|
713
|
+
* once the secret appears. An adapter declaring none skips this entirely.
|
|
714
|
+
* 3b. **The adapter's own pre-token check** (APRV-276), in a window of its own,
|
|
715
|
+
* on the same presented-phase grant. Whatever it refuses is refused with the
|
|
716
|
+
* log untouched and the token unspent, so a condition the runtime could
|
|
717
|
+
* learn without attempting the side effect costs no authority to discover.
|
|
718
|
+
* An adapter implementing none skips this entirely, and so does a payload
|
|
719
|
+
* the log does not bind this action to: step 4 owns that refusal.
|
|
720
|
+
* 4. **`startExecution`**, which on the manual path verifies and consumes the
|
|
721
|
+
* token, refuses `payload-mismatch` against the digest from step 1, and
|
|
722
|
+
* appends `execution.started`. Any refusal here returns with `acted: false`;
|
|
723
|
+
* `act` is not called, so no side effect was attempted.
|
|
724
|
+
* 5. **`act`**, inside a credential window that closes the moment it returns.
|
|
725
|
+
* 6. **The outcome event**, and WHICH one depends on where things went wrong
|
|
726
|
+
* (APRV-120). `act` returning success is `execution.completed`; `act`
|
|
727
|
+
* returning a failure is `execution.failed`, because the provider answered
|
|
728
|
+
* and the answer was no; a throw on the way INTO `act` is `execution.failed`
|
|
729
|
+
* too, because nothing was attempted; and a throw from inside `act` is
|
|
730
|
+
* `execution.indeterminate`, because the provider may or may not have
|
|
731
|
+
* committed and this runtime cannot tell. The boundary is the invocation
|
|
732
|
+
* itself, not a judgment about the error.
|
|
733
|
+
*
|
|
734
|
+
* A refusal from step 6 is returned with `started_seq` set and the log left
|
|
735
|
+
* holding a dangling execution — which is the honest state, since the side
|
|
736
|
+
* effect did happen and its outcome could not be recorded. `approval status`
|
|
737
|
+
* reports it and `approval execution resolve` is how a human closes it. An
|
|
738
|
+
* indeterminate outcome is not that: it IS recorded, the consumption stays
|
|
739
|
+
* burned, a retry is refused, and `approval execution reconcile` is how a
|
|
740
|
+
* person resolves it from the relying party's own evidence.
|
|
741
|
+
*/
|
|
742
|
+
export async function executeThroughAdapter(adapter, request, options = {}) {
|
|
743
|
+
const { logPath, actionKey, payload, actor } = request;
|
|
744
|
+
// (a) The hash, computed here from the bytes, never accepted from a caller.
|
|
745
|
+
let hash;
|
|
746
|
+
try {
|
|
747
|
+
hash = payloadHash(payload);
|
|
748
|
+
}
|
|
749
|
+
catch (error) {
|
|
750
|
+
return refuse(adapter, actionKey, "payload-unhashable", `the payload for ${actionKey} has no RFC 8785 canonical serialization (${error instanceof Error ? error.message : String(error)}), so it cannot be hashed and cannot be shown to be the bytes the grant or registered declaration bound. Nothing was appended.`);
|
|
751
|
+
}
|
|
752
|
+
// (b) The declared class, from the verified log.
|
|
753
|
+
const read = readVerifiedRecords(logPath, options.schemaDir === undefined ? {} : { schemaDir: options.schemaDir });
|
|
754
|
+
if (!read.ok)
|
|
755
|
+
return refuse(adapter, actionKey, read.code, read.message);
|
|
756
|
+
const declaring = declaringTasks(read.records, actionKey);
|
|
757
|
+
if (declaring.length > 1) {
|
|
758
|
+
return refuse(adapter, actionKey, "action-not-registered", `action key ${JSON.stringify(actionKey)} is declared by more than one task (${declaring.join(", ")}); the runtime refuses an ambiguous declaration rather than route the later one. Registration refuses such collisions (APRV-138).`);
|
|
759
|
+
}
|
|
760
|
+
const declared = findDeclaration(read.records, actionKey);
|
|
761
|
+
if (declared === null) {
|
|
762
|
+
return refuse(adapter, actionKey, "action-not-registered", `no task.registered record declares an action with idempotency_key ${JSON.stringify(actionKey)}; SPEC.md §7 requires a class to be declared before the action can execute, and an adapter cannot supply the declaration on the action's behalf.`);
|
|
763
|
+
}
|
|
764
|
+
if (!adapter.classes.includes(declared.class)) {
|
|
765
|
+
return refuse(adapter, actionKey, "adapter-class-mismatch", `adapter ${JSON.stringify(adapter.name)} serves ${adapter.classes.length === 0 ? "no classes" : adapter.classes.map((cls) => JSON.stringify(cls)).join(", ")}, and action ${actionKey} is declared under class ${JSON.stringify(declared.class)}. The action was routed to the wrong executor; nothing was appended and no token was spent.`);
|
|
766
|
+
}
|
|
767
|
+
// Supervised-live selection belongs to gate intake. Its unselected result is
|
|
768
|
+
// intentionally absent from the log, so an adapter cannot infer that intake
|
|
769
|
+
// happened merely because no approval.requested exists. On a genuinely
|
|
770
|
+
// no-token, no-cycle call, invoke the existing request path from fields read
|
|
771
|
+
// exclusively from the verified declaration. Selected or unavailable draws
|
|
772
|
+
// create the ordinary pending cycle; an unselected draw appends no approval
|
|
773
|
+
// event and continues. A prior cycle, including a rejected or expired one,
|
|
774
|
+
// is never redrawn here.
|
|
775
|
+
const policyOptions = options.policy ?? {};
|
|
776
|
+
const load = loadPolicy({
|
|
777
|
+
...(policyOptions.file === undefined
|
|
778
|
+
? { dir: policyOptions.dir ?? process.cwd() }
|
|
779
|
+
: { file: policyOptions.file }),
|
|
780
|
+
...(options.schemaDir === undefined ? {} : { schemaDir: options.schemaDir }),
|
|
781
|
+
});
|
|
782
|
+
const initialResolution = resolve(load, declared.class, declared.reversible === null ? {} : { reversible: declared.reversible });
|
|
783
|
+
let livePolicySha256;
|
|
784
|
+
if ((options.token === undefined || options.token.length === 0) &&
|
|
785
|
+
initialResolution.supervision === "live" &&
|
|
786
|
+
!hasApprovalCycle(read.records, actionKey)) {
|
|
787
|
+
const intake = requestApproval(logPath, {
|
|
788
|
+
task: declared.task,
|
|
789
|
+
actionKey,
|
|
790
|
+
cls: declared.class,
|
|
791
|
+
est_cost_usd: declared.est_cost_usd,
|
|
792
|
+
...(declared.reversible === null ? {} : { reversible: declared.reversible }),
|
|
793
|
+
...(declared.summary === null ? {} : { summary: declared.summary }),
|
|
794
|
+
...(declared.payload_hash === null ? {} : { payload_hash: declared.payload_hash }),
|
|
795
|
+
payload: { value: payload },
|
|
796
|
+
delivery: "self",
|
|
797
|
+
}, actor, liveIntakeOptionsFrom(options));
|
|
798
|
+
if (!intake.ok) {
|
|
799
|
+
return refuse(adapter, actionKey, intake.code, intake.message);
|
|
800
|
+
}
|
|
801
|
+
if (intake.proceed && intake.live !== undefined) {
|
|
802
|
+
livePolicySha256 = intake.policySha256;
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
// A no-token path must prove that core would admit this exact execution
|
|
806
|
+
// before credentials or a provider-backed precheck are touched. This result
|
|
807
|
+
// is only a preflight: startExecution repeats every check after precheck and
|
|
808
|
+
// remains the sole writer of execution.started.
|
|
809
|
+
const eligibilityOptions = {
|
|
810
|
+
...executeOptionsFrom(options, hash),
|
|
811
|
+
...(livePolicySha256 === undefined
|
|
812
|
+
? {}
|
|
813
|
+
: { expectedPolicySha256: livePolicySha256 }),
|
|
814
|
+
};
|
|
815
|
+
const eligibility = checkExecutionEligibility(logPath, actionKey, eligibilityOptions, actor);
|
|
816
|
+
if (!eligibility.ok) {
|
|
817
|
+
return refuse(adapter, actionKey, eligibility.code, eligibility.message, {
|
|
818
|
+
execute: eligibility,
|
|
819
|
+
});
|
|
820
|
+
}
|
|
821
|
+
const effectiveToken = eligibility.mode === "token" ? eligibility.token : undefined;
|
|
822
|
+
// (c) The credentials the adapter declared it cannot act without, resolved
|
|
823
|
+
// BEFORE the token is consumed (APRV-169). See
|
|
824
|
+
// {@link resolveRequiredCredentials} for why this sits here and why the
|
|
825
|
+
// consume-then-execute ordering below is untouched.
|
|
826
|
+
const provider = options.credentials ?? NO_CREDENTIALS;
|
|
827
|
+
// The presented-phase grant (APRV-168). Minted only when the caller's token
|
|
828
|
+
// matches the digest a human's grant recorded for this action, so a provider
|
|
829
|
+
// may offer a capability here on the strength of that decision. Everything
|
|
830
|
+
// that decides whether the token may actually be SPENT still happens below,
|
|
831
|
+
// in `startExecution`, and nothing here appends or consumes.
|
|
832
|
+
const presented = tokenMatchesGrant(read.records, actionKey, effectiveToken)
|
|
833
|
+
? { phase: "presented", actionKey, startedSeq: null, autonomy: null }
|
|
834
|
+
: null;
|
|
835
|
+
tell(provider, presented);
|
|
836
|
+
const resolved = resolveRequiredCredentials(adapter, actionKey, provider);
|
|
837
|
+
if (!resolved.ok) {
|
|
838
|
+
tell(provider, null);
|
|
839
|
+
return refuse(adapter, actionKey, "credential-unavailable", resolved.message, {
|
|
840
|
+
adapter_code: resolved.credentialCode,
|
|
841
|
+
redactions: resolved.redactions,
|
|
842
|
+
});
|
|
843
|
+
}
|
|
844
|
+
// (c2) The adapter's own pre-token check (APRV-276), in a window of its own,
|
|
845
|
+
// still on the presented-phase grant. A refusal here appends nothing and
|
|
846
|
+
// spends nothing, for the same reason (c) does.
|
|
847
|
+
//
|
|
848
|
+
// Skipped outright unless the log binds this action to the bytes in
|
|
849
|
+
// hand: a precheck asked about a payload no human approved is being
|
|
850
|
+
// asked the wrong question, and answering it would let an adapter's own
|
|
851
|
+
// vocabulary describe what is really a `payload-mismatch`. Those bytes
|
|
852
|
+
// get no precheck and no far-side request; `startExecution` refuses them
|
|
853
|
+
// below, in core's words, with the log untouched and the token live.
|
|
854
|
+
const prechecked = logBindsPayload(read.records, actionKey, declared, hash)
|
|
855
|
+
? await runPrecheck(adapter, actionKey, payload, provider, resolved.issued, options.signal)
|
|
856
|
+
: { ok: true, issued: new Set() };
|
|
857
|
+
if (!prechecked.ok) {
|
|
858
|
+
tell(provider, null);
|
|
859
|
+
return refuse(adapter, actionKey, "adapter-precheck-refused", prechecked.message, {
|
|
860
|
+
adapter_code: prechecked.adapterCode,
|
|
861
|
+
redactions: prechecked.redactions,
|
|
862
|
+
});
|
|
863
|
+
}
|
|
864
|
+
// (d) Authorization and the start event. Refused here means act never runs.
|
|
865
|
+
const executeOptions = {
|
|
866
|
+
...eligibilityOptions,
|
|
867
|
+
...(effectiveToken === undefined ? {} : { token: effectiveToken }),
|
|
868
|
+
};
|
|
869
|
+
const started = startExecution(logPath, actionKey, executeOptions, actor);
|
|
870
|
+
if (!started.ok) {
|
|
871
|
+
tell(provider, null);
|
|
872
|
+
return refuse(adapter, actionKey, started.code, started.message, { execute: started });
|
|
873
|
+
}
|
|
874
|
+
const startedSeq = started.record.seq;
|
|
875
|
+
// (e) The adapter's own step, inside the credential window.
|
|
876
|
+
//
|
|
877
|
+
// `entered` is the custody boundary of APRV-120, and it is positional rather
|
|
878
|
+
// than a judgment: everything up to and including the read of `adapter.act`
|
|
879
|
+
// is preparation this runtime performed and can speak for, and the instant
|
|
880
|
+
// after it the call is the far side's. A throw on the near side is
|
|
881
|
+
// `execution.failed` — nothing was attempted. A throw on the far side is
|
|
882
|
+
// `execution.indeterminate` — the provider may or may not have committed, and
|
|
883
|
+
// saying "failed" about it is the assertion that produces double sends.
|
|
884
|
+
const scope = scopeCredentials(provider);
|
|
885
|
+
// Everything the pre-token resolution was handed is already a secret this
|
|
886
|
+
// path may be about to print, so it joins the corpus before `act` runs rather
|
|
887
|
+
// than only if `act` happens to ask for the same names again. The precheck's
|
|
888
|
+
// window is closed by now and its values join for the same reason.
|
|
889
|
+
for (const secret of resolved.issued)
|
|
890
|
+
scope.issued.add(secret);
|
|
891
|
+
for (const secret of prechecked.issued)
|
|
892
|
+
scope.issued.add(secret);
|
|
893
|
+
// The window is now a GRANTED one, and the provider is told so (APRV-168).
|
|
894
|
+
// Minted here because this is the only place that can: the brand on
|
|
895
|
+
// `ExecutionGrant` is a symbol no other module can name. It is dropped in the
|
|
896
|
+
// same `finally` that closes the window, so the capability it carries lives
|
|
897
|
+
// exactly as long as `act` does.
|
|
898
|
+
// The brand is a type-level marker with no runtime member (a `declare const
|
|
899
|
+
// unique symbol` emits nothing), so the cast is what mints it. That is the
|
|
900
|
+
// point: the cast compiles here and refuses to compile anywhere the brand
|
|
901
|
+
// cannot be named, which is everywhere else.
|
|
902
|
+
tell(provider, {
|
|
903
|
+
phase: "consumed",
|
|
904
|
+
actionKey,
|
|
905
|
+
startedSeq,
|
|
906
|
+
autonomy: started.autonomy,
|
|
907
|
+
...(started.tokenSha256 === undefined ? {} : { tokenSha256: started.tokenSha256 }),
|
|
908
|
+
});
|
|
909
|
+
let entered = false;
|
|
910
|
+
let outcome;
|
|
911
|
+
let threw = null;
|
|
912
|
+
try {
|
|
913
|
+
const input = {
|
|
914
|
+
actionKey,
|
|
915
|
+
payload,
|
|
916
|
+
credentials: scope.provider,
|
|
917
|
+
...(options.signal === undefined ? {} : { signal: options.signal }),
|
|
918
|
+
};
|
|
919
|
+
const act = adapter.act.bind(adapter);
|
|
920
|
+
entered = true;
|
|
921
|
+
outcome = await act(input);
|
|
922
|
+
}
|
|
923
|
+
catch (error) {
|
|
924
|
+
// The message and nothing else: a stack trace quotes call arguments, and a
|
|
925
|
+
// credential passed as one would ride out of here inside an error report.
|
|
926
|
+
threw = error instanceof Error ? error.message : String(error);
|
|
927
|
+
outcome = { ok: false, code: "adapter-act-threw", message: threw };
|
|
928
|
+
}
|
|
929
|
+
finally {
|
|
930
|
+
scope.close();
|
|
931
|
+
// The window and the grant end together. A provider that kept the grant is
|
|
932
|
+
// holding a value the contract no longer honours, and it was told so.
|
|
933
|
+
tell(provider, null);
|
|
934
|
+
}
|
|
935
|
+
// (f) The redaction sweep, THEN the outcome event, then the redacted result.
|
|
936
|
+
//
|
|
937
|
+
// The sweep runs first because one thing it produces now goes into the record
|
|
938
|
+
// rather than only into the result: the provider reference of APRV-251, which
|
|
939
|
+
// the adapter names inside its own detail and which therefore passes exactly
|
|
940
|
+
// the scan every other returned string passes before anything writes it down.
|
|
941
|
+
// Nothing else about the ordering matters — the sweep reads the outcome and
|
|
942
|
+
// the issued secrets, both settled the moment `act` returned.
|
|
943
|
+
const secrets = scope.issued;
|
|
944
|
+
const unknown = threw !== null && entered;
|
|
945
|
+
const exitCode = outcome.ok ? 0 : ADAPTER_FAILURE_EXIT_CODE;
|
|
946
|
+
const failureCode = threw === null ? "adapter-failed" : "adapter-act-threw";
|
|
947
|
+
const rawMessage = outcome.ok ? "" : outcome.message;
|
|
948
|
+
const message = redactSecrets(rawMessage, secrets);
|
|
949
|
+
const adapterCode = redactSecrets(outcome.ok ? "" : outcome.code, secrets);
|
|
950
|
+
const detail = outcome.ok && outcome.detail !== undefined
|
|
951
|
+
? redactJson(outcome.detail, secrets)
|
|
952
|
+
: { value: null, hits: 0 };
|
|
953
|
+
const redactions = message.hits + adapterCode.hits + detail.hits;
|
|
954
|
+
// Only a completion has an effect for a provider to have filed, and only the
|
|
955
|
+
// adapter can say what its receipt called it. The `adapter` half is this
|
|
956
|
+
// runtime's own knowledge of which adapter it called, never a claim the
|
|
957
|
+
// detail makes about itself.
|
|
958
|
+
const providerRef = outcome.ok
|
|
959
|
+
? providerRefFor(adapter.name, outcome.detail, detail.value)
|
|
960
|
+
: null;
|
|
961
|
+
const finished = unknown
|
|
962
|
+
? indeterminateExecution(logPath, actionKey, "act-threw", actor, executeOptions)
|
|
963
|
+
: finishExecution(logPath, actionKey, exitCode, actor, {
|
|
964
|
+
...executeOptions,
|
|
965
|
+
...(providerRef === null ? {} : { providerRef }),
|
|
966
|
+
});
|
|
967
|
+
if (!finished.ok) {
|
|
968
|
+
return {
|
|
969
|
+
ok: false,
|
|
970
|
+
code: finished.code,
|
|
971
|
+
message: `${adapter.name} ${outcome.ok ? "completed" : "did not complete"} action ${actionKey}, and the outcome could not be recorded: ${finished.message}. The side effect was attempted and the log now holds a dangling execution; close it with \`approval execution resolve\`.`,
|
|
972
|
+
adapter: adapter.name,
|
|
973
|
+
action_key: actionKey,
|
|
974
|
+
acted: entered,
|
|
975
|
+
started_seq: startedSeq,
|
|
976
|
+
execute: finished,
|
|
977
|
+
redactions,
|
|
978
|
+
};
|
|
979
|
+
}
|
|
980
|
+
if (unknown) {
|
|
981
|
+
// The message is the adapter's, redacted, and it reaches the CALLER only:
|
|
982
|
+
// the record carries the closed reason and nothing else (APRV-120 #3).
|
|
983
|
+
return {
|
|
984
|
+
ok: false,
|
|
985
|
+
code: "execution-indeterminate",
|
|
986
|
+
message: `${adapter.name} entered act for ${actionKey} and raised, so the log records execution.indeterminate at seq ${finished.record.seq}: the side effect was attempted and nobody knows whether it committed. The token is spent and the idempotency key is burned; a retry is refused. Establish what happened and record it with \`approval execution reconcile\`. The adapter reported: ${message.text}`,
|
|
987
|
+
adapter: adapter.name,
|
|
988
|
+
action_key: actionKey,
|
|
989
|
+
acted: true,
|
|
990
|
+
started_seq: startedSeq,
|
|
991
|
+
outcome: "execution.indeterminate",
|
|
992
|
+
outcome_seq: finished.record.seq,
|
|
993
|
+
adapter_code: adapterCode.text,
|
|
994
|
+
redactions,
|
|
995
|
+
};
|
|
996
|
+
}
|
|
997
|
+
if (!outcome.ok) {
|
|
998
|
+
return {
|
|
999
|
+
ok: false,
|
|
1000
|
+
code: failureCode,
|
|
1001
|
+
message: message.text,
|
|
1002
|
+
adapter: adapter.name,
|
|
1003
|
+
action_key: actionKey,
|
|
1004
|
+
// A throw BEFORE act was entered attempted nothing: the credential window
|
|
1005
|
+
// or the adapter's own method raised, and no side effect was reached.
|
|
1006
|
+
acted: entered,
|
|
1007
|
+
started_seq: startedSeq,
|
|
1008
|
+
outcome: "execution.failed",
|
|
1009
|
+
outcome_seq: finished.record.seq,
|
|
1010
|
+
exit_code: exitCode,
|
|
1011
|
+
adapter_code: adapterCode.text,
|
|
1012
|
+
redactions,
|
|
1013
|
+
};
|
|
1014
|
+
}
|
|
1015
|
+
return {
|
|
1016
|
+
ok: true,
|
|
1017
|
+
adapter: adapter.name,
|
|
1018
|
+
action_key: actionKey,
|
|
1019
|
+
task: started.task,
|
|
1020
|
+
class: started.class,
|
|
1021
|
+
autonomy: started.autonomy,
|
|
1022
|
+
payload_hash: hash,
|
|
1023
|
+
started_seq: startedSeq,
|
|
1024
|
+
outcome: "execution.completed",
|
|
1025
|
+
outcome_seq: finished.record.seq,
|
|
1026
|
+
exit_code: 0,
|
|
1027
|
+
...(outcome.detail === undefined ? {} : { detail: detail.value }),
|
|
1028
|
+
// What the log now says this effect is called on the provider's side, so a
|
|
1029
|
+
// caller reads the recorded reference rather than re-deriving it from the
|
|
1030
|
+
// detail and reaching a different answer (APRV-251).
|
|
1031
|
+
...(providerRef === null ? {} : { provider_ref: providerRef }),
|
|
1032
|
+
redactions,
|
|
1033
|
+
};
|
|
1034
|
+
}
|
|
1035
|
+
//# sourceMappingURL=contract.js.map
|