approval-md 0.1.0 → 0.3.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/README.md +629 -559
- package/SPEC.md +99 -24
- package/dist/src/adapters/agentmail.d.ts +426 -0
- package/dist/src/adapters/agentmail.js +2 -2
- package/dist/src/adapters/conformance.d.ts +149 -0
- package/dist/src/adapters/contract.d.ts +628 -0
- package/dist/src/adapters/contract.js +110 -16
- package/dist/src/adapters/contract.js.map +1 -1
- package/dist/src/adapters/email.d.ts +324 -0
- package/dist/src/adapters/env-passphrase.d.ts +93 -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 +2 -1
- package/dist/src/adapters/registry.js.map +1 -1
- package/dist/src/adapters/smtp.d.ts +213 -0
- package/dist/src/adapters/vault-provider.d.ts +114 -0
- package/dist/src/adapters/vault-provider.js +3 -3
- 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/cli.d.ts +193 -0
- package/dist/src/channels/conformance.d.ts +92 -0
- package/dist/src/channels/contract.d.ts +656 -0
- package/dist/src/channels/contract.js +200 -7
- package/dist/src/channels/contract.js.map +1 -1
- package/dist/src/channels/payload-view.d.ts +35 -0
- package/dist/src/channels/render-queue.d.ts +149 -0
- package/dist/src/channels/tagging.d.ts +196 -0
- package/dist/src/channels/telegram.d.ts +1944 -0
- package/dist/src/channels/telegram.js +218 -23
- package/dist/src/channels/telegram.js.map +1 -1
- package/dist/src/channels/web.d.ts +350 -0
- package/dist/src/channels/web.js +17 -0
- package/dist/src/channels/web.js.map +1 -1
- package/dist/src/cli/adapter.d.ts +90 -0
- package/dist/src/cli/adapter.js +25 -15
- package/dist/src/cli/adapter.js.map +1 -1
- package/dist/src/cli/amend.d.ts +59 -0
- package/dist/src/cli/amend.js +214 -30
- package/dist/src/cli/amend.js.map +1 -1
- package/dist/src/cli/args.d.ts +43 -0
- package/dist/src/cli/attest.d.ts +50 -0
- package/dist/src/cli/attest.js +134 -7
- package/dist/src/cli/attest.js.map +1 -1
- package/dist/src/cli/audit-card.d.ts +62 -0
- package/dist/src/cli/audit.d.ts +59 -0
- package/dist/src/cli/channel-telegram.d.ts +879 -0
- package/dist/src/cli/channel-telegram.js +311 -13
- package/dist/src/cli/channel-telegram.js.map +1 -1
- package/dist/src/cli/channel-web.d.ts +131 -0
- package/dist/src/cli/channel.d.ts +80 -0
- package/dist/src/cli/channel.js +9 -0
- package/dist/src/cli/channel.js.map +1 -1
- package/dist/src/cli/checkpoint-tap.d.ts +169 -0
- package/dist/src/cli/codex-bridge.d.ts +819 -0
- package/dist/src/cli/codex-bridge.js +1607 -0
- package/dist/src/cli/codex-bridge.js.map +1 -0
- package/dist/src/cli/codex.d.ts +2 -0
- package/dist/src/cli/codex.js +469 -0
- package/dist/src/cli/codex.js.map +1 -0
- package/dist/src/cli/coverage.d.ts +61 -0
- package/dist/src/cli/daemon.d.ts +120 -0
- package/dist/src/cli/daemon.js +4 -1
- package/dist/src/cli/daemon.js.map +1 -1
- package/dist/src/cli/doctor.d.ts +129 -0
- package/dist/src/cli/doctor.js +586 -17
- package/dist/src/cli/doctor.js.map +1 -1
- package/dist/src/cli/env.d.ts +65 -0
- package/dist/src/cli/execute.d.ts +202 -0
- package/dist/src/cli/execute.js +25 -2
- package/dist/src/cli/execute.js.map +1 -1
- package/dist/src/cli/exit-codes.d.ts +73 -0
- package/dist/src/cli/feedback.d.ts +60 -0
- package/dist/src/cli/gate-window.d.ts +40 -0
- package/dist/src/cli/gate.d.ts +68 -0
- package/dist/src/cli/git-scope.d.ts +190 -0
- package/dist/src/cli/gloss-attach.d.ts +85 -0
- package/dist/src/cli/gloss-codex-child.d.ts +9 -0
- package/dist/src/cli/gloss-codex.d.ts +24 -0
- package/dist/src/cli/gloss-options.d.ts +42 -0
- package/dist/src/cli/gloss.d.ts +265 -0
- package/dist/src/cli/help.d.ts +107 -0
- package/dist/src/cli/help.js +320 -93
- package/dist/src/cli/help.js.map +1 -1
- package/dist/src/cli/hook-codex.d.ts +126 -0
- package/dist/src/cli/hook-codex.js +226 -0
- package/dist/src/cli/hook-codex.js.map +1 -0
- package/dist/src/cli/hook.d.ts +787 -0
- package/dist/src/cli/hook.js +1235 -181
- package/dist/src/cli/hook.js.map +1 -1
- package/dist/src/cli/import.d.ts +35 -0
- package/dist/src/cli/import.js +1 -1
- package/dist/src/cli/import.js.map +1 -1
- package/dist/src/cli/init.d.ts +84 -0
- package/dist/src/cli/init.js +2 -2
- package/dist/src/cli/init.js.map +1 -1
- package/dist/src/cli/instructions.d.ts +23 -0
- package/dist/src/cli/journal.d.ts +41 -0
- package/dist/src/cli/log-advance.d.ts +287 -0
- package/dist/src/cli/log-advance.js +102 -11
- package/dist/src/cli/log-advance.js.map +1 -1
- package/dist/src/cli/log-anchor.d.ts +176 -0
- package/dist/src/cli/log-checkpoint.d.ts +22 -0
- package/dist/src/cli/log-sync.d.ts +243 -0
- package/dist/src/cli/log-verbs.d.ts +16 -0
- package/dist/src/cli/log-verbs.js +7 -1
- package/dist/src/cli/log-verbs.js.map +1 -1
- package/dist/src/cli/long-help.d.ts +70 -0
- package/dist/src/cli/main.d.ts +77 -0
- package/dist/src/cli/main.js +159 -7
- package/dist/src/cli/main.js.map +1 -1
- package/dist/src/cli/mcp.d.ts +52 -0
- package/dist/src/cli/paths.d.ts +56 -0
- package/dist/src/cli/payload.d.ts +58 -0
- package/dist/src/cli/policy-apply.d.ts +195 -0
- package/dist/src/cli/policy-apply.js +573 -0
- package/dist/src/cli/policy-apply.js.map +1 -0
- package/dist/src/cli/policy.d.ts +43 -0
- package/dist/src/cli/policy.js +14 -1
- package/dist/src/cli/policy.js.map +1 -1
- package/dist/src/cli/preflight.d.ts +501 -0
- package/dist/src/cli/preflight.js +689 -45
- package/dist/src/cli/preflight.js.map +1 -1
- package/dist/src/cli/progress.d.ts +78 -0
- package/dist/src/cli/prompt.d.ts +209 -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/render.d.ts +22 -0
- package/dist/src/cli/sandbox.d.ts +51 -0
- package/dist/src/cli/sandbox.js +17 -1
- package/dist/src/cli/sandbox.js.map +1 -1
- package/dist/src/cli/scaffold.d.ts +79 -0
- package/dist/src/cli/scaffold.js +1 -1
- package/dist/src/cli/setup-adapter.d.ts +137 -0
- package/dist/src/cli/setup-adapter.js +38 -4
- package/dist/src/cli/setup-adapter.js.map +1 -1
- package/dist/src/cli/setup-channel.d.ts +126 -0
- package/dist/src/cli/setup-channel.js +28 -1
- package/dist/src/cli/setup-channel.js.map +1 -1
- package/dist/src/cli/setup-checkpoint.d.ts +57 -0
- package/dist/src/cli/setup-common.d.ts +277 -0
- package/dist/src/cli/setup-common.js +3 -2
- package/dist/src/cli/setup-common.js.map +1 -1
- package/dist/src/cli/setup-flow.d.ts +287 -0
- package/dist/src/cli/setup-service.d.ts +96 -0
- package/dist/src/cli/setup.d.ts +204 -0
- package/dist/src/cli/setup.js +94 -2
- package/dist/src/cli/setup.js.map +1 -1
- package/dist/src/cli/style.d.ts +320 -0
- package/dist/src/cli/token.d.ts +39 -0
- package/dist/src/cli/up.d.ts +155 -0
- package/dist/src/cli/up.js +119 -53
- package/dist/src/cli/up.js.map +1 -1
- package/dist/src/cli/usage.d.ts +37 -0
- package/dist/src/cli/values.d.ts +40 -0
- package/dist/src/cli/values.js +3 -4
- package/dist/src/cli/values.js.map +1 -1
- package/dist/src/cli/vault.d.ts +59 -0
- package/dist/src/cli/vault.js +2 -2
- package/dist/src/cli/vault.js.map +1 -1
- package/dist/src/cli/verb-registry.d.ts +76 -0
- package/dist/src/cli/verb-registry.js +344 -11
- package/dist/src/cli/verb-registry.js.map +1 -1
- package/dist/src/cli/wordmark.d.ts +31 -0
- package/dist/src/cli/wordmark.js +2 -2
- package/dist/src/codex/broker.d.ts +229 -0
- package/dist/src/codex/broker.js +548 -0
- package/dist/src/codex/broker.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/runner.d.ts +178 -0
- package/dist/src/codex/runner.js +231 -0
- package/dist/src/codex/runner.js.map +1 -0
- package/dist/src/codex/serve.d.ts +56 -0
- package/dist/src/codex/serve.js +98 -0
- package/dist/src/codex/serve.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-commit.d.ts +219 -0
- package/dist/src/codex/workspace-commit.js +549 -0
- package/dist/src/codex/workspace-commit.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 +221 -0
- package/dist/src/core/advance-cycle.js +66 -2
- package/dist/src/core/advance-cycle.js.map +1 -1
- package/dist/src/core/agents-md.d.ts +278 -0
- package/dist/src/core/agents-md.js +33 -31
- package/dist/src/core/agents-md.js.map +1 -1
- 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 +635 -0
- package/dist/src/core/attest.js +326 -4
- package/dist/src/core/attest.js.map +1 -1
- package/dist/src/core/audit.d.ts +510 -0
- package/dist/src/core/audit.js +13 -0
- package/dist/src/core/audit.js.map +1 -1
- package/dist/src/core/budgets.d.ts +238 -0
- package/dist/src/core/channel-owner.d.ts +213 -0
- package/dist/src/core/channel-owner.js +358 -0
- package/dist/src/core/channel-owner.js.map +1 -0
- package/dist/src/core/checkpoint.d.ts +500 -0
- package/dist/src/core/child-env.d.ts +88 -0
- package/dist/src/core/clock.d.ts +52 -0
- package/dist/src/core/command-class.d.ts +697 -0
- package/dist/src/core/command-class.js +713 -25
- package/dist/src/core/command-class.js.map +1 -1
- package/dist/src/core/commit-guard.d.ts +272 -0
- package/dist/src/core/commit-guard.js +424 -0
- package/dist/src/core/commit-guard.js.map +1 -0
- package/dist/src/core/coverage-sources/adapter.d.ts +40 -0
- package/dist/src/core/coverage-sources/gh.d.ts +48 -0
- package/dist/src/core/coverage-sources/git.d.ts +101 -0
- package/dist/src/core/coverage.d.ts +217 -0
- package/dist/src/core/credential-spec.d.ts +72 -0
- package/dist/src/core/daemon-actor.d.ts +45 -0
- package/dist/src/core/daemon-actor.js +54 -0
- package/dist/src/core/daemon-actor.js.map +1 -0
- package/dist/src/core/dark-session.d.ts +432 -0
- package/dist/src/core/dark-session.js +266 -82
- package/dist/src/core/dark-session.js.map +1 -1
- package/dist/src/core/decision-refusal.d.ts +206 -0
- package/dist/src/core/decision-refusal.js +24 -2
- package/dist/src/core/decision-refusal.js.map +1 -1
- package/dist/src/core/env-file.d.ts +455 -0
- package/dist/src/core/env-file.js +60 -1
- package/dist/src/core/env-file.js.map +1 -1
- package/dist/src/core/execute.d.ts +871 -0
- package/dist/src/core/execute.js +59 -8
- package/dist/src/core/execute.js.map +1 -1
- package/dist/src/core/frontmatter.d.ts +78 -0
- package/dist/src/core/gate-window.d.ts +312 -0
- package/dist/src/core/gate.d.ts +1449 -0
- package/dist/src/core/gate.js +149 -14
- package/dist/src/core/gate.js.map +1 -1
- package/dist/src/core/gesture-refusal.d.ts +166 -0
- package/dist/src/core/gesture-refusal.js +188 -0
- package/dist/src/core/gesture-refusal.js.map +1 -0
- package/dist/src/core/git-run.d.ts +73 -0
- package/dist/src/core/harness-version.d.ts +157 -0
- package/dist/src/core/harness-version.js +4 -1
- package/dist/src/core/harness-version.js.map +1 -1
- package/dist/src/core/harness-wait.d.ts +55 -0
- package/dist/src/core/head-retry.d.ts +107 -0
- package/dist/src/core/instance.d.ts +310 -0
- package/dist/src/core/instance.js +113 -0
- package/dist/src/core/instance.js.map +1 -1
- package/dist/src/core/intake-limits.d.ts +247 -0
- package/dist/src/core/jcs.d.ts +52 -0
- package/dist/src/core/journal.d.ts +144 -0
- package/dist/src/core/live-draw.d.ts +436 -0
- package/dist/src/core/log-reconcile.d.ts +89 -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 +316 -0
- package/dist/src/core/log.js.map +1 -1
- package/dist/src/core/loop.d.ts +274 -0
- package/dist/src/core/loop.js +11 -0
- package/dist/src/core/loop.js.map +1 -1
- package/dist/src/core/md-fence.d.ts +41 -0
- package/dist/src/core/money.d.ts +147 -0
- package/dist/src/core/payload-census.d.ts +74 -0
- package/dist/src/core/payload-store.d.ts +175 -0
- package/dist/src/core/payload.d.ts +71 -0
- package/dist/src/core/policy-diff.d.ts +292 -0
- package/dist/src/core/policy-diff.js +27 -4
- package/dist/src/core/policy-diff.js.map +1 -1
- package/dist/src/core/policy-expectations.d.ts +199 -0
- package/dist/src/core/policy-explain.d.ts +160 -0
- package/dist/src/core/policy-explain.js +63 -3
- package/dist/src/core/policy-explain.js.map +1 -1
- package/dist/src/core/policy-load.d.ts +567 -0
- package/dist/src/core/policy-load.js +36 -6
- package/dist/src/core/policy-load.js.map +1 -1
- package/dist/src/core/policy-match.d.ts +324 -0
- package/dist/src/core/policy-match.js +72 -9
- package/dist/src/core/policy-match.js.map +1 -1
- package/dist/src/core/policy-proposal.d.ts +317 -0
- package/dist/src/core/policy-proposal.js +102 -2
- package/dist/src/core/policy-proposal.js.map +1 -1
- package/dist/src/core/prompt-layout.d.ts +221 -0
- package/dist/src/core/protected-path-guard.d.ts +566 -0
- package/dist/src/core/protected-path-guard.js +848 -55
- package/dist/src/core/protected-path-guard.js.map +1 -1
- package/dist/src/core/question-preempted.d.ts +141 -0
- package/dist/src/core/question-preempted.js +152 -0
- package/dist/src/core/question-preempted.js.map +1 -0
- package/dist/src/core/read-scope.d.ts +172 -0
- package/dist/src/core/read-scope.js +252 -0
- package/dist/src/core/read-scope.js.map +1 -0
- package/dist/src/core/registration.d.ts +25 -0
- package/dist/src/core/reindex.d.ts +99 -0
- package/dist/src/core/sampler.d.ts +313 -0
- package/dist/src/core/sandbox.d.ts +371 -0
- package/dist/src/core/sandbox.js +190 -1
- package/dist/src/core/sandbox.js.map +1 -1
- package/dist/src/core/seal.d.ts +165 -0
- package/dist/src/core/sender-identity.d.ts +476 -0
- package/dist/src/core/sender-identity.js +572 -0
- package/dist/src/core/sender-identity.js.map +1 -0
- package/dist/src/core/shlex.d.ts +102 -0
- package/dist/src/core/shlex.js +159 -0
- package/dist/src/core/shlex.js.map +1 -0
- package/dist/src/core/state.d.ts +505 -0
- package/dist/src/core/task-file.d.ts +185 -0
- package/dist/src/core/telegram-config.d.ts +93 -0
- package/dist/src/core/token.d.ts +409 -0
- package/dist/src/core/token.js +21 -38
- package/dist/src/core/token.js.map +1 -1
- package/dist/src/core/validate.d.ts +138 -0
- package/dist/src/core/values.d.ts +147 -0
- package/dist/src/core/values.js +36 -1
- package/dist/src/core/values.js.map +1 -1
- package/dist/src/core/vault.d.ts +291 -0
- package/dist/src/core/verified-snapshot.d.ts +204 -0
- package/dist/src/core/verify.d.ts +336 -0
- package/dist/src/core/version.d.ts +8 -0
- package/dist/src/core/wysiwys.d.ts +370 -0
- package/dist/src/daemon/advance-child.d.ts +39 -0
- package/dist/src/daemon/advance.d.ts +476 -0
- package/dist/src/daemon/advance.js +25 -4
- package/dist/src/daemon/advance.js.map +1 -1
- package/dist/src/daemon/audit.d.ts +87 -0
- package/dist/src/daemon/daemon.d.ts +1180 -0
- package/dist/src/daemon/daemon.js +9 -0
- package/dist/src/daemon/daemon.js.map +1 -1
- package/dist/src/daemon/dark-session.d.ts +64 -0
- package/dist/src/daemon/draw-child.d.ts +36 -0
- package/dist/src/daemon/draw.d.ts +154 -0
- package/dist/src/daemon/git-evidence.d.ts +173 -0
- package/dist/src/daemon/git-evidence.js +1 -1
- package/dist/src/daemon/projection.d.ts +180 -0
- package/dist/src/daemon/prune.d.ts +207 -0
- package/dist/src/mcp/http.d.ts +113 -0
- package/dist/src/mcp/server.d.ts +265 -0
- package/dist/src/mcp/server.js +17 -1
- package/dist/src/mcp/server.js.map +1 -1
- package/docs/adapter-api.md +106 -0
- package/docs/cli-reference.md +1316 -63
- package/docs/codex-enforced-session.md +103 -0
- package/docs/codex-workspace-broker.md +118 -0
- package/package.json +14 -2
- package/schema/codex-instance.schema.json +82 -0
- package/schema/event.schema.json +539 -9
- 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/event/invalid/approval-granted-sender-hashed-false.json +20 -0
- package/schema/fixtures/event/invalid/approval-granted-sender-hashed-raw-id.json +20 -0
- package/schema/fixtures/event/invalid/audit-gesture-refused-human-actor.json +16 -0
- package/schema/fixtures/event/invalid/audit-gesture-refused-no-actor-no-sender.json +15 -0
- package/schema/fixtures/event/invalid/audit-gesture-refused-unknown-gesture.json +16 -0
- package/schema/fixtures/event/invalid/audit-question-preempted-agent-actor.json +16 -0
- package/schema/fixtures/event/invalid/audit-question-preempted-no-question-id.json +16 -0
- package/schema/fixtures/event/invalid/audit-question-preempted-unknown-source.json +15 -0
- package/schema/fixtures/event/invalid/gate-path-signed-off-absolute-path.json +14 -0
- package/schema/fixtures/event/invalid/gate-path-signed-off-agent-actor.json +14 -0
- package/schema/fixtures/event/invalid/gate-path-signed-off-missing-path.json +13 -0
- package/schema/fixtures/event/valid/approval-granted-sender-hashed.json +20 -0
- package/schema/fixtures/event/valid/audit-gesture-refused-review-note.json +21 -0
- package/schema/fixtures/event/valid/audit-gesture-refused-sender-key-unavailable.json +19 -0
- package/schema/fixtures/event/valid/audit-gesture-refused.json +19 -0
- package/schema/fixtures/event/valid/audit-question-preempted-no-verdict.json +16 -0
- package/schema/fixtures/event/valid/audit-question-preempted.json +20 -0
- package/schema/fixtures/event/valid/gate-path-signed-off.json +14 -0
- package/schema/fixtures/event/valid/harness-kind-claude-code.json +23 -0
- package/schema/fixtures/event/valid/harness-kind-codex.json +23 -0
- package/schema/fixtures/event/valid/harness-kind-cursor.json +23 -0
- package/schema/fixtures/event/valid/harness-kind-grok.json +23 -0
- package/schema/fixtures/event/valid/harness-kind-muse.json +23 -0
- package/schema/fixtures/policy/invalid/senders-half-keyed.json +20 -0
- package/schema/fixtures/policy/valid/canonical.json +1 -1
- package/schema/fixtures/policy/valid/senders-keyed.json +24 -0
- package/schema/fixtures/policy-md/valid/canonical.md +1 -1
- package/schema/fixtures/policy-md/valid/with-values.md +5 -7
- package/schema/fixtures/values/invalid/class-shaped.json +1 -1
- package/schema/fixtures/values/invalid/duplicate-entry.json +1 -1
- package/schema/fixtures/values/invalid/non-string-item.json +1 -1
- package/schema/fixtures/values/invalid/over-cap.json +1 -1
- package/schema/fixtures/values/invalid/unknown-key.json +1 -1
- package/schema/fixtures/values/invalid/version-float.json +1 -0
- package/schema/fixtures/values/invalid/version-integer.json +1 -0
- package/schema/fixtures/values/invalid/version-wrong-string.json +1 -0
- package/schema/fixtures/values/valid/empty-lists.json +2 -3
- package/schema/fixtures/values/valid/full.json +5 -7
- package/schema/fixtures/values/valid/minimal.json +1 -1
- package/schema/fixtures/values-md/invalid/schema-invalid.md +5 -3
- package/schema/fixtures/values-md/invalid/two-blocks.md +3 -3
- package/schema/fixtures/values-md/invalid/unterminated.md +2 -2
- package/schema/fixtures/values-md/invalid/version-1.md +69 -0
- package/schema/fixtures/values-md/invalid/version-unquoted.md +64 -0
- package/schema/fixtures/values-md/invalid/yaml-error.md +2 -2
- package/schema/fixtures/values-md/valid/absent.md +1 -1
- package/schema/fixtures/values-md/valid/with-values.md +5 -7
- package/schema/policy.schema.json +75 -3
- package/schema/values.schema.json +7 -11
- package/templates/codex/README.md +9 -0
- package/schema/fixtures/values/invalid/version-string.json +0 -1
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read scope: which directories an agent may read from (APRV-347).
|
|
3
|
+
*
|
|
4
|
+
* ## The hole this closes
|
|
5
|
+
*
|
|
6
|
+
* Writes and deletes have been path-scoped for a while. `files.delete.scratch`
|
|
7
|
+
* versus `files.delete.out_of_scope` is decided by comparing a resolved target
|
|
8
|
+
* against roots the caller supplied (`ClassifierContext.scratchRoots`), and the
|
|
9
|
+
* file tools carry their target into the payload a grant binds. Reads had none
|
|
10
|
+
* of that: every shell reader classified `read.shell` with no path bound, and
|
|
11
|
+
* `Read`, `Glob` and `Grep` were answered `allow` before classification ever
|
|
12
|
+
* ran. So a policy could say a great deal about what an agent may WRITE and
|
|
13
|
+
* nothing at all about what it may SEE, and an agent working in one directory
|
|
14
|
+
* could read every sibling of it.
|
|
15
|
+
*
|
|
16
|
+
* This module is the pure half of the read-side mirror. It holds the class
|
|
17
|
+
* name, the roots arithmetic, and the one genuinely fiddly question — which
|
|
18
|
+
* words of a read command are paths — and it touches no disk, reads no
|
|
19
|
+
* environment and resolves nothing. The impure half (relative paths resolved
|
|
20
|
+
* against a working directory, symlinks followed, the escape that only the
|
|
21
|
+
* filesystem can see) lives in `src/cli/hook.ts`, exactly where the delete
|
|
22
|
+
* rule's second pass lives, and it can only ever TIGHTEN this file's answer.
|
|
23
|
+
*
|
|
24
|
+
* ## Fail closed, in three places
|
|
25
|
+
*
|
|
26
|
+
* SPEC.md §11.1: ambiguity resolves to the stricter path. Here that is
|
|
27
|
+
*
|
|
28
|
+
* 1. a target this file cannot read as a path (a `$VAR`, a glob, a `~`) is out
|
|
29
|
+
* of scope, because what it expands to is not in the text;
|
|
30
|
+
* 2. a read command naming NO target reads the working directory, so it is
|
|
31
|
+
* checked against the working directory rather than waved through;
|
|
32
|
+
* 3. an empty root list means nothing is in scope — but a caller that passes no
|
|
33
|
+
* roots at all gets today's answer instead (see {@link ClassifierContext}),
|
|
34
|
+
* because a caller that forgot the field must not have every read it makes
|
|
35
|
+
* turned into a decision.
|
|
36
|
+
*
|
|
37
|
+
* ## What a root is
|
|
38
|
+
*
|
|
39
|
+
* The gate root (the directory holding the policy file the runtime resolved),
|
|
40
|
+
* the session scratchpad, and the system temp root. A policy may WIDEN that
|
|
41
|
+
* with `read_scope.roots`; it may not narrow it below the gate root, because a
|
|
42
|
+
* runtime that cannot read its own policy, log and workspace cannot run at all.
|
|
43
|
+
*/
|
|
44
|
+
/**
|
|
45
|
+
* The class a read outside every root takes.
|
|
46
|
+
*
|
|
47
|
+
* A sibling of `read.shell` rather than a replacement for it: a read INSIDE the
|
|
48
|
+
* roots is the same ordinary, autonomous act it has always been, and a policy
|
|
49
|
+
* that says nothing about this class gets `defaults.autonomy` for it, which is
|
|
50
|
+
* the fail-closed direction for a name nobody has declared.
|
|
51
|
+
*/
|
|
52
|
+
export declare const READ_OUT_OF_SCOPE_CLASS = "read.file.out_of_scope";
|
|
53
|
+
/**
|
|
54
|
+
* The `read_scope` block of a policy (SPEC.md §5, amended APRV-347).
|
|
55
|
+
*
|
|
56
|
+
* Additive and optional, with the same discipline `protected_paths` has: the
|
|
57
|
+
* built-in roots stand whatever this says, so a policy can widen the scope and
|
|
58
|
+
* never shrink it. A relative entry is resolved against the gate root, so a
|
|
59
|
+
* policy stays portable between a checkout and a clone of it.
|
|
60
|
+
*/
|
|
61
|
+
export interface ReadScope {
|
|
62
|
+
roots?: string[];
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Is `candidate` AT or under `root`, by path segment?
|
|
66
|
+
*
|
|
67
|
+
* At-or-under rather than the delete rule's strictly-under: `ls <gate root>` is
|
|
68
|
+
* a read of the workspace an agent is working in, and a rule that made the root
|
|
69
|
+
* itself out of scope would classify the most ordinary command in the session.
|
|
70
|
+
*
|
|
71
|
+
* Segment matching, never string prefixes: `/dev/muse-other` must not match a
|
|
72
|
+
* root of `/dev/muse`, and `startsWith` says it does.
|
|
73
|
+
*/
|
|
74
|
+
export declare function isAtOrUnderReadRoot(candidate: string, root: string): boolean;
|
|
75
|
+
/** Is this path inside ANY of these roots? */
|
|
76
|
+
export declare function isInReadScope(candidate: string, roots: readonly string[]): boolean;
|
|
77
|
+
/**
|
|
78
|
+
* A value whose expansion the classifier cannot see, and therefore may not
|
|
79
|
+
* vouch for. The same test the delete rule applies, and for the same reason:
|
|
80
|
+
* `cat $SOMEWHERE` reads whatever that variable holds.
|
|
81
|
+
*/
|
|
82
|
+
export declare function isUnreadableTarget(word: string): boolean;
|
|
83
|
+
/**
|
|
84
|
+
* The effective read roots: the built-ins, plus whatever the policy added.
|
|
85
|
+
*
|
|
86
|
+
* Pure, and every input is the caller's. `gateRoot` is the directory holding
|
|
87
|
+
* the policy file the runtime resolved; `systemRoots` are the scratchpad and
|
|
88
|
+
* temp roots the caller already resolved (`resolveScratchRoots` in the hook);
|
|
89
|
+
* `declared` is `read_scope.roots` verbatim.
|
|
90
|
+
*
|
|
91
|
+
* A declared entry that is relative is joined onto the gate root. A declared
|
|
92
|
+
* entry the caller cannot vouch for — empty, or one this file can see is not a
|
|
93
|
+
* path at all — is DROPPED rather than accepted, because a root is an
|
|
94
|
+
* authorization and a malformed one must not become `/`.
|
|
95
|
+
*
|
|
96
|
+
* The result is de-duplicated and otherwise in the order given, so the first
|
|
97
|
+
* root a path matches is the most specific one a reader would expect.
|
|
98
|
+
*/
|
|
99
|
+
export declare function effectiveReadRoots(options: {
|
|
100
|
+
gateRoot: string;
|
|
101
|
+
declared?: readonly string[] | undefined;
|
|
102
|
+
systemRoots?: readonly string[] | undefined;
|
|
103
|
+
}): string[];
|
|
104
|
+
/**
|
|
105
|
+
* How a reader's positionals map to paths.
|
|
106
|
+
*
|
|
107
|
+
* - `all`: every positional is a file or directory (`cat a b`, `ls src`,
|
|
108
|
+
* `diff a b`, `cut -d: -f1 /etc/passwd` — `cut`'s delimiter and field list
|
|
109
|
+
* are flags, so none of its positionals is a pattern).
|
|
110
|
+
* - `after-pattern`: the FIRST positional is a pattern or a script and the rest
|
|
111
|
+
* are paths (`grep needle src`, `sed -n 1,5p file`, `jq .x file.json`) —
|
|
112
|
+
* UNLESS the pattern arrived through a flag (`-e`, `-f`), in which case every
|
|
113
|
+
* positional is a path and the shape collapses to `all`.
|
|
114
|
+
* - `walk`: `find`'s shape — positionals up to the first primary are paths.
|
|
115
|
+
*
|
|
116
|
+
* Binaries absent from this table are absent on purpose, and each omission is a
|
|
117
|
+
* decision not to widen anything:
|
|
118
|
+
*
|
|
119
|
+
* - `echo`, `printf`, `tr`, `test`, `type`, `which`, `pwd`, `true`, `false`,
|
|
120
|
+
* `cd`: their positionals are not files, or name a file without reading its
|
|
121
|
+
* contents. A rule that treated `echo /etc/passwd` as a read of that file
|
|
122
|
+
* would route text through a human.
|
|
123
|
+
* - `basename`, `dirname`, `readlink`, `realpath`: they manipulate or resolve a
|
|
124
|
+
* path and never open it. What leaks is the existence of a name, which is not
|
|
125
|
+
* what this class is about.
|
|
126
|
+
* - `less` and `more` are NOT added to the classifier's reader list by this
|
|
127
|
+
* task. Adding them would take them from `unclassified` (a deny) to
|
|
128
|
+
* `read.shell` (this repository's policy: autonomous), which is a widening,
|
|
129
|
+
* and a task that exists to narrow reads has no business doing that in
|
|
130
|
+
* passing. They are named in the follow-up in `docs/sandboxed-exec.md`.
|
|
131
|
+
*/
|
|
132
|
+
export type ReadTargetShape = "all" | "after-pattern" | "walk";
|
|
133
|
+
/** Which readers take paths, and where. Keyed by the binary's basename. */
|
|
134
|
+
export declare const READ_TARGET_SHAPES: Readonly<Record<string, ReadTargetShape>>;
|
|
135
|
+
/**
|
|
136
|
+
* The paths a read command will open, or `null` when this binary is not one
|
|
137
|
+
* whose reads this module scopes.
|
|
138
|
+
*
|
|
139
|
+
* An empty array is a real answer and is NOT the same as `null`: it means this
|
|
140
|
+
* reader opens the working directory (`ls`, `find`, `grep needle` with no file
|
|
141
|
+
* operand), and the caller checks the working directory in its place. `null`
|
|
142
|
+
* means "not a scoped reader", and the caller leaves the segment alone.
|
|
143
|
+
*
|
|
144
|
+
* Treating a non-path positional as a path costs nothing: a bare word resolves
|
|
145
|
+
* against the working directory, which is inside a root in every session this
|
|
146
|
+
* module is meant for. Treating a path as a non-path costs the whole property.
|
|
147
|
+
*/
|
|
148
|
+
export declare function readTargetsOf(bin: string, positionals: readonly string[], args?: readonly string[]): string[] | null;
|
|
149
|
+
/**
|
|
150
|
+
* The verdict the PURE half can reach for one target.
|
|
151
|
+
*
|
|
152
|
+
* `out-of-scope` and `in-scope` are final. `needs-disk` is the honest answer
|
|
153
|
+
* for a relative path: its meaning depends on a working directory this file
|
|
154
|
+
* does not have, and the caller with the disk decides it. A caller that cannot
|
|
155
|
+
* do the second pass must treat `needs-disk` as out of scope — which is what
|
|
156
|
+
* `hook classify` and `hook <harness>` both do, through the same function.
|
|
157
|
+
*/
|
|
158
|
+
export type ReadTargetVerdict = "in-scope" | "out-of-scope" | "needs-disk";
|
|
159
|
+
/**
|
|
160
|
+
* Read one target against the roots, as far as text alone can settle it.
|
|
161
|
+
*
|
|
162
|
+
* Absolute and inside a root: in scope. Absolute and outside every root: out of
|
|
163
|
+
* scope, decided here, no disk needed. Unreadable (a variable, a glob, a `~`):
|
|
164
|
+
* out of scope, because what it names is not in the text. Anything relative, or
|
|
165
|
+
* carrying a `..`, is `needs-disk`.
|
|
166
|
+
*/
|
|
167
|
+
export declare function readTargetVerdict(target: string, roots: readonly string[]): ReadTargetVerdict;
|
|
168
|
+
/**
|
|
169
|
+
* The roots, rendered for a human: `approval policy check`'s line and the
|
|
170
|
+
* hook's verdict note say the same sentence.
|
|
171
|
+
*/
|
|
172
|
+
export declare function renderReadRoots(roots: readonly string[]): string;
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read scope: which directories an agent may read from (APRV-347).
|
|
3
|
+
*
|
|
4
|
+
* ## The hole this closes
|
|
5
|
+
*
|
|
6
|
+
* Writes and deletes have been path-scoped for a while. `files.delete.scratch`
|
|
7
|
+
* versus `files.delete.out_of_scope` is decided by comparing a resolved target
|
|
8
|
+
* against roots the caller supplied (`ClassifierContext.scratchRoots`), and the
|
|
9
|
+
* file tools carry their target into the payload a grant binds. Reads had none
|
|
10
|
+
* of that: every shell reader classified `read.shell` with no path bound, and
|
|
11
|
+
* `Read`, `Glob` and `Grep` were answered `allow` before classification ever
|
|
12
|
+
* ran. So a policy could say a great deal about what an agent may WRITE and
|
|
13
|
+
* nothing at all about what it may SEE, and an agent working in one directory
|
|
14
|
+
* could read every sibling of it.
|
|
15
|
+
*
|
|
16
|
+
* This module is the pure half of the read-side mirror. It holds the class
|
|
17
|
+
* name, the roots arithmetic, and the one genuinely fiddly question — which
|
|
18
|
+
* words of a read command are paths — and it touches no disk, reads no
|
|
19
|
+
* environment and resolves nothing. The impure half (relative paths resolved
|
|
20
|
+
* against a working directory, symlinks followed, the escape that only the
|
|
21
|
+
* filesystem can see) lives in `src/cli/hook.ts`, exactly where the delete
|
|
22
|
+
* rule's second pass lives, and it can only ever TIGHTEN this file's answer.
|
|
23
|
+
*
|
|
24
|
+
* ## Fail closed, in three places
|
|
25
|
+
*
|
|
26
|
+
* SPEC.md §11.1: ambiguity resolves to the stricter path. Here that is
|
|
27
|
+
*
|
|
28
|
+
* 1. a target this file cannot read as a path (a `$VAR`, a glob, a `~`) is out
|
|
29
|
+
* of scope, because what it expands to is not in the text;
|
|
30
|
+
* 2. a read command naming NO target reads the working directory, so it is
|
|
31
|
+
* checked against the working directory rather than waved through;
|
|
32
|
+
* 3. an empty root list means nothing is in scope — but a caller that passes no
|
|
33
|
+
* roots at all gets today's answer instead (see {@link ClassifierContext}),
|
|
34
|
+
* because a caller that forgot the field must not have every read it makes
|
|
35
|
+
* turned into a decision.
|
|
36
|
+
*
|
|
37
|
+
* ## What a root is
|
|
38
|
+
*
|
|
39
|
+
* The gate root (the directory holding the policy file the runtime resolved),
|
|
40
|
+
* the session scratchpad, and the system temp root. A policy may WIDEN that
|
|
41
|
+
* with `read_scope.roots`; it may not narrow it below the gate root, because a
|
|
42
|
+
* runtime that cannot read its own policy, log and workspace cannot run at all.
|
|
43
|
+
*/
|
|
44
|
+
/**
|
|
45
|
+
* The class a read outside every root takes.
|
|
46
|
+
*
|
|
47
|
+
* A sibling of `read.shell` rather than a replacement for it: a read INSIDE the
|
|
48
|
+
* roots is the same ordinary, autonomous act it has always been, and a policy
|
|
49
|
+
* that says nothing about this class gets `defaults.autonomy` for it, which is
|
|
50
|
+
* the fail-closed direction for a name nobody has declared.
|
|
51
|
+
*/
|
|
52
|
+
export const READ_OUT_OF_SCOPE_CLASS = "read.file.out_of_scope";
|
|
53
|
+
/** Non-empty path segments, `.` dropped. Identical to the classifier's own. */
|
|
54
|
+
function segmentsOf(candidate) {
|
|
55
|
+
return candidate
|
|
56
|
+
.split(/[/\\]+/u)
|
|
57
|
+
.filter((segment) => segment.length > 0 && segment !== ".");
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Is `candidate` AT or under `root`, by path segment?
|
|
61
|
+
*
|
|
62
|
+
* At-or-under rather than the delete rule's strictly-under: `ls <gate root>` is
|
|
63
|
+
* a read of the workspace an agent is working in, and a rule that made the root
|
|
64
|
+
* itself out of scope would classify the most ordinary command in the session.
|
|
65
|
+
*
|
|
66
|
+
* Segment matching, never string prefixes: `/dev/muse-other` must not match a
|
|
67
|
+
* root of `/dev/muse`, and `startsWith` says it does.
|
|
68
|
+
*/
|
|
69
|
+
export function isAtOrUnderReadRoot(candidate, root) {
|
|
70
|
+
const want = segmentsOf(root);
|
|
71
|
+
const have = segmentsOf(candidate);
|
|
72
|
+
if (want.length === 0)
|
|
73
|
+
return false;
|
|
74
|
+
if (have.length < want.length)
|
|
75
|
+
return false;
|
|
76
|
+
return want.every((segment, index) => segment === have[index]);
|
|
77
|
+
}
|
|
78
|
+
/** Is this path inside ANY of these roots? */
|
|
79
|
+
export function isInReadScope(candidate, roots) {
|
|
80
|
+
return roots.some((root) => isAtOrUnderReadRoot(candidate, root));
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* A value whose expansion the classifier cannot see, and therefore may not
|
|
84
|
+
* vouch for. The same test the delete rule applies, and for the same reason:
|
|
85
|
+
* `cat $SOMEWHERE` reads whatever that variable holds.
|
|
86
|
+
*/
|
|
87
|
+
export function isUnreadableTarget(word) {
|
|
88
|
+
return (word.includes("$") ||
|
|
89
|
+
word.includes("*") ||
|
|
90
|
+
word.includes("?") ||
|
|
91
|
+
word.includes("[") ||
|
|
92
|
+
word.startsWith("~"));
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* The effective read roots: the built-ins, plus whatever the policy added.
|
|
96
|
+
*
|
|
97
|
+
* Pure, and every input is the caller's. `gateRoot` is the directory holding
|
|
98
|
+
* the policy file the runtime resolved; `systemRoots` are the scratchpad and
|
|
99
|
+
* temp roots the caller already resolved (`resolveScratchRoots` in the hook);
|
|
100
|
+
* `declared` is `read_scope.roots` verbatim.
|
|
101
|
+
*
|
|
102
|
+
* A declared entry that is relative is joined onto the gate root. A declared
|
|
103
|
+
* entry the caller cannot vouch for — empty, or one this file can see is not a
|
|
104
|
+
* path at all — is DROPPED rather than accepted, because a root is an
|
|
105
|
+
* authorization and a malformed one must not become `/`.
|
|
106
|
+
*
|
|
107
|
+
* The result is de-duplicated and otherwise in the order given, so the first
|
|
108
|
+
* root a path matches is the most specific one a reader would expect.
|
|
109
|
+
*/
|
|
110
|
+
export function effectiveReadRoots(options) {
|
|
111
|
+
const roots = [];
|
|
112
|
+
const add = (candidate) => {
|
|
113
|
+
if (candidate.length === 0)
|
|
114
|
+
return;
|
|
115
|
+
if (!roots.includes(candidate))
|
|
116
|
+
roots.push(candidate);
|
|
117
|
+
};
|
|
118
|
+
add(options.gateRoot);
|
|
119
|
+
for (const root of options.systemRoots ?? [])
|
|
120
|
+
add(root);
|
|
121
|
+
for (const entry of options.declared ?? []) {
|
|
122
|
+
if (typeof entry !== "string" || entry.length === 0)
|
|
123
|
+
continue;
|
|
124
|
+
if (isUnreadableTarget(entry))
|
|
125
|
+
continue;
|
|
126
|
+
add(entry.startsWith("/") ? entry : `${options.gateRoot}/${entry}`);
|
|
127
|
+
}
|
|
128
|
+
return roots;
|
|
129
|
+
}
|
|
130
|
+
/** Which readers take paths, and where. Keyed by the binary's basename. */
|
|
131
|
+
export const READ_TARGET_SHAPES = {
|
|
132
|
+
cat: "all",
|
|
133
|
+
cksum: "all",
|
|
134
|
+
cut: "all",
|
|
135
|
+
diff: "all",
|
|
136
|
+
du: "all",
|
|
137
|
+
file: "all",
|
|
138
|
+
find: "walk",
|
|
139
|
+
grep: "after-pattern",
|
|
140
|
+
head: "all",
|
|
141
|
+
jq: "after-pattern",
|
|
142
|
+
ls: "all",
|
|
143
|
+
md5sum: "all",
|
|
144
|
+
rg: "after-pattern",
|
|
145
|
+
sed: "after-pattern",
|
|
146
|
+
sha256sum: "all",
|
|
147
|
+
shasum: "all",
|
|
148
|
+
sort: "all",
|
|
149
|
+
stat: "all",
|
|
150
|
+
tail: "all",
|
|
151
|
+
tree: "all",
|
|
152
|
+
uniq: "all",
|
|
153
|
+
wc: "all",
|
|
154
|
+
};
|
|
155
|
+
/**
|
|
156
|
+
* `find` primaries: the first word starting with `-` ends the path list.
|
|
157
|
+
*
|
|
158
|
+
* `find` is the one reader whose arguments are a little language, and its shape
|
|
159
|
+
* is `find [paths…] [expression]`. This one reads the RAW argument list rather
|
|
160
|
+
* than the flag-filtered positionals, because the filter is what tells the
|
|
161
|
+
* paths from the expression: in `find . -name '*.ts'` the pattern `*.ts` is a
|
|
162
|
+
* positional too, and a rule fed the filtered list would read it as a path,
|
|
163
|
+
* find it unreadable, and call an ordinary walk of the workspace out of scope.
|
|
164
|
+
*/
|
|
165
|
+
function walkTargets(args) {
|
|
166
|
+
const targets = [];
|
|
167
|
+
for (const word of args) {
|
|
168
|
+
if (word.startsWith("-"))
|
|
169
|
+
break;
|
|
170
|
+
targets.push(word);
|
|
171
|
+
}
|
|
172
|
+
return targets;
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Flags that carry the pattern or the script, so every positional is a path.
|
|
176
|
+
*
|
|
177
|
+
* `grep -e needle src`, `sed -f script.sed file`, `rg --regexp needle dir`: the
|
|
178
|
+
* first positional is the FILE, and a rule that skipped it would leave the one
|
|
179
|
+
* target that matters unchecked. Under-detection is the failure mode that
|
|
180
|
+
* matters here — an unchecked read is a read outside the jail — so the shape
|
|
181
|
+
* widens to `all` whenever one of these appears.
|
|
182
|
+
*/
|
|
183
|
+
const PATTERN_BEARING_FLAGS = [
|
|
184
|
+
"-e",
|
|
185
|
+
"-f",
|
|
186
|
+
"--regexp",
|
|
187
|
+
"--expression",
|
|
188
|
+
"--file",
|
|
189
|
+
];
|
|
190
|
+
/** Did the pattern (or script) arrive through a flag rather than a positional? */
|
|
191
|
+
function patternCameFromFlag(args) {
|
|
192
|
+
return args.some((arg) => PATTERN_BEARING_FLAGS.includes(arg) ||
|
|
193
|
+
PATTERN_BEARING_FLAGS.some((flag) => flag.startsWith("--") && arg.startsWith(`${flag}=`)));
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* The paths a read command will open, or `null` when this binary is not one
|
|
197
|
+
* whose reads this module scopes.
|
|
198
|
+
*
|
|
199
|
+
* An empty array is a real answer and is NOT the same as `null`: it means this
|
|
200
|
+
* reader opens the working directory (`ls`, `find`, `grep needle` with no file
|
|
201
|
+
* operand), and the caller checks the working directory in its place. `null`
|
|
202
|
+
* means "not a scoped reader", and the caller leaves the segment alone.
|
|
203
|
+
*
|
|
204
|
+
* Treating a non-path positional as a path costs nothing: a bare word resolves
|
|
205
|
+
* against the working directory, which is inside a root in every session this
|
|
206
|
+
* module is meant for. Treating a path as a non-path costs the whole property.
|
|
207
|
+
*/
|
|
208
|
+
export function readTargetsOf(bin, positionals, args = []) {
|
|
209
|
+
const shape = READ_TARGET_SHAPES[basenameOf(bin)];
|
|
210
|
+
if (shape === undefined)
|
|
211
|
+
return null;
|
|
212
|
+
switch (shape) {
|
|
213
|
+
case "all":
|
|
214
|
+
return [...positionals];
|
|
215
|
+
case "after-pattern":
|
|
216
|
+
return patternCameFromFlag(args) ? [...positionals] : positionals.slice(1);
|
|
217
|
+
case "walk":
|
|
218
|
+
return walkTargets(args.length === 0 ? positionals : args);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
/** The last segment of a command word, so `/usr/bin/cat` reads as `cat`. */
|
|
222
|
+
function basenameOf(bin) {
|
|
223
|
+
const segments = segmentsOf(bin);
|
|
224
|
+
return segments[segments.length - 1] ?? bin;
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Read one target against the roots, as far as text alone can settle it.
|
|
228
|
+
*
|
|
229
|
+
* Absolute and inside a root: in scope. Absolute and outside every root: out of
|
|
230
|
+
* scope, decided here, no disk needed. Unreadable (a variable, a glob, a `~`):
|
|
231
|
+
* out of scope, because what it names is not in the text. Anything relative, or
|
|
232
|
+
* carrying a `..`, is `needs-disk`.
|
|
233
|
+
*/
|
|
234
|
+
export function readTargetVerdict(target, roots) {
|
|
235
|
+
if (target.length === 0)
|
|
236
|
+
return "needs-disk";
|
|
237
|
+
if (isUnreadableTarget(target))
|
|
238
|
+
return "out-of-scope";
|
|
239
|
+
if (!target.startsWith("/"))
|
|
240
|
+
return "needs-disk";
|
|
241
|
+
if (segmentsOf(target).includes(".."))
|
|
242
|
+
return "needs-disk";
|
|
243
|
+
return isInReadScope(target, roots) ? "in-scope" : "out-of-scope";
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* The roots, rendered for a human: `approval policy check`'s line and the
|
|
247
|
+
* hook's verdict note say the same sentence.
|
|
248
|
+
*/
|
|
249
|
+
export function renderReadRoots(roots) {
|
|
250
|
+
return roots.length === 0 ? "(none)" : roots.join(", ");
|
|
251
|
+
}
|
|
252
|
+
//# sourceMappingURL=read-scope.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"read-scope.js","sourceRoot":"","sources":["../../../src/core/read-scope.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Registration lookups shared by the gate, the daemon, and the doctor.
|
|
3
|
+
*
|
|
4
|
+
* Both exports are pure facts about the log and the Backlog.md layout that
|
|
5
|
+
* more than one layer needs. They lived in `daemon/` until APRV-59's layering
|
|
6
|
+
* guard (`tests/layering.test.ts`) made the rule explicit: `src/cli/` imports
|
|
7
|
+
* from `core/`, never from `daemon/`, so anything the CLI needs from the
|
|
8
|
+
* daemon's projection code moves here instead of widening the exception list.
|
|
9
|
+
*/
|
|
10
|
+
import type { EventRecord } from "./log.js";
|
|
11
|
+
/**
|
|
12
|
+
* Where Backlog.md keeps task files, relative to the project directory. The
|
|
13
|
+
* daemon watches it and doctor's `--tasks` defaults to it.
|
|
14
|
+
*/
|
|
15
|
+
export declare const DEFAULT_TASKS_DIR = "backlog/tasks";
|
|
16
|
+
/**
|
|
17
|
+
* The latest `task.registered` record for `task`, or `null`.
|
|
18
|
+
*
|
|
19
|
+
* `loose` matches the task id case-insensitively, for the one caller that has
|
|
20
|
+
* no frontmatter to read an id out of and must work from the Backlog.md file
|
|
21
|
+
* name (`task-3 - Slug.md` for a board key written `TASK-3`). It is a matching
|
|
22
|
+
* relaxation only: the record returned, and therefore the id every later step
|
|
23
|
+
* uses, is the log's, never the file name's.
|
|
24
|
+
*/
|
|
25
|
+
export declare function latestRegistration(records: EventRecord[], task: string, loose?: boolean): EventRecord | null;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The SQLite index projection (SPEC.md §9.2, `.approval/index.sqlite`).
|
|
3
|
+
*
|
|
4
|
+
* **The database is a cache; the log is the truth.** Everything in this module
|
|
5
|
+
* follows from that one sentence:
|
|
6
|
+
*
|
|
7
|
+
* - The index is *derived*. It is rebuilt from scratch on every call — there is
|
|
8
|
+
* no incremental path, no upsert, no "catch up from seq N". A projection that
|
|
9
|
+
* can drift is a second source of truth, and this project only has one.
|
|
10
|
+
* - Deleting `index.sqlite` loses nothing. Every byte in it is recomputable
|
|
11
|
+
* from `events.jsonl`; the file is a query surface, never a record.
|
|
12
|
+
* - Nothing here writes to the log. {@link reindex} opens `logPath` for reading
|
|
13
|
+
* only: it never appends, never truncates, never repairs a torn tail.
|
|
14
|
+
* - It refuses to index what it cannot vouch for. Chain verification (APRV-7)
|
|
15
|
+
* runs *first*, always. A corrupt log is refused outright; a torn tail is
|
|
16
|
+
* refused unless the caller explicitly opts in, and even then only the intact
|
|
17
|
+
* prefix is indexed and the truncation is recorded in the index metadata. An
|
|
18
|
+
* index that silently contains tampered rows is worse than no index at all.
|
|
19
|
+
*
|
|
20
|
+
* **Determinism.** The same log always produces the same index *content*: rows
|
|
21
|
+
* are inserted in `seq` order inside a single transaction, `payload` is stored
|
|
22
|
+
* as its RFC 8785 canonicalization (so key order cannot vary), and no clock,
|
|
23
|
+
* hostname, or random value is ever written. SQLite's internal file bytes are
|
|
24
|
+
* explicitly *not* claimed to be reproducible — page layout and freelist state
|
|
25
|
+
* are the engine's business. Equality is asserted at the SQL level: identical
|
|
26
|
+
* logs yield identical query results.
|
|
27
|
+
*
|
|
28
|
+
* **Crash safety.** The index is built at a temporary path in the *same*
|
|
29
|
+
* directory and `rename(2)`d over `indexPath` once complete. A reindex killed
|
|
30
|
+
* halfway therefore never leaves a half-built index where a reader expects a
|
|
31
|
+
* whole one: the previous index survives untouched, and the temp file is
|
|
32
|
+
* cleaned up.
|
|
33
|
+
*
|
|
34
|
+
* **Staleness** is the caller's call to make, not this module's. The index
|
|
35
|
+
* records the log head `(seq, hash)` it was built from; {@link indexHead} reads
|
|
36
|
+
* it back so a caller can compare it against a fresh {@link verify} head and
|
|
37
|
+
* decide whether to rebuild.
|
|
38
|
+
*
|
|
39
|
+
* Dependency note: `better-sqlite3`, exact-pinned. `node:sqlite` was ruled out —
|
|
40
|
+
* `engines.node` is `>=20` and `node:sqlite` does not exist before Node 22.5.
|
|
41
|
+
*/
|
|
42
|
+
import type { ValidateOptions } from "./validate.js";
|
|
43
|
+
import { type LogHead, type VerifyResult } from "./verify.js";
|
|
44
|
+
/**
|
|
45
|
+
* Version of the index's own table layout. Bumped whenever the SQL schema
|
|
46
|
+
* below changes shape, so a reader can tell an index it understands from one it
|
|
47
|
+
* does not. It is not the log's version: the log is versioned by SPEC.md.
|
|
48
|
+
*/
|
|
49
|
+
export declare const INDEX_SCHEMA_VERSION = 1;
|
|
50
|
+
/** Why a reindex was refused. Every failure is one of these, never a throw. */
|
|
51
|
+
export type ReindexErrorCode = "not-clean" | "torn-tail" | "io";
|
|
52
|
+
export interface ReindexError {
|
|
53
|
+
code: ReindexErrorCode;
|
|
54
|
+
message: string;
|
|
55
|
+
/** The verification result behind the refusal, when there is one. */
|
|
56
|
+
verify?: VerifyResult;
|
|
57
|
+
}
|
|
58
|
+
export type ReindexResult = {
|
|
59
|
+
ok: true;
|
|
60
|
+
/** Rows written to `events`. */
|
|
61
|
+
records: number;
|
|
62
|
+
/** The log head the index was built from; `null` for an empty log. */
|
|
63
|
+
head: LogHead | null;
|
|
64
|
+
/** True when only an intact prefix was indexed (forced torn tail). */
|
|
65
|
+
truncated: boolean;
|
|
66
|
+
} | {
|
|
67
|
+
ok: false;
|
|
68
|
+
error: ReindexError;
|
|
69
|
+
};
|
|
70
|
+
/** Options for {@link reindex}. */
|
|
71
|
+
export interface ReindexOptions extends ValidateOptions {
|
|
72
|
+
/**
|
|
73
|
+
* Index the intact prefix of a torn-tail log (records `1..intactThroughSeq`)
|
|
74
|
+
* instead of refusing. The truncation is recorded in `meta`. This never
|
|
75
|
+
* repairs the log — the torn line stays exactly where it is.
|
|
76
|
+
*/
|
|
77
|
+
force?: boolean;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Rebuild the SQLite index at `indexPath` from the log at `logPath`.
|
|
81
|
+
*
|
|
82
|
+
* Always verifies the chain first. Returns a structured result rather than
|
|
83
|
+
* throwing; on any refusal the existing index (if any) is left exactly as it
|
|
84
|
+
* was and no new file is created.
|
|
85
|
+
*/
|
|
86
|
+
export declare function reindex(logPath: string, indexPath: string, options?: ReindexOptions): ReindexResult;
|
|
87
|
+
/**
|
|
88
|
+
* The provenance recorded in an existing index: the log head it was built from,
|
|
89
|
+
* and whether it covers only an intact prefix.
|
|
90
|
+
*
|
|
91
|
+
* Returns `null` when the file is missing, unreadable, or carries no `meta`
|
|
92
|
+
* row — all of which mean the same thing to a caller: there is no index to
|
|
93
|
+
* trust, rebuild. Compare `head` against a fresh `verify(logPath).head` to
|
|
94
|
+
* detect staleness. Deleting `indexPath` loses nothing.
|
|
95
|
+
*/
|
|
96
|
+
export declare function indexHead(indexPath: string): {
|
|
97
|
+
head: LogHead | null;
|
|
98
|
+
truncated: boolean;
|
|
99
|
+
} | null;
|