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,1206 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `approval` CLI entry point — the commands of SPEC.md §10.1: the log-facing
|
|
3
|
+
* `approval log verify | tail | export` and `approval reindex`, the policy
|
|
4
|
+
* verbs, and the gate verbs `register`, `request`, `grant`, `reject`, `revoke`,
|
|
5
|
+
* and `expire`.
|
|
6
|
+
*
|
|
7
|
+
* **The CLI holds no logic.** Chain verification lives in `core/verify.ts`, the
|
|
8
|
+
* projection in `core/reindex.ts`, and appends in `core/log.ts`. Everything
|
|
9
|
+
* here is argument parsing, path resolution, output formatting, and the mapping
|
|
10
|
+
* from a core result to an exit code. That boundary is deliberate: the rules
|
|
11
|
+
* about what counts as a clean log must have exactly one implementation, and it
|
|
12
|
+
* is not this one.
|
|
13
|
+
*
|
|
14
|
+
* **Two things are frozen public API**, because agents depend on them
|
|
15
|
+
* mechanically: the exit codes (see `exit-codes.ts`) and the `--json` shapes
|
|
16
|
+
* (documented in every `--help`). Both are pinned by tests.
|
|
17
|
+
*
|
|
18
|
+
* **I/O is not integrity.** `verify()` cannot tell an unreadable log from a
|
|
19
|
+
* broken one, so this layer stats and access-checks the path *first* and
|
|
20
|
+
* reports filesystem problems as {@link EXIT_IO} with a message that never uses
|
|
21
|
+
* the word "corrupt". Absent files are exempt: an empty log is clean.
|
|
22
|
+
*
|
|
23
|
+
* Nothing in this file writes to the log, and no command repairs a torn tail.
|
|
24
|
+
* The gate verbs do append — through `core/gate.ts`, which appends through
|
|
25
|
+
* `core/log.ts` — and their exit-code mapping lives in `gate.ts` beside them: a
|
|
26
|
+
* gate refusal is {@link EXIT_INTEGRITY}, because the command was well-formed
|
|
27
|
+
* and the runtime's answer was no.
|
|
28
|
+
*/
|
|
29
|
+
import { pathToFileURL } from "node:url";
|
|
30
|
+
import { boolFlag, countFlag, parseFlags, stringFlag } from "./args.js";
|
|
31
|
+
import { EXIT_INTEGRITY, EXIT_IO, EXIT_OK, EXIT_TORN_TAIL, EXIT_USAGE, } from "./exit-codes.js";
|
|
32
|
+
import { EXPORT_HELP, FOLLOW_HELP, LOG_HELP, REINDEX_HELP, ROOT_HELP, TAIL_HELP, VERIFY_HELP, } from "./help.js";
|
|
33
|
+
import { DEFAULT_INDEX_PATH, DEFAULT_LOG_PATH, preflightLog, resolvePath, } from "./paths.js";
|
|
34
|
+
import { parseLines, readCompleteLines } from "./records.js";
|
|
35
|
+
import { helpFor, longHelp } from "./long-help.js";
|
|
36
|
+
import { refusal as renderRefusal, resetStyle, style, table, } from "./style.js";
|
|
37
|
+
import { usageErrorText } from "./usage.js";
|
|
38
|
+
import { VERSION, wordmark } from "./wordmark.js";
|
|
39
|
+
const DEFAULT_TAIL_COUNT = 10;
|
|
40
|
+
const HELP_FLAGS = { "--help": "boolean", "-h": "boolean" };
|
|
41
|
+
function defaultStreams() {
|
|
42
|
+
return {
|
|
43
|
+
out: (text) => void process.stdout.write(text),
|
|
44
|
+
err: (text) => void process.stderr.write(text),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
function emitJson(streams, value) {
|
|
48
|
+
streams.out(`${JSON.stringify(value)}\n`);
|
|
49
|
+
}
|
|
50
|
+
function emitJsonError(streams, code, message) {
|
|
51
|
+
streams.err(`${JSON.stringify({ error: { code, message } })}\n`);
|
|
52
|
+
}
|
|
53
|
+
function usageError(streams, json, message, helpText) {
|
|
54
|
+
if (json)
|
|
55
|
+
emitJsonError(streams, "usage", message);
|
|
56
|
+
else
|
|
57
|
+
streams.err(usageErrorText(message, helpText));
|
|
58
|
+
return EXIT_USAGE;
|
|
59
|
+
}
|
|
60
|
+
function ioError(streams, json, message) {
|
|
61
|
+
if (json)
|
|
62
|
+
emitJsonError(streams, "io", message);
|
|
63
|
+
else
|
|
64
|
+
streams.err(`approval: ${message}\n`);
|
|
65
|
+
return EXIT_IO;
|
|
66
|
+
}
|
|
67
|
+
function integrityError(streams, json, message) {
|
|
68
|
+
if (json)
|
|
69
|
+
emitJsonError(streams, "integrity", message);
|
|
70
|
+
else
|
|
71
|
+
streams.err(`approval: ${message}\n`);
|
|
72
|
+
return EXIT_INTEGRITY;
|
|
73
|
+
}
|
|
74
|
+
/** `--json` as seen before parsing, so parse failures can still answer in JSON. */
|
|
75
|
+
function wantsJson(argv) {
|
|
76
|
+
return argv.includes("--json");
|
|
77
|
+
}
|
|
78
|
+
/** One human-readable line per record: seq, ts, event, actor, task. */
|
|
79
|
+
function formatRecord(record) {
|
|
80
|
+
const fields = (record ?? {});
|
|
81
|
+
const cell = (value) => value === undefined || value === null ? "-" : String(value);
|
|
82
|
+
return [
|
|
83
|
+
cell(fields["seq"]),
|
|
84
|
+
cell(fields["ts"]),
|
|
85
|
+
cell(fields["event"]),
|
|
86
|
+
cell(fields["actor"]),
|
|
87
|
+
cell(fields["task"]),
|
|
88
|
+
].join("\t");
|
|
89
|
+
}
|
|
90
|
+
/** The role an actor wears in `log tail`: a human decided, a robot did not. */
|
|
91
|
+
function actorRole(actor) {
|
|
92
|
+
if (actor.startsWith("human:"))
|
|
93
|
+
return "ok";
|
|
94
|
+
if (actor.startsWith("system:"))
|
|
95
|
+
return "muted";
|
|
96
|
+
return undefined;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* `approval log tail`'s human rendering (APRV-91 #9, APRV-102).
|
|
100
|
+
*
|
|
101
|
+
* TWO SHAPES, DELIBERATELY, and the piped one is unchanged.
|
|
102
|
+
*
|
|
103
|
+
* In a pipe (and under `NO_COLOR`) this is exactly what it always was:
|
|
104
|
+
* tab-separated fields, one record per line. That shape is pinned by
|
|
105
|
+
* `tests/cli.test.ts`, printed in three `examples/*.md` transcripts, and — the
|
|
106
|
+
* reason that matters more than either — it is what `cut -f2` reads. An aligned
|
|
107
|
+
* table is a nicer thing to look at and a worse thing to pipe, because the
|
|
108
|
+
* separator stops being a character and starts being "however many spaces this
|
|
109
|
+
* particular log needed". A log tail is the surface most likely to be on the
|
|
110
|
+
* left of a pipe, so the plain bytes win there.
|
|
111
|
+
*
|
|
112
|
+
* On a terminal, where nothing is parsing the output, the columns are aligned
|
|
113
|
+
* and the brief's roles apply: the seq right-aligned so the digits line up,
|
|
114
|
+
* the event name in `key`, and the actor coloured by kind (human `ok`, agent
|
|
115
|
+
* undressed, system `muted`). Colour is redundant with the actor prefix printed
|
|
116
|
+
* beside it, as everywhere. The TIMESTAMP is left undressed against the brief's
|
|
117
|
+
* `muted`: APRV-102's rule that a copyable value is never painted outranks it,
|
|
118
|
+
* and this is the surface an operator lifts timestamps out of.
|
|
119
|
+
*
|
|
120
|
+
* Both shapes carry the same fields in the same order, so this is a change of
|
|
121
|
+
* spacing and dressing, never of content.
|
|
122
|
+
*/
|
|
123
|
+
export function renderTailHuman(records, st = style()) {
|
|
124
|
+
if (!st.enabled)
|
|
125
|
+
return records.map((record) => `${formatRecord(record)}\n`).join("");
|
|
126
|
+
const cellOf = (value) => value === undefined || value === null ? "-" : String(value);
|
|
127
|
+
const rows = records.map((record) => {
|
|
128
|
+
const fields = (record ?? {});
|
|
129
|
+
const actor = cellOf(fields["actor"]);
|
|
130
|
+
const role = actorRole(actor);
|
|
131
|
+
return [
|
|
132
|
+
// Not `value`-roled but genuinely undressed: a seq is the thing an
|
|
133
|
+
// operator retypes into `approval audit review`.
|
|
134
|
+
cellOf(fields["seq"]),
|
|
135
|
+
// The brief marks a timestamp `muted`, and APRV-102's later rule — no
|
|
136
|
+
// colour inside a value a human copies — outranks it. A dim timestamp is
|
|
137
|
+
// exactly as unpasteable as a bold one, and this is the surface an
|
|
138
|
+
// operator lifts timestamps out of. The alignment does the separating.
|
|
139
|
+
cellOf(fields["ts"]),
|
|
140
|
+
{ text: cellOf(fields["event"]), role: "key" },
|
|
141
|
+
role === undefined ? actor : { text: actor, role },
|
|
142
|
+
cellOf(fields["task"]),
|
|
143
|
+
];
|
|
144
|
+
});
|
|
145
|
+
return `${table(st, rows, { align: ["right"] })}\n`;
|
|
146
|
+
}
|
|
147
|
+
function prelude(argv, spec, helpText, streams, cwd) {
|
|
148
|
+
const json = wantsJson(argv);
|
|
149
|
+
const parsed = parseFlags(argv, { ...spec, ...HELP_FLAGS });
|
|
150
|
+
if (!parsed.ok) {
|
|
151
|
+
return { kind: "handled", code: usageError(streams, json, parsed.message, helpText) };
|
|
152
|
+
}
|
|
153
|
+
if (boolFlag(parsed.flags, "--help") || boolFlag(parsed.flags, "-h")) {
|
|
154
|
+
streams.out(`${helpText}\n`);
|
|
155
|
+
return { kind: "handled", code: EXIT_OK };
|
|
156
|
+
}
|
|
157
|
+
const extra = parsed.positionals[0];
|
|
158
|
+
if (extra !== undefined) {
|
|
159
|
+
return {
|
|
160
|
+
kind: "handled",
|
|
161
|
+
code: usageError(streams, json, `unexpected argument ${JSON.stringify(extra)}`, helpText),
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
const logPath = resolvePath(stringFlag(parsed.flags, "--log"), DEFAULT_LOG_PATH, cwd);
|
|
165
|
+
return { kind: "run", flags: parsed.flags, logPath, json };
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* The `anomalies` field, present only when there is something to report.
|
|
169
|
+
*
|
|
170
|
+
* ADDITIVE, in the strict sense the frozen `--json` shapes require: a consumer
|
|
171
|
+
* written against the pre-APRV-40 shape sees byte-identical output for every log
|
|
172
|
+
* that has no anomaly, and the key appears only when the runtime has something
|
|
173
|
+
* new to say. Omitting the empty case is deliberate rather than lazy — an
|
|
174
|
+
* always-present empty array would change the shape of every existing clean
|
|
175
|
+
* result, and those shapes are what agents parse.
|
|
176
|
+
*/
|
|
177
|
+
function anomalyField(anomalies) {
|
|
178
|
+
return anomalies.length === 0 ? {} : { anomalies };
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Print anomalies to stderr, one line each.
|
|
182
|
+
*
|
|
183
|
+
* stderr rather than stdout, and after the verdict rather than instead of it:
|
|
184
|
+
* the verdict is the answer to the question asked (does this chain verify?), and
|
|
185
|
+
* an anomaly is a note in the margin. The exit code does not move. A clean log
|
|
186
|
+
* with anomalies exits 0, because the chain verifies and skew is a judgment for
|
|
187
|
+
* a human, not a proof the runtime is entitled to enforce.
|
|
188
|
+
*/
|
|
189
|
+
function reportAnomalies(streams, anomalies) {
|
|
190
|
+
if (anomalies.length === 0)
|
|
191
|
+
return;
|
|
192
|
+
streams.err(`approval: ${anomalies.length} timestamp anomaly(ies) — the chain verifies and NOTHING is refused; these are reported for a human to weigh\n`);
|
|
193
|
+
for (const anomaly of anomalies) {
|
|
194
|
+
streams.err(`approval: ${anomaly.kind}: ${anomaly.message}\n`);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* The anchor half of `approval log verify` (APRV-219).
|
|
199
|
+
*
|
|
200
|
+
* Runs only behind `--anchor`, and only on a chain that already verified: the
|
|
201
|
+
* committed copy answers a different question from the one the chain walk
|
|
202
|
+
* answers ("does anybody else hold these records?" rather than "is this file
|
|
203
|
+
* self-consistent?"), and asking it about a log that does not verify would be
|
|
204
|
+
* deciding something from an unverified log.
|
|
205
|
+
*
|
|
206
|
+
* A divergence is an integrity refusal and exits where `corrupt` exits. A skip
|
|
207
|
+
* is a skip: a repository with no committed copy has said nothing about this
|
|
208
|
+
* log, and this verb never reports silence as a pass.
|
|
209
|
+
*/
|
|
210
|
+
function anchorField(outcome) {
|
|
211
|
+
if (outcome.status === "skip")
|
|
212
|
+
return { anchor: { status: "skip", reason: outcome.reason } };
|
|
213
|
+
return {
|
|
214
|
+
anchor: {
|
|
215
|
+
status: outcome.status,
|
|
216
|
+
rev: outcome.anchor.rev,
|
|
217
|
+
seq: outcome.anchor.head.seq,
|
|
218
|
+
hash: outcome.anchor.head.hash,
|
|
219
|
+
bytes: outcome.anchor.byteLength,
|
|
220
|
+
...(outcome.status === "diverged" ? { message: outcome.message } : {}),
|
|
221
|
+
},
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
/** The anchor line a human reads, after the chain verdict it qualifies. */
|
|
225
|
+
function reportAnchor(outcome, streams) {
|
|
226
|
+
if (outcome.status === "skip") {
|
|
227
|
+
streams.err(`approval: anchor skipped — ${outcome.reason}\n`);
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
if (outcome.status === "diverged")
|
|
231
|
+
return;
|
|
232
|
+
streams.out(`anchor ${outcome.anchor.rev}: ${outcome.detail}\n`);
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* The anchor block on the checkpoint-refusal path, where there may be none.
|
|
236
|
+
*
|
|
237
|
+
* A separate spelling rather than a nullable {@link anchorField}, so the field
|
|
238
|
+
* stays ADDITIVE in the strict sense the frozen `--json` shapes require: a
|
|
239
|
+
* consumer that never asked for `--anchor` sees no `anchor` key, on this path as
|
|
240
|
+
* on every other.
|
|
241
|
+
*/
|
|
242
|
+
function anchorField2(outcome) {
|
|
243
|
+
return outcome === null ? {} : anchorField(outcome);
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* The checkpoint half of `approval log verify` (APRV-220).
|
|
247
|
+
*
|
|
248
|
+
* The keys come from the policy, which is where the human wrote them, and an
|
|
249
|
+
* unloadable policy is a SKIP naming that rather than a pass: the check has
|
|
250
|
+
* said nothing about this log, and a verb that reported silence as a verified
|
|
251
|
+
* chain would be a verb that stopped verifying.
|
|
252
|
+
*/
|
|
253
|
+
async function runCheckpointCheck(records, cwd) {
|
|
254
|
+
const { checkLogCheckpoints, checkpointPolicyOf } = await import("../core/checkpoint.js");
|
|
255
|
+
const configured = checkpointPolicyOf({ dir: cwd });
|
|
256
|
+
return checkLogCheckpoints({
|
|
257
|
+
records,
|
|
258
|
+
publicKeys: configured.publicKeys,
|
|
259
|
+
checkpointEveryMs: configured.checkpointEveryMs,
|
|
260
|
+
keysUnavailable: configured.unloadable,
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
function checkpointField(outcome) {
|
|
264
|
+
if (outcome.status === "skip") {
|
|
265
|
+
return { checkpoints: { status: "skip", reason: outcome.reason } };
|
|
266
|
+
}
|
|
267
|
+
if (outcome.status === "refused") {
|
|
268
|
+
return {
|
|
269
|
+
checkpoints: {
|
|
270
|
+
status: "refused",
|
|
271
|
+
code: outcome.code,
|
|
272
|
+
at: outcome.at,
|
|
273
|
+
verified: outcome.checkpoints.length,
|
|
274
|
+
message: outcome.message,
|
|
275
|
+
},
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
const newest = outcome.checkpoints[outcome.checkpoints.length - 1] ?? null;
|
|
279
|
+
return {
|
|
280
|
+
checkpoints: {
|
|
281
|
+
status: "pass",
|
|
282
|
+
verified: outcome.checkpoints.length,
|
|
283
|
+
keys: outcome.keys,
|
|
284
|
+
unchecked: outcome.unchecked,
|
|
285
|
+
newest: newest === null ? null : { at: newest.at, seq: newest.seq, hash: newest.hash },
|
|
286
|
+
...(outcome.warning === null ? {} : { warning: outcome.warning }),
|
|
287
|
+
},
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
/** The checkpoint line a human reads, after the chain verdict it qualifies. */
|
|
291
|
+
function reportCheckpoints(outcome, streams) {
|
|
292
|
+
if (outcome.status === "skip") {
|
|
293
|
+
streams.err(`approval: checkpoints skipped — ${outcome.reason}\n`);
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
if (outcome.status === "refused")
|
|
297
|
+
return;
|
|
298
|
+
streams.out(`checkpoints: ${outcome.detail}\n`);
|
|
299
|
+
if (outcome.warning !== null)
|
|
300
|
+
streams.err(`approval: ${outcome.warning}\n`);
|
|
301
|
+
}
|
|
302
|
+
async function commandVerify(argv, streams, cwd) {
|
|
303
|
+
const front = prelude(argv, {
|
|
304
|
+
"--log": "string",
|
|
305
|
+
"--json": "boolean",
|
|
306
|
+
// APRV-219. `--anchor` is the default resolution (the newest committed
|
|
307
|
+
// copy this checkout can see); `--anchor-rev` names one and implies it.
|
|
308
|
+
// Two flags rather than one optional-value flag, because this CLI's
|
|
309
|
+
// parser has no optional-value form and inventing one to save a word
|
|
310
|
+
// would make every other flag's shape a special case.
|
|
311
|
+
"--anchor": "boolean",
|
|
312
|
+
"--anchor-rev": "string",
|
|
313
|
+
// APRV-220. The second witness, and a separate flag from `--anchor`
|
|
314
|
+
// because they answer different questions and fail in different
|
|
315
|
+
// directions: the anchor asks whether anybody else holds these bytes, a
|
|
316
|
+
// checkpoint asks whether a key no agent holds signed this head. Asking
|
|
317
|
+
// for one has never implied the other, and neither may be weakened to
|
|
318
|
+
// make the other pass.
|
|
319
|
+
"--checkpoints": "boolean",
|
|
320
|
+
}, VERIFY_HELP, streams, cwd);
|
|
321
|
+
if (front.kind === "handled")
|
|
322
|
+
return front.code;
|
|
323
|
+
const { flags, logPath, json } = front;
|
|
324
|
+
const anchorRev = stringFlag(flags, "--anchor-rev");
|
|
325
|
+
const wantsAnchor = boolFlag(flags, "--anchor") || anchorRev !== null;
|
|
326
|
+
const wantsCheckpoints = boolFlag(flags, "--checkpoints");
|
|
327
|
+
const check = preflightLog(logPath);
|
|
328
|
+
if (!check.ok)
|
|
329
|
+
return ioError(streams, json, check.message);
|
|
330
|
+
// The policy is consulted for one number, `audit.skew_tolerance` (APRV-58),
|
|
331
|
+
// and it reaches only which anomalies are reported. The verdict below is a
|
|
332
|
+
// function of the log bytes and the schemas, so a missing or unloadable
|
|
333
|
+
// policy leaves this command's answer exactly as it was.
|
|
334
|
+
// `verifyWithRecords` only when the anchor was asked for (APRV-219): the
|
|
335
|
+
// anchor check compares against records the caller has already verified, and
|
|
336
|
+
// a plain run has no use for them.
|
|
337
|
+
const { verify, verifyWithRecords } = await import("../core/verify.js");
|
|
338
|
+
const walked = wantsAnchor || wantsCheckpoints
|
|
339
|
+
? verifyWithRecords(logPath, { policy: { dir: cwd } })
|
|
340
|
+
: { result: verify(logPath, { policy: { dir: cwd } }), records: [] };
|
|
341
|
+
const result = walked.result;
|
|
342
|
+
if (result.status === "clean") {
|
|
343
|
+
// Loaded only when asked for (APRV-209): the anchor check pulls in git-scope
|
|
344
|
+
// and the chain reconciler, and a plain `log verify` has no use for either.
|
|
345
|
+
const anchor = wantsAnchor
|
|
346
|
+
? (await import("./log-anchor.js")).checkLogAnchor({
|
|
347
|
+
logPath,
|
|
348
|
+
records: walked.records,
|
|
349
|
+
...(anchorRev === null ? {} : { rev: anchorRev }),
|
|
350
|
+
})
|
|
351
|
+
: null;
|
|
352
|
+
// A divergence replaces the clean verdict rather than qualifying it. The
|
|
353
|
+
// chain walk's answer is still true and it is no longer the answer to the
|
|
354
|
+
// question `--anchor` asked, so printing `clean` beside it would be this
|
|
355
|
+
// verb reporting a pass it does not mean.
|
|
356
|
+
if (anchor !== null && anchor.status === "diverged") {
|
|
357
|
+
if (json) {
|
|
358
|
+
emitJson(streams, {
|
|
359
|
+
status: "anchor-diverged",
|
|
360
|
+
records: result.records,
|
|
361
|
+
head: result.head,
|
|
362
|
+
...anchorField(anchor),
|
|
363
|
+
message: anchor.message,
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
else {
|
|
367
|
+
streams.err(`${renderRefusal(style({ json }), anchor.code, anchor.message)}\n`);
|
|
368
|
+
}
|
|
369
|
+
return EXIT_INTEGRITY;
|
|
370
|
+
}
|
|
371
|
+
// The second witness (APRV-220), run independently of the first and after
|
|
372
|
+
// it. Independently, because a checkpoint refusal and an anchor divergence
|
|
373
|
+
// are different facts with different repairs, and neither may be softened
|
|
374
|
+
// to let the other report a pass; after it, only so that a log failing both
|
|
375
|
+
// reports the older check's message first.
|
|
376
|
+
const checkpoints = wantsCheckpoints
|
|
377
|
+
? await runCheckpointCheck(walked.records, cwd)
|
|
378
|
+
: null;
|
|
379
|
+
if (checkpoints !== null && checkpoints.status === "refused") {
|
|
380
|
+
if (json) {
|
|
381
|
+
emitJson(streams, {
|
|
382
|
+
status: "checkpoint-invalid",
|
|
383
|
+
records: result.records,
|
|
384
|
+
head: result.head,
|
|
385
|
+
...anchorField2(anchor),
|
|
386
|
+
...checkpointField(checkpoints),
|
|
387
|
+
message: checkpoints.message,
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
else {
|
|
391
|
+
streams.err(`${renderRefusal(style({ json }), checkpoints.code, checkpoints.message)}\n`);
|
|
392
|
+
}
|
|
393
|
+
return EXIT_INTEGRITY;
|
|
394
|
+
}
|
|
395
|
+
if (json) {
|
|
396
|
+
emitJson(streams, {
|
|
397
|
+
status: result.status,
|
|
398
|
+
records: result.records,
|
|
399
|
+
head: result.head,
|
|
400
|
+
...anomalyField(result.anomalies),
|
|
401
|
+
...(anchor === null ? {} : anchorField(anchor)),
|
|
402
|
+
...(checkpoints === null ? {} : checkpointField(checkpoints)),
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
else {
|
|
406
|
+
const head = result.head === null ? "head none" : `head seq ${result.head.seq} ${result.head.hash}`;
|
|
407
|
+
streams.out(`clean: ${result.records} record(s), ${head}\n`);
|
|
408
|
+
reportAnomalies(streams, result.anomalies);
|
|
409
|
+
if (anchor !== null)
|
|
410
|
+
reportAnchor(anchor, streams);
|
|
411
|
+
if (checkpoints !== null)
|
|
412
|
+
reportCheckpoints(checkpoints, streams);
|
|
413
|
+
}
|
|
414
|
+
return EXIT_OK;
|
|
415
|
+
}
|
|
416
|
+
if (result.status === "torn-tail") {
|
|
417
|
+
if (json) {
|
|
418
|
+
emitJson(streams, {
|
|
419
|
+
status: result.status,
|
|
420
|
+
records: result.records,
|
|
421
|
+
head: null,
|
|
422
|
+
intactThroughSeq: result.intactThroughSeq,
|
|
423
|
+
message: result.message,
|
|
424
|
+
...anomalyField(result.anomalies),
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
else {
|
|
428
|
+
reportAnomalies(streams, result.anomalies);
|
|
429
|
+
streams.out(`torn-tail: ${result.records} record(s), intact through seq ${result.intactThroughSeq}\n`);
|
|
430
|
+
streams.err(`approval: ${result.message}\n`);
|
|
431
|
+
}
|
|
432
|
+
return EXIT_TORN_TAIL;
|
|
433
|
+
}
|
|
434
|
+
if (json) {
|
|
435
|
+
emitJson(streams, {
|
|
436
|
+
status: result.status,
|
|
437
|
+
records: null,
|
|
438
|
+
head: null,
|
|
439
|
+
firstBadSeq: result.firstBadSeq,
|
|
440
|
+
reason: result.reason,
|
|
441
|
+
message: result.message,
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
else {
|
|
445
|
+
const where = result.firstBadSeq === null ? "unknown seq" : `seq ${result.firstBadSeq}`;
|
|
446
|
+
// APRV-102: the shared refusal shape. `corrupt` is the machine-readable word
|
|
447
|
+
// here (it is `status` in `--json`, which is unchanged), and the reason and
|
|
448
|
+
// the seq are the message.
|
|
449
|
+
streams.err(`${renderRefusal(style({ json }), "corrupt", `${result.reason} at ${where}`)}\n`);
|
|
450
|
+
streams.err(`approval: ${result.message}\n`);
|
|
451
|
+
}
|
|
452
|
+
return EXIT_INTEGRITY;
|
|
453
|
+
}
|
|
454
|
+
/**
|
|
455
|
+
* `tail` and `export` share everything but the slice and the rendering, so they
|
|
456
|
+
* share the verify → read → refuse-or-print sequence too.
|
|
457
|
+
*/
|
|
458
|
+
async function readForOutput(logPath, streams, json) {
|
|
459
|
+
const { verify } = await import("../core/verify.js");
|
|
460
|
+
const result = verify(logPath);
|
|
461
|
+
if (result.status === "corrupt") {
|
|
462
|
+
return {
|
|
463
|
+
code: integrityError(streams, json, `log ${logPath} failed chain verification (${result.reason}); refusing to print records from a tampered log: ${result.message}`),
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
const read = readCompleteLines(logPath, result.records);
|
|
467
|
+
if (!read.ok)
|
|
468
|
+
return { code: ioError(streams, json, read.message) };
|
|
469
|
+
return {
|
|
470
|
+
lines: read.lines,
|
|
471
|
+
warning: result.status === "torn-tail"
|
|
472
|
+
? `log ${logPath} ends with a torn line (an unterminated final record, the signature of a crashed write); the ${result.records} intact record(s) are shown and the log is left exactly as it is — nothing was repaired or truncated`
|
|
473
|
+
: null,
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
async function commandTail(argv, streams, cwd) {
|
|
477
|
+
const front = prelude(argv, { "--log": "string", "--json": "boolean", "-n": "string" }, TAIL_HELP, streams, cwd);
|
|
478
|
+
if (front.kind === "handled")
|
|
479
|
+
return front.code;
|
|
480
|
+
const { flags, logPath, json } = front;
|
|
481
|
+
const count = countFlag(flags, "-n");
|
|
482
|
+
if (!count.ok)
|
|
483
|
+
return usageError(streams, json, count.message, TAIL_HELP);
|
|
484
|
+
const limit = count.value ?? DEFAULT_TAIL_COUNT;
|
|
485
|
+
const check = preflightLog(logPath);
|
|
486
|
+
if (!check.ok)
|
|
487
|
+
return ioError(streams, json, check.message);
|
|
488
|
+
const outcome = await readForOutput(logPath, streams, json);
|
|
489
|
+
if ("code" in outcome)
|
|
490
|
+
return outcome.code;
|
|
491
|
+
const selected = limit === 0 ? [] : outcome.lines.slice(-limit);
|
|
492
|
+
const parsed = parseLines(logPath, selected);
|
|
493
|
+
if (!parsed.ok)
|
|
494
|
+
return ioError(streams, json, parsed.message);
|
|
495
|
+
if (json) {
|
|
496
|
+
const status = outcome.warning === null ? "ok" : "torn-tail";
|
|
497
|
+
emitJson(streams, outcome.warning === null
|
|
498
|
+
? { status, records: parsed.records }
|
|
499
|
+
: { status, records: parsed.records, warning: outcome.warning });
|
|
500
|
+
}
|
|
501
|
+
else {
|
|
502
|
+
streams.out(renderTailHuman(parsed.records, style({ json })));
|
|
503
|
+
}
|
|
504
|
+
if (outcome.warning !== null && !json)
|
|
505
|
+
streams.err(`approval: ${outcome.warning}\n`);
|
|
506
|
+
return EXIT_OK;
|
|
507
|
+
}
|
|
508
|
+
async function commandExport(argv, streams, cwd) {
|
|
509
|
+
const front = prelude(argv, { "--log": "string", "--json": "boolean" }, EXPORT_HELP, streams, cwd);
|
|
510
|
+
if (front.kind === "handled")
|
|
511
|
+
return front.code;
|
|
512
|
+
const { logPath, json } = front;
|
|
513
|
+
const check = preflightLog(logPath);
|
|
514
|
+
if (!check.ok)
|
|
515
|
+
return ioError(streams, json, check.message);
|
|
516
|
+
const outcome = await readForOutput(logPath, streams, json);
|
|
517
|
+
if ("code" in outcome)
|
|
518
|
+
return outcome.code;
|
|
519
|
+
if (json) {
|
|
520
|
+
const parsed = parseLines(logPath, outcome.lines);
|
|
521
|
+
if (!parsed.ok)
|
|
522
|
+
return ioError(streams, json, parsed.message);
|
|
523
|
+
emitJson(streams, outcome.warning === null
|
|
524
|
+
? { records: parsed.records }
|
|
525
|
+
: { records: parsed.records, warning: outcome.warning });
|
|
526
|
+
}
|
|
527
|
+
else {
|
|
528
|
+
// Verbatim: the stored line plus the newline that terminated it. No parse,
|
|
529
|
+
// no re-serialization — export of a clean log is a byte-for-byte copy.
|
|
530
|
+
for (const line of outcome.lines)
|
|
531
|
+
streams.out(`${line}\n`);
|
|
532
|
+
}
|
|
533
|
+
if (outcome.warning !== null && !json)
|
|
534
|
+
streams.err(`approval: ${outcome.warning}\n`);
|
|
535
|
+
return EXIT_OK;
|
|
536
|
+
}
|
|
537
|
+
/** Long-lived, line-delimited verified event stream (APRV-322). */
|
|
538
|
+
async function commandFollow(argv, streams, cwd, nativeOutput) {
|
|
539
|
+
const front = prelude(argv, { "--log": "string", "--json": "boolean", "--from": "string", "--cursor-hash": "string" }, FOLLOW_HELP, streams, cwd);
|
|
540
|
+
if (front.kind === "handled")
|
|
541
|
+
return front.code;
|
|
542
|
+
const { flags, logPath, json } = front;
|
|
543
|
+
if (!json) {
|
|
544
|
+
return usageError(streams, false, "log follow requires --json", FOLLOW_HELP);
|
|
545
|
+
}
|
|
546
|
+
const fromResult = countFlag(flags, "--from");
|
|
547
|
+
if (!fromResult.ok)
|
|
548
|
+
return usageError(streams, true, fromResult.message, FOLLOW_HELP);
|
|
549
|
+
const from = fromResult.value ?? 0;
|
|
550
|
+
const expectedHash = stringFlag(flags, "--cursor-hash");
|
|
551
|
+
if (expectedHash !== null && from === 0) {
|
|
552
|
+
return usageError(streams, true, "--cursor-hash requires --from greater than zero", FOLLOW_HELP);
|
|
553
|
+
}
|
|
554
|
+
if (expectedHash !== null && !/^[a-f0-9]{64}$/u.test(expectedHash)) {
|
|
555
|
+
return usageError(streams, true, "--cursor-hash expects a lowercase 64-character SHA-256 digest", FOLLOW_HELP);
|
|
556
|
+
}
|
|
557
|
+
const check = preflightLog(logPath);
|
|
558
|
+
if (!check.ok)
|
|
559
|
+
return ioError(streams, true, check.message);
|
|
560
|
+
const controller = new AbortController();
|
|
561
|
+
let brokenPipe = false;
|
|
562
|
+
let signalCancellation = false;
|
|
563
|
+
const outputFailures = [];
|
|
564
|
+
const stop = () => {
|
|
565
|
+
signalCancellation = true;
|
|
566
|
+
controller.abort();
|
|
567
|
+
// A stalled downstream reader can leave one bounded chunk pending. The
|
|
568
|
+
// foreground command is ending by signal, so close that native write side
|
|
569
|
+
// rather than making shutdown depend on the reader resuming.
|
|
570
|
+
if (nativeOutput) {
|
|
571
|
+
process.stdout.destroy();
|
|
572
|
+
// A pipe-backed stdout can retain an outstanding libuv write even after
|
|
573
|
+
// destroy. Give the abort microtasks one turn to close the iterator and
|
|
574
|
+
// its watcher, then finish the signal-requested process without waiting
|
|
575
|
+
// for a downstream reader that has explicitly stopped reading.
|
|
576
|
+
setImmediate(() => process.exit(EXIT_OK));
|
|
577
|
+
}
|
|
578
|
+
};
|
|
579
|
+
const outputError = (cause) => {
|
|
580
|
+
if (signalCancellation) {
|
|
581
|
+
controller.abort();
|
|
582
|
+
return;
|
|
583
|
+
}
|
|
584
|
+
if (cause.code === "EPIPE")
|
|
585
|
+
brokenPipe = true;
|
|
586
|
+
else
|
|
587
|
+
outputFailures.push(cause);
|
|
588
|
+
controller.abort();
|
|
589
|
+
};
|
|
590
|
+
process.once("SIGINT", stop);
|
|
591
|
+
process.once("SIGTERM", stop);
|
|
592
|
+
if (nativeOutput)
|
|
593
|
+
process.stdout.on("error", outputError);
|
|
594
|
+
const writeRecord = async (record) => {
|
|
595
|
+
const line = `${JSON.stringify(record)}\n`;
|
|
596
|
+
if (!nativeOutput) {
|
|
597
|
+
streams.out(line);
|
|
598
|
+
return;
|
|
599
|
+
}
|
|
600
|
+
await new Promise((resolve, reject) => {
|
|
601
|
+
let settled = false;
|
|
602
|
+
const finish = (cause) => {
|
|
603
|
+
if (settled)
|
|
604
|
+
return;
|
|
605
|
+
settled = true;
|
|
606
|
+
process.stdout.removeListener("drain", drained);
|
|
607
|
+
controller.signal.removeEventListener("abort", cancelled);
|
|
608
|
+
if (cause === undefined)
|
|
609
|
+
resolve();
|
|
610
|
+
else
|
|
611
|
+
reject(cause);
|
|
612
|
+
};
|
|
613
|
+
const drained = () => finish();
|
|
614
|
+
const cancelled = () => finish();
|
|
615
|
+
controller.signal.addEventListener("abort", cancelled, { once: true });
|
|
616
|
+
process.stdout.once("drain", drained);
|
|
617
|
+
try {
|
|
618
|
+
// A false return is the Writable contract's backpressure signal. Do
|
|
619
|
+
// not ask the iterator for another record until `drain`; otherwise
|
|
620
|
+
// Node accepts the whole verified snapshot into its stdout queue.
|
|
621
|
+
if (process.stdout.write(line))
|
|
622
|
+
finish();
|
|
623
|
+
}
|
|
624
|
+
catch (cause) {
|
|
625
|
+
finish(cause instanceof Error ? cause : new Error(String(cause)));
|
|
626
|
+
}
|
|
627
|
+
});
|
|
628
|
+
};
|
|
629
|
+
try {
|
|
630
|
+
const { subscribeVerifiedLog } = await import("../core/log-subscribe.js");
|
|
631
|
+
const options = expectedHash === null
|
|
632
|
+
? { from, signal: controller.signal }
|
|
633
|
+
: { from, expectedHash, signal: controller.signal };
|
|
634
|
+
for await (const record of subscribeVerifiedLog(logPath, options)) {
|
|
635
|
+
try {
|
|
636
|
+
await writeRecord(record);
|
|
637
|
+
}
|
|
638
|
+
catch (cause) {
|
|
639
|
+
if (cause.code === "EPIPE") {
|
|
640
|
+
brokenPipe = true;
|
|
641
|
+
controller.abort();
|
|
642
|
+
break;
|
|
643
|
+
}
|
|
644
|
+
throw cause;
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
if (brokenPipe || signalCancellation)
|
|
648
|
+
return EXIT_OK;
|
|
649
|
+
const outputFailure = outputFailures[0];
|
|
650
|
+
if (outputFailure !== undefined)
|
|
651
|
+
return ioError(streams, true, outputFailure.message);
|
|
652
|
+
return EXIT_OK;
|
|
653
|
+
}
|
|
654
|
+
catch (cause) {
|
|
655
|
+
const { LogSubscriptionError } = await import("../core/log-subscribe.js");
|
|
656
|
+
if (!(cause instanceof LogSubscriptionError))
|
|
657
|
+
throw cause;
|
|
658
|
+
if (cause.kind === "torn-tail") {
|
|
659
|
+
streams.err(`${JSON.stringify({ error: { code: "torn-tail", message: cause.message } })}\n`);
|
|
660
|
+
return EXIT_TORN_TAIL;
|
|
661
|
+
}
|
|
662
|
+
return cause.kind === "io"
|
|
663
|
+
? ioError(streams, true, cause.message)
|
|
664
|
+
: integrityError(streams, true, cause.message);
|
|
665
|
+
}
|
|
666
|
+
finally {
|
|
667
|
+
process.removeListener("SIGINT", stop);
|
|
668
|
+
process.removeListener("SIGTERM", stop);
|
|
669
|
+
if (nativeOutput)
|
|
670
|
+
process.stdout.removeListener("error", outputError);
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
async function commandReindex(argv, streams, cwd) {
|
|
674
|
+
const front = prelude(argv, {
|
|
675
|
+
"--log": "string",
|
|
676
|
+
"--index": "string",
|
|
677
|
+
"--force": "boolean",
|
|
678
|
+
"--json": "boolean",
|
|
679
|
+
}, REINDEX_HELP, streams, cwd);
|
|
680
|
+
if (front.kind === "handled")
|
|
681
|
+
return front.code;
|
|
682
|
+
const { flags, logPath, json } = front;
|
|
683
|
+
const indexPath = resolvePath(stringFlag(flags, "--index"), DEFAULT_INDEX_PATH, cwd);
|
|
684
|
+
const check = preflightLog(logPath);
|
|
685
|
+
if (!check.ok)
|
|
686
|
+
return ioError(streams, json, check.message);
|
|
687
|
+
// The projection is the only thing in this CLI that loads `better-sqlite3`,
|
|
688
|
+
// and it is loaded here rather than at the top of the file so that the verbs
|
|
689
|
+
// that never touch the index never pay for the native addon (APRV-209).
|
|
690
|
+
const { reindex } = await import("../core/reindex.js");
|
|
691
|
+
const result = reindex(logPath, indexPath, boolFlag(flags, "--force") ? { force: true } : {});
|
|
692
|
+
if (result.ok) {
|
|
693
|
+
if (json) {
|
|
694
|
+
emitJson(streams, {
|
|
695
|
+
ok: true,
|
|
696
|
+
records: result.records,
|
|
697
|
+
head: result.head,
|
|
698
|
+
truncated: result.truncated,
|
|
699
|
+
});
|
|
700
|
+
}
|
|
701
|
+
else {
|
|
702
|
+
const head = result.head === null ? "head none" : `head seq ${result.head.seq} ${result.head.hash}`;
|
|
703
|
+
streams.out(`indexed ${result.records} record(s) into ${indexPath}: ${head}, truncated ${result.truncated}\n`);
|
|
704
|
+
}
|
|
705
|
+
return EXIT_OK;
|
|
706
|
+
}
|
|
707
|
+
if (json) {
|
|
708
|
+
emitJson(streams, {
|
|
709
|
+
ok: false,
|
|
710
|
+
error: { code: result.error.code, message: result.error.message },
|
|
711
|
+
});
|
|
712
|
+
}
|
|
713
|
+
else {
|
|
714
|
+
streams.err(`approval: ${result.error.message}\n`);
|
|
715
|
+
}
|
|
716
|
+
switch (result.error.code) {
|
|
717
|
+
case "not-clean":
|
|
718
|
+
return EXIT_INTEGRITY;
|
|
719
|
+
case "torn-tail":
|
|
720
|
+
return EXIT_TORN_TAIL;
|
|
721
|
+
default:
|
|
722
|
+
return EXIT_IO;
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
async function commandLog(argv, streams, cwd, nativeOutput) {
|
|
726
|
+
const sub = argv[0];
|
|
727
|
+
const rest = argv.slice(1);
|
|
728
|
+
if (sub === undefined) {
|
|
729
|
+
return usageError(streams, wantsJson(argv), "missing subcommand for `approval log`", LOG_HELP);
|
|
730
|
+
}
|
|
731
|
+
if (sub === "--help" || sub === "-h" || sub === "help") {
|
|
732
|
+
streams.out(`${LOG_HELP}\n`);
|
|
733
|
+
return EXIT_OK;
|
|
734
|
+
}
|
|
735
|
+
switch (sub) {
|
|
736
|
+
case "verify":
|
|
737
|
+
return commandVerify(rest, streams, cwd);
|
|
738
|
+
case "tail":
|
|
739
|
+
return commandTail(rest, streams, cwd);
|
|
740
|
+
case "export":
|
|
741
|
+
return commandExport(rest, streams, cwd);
|
|
742
|
+
case "follow":
|
|
743
|
+
return commandFollow(rest, streams, cwd, nativeOutput);
|
|
744
|
+
// APRV-125. The two verbs that move the log FILE rather than reading it: a
|
|
745
|
+
// fast-forward pull with a chain reconcile, and the commit-and-push of what
|
|
746
|
+
// the chain has grown since. Neither appends an event.
|
|
747
|
+
case "sync": {
|
|
748
|
+
const { commandLogSync } = await import("./log-verbs.js");
|
|
749
|
+
return commandLogSync(rest, streams, cwd);
|
|
750
|
+
}
|
|
751
|
+
case "advance": {
|
|
752
|
+
const { commandLogAdvance } = await import("./log-verbs.js");
|
|
753
|
+
return commandLogAdvance(rest, streams, cwd);
|
|
754
|
+
}
|
|
755
|
+
// APRV-220. The one verb here that APPENDS: a human signing the current
|
|
756
|
+
// head with a key no agent process holds. Loaded lazily like the two above,
|
|
757
|
+
// because it reaches the vault and the signing primitives and a plain
|
|
758
|
+
// `approval log tail` has no use for either.
|
|
759
|
+
case "checkpoint": {
|
|
760
|
+
const { commandLogCheckpoint } = await import("./log-checkpoint.js");
|
|
761
|
+
return commandLogCheckpoint(rest, streams, cwd);
|
|
762
|
+
}
|
|
763
|
+
default:
|
|
764
|
+
return usageError(streams, wantsJson(argv), `unknown subcommand ${JSON.stringify(sub)} for \`approval log\``, LOG_HELP);
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
/**
|
|
768
|
+
* The part of a command line that belongs to `approval` itself.
|
|
769
|
+
*
|
|
770
|
+
* `approval run … -- git push` and `hook classify -- <command…>` hand the tail
|
|
771
|
+
* to a child, and a `--no-color` in THAT half is the child's business. Reading
|
|
772
|
+
* presentation flags only from the near side is what keeps this CLI from
|
|
773
|
+
* quietly editing the command it was asked to run.
|
|
774
|
+
*/
|
|
775
|
+
function beforeSeparator(argv) {
|
|
776
|
+
const separator = argv.indexOf("--");
|
|
777
|
+
return separator === -1 ? [...argv] : argv.slice(0, separator);
|
|
778
|
+
}
|
|
779
|
+
/** Remove `--no-color`, near side only, so no verb needs it in its flag spec. */
|
|
780
|
+
function stripNoColor(argv) {
|
|
781
|
+
const separator = argv.indexOf("--");
|
|
782
|
+
const near = (separator === -1 ? argv : argv.slice(0, separator)).filter((word) => word !== "--no-color");
|
|
783
|
+
return separator === -1 ? near : [...near, ...argv.slice(separator)];
|
|
784
|
+
}
|
|
785
|
+
/** The five verbs a new operator needs, under the wordmark, and nothing else. */
|
|
786
|
+
function splash(theme) {
|
|
787
|
+
const rows = [
|
|
788
|
+
{ left: "init", right: "scaffold APPROVAL.md and .approval/ here" },
|
|
789
|
+
{ left: "setup", right: "declare who you are and store credentials" },
|
|
790
|
+
{ left: "doctor", right: "can this machine run the system?" },
|
|
791
|
+
{ left: "queue", right: "what is waiting for your decision" },
|
|
792
|
+
{ left: "--help", right: "every verb, and the exit codes" },
|
|
793
|
+
];
|
|
794
|
+
return `${wordmark(theme)}\n\n${theme.table(rows, { indent: 2, gap: 3 })}`;
|
|
795
|
+
}
|
|
796
|
+
/**
|
|
797
|
+
* The help text `--long` was asked for, or null when it was not asked for.
|
|
798
|
+
*
|
|
799
|
+
* `--long` means nothing on its own: it is a modifier on a help request, so it
|
|
800
|
+
* is honoured only alongside `--help`/`-h` or the `help` verb. Anywhere else it
|
|
801
|
+
* falls through to the verb, which will call it an unknown flag, which is the
|
|
802
|
+
* right answer.
|
|
803
|
+
*/
|
|
804
|
+
function longHelpRequest(argv) {
|
|
805
|
+
const near = beforeSeparator(argv);
|
|
806
|
+
if (!near.includes("--long"))
|
|
807
|
+
return null;
|
|
808
|
+
const asking = near[0] === "help" || near.includes("--help") || near.includes("-h");
|
|
809
|
+
if (!asking)
|
|
810
|
+
return null;
|
|
811
|
+
const words = (near[0] === "help" ? near.slice(1) : near).filter((word) => !word.startsWith("-"));
|
|
812
|
+
return (words.length === 0 ? null : helpFor(words)) ?? ROOT_HELP;
|
|
813
|
+
}
|
|
814
|
+
/**
|
|
815
|
+
* Await a verb that may answer asynchronously, and return its code.
|
|
816
|
+
*
|
|
817
|
+
* Before APRV-209 the arms that call this could not return the code at all:
|
|
818
|
+
* `main()` was synchronous, so an asynchronous verb's promise was dropped into
|
|
819
|
+
* `process.exitCode` and the arm returned {@link EXIT_OK}. Awaiting is now
|
|
820
|
+
* possible, and it also closes a hole the drop had opened: the entry point's own
|
|
821
|
+
* assignment to `process.exitCode` could land after the dropped promise's and
|
|
822
|
+
* overwrite a usage error with a zero.
|
|
823
|
+
*
|
|
824
|
+
* `label` is the phrase that named the verb in the old rejection message
|
|
825
|
+
* ("doctor failed", "MCP server failed"), so those messages are unchanged.
|
|
826
|
+
*/
|
|
827
|
+
async function settle(outcome, streams, label) {
|
|
828
|
+
try {
|
|
829
|
+
return await outcome;
|
|
830
|
+
}
|
|
831
|
+
catch (cause) {
|
|
832
|
+
streams.err(`approval: ${label}: ${cause instanceof Error ? cause.message : String(cause)}\n`);
|
|
833
|
+
return EXIT_IO;
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
/**
|
|
837
|
+
* Run the CLI. Resolves to the process exit code rather than calling
|
|
838
|
+
* `process.exit`, so buffered stdout is flushed by the normal exit path — a
|
|
839
|
+
* truncated JSON object would be worse than no output at all.
|
|
840
|
+
*
|
|
841
|
+
* ASYNCHRONOUS since APRV-209, and for one reason: every verb is loaded by
|
|
842
|
+
* `await import()` inside the switch below, and ESM has no synchronous dynamic
|
|
843
|
+
* import. The awaits do not make any verb concurrent — exactly one runs per
|
|
844
|
+
* invocation, the preamble still decides presentation once before any of them
|
|
845
|
+
* can print, and the long-lived verbs (`channel`, `daemon`, `up`, `mcp`) report
|
|
846
|
+
* their eventual code through `process.exitCode` exactly as they did.
|
|
847
|
+
*/
|
|
848
|
+
export async function main(argv, options = {}) {
|
|
849
|
+
const streams = options.streams ?? defaultStreams();
|
|
850
|
+
const cwd = options.cwd ?? process.cwd();
|
|
851
|
+
// Presentation is decided ONCE per invocation, before any verb can print
|
|
852
|
+
// (APRV-91). `--no-color` is answered here and stripped, so no verb has to
|
|
853
|
+
// carry it in its flag spec and none can disagree about it; `--json` is a
|
|
854
|
+
// veto on colour, which is why it is read before the verb parses anything.
|
|
855
|
+
const argvForStyle = beforeSeparator(argv);
|
|
856
|
+
const noColor = argvForStyle.includes("--no-color");
|
|
857
|
+
resetStyle();
|
|
858
|
+
const theme = style({ json: wantsJson(argvForStyle), noColor });
|
|
859
|
+
const cleanArgv = noColor ? stripNoColor(argv) : argv;
|
|
860
|
+
const command = cleanArgv[0];
|
|
861
|
+
const rest = cleanArgv.slice(1);
|
|
862
|
+
// `--help --long` and `approval help <verb> --long` (APRV-91 #16): the short
|
|
863
|
+
// help verbatim, then the reference section its `why:` footer points at.
|
|
864
|
+
// Intercepted HERE rather than in each verb, because the alternative is the
|
|
865
|
+
// same three lines in sixty places and one of them getting it wrong.
|
|
866
|
+
const longRequest = longHelpRequest(cleanArgv);
|
|
867
|
+
if (longRequest !== null) {
|
|
868
|
+
streams.out(`${longHelp(longRequest, { style: theme })}\n`);
|
|
869
|
+
return EXIT_OK;
|
|
870
|
+
}
|
|
871
|
+
if (command === undefined) {
|
|
872
|
+
// The orientation screen (APRV-91 #7/#12). It goes to STDOUT while the
|
|
873
|
+
// refusal stays on stderr with today's exit 2: a bare invocation is still a
|
|
874
|
+
// usage error for anything scripting this CLI, and the human staring at a
|
|
875
|
+
// terminal still gets the wordmark and the five verbs they need.
|
|
876
|
+
streams.out(`${splash(theme)}\n`);
|
|
877
|
+
return usageError(streams, false, "no command given", ROOT_HELP);
|
|
878
|
+
}
|
|
879
|
+
if (command === "--version" || command === "-v" || command === "version") {
|
|
880
|
+
streams.out(`${VERSION}\n`);
|
|
881
|
+
return EXIT_OK;
|
|
882
|
+
}
|
|
883
|
+
if (command === "--help" || command === "-h" || command === "help") {
|
|
884
|
+
// `approval help <verb>` is the third spelling of `approval <verb> --help`,
|
|
885
|
+
// and the one a person guesses first.
|
|
886
|
+
const words = rest.filter((word) => !word.startsWith("-"));
|
|
887
|
+
const target = words.length === 0 ? null : helpFor(words);
|
|
888
|
+
if (target !== null) {
|
|
889
|
+
streams.out(`${target}\n`);
|
|
890
|
+
return EXIT_OK;
|
|
891
|
+
}
|
|
892
|
+
streams.out(`${wordmark(theme)}\n\n${ROOT_HELP}\n`);
|
|
893
|
+
return EXIT_OK;
|
|
894
|
+
}
|
|
895
|
+
switch (command) {
|
|
896
|
+
// The self-describing verb (APRV-85). `instructions` prints the agent-facing
|
|
897
|
+
// guide, and `--schemas` prints the verb registry the guide's table is
|
|
898
|
+
// generated from — the one source SPEC.md §10.5's MCP wrapper derives its
|
|
899
|
+
// tool descriptions and input schemas from, so the two surfaces cannot
|
|
900
|
+
// drift. It reads no log, resolves no policy, and writes nothing.
|
|
901
|
+
case "instructions": {
|
|
902
|
+
const { commandInstructions } = await import("./instructions.js");
|
|
903
|
+
return commandInstructions(rest, streams, cwd);
|
|
904
|
+
}
|
|
905
|
+
// The scaffolding verb (APRV-71). It is the only command that writes files
|
|
906
|
+
// a human has not asked for by name, and it is deliberately the least
|
|
907
|
+
// authoritative one in the CLI: it appends nothing, attests nothing, and
|
|
908
|
+
// overwrites nothing. Everything it creates is inert until a human attests.
|
|
909
|
+
case "init": {
|
|
910
|
+
const { commandInit } = await import("./init.js");
|
|
911
|
+
return commandInit(rest, streams, cwd);
|
|
912
|
+
}
|
|
913
|
+
case "quickstart": {
|
|
914
|
+
const { commandQuickstart } = await import("./quickstart.js");
|
|
915
|
+
return settle(commandQuickstart(rest, streams, cwd), streams, "quickstart failed");
|
|
916
|
+
}
|
|
917
|
+
case "log":
|
|
918
|
+
return commandLog(rest, streams, cwd, options.streams === undefined);
|
|
919
|
+
case "policy": {
|
|
920
|
+
const { commandPolicy } = await import("./policy.js");
|
|
921
|
+
return commandPolicy(rest, streams, cwd);
|
|
922
|
+
}
|
|
923
|
+
// The gate verbs (APRV-16). grant/reject/revoke are human-only and expire
|
|
924
|
+
// is the system verb; the enforcement lives in core, not in this dispatch.
|
|
925
|
+
case "register": {
|
|
926
|
+
const { commandRegister } = await import("./gate.js");
|
|
927
|
+
return commandRegister(rest, streams, cwd);
|
|
928
|
+
}
|
|
929
|
+
case "request": {
|
|
930
|
+
const { commandRequest } = await import("./gate.js");
|
|
931
|
+
return commandRequest(rest, streams, cwd);
|
|
932
|
+
}
|
|
933
|
+
case "grant":
|
|
934
|
+
case "reject":
|
|
935
|
+
case "revoke": {
|
|
936
|
+
const { commandDecide } = await import("./gate.js");
|
|
937
|
+
return commandDecide(command, rest, streams, cwd);
|
|
938
|
+
}
|
|
939
|
+
// APRV-106. The one terminal gate verb that is NOT human-only: withdrawal
|
|
940
|
+
// is the requester retracting its own question, and the requester is
|
|
941
|
+
// usually an agent. The gate checks the actor against the request record,
|
|
942
|
+
// so the verb cannot be used to clear anyone else's queue.
|
|
943
|
+
case "withdraw": {
|
|
944
|
+
const { commandWithdraw } = await import("./gate.js");
|
|
945
|
+
return commandWithdraw(rest, streams, cwd);
|
|
946
|
+
}
|
|
947
|
+
case "expire": {
|
|
948
|
+
const { commandExpire } = await import("./gate.js");
|
|
949
|
+
return commandExpire(rest, streams, cwd);
|
|
950
|
+
}
|
|
951
|
+
// The token verbs (APRV-17). `token` reports status and writes nothing;
|
|
952
|
+
// `consume` is internal plumbing for APRV-18's `approval run` and is the
|
|
953
|
+
// only sanctioned appender of execution.started on the manual path.
|
|
954
|
+
case "token": {
|
|
955
|
+
const { commandToken } = await import("./token.js");
|
|
956
|
+
return commandToken(rest, streams, cwd);
|
|
957
|
+
}
|
|
958
|
+
case "consume": {
|
|
959
|
+
const { commandConsume } = await import("./token.js");
|
|
960
|
+
return commandConsume(rest, streams, cwd);
|
|
961
|
+
}
|
|
962
|
+
// The execution verbs (APRV-18). `run` is the only command that spawns
|
|
963
|
+
// anything and the only one that can exit 5; `wait` the only one that can
|
|
964
|
+
// exit 6. `queue` is the pending-decision inbox and `status` is system
|
|
965
|
+
// health — deliberately two verbs, because they answer to two different
|
|
966
|
+
// people (the human who decides, the operator who repairs).
|
|
967
|
+
case "run": {
|
|
968
|
+
const { commandRun } = await import("./execute.js");
|
|
969
|
+
return commandRun(rest, streams, cwd);
|
|
970
|
+
}
|
|
971
|
+
// The starving verb (APRV-193). It authorizes nothing and appends nothing:
|
|
972
|
+
// it runs a command with outbound network denied, which is what the hook
|
|
973
|
+
// cannot do for the commands it merely allows. `approval run` is the gate;
|
|
974
|
+
// this is the room the code the gate never saw runs in.
|
|
975
|
+
case "sandbox": {
|
|
976
|
+
const { commandSandbox } = await import("./sandbox.js");
|
|
977
|
+
return commandSandbox(rest, streams, cwd);
|
|
978
|
+
}
|
|
979
|
+
// The recovery verb (APRV-20 pass two). `execution resolve` is the only
|
|
980
|
+
// sanctioned way to close a dangling execution, and it is human-only,
|
|
981
|
+
// note-mandatory, and records no invented exit code.
|
|
982
|
+
case "execution": {
|
|
983
|
+
const { commandExecution } = await import("./execute.js");
|
|
984
|
+
return commandExecution(rest, streams, cwd);
|
|
985
|
+
}
|
|
986
|
+
// The audit verbs (APRV-40). `audit list` reads the sampled-audit backlog
|
|
987
|
+
// and `audit review` closes one item of it, human-only. There is no
|
|
988
|
+
// `audit sample`: selection is the runtime's, made by the daemon from an
|
|
989
|
+
// operator-held secret, and a caller who could sample could decline to.
|
|
990
|
+
case "audit": {
|
|
991
|
+
const { commandAudit } = await import("./audit.js");
|
|
992
|
+
return commandAudit(rest, streams, cwd);
|
|
993
|
+
}
|
|
994
|
+
case "wait": {
|
|
995
|
+
const { commandWait } = await import("./execute.js");
|
|
996
|
+
return commandWait(rest, streams, cwd);
|
|
997
|
+
}
|
|
998
|
+
case "queue": {
|
|
999
|
+
const { commandQueue } = await import("./execute.js");
|
|
1000
|
+
return commandQueue(rest, streams, cwd);
|
|
1001
|
+
}
|
|
1002
|
+
// The open window (APRV-214, amended SPEC.md §5.2). `gate open` is the one
|
|
1003
|
+
// verb that SUSPENDS the policy for the harness hook, so it is human-only
|
|
1004
|
+
// three times over: it classifies `policy.core` (which APPROVAL.md holds
|
|
1005
|
+
// human-only, so the hook denies an agent running it), it refuses a stdin
|
|
1006
|
+
// that is not a terminal, and it reads the word `understood` with no --yes
|
|
1007
|
+
// and no --force. `gate close` only tightens and `gate status` decides
|
|
1008
|
+
// nothing. The window's whole state is in the log; no file holds it.
|
|
1009
|
+
case "gate": {
|
|
1010
|
+
const { commandGate } = await import("./gate-window.js");
|
|
1011
|
+
return commandGate(rest, streams, cwd);
|
|
1012
|
+
}
|
|
1013
|
+
case "status": {
|
|
1014
|
+
const { commandStatus } = await import("./execute.js");
|
|
1015
|
+
return commandStatus(rest, streams, cwd);
|
|
1016
|
+
}
|
|
1017
|
+
// The witness verb (APRV-245). `status` reports what this runtime knows
|
|
1018
|
+
// about itself; `coverage` asks git, `gh` and a provider what happened
|
|
1019
|
+
// whether or not anybody routed it through the gate, and joins the answer
|
|
1020
|
+
// to the verified log. Informational: gaps are questions, not verdicts.
|
|
1021
|
+
case "coverage": {
|
|
1022
|
+
const { commandCoverage } = await import("./coverage.js");
|
|
1023
|
+
return commandCoverage(rest, streams, cwd);
|
|
1024
|
+
}
|
|
1025
|
+
// The diagnostic verb (APRV-31). `doctor` answers for the MACHINE what
|
|
1026
|
+
// `status` answers for the system, and it is asynchronous for the same
|
|
1027
|
+
// reason `channel` is: two of its checks touch the network stack (a Bot API
|
|
1028
|
+
// `getMe`, a loopback bind probe). It writes nothing anywhere.
|
|
1029
|
+
case "doctor": {
|
|
1030
|
+
const { commandDoctor } = await import("./doctor.js");
|
|
1031
|
+
return settle(commandDoctor(rest, streams, cwd), streams, "doctor failed");
|
|
1032
|
+
}
|
|
1033
|
+
// The channel verbs (APRV-23 cli, APRV-26 telegram). `channel cli` renders
|
|
1034
|
+
// the pending queue over the plugin contract and, with a terminal, collects
|
|
1035
|
+
// decisions through `recordChannelDecision` — the same human-only gate
|
|
1036
|
+
// `grant` and `reject` call. `channel telegram listen` is the first of the
|
|
1037
|
+
// LONG-LIVED commands in this CLI: it delivers the pending queue and then
|
|
1038
|
+
// long-polls until it is interrupted. `main` awaits it since APRV-209, so
|
|
1039
|
+
// the promise stays pending for as long as the listener runs and its code
|
|
1040
|
+
// is returned rather than dropped into `process.exitCode`.
|
|
1041
|
+
case "channel": {
|
|
1042
|
+
const { commandChannel } = await import("./channel.js");
|
|
1043
|
+
return settle(commandChannel(rest, streams, cwd), streams, "channel listener failed");
|
|
1044
|
+
}
|
|
1045
|
+
// The daemon verb (APRV-39). `daemon run` is the second LONG-LIVED command
|
|
1046
|
+
// in this CLI and is handled exactly like `channel`. It is the only command
|
|
1047
|
+
// that both watches and appends, and the only one whose ordinary ending is a
|
|
1048
|
+
// signal (which is exit 0, not a failure).
|
|
1049
|
+
case "daemon": {
|
|
1050
|
+
const { commandDaemon } = await import("./daemon.js");
|
|
1051
|
+
return settle(commandDaemon(rest, streams, cwd), streams, "daemon failed");
|
|
1052
|
+
}
|
|
1053
|
+
// The ambient runtime (APRV-110). `approval up` is the daemon loop and every
|
|
1054
|
+
// channel the policy configures in ONE supervised process, and it is the
|
|
1055
|
+
// fourth LONG-LIVED command here, awaited exactly as `channel` and `daemon`
|
|
1056
|
+
// are. `daemon run --with-channels` reaches the same function.
|
|
1057
|
+
case "up": {
|
|
1058
|
+
const { commandUp } = await import("./up.js");
|
|
1059
|
+
return settle(commandUp(rest, streams, cwd), streams, "the ambient runtime failed");
|
|
1060
|
+
}
|
|
1061
|
+
// The binding verb (APRV-29). `payload hash` prints the payload_hash of a
|
|
1062
|
+
// JSON document through the same core function the gate uses, so nobody has
|
|
1063
|
+
// to import an internal module (or reinvent JCS) to fill in a declaration.
|
|
1064
|
+
// It reads no log and writes nothing.
|
|
1065
|
+
// `payload agentmail-draft` (APRV-223) reads one draft over HTTPS, so this
|
|
1066
|
+
// verb joins the asynchronous family and is awaited the same way; the
|
|
1067
|
+
// `hash` path is still synchronous, and awaiting a number is a number.
|
|
1068
|
+
case "payload": {
|
|
1069
|
+
const { commandPayload } = await import("./payload.js");
|
|
1070
|
+
return settle(commandPayload(rest, streams, cwd), streams, "payload failed");
|
|
1071
|
+
}
|
|
1072
|
+
// The ungated channel (APRV-195). `journal write` is the one verb in this
|
|
1073
|
+
// switch that reaches no policy, no log and no token: it appends free text
|
|
1074
|
+
// to a local file so that an agent complying perfectly can still say it
|
|
1075
|
+
// thinks something is wrong. Nothing in the runtime reads what it writes,
|
|
1076
|
+
// which is what makes leaving it ungated safe (SPEC.md §11.1 invariant 4).
|
|
1077
|
+
case "journal": {
|
|
1078
|
+
const { commandJournal } = await import("./journal.js");
|
|
1079
|
+
return commandJournal(rest, streams, cwd);
|
|
1080
|
+
}
|
|
1081
|
+
// The human's half of the same pair (APRV-238). `values` prints the
|
|
1082
|
+
// optional values block of APPROVAL.md — what the operator values, wants
|
|
1083
|
+
// and how they answer — and it is guidance rather than policy: it grants
|
|
1084
|
+
// nothing, and no path that computes a verdict, a class, a sample, a budget
|
|
1085
|
+
// or a token reads it (SPEC.md §11.1 invariant 10). It resolves no policy
|
|
1086
|
+
// rule, reads no log and appends nothing.
|
|
1087
|
+
case "values": {
|
|
1088
|
+
const { commandValues } = await import("./values.js");
|
|
1089
|
+
return commandValues(rest, streams, cwd);
|
|
1090
|
+
}
|
|
1091
|
+
// The other direction of the same channel (APRV-239). `journal read` is the
|
|
1092
|
+
// operator reading what the agents said; this is the agents reading what the
|
|
1093
|
+
// operator said about their work. It reads a verified log and writes
|
|
1094
|
+
// nothing, and every output form labels what it prints as human-authored
|
|
1095
|
+
// GUIDANCE: no enforcement path anywhere in this dispatch reads a reaction
|
|
1096
|
+
// (SPEC.md §11.1 invariant 10), so a surface that let one read as a rule
|
|
1097
|
+
// would be the only place the invariant could break.
|
|
1098
|
+
case "feedback": {
|
|
1099
|
+
const { commandFeedback } = await import("./feedback.js");
|
|
1100
|
+
return commandFeedback(rest, streams, cwd);
|
|
1101
|
+
}
|
|
1102
|
+
// The environment verb (APRV-73). `env` resolves `.approval/env` — the
|
|
1103
|
+
// source map naming where each *_env variable's value lives — and prints an
|
|
1104
|
+
// export block for a shell to evaluate. IT IS THE ONLY COMMAND IN THIS
|
|
1105
|
+
// SWITCH THAT READS THAT FILE, and no command in this switch loads it into
|
|
1106
|
+
// its own environment: human identity is one of the variables it can carry,
|
|
1107
|
+
// so a file a process read on its own would let anything able to write it
|
|
1108
|
+
// act as the human on every human-only verb (SPEC.md §11.1 invariant 7).
|
|
1109
|
+
case "env": {
|
|
1110
|
+
const { commandEnv } = await import("./env.js");
|
|
1111
|
+
return commandEnv(rest, streams, cwd);
|
|
1112
|
+
}
|
|
1113
|
+
// The configuration verb (APRV-74) and the only WRITER of .approval/env.
|
|
1114
|
+
// It is interactive by construction: every subcommand refuses a
|
|
1115
|
+
// non-terminal stdin and --json, because a setup a pipe could drive would
|
|
1116
|
+
// be a way for a CI job or an agent to declare a human identity and store
|
|
1117
|
+
// a credential. It appends nothing to the log, attests nothing, and edits
|
|
1118
|
+
// no policy file. `setup channel telegram` reaches the network, so the dispatch
|
|
1119
|
+
// unwraps a promise exactly as `channel`, `daemon` and `adapter` do.
|
|
1120
|
+
case "setup": {
|
|
1121
|
+
const { commandSetup } = await import("./setup.js");
|
|
1122
|
+
return settle(commandSetup(rest, streams, cwd), streams, "setup failed");
|
|
1123
|
+
}
|
|
1124
|
+
// The credential verbs (APRV-68). `vault set|list|remove` manage the
|
|
1125
|
+
// encrypted store adapters read from, and all three are human-only. There
|
|
1126
|
+
// is deliberately no `vault get`: a credential's only sanctioned journey is
|
|
1127
|
+
// from the vault into an adapter inside the verified execution window, and a
|
|
1128
|
+
// verb that printed one would put it in a terminal and a shell history.
|
|
1129
|
+
// Nothing under this verb appends to the log.
|
|
1130
|
+
case "vault": {
|
|
1131
|
+
const { commandVault } = await import("./vault.js");
|
|
1132
|
+
return commandVault(rest, streams, cwd);
|
|
1133
|
+
}
|
|
1134
|
+
// The side-effect verb (APRV-69). `adapter email` is the first thing in
|
|
1135
|
+
// this CLI that reaches the world: it executes one granted action through
|
|
1136
|
+
// the adapter contract, which spends the token and writes both execution
|
|
1137
|
+
// events around the send. It is asynchronous for the obvious reason (a
|
|
1138
|
+
// socket), and is unwrapped exactly as `channel` and `daemon` are.
|
|
1139
|
+
case "adapter": {
|
|
1140
|
+
const { commandAdapter } = await import("./adapter.js");
|
|
1141
|
+
return settle(commandAdapter(rest, streams, cwd), streams, "adapter failed");
|
|
1142
|
+
}
|
|
1143
|
+
// The harness verbs (APRV-82, APRV-133). `hook claude-code` and
|
|
1144
|
+
// `hook cursor` each read a pre-tool event on STDIN and answer allow or
|
|
1145
|
+
// deny, so a command the harness runs itself cannot skip the gate the way
|
|
1146
|
+
// `approval run` cannot. They are the commands whose stdout is a decision
|
|
1147
|
+
// object for another program rather than a report for a human, and whose
|
|
1148
|
+
// exit code is deliberately 0 on a refusal: the harness reads a hook's
|
|
1149
|
+
// verdict only on exit 0.
|
|
1150
|
+
case "hook": {
|
|
1151
|
+
// The latency-critical case (APRV-209): a session pays this load on every
|
|
1152
|
+
// command it runs, so `hook.ts` and its core dependencies are the only
|
|
1153
|
+
// verb graph a pass-through invocation brings in.
|
|
1154
|
+
const { commandHook } = await import("./hook.js");
|
|
1155
|
+
return commandHook(rest, streams, cwd);
|
|
1156
|
+
}
|
|
1157
|
+
// The interoperability verb (APRV-64). `import agents-md` reads permissions
|
|
1158
|
+
// PROSE and prints a draft policy block. It is the only verb whose output is
|
|
1159
|
+
// a proposal: it writes no policy, appends nothing, and attests nothing —
|
|
1160
|
+
// the human's `policy amend` is what puts any of it in force.
|
|
1161
|
+
case "import": {
|
|
1162
|
+
const { commandImport } = await import("./import.js");
|
|
1163
|
+
return commandImport(rest, streams, cwd);
|
|
1164
|
+
}
|
|
1165
|
+
// The wrapper verb (APRV-87). `mcp serve` publishes the agent-facing verbs
|
|
1166
|
+
// as MCP tools over stdio (SPEC.md §10.5) and is the third LONG-LIVED
|
|
1167
|
+
// command here, unwrapped exactly as `channel` and `daemon` are. It is
|
|
1168
|
+
// AGENT-FACING BY CONSTRUCTION: its tool list is the verb registry filtered
|
|
1169
|
+
// by human_only, so nothing that records a human's authority is reachable
|
|
1170
|
+
// through it, and the identity it runs as is fixed before the transport
|
|
1171
|
+
// exists. The verb itself is human-only, because starting one is an
|
|
1172
|
+
// operator's act.
|
|
1173
|
+
case "codex": {
|
|
1174
|
+
const { commandCodex } = await import("./codex.js");
|
|
1175
|
+
return commandCodex(rest, streams, cwd);
|
|
1176
|
+
}
|
|
1177
|
+
case "mcp": {
|
|
1178
|
+
const { commandMcp } = await import("./mcp.js");
|
|
1179
|
+
return settle(commandMcp(rest, streams, cwd), streams, "MCP server failed");
|
|
1180
|
+
}
|
|
1181
|
+
case "reindex":
|
|
1182
|
+
return commandReindex(rest, streams, cwd);
|
|
1183
|
+
// The projection verb (APRV-24). `render` writes .approval/QUEUE.md and
|
|
1184
|
+
// nothing else; the projection itself is `channels/render-queue.ts`.
|
|
1185
|
+
case "render": {
|
|
1186
|
+
const { commandRender } = await import("./render.js");
|
|
1187
|
+
return commandRender(rest, streams, cwd);
|
|
1188
|
+
}
|
|
1189
|
+
default:
|
|
1190
|
+
return usageError(streams, wantsJson(argv), `unknown command ${JSON.stringify(command)}`, ROOT_HELP);
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
// Direct execution: `node dist/src/cli/main.js …` behaves exactly like the
|
|
1194
|
+
// `approval` bin, which is a thin loader around this module.
|
|
1195
|
+
const invoked = process.argv[1];
|
|
1196
|
+
if (invoked !== undefined && import.meta.url === pathToFileURL(invoked).href) {
|
|
1197
|
+
// `main` resolves rather than returns since APRV-209; the code still reaches
|
|
1198
|
+
// the process through `process.exitCode`, so stdout is flushed by the normal
|
|
1199
|
+
// exit path. The rejection is DELIBERATELY not caught: a throw out of the
|
|
1200
|
+
// dispatch used to be an uncaught exception (stack trace, exit 1) and it stays
|
|
1201
|
+
// one, rather than being dressed up as one of the frozen exit codes.
|
|
1202
|
+
void main(process.argv.slice(2)).then((code) => {
|
|
1203
|
+
process.exitCode = code;
|
|
1204
|
+
});
|
|
1205
|
+
}
|
|
1206
|
+
//# sourceMappingURL=main.js.map
|