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,714 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The dark-session detector (APRV-192): the log is asked what git already knows.
|
|
3
|
+
*
|
|
4
|
+
* ## The hole this closes
|
|
5
|
+
*
|
|
6
|
+
* APRV-151 built the CI-side grant cross-check, and its root cause is the thing
|
|
7
|
+
* that check cannot reach. A session whose harness never invoked the hook writes
|
|
8
|
+
* NO records at all: not a grant, not a refusal, not a request. Nothing inside
|
|
9
|
+
* such a session is in a position to notice, because the component that would
|
|
10
|
+
* have written the record is the component that did not run, and asking a
|
|
11
|
+
* session whether it is hooked asks the party under oversight to report its own
|
|
12
|
+
* oversight (SPEC.md §11). The CI guard sees only the diffs that reach a pull
|
|
13
|
+
* request; a session that ran dark and pushed nothing, or pushed to a branch
|
|
14
|
+
* nobody opened a pull request for, leaves the guard with nothing to read.
|
|
15
|
+
*
|
|
16
|
+
* So the direction is reversed. APRV-42's git evidence asks "does every log
|
|
17
|
+
* entry match git?"; this asks **"does every piece of git activity match a log
|
|
18
|
+
* entry?"** Git is the artifact a session cannot help producing: a commit has an
|
|
19
|
+
* author, a date and a tree, a linked worktree has a root and a birth, and none
|
|
20
|
+
* of those is authored by the runtime's hook. The absence of a record beside
|
|
21
|
+
* them is the signal.
|
|
22
|
+
*
|
|
23
|
+
* ## Two arms, because there are two questions
|
|
24
|
+
*
|
|
25
|
+
* **Arm A, evidence.** For each commit in the window, every guarded path it
|
|
26
|
+
* changed must carry evidence in the verified log that a human decided it. This
|
|
27
|
+
* is not reimplemented: it is {@link evaluateProtectedPaths}, APRV-151's own
|
|
28
|
+
* evaluator, called per commit, so the verdict vocabulary (`attested`,
|
|
29
|
+
* `granted-file`, `granted-command`), the recency bound, the exempt evidence
|
|
30
|
+
* surface and the `no-evidence` code are one implementation with two callers.
|
|
31
|
+
* Arm A runs over EVERY checkout, the primary included, because a guarded-path
|
|
32
|
+
* commit with no evidence is dark wherever it was made.
|
|
33
|
+
*
|
|
34
|
+
* **Arm B, silence.** A linked worktree that produced commits in the window (or
|
|
35
|
+
* that came into existence in it) and to which NOT ONE record in the window can
|
|
36
|
+
* be attributed is a dark session, whatever it touched. This is the arm that
|
|
37
|
+
* catches the APRV-151 root cause on paths the CI guard never sees, and it is
|
|
38
|
+
* the arm that needs an attribution rule.
|
|
39
|
+
*
|
|
40
|
+
* ## How a record is attributed to a worktree
|
|
41
|
+
*
|
|
42
|
+
* Hook task ids are `hook:<session>:<tool-use>` and carry no checkout, so the
|
|
43
|
+
* id is not the key. The bound material is. Every gated file-tool call binds
|
|
44
|
+
* `{tool, rule, file, …}` with `file` ABSOLUTE (`cli/hook.ts`), and every gated
|
|
45
|
+
* shell call binds `{command, cwd}` with `cwd` absolute. A record whose bound
|
|
46
|
+
* material names a path inside a worktree root was written by a session working
|
|
47
|
+
* in that worktree. That is PR #169's checkout-root anchoring, which APRV-151's
|
|
48
|
+
* notes recorded as the stronger answer to the two-checkouts problem and did not
|
|
49
|
+
* have a caller for; it has one now.
|
|
50
|
+
*
|
|
51
|
+
* The branch name is the second key, and a weak one: a branch `aprv-192-…`
|
|
52
|
+
* names task `APRV-192`, and a `task.registered` for that id in the window is
|
|
53
|
+
* attribution. Weak because a branch name is chosen by the party under
|
|
54
|
+
* oversight, so it may only ever ADD attribution (SPEC.md §11.1 invariant 4:
|
|
55
|
+
* a self-reported field may raise scrutiny and never lower it) — which is
|
|
56
|
+
* exactly what it does here, since attribution is what stops an alarm. Stated
|
|
57
|
+
* plainly rather than hidden: a session that names its branch after a task some
|
|
58
|
+
* other session registered escapes arm B. It does not escape arm A, and the
|
|
59
|
+
* cost of the alternative (no branch key at all) is an alarm on every worktree
|
|
60
|
+
* whose session worked entirely outside the payload store's reach.
|
|
61
|
+
*
|
|
62
|
+
* ## Fail closed in the report
|
|
63
|
+
*
|
|
64
|
+
* Uncertainty is never "fine". A log that does not verify, a payload whose
|
|
65
|
+
* bytes will not resolve, a commit git could not date, a worktree whose root
|
|
66
|
+
* cannot be placed: each is its own `undetermined` verdict with its own code,
|
|
67
|
+
* counted separately from the clean ones, and the renderer never folds an
|
|
68
|
+
* undetermined subject into the pass count. What this module does NOT do is
|
|
69
|
+
* escalate on uncertainty, because a detector is not an enforcement path: it
|
|
70
|
+
* reports, a human reads, and nothing here changes what the gate authorizes.
|
|
71
|
+
*
|
|
72
|
+
* ## What keeps the human's own commits quiet (AC4)
|
|
73
|
+
*
|
|
74
|
+
* Three exemptions, narrow and documented, and each of them fails NOISY when it
|
|
75
|
+
* does not apply:
|
|
76
|
+
*
|
|
77
|
+
* 1. A commit that touches only the daemon's own append surface
|
|
78
|
+
* ({@link EXEMPT_PREFIXES}) is a records advance carrying evidence, not a
|
|
79
|
+
* session doing work. Requiring evidence for the evidence is circular.
|
|
80
|
+
* 2. A commit authored by the git-evidence identity is the daemon's own
|
|
81
|
+
* (`daemon/git-evidence.ts`), and the daemon is not a session.
|
|
82
|
+
* 3. Arm B's subject is LINKED WORKTREES ONLY. The agent-session artifact is
|
|
83
|
+
* the worktree; the human commits in the primary checkout, where the policy
|
|
84
|
+
* ceremony and the genesis events are made. The primary is not thereby
|
|
85
|
+
* passed: it stays wholly subject to arm A, so a ceremony commit passes on
|
|
86
|
+
* its attestation and an unevidenced guarded-path commit there is still
|
|
87
|
+
* reported. And the limit is stated on every report as its own coverage
|
|
88
|
+
* line, so an agent session that ran in the primary checkout shows up as a
|
|
89
|
+
* gap in the report rather than as silence.
|
|
90
|
+
*
|
|
91
|
+
* ## Layout
|
|
92
|
+
*
|
|
93
|
+
* The evaluator ({@link evaluateDarkSessions}) is PURE: no IO, no clock, no
|
|
94
|
+
* git, so a fixture is enough to test it and a repository is never required.
|
|
95
|
+
* The observing half below the fold does run git and read the payload store,
|
|
96
|
+
* and it lives here rather than under `daemon/` because `approval doctor`
|
|
97
|
+
* reports the same findings and `src/cli/` may not import `src/daemon/`
|
|
98
|
+
* (APRV-59). The one thing that stays in `daemon/dark-session.ts` is the
|
|
99
|
+
* append, which is the one thing a reader must never do.
|
|
100
|
+
*/
|
|
101
|
+
import { spawnSync } from "node:child_process";
|
|
102
|
+
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
103
|
+
import { basename, join } from "node:path";
|
|
104
|
+
import { policyBytesHash } from "./attest.js";
|
|
105
|
+
import { childEnvironment } from "./child-env.js";
|
|
106
|
+
import { tick as readClock } from "./clock.js";
|
|
107
|
+
import { loadPayload, payloadStoreDirFor } from "./payload-store.js";
|
|
108
|
+
import { loadPolicy } from "./policy-load.js";
|
|
109
|
+
import { EXEMPT_PREFIXES, evaluateProtectedPaths, isExemptPath, isGuardedPath, } from "./protected-path-guard.js";
|
|
110
|
+
/**
|
|
111
|
+
* The events a hooked session cannot avoid writing.
|
|
112
|
+
*
|
|
113
|
+
* A registration opens the question, a request puts it to a human, and a start
|
|
114
|
+
* records that something ran. A session that made any gated tool call at all
|
|
115
|
+
* wrote at least one of these; a session that wrote none of them made no gated
|
|
116
|
+
* call the runtime ever saw, which is the definition of dark.
|
|
117
|
+
*/
|
|
118
|
+
export const SESSION_EVENTS = [
|
|
119
|
+
"task.registered",
|
|
120
|
+
"approval.requested",
|
|
121
|
+
"execution.started",
|
|
122
|
+
// APRV-214. A tool call the open window let through is recorded rather than
|
|
123
|
+
// authorized, and it is recorded loudly: a session running behind a window is
|
|
124
|
+
// the opposite of dark, and counting it as dark would report the one state a
|
|
125
|
+
// human deliberately created as the state this sweep exists to find.
|
|
126
|
+
"gate.bypassed",
|
|
127
|
+
];
|
|
128
|
+
/**
|
|
129
|
+
* How a subject came out. **Frozen union**, additive-only, in the same sense as
|
|
130
|
+
* every other verdict vocabulary here (SPEC.md §11.1 invariant 6).
|
|
131
|
+
*/
|
|
132
|
+
export const DARK_SESSION_VERDICTS = ["hooked", "dark", "exempt", "undetermined"];
|
|
133
|
+
/**
|
|
134
|
+
* Why a subject got the verdict it got. **Frozen union**, additive-only.
|
|
135
|
+
*
|
|
136
|
+
* `no-evidence` is spelled exactly as APRV-151 spells it, because it IS
|
|
137
|
+
* APRV-151's finding, arrived at through the same evaluator.
|
|
138
|
+
*/
|
|
139
|
+
export const DARK_SESSION_CODES = [
|
|
140
|
+
/** Arm B: git activity in the window, and not one attributable record. */
|
|
141
|
+
"no-records",
|
|
142
|
+
/** Arm A: a guarded path changed and the log carries no evidence for it. */
|
|
143
|
+
"no-evidence",
|
|
144
|
+
/** Exempt: the commits touch only the daemon's own append surface. */
|
|
145
|
+
"evidence-surface",
|
|
146
|
+
/** Exempt: authored by the daemon's own git identity. */
|
|
147
|
+
"daemon-authored",
|
|
148
|
+
/** Exempt from arm B (never from arm A): the primary checkout. */
|
|
149
|
+
"primary-checkout",
|
|
150
|
+
/** Undetermined: the log did not verify, so nothing may be read from it. */
|
|
151
|
+
"log-unverified",
|
|
152
|
+
/** Undetermined: git could not be run, or answered nothing. */
|
|
153
|
+
"git-unavailable",
|
|
154
|
+
/** Undetermined: bound material would not resolve, so records cannot be placed. */
|
|
155
|
+
"payload-unresolvable",
|
|
156
|
+
/** Undetermined: git named no usable date for the activity. */
|
|
157
|
+
"activity-undated",
|
|
158
|
+
];
|
|
159
|
+
/** The observation key: one subject, one state of the world. */
|
|
160
|
+
export function observationKey(subject, newestSha, born) {
|
|
161
|
+
return `${subject}@${newestSha ?? "no-commit"}#${born ?? "unknown-birth"}`;
|
|
162
|
+
}
|
|
163
|
+
/** A record's payload as a map. The log's payload shape is open at v0.1. */
|
|
164
|
+
function payloadOf(record) {
|
|
165
|
+
const value = record.payload;
|
|
166
|
+
return typeof value === "object" && value !== null && !Array.isArray(value)
|
|
167
|
+
? value
|
|
168
|
+
: {};
|
|
169
|
+
}
|
|
170
|
+
/** Every `payload_hash` a record names, on the record or on its envelope actions. */
|
|
171
|
+
function hashesOf(record) {
|
|
172
|
+
const payload = payloadOf(record);
|
|
173
|
+
const found = [];
|
|
174
|
+
const direct = payload["payload_hash"];
|
|
175
|
+
if (typeof direct === "string")
|
|
176
|
+
found.push(direct);
|
|
177
|
+
const envelope = payload["envelope"];
|
|
178
|
+
const actions = typeof envelope === "object" && envelope !== null && !Array.isArray(envelope)
|
|
179
|
+
? envelope["actions"]
|
|
180
|
+
: payload["actions"];
|
|
181
|
+
if (Array.isArray(actions)) {
|
|
182
|
+
for (const action of actions) {
|
|
183
|
+
if (typeof action !== "object" || action === null)
|
|
184
|
+
continue;
|
|
185
|
+
const hash = action["payload_hash"];
|
|
186
|
+
if (typeof hash === "string")
|
|
187
|
+
found.push(hash);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return found;
|
|
191
|
+
}
|
|
192
|
+
/** The absolute paths one piece of bound material names. */
|
|
193
|
+
function pathsIn(material) {
|
|
194
|
+
if (typeof material !== "object" || material === null || Array.isArray(material))
|
|
195
|
+
return [];
|
|
196
|
+
const map = material;
|
|
197
|
+
const found = [];
|
|
198
|
+
for (const key of ["file", "cwd", "path"]) {
|
|
199
|
+
const value = map[key];
|
|
200
|
+
if (typeof value === "string" && value.length > 0)
|
|
201
|
+
found.push(value);
|
|
202
|
+
}
|
|
203
|
+
const argv = map["argv"];
|
|
204
|
+
if (Array.isArray(argv)) {
|
|
205
|
+
for (const word of argv)
|
|
206
|
+
if (typeof word === "string" && word.startsWith("/"))
|
|
207
|
+
found.push(word);
|
|
208
|
+
}
|
|
209
|
+
return found;
|
|
210
|
+
}
|
|
211
|
+
/** Is `path` inside `root`, or `root` itself? Both are absolute and resolved. */
|
|
212
|
+
function inside(path, root) {
|
|
213
|
+
if (path === root)
|
|
214
|
+
return true;
|
|
215
|
+
return path.startsWith(root.endsWith("/") ? root : `${root}/`);
|
|
216
|
+
}
|
|
217
|
+
/** The Backlog.md task id a branch name begins with, upper-cased (`aprv-192-x`). */
|
|
218
|
+
export function taskIdFromBranch(branch) {
|
|
219
|
+
if (branch === null)
|
|
220
|
+
return null;
|
|
221
|
+
const match = /^([A-Za-z][A-Za-z0-9_]*-\d+)/u.exec(branch);
|
|
222
|
+
return match?.[1]?.toUpperCase() ?? null;
|
|
223
|
+
}
|
|
224
|
+
/** Records inside the window, in log order. */
|
|
225
|
+
function inWindow(records, window) {
|
|
226
|
+
const from = Date.parse(window.from);
|
|
227
|
+
const to = Date.parse(window.to);
|
|
228
|
+
if (Number.isNaN(from) || Number.isNaN(to))
|
|
229
|
+
return [...records];
|
|
230
|
+
return records.filter((record) => {
|
|
231
|
+
const at = Date.parse(record.ts);
|
|
232
|
+
return !Number.isNaN(at) && at >= from && at <= to;
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
function attribute(checkout, windowed, payloadFor) {
|
|
236
|
+
const seqs = [];
|
|
237
|
+
let unresolved = false;
|
|
238
|
+
const branchTask = taskIdFromBranch(checkout.branch);
|
|
239
|
+
for (const record of windowed) {
|
|
240
|
+
if (!SESSION_EVENTS.includes(record.event))
|
|
241
|
+
continue;
|
|
242
|
+
// The strong key: bound material naming a path inside this checkout.
|
|
243
|
+
let placed = false;
|
|
244
|
+
for (const hash of hashesOf(record)) {
|
|
245
|
+
const material = payloadFor(hash);
|
|
246
|
+
if (material === null) {
|
|
247
|
+
unresolved = true;
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
if (pathsIn(material).some((path) => inside(path, checkout.root))) {
|
|
251
|
+
placed = true;
|
|
252
|
+
break;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
// The weak key: this checkout's branch names a task this record is under.
|
|
256
|
+
// It may only ADD attribution, never remove it (invariant 4).
|
|
257
|
+
if (!placed && branchTask !== null && typeof record.task === "string") {
|
|
258
|
+
placed = record.task.toUpperCase().startsWith(branchTask);
|
|
259
|
+
}
|
|
260
|
+
if (placed)
|
|
261
|
+
seqs.push(record.seq);
|
|
262
|
+
}
|
|
263
|
+
return { seqs, unresolved };
|
|
264
|
+
}
|
|
265
|
+
/** Did this commit touch anything but the daemon's own append surface? */
|
|
266
|
+
function touchesOnlyEvidence(commit) {
|
|
267
|
+
return commit.changedPaths.length > 0 && commit.changedPaths.every((path) => isExemptPath(path));
|
|
268
|
+
}
|
|
269
|
+
/** The limit arm B carries, repeated wherever the primary checkout is reported. */
|
|
270
|
+
const coverageNote = "A session that ran in the primary checkout is not covered by arm B; that is a stated limit of this detector, not a clean bill.";
|
|
271
|
+
/**
|
|
272
|
+
* Evaluate what git showed against what the log carries. Pure: no IO, no clock.
|
|
273
|
+
*/
|
|
274
|
+
export function evaluateDarkSessions(input) {
|
|
275
|
+
const coverage = "arm B (a checkout with git activity and no records) covers LINKED WORKTREES only: the " +
|
|
276
|
+
"agent-session artifact is the worktree, and the primary checkout is where the human's " +
|
|
277
|
+
"policy ceremony and genesis commits are made. A session that ran IN the primary checkout " +
|
|
278
|
+
"is outside arm B and is covered only by arm A (guarded paths must carry evidence).";
|
|
279
|
+
const findings = [];
|
|
280
|
+
for (const checkout of input.activity.checkouts) {
|
|
281
|
+
findings.push(judge(checkout, input));
|
|
282
|
+
}
|
|
283
|
+
return {
|
|
284
|
+
ok: findings.every((finding) => finding.verdict !== "dark"),
|
|
285
|
+
settled: findings.every((finding) => finding.verdict !== "undetermined"),
|
|
286
|
+
findings,
|
|
287
|
+
window: input.window,
|
|
288
|
+
coverage,
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
function base(checkout, verdict, code, guardedPaths, attributed, detail) {
|
|
292
|
+
const newestSha = checkout.commits.length === 0 ? null : checkout.commits[0].sha;
|
|
293
|
+
return {
|
|
294
|
+
subject: checkout.name,
|
|
295
|
+
root: checkout.root,
|
|
296
|
+
primary: checkout.primary,
|
|
297
|
+
branch: checkout.branch,
|
|
298
|
+
verdict,
|
|
299
|
+
code,
|
|
300
|
+
commits: checkout.commits.length,
|
|
301
|
+
guardedPaths,
|
|
302
|
+
attributed,
|
|
303
|
+
newestSha,
|
|
304
|
+
key: observationKey(checkout.name, newestSha, checkout.born),
|
|
305
|
+
detail,
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
function judge(checkout, input) {
|
|
309
|
+
const where = `${checkout.name} (${checkout.root}${checkout.branch === null ? ", detached HEAD" : `, branch ${checkout.branch}`})`;
|
|
310
|
+
if (input.activity.unavailable !== null) {
|
|
311
|
+
return base(checkout, "undetermined", "git-unavailable", [], [], `${where}: git could not be asked what happened here (${input.activity.unavailable}), so this sweep establishes nothing about it. Reported as uncertainty rather than as a pass.`);
|
|
312
|
+
}
|
|
313
|
+
if (input.records === null) {
|
|
314
|
+
return base(checkout, "undetermined", "log-unverified", [], [], `${where}: the log does not verify (${input.logDetail ?? "no detail"}), and nothing in this runtime reads evidence out of records that have not passed chain verification. Reported as uncertainty rather than as a pass.`);
|
|
315
|
+
}
|
|
316
|
+
const records = input.records;
|
|
317
|
+
// The daemon's own commits, and a records advance carrying evidence, are not
|
|
318
|
+
// sessions. Both exemptions are computed BEFORE any alarm, and both are
|
|
319
|
+
// narrow: a commit doing anything else on the same subject removes them.
|
|
320
|
+
const substantive = checkout.commits.filter((commit) => !input.daemonEmails.includes(commit.authorEmail.toLowerCase()) && !touchesOnlyEvidence(commit));
|
|
321
|
+
if (checkout.commits.length > 0 && substantive.length === 0) {
|
|
322
|
+
const daemonAuthored = checkout.commits.every((commit) => input.daemonEmails.includes(commit.authorEmail.toLowerCase()));
|
|
323
|
+
return base(checkout, "exempt", daemonAuthored ? "daemon-authored" : "evidence-surface", [], [], daemonAuthored
|
|
324
|
+
? `${where}: every one of the ${String(checkout.commits.length)} commit(s) here was authored by the daemon's own git identity, which is the runtime witnessing the log rather than a session doing work`
|
|
325
|
+
: `${where}: every one of the ${String(checkout.commits.length)} commit(s) here touches only the daemon's append surface (${EXEMPT_PREFIXES.join(", ")}) — a records advance carrying evidence, and requiring evidence for the evidence is circular`);
|
|
326
|
+
}
|
|
327
|
+
// ---------------------------------------------------------------------
|
|
328
|
+
// Arm A: guarded paths need evidence, in every checkout.
|
|
329
|
+
// ---------------------------------------------------------------------
|
|
330
|
+
const guarded = new Set();
|
|
331
|
+
for (const commit of substantive) {
|
|
332
|
+
for (const path of commit.changedPaths) {
|
|
333
|
+
if (isGuardedPath(path, input.policyProtectedPaths))
|
|
334
|
+
guarded.add(path);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
const guardedPaths = [...guarded].sort();
|
|
338
|
+
const failures = [];
|
|
339
|
+
if (guardedPaths.length > 0) {
|
|
340
|
+
const changeTs = new Map();
|
|
341
|
+
for (const commit of substantive) {
|
|
342
|
+
for (const path of commit.changedPaths) {
|
|
343
|
+
if (!guarded.has(path))
|
|
344
|
+
continue;
|
|
345
|
+
const seen = changeTs.get(path);
|
|
346
|
+
if (seen === undefined || (commit.ts !== null && seen !== null && commit.ts > seen)) {
|
|
347
|
+
changeTs.set(path, commit.ts);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
const logWindow = {
|
|
352
|
+
firstSeq: records.length === 0 ? null : records[0].seq,
|
|
353
|
+
lastSeq: records.length === 0 ? null : records[records.length - 1].seq,
|
|
354
|
+
firstTs: records.length === 0 ? null : records[0].ts,
|
|
355
|
+
lastTs: records.length === 0 ? null : records[records.length - 1].ts,
|
|
356
|
+
base: input.window.from,
|
|
357
|
+
head: `${checkout.name}@${substantive[0].sha.slice(0, 12)}`,
|
|
358
|
+
};
|
|
359
|
+
// APRV-202 made the guard ask whether THIS change was granted, so it needs
|
|
360
|
+
// the path's bytes on both sides of the change. Here the change is the span
|
|
361
|
+
// of substantive commits that touched the path: base is the parent of the
|
|
362
|
+
// oldest of them, head is the newest. A blob git cannot show, or one that
|
|
363
|
+
// is binary, answers null and the guard fails the path as
|
|
364
|
+
// change-unreadable rather than falling back to the path-level rule.
|
|
365
|
+
const blobCache = new Map();
|
|
366
|
+
const blobsFor = (path) => {
|
|
367
|
+
const cached = blobCache.get(path);
|
|
368
|
+
if (cached !== undefined)
|
|
369
|
+
return cached;
|
|
370
|
+
const touching = substantive.filter((commit) => commit.changedPaths.includes(path));
|
|
371
|
+
let value = null;
|
|
372
|
+
if (touching.length > 0) {
|
|
373
|
+
const byTime = [...touching].sort((a, b) => (a.ts ?? "").localeCompare(b.ts ?? ""));
|
|
374
|
+
const baseRev = `${byTime[0].sha}^`;
|
|
375
|
+
const headRev = byTime[byTime.length - 1].sha;
|
|
376
|
+
const inTree = (rev) => {
|
|
377
|
+
const listed = git(["ls-tree", "--name-only", rev, "--", path], checkout.root);
|
|
378
|
+
return listed.ok && listed.stdout.trim().length > 0;
|
|
379
|
+
};
|
|
380
|
+
const show = (rev) => {
|
|
381
|
+
const shown = git(["show", `${rev}:${path}`], checkout.root);
|
|
382
|
+
return shown.ok ? shown.stdout : null;
|
|
383
|
+
};
|
|
384
|
+
const baseHas = inTree(baseRev);
|
|
385
|
+
const headHas = inTree(headRev);
|
|
386
|
+
const baseText = baseHas ? show(baseRev) : null;
|
|
387
|
+
const headText = headHas ? show(headRev) : null;
|
|
388
|
+
const unreadable = (baseHas && baseText === null) ||
|
|
389
|
+
(headHas && headText === null) ||
|
|
390
|
+
(baseText !== null && baseText.includes("\u0000")) ||
|
|
391
|
+
(headText !== null && headText.includes("\u0000"));
|
|
392
|
+
if (!unreadable)
|
|
393
|
+
value = { base: baseText, head: headText };
|
|
394
|
+
}
|
|
395
|
+
blobCache.set(path, value);
|
|
396
|
+
return value;
|
|
397
|
+
};
|
|
398
|
+
const report = evaluateProtectedPaths({
|
|
399
|
+
changedPaths: guardedPaths,
|
|
400
|
+
blobsFor,
|
|
401
|
+
records,
|
|
402
|
+
logStatus: "ok",
|
|
403
|
+
policyProtectedPaths: input.policyProtectedPaths,
|
|
404
|
+
policySha256AtHead: input.policySha256,
|
|
405
|
+
policyPath: input.policyPath,
|
|
406
|
+
payloadFor: input.payloadFor,
|
|
407
|
+
changeTsFor: (path) => changeTs.get(path) ?? null,
|
|
408
|
+
...(input.lookbackMs === undefined ? {} : { lookbackMs: input.lookbackMs }),
|
|
409
|
+
window: logWindow,
|
|
410
|
+
});
|
|
411
|
+
for (const finding of report.findings)
|
|
412
|
+
if (!finding.ok)
|
|
413
|
+
failures.push(finding);
|
|
414
|
+
}
|
|
415
|
+
const attribution = attribute(checkout, inWindow(records, input.window), input.payloadFor);
|
|
416
|
+
if (failures.length > 0) {
|
|
417
|
+
return base(checkout, "dark", "no-evidence", guardedPaths, attribution.seqs, `${where}: ${String(failures.length)} guarded path(s) changed here in ${input.window.from}..${input.window.to} with no evidence in the log that a human decided them — ${failures
|
|
418
|
+
.map((finding) => `${finding.path}: ${finding.detail}`)
|
|
419
|
+
.join(" | ")}`);
|
|
420
|
+
}
|
|
421
|
+
// ---------------------------------------------------------------------
|
|
422
|
+
// Arm B: silence, in a linked worktree.
|
|
423
|
+
// ---------------------------------------------------------------------
|
|
424
|
+
if (checkout.primary) {
|
|
425
|
+
return base(checkout, "exempt", "primary-checkout", guardedPaths, attribution.seqs, `${where}: the primary checkout is outside arm B by design — it is where the human's policy ceremony and genesis commits are made, and the agent-session artifact this detector keys on is the linked worktree. Arm A did run here and found evidence for every one of the ${String(guardedPaths.length)} guarded path(s) it changed. ${coverageNote}`);
|
|
426
|
+
}
|
|
427
|
+
const produced = substantive.length > 0 || checkout.born !== null;
|
|
428
|
+
if (!produced) {
|
|
429
|
+
return base(checkout, "hooked", null, guardedPaths, attribution.seqs, `${where}: no commits in ${input.window.from}..${input.window.to} and no birth inside it, so there is no git activity for the log to owe a record against`);
|
|
430
|
+
}
|
|
431
|
+
if (attribution.seqs.length > 0) {
|
|
432
|
+
return base(checkout, "hooked", null, guardedPaths, attribution.seqs, `${where}: ${String(substantive.length)} commit(s) in the window, and ${String(attribution.seqs.length)} record(s) in the log are attributable to this checkout (seq ${attribution.seqs
|
|
433
|
+
.slice(0, 5)
|
|
434
|
+
.join(", ")}${attribution.seqs.length > 5 ? ", …" : ""}), so a hook ran here`);
|
|
435
|
+
}
|
|
436
|
+
if (attribution.unresolved) {
|
|
437
|
+
return base(checkout, "undetermined", "payload-unresolvable", guardedPaths, [], `${where}: ${String(substantive.length)} commit(s) in the window and no attributable record, but at least one record's bound material would not resolve from the payload store, so a record written from HERE may exist and be unplaceable. Reported as uncertainty rather than as an alarm or as a pass; the repair is the payload store, and \`approval doctor\` reports its health.`);
|
|
438
|
+
}
|
|
439
|
+
if (substantive.length > 0 && substantive.every((commit) => commit.ts === null)) {
|
|
440
|
+
return base(checkout, "undetermined", "activity-undated", guardedPaths, [], `${where}: git reported ${String(substantive.length)} commit(s) here and dated none of them, so whether this activity falls in ${input.window.from}..${input.window.to} cannot be established. Reported as uncertainty rather than as a pass.`);
|
|
441
|
+
}
|
|
442
|
+
return base(checkout, "dark", "no-records", guardedPaths, [], `${where}: DARK SESSION. ${substantive.length === 0
|
|
443
|
+
? `this worktree came into existence at ${checkout.born ?? "an unknown instant"} inside the window`
|
|
444
|
+
: `${String(substantive.length)} commit(s) were made here in the window (newest ${substantive[0].sha.slice(0, 12)} by ${substantive[0].author})`}, and NOT ONE ${SESSION_EVENTS.join("/")} record in ${input.window.from}..${input.window.to} can be attributed to it. A session whose harness hook never fired writes exactly this: no grant, no refusal, no request. What this establishes is the absence, not what the session did; the repair is \`approval doctor\`'s harness-hook-wiring row in that checkout and \`approval instructions hook\`.`);
|
|
445
|
+
}
|
|
446
|
+
/** The report as lines an operator reads. Pure. */
|
|
447
|
+
export function renderDarkSessionReport(report) {
|
|
448
|
+
const lines = [`dark-session sweep: ${report.window.from} .. ${report.window.to}`];
|
|
449
|
+
if (report.findings.length === 0) {
|
|
450
|
+
lines.push(" no checkouts observed");
|
|
451
|
+
}
|
|
452
|
+
for (const finding of report.findings) {
|
|
453
|
+
const label = finding.verdict === "dark"
|
|
454
|
+
? ` DARK ${finding.subject} [${finding.code ?? "?"}]`
|
|
455
|
+
: finding.verdict === "undetermined"
|
|
456
|
+
? ` UNDETERMINED ${finding.subject} [${finding.code ?? "?"}]`
|
|
457
|
+
: ` ${finding.verdict === "exempt" ? "EXEMPT" : "OK"} ${finding.subject}${finding.code === null ? "" : ` [${finding.code}]`}`;
|
|
458
|
+
lines.push(label);
|
|
459
|
+
lines.push(` ${finding.detail}`);
|
|
460
|
+
}
|
|
461
|
+
lines.push(` ${report.ok ? "no dark session" : "DARK SESSION(S) FOUND"}; ${report.settled ? "every subject established" : "SOME SUBJECTS NOT ESTABLISHED"}`);
|
|
462
|
+
lines.push(` coverage: ${report.coverage}`);
|
|
463
|
+
return `${lines.join("\n")}\n`;
|
|
464
|
+
}
|
|
465
|
+
// ===========================================================================
|
|
466
|
+
// Observing: git, the payload store, and the clock
|
|
467
|
+
// ===========================================================================
|
|
468
|
+
/**
|
|
469
|
+
* Everything below this line does IO, and it lives here rather than in
|
|
470
|
+
* `daemon/` for one reason: `approval doctor` reports the same findings and
|
|
471
|
+
* `src/cli/` may not import `src/daemon/` (APRV-59, `tests/layering.test.ts`).
|
|
472
|
+
* Two observers would be two answers to one question, so there is one, and the
|
|
473
|
+
* daemon's module keeps only the thing a reader must never do — the append.
|
|
474
|
+
*/
|
|
475
|
+
/** How far back a sweep looks, absent an operator saying otherwise. */
|
|
476
|
+
export const DEFAULT_DARK_WINDOW_MS = 24 * 60 * 60 * 1000;
|
|
477
|
+
/** How often the sweep runs, absent an operator saying otherwise. */
|
|
478
|
+
export const DEFAULT_DARK_INTERVAL_MS = 15 * 60 * 1000;
|
|
479
|
+
/**
|
|
480
|
+
* The git identities whose commits are the runtime's own.
|
|
481
|
+
*
|
|
482
|
+
* Spelled here rather than imported from `daemon/git-evidence.ts`, which is
|
|
483
|
+
* where the daemon's commit identity is defined: core must not depend on the
|
|
484
|
+
* daemon. `tests/dark-session.test.ts` pins the two against each other so they
|
|
485
|
+
* cannot drift apart, the same device `APPROVALD_VERSION` uses for its own
|
|
486
|
+
* duplicate of the package version.
|
|
487
|
+
*/
|
|
488
|
+
export const DAEMON_EVIDENCE_EMAILS = ["approvald@noreply.approval.md"];
|
|
489
|
+
/**
|
|
490
|
+
* Record and field separators inside `git log --pretty`.
|
|
491
|
+
*
|
|
492
|
+
* ASCII 30 and 31, which cannot reach the parser inside a path: git quotes any
|
|
493
|
+
* path containing a control character, so a filename engineered to carry one of
|
|
494
|
+
* these arrives escaped and cannot forge a commit boundary. That matters more
|
|
495
|
+
* here than in an ordinary parser, because the party who chooses the filenames
|
|
496
|
+
* is the party this module reports on.
|
|
497
|
+
*/
|
|
498
|
+
const RS = "\u001e";
|
|
499
|
+
const FS = "\u001f";
|
|
500
|
+
function detailOf(cause) {
|
|
501
|
+
return cause instanceof Error ? cause.message : String(cause);
|
|
502
|
+
}
|
|
503
|
+
/**
|
|
504
|
+
* One git invocation, non-interactive, in a scrubbed environment.
|
|
505
|
+
*
|
|
506
|
+
* The same shape `daemon/git-evidence.ts` uses and for the same reasons: an
|
|
507
|
+
* inherited `GIT_DIR` would redirect the command at another repository, a
|
|
508
|
+
* terminal prompt would hang a daemon silently, and the sweep has no use for a
|
|
509
|
+
* credential (APRV-205's scrub).
|
|
510
|
+
*/
|
|
511
|
+
function git(args, cwd) {
|
|
512
|
+
const env = { ...childEnvironment().env, GIT_TERMINAL_PROMPT: "0" };
|
|
513
|
+
delete env["GIT_DIR"];
|
|
514
|
+
delete env["GIT_WORK_TREE"];
|
|
515
|
+
const result = spawnSync("git", args, { cwd, encoding: "utf8", env, maxBuffer: 32 * 1024 * 1024 });
|
|
516
|
+
if (result.error !== undefined || result.status === null) {
|
|
517
|
+
return { ok: false, stdout: "", stderr: detailOf(result.error ?? "git did not run") };
|
|
518
|
+
}
|
|
519
|
+
return { ok: result.status === 0, stdout: result.stdout ?? "", stderr: result.stderr ?? "" };
|
|
520
|
+
}
|
|
521
|
+
/**
|
|
522
|
+
* When this checkout came into being, as git and the filesystem can say.
|
|
523
|
+
*
|
|
524
|
+
* A linked worktree's `.git` is a FILE written at `git worktree add`, so its
|
|
525
|
+
* birth is the worktree's. The primary's `.git` is a directory whose birth is
|
|
526
|
+
* the clone's, which is why arm B never reads this for the primary. `null` when
|
|
527
|
+
* the platform reports no usable birth time, which is a reporting gap and never
|
|
528
|
+
* a correctness one: a worktree with no birth and no commits simply produces no
|
|
529
|
+
* activity for the log to owe a record against.
|
|
530
|
+
*/
|
|
531
|
+
function birthOf(root) {
|
|
532
|
+
try {
|
|
533
|
+
const stats = statSync(join(root, ".git"));
|
|
534
|
+
const born = stats.birthtimeMs > 0 ? stats.birthtimeMs : stats.mtimeMs;
|
|
535
|
+
if (!Number.isFinite(born) || born <= 0)
|
|
536
|
+
return null;
|
|
537
|
+
return new Date(born).toISOString();
|
|
538
|
+
}
|
|
539
|
+
catch {
|
|
540
|
+
return null;
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
/** The checkouts git knows about: the primary first, then every linked one. */
|
|
544
|
+
function worktrees(root) {
|
|
545
|
+
const listed = git(["worktree", "list", "--porcelain"], root);
|
|
546
|
+
if (!listed.ok) {
|
|
547
|
+
return {
|
|
548
|
+
ok: false,
|
|
549
|
+
message: `\`git worktree list\` failed in ${root}: ${listed.stderr.trim() || "no output"}`,
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
const found = [];
|
|
553
|
+
let path = null;
|
|
554
|
+
let branch = null;
|
|
555
|
+
const flush = () => {
|
|
556
|
+
if (path === null)
|
|
557
|
+
return;
|
|
558
|
+
found.push({
|
|
559
|
+
root: path,
|
|
560
|
+
name: found.length === 0 ? "primary" : basename(path),
|
|
561
|
+
primary: found.length === 0,
|
|
562
|
+
branch,
|
|
563
|
+
born: birthOf(path),
|
|
564
|
+
commits: [],
|
|
565
|
+
});
|
|
566
|
+
path = null;
|
|
567
|
+
branch = null;
|
|
568
|
+
};
|
|
569
|
+
for (const line of listed.stdout.split("\n")) {
|
|
570
|
+
if (line.startsWith("worktree ")) {
|
|
571
|
+
flush();
|
|
572
|
+
path = line.slice("worktree ".length).trim();
|
|
573
|
+
}
|
|
574
|
+
else if (line.startsWith("branch ")) {
|
|
575
|
+
const ref = line.slice("branch ".length).trim();
|
|
576
|
+
branch = ref.startsWith("refs/heads/") ? ref.slice("refs/heads/".length) : ref;
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
flush();
|
|
580
|
+
return { ok: true, found };
|
|
581
|
+
}
|
|
582
|
+
/** The trunk to exclude a worktree's own commits from, or `null` when there is none. */
|
|
583
|
+
function trunkOf(root) {
|
|
584
|
+
for (const candidate of ["origin/main", "origin/master", "main", "master"]) {
|
|
585
|
+
if (git(["rev-parse", "--verify", "--quiet", `${candidate}^{commit}`], root).ok)
|
|
586
|
+
return candidate;
|
|
587
|
+
}
|
|
588
|
+
return null;
|
|
589
|
+
}
|
|
590
|
+
/** Parse `git log`'s record-separated output into commits. */
|
|
591
|
+
function parseCommits(text, ref) {
|
|
592
|
+
const commits = [];
|
|
593
|
+
for (const chunk of text.split(RS)) {
|
|
594
|
+
const lines = chunk.split("\n").filter((line) => line.length > 0);
|
|
595
|
+
const header = lines.shift();
|
|
596
|
+
if (header === undefined)
|
|
597
|
+
continue;
|
|
598
|
+
const [sha, ts, author, email] = header.split(FS);
|
|
599
|
+
if (sha === undefined || sha.length === 0)
|
|
600
|
+
continue;
|
|
601
|
+
commits.push({
|
|
602
|
+
sha,
|
|
603
|
+
ts: ts === undefined || ts.length === 0 ? null : ts,
|
|
604
|
+
author: author ?? "",
|
|
605
|
+
authorEmail: (email ?? "").toLowerCase(),
|
|
606
|
+
changedPaths: lines,
|
|
607
|
+
ref,
|
|
608
|
+
});
|
|
609
|
+
}
|
|
610
|
+
return commits;
|
|
611
|
+
}
|
|
612
|
+
/**
|
|
613
|
+
* Commits on one checkout inside the window, newest first.
|
|
614
|
+
*
|
|
615
|
+
* For a linked worktree the range is `HEAD --not <trunk>`: a branch carries all
|
|
616
|
+
* of the trunk's history, and counting the trunk's commits as this worktree's
|
|
617
|
+
* would attribute every merged commit to whichever worktree happened to branch
|
|
618
|
+
* from it. For the primary the range is plain `HEAD`, because the primary's own
|
|
619
|
+
* commits are exactly what arm A is there to check.
|
|
620
|
+
*/
|
|
621
|
+
function commitsOf(checkout, trunk, from, to) {
|
|
622
|
+
const range = checkout.primary || trunk === null ? ["HEAD"] : ["HEAD", "--not", trunk];
|
|
623
|
+
const run = git([
|
|
624
|
+
"log",
|
|
625
|
+
...range,
|
|
626
|
+
`--since=${from}`,
|
|
627
|
+
`--until=${to}`,
|
|
628
|
+
`--pretty=format:${RS}%H${FS}%aI${FS}%an <%ae>${FS}%ae`,
|
|
629
|
+
"--name-only",
|
|
630
|
+
], checkout.root);
|
|
631
|
+
if (!run.ok) {
|
|
632
|
+
return {
|
|
633
|
+
ok: false,
|
|
634
|
+
message: `\`git log\` failed in ${checkout.root}: ${run.stderr.trim() || "no output"}`,
|
|
635
|
+
};
|
|
636
|
+
}
|
|
637
|
+
return { ok: true, commits: parseCommits(run.stdout, checkout.branch ?? "HEAD") };
|
|
638
|
+
}
|
|
639
|
+
/** Everything git could tell the sweep about a repository in one window. */
|
|
640
|
+
export function observeGitActivity(root, from, to) {
|
|
641
|
+
const listed = worktrees(root);
|
|
642
|
+
if (!listed.ok)
|
|
643
|
+
return { checkouts: [], unavailable: listed.message };
|
|
644
|
+
const trunk = trunkOf(root);
|
|
645
|
+
const checkouts = [];
|
|
646
|
+
for (const checkout of listed.found) {
|
|
647
|
+
if (!existsSync(checkout.root))
|
|
648
|
+
continue;
|
|
649
|
+
const found = commitsOf(checkout, trunk, from, to);
|
|
650
|
+
if (!found.ok)
|
|
651
|
+
return { checkouts: [], unavailable: found.message };
|
|
652
|
+
checkouts.push({ ...checkout, commits: found.commits });
|
|
653
|
+
}
|
|
654
|
+
return { checkouts, unavailable: null };
|
|
655
|
+
}
|
|
656
|
+
/** The policy file's name and byte digest, for the `attested` verdict. */
|
|
657
|
+
function policyFacts(options) {
|
|
658
|
+
const where = options.policy.file !== undefined
|
|
659
|
+
? { file: options.policy.file }
|
|
660
|
+
: { dir: options.policy.dir ?? options.root };
|
|
661
|
+
if (options.schemaDir !== undefined)
|
|
662
|
+
where.schemaDir = options.schemaDir;
|
|
663
|
+
const load = loadPolicy(where);
|
|
664
|
+
// Fail closed exactly as the daemon's TTL read does: a policy that will not
|
|
665
|
+
// load widens nothing, so the guarded set is the built-in one and no path is
|
|
666
|
+
// quietly dropped out of it.
|
|
667
|
+
const protectedPaths = load.ok ? (load.policy.protected_paths ?? []) : [];
|
|
668
|
+
const path = options.policy.file ?? join(options.policy.dir ?? options.root, "APPROVAL.md");
|
|
669
|
+
let sha256 = null;
|
|
670
|
+
try {
|
|
671
|
+
sha256 = policyBytesHash(readFileSync(path));
|
|
672
|
+
}
|
|
673
|
+
catch {
|
|
674
|
+
sha256 = null;
|
|
675
|
+
}
|
|
676
|
+
return { path: basename(path), sha256, protectedPaths };
|
|
677
|
+
}
|
|
678
|
+
/**
|
|
679
|
+
* Observe and judge, appending nothing.
|
|
680
|
+
*
|
|
681
|
+
* The read-only half, which is the half `approval doctor` calls: doctor reports
|
|
682
|
+
* and the daemon writes, so two processes never become two writers of one fact.
|
|
683
|
+
*/
|
|
684
|
+
export function reportDarkSessions(options) {
|
|
685
|
+
const now = readClock(options.clock === undefined ? {} : { clock: options.clock });
|
|
686
|
+
const at = Date.parse(now);
|
|
687
|
+
const from = new Date((Number.isNaN(at) ? Date.now() : at) - options.windowMs).toISOString();
|
|
688
|
+
const observe = options.observe ?? observeGitActivity;
|
|
689
|
+
const activity = observe(options.root, from, now);
|
|
690
|
+
const facts = policyFacts(options);
|
|
691
|
+
const storeDir = payloadStoreDirFor(options.logPath);
|
|
692
|
+
const cache = new Map();
|
|
693
|
+
const payloadFor = (hash) => {
|
|
694
|
+
if (cache.has(hash))
|
|
695
|
+
return cache.get(hash) ?? null;
|
|
696
|
+
const loaded = loadPayload(storeDir, hash);
|
|
697
|
+
const value = loaded.ok ? loaded.value : null;
|
|
698
|
+
cache.set(hash, value);
|
|
699
|
+
return value;
|
|
700
|
+
};
|
|
701
|
+
const report = evaluateDarkSessions({
|
|
702
|
+
activity,
|
|
703
|
+
records: options.records,
|
|
704
|
+
...(options.logDetail === undefined ? {} : { logDetail: options.logDetail }),
|
|
705
|
+
policyProtectedPaths: facts.protectedPaths,
|
|
706
|
+
policyPath: facts.path,
|
|
707
|
+
policySha256: facts.sha256,
|
|
708
|
+
payloadFor,
|
|
709
|
+
daemonEmails: DAEMON_EVIDENCE_EMAILS,
|
|
710
|
+
window: { from, to: now },
|
|
711
|
+
});
|
|
712
|
+
return { report, window: { from, to: now } };
|
|
713
|
+
}
|
|
714
|
+
//# sourceMappingURL=dark-session.js.map
|