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,703 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The daemon-answered live draw (APRV-208).
|
|
3
|
+
*
|
|
4
|
+
* ## The hole this closes
|
|
5
|
+
*
|
|
6
|
+
* `supervised-live` (APRV-127) puts a declared fraction of a class through the
|
|
7
|
+
* human gate, chosen by HMAC over the payload hash under an operator-held
|
|
8
|
+
* secret. `core/sampler.ts`'s {@link import("./sampler.js").resolveLiveSelector}
|
|
9
|
+
* reads that secret out of the DECIDING PROCESS's own environment, and the
|
|
10
|
+
* deciding process is usually the harness hook — a short-lived child of an
|
|
11
|
+
* agent session. Nothing sources the secret there, and nothing may: a secret an
|
|
12
|
+
* agent-launched process can read is a secret the agent can read, and an agent
|
|
13
|
+
* that can compute its own luck can grind its payload until the draw comes up
|
|
14
|
+
* unsampled. SPEC.md §5.2 forbids exactly that.
|
|
15
|
+
*
|
|
16
|
+
* So the draw failed closed, correctly and permanently: measured on this
|
|
17
|
+
* repository's own log, 15 of 15 supervised-live actions since the seq 5147
|
|
18
|
+
* ceremony gated to a human (APRV-184). A class an operator asked to sample at
|
|
19
|
+
* 10% was gated at 100%, which is safe and is also the setting never once
|
|
20
|
+
* having been live.
|
|
21
|
+
*
|
|
22
|
+
* The fix is not to hand the hook the secret. It is to move the DRAW to the one
|
|
23
|
+
* process that legitimately holds it — the daemon, started by the operator in a
|
|
24
|
+
* terminal where `eval "$(approval env)"` has run — and let the hook ASK.
|
|
25
|
+
*
|
|
26
|
+
* ## The protocol, and what each part is for
|
|
27
|
+
*
|
|
28
|
+
* One owner-only Unix socket under the approval home
|
|
29
|
+
* (`.approval/daemon/draw.sock`, directory 0700, socket 0600). One JSON line in,
|
|
30
|
+
* one JSON line out, connection closed.
|
|
31
|
+
*
|
|
32
|
+
* ```
|
|
33
|
+
* -> {"v":1,"action_key":"...","payload_hash":"<64hex>","policy_hash":"<64hex>","live_rate":0.1}
|
|
34
|
+
* <- {"v":1,"question":{...},"selected":true,"mac":"<64hex>","daemon_pid":123,"answered_at":"..."}
|
|
35
|
+
* ```
|
|
36
|
+
*
|
|
37
|
+
* - **The question is a PROPOSAL; the echoed question is the daemon's own
|
|
38
|
+
* derivation.** The daemon does not take `live_rate` or `policy_hash` on the
|
|
39
|
+
* asker's word — it resolves the class from its own policy and echoes what it
|
|
40
|
+
* derived. The asker requires the echo to equal what it asked. A daemon and a
|
|
41
|
+
* hook reading different policies therefore fail closed instead of agreeing on
|
|
42
|
+
* the wrong rate, and no answer is ever MAC'd over a rate the daemon did not
|
|
43
|
+
* itself compute.
|
|
44
|
+
* - **The MAC binds the whole question AND the verdict.** `HMAC-SHA-256(secret,
|
|
45
|
+
* domain || canonical question || "\n" || selected)`. An answer is worth
|
|
46
|
+
* nothing without it: the asker cannot check it (it holds no secret), so it
|
|
47
|
+
* records it, and an operator who holds the secret recomputes it later from
|
|
48
|
+
* the record's own `action_key`, `payload_hash` and `policy_sha256`. That is
|
|
49
|
+
* what keeps SPEC.md §11's "self-reported fields never reduce scrutiny" true
|
|
50
|
+
* here — the answer is not believed, it is EVIDENCED, and an answer that
|
|
51
|
+
* cannot be evidenced (absent daemon, dead daemon, malformed reply) is refused
|
|
52
|
+
* and the action gates.
|
|
53
|
+
* - **A domain separator.** The MAC message is prefixed with
|
|
54
|
+
* {@link DRAW_MAC_DOMAIN} so a MAC can never be confused with, or substituted
|
|
55
|
+
* for, the selection value `core/sampler.ts` derives from the same key over a
|
|
56
|
+
* bare payload hash.
|
|
57
|
+
*
|
|
58
|
+
* ## Why the socket is not a free oracle
|
|
59
|
+
*
|
|
60
|
+
* An owner-only socket is reachable by any process of the same user, and an
|
|
61
|
+
* agent session runs as that user. If the daemon answered any question put to
|
|
62
|
+
* it, the agent could ask "would these bytes be sampled?" for a thousand
|
|
63
|
+
* candidate payloads and pick one that is not — the very grinding attack the
|
|
64
|
+
* secret exists to prevent, restored through the front door.
|
|
65
|
+
*
|
|
66
|
+
* `daemon/draw.ts` closes it by refusing to answer for an action that is not
|
|
67
|
+
* ALREADY REGISTERED in the verified log with exactly this payload hash. To ask
|
|
68
|
+
* a question you must first append a `task.registered` naming the bytes, so
|
|
69
|
+
* every candidate an agent grinds is permanently in the append-only log, in
|
|
70
|
+
* order, countable by anyone. The oracle still exists; it just cannot be
|
|
71
|
+
* consulted in secret, which is the property this project trades in.
|
|
72
|
+
*
|
|
73
|
+
* ## The secret is never here
|
|
74
|
+
*
|
|
75
|
+
* This module takes a secret as a parameter in exactly two pure functions
|
|
76
|
+
* ({@link drawMac}, {@link verifyDrawAnswer}) and stores it nowhere. Nothing in
|
|
77
|
+
* the client half ever sees one: the asking process holds no secret, which is
|
|
78
|
+
* the entire point.
|
|
79
|
+
*/
|
|
80
|
+
import { spawnSync } from "node:child_process";
|
|
81
|
+
import { createHmac, timingSafeEqual } from "node:crypto";
|
|
82
|
+
import { existsSync, statSync } from "node:fs";
|
|
83
|
+
import { connect } from "node:net";
|
|
84
|
+
import { dirname, join, resolve } from "node:path";
|
|
85
|
+
import { fileURLToPath } from "node:url";
|
|
86
|
+
import { canonicalize } from "./jcs.js";
|
|
87
|
+
/** The protocol version. A mismatch is a refusal, never a negotiation. */
|
|
88
|
+
export const DRAW_PROTOCOL_VERSION = 1;
|
|
89
|
+
/** The socket's basename under the approval home's `daemon/` directory. */
|
|
90
|
+
export const DRAW_SOCKET_NAME = "draw.sock";
|
|
91
|
+
/** The MAC's domain separator. See the module header. */
|
|
92
|
+
export const DRAW_MAC_DOMAIN = "approval.md/live-draw/v1";
|
|
93
|
+
/**
|
|
94
|
+
* The longest socket path this runtime will bind or dial.
|
|
95
|
+
*
|
|
96
|
+
* `sockaddr_un.sun_path` is 104 bytes on macOS and 108 on Linux, and a `bind`
|
|
97
|
+
* past it fails with a message an operator cannot act on. Checked on both sides
|
|
98
|
+
* so the daemon reports it as a refusal to serve and the asker reports it as an
|
|
99
|
+
* absent daemon, rather than either of them producing an `ENAMETOOLONG` nobody
|
|
100
|
+
* expected.
|
|
101
|
+
*/
|
|
102
|
+
export const DRAW_SOCKET_PATH_LIMIT = 100;
|
|
103
|
+
/** How long the asking child waits for a connection and an answer. */
|
|
104
|
+
export const DRAW_TIMEOUT_MS = 500;
|
|
105
|
+
/** The whole child invocation, including Node's own start. */
|
|
106
|
+
export const DRAW_SPAWN_TIMEOUT_MS = 5_000;
|
|
107
|
+
// ---------------------------------------------------------------------------
|
|
108
|
+
// Where the socket lives
|
|
109
|
+
// ---------------------------------------------------------------------------
|
|
110
|
+
/**
|
|
111
|
+
* The approval home's `daemon/` directory for a log, derived and never
|
|
112
|
+
* configured.
|
|
113
|
+
*
|
|
114
|
+
* `logPath` is `<home>/log/events.jsonl`, so the home is two levels up. One log
|
|
115
|
+
* has one daemon directory: a reader that had to be TOLD where to look could be
|
|
116
|
+
* pointed at another instance's daemon, and answers would cross between
|
|
117
|
+
* instances that share a machine.
|
|
118
|
+
*/
|
|
119
|
+
export function drawDirFor(logPath) {
|
|
120
|
+
return join(dirname(dirname(resolve(logPath))), "daemon");
|
|
121
|
+
}
|
|
122
|
+
/** Where the draw socket for `logPath` lives. */
|
|
123
|
+
export function drawSocketPathFor(logPath) {
|
|
124
|
+
return join(drawDirFor(logPath), DRAW_SOCKET_NAME);
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Which class patterns this policy declares `supervised-live`, sorted.
|
|
128
|
+
*
|
|
129
|
+
* The one question that decides whether any of this machinery is the operator's
|
|
130
|
+
* business at all: with no live class, no draw is ever made, so a missing
|
|
131
|
+
* socket is not a fault, an unset secret is not a misconfiguration, and neither
|
|
132
|
+
* deserves a doctor row or a line on the daemon's stderr. Shared by the doctor
|
|
133
|
+
* row and the daemon's server so the two cannot come to different conclusions
|
|
134
|
+
* about the same file.
|
|
135
|
+
*
|
|
136
|
+
* `defaults.autonomy` is not consulted, and cannot be: `supervised-live` carries
|
|
137
|
+
* a required rate that `defaults` has no field to hold, which `policy.schema.json`
|
|
138
|
+
* enforces. Only a class rule can be live.
|
|
139
|
+
*/
|
|
140
|
+
export function liveClassesOf(policy) {
|
|
141
|
+
return Object.entries(policy.classes ?? {})
|
|
142
|
+
.filter(([, rule]) => rule?.autonomy === "supervised-live")
|
|
143
|
+
.map(([pattern]) => pattern)
|
|
144
|
+
.sort();
|
|
145
|
+
}
|
|
146
|
+
/** Is this a well-formed 64-character lowercase hex digest? */
|
|
147
|
+
export function isHex64(value) {
|
|
148
|
+
return typeof value === "string" && /^[a-f0-9]{64}$/u.test(value);
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* The canonical bytes a MAC covers: RFC 8785 over the question.
|
|
152
|
+
*
|
|
153
|
+
* `core/jcs.ts` is the runtime's one canonicalizer, so the asker, the daemon and
|
|
154
|
+
* a later verifier cannot drift apart over key order or number spelling.
|
|
155
|
+
*/
|
|
156
|
+
export function canonicalQuestion(question) {
|
|
157
|
+
return canonicalize({
|
|
158
|
+
action_key: question.action_key,
|
|
159
|
+
live_rate: question.live_rate,
|
|
160
|
+
payload_hash: question.payload_hash,
|
|
161
|
+
policy_hash: question.policy_hash,
|
|
162
|
+
v: question.v,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
/** `HMAC-SHA-256(secret, domain || question || verdict)`, lowercase hex. */
|
|
166
|
+
export function drawMac(secret, question, selected) {
|
|
167
|
+
return createHmac("sha256", secret)
|
|
168
|
+
.update(`${DRAW_MAC_DOMAIN}\n${canonicalQuestion(question)}\n${selected ? "1" : "0"}`, "utf8")
|
|
169
|
+
.digest("hex");
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Does this MAC belong to this question and this verdict, under this secret?
|
|
173
|
+
*
|
|
174
|
+
* Constant-time, which costs nothing and removes the argument about whether it
|
|
175
|
+
* matters. A malformed MAC is `false` rather than a throw: this is a verifier,
|
|
176
|
+
* and every wrong answer is one answer.
|
|
177
|
+
*/
|
|
178
|
+
export function verifyDrawAnswer(secret, question, selected, mac) {
|
|
179
|
+
if (!isHex64(mac))
|
|
180
|
+
return false;
|
|
181
|
+
const expected = Buffer.from(drawMac(secret, question, selected), "hex");
|
|
182
|
+
const actual = Buffer.from(mac, "hex");
|
|
183
|
+
if (expected.length !== actual.length)
|
|
184
|
+
return false;
|
|
185
|
+
return timingSafeEqual(expected, actual);
|
|
186
|
+
}
|
|
187
|
+
// ---------------------------------------------------------------------------
|
|
188
|
+
// Refusals
|
|
189
|
+
// ---------------------------------------------------------------------------
|
|
190
|
+
/**
|
|
191
|
+
* Why a delegated draw produced no usable answer. Machine-readable, closed and
|
|
192
|
+
* DISTINCT (SPEC.md §11 invariant 6), because the operator's action differs:
|
|
193
|
+
* "start the daemon", "your daemon is wedged or was killed", "something
|
|
194
|
+
* answered and it was not a daemon holding your secret".
|
|
195
|
+
*
|
|
196
|
+
* Every one of them gates the action, exactly as an unavailable secret does
|
|
197
|
+
* today. None of them is a degraded mode.
|
|
198
|
+
*/
|
|
199
|
+
export const DRAW_REFUSAL_REASONS = [
|
|
200
|
+
/** No socket at the derived path: no daemon has ever served draws here. */
|
|
201
|
+
"draw-daemon-absent",
|
|
202
|
+
/** A socket that cannot be dialled, times out, or names a pid that is gone. */
|
|
203
|
+
"draw-daemon-stale",
|
|
204
|
+
/** An answer that is malformed, off-version, off-question, or badly MAC'd. */
|
|
205
|
+
"draw-answer-invalid",
|
|
206
|
+
];
|
|
207
|
+
/**
|
|
208
|
+
* Recompute a recorded delegation, from the record and the operator's secret.
|
|
209
|
+
*
|
|
210
|
+
* This is the whole point of the MAC, and it is deliberately a function of the
|
|
211
|
+
* RECORD rather than of anything the asker kept: `action_key` and
|
|
212
|
+
* `payload_hash` are the request's own fields and `policy_sha256` is assigned at
|
|
213
|
+
* the write boundary, so a verifier reconstructs the question from bytes the
|
|
214
|
+
* requester could not choose freely and could not alter afterwards without
|
|
215
|
+
* breaking the chain.
|
|
216
|
+
*
|
|
217
|
+
* `false` for a record with no MAC (a refusal), for a tampered MAC, and for a
|
|
218
|
+
* flipped verdict. Never throws.
|
|
219
|
+
*/
|
|
220
|
+
export function verifyLiveDrawRecord(secret, fields) {
|
|
221
|
+
const { draw } = fields;
|
|
222
|
+
if (draw.source !== "daemon")
|
|
223
|
+
return false;
|
|
224
|
+
if (typeof draw.selected !== "boolean" || draw.mac === undefined)
|
|
225
|
+
return false;
|
|
226
|
+
const question = {
|
|
227
|
+
v: draw.v,
|
|
228
|
+
action_key: fields.actionKey,
|
|
229
|
+
payload_hash: fields.payloadHash,
|
|
230
|
+
policy_hash: fields.policyHash,
|
|
231
|
+
live_rate: draw.live_rate,
|
|
232
|
+
};
|
|
233
|
+
return verifyDrawAnswer(secret, question, draw.selected, draw.mac);
|
|
234
|
+
}
|
|
235
|
+
// ---------------------------------------------------------------------------
|
|
236
|
+
// Parsing an answer
|
|
237
|
+
// ---------------------------------------------------------------------------
|
|
238
|
+
function sameQuestion(a, b) {
|
|
239
|
+
return (a.v === b.v &&
|
|
240
|
+
a.action_key === b.action_key &&
|
|
241
|
+
a.payload_hash === b.payload_hash &&
|
|
242
|
+
a.policy_hash === b.policy_hash &&
|
|
243
|
+
a.live_rate === b.live_rate);
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Read one answer line against the question that was asked.
|
|
247
|
+
*
|
|
248
|
+
* Every check can only REJECT. The asker holds no secret, so it cannot check the
|
|
249
|
+
* MAC; what it CAN check is that the answer is this protocol's version, is an
|
|
250
|
+
* answer to this exact question (the daemon's own derivation, field for field),
|
|
251
|
+
* carries a MAC of the right shape, and names a live process. Anything else is
|
|
252
|
+
* `draw-answer-invalid` and the action gates.
|
|
253
|
+
*/
|
|
254
|
+
export function parseDrawAnswer(text, asked) {
|
|
255
|
+
let parsed;
|
|
256
|
+
try {
|
|
257
|
+
parsed = JSON.parse(text);
|
|
258
|
+
}
|
|
259
|
+
catch {
|
|
260
|
+
return { ok: false, reason: "draw-answer-invalid", detail: "the answer was not JSON" };
|
|
261
|
+
}
|
|
262
|
+
if (typeof parsed !== "object" || parsed === null) {
|
|
263
|
+
return { ok: false, reason: "draw-answer-invalid", detail: "the answer was not an object" };
|
|
264
|
+
}
|
|
265
|
+
const body = parsed;
|
|
266
|
+
if (body["v"] !== DRAW_PROTOCOL_VERSION) {
|
|
267
|
+
return {
|
|
268
|
+
ok: false,
|
|
269
|
+
reason: "draw-answer-invalid",
|
|
270
|
+
detail: `the answer declares protocol version ${JSON.stringify(body["v"])}, not ${String(DRAW_PROTOCOL_VERSION)}`,
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
const echoed = body["question"];
|
|
274
|
+
if (typeof echoed !== "object" || echoed === null) {
|
|
275
|
+
return { ok: false, reason: "draw-answer-invalid", detail: "the answer echoed no question" };
|
|
276
|
+
}
|
|
277
|
+
const question = echoed;
|
|
278
|
+
const derived = {
|
|
279
|
+
v: typeof question["v"] === "number" ? question["v"] : -1,
|
|
280
|
+
action_key: typeof question["action_key"] === "string" ? question["action_key"] : "",
|
|
281
|
+
payload_hash: typeof question["payload_hash"] === "string" ? question["payload_hash"] : "",
|
|
282
|
+
policy_hash: typeof question["policy_hash"] === "string" ? question["policy_hash"] : "",
|
|
283
|
+
live_rate: typeof question["live_rate"] === "number" ? question["live_rate"] : Number.NaN,
|
|
284
|
+
};
|
|
285
|
+
if (!sameQuestion(derived, asked)) {
|
|
286
|
+
return {
|
|
287
|
+
ok: false,
|
|
288
|
+
reason: "draw-answer-invalid",
|
|
289
|
+
detail: "the daemon answered a different question than the one asked; it resolved this action against a policy or a rate this process did not",
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
if (typeof body["selected"] !== "boolean") {
|
|
293
|
+
return { ok: false, reason: "draw-answer-invalid", detail: "the verdict was not a boolean" };
|
|
294
|
+
}
|
|
295
|
+
if (!isHex64(body["mac"])) {
|
|
296
|
+
return { ok: false, reason: "draw-answer-invalid", detail: "the answer carried no usable MAC" };
|
|
297
|
+
}
|
|
298
|
+
const pid = body["daemon_pid"];
|
|
299
|
+
if (typeof pid !== "number" || !Number.isInteger(pid) || pid <= 0) {
|
|
300
|
+
return { ok: false, reason: "draw-answer-invalid", detail: "the answer named no daemon pid" };
|
|
301
|
+
}
|
|
302
|
+
return {
|
|
303
|
+
ok: true,
|
|
304
|
+
answer: {
|
|
305
|
+
v: DRAW_PROTOCOL_VERSION,
|
|
306
|
+
question: derived,
|
|
307
|
+
selected: body["selected"],
|
|
308
|
+
mac: body["mac"],
|
|
309
|
+
daemon_pid: pid,
|
|
310
|
+
answered_at: typeof body["answered_at"] === "string" ? body["answered_at"] : "",
|
|
311
|
+
},
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
/** Where the relay child lives, beside this module in the built tree. */
|
|
315
|
+
export function drawChildPath() {
|
|
316
|
+
return fileURLToPath(new URL("../daemon/draw-child.js", import.meta.url));
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* What an asker concludes about the socket file, before anything is dialled.
|
|
320
|
+
*
|
|
321
|
+
* Exported since APRV-281 for a second, non-asking caller: the hook prints a
|
|
322
|
+
* line saying where a request went and whether anything is there to consume it,
|
|
323
|
+
* and "anything is there" is exactly this question. Nothing is connected here,
|
|
324
|
+
* so a `{ ok: true }` says the file looks like this user's live daemon rather
|
|
325
|
+
* than that the far side answers; a caller that needs the stronger claim asks a
|
|
326
|
+
* question through {@link askDaemonDraw}.
|
|
327
|
+
*
|
|
328
|
+
* One predicate rather than a copy per caller, for the reason `liveClassesOf`
|
|
329
|
+
* is shared: two readers of the same file that reach different conclusions
|
|
330
|
+
* about whether a daemon is up would each be right in its own words and
|
|
331
|
+
* useless together.
|
|
332
|
+
*/
|
|
333
|
+
export function drawSocketUsable(path) {
|
|
334
|
+
if (path.length > DRAW_SOCKET_PATH_LIMIT) {
|
|
335
|
+
return {
|
|
336
|
+
ok: false,
|
|
337
|
+
reason: "draw-daemon-absent",
|
|
338
|
+
detail: `the derived socket path is ${String(path.length)} bytes, past the ${String(DRAW_SOCKET_PATH_LIMIT)}-byte limit a Unix socket can carry; no daemon can serve draws for this log`,
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
if (!existsSync(path)) {
|
|
342
|
+
return {
|
|
343
|
+
ok: false,
|
|
344
|
+
reason: "draw-daemon-absent",
|
|
345
|
+
detail: `no draw socket at ${path}; supervised-live needs \`approval daemon run\` (or \`approval up\`) started in a terminal where the sampling secret resolves`,
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
let stats;
|
|
349
|
+
try {
|
|
350
|
+
stats = statSync(path);
|
|
351
|
+
}
|
|
352
|
+
catch (cause) {
|
|
353
|
+
return {
|
|
354
|
+
ok: false,
|
|
355
|
+
reason: "draw-daemon-stale",
|
|
356
|
+
detail: `${path} could not be stat'd: ${cause instanceof Error ? cause.message : String(cause)}`,
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
if (!stats.isSocket()) {
|
|
360
|
+
return { ok: false, reason: "draw-daemon-stale", detail: `${path} is not a socket` };
|
|
361
|
+
}
|
|
362
|
+
const euid = typeof process.geteuid === "function" ? process.geteuid() : null;
|
|
363
|
+
if (euid === null) {
|
|
364
|
+
return {
|
|
365
|
+
ok: false,
|
|
366
|
+
reason: "draw-daemon-stale",
|
|
367
|
+
detail: "socket ownership cannot be established on this platform, so no answer from it can be attributed to this user's daemon",
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
if (stats.uid !== euid) {
|
|
371
|
+
return {
|
|
372
|
+
ok: false,
|
|
373
|
+
reason: "draw-daemon-stale",
|
|
374
|
+
detail: `${path} is owned by uid ${String(stats.uid)}, not by this user (${String(euid)})`,
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
if ((stats.mode & 0o077) !== 0) {
|
|
378
|
+
return {
|
|
379
|
+
ok: false,
|
|
380
|
+
reason: "draw-daemon-stale",
|
|
381
|
+
detail: `${path} is reachable by group or other (mode ${(stats.mode & 0o777).toString(8)}); an answer from a socket anyone can bind is an answer from nobody in particular`,
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
return { ok: true };
|
|
385
|
+
}
|
|
386
|
+
/**
|
|
387
|
+
* Ask the daemon, synchronously, from a process that holds no secret.
|
|
388
|
+
*
|
|
389
|
+
* A child process, and the module header of `daemon/draw-child.ts` says why:
|
|
390
|
+
* the gate's request path is synchronous end to end (APRV-188 measured and
|
|
391
|
+
* documented the same constraint), `node:net` is not, and there is no
|
|
392
|
+
* synchronous Unix-socket client in Node. So one `spawnSync` of a tiny relay,
|
|
393
|
+
* about 20-40 ms of Node start. That cost is affordable HERE and nowhere else:
|
|
394
|
+
* this runs only for a `supervised-live` class whose deciding process has no
|
|
395
|
+
* secret, which is off the pass-through path entirely — no `Read`, no ordinary
|
|
396
|
+
* `Edit`, no autonomous class ever reaches it.
|
|
397
|
+
*
|
|
398
|
+
* The child is given a deliberately bare environment. It needs `PATH` for
|
|
399
|
+
* nothing and holds no secret; passing the session's environment to it would
|
|
400
|
+
* hand a relay every credential the session was launched with, which is the
|
|
401
|
+
* scrub `core/child-env.ts` exists to prevent.
|
|
402
|
+
*/
|
|
403
|
+
export function askDaemonDraw(logPath, question) {
|
|
404
|
+
const path = drawSocketPathFor(logPath);
|
|
405
|
+
const usable = drawSocketUsable(path);
|
|
406
|
+
if (!usable.ok)
|
|
407
|
+
return usable;
|
|
408
|
+
const child = spawnSync(process.execPath, [drawChildPath(), path], {
|
|
409
|
+
input: `${JSON.stringify(question)}\n`,
|
|
410
|
+
encoding: "utf8",
|
|
411
|
+
timeout: DRAW_SPAWN_TIMEOUT_MS,
|
|
412
|
+
env: {},
|
|
413
|
+
});
|
|
414
|
+
if (child.error !== undefined && child.error !== null) {
|
|
415
|
+
return {
|
|
416
|
+
ok: false,
|
|
417
|
+
reason: "draw-daemon-stale",
|
|
418
|
+
detail: `the draw relay could not be run: ${child.error.message}`,
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
const line = (child.stdout ?? "").trim();
|
|
422
|
+
if (line.length === 0) {
|
|
423
|
+
return {
|
|
424
|
+
ok: false,
|
|
425
|
+
reason: "draw-daemon-stale",
|
|
426
|
+
detail: `the draw relay said nothing (exit ${String(child.status)})`,
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
let relayed;
|
|
430
|
+
try {
|
|
431
|
+
relayed = JSON.parse(line);
|
|
432
|
+
}
|
|
433
|
+
catch {
|
|
434
|
+
return { ok: false, reason: "draw-answer-invalid", detail: "the relay did not emit JSON" };
|
|
435
|
+
}
|
|
436
|
+
const body = relayed;
|
|
437
|
+
if (body["ok"] !== true) {
|
|
438
|
+
const reason = body["reason"];
|
|
439
|
+
return {
|
|
440
|
+
ok: false,
|
|
441
|
+
reason: DRAW_REFUSAL_REASONS.includes(reason)
|
|
442
|
+
? reason
|
|
443
|
+
: "draw-daemon-stale",
|
|
444
|
+
detail: typeof body["detail"] === "string" ? body["detail"] : "the relay refused without a reason",
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
const outcome = parseDrawAnswer(JSON.stringify(body["answer"]), question);
|
|
448
|
+
if (!outcome.ok)
|
|
449
|
+
return outcome;
|
|
450
|
+
// A pid that is gone means the socket outlived its server: something answered
|
|
451
|
+
// from a file nobody is listening on, which cannot happen, or the daemon died
|
|
452
|
+
// between answering and now, which can. Either way the answer is not one a
|
|
453
|
+
// running daemon stands behind.
|
|
454
|
+
try {
|
|
455
|
+
process.kill(outcome.answer.daemon_pid, 0);
|
|
456
|
+
}
|
|
457
|
+
catch (cause) {
|
|
458
|
+
if (cause.code === "ESRCH") {
|
|
459
|
+
return {
|
|
460
|
+
ok: false,
|
|
461
|
+
reason: "draw-daemon-stale",
|
|
462
|
+
detail: `the answering daemon (pid ${String(outcome.answer.daemon_pid)}) is no longer running`,
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
// EPERM means it exists and belongs to someone else, which the socket
|
|
466
|
+
// ownership check above has already ruled out for the socket itself.
|
|
467
|
+
}
|
|
468
|
+
return outcome;
|
|
469
|
+
}
|
|
470
|
+
// ---------------------------------------------------------------------------
|
|
471
|
+
// The status question (APRV-271)
|
|
472
|
+
// ---------------------------------------------------------------------------
|
|
473
|
+
/**
|
|
474
|
+
* The second thing this socket answers: not "would these bytes be sampled" but
|
|
475
|
+
* "is your sampler running at all" (APRV-271).
|
|
476
|
+
*
|
|
477
|
+
* ## Why the socket, and not the asker's own environment
|
|
478
|
+
*
|
|
479
|
+
* `approval doctor`'s `audit-sampling` row read `APPROVAL_SAMPLING_SECRET` out
|
|
480
|
+
* of the shell doctor was launched in, and reported `secret-unset` whenever it
|
|
481
|
+
* was not there. It is never there: the secret lives in the ONE terminal the
|
|
482
|
+
* operator ran `eval "$(approval env)"` in and started the daemon from, and
|
|
483
|
+
* `core/child-env.ts` strips `APPROVAL_*` from every child. So the row was red
|
|
484
|
+
* on machines where sampling was running perfectly, which is the same shape of
|
|
485
|
+
* bug APRV-208 fixed for the draw itself, in the row next door.
|
|
486
|
+
*
|
|
487
|
+
* The process that legitimately knows is the daemon. This asks it.
|
|
488
|
+
*
|
|
489
|
+
* ## What it deliberately is not
|
|
490
|
+
*
|
|
491
|
+
* **Not MAC'd, and not believed the way a draw is.** A draw answer decides a
|
|
492
|
+
* verdict, so it is evidenced: recorded with a MAC an operator recomputes later
|
|
493
|
+
* (see {@link LiveDrawRecord}). This one decides nothing. It authorizes no
|
|
494
|
+
* action, spends no budget, gates nothing and is written to no log; it changes
|
|
495
|
+
* the wording and the colour of one diagnostic row. What bounds who may make
|
|
496
|
+
* the claim is the socket itself, which {@link askDaemonSampling} requires to
|
|
497
|
+
* be owner-only and owned by this euid before it will dial it.
|
|
498
|
+
*
|
|
499
|
+
* That bound is why the caller must keep the question narrow. The only sampler
|
|
500
|
+
* state another process may honestly answer for is `secret-unset`, which is a
|
|
501
|
+
* fact about a PROCESS ENVIRONMENT. Every other disabled reason (`rate-absent`,
|
|
502
|
+
* `rate-invalid`, `rate-zero`, `secret-env-unnamed`, `policy-unreadable`) is a
|
|
503
|
+
* fact about the policy FILE, which the asker is reading for itself and no
|
|
504
|
+
* daemon's answer may soften. `cli/doctor.ts` consults this on that one branch
|
|
505
|
+
* and nowhere else.
|
|
506
|
+
*
|
|
507
|
+
* **Never the secret.** The report carries the variable's NAME, which the
|
|
508
|
+
* policy file already states in the open, and the rate, which it also states.
|
|
509
|
+
* The value is in one field of one process and leaves it in nothing but a MAC.
|
|
510
|
+
*/
|
|
511
|
+
export const SAMPLING_QUERY = "sampling";
|
|
512
|
+
/** Is this line a status question rather than a draw? Used by the server. */
|
|
513
|
+
export function isSamplingQuery(line) {
|
|
514
|
+
let parsed;
|
|
515
|
+
try {
|
|
516
|
+
parsed = JSON.parse(line);
|
|
517
|
+
}
|
|
518
|
+
catch {
|
|
519
|
+
return false;
|
|
520
|
+
}
|
|
521
|
+
if (typeof parsed !== "object" || parsed === null)
|
|
522
|
+
return false;
|
|
523
|
+
const body = parsed;
|
|
524
|
+
return body["v"] === DRAW_PROTOCOL_VERSION && body["query"] === SAMPLING_QUERY;
|
|
525
|
+
}
|
|
526
|
+
/**
|
|
527
|
+
* Read one status answer.
|
|
528
|
+
*
|
|
529
|
+
* Every field is taken apart and rebuilt rather than passed through, so an
|
|
530
|
+
* answer carrying keys this protocol never defined loses them here: a report
|
|
531
|
+
* printed by a diagnostic must not be able to carry text of the answerer's
|
|
532
|
+
* choosing into an operator's terminal.
|
|
533
|
+
*/
|
|
534
|
+
export function parseSamplingAnswer(text) {
|
|
535
|
+
let parsed;
|
|
536
|
+
try {
|
|
537
|
+
parsed = JSON.parse(text);
|
|
538
|
+
}
|
|
539
|
+
catch {
|
|
540
|
+
return { ok: false, reason: "draw-answer-invalid", detail: "the answer was not JSON" };
|
|
541
|
+
}
|
|
542
|
+
if (typeof parsed !== "object" || parsed === null) {
|
|
543
|
+
return { ok: false, reason: "draw-answer-invalid", detail: "the answer was not an object" };
|
|
544
|
+
}
|
|
545
|
+
const body = parsed;
|
|
546
|
+
if (body["ok"] === false) {
|
|
547
|
+
// An older daemon, or one that read this line as a malformed draw. It said
|
|
548
|
+
// no, which is the safe direction: the caller reports no daemon answered.
|
|
549
|
+
return {
|
|
550
|
+
ok: false,
|
|
551
|
+
reason: "draw-answer-invalid",
|
|
552
|
+
detail: typeof body["detail"] === "string"
|
|
553
|
+
? body["detail"]
|
|
554
|
+
: "the daemon refused the status question",
|
|
555
|
+
};
|
|
556
|
+
}
|
|
557
|
+
if (body["v"] !== DRAW_PROTOCOL_VERSION) {
|
|
558
|
+
return {
|
|
559
|
+
ok: false,
|
|
560
|
+
reason: "draw-answer-invalid",
|
|
561
|
+
detail: `the answer declares protocol version ${JSON.stringify(body["v"])}, not ${String(DRAW_PROTOCOL_VERSION)}`,
|
|
562
|
+
};
|
|
563
|
+
}
|
|
564
|
+
const reported = body["sampling"];
|
|
565
|
+
if (typeof reported !== "object" || reported === null) {
|
|
566
|
+
return { ok: false, reason: "draw-answer-invalid", detail: "the answer carried no sampling report" };
|
|
567
|
+
}
|
|
568
|
+
const report = reported;
|
|
569
|
+
if (typeof report["enabled"] !== "boolean") {
|
|
570
|
+
return { ok: false, reason: "draw-answer-invalid", detail: "the report did not say whether sampling is enabled" };
|
|
571
|
+
}
|
|
572
|
+
const pid = body["daemon_pid"];
|
|
573
|
+
if (typeof pid !== "number" || !Number.isInteger(pid) || pid <= 0) {
|
|
574
|
+
return { ok: false, reason: "draw-answer-invalid", detail: "the answer named no daemon pid" };
|
|
575
|
+
}
|
|
576
|
+
const classes = report["live_classes"];
|
|
577
|
+
return {
|
|
578
|
+
ok: true,
|
|
579
|
+
answer: {
|
|
580
|
+
v: DRAW_PROTOCOL_VERSION,
|
|
581
|
+
sampling: {
|
|
582
|
+
enabled: report["enabled"],
|
|
583
|
+
reason: typeof report["reason"] === "string" ? report["reason"] : null,
|
|
584
|
+
secret_env: typeof report["secret_env"] === "string" ? report["secret_env"] : null,
|
|
585
|
+
rate: typeof report["rate"] === "number" && Number.isFinite(report["rate"]) ? report["rate"] : null,
|
|
586
|
+
live_classes: Array.isArray(classes)
|
|
587
|
+
? classes.filter((entry) => typeof entry === "string")
|
|
588
|
+
: [],
|
|
589
|
+
},
|
|
590
|
+
daemon_pid: pid,
|
|
591
|
+
answered_at: typeof body["answered_at"] === "string" ? body["answered_at"] : "",
|
|
592
|
+
},
|
|
593
|
+
};
|
|
594
|
+
}
|
|
595
|
+
/**
|
|
596
|
+
* Open the draw socket, optionally exchange one line, and hang up.
|
|
597
|
+
*
|
|
598
|
+
* ASYNCHRONOUS, unlike {@link askDaemonDraw}, and that difference is the whole
|
|
599
|
+
* reason it can exist without a relay child. The draw is asked from the gate's
|
|
600
|
+
* synchronous request path, which is why it pays for a `spawnSync`; every
|
|
601
|
+
* caller of this one is a diagnostic that is already inside an async function,
|
|
602
|
+
* so it dials the socket directly and costs no process at all.
|
|
603
|
+
*
|
|
604
|
+
* `request` of `null` connects and closes without saying anything, which is how
|
|
605
|
+
* a caller asks the only question a socket file cannot answer by existing:
|
|
606
|
+
* whether anything is listening on it.
|
|
607
|
+
*/
|
|
608
|
+
export async function dialDrawSocket(path, request, timeoutMs = DRAW_TIMEOUT_MS) {
|
|
609
|
+
return await new Promise((settleWith) => {
|
|
610
|
+
let settled = false;
|
|
611
|
+
let buffer = "";
|
|
612
|
+
const socket = connect(path);
|
|
613
|
+
const settle = (outcome) => {
|
|
614
|
+
if (settled)
|
|
615
|
+
return;
|
|
616
|
+
settled = true;
|
|
617
|
+
socket.destroy();
|
|
618
|
+
settleWith(outcome);
|
|
619
|
+
};
|
|
620
|
+
socket.setEncoding("utf8");
|
|
621
|
+
socket.setTimeout(timeoutMs);
|
|
622
|
+
socket.on("connect", () => {
|
|
623
|
+
if (request === null) {
|
|
624
|
+
settle({ ok: true, line: null });
|
|
625
|
+
return;
|
|
626
|
+
}
|
|
627
|
+
socket.write(`${request}\n`);
|
|
628
|
+
});
|
|
629
|
+
socket.on("data", (chunk) => {
|
|
630
|
+
buffer += chunk;
|
|
631
|
+
const newline = buffer.indexOf("\n");
|
|
632
|
+
if (newline < 0) {
|
|
633
|
+
if (buffer.length > 8192) {
|
|
634
|
+
settle({ ok: false, reason: "draw-answer-invalid", detail: "the answer had no line ending" });
|
|
635
|
+
}
|
|
636
|
+
return;
|
|
637
|
+
}
|
|
638
|
+
settle({ ok: true, line: buffer.slice(0, newline) });
|
|
639
|
+
});
|
|
640
|
+
socket.on("timeout", () => {
|
|
641
|
+
settle({
|
|
642
|
+
ok: false,
|
|
643
|
+
reason: "draw-daemon-stale",
|
|
644
|
+
detail: `nothing answered within ${String(timeoutMs)}ms; the socket is there and the daemon behind it is not serving`,
|
|
645
|
+
});
|
|
646
|
+
});
|
|
647
|
+
socket.on("error", (cause) => {
|
|
648
|
+
settle({
|
|
649
|
+
ok: false,
|
|
650
|
+
reason: "draw-daemon-stale",
|
|
651
|
+
detail: `the socket refused the connection: ${cause.message}`,
|
|
652
|
+
});
|
|
653
|
+
});
|
|
654
|
+
socket.on("close", () => {
|
|
655
|
+
settle({
|
|
656
|
+
ok: false,
|
|
657
|
+
reason: "draw-daemon-stale",
|
|
658
|
+
detail: "the daemon closed the connection without answering",
|
|
659
|
+
});
|
|
660
|
+
});
|
|
661
|
+
});
|
|
662
|
+
}
|
|
663
|
+
/**
|
|
664
|
+
* Ask the running daemon what its own sampler is doing.
|
|
665
|
+
*
|
|
666
|
+
* The same pre-checks a draw makes, for the same reasons: a path past the
|
|
667
|
+
* address limit, an absent file, a foreign owner and a group- or world-writable
|
|
668
|
+
* mode each mean no answer from here can be attributed to this user's daemon.
|
|
669
|
+
* The pid is checked for liveness afterwards, because a socket that outlived
|
|
670
|
+
* its server is a file answering for a process that is gone.
|
|
671
|
+
*/
|
|
672
|
+
export async function askDaemonSampling(logPath, timeoutMs = DRAW_TIMEOUT_MS) {
|
|
673
|
+
const socket = drawSocketPathFor(logPath);
|
|
674
|
+
const usable = drawSocketUsable(socket);
|
|
675
|
+
if (!usable.ok)
|
|
676
|
+
return { ok: false, reason: usable.reason, detail: usable.detail, socket };
|
|
677
|
+
const dialled = await dialDrawSocket(socket, JSON.stringify({ v: DRAW_PROTOCOL_VERSION, query: SAMPLING_QUERY }), timeoutMs);
|
|
678
|
+
if (!dialled.ok)
|
|
679
|
+
return { ok: false, reason: dialled.reason, detail: dialled.detail, socket };
|
|
680
|
+
if (dialled.line === null) {
|
|
681
|
+
return { ok: false, reason: "draw-answer-invalid", detail: "the daemon said nothing", socket };
|
|
682
|
+
}
|
|
683
|
+
const parsed = parseSamplingAnswer(dialled.line);
|
|
684
|
+
if (!parsed.ok)
|
|
685
|
+
return { ok: false, reason: parsed.reason, detail: parsed.detail, socket };
|
|
686
|
+
try {
|
|
687
|
+
process.kill(parsed.answer.daemon_pid, 0);
|
|
688
|
+
}
|
|
689
|
+
catch (cause) {
|
|
690
|
+
if (cause.code === "ESRCH") {
|
|
691
|
+
return {
|
|
692
|
+
ok: false,
|
|
693
|
+
reason: "draw-daemon-stale",
|
|
694
|
+
detail: `the answering daemon (pid ${String(parsed.answer.daemon_pid)}) is no longer running`,
|
|
695
|
+
socket,
|
|
696
|
+
};
|
|
697
|
+
}
|
|
698
|
+
// EPERM means it exists and belongs to someone else, which the socket's
|
|
699
|
+
// own ownership check has already ruled out.
|
|
700
|
+
}
|
|
701
|
+
return { ok: true, answer: parsed.answer, socket };
|
|
702
|
+
}
|
|
703
|
+
//# sourceMappingURL=live-draw.js.map
|