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,635 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `approval setup channel <name>` — configure one channel (APRV-79).
|
|
3
|
+
*
|
|
4
|
+
* SPEC.md §4 draws a line this verb's NAME now carries: a **channel** surfaces
|
|
5
|
+
* requests and collects decisions and holds no state; an **adapter** executes
|
|
6
|
+
* side effects and holds credentials. `approval setup adapter <name>` (APRV-78)
|
|
7
|
+
* fills the VAULT from an adapter's manifest. This verb fills the OS keystore
|
|
8
|
+
* and `.approval/env` from a channel's, because what a channel needs is not a
|
|
9
|
+
* credential an action spends inside a token window: it is the transport
|
|
10
|
+
* credential that lets the runtime ask a human anything at all.
|
|
11
|
+
*
|
|
12
|
+
* An older build spelled the Telegram one without the `channel` noun. That form
|
|
13
|
+
* is gone rather than aliased — the dispatch in `cli/setup.ts` answers it at
|
|
14
|
+
* exit 2 with the new one, in {@link RENAMED_NOTICE} — because two spellings of
|
|
15
|
+
* a distinction the SPEC draws on purpose is exactly how the distinction stops
|
|
16
|
+
* being drawn.
|
|
17
|
+
*
|
|
18
|
+
* ## What Telegram's flow keeps, unchanged
|
|
19
|
+
*
|
|
20
|
+
* The conversation is now `cli/setup-flow.ts`'s, the same one `setup adapter`
|
|
21
|
+
* runs, over the manifest `core/telegram-config.ts` declares. Everything that
|
|
22
|
+
* was a decision rather than a phrasing survives it, and each of these is
|
|
23
|
+
* asserted in `tests/cli-setup.test.ts`:
|
|
24
|
+
*
|
|
25
|
+
* - **`getMe` happens before anything is written.** It runs at the END of the
|
|
26
|
+
* token's collection (step five of the flow), not after the write (step
|
|
27
|
+
* seven), so a token the Bot API refuses costs the operator one line of
|
|
28
|
+
* output and leaves `.approval/env` untouched. The chat discovery below it
|
|
29
|
+
* also needs the bot's username, and asking `getMe` for it is the same call.
|
|
30
|
+
* - **No `getUpdates` from this verb carries an offset, ever.** An offset is an
|
|
31
|
+
* ACKNOWLEDGEMENT: it tells the Bot API that everything below it may be
|
|
32
|
+
* discarded, and a decision tap consumed here would never reach the listener
|
|
33
|
+
* that was waiting for it. `allowed_updates` is `["message"]`, so a pending
|
|
34
|
+
* `callback_query` is not even delivered to this process.
|
|
35
|
+
* - **The token is never typed into this process** on a machine with a
|
|
36
|
+
* keystore: the helper's own no-echo prompt collects it and this runtime
|
|
37
|
+
* learns it by reading the item back on stdout.
|
|
38
|
+
* - **The chat id is a literal.** A chat id is not a secret; the token is.
|
|
39
|
+
* - "send the bot a message", the 409 hint, the manual-curl refusal when nothing
|
|
40
|
+
* arrives, and the optional send-a-test-message proof that defaults to NO.
|
|
41
|
+
* What changed in APRV-96 is only WHEN the read happens: the verb long-polls
|
|
42
|
+
* continuously until a message arrives or {@link DISCOVERY_DEADLINE_MS}
|
|
43
|
+
* passes, instead of asking the operator to press Enter between reads.
|
|
44
|
+
*
|
|
45
|
+
* ## Where the two hooks sit, and why
|
|
46
|
+
*
|
|
47
|
+
* The flow asks its questions in one fixed order, and Telegram's shape falls
|
|
48
|
+
* out of it: the token is `collect`, because a human supplies it; the chat id is
|
|
49
|
+
* `discover`, because the SERVICE supplies it and the human's only part is
|
|
50
|
+
* sending a message and picking from what arrived. That is the hook APRV-78
|
|
51
|
+
* reserved and did not call. The proof is `verify`, which runs after the write —
|
|
52
|
+
* later than the old hand-rolled flow ran it, and correct in the same way the
|
|
53
|
+
* adapter's SMTP probe is: what is being proved is the stored configuration.
|
|
54
|
+
*/
|
|
55
|
+
import { LEGACY_SERVICE_TELEGRAM_TOKEN, instanceHomeFor, instanceIdFor } from "../core/instance.js";
|
|
56
|
+
import { telegramChatEnvFor, telegramCredentialSpecs, telegramTokenEnvFor, } from "../core/telegram-config.js";
|
|
57
|
+
import { EXIT_INTEGRITY, EXIT_IO, EXIT_OK } from "./exit-codes.js";
|
|
58
|
+
import { SETUP_CHANNEL_HELP, SETUP_CHANNEL_TELEGRAM_HELP } from "./help.js";
|
|
59
|
+
import { PROBE_TIMEOUT_MS, detail, front, offerLiteral, requireHuman, retrievalCommand, schemeFor, storageCommand, usageError, } from "./setup-common.js";
|
|
60
|
+
import { envFileDestination, pickOne, runCredentialFlow, } from "./setup-flow.js";
|
|
61
|
+
/**
|
|
62
|
+
* How long the verb waits for a message before it gives up (APRV-96).
|
|
63
|
+
*
|
|
64
|
+
* It replaces the three "send one and press Enter" attempts, which made the
|
|
65
|
+
* operator's TIMING part of whether the verb worked: a message sent while the
|
|
66
|
+
* 10s long poll was not running was simply not seen, and the verb had no way to
|
|
67
|
+
* say so. Ninety seconds is long enough to unlock a phone and find the chat,
|
|
68
|
+
* and short enough that a run left in a forgotten terminal ends by itself.
|
|
69
|
+
*/
|
|
70
|
+
const DISCOVERY_DEADLINE_MS = 90_000;
|
|
71
|
+
/** A deadline in whole seconds, for the two lines that state it. Never "0s". */
|
|
72
|
+
function statedSeconds(ms) {
|
|
73
|
+
return `${String(Math.max(1, Math.round(ms / 1000)))}s`;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* The refusal for the OLD spelling, as one constant on one line.
|
|
77
|
+
*
|
|
78
|
+
* One line and one identifier, because `tests/setup-rename.test.ts` sweeps
|
|
79
|
+
* `src/`, `docs/`, `examples/`, `README.md` and `SPEC.md` for the bare phrase
|
|
80
|
+
* and exempts exactly the lines that also name `RENAMED_NOTICE`. That is the
|
|
81
|
+
* whole exemption mechanism: a future line that reintroduces the old spelling
|
|
82
|
+
* cannot claim the exemption without saying this constant's name out loud.
|
|
83
|
+
*/
|
|
84
|
+
export const RENAMED_NOTICE = `\`approval setup telegram\` is now \`approval setup channel telegram\`, and there is no alias. A channel surfaces requests and collects decisions and holds no state; an adapter executes side effects and holds credentials. The two setup verbs fill different stores, so a channel's name belongs under \`channel\` (the OS keystore and .approval/env) and an adapter's under \`adapter\` (the vault)`;
|
|
85
|
+
// ---------------------------------------------------------------------------
|
|
86
|
+
// Telegram: the Bot API calls
|
|
87
|
+
// ---------------------------------------------------------------------------
|
|
88
|
+
/** Replace the token wherever it appears. Nothing leaves this file with it. */
|
|
89
|
+
function redact(text, token) {
|
|
90
|
+
return token.length === 0 ? text : text.split(token).join("<token redacted>");
|
|
91
|
+
}
|
|
92
|
+
/** One Bot API call, with doctor's probe shape: the token is in the URL only. */
|
|
93
|
+
async function call(fetchImpl, apiBase, token, method, body, timeoutMs) {
|
|
94
|
+
const controller = new AbortController();
|
|
95
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
96
|
+
try {
|
|
97
|
+
const response = await fetchImpl(`${apiBase}/bot${token}/${method}`, {
|
|
98
|
+
method: "POST",
|
|
99
|
+
headers: { "content-type": "application/json" },
|
|
100
|
+
body: JSON.stringify(body),
|
|
101
|
+
signal: controller.signal,
|
|
102
|
+
});
|
|
103
|
+
const raw = await response.text();
|
|
104
|
+
let envelope = {};
|
|
105
|
+
try {
|
|
106
|
+
const parsed = JSON.parse(raw);
|
|
107
|
+
if (typeof parsed === "object" && parsed !== null)
|
|
108
|
+
envelope = parsed;
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
/* a non-JSON body is not an ok envelope; handled by the caller */
|
|
112
|
+
}
|
|
113
|
+
return { ok: response.ok, status: response.status, envelope };
|
|
114
|
+
}
|
|
115
|
+
catch (cause) {
|
|
116
|
+
return { failed: redact(detail(cause), token) };
|
|
117
|
+
}
|
|
118
|
+
finally {
|
|
119
|
+
clearTimeout(timer);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* The chats in a `getUpdates` result, newest first and deduplicated by id.
|
|
124
|
+
*
|
|
125
|
+
* `title ?? username ?? first_name` is Telegram's own precedence for what to
|
|
126
|
+
* call a chat: groups and channels carry a title, a private chat carries the
|
|
127
|
+
* user's username or, for a user who has set none, their first name.
|
|
128
|
+
*/
|
|
129
|
+
function candidatesFrom(updates) {
|
|
130
|
+
const found = new Map();
|
|
131
|
+
const list = Array.isArray(updates) ? [...updates].reverse() : [];
|
|
132
|
+
for (const update of list) {
|
|
133
|
+
if (typeof update !== "object" || update === null)
|
|
134
|
+
continue;
|
|
135
|
+
const message = update["message"];
|
|
136
|
+
if (typeof message !== "object" || message === null)
|
|
137
|
+
continue;
|
|
138
|
+
const chat = message["chat"];
|
|
139
|
+
if (typeof chat !== "object" || chat === null)
|
|
140
|
+
continue;
|
|
141
|
+
const record = chat;
|
|
142
|
+
const id = record["id"];
|
|
143
|
+
if (typeof id !== "number" && typeof id !== "string")
|
|
144
|
+
continue;
|
|
145
|
+
const key = String(id);
|
|
146
|
+
if (found.has(key))
|
|
147
|
+
continue;
|
|
148
|
+
const name = typeof record["title"] === "string"
|
|
149
|
+
? record["title"]
|
|
150
|
+
: typeof record["username"] === "string"
|
|
151
|
+
? `@${record["username"]}`
|
|
152
|
+
: typeof record["first_name"] === "string"
|
|
153
|
+
? record["first_name"]
|
|
154
|
+
: "unnamed";
|
|
155
|
+
found.set(key, {
|
|
156
|
+
id: key,
|
|
157
|
+
type: typeof record["type"] === "string" ? record["type"] : "unknown",
|
|
158
|
+
name,
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
return [...found.values()];
|
|
162
|
+
}
|
|
163
|
+
// ---------------------------------------------------------------------------
|
|
164
|
+
// Telegram: the hooks
|
|
165
|
+
// ---------------------------------------------------------------------------
|
|
166
|
+
const TELEGRAM_HINT = (where) => ` # 1. store the bot token (the helper prompts for it with NO ECHO; the token is\n # never an argument, so it never reaches your shell history or \`ps\`):\n ${storageCommand(where.kind === "none" ? "keychain" : where.kind, where.services.telegramToken)}\n\n # 2. find the chat id — send your bot a message first, then:\n curl -s "https://api.telegram.org/bot<token>/getUpdates" \\\n | grep -o '"chat":{"id":[-0-9]*' | head -1\n\n # 3. record both (a chat id is not a secret; the item name carries THIS\n # instance's id, so a second gate on this machine gets its own token):\n printf '%s\\n' '${where.tokenEnv}=${schemeFor(where.kind === "none" ? "keychain" : where.kind, where.services.telegramToken) ?? ""}' '${where.chatEnv}=<id>' >> ${where.envPath}\n chmod 600 ${where.envPath}`;
|
|
167
|
+
/**
|
|
168
|
+
* The Telegram conversation, as three hooks over one run.
|
|
169
|
+
*
|
|
170
|
+
* The mutable state is the token and the bot's username, both learned in
|
|
171
|
+
* `collect` and both needed by `discover` and `verify`. It is a closure and not
|
|
172
|
+
* a field on the entry, so two runs in one process (which is what the test
|
|
173
|
+
* suite is) cannot see each other's token.
|
|
174
|
+
*/
|
|
175
|
+
function telegramHooks(context, deps, streams, helpText) {
|
|
176
|
+
const tokenEnv = telegramTokenEnvFor(context.load);
|
|
177
|
+
const fetchImpl = deps.fetch ?? globalThis.fetch;
|
|
178
|
+
const apiBase = context.apiBase.replace(/\/+$/u, "");
|
|
179
|
+
/** The operator's token. Held for the length of one run and printed nowhere. */
|
|
180
|
+
let token = null;
|
|
181
|
+
/** `@botname`, from getMe. Also the proof that the token works. */
|
|
182
|
+
let username = null;
|
|
183
|
+
/** The chat the operator picked, for the proof's report line. */
|
|
184
|
+
let chosen = null;
|
|
185
|
+
/** The keystore item this run reads and writes. Decided once, by {@link chooseService}. */
|
|
186
|
+
let service = null;
|
|
187
|
+
/**
|
|
188
|
+
* Which keystore item this instance's token lives in (APRV-178).
|
|
189
|
+
*
|
|
190
|
+
* The name it WANTS is the scoped one, `approval-tg-token-<instance id>`, and
|
|
191
|
+
* on a machine with one gate and a clean keystore that is the end of it. Two
|
|
192
|
+
* other situations exist, and the difference between them is the whole task:
|
|
193
|
+
*
|
|
194
|
+
* - **The scoped item is already there.** This instance provisioned itself
|
|
195
|
+
* before. Nothing to ask: the item is this instance's by construction, and
|
|
196
|
+
* storing over it replaces this gate's own token, which is what a re-run is
|
|
197
|
+
* for.
|
|
198
|
+
* - **The scoped item is absent and the UNSCOPED legacy item exists.** That
|
|
199
|
+
* item is the pre-APRV-178 name every gate on this machine resolves to the
|
|
200
|
+
* same value, so it may be this instance's token from before the rename, or
|
|
201
|
+
* it may be another instance's production bot — which is the incident. This
|
|
202
|
+
* runtime cannot tell those apart, and guessing is what consumed a human's
|
|
203
|
+
* approval tap in the wrong listener, so it ASKS, naming the item and this
|
|
204
|
+
* instance's directory, and adopts the legacy name only on a typed `yes`.
|
|
205
|
+
*
|
|
206
|
+
* The reads here can, on a locked keychain, put the OS's own unlock prompt in
|
|
207
|
+
* front of the operator. That is acceptable HERE and nowhere else: this is an
|
|
208
|
+
* interactive verb with a human at the machine who is about to be asked for a
|
|
209
|
+
* password by `security` anyway. `approval doctor` answers the same question
|
|
210
|
+
* from names alone, precisely so that a diagnostic never blocks on a dialog.
|
|
211
|
+
*/
|
|
212
|
+
function chooseService() {
|
|
213
|
+
if (service !== null)
|
|
214
|
+
return service;
|
|
215
|
+
const mine = context.services.telegramToken;
|
|
216
|
+
if (context.backend === "none") {
|
|
217
|
+
service = mine;
|
|
218
|
+
return service;
|
|
219
|
+
}
|
|
220
|
+
// The legacy item is probed FIRST and the scoped one only if it is there,
|
|
221
|
+
// so the ordinary machine — the one with no legacy item at all — costs one
|
|
222
|
+
// extra keystore lookup rather than two, and asks nothing.
|
|
223
|
+
if (!context.keystore.read(LEGACY_SERVICE_TELEGRAM_TOKEN).ok) {
|
|
224
|
+
service = mine;
|
|
225
|
+
return service;
|
|
226
|
+
}
|
|
227
|
+
if (context.keystore.read(mine).ok) {
|
|
228
|
+
service = mine;
|
|
229
|
+
return service;
|
|
230
|
+
}
|
|
231
|
+
streams.out(`\nA bot token is already stored under ${LEGACY_SERVICE_TELEGRAM_TOKEN}, which is the name this\n` +
|
|
232
|
+
`runtime used before item names were scoped to an instance. EVERY gate on this machine\n` +
|
|
233
|
+
`resolves that one name to that one item, so it belongs to whichever instance stored it\n` +
|
|
234
|
+
`last — and nothing here can tell whether that was this one.\n\n` +
|
|
235
|
+
`This instance is ${instanceHomeFor(context.logPath)} (id ${instanceIdFor(context.logPath)}).\n` +
|
|
236
|
+
`Its own item name is ${mine}.\n\n` +
|
|
237
|
+
`Answer NO unless you are certain that stored token is this instance's bot. Sharing one\n` +
|
|
238
|
+
`token between two gates makes both of them long-poll the same bot, and a human's\n` +
|
|
239
|
+
`approval tap is then delivered to whichever listener asked for updates first.\n\n`);
|
|
240
|
+
const answer = context.prompter.readLine(`does ${LEGACY_SERVICE_TELEGRAM_TOKEN} belong to THIS instance? type \`yes\` in full to reuse it: `);
|
|
241
|
+
if ((answer ?? "").trim() === "yes") {
|
|
242
|
+
service = LEGACY_SERVICE_TELEGRAM_TOKEN;
|
|
243
|
+
streams.out(`reusing ${LEGACY_SERVICE_TELEGRAM_TOKEN} for this instance; nothing else on this machine may name it\n`);
|
|
244
|
+
return service;
|
|
245
|
+
}
|
|
246
|
+
service = mine;
|
|
247
|
+
streams.out(`not reused: this instance will store its own token as ${mine}, and ${LEGACY_SERVICE_TELEGRAM_TOKEN} is left untouched\n`);
|
|
248
|
+
return service;
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* getMe — doctor's probe, verbatim in shape: it mutates nothing, sends
|
|
252
|
+
* nothing, and acknowledges nothing. Runs once per run, and always BEFORE the
|
|
253
|
+
* flow reaches its write step.
|
|
254
|
+
*/
|
|
255
|
+
async function probeIdentity() {
|
|
256
|
+
if (username !== null)
|
|
257
|
+
return { ok: true };
|
|
258
|
+
const held = token ?? "";
|
|
259
|
+
const identity = await call(fetchImpl, apiBase, held, "getMe", {}, PROBE_TIMEOUT_MS);
|
|
260
|
+
if ("failed" in identity) {
|
|
261
|
+
streams.err(`approval: getMe on ${apiBase} failed: ${identity.failed}\n`);
|
|
262
|
+
streams.err(` check network reachability of ${apiBase}\n`);
|
|
263
|
+
return { ok: false, code: EXIT_IO };
|
|
264
|
+
}
|
|
265
|
+
if (!identity.ok || identity.envelope["ok"] !== true) {
|
|
266
|
+
const description = redact(String(identity.envelope["description"] ?? "no description"), held);
|
|
267
|
+
streams.err(`approval: getMe on ${apiBase} was refused: HTTP ${String(identity.status)} (${description})\n`);
|
|
268
|
+
streams.err(identity.status === 401 || /unauthorized/iu.test(description)
|
|
269
|
+
? ` the bot token is not valid: re-copy it from @BotFather into ${tokenEnv}\n`
|
|
270
|
+
: ` check the token and that ${apiBase} is the right Bot API base\n`);
|
|
271
|
+
streams.err(` nothing was written to ${context.envPath}\n`);
|
|
272
|
+
return { ok: false, code: EXIT_INTEGRITY };
|
|
273
|
+
}
|
|
274
|
+
const result = (identity.envelope["result"] ?? {});
|
|
275
|
+
username = typeof result["username"] === "string" ? `@${result["username"]}` : "the bot";
|
|
276
|
+
streams.out(`\ntoken valid: ${username} via ${apiBase}\n`);
|
|
277
|
+
return { ok: true };
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* The token, and then the identity probe.
|
|
281
|
+
*
|
|
282
|
+
* On a machine with a keystore the HELPER's prompt collects it and this
|
|
283
|
+
* process learns it only by reading the item back on stdout; the value that
|
|
284
|
+
* lands in `.approval/env` is the SOURCE and never the token. With no
|
|
285
|
+
* keystore it is read with no echo and — after a typed `yes` — written as a
|
|
286
|
+
* plaintext literal, which is the one path on which the file holds the token.
|
|
287
|
+
*/
|
|
288
|
+
async function collectToken() {
|
|
289
|
+
if (context.backend === "none") {
|
|
290
|
+
const read = context.prompter.readSecret(`bot token from @BotFather (not echoed): `);
|
|
291
|
+
if (!read.ok) {
|
|
292
|
+
return {
|
|
293
|
+
kind: "refused",
|
|
294
|
+
code: usageError(streams, false, "the token entry was aborted; nothing was stored and nothing was written", helpText),
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
token = read.value.trim();
|
|
298
|
+
if (token.length === 0) {
|
|
299
|
+
return {
|
|
300
|
+
kind: "refused",
|
|
301
|
+
code: usageError(streams, false, "no token was entered; nothing was written", helpText),
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
if (!offerLiteral(streams, context.prompter, context.envPath, "bot token")) {
|
|
305
|
+
return { kind: "refused", code: EXIT_OK };
|
|
306
|
+
}
|
|
307
|
+
const probed = await probeIdentity();
|
|
308
|
+
if (!probed.ok)
|
|
309
|
+
return { kind: "refused", code: probed.code };
|
|
310
|
+
return { kind: "value", value: token };
|
|
311
|
+
}
|
|
312
|
+
const helper = context.backend === "keychain" ? "macOS `security`" : "`secret-tool`";
|
|
313
|
+
const helperPrompt = context.backend === "keychain"
|
|
314
|
+
? '"password data for new item:" and then "retype password for new item:"'
|
|
315
|
+
: '"Password:"';
|
|
316
|
+
const item = chooseService();
|
|
317
|
+
streams.out(`Next: paste the BOT TOKEN from @BotFather (Telegram: /mybots, pick the bot, "API Token"; it looks like 123456789:AAH...).\n` +
|
|
318
|
+
`${helper} asks for it with its own prompt, ${helperPrompt}. Nothing is echoed as\n` +
|
|
319
|
+
`you paste, and the value goes straight into the keystore as ${item};\n` +
|
|
320
|
+
`this process never sees you type it. There is nothing to look up first: this creates the item.\n` +
|
|
321
|
+
`Already saved it under that name from an earlier run? Pasting the same value updates the item in\n` +
|
|
322
|
+
`place; print it in another window with: ${retrievalCommand(context.backend, item)}\n\n`);
|
|
323
|
+
const stored = context.keystore.storePrompted(item);
|
|
324
|
+
if (!stored.ok) {
|
|
325
|
+
streams.err(`approval: the token could not be stored (${stored.message}); nothing was written to ${context.envPath}\n`);
|
|
326
|
+
return { kind: "refused", code: EXIT_IO };
|
|
327
|
+
}
|
|
328
|
+
const read = context.keystore.read(item);
|
|
329
|
+
if (!read.ok) {
|
|
330
|
+
streams.err(`approval: the token was stored but could not be read back (${read.message}); nothing was written to ${context.envPath}\n`);
|
|
331
|
+
return { kind: "refused", code: EXIT_IO };
|
|
332
|
+
}
|
|
333
|
+
token = read.value.trim();
|
|
334
|
+
const scheme = schemeFor(context.backend, item);
|
|
335
|
+
streams.out(`stored the token as ${scheme}\n`);
|
|
336
|
+
streams.out(` read it back with: ${retrievalCommand(context.backend, item)}\n`);
|
|
337
|
+
const probed = await probeIdentity();
|
|
338
|
+
if (!probed.ok)
|
|
339
|
+
return { kind: "refused", code: probed.code };
|
|
340
|
+
return { kind: "value", value: scheme };
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* The token this run needs, when the token LINE was left alone.
|
|
344
|
+
*
|
|
345
|
+
* A re-run that replaces only the chat id still has to talk to the Bot API,
|
|
346
|
+
* and the token it should use is the one already stored — so it is read back
|
|
347
|
+
* out of the keystore, with no prompt and no write. `.approval/env` is not
|
|
348
|
+
* consulted for it on any path: reading a value out of that file is the
|
|
349
|
+
* resolution §11.1 invariant 7 forbids, which is also why the no-keystore
|
|
350
|
+
* machine (whose token IS the file's literal) cannot take this path.
|
|
351
|
+
*
|
|
352
|
+
* The scoped item is tried first and the unscoped legacy one second (APRV-178),
|
|
353
|
+
* because an instance provisioned before the rename has its token only under
|
|
354
|
+
* the old name and a re-run that replaced just the chat id must not start
|
|
355
|
+
* demanding a token the operator already stored. The fallback is announced on
|
|
356
|
+
* stderr every time it is taken: adopting a machine-global item silently is
|
|
357
|
+
* the behaviour that put a demo gate on the production bot.
|
|
358
|
+
*/
|
|
359
|
+
function recoverToken() {
|
|
360
|
+
if (token !== null)
|
|
361
|
+
return { ok: true };
|
|
362
|
+
if (context.backend === "none") {
|
|
363
|
+
streams.err(`approval: the ${tokenEnv} line was left alone, and this machine has no keystore to read the token back from — the only copy is the literal in ${context.envPath}, and no verb resolves that file on its own. Re-run and replace both lines. Nothing was written\n`);
|
|
364
|
+
return { ok: false, code: EXIT_IO };
|
|
365
|
+
}
|
|
366
|
+
const mine = context.services.telegramToken;
|
|
367
|
+
let read = context.keystore.read(mine);
|
|
368
|
+
if (!read.ok) {
|
|
369
|
+
const legacy = context.keystore.read(LEGACY_SERVICE_TELEGRAM_TOKEN);
|
|
370
|
+
if (legacy.ok) {
|
|
371
|
+
streams.err(`approval: no ${mine} item for this instance, so the token was read from ${LEGACY_SERVICE_TELEGRAM_TOKEN} — the unscoped name every gate on this machine shares. Re-run \`approval setup channel telegram\` and replace the ${tokenEnv} line to give this instance its own item\n`);
|
|
372
|
+
}
|
|
373
|
+
read = legacy;
|
|
374
|
+
}
|
|
375
|
+
if (!read.ok) {
|
|
376
|
+
streams.err(`approval: the ${tokenEnv} line was left alone, so the stored token is what this run would ask the Bot API with, and it could not be read (${read.message}); nothing was written to ${context.envPath}\n`);
|
|
377
|
+
return { ok: false, code: EXIT_IO };
|
|
378
|
+
}
|
|
379
|
+
token = read.value.trim();
|
|
380
|
+
return { ok: true };
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
* What Telegram itself says about this bot's update stream (APRV-96).
|
|
384
|
+
*
|
|
385
|
+
* Read ONLY on the give-up path, and read for one reason: when no message
|
|
386
|
+
* arrives, the three explanations an operator cannot tell apart are "you
|
|
387
|
+
* messaged a different bot", "a webhook is set, so getUpdates returns nothing
|
|
388
|
+
* ever", and "another poller acknowledged it with an offset". `getWebhookInfo`
|
|
389
|
+
* answers the last two directly and `getMe`'s username answers the first, so
|
|
390
|
+
* the refusal prints all three rather than "no message seen yet".
|
|
391
|
+
*
|
|
392
|
+
* It mutates nothing and acknowledges nothing, exactly like `getMe`.
|
|
393
|
+
*/
|
|
394
|
+
async function webhookReport(held) {
|
|
395
|
+
const info = await call(fetchImpl, apiBase, held, "getWebhookInfo", {}, PROBE_TIMEOUT_MS);
|
|
396
|
+
if ("failed" in info) {
|
|
397
|
+
return [` getWebhookInfo could not be reached (${info.failed}), so Telegram's own view of this bot is unknown\n`];
|
|
398
|
+
}
|
|
399
|
+
if (!info.ok || info.envelope["ok"] !== true) {
|
|
400
|
+
const description = redact(String(info.envelope["description"] ?? "no description"), held);
|
|
401
|
+
return [` getWebhookInfo was refused: HTTP ${String(info.status)} (${description})\n`];
|
|
402
|
+
}
|
|
403
|
+
const result = (info.envelope["result"] ?? {});
|
|
404
|
+
const pending = typeof result["pending_update_count"] === "number" ? result["pending_update_count"] : 0;
|
|
405
|
+
const hook = typeof result["url"] === "string" ? redact(result["url"], held) : "";
|
|
406
|
+
const lines = [];
|
|
407
|
+
lines.push(pending > 0
|
|
408
|
+
? ` Telegram holds ${String(pending)} update(s) for this bot that no poller has consumed; another\n process may be long-polling with an offset — stop \`approval channel telegram listen\`\n (here and on any other machine) and retry.\n`
|
|
409
|
+
: ` Telegram holds no pending updates for this bot. If you did send one, something else\n acknowledged it with an offset (a listener or daemon, possibly on another machine); that\n process will also fight \`approval channel telegram listen\` with 409s.\n`);
|
|
410
|
+
lines.push(hook.length > 0
|
|
411
|
+
? ` a webhook is registered at ${hook}; getUpdates returns nothing while a webhook is set —\n remove it with deleteWebhook, or read the chat id off the webhook instead.\n`
|
|
412
|
+
: ` no webhook is registered, so getUpdates is the right way to read this bot.\n`);
|
|
413
|
+
return lines;
|
|
414
|
+
}
|
|
415
|
+
/**
|
|
416
|
+
* The chat, discovered from the bot's own updates.
|
|
417
|
+
*
|
|
418
|
+
* THE getUpdates BELOW CARRIES NO OFFSET, EVER. See this file's module doc.
|
|
419
|
+
*
|
|
420
|
+
* ## Waiting, rather than asking (APRV-96)
|
|
421
|
+
*
|
|
422
|
+
* The loop re-issues the same offset-less read back to back until a message
|
|
423
|
+
* turns up or the deadline passes, and asks the operator for nothing while it
|
|
424
|
+
* does. The old shape asked for Enter between reads, which made the OPERATOR
|
|
425
|
+
* responsible for overlapping their message with a 10s window: a message sent
|
|
426
|
+
* a second late was consumed by nothing, seen by nothing, and reported as "no
|
|
427
|
+
* message seen yet" (observed 2026-08-18 running `examples/email-demo.md`).
|
|
428
|
+
*
|
|
429
|
+
* **Ctrl-C is the abort, and it is the terminal's own.** Nothing here reads
|
|
430
|
+
* the keyboard, so stdin is not in raw mode and this process installs no
|
|
431
|
+
* SIGINT handler: the signal reaches Node's default disposition and the
|
|
432
|
+
* process dies between two HTTP calls. That is safe precisely because of
|
|
433
|
+
* where this hook sits — `.approval/env` is written by the flow only after
|
|
434
|
+
* every hook has returned, so an interrupted wait leaves the file exactly as
|
|
435
|
+
* it found it. (The keystore item the token step created stays, as it does on
|
|
436
|
+
* the give-up path below; it is a stored credential, not a half-written
|
|
437
|
+
* record.) A handler would add a way to be wrong about that and no capability.
|
|
438
|
+
*/
|
|
439
|
+
async function discoverChat() {
|
|
440
|
+
const recovered = recoverToken();
|
|
441
|
+
if (!recovered.ok)
|
|
442
|
+
return { kind: "refused", code: recovered.code };
|
|
443
|
+
const probed = await probeIdentity();
|
|
444
|
+
if (!probed.ok)
|
|
445
|
+
return { kind: "refused", code: probed.code };
|
|
446
|
+
const bot = username ?? "the bot";
|
|
447
|
+
const held = token ?? "";
|
|
448
|
+
const deadlineMs = deps.discoveryDeadlineMs ?? DISCOVERY_DEADLINE_MS;
|
|
449
|
+
const giveUpAt = Date.now() + deadlineMs;
|
|
450
|
+
streams.out(`\nwaiting for a message to ${bot} (up to ${statedSeconds(deadlineMs)}, Ctrl-C to stop):\nopen Telegram and send it anything. No Enter is needed here — this keeps reading\nuntil your message lands, so it does not matter when you send it.\n`);
|
|
451
|
+
let candidates = [];
|
|
452
|
+
for (;;) {
|
|
453
|
+
// NO OFFSET, EVER. An `offset` is an ACKNOWLEDGEMENT: it tells the Bot API
|
|
454
|
+
// that everything below it may be discarded. A running
|
|
455
|
+
// `approval channel telegram listen` owns that acknowledgement, and a
|
|
456
|
+
// decision tap consumed here would never reach the listener that was
|
|
457
|
+
// waiting for it — which is exactly why `approval doctor` refuses to call
|
|
458
|
+
// getUpdates at all. Reading WITHOUT an offset confirms nothing: the
|
|
459
|
+
// pending callback_query updates a listener is waiting for are still
|
|
460
|
+
// pending when this returns. `allowed_updates: ["message"]` narrows the
|
|
461
|
+
// read to the only kind this verb has any use for, so a callback is not
|
|
462
|
+
// even delivered here.
|
|
463
|
+
const updates = await call(fetchImpl, apiBase, held, "getUpdates", { timeout: context.pollTimeoutSeconds, allowed_updates: ["message"] }, context.pollTimeoutSeconds * 1000 + PROBE_TIMEOUT_MS);
|
|
464
|
+
if ("failed" in updates) {
|
|
465
|
+
streams.err(`approval: getUpdates on ${apiBase} failed: ${updates.failed}\n`);
|
|
466
|
+
streams.err(` nothing was written to ${context.envPath}\n`);
|
|
467
|
+
return { kind: "refused", code: EXIT_IO };
|
|
468
|
+
}
|
|
469
|
+
if (!updates.ok || updates.envelope["ok"] !== true) {
|
|
470
|
+
const description = redact(String(updates.envelope["description"] ?? "no description"), held);
|
|
471
|
+
streams.err(`approval: getUpdates on ${apiBase} was refused: HTTP ${String(updates.status)} (${description})\n`);
|
|
472
|
+
streams.err(` a 409 here means another process is long-polling this bot: stop \`approval channel telegram listen\` and re-run\n`);
|
|
473
|
+
return { kind: "refused", code: EXIT_INTEGRITY };
|
|
474
|
+
}
|
|
475
|
+
candidates = candidatesFrom(updates.envelope["result"]);
|
|
476
|
+
if (candidates.length > 0)
|
|
477
|
+
break;
|
|
478
|
+
if (Date.now() >= giveUpAt)
|
|
479
|
+
break;
|
|
480
|
+
}
|
|
481
|
+
if (candidates.length === 0) {
|
|
482
|
+
const said = await webhookReport(held);
|
|
483
|
+
streams.err(`approval: no message reached ${bot} in ${statedSeconds(deadlineMs)}, so there is no chat id to record.\n\nWhat to check first, and what Telegram says about this bot right now:\n\n did you message ${bot}? That is the bot getMe answered for, and the chat header on\n your phone must read exactly that — a message to a different bot lands nowhere here.\n${said.join("")}\nThe token is stored; only the two ${context.envPath} lines are missing. Find the id\nby hand — send the bot a message, then:\n\n curl -s "${apiBase}/bot<token>/getUpdates" | grep -o '"chat":{"id":[-0-9]*'\n\n(the <token> is yours to substitute; it is deliberately not printed here). Then:\n\n printf '%s\\n' '${telegramChatEnvFor(context.load)}=<id>' >> ${context.envPath}\n\nIf the bot is in a GROUP, check that privacy mode is off in @BotFather, or the\nbot never sees plain group messages at all.\n`);
|
|
484
|
+
return { kind: "refused", code: EXIT_INTEGRITY };
|
|
485
|
+
}
|
|
486
|
+
if (candidates.length === 1) {
|
|
487
|
+
const only = candidates[0];
|
|
488
|
+
if (!context.prompter.confirm(`use chat ${only.id} (${only.type}, ${only.name})?`)) {
|
|
489
|
+
streams.out("aborted: nothing was written\n");
|
|
490
|
+
return { kind: "refused", code: EXIT_OK };
|
|
491
|
+
}
|
|
492
|
+
chosen = only;
|
|
493
|
+
}
|
|
494
|
+
else {
|
|
495
|
+
const picked = pickOne(streams, context.prompter, {
|
|
496
|
+
heading: `\n${String(candidates.length)} chats have messaged ${bot}, newest first:\n`,
|
|
497
|
+
items: candidates,
|
|
498
|
+
label: (candidate) => `${candidate.id} (${candidate.type}, ${candidate.name})`,
|
|
499
|
+
prompt: `which one? [1-${String(candidates.length)}]: `,
|
|
500
|
+
defaultIndex: null,
|
|
501
|
+
});
|
|
502
|
+
if (!picked.ok) {
|
|
503
|
+
return {
|
|
504
|
+
kind: "refused",
|
|
505
|
+
code: usageError(streams, false, `${picked.message}; nothing was written`, helpText),
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
chosen = picked.item;
|
|
509
|
+
}
|
|
510
|
+
return { kind: "value", value: chosen.id };
|
|
511
|
+
}
|
|
512
|
+
/**
|
|
513
|
+
* The optional proof. Default NO: a configuration verb that buzzes a phone by
|
|
514
|
+
* default is one an operator runs once and then avoids, which is doctor's
|
|
515
|
+
* argument for calling getMe and nothing else.
|
|
516
|
+
*
|
|
517
|
+
* A send that fails is reported as DECLINED rather than as a failure, because
|
|
518
|
+
* the configuration it would have proved is stored and correct as far as
|
|
519
|
+
* anything here knows: a chat the bot may not post into is a Telegram-side
|
|
520
|
+
* fact the operator fixes in Telegram, and exiting 1 over it would make the
|
|
521
|
+
* verb look like it had refused to write the lines it had just written.
|
|
522
|
+
*/
|
|
523
|
+
async function verifyChat(values) {
|
|
524
|
+
const chatId = values[telegramChatEnvFor(context.load)];
|
|
525
|
+
if (chatId === undefined)
|
|
526
|
+
return { ok: true, declined: true, detail: "" };
|
|
527
|
+
if (!context.prompter.confirm(`send a test message to ${chatId} to prove it?`)) {
|
|
528
|
+
return { ok: true, declined: true, detail: "" };
|
|
529
|
+
}
|
|
530
|
+
const held = token ?? "";
|
|
531
|
+
const sent = await call(fetchImpl, apiBase, held, "sendMessage", { chat_id: chatId, text: "approval.md: setup test message. Nothing is pending." }, PROBE_TIMEOUT_MS);
|
|
532
|
+
if ("failed" in sent || !sent.ok || sent.envelope["ok"] !== true) {
|
|
533
|
+
const why = "failed" in sent
|
|
534
|
+
? sent.failed
|
|
535
|
+
: redact(String(sent.envelope["description"] ?? "no description"), held);
|
|
536
|
+
return {
|
|
537
|
+
ok: true,
|
|
538
|
+
declined: true,
|
|
539
|
+
detail: ` the test message did not send (${why}); the chat id is recorded either way\n`,
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
return { ok: true, detail: ` sent — check ${chosen?.name ?? chatId}\n` };
|
|
543
|
+
}
|
|
544
|
+
return {
|
|
545
|
+
collect: async (spec) => spec.name === tokenEnv ? await collectToken() : { kind: "skip" },
|
|
546
|
+
discover: async (spec) => spec.name === telegramChatEnvFor(context.load)
|
|
547
|
+
? await discoverChat()
|
|
548
|
+
: { kind: "skip" },
|
|
549
|
+
verify: async (values) => verifyChat(values),
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
/** Every channel this verb can configure. Keyed by the `channel <name>` name. */
|
|
553
|
+
export const CHANNEL_SETUPS = {
|
|
554
|
+
telegram: {
|
|
555
|
+
specs: (load) => telegramCredentialSpecs(load),
|
|
556
|
+
summary: "the bot token and the approver chat, recorded where each of them lives",
|
|
557
|
+
prereq: `IF \`approval channel telegram listen\` IS RUNNING, STOP IT FIRST. Two processes\nlong-polling one bot is a 409 from the Bot API, and the loser is whichever asked\nsecond. This verb is a configuration verb; it is not meant to run beside the\nlistener.\n\nThe token goes into the OS KEYSTORE and the chat id into .approval/env as a\nliteral: a channel holds no state, and what this file records is where the\ntransport credential lives. Nothing here appends to the log or\nattests anything.`,
|
|
558
|
+
hint: TELEGRAM_HINT,
|
|
559
|
+
hooks: telegramHooks,
|
|
560
|
+
nextSteps: [
|
|
561
|
+
`No update was acknowledged by this verb: every getUpdates above carried no`,
|
|
562
|
+
`offset, so a running listener's pending callbacks are exactly where they were.`,
|
|
563
|
+
``,
|
|
564
|
+
`Establish the variables and check the channel:`,
|
|
565
|
+
``,
|
|
566
|
+
` eval "$(approval env)"`,
|
|
567
|
+
` approval channel telegram health`,
|
|
568
|
+
],
|
|
569
|
+
help: SETUP_CHANNEL_TELEGRAM_HELP,
|
|
570
|
+
},
|
|
571
|
+
};
|
|
572
|
+
/** The known names, sorted, for a usage error and for the help text. */
|
|
573
|
+
export function knownChannelNames() {
|
|
574
|
+
return Object.keys(CHANNEL_SETUPS).sort();
|
|
575
|
+
}
|
|
576
|
+
// ---------------------------------------------------------------------------
|
|
577
|
+
// The verb
|
|
578
|
+
// ---------------------------------------------------------------------------
|
|
579
|
+
/**
|
|
580
|
+
* `approval setup channel <name>` — the interactive writer for one channel's
|
|
581
|
+
* transport credentials. HUMAN-ONLY.
|
|
582
|
+
*
|
|
583
|
+
* The human-only gate is NEW in APRV-79. The Telegram help text had said
|
|
584
|
+
* HUMAN-ONLY since APRV-74 and nothing enforced it: the verb stores a bot
|
|
585
|
+
* credential and writes `.approval/env`, which is exactly what `vault` and
|
|
586
|
+
* `sampling` are gated for, and a help text that claims a control the code does
|
|
587
|
+
* not apply is worse than no claim at all.
|
|
588
|
+
*
|
|
589
|
+
* `argv` starts at the channel's name: `commandSetup` has already eaten
|
|
590
|
+
* `channel`.
|
|
591
|
+
*/
|
|
592
|
+
export async function commandSetupChannel(argv, streams, cwd, deps = {}) {
|
|
593
|
+
const json = argv.includes("--json");
|
|
594
|
+
const name = argv[0];
|
|
595
|
+
// The name is resolved BEFORE the terminal check, so that a typo is answered
|
|
596
|
+
// with "here are the channels" rather than with a lecture about pipes.
|
|
597
|
+
if (name === "--help" || name === "-h" || name === "help") {
|
|
598
|
+
streams.out(`${SETUP_CHANNEL_HELP}\n`);
|
|
599
|
+
return EXIT_OK;
|
|
600
|
+
}
|
|
601
|
+
if (name === undefined || name.startsWith("-")) {
|
|
602
|
+
return usageError(streams, json, `missing <name> for \`approval setup channel\`; known channels: ${knownChannelNames().join(", ")}`, SETUP_CHANNEL_HELP);
|
|
603
|
+
}
|
|
604
|
+
const entry = CHANNEL_SETUPS[name];
|
|
605
|
+
if (entry === undefined) {
|
|
606
|
+
return usageError(streams, json, `unknown channel ${JSON.stringify(name)}; known channels: ${knownChannelNames().join(", ")}`, SETUP_CHANNEL_HELP);
|
|
607
|
+
}
|
|
608
|
+
const helpText = entry.help;
|
|
609
|
+
const outcome = front(`channel ${name}`, argv.slice(1), streams, cwd, deps, helpText, (context) => entry.hint(context));
|
|
610
|
+
if (outcome.kind === "handled")
|
|
611
|
+
return outcome.code;
|
|
612
|
+
const context = outcome;
|
|
613
|
+
const extra = context.positionals[0];
|
|
614
|
+
if (extra !== undefined) {
|
|
615
|
+
return usageError(streams, false, `unexpected argument ${JSON.stringify(extra)}`, helpText);
|
|
616
|
+
}
|
|
617
|
+
const human = requireHuman(context.flags, streams, helpText, `channel ${name}`);
|
|
618
|
+
if (!human.ok)
|
|
619
|
+
return human.code;
|
|
620
|
+
const result = await runCredentialFlow({
|
|
621
|
+
streams,
|
|
622
|
+
prompter: context.prompter,
|
|
623
|
+
specs: entry.specs(context.load),
|
|
624
|
+
destination: envFileDestination(context.envPath),
|
|
625
|
+
labels: {
|
|
626
|
+
title: `approval setup channel ${name} — ${entry.summary}.`,
|
|
627
|
+
prereq: entry.prereq,
|
|
628
|
+
nextSteps: entry.nextSteps,
|
|
629
|
+
},
|
|
630
|
+
hooks: entry.hooks(context, deps, streams, helpText),
|
|
631
|
+
});
|
|
632
|
+
// The flow decided the code; this verb adds nothing to it.
|
|
633
|
+
return result.code;
|
|
634
|
+
}
|
|
635
|
+
//# sourceMappingURL=setup-channel.js.map
|