toolroll 0.5.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 +21 -0
- package/README.md +1113 -0
- package/THIRD_PARTY_NOTICES.md +449 -0
- package/dist/accent-colors.d.ts +48 -0
- package/dist/accent-colors.js +122 -0
- package/dist/action-ledger.d.ts +26 -0
- package/dist/action-ledger.js +75 -0
- package/dist/agent-fence.d.ts +42 -0
- package/dist/agent-fence.js +183 -0
- package/dist/agentconfig.d.ts +216 -0
- package/dist/agentconfig.js +454 -0
- package/dist/api-tokens.d.ts +16 -0
- package/dist/api-tokens.js +27 -0
- package/dist/approval-policy.d.ts +69 -0
- package/dist/approval-policy.js +137 -0
- package/dist/approval-rules-ui.d.ts +25 -0
- package/dist/approval-rules-ui.js +39 -0
- package/dist/assignment-adapters.d.ts +180 -0
- package/dist/assignment-adapters.js +239 -0
- package/dist/assignment-brief.d.ts +71 -0
- package/dist/assignment-brief.js +129 -0
- package/dist/assignment-delivery.d.ts +56 -0
- package/dist/assignment-delivery.js +160 -0
- package/dist/assignment-presentation.d.ts +26 -0
- package/dist/assignment-presentation.js +80 -0
- package/dist/assignment-status.d.ts +62 -0
- package/dist/assignment-status.js +152 -0
- package/dist/assignment-ui.d.ts +71 -0
- package/dist/assignment-ui.js +103 -0
- package/dist/assignment.d.ts +222 -0
- package/dist/assignment.js +399 -0
- package/dist/attest.d.ts +56 -0
- package/dist/attest.js +153 -0
- package/dist/backend.d.ts +97 -0
- package/dist/backend.js +166 -0
- package/dist/backup-ui.d.ts +22 -0
- package/dist/backup-ui.js +59 -0
- package/dist/backup.d.ts +84 -0
- package/dist/backup.js +421 -0
- package/dist/beads.d.ts +34 -0
- package/dist/beads.js +135 -0
- package/dist/bin.d.ts +2 -0
- package/dist/bin.js +22 -0
- package/dist/board.d.ts +150 -0
- package/dist/board.js +210 -0
- package/dist/boot-identity.d.ts +63 -0
- package/dist/boot-identity.js +99 -0
- package/dist/browser/THIRD_PARTY_NOTICES.txt +2295 -0
- package/dist/browser/workspace.css +4 -0
- package/dist/browser/workspace.js +225 -0
- package/dist/browser-crew.d.ts +11 -0
- package/dist/browser-crew.js +50 -0
- package/dist/browser-shell.d.ts +8 -0
- package/dist/browser-shell.js +60 -0
- package/dist/browser-workspace.d.ts +780 -0
- package/dist/browser-workspace.js +47 -0
- package/dist/budget-alerts.d.ts +19 -0
- package/dist/budget-alerts.js +46 -0
- package/dist/builder.d.ts +370 -0
- package/dist/builder.js +3262 -0
- package/dist/capscan.d.ts +35 -0
- package/dist/capscan.js +113 -0
- package/dist/chat-acceptance.d.ts +71 -0
- package/dist/chat-acceptance.js +120 -0
- package/dist/chat-actions.d.ts +265 -0
- package/dist/chat-actions.js +1273 -0
- package/dist/chat-channel.d.ts +109 -0
- package/dist/chat-channel.js +652 -0
- package/dist/chat-continuity.d.ts +32 -0
- package/dist/chat-continuity.js +317 -0
- package/dist/chat-controls.d.ts +113 -0
- package/dist/chat-controls.js +53 -0
- package/dist/chat-delivery-state.d.ts +159 -0
- package/dist/chat-delivery-state.js +309 -0
- package/dist/chat-delivery.d.ts +38 -0
- package/dist/chat-delivery.js +693 -0
- package/dist/chat-display.d.ts +1 -0
- package/dist/chat-display.js +18 -0
- package/dist/chat-evidence.d.ts +123 -0
- package/dist/chat-evidence.js +207 -0
- package/dist/chat-flow.d.ts +44 -0
- package/dist/chat-flow.js +215 -0
- package/dist/chat-inbox.d.ts +57 -0
- package/dist/chat-inbox.js +123 -0
- package/dist/chat-polish.d.ts +16 -0
- package/dist/chat-polish.js +135 -0
- package/dist/chat-review.d.ts +46 -0
- package/dist/chat-review.js +98 -0
- package/dist/chat-rooms.d.ts +123 -0
- package/dist/chat-rooms.js +216 -0
- package/dist/chat-task-actions.d.ts +44 -0
- package/dist/chat-task-actions.js +79 -0
- package/dist/check-progress.d.ts +51 -0
- package/dist/check-progress.js +290 -0
- package/dist/child-database.d.ts +12 -0
- package/dist/child-database.js +30 -0
- package/dist/claim.d.ts +688 -0
- package/dist/claim.js +1740 -0
- package/dist/cli.d.ts +137 -0
- package/dist/cli.js +1461 -0
- package/dist/codex-limits.d.ts +15 -0
- package/dist/codex-limits.js +145 -0
- package/dist/coding-context.d.ts +55 -0
- package/dist/coding-context.js +92 -0
- package/dist/coding-handoff.d.ts +57 -0
- package/dist/coding-handoff.js +277 -0
- package/dist/coding-provider.d.ts +72 -0
- package/dist/coding-provider.js +424 -0
- package/dist/coding-shipping-ui.d.ts +4 -0
- package/dist/coding-shipping-ui.js +12 -0
- package/dist/coding-types.d.ts +62 -0
- package/dist/coding-types.js +1 -0
- package/dist/coding-ui.d.ts +17 -0
- package/dist/coding-ui.js +364 -0
- package/dist/coding-update.d.ts +15 -0
- package/dist/coding-update.js +149 -0
- package/dist/coding-workspace.d.ts +124 -0
- package/dist/coding-workspace.js +768 -0
- package/dist/container-state.d.ts +5 -0
- package/dist/container-state.js +62 -0
- package/dist/containment.d.ts +197 -0
- package/dist/containment.js +559 -0
- package/dist/contest.d.ts +318 -0
- package/dist/contest.js +753 -0
- package/dist/control-setup.d.ts +35 -0
- package/dist/control-setup.js +40 -0
- package/dist/control-ui.d.ts +29 -0
- package/dist/control-ui.js +53 -0
- package/dist/controller-service.d.ts +1 -0
- package/dist/controller-service.js +19 -0
- package/dist/controller-supervisor.d.ts +30 -0
- package/dist/controller-supervisor.js +104 -0
- package/dist/converse.d.ts +304 -0
- package/dist/converse.js +849 -0
- package/dist/coordinator-proposals.d.ts +29 -0
- package/dist/coordinator-proposals.js +137 -0
- package/dist/coordinator.d.ts +186 -0
- package/dist/coordinator.js +447 -0
- package/dist/credentials-ui.d.ts +25 -0
- package/dist/credentials-ui.js +48 -0
- package/dist/daemon.d.ts +209 -0
- package/dist/daemon.js +604 -0
- package/dist/decision.d.ts +120 -0
- package/dist/decision.js +388 -0
- package/dist/demo.d.ts +55 -0
- package/dist/demo.js +992 -0
- package/dist/desktop-access.d.ts +28 -0
- package/dist/desktop-access.js +88 -0
- package/dist/desktop-bundle.d.ts +16 -0
- package/dist/desktop-bundle.js +74 -0
- package/dist/desktop-host.d.ts +54 -0
- package/dist/desktop-host.js +508 -0
- package/dist/desktop-update-gate.d.ts +12 -0
- package/dist/desktop-update-gate.js +91 -0
- package/dist/desktop-update-recovery.d.ts +14 -0
- package/dist/desktop-update-recovery.js +243 -0
- package/dist/desktop-update.d.ts +110 -0
- package/dist/desktop-update.js +740 -0
- package/dist/discord-api.d.ts +20 -0
- package/dist/discord-api.js +138 -0
- package/dist/discord-chat.d.ts +16 -0
- package/dist/discord-chat.js +381 -0
- package/dist/discord-settings.d.ts +7 -0
- package/dist/discord-settings.js +75 -0
- package/dist/discord.d.ts +10 -0
- package/dist/discord.js +169 -0
- package/dist/discover.d.ts +75 -0
- package/dist/discover.js +150 -0
- package/dist/dispatch.d.ts +100 -0
- package/dist/dispatch.js +311 -0
- package/dist/dispose.d.ts +161 -0
- package/dist/dispose.js +803 -0
- package/dist/email-settings.d.ts +34 -0
- package/dist/email-settings.js +50 -0
- package/dist/envelope.d.ts +47 -0
- package/dist/envelope.js +105 -0
- package/dist/evidence-pack.d.ts +185 -0
- package/dist/evidence-pack.js +258 -0
- package/dist/evidence.d.ts +345 -0
- package/dist/evidence.js +782 -0
- package/dist/exec.d.ts +285 -0
- package/dist/exec.js +1841 -0
- package/dist/exhaustion.d.ts +85 -0
- package/dist/exhaustion.js +141 -0
- package/dist/export-ui.d.ts +4 -0
- package/dist/export-ui.js +17 -0
- package/dist/export.d.ts +48 -0
- package/dist/export.js +305 -0
- package/dist/flow-actions.d.ts +77 -0
- package/dist/flow-actions.js +203 -0
- package/dist/flow-code.d.ts +50 -0
- package/dist/flow-code.js +159 -0
- package/dist/flow-draft.d.ts +38 -0
- package/dist/flow-draft.js +80 -0
- package/dist/flow-engine.d.ts +66 -0
- package/dist/flow-engine.js +416 -0
- package/dist/flow-insights.d.ts +95 -0
- package/dist/flow-insights.js +149 -0
- package/dist/flow-live.d.ts +24 -0
- package/dist/flow-live.js +150 -0
- package/dist/flow-people.d.ts +24 -0
- package/dist/flow-people.js +91 -0
- package/dist/flow-replies.d.ts +48 -0
- package/dist/flow-replies.js +113 -0
- package/dist/flow-scripts.d.ts +38 -0
- package/dist/flow-scripts.js +73 -0
- package/dist/flow-secrets.d.ts +13 -0
- package/dist/flow-secrets.js +45 -0
- package/dist/flow-sort.d.ts +82 -0
- package/dist/flow-sort.js +153 -0
- package/dist/flow-steps.d.ts +40 -0
- package/dist/flow-steps.js +408 -0
- package/dist/flow-triggers.d.ts +244 -0
- package/dist/flow-triggers.js +959 -0
- package/dist/flows-ui.d.ts +20 -0
- package/dist/flows-ui.js +175 -0
- package/dist/flows.d.ts +229 -0
- package/dist/flows.js +968 -0
- package/dist/fonts.d.ts +19 -0
- package/dist/fonts.js +19 -0
- package/dist/gaps.d.ts +35 -0
- package/dist/gaps.js +101 -0
- package/dist/gate-failure.d.ts +40 -0
- package/dist/gate-failure.js +66 -0
- package/dist/git.d.ts +54 -0
- package/dist/git.js +94 -0
- package/dist/google-mail.d.ts +58 -0
- package/dist/google-mail.js +151 -0
- package/dist/grant.d.ts +136 -0
- package/dist/grant.js +238 -0
- package/dist/graph.d.ts +164 -0
- package/dist/graph.js +383 -0
- package/dist/guides.d.ts +22 -0
- package/dist/guides.js +363 -0
- package/dist/held.d.ts +150 -0
- package/dist/held.js +799 -0
- package/dist/invoke.d.ts +112 -0
- package/dist/invoke.js +780 -0
- package/dist/issues.d.ts +41 -0
- package/dist/issues.js +131 -0
- package/dist/job-object-helper.ps1 +252 -0
- package/dist/jsonl-discriminants.d.ts +11 -0
- package/dist/jsonl-discriminants.js +93 -0
- package/dist/keys.d.ts +104 -0
- package/dist/keys.js +229 -0
- package/dist/kits-ui.d.ts +17 -0
- package/dist/kits-ui.js +46 -0
- package/dist/kits.d.ts +86 -0
- package/dist/kits.js +215 -0
- package/dist/knowledge-cli.d.ts +101 -0
- package/dist/knowledge-cli.js +92 -0
- package/dist/knowledge-ui.d.ts +24 -0
- package/dist/knowledge-ui.js +40 -0
- package/dist/lead-context.d.ts +8 -0
- package/dist/lead-context.js +56 -0
- package/dist/lead-follow.d.ts +22 -0
- package/dist/lead-follow.js +167 -0
- package/dist/lead-status.d.ts +75 -0
- package/dist/lead-status.js +285 -0
- package/dist/ledger-chain.d.ts +46 -0
- package/dist/ledger-chain.js +187 -0
- package/dist/ledger-csv.d.ts +4 -0
- package/dist/ledger-csv.js +11 -0
- package/dist/ledger-view.d.ts +24 -0
- package/dist/ledger-view.js +58 -0
- package/dist/limits-ui.d.ts +26 -0
- package/dist/limits-ui.js +67 -0
- package/dist/link.d.ts +72 -0
- package/dist/link.js +217 -0
- package/dist/live.d.ts +96 -0
- package/dist/live.js +366 -0
- package/dist/liveness.d.ts +30 -0
- package/dist/liveness.js +42 -0
- package/dist/log.d.ts +7 -0
- package/dist/log.js +25 -0
- package/dist/mailbox.d.ts +57 -0
- package/dist/mailbox.js +150 -0
- package/dist/maintenance.d.ts +11 -0
- package/dist/maintenance.js +35 -0
- package/dist/mate-cli.d.ts +52 -0
- package/dist/mate-cli.js +345 -0
- package/dist/mate-contract.d.ts +10 -0
- package/dist/mate-contract.js +30 -0
- package/dist/mate-doors.d.ts +69 -0
- package/dist/mate-doors.js +548 -0
- package/dist/mate-progress.d.ts +29 -0
- package/dist/mate-progress.js +121 -0
- package/dist/mate-tools.d.ts +94 -0
- package/dist/mate-tools.js +1878 -0
- package/dist/mate.d.ts +92 -0
- package/dist/mate.js +435 -0
- package/dist/mcp-connect.d.ts +103 -0
- package/dist/mcp-connect.js +252 -0
- package/dist/mcp.d.ts +27 -0
- package/dist/mcp.js +651 -0
- package/dist/memory-cli.d.ts +466 -0
- package/dist/memory-cli.js +111 -0
- package/dist/memory-pass.d.ts +160 -0
- package/dist/memory-pass.js +409 -0
- package/dist/metrics.d.ts +3 -0
- package/dist/metrics.js +56 -0
- package/dist/mobile-viewport.d.ts +3 -0
- package/dist/mobile-viewport.js +37 -0
- package/dist/model-catalog.d.ts +118 -0
- package/dist/model-catalog.js +376 -0
- package/dist/models-cli.d.ts +102 -0
- package/dist/models-cli.js +66 -0
- package/dist/models-ui.d.ts +34 -0
- package/dist/models-ui.js +68 -0
- package/dist/modes.d.ts +80 -0
- package/dist/modes.js +182 -0
- package/dist/monitoring-settings.d.ts +44 -0
- package/dist/monitoring-settings.js +173 -0
- package/dist/monitoring-ui.d.ts +20 -0
- package/dist/monitoring-ui.js +51 -0
- package/dist/monitoring.d.ts +34 -0
- package/dist/monitoring.js +221 -0
- package/dist/names.d.ts +67 -0
- package/dist/names.js +102 -0
- package/dist/observations.d.ts +40 -0
- package/dist/observations.js +211 -0
- package/dist/oidc.d.ts +71 -0
- package/dist/oidc.js +182 -0
- package/dist/onboard.d.ts +108 -0
- package/dist/onboard.js +325 -0
- package/dist/openrouter-models.d.ts +30 -0
- package/dist/openrouter-models.js +120 -0
- package/dist/operate.d.ts +118 -0
- package/dist/operate.js +11321 -0
- package/dist/peek-cli.d.ts +97 -0
- package/dist/peek-cli.js +215 -0
- package/dist/peek.d.ts +134 -0
- package/dist/peek.js +578 -0
- package/dist/phase-routing.d.ts +307 -0
- package/dist/phase-routing.js +658 -0
- package/dist/plan-auto.d.ts +14 -0
- package/dist/plan-auto.js +92 -0
- package/dist/plan.d.ts +186 -0
- package/dist/plan.js +401 -0
- package/dist/planner-source.d.ts +251 -0
- package/dist/planner-source.js +460 -0
- package/dist/planner.d.ts +75 -0
- package/dist/planner.js +992 -0
- package/dist/policy-ui.d.ts +20 -0
- package/dist/policy-ui.js +41 -0
- package/dist/policy.d.ts +111 -0
- package/dist/policy.js +231 -0
- package/dist/prepared-evidence.d.ts +16 -0
- package/dist/prepared-evidence.js +96 -0
- package/dist/pricing.d.ts +45 -0
- package/dist/pricing.js +77 -0
- package/dist/principal.d.ts +42 -0
- package/dist/principal.js +82 -0
- package/dist/probe.d.ts +46 -0
- package/dist/probe.js +79 -0
- package/dist/process-custody.d.ts +17 -0
- package/dist/process-custody.js +91 -0
- package/dist/process-liveness.d.ts +10 -0
- package/dist/process-liveness.js +81 -0
- package/dist/process-recovery-anchor.d.ts +75 -0
- package/dist/process-recovery-anchor.js +188 -0
- package/dist/process-recovery-coalition.d.ts +25 -0
- package/dist/process-recovery-coalition.js +105 -0
- package/dist/process-recovery-eligibility.d.ts +178 -0
- package/dist/process-recovery-eligibility.js +170 -0
- package/dist/process-recovery-native.d.ts +172 -0
- package/dist/process-recovery-native.js +592 -0
- package/dist/process-recovery-provenance.d.ts +163 -0
- package/dist/process-recovery-provenance.js +292 -0
- package/dist/process-recovery-services.d.ts +56 -0
- package/dist/process-recovery-services.js +191 -0
- package/dist/process-recovery-settlement.d.ts +32 -0
- package/dist/process-recovery-settlement.js +132 -0
- package/dist/process-recovery.d.ts +41 -0
- package/dist/process-recovery.js +91 -0
- package/dist/process-tree.d.ts +41 -0
- package/dist/process-tree.js +264 -0
- package/dist/project-access.d.ts +14 -0
- package/dist/project-access.js +34 -0
- package/dist/project-cli.d.ts +18 -0
- package/dist/project-cli.js +104 -0
- package/dist/project-delete-ui.d.ts +22 -0
- package/dist/project-delete-ui.js +48 -0
- package/dist/project-delete.d.ts +55 -0
- package/dist/project-delete.js +413 -0
- package/dist/project-knowledge.d.ts +111 -0
- package/dist/project-knowledge.js +241 -0
- package/dist/project-learning.d.ts +100 -0
- package/dist/project-learning.js +438 -0
- package/dist/project-memory.d.ts +81 -0
- package/dist/project-memory.js +264 -0
- package/dist/project-skills.d.ts +152 -0
- package/dist/project-skills.js +660 -0
- package/dist/project-tools.d.ts +219 -0
- package/dist/project-tools.js +796 -0
- package/dist/project.d.ts +61 -0
- package/dist/project.js +125 -0
- package/dist/prompt.d.ts +22 -0
- package/dist/prompt.js +72 -0
- package/dist/proof.d.ts +513 -0
- package/dist/proof.js +1140 -0
- package/dist/proposal.d.ts +82 -0
- package/dist/proposal.js +210 -0
- package/dist/provider-connection.d.ts +22 -0
- package/dist/provider-connection.js +98 -0
- package/dist/provider-limits.d.ts +35 -0
- package/dist/provider-limits.js +95 -0
- package/dist/provider.d.ts +279 -0
- package/dist/provider.js +935 -0
- package/dist/publish.d.ts +107 -0
- package/dist/publish.js +650 -0
- package/dist/pulls.d.ts +118 -0
- package/dist/pulls.js +240 -0
- package/dist/push.d.ts +95 -0
- package/dist/push.js +353 -0
- package/dist/quality.d.ts +8 -0
- package/dist/quality.js +6 -0
- package/dist/recipe-ui.d.ts +18 -0
- package/dist/recipe-ui.js +152 -0
- package/dist/recipes.d.ts +81 -0
- package/dist/recipes.js +332 -0
- package/dist/remote.d.ts +67 -0
- package/dist/remote.js +122 -0
- package/dist/render.d.ts +64 -0
- package/dist/render.js +587 -0
- package/dist/report-summary.d.ts +13 -0
- package/dist/report-summary.js +15 -0
- package/dist/repos.d.ts +65 -0
- package/dist/repos.js +277 -0
- package/dist/repository-context-ui.d.ts +2 -0
- package/dist/repository-context-ui.js +10 -0
- package/dist/repository-context.d.ts +67 -0
- package/dist/repository-context.js +376 -0
- package/dist/restart-certification.d.ts +134 -0
- package/dist/restart-certification.js +226 -0
- package/dist/result-actions.d.ts +28 -0
- package/dist/result-actions.js +270 -0
- package/dist/result-completion.d.ts +18 -0
- package/dist/result-completion.js +66 -0
- package/dist/result-review.d.ts +213 -0
- package/dist/result-review.js +430 -0
- package/dist/retention-ui.d.ts +12 -0
- package/dist/retention-ui.js +39 -0
- package/dist/retention.d.ts +72 -0
- package/dist/retention.js +288 -0
- package/dist/review-context.d.ts +243 -0
- package/dist/review-context.js +1043 -0
- package/dist/review-evidence.d.ts +33 -0
- package/dist/review-evidence.js +56 -0
- package/dist/reviewer.d.ts +97 -0
- package/dist/reviewer.js +202 -0
- package/dist/routine.d.ts +228 -0
- package/dist/routine.js +764 -0
- package/dist/runner.d.ts +266 -0
- package/dist/runner.js +399 -0
- package/dist/scan.d.ts +32 -0
- package/dist/scan.js +92 -0
- package/dist/scope.d.ts +695 -0
- package/dist/scope.js +1176 -0
- package/dist/scout-report.d.ts +42 -0
- package/dist/scout-report.js +108 -0
- package/dist/scout.d.ts +69 -0
- package/dist/scout.js +351 -0
- package/dist/serve.d.ts +313 -0
- package/dist/serve.js +21776 -0
- package/dist/session-brief.d.ts +8 -0
- package/dist/session-brief.js +24 -0
- package/dist/session-cli.d.ts +13 -0
- package/dist/session-cli.js +284 -0
- package/dist/session-contract.d.ts +183 -0
- package/dist/session-contract.js +122 -0
- package/dist/session-http.d.ts +19 -0
- package/dist/session-http.js +154 -0
- package/dist/session-server.d.ts +12 -0
- package/dist/session-server.js +37 -0
- package/dist/session-service.d.ts +22 -0
- package/dist/session-service.js +157 -0
- package/dist/setup-guide.d.ts +39 -0
- package/dist/setup-guide.js +93 -0
- package/dist/sign-in-guard.d.ts +45 -0
- package/dist/sign-in-guard.js +74 -0
- package/dist/skills-ui.d.ts +11 -0
- package/dist/skills-ui.js +58 -0
- package/dist/skills.d.ts +97 -0
- package/dist/skills.js +276 -0
- package/dist/slack-api.d.ts +55 -0
- package/dist/slack-api.js +229 -0
- package/dist/slack-chat.d.ts +28 -0
- package/dist/slack-chat.js +413 -0
- package/dist/slack-settings.d.ts +7 -0
- package/dist/slack-settings.js +75 -0
- package/dist/slack-state.d.ts +13 -0
- package/dist/slack-state.js +9 -0
- package/dist/slack.d.ts +13 -0
- package/dist/slack.js +167 -0
- package/dist/spend-ui.d.ts +28 -0
- package/dist/spend-ui.js +95 -0
- package/dist/spend.d.ts +142 -0
- package/dist/spend.js +313 -0
- package/dist/sqlite-runtime.d.ts +3 -0
- package/dist/sqlite-runtime.js +6 -0
- package/dist/sso-settings.d.ts +33 -0
- package/dist/sso-settings.js +103 -0
- package/dist/sso-ui.d.ts +22 -0
- package/dist/sso-ui.js +39 -0
- package/dist/storage.d.ts +15 -0
- package/dist/storage.js +84 -0
- package/dist/store.d.ts +7754 -0
- package/dist/store.js +23279 -0
- package/dist/structured-output.d.ts +58 -0
- package/dist/structured-output.js +103 -0
- package/dist/style-asset.d.ts +7 -0
- package/dist/style-asset.js +41 -0
- package/dist/subscription-chat.d.ts +46 -0
- package/dist/subscription-chat.js +275 -0
- package/dist/summary.d.ts +33 -0
- package/dist/summary.js +71 -0
- package/dist/supervisor.mjs +297 -0
- package/dist/surface.d.ts +63 -0
- package/dist/surface.js +352 -0
- package/dist/sync.d.ts +94 -0
- package/dist/sync.js +279 -0
- package/dist/task-composer.d.ts +13 -0
- package/dist/task-composer.js +52 -0
- package/dist/task-control.d.ts +165 -0
- package/dist/task-control.js +214 -0
- package/dist/task-outcome-cli.d.ts +10 -0
- package/dist/task-outcome-cli.js +92 -0
- package/dist/task-text.d.ts +30 -0
- package/dist/task-text.js +41 -0
- package/dist/team-cli.d.ts +15 -0
- package/dist/team-cli.js +618 -0
- package/dist/team-contract.d.ts +92 -0
- package/dist/team-contract.js +1 -0
- package/dist/team-http.d.ts +17 -0
- package/dist/team-http.js +144 -0
- package/dist/team-leads.d.ts +81 -0
- package/dist/team-leads.js +565 -0
- package/dist/team-runtime.d.ts +27 -0
- package/dist/team-runtime.js +264 -0
- package/dist/team-ui.d.ts +3 -0
- package/dist/team-ui.js +9 -0
- package/dist/team-updates.d.ts +8 -0
- package/dist/team-updates.js +57 -0
- package/dist/teammate-admin.d.ts +53 -0
- package/dist/teammate-admin.js +111 -0
- package/dist/teammate-desk.d.ts +60 -0
- package/dist/teammate-desk.js +154 -0
- package/dist/teammate-memory.d.ts +64 -0
- package/dist/teammate-memory.js +196 -0
- package/dist/teammate-question.d.ts +59 -0
- package/dist/teammate-question.js +175 -0
- package/dist/teammate-tools.d.ts +108 -0
- package/dist/teammate-tools.js +271 -0
- package/dist/teammate-week.d.ts +68 -0
- package/dist/teammate-week.js +132 -0
- package/dist/teammate-work.d.ts +76 -0
- package/dist/teammate-work.js +280 -0
- package/dist/teammates-ui.d.ts +20 -0
- package/dist/teammates-ui.js +166 -0
- package/dist/teammates.d.ts +180 -0
- package/dist/teammates.js +253 -0
- package/dist/teams-api.d.ts +33 -0
- package/dist/teams-api.js +204 -0
- package/dist/teams-chat.d.ts +26 -0
- package/dist/teams-chat.js +215 -0
- package/dist/teams-settings.d.ts +12 -0
- package/dist/teams-settings.js +57 -0
- package/dist/teams.d.ts +21 -0
- package/dist/teams.js +124 -0
- package/dist/telegram-flow.d.ts +49 -0
- package/dist/telegram-flow.js +110 -0
- package/dist/telegram-mate.d.ts +137 -0
- package/dist/telegram-mate.js +707 -0
- package/dist/telegram-progress.d.ts +14 -0
- package/dist/telegram-progress.js +129 -0
- package/dist/telegram-settings.d.ts +9 -0
- package/dist/telegram-settings.js +36 -0
- package/dist/telegram-status.d.ts +75 -0
- package/dist/telegram-status.js +265 -0
- package/dist/telegram-team.d.ts +85 -0
- package/dist/telegram-team.js +359 -0
- package/dist/telegram.d.ts +230 -0
- package/dist/telegram.js +1931 -0
- package/dist/templates.d.ts +65 -0
- package/dist/templates.js +118 -0
- package/dist/tool-launcher.d.ts +1 -0
- package/dist/tool-launcher.js +101 -0
- package/dist/tools-ui.d.ts +30 -0
- package/dist/tools-ui.js +40 -0
- package/dist/transitions-recipes.d.ts +1 -0
- package/dist/transitions-recipes.js +203 -0
- package/dist/tree-proof.d.ts +34 -0
- package/dist/tree-proof.js +58 -0
- package/dist/verification-evidence.d.ts +44 -0
- package/dist/verification-evidence.js +238 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.js +10 -0
- package/dist/webhooks.d.ts +91 -0
- package/dist/webhooks.js +283 -0
- package/dist/work-index.d.ts +68 -0
- package/dist/work-index.js +384 -0
- package/dist/work-summary.d.ts +57 -0
- package/dist/work-summary.js +116 -0
- package/dist/workspace-motion.d.ts +4 -0
- package/dist/workspace-motion.js +133 -0
- package/dist/workspace-revision.d.ts +19 -0
- package/dist/workspace-revision.js +92 -0
- package/dist/workspace-ui.d.ts +204 -0
- package/dist/workspace-ui.js +411 -0
- package/dist/worktree-notices.d.ts +13 -0
- package/dist/worktree-notices.js +17 -0
- package/dist/worktree.d.ts +249 -0
- package/dist/worktree.js +720 -0
- package/package.json +122 -0
- package/scripts/canary-assertions.mjs +42 -0
- package/scripts/crash-canary.mjs +253 -0
- package/scripts/fixtures/crash-process.mjs +74 -0
- package/scripts/fixtures/pilot-scenarios.mjs +85 -0
- package/scripts/fixtures/restart-service.mjs +27 -0
- package/scripts/launchd-certification.mjs +143 -0
- package/scripts/pilot.mjs +121 -0
- package/scripts/proof-preflight.mjs +140 -0
- package/scripts/provider-canary.mjs +379 -0
- package/scripts/recovery-canary.mjs +135 -0
- package/scripts/restart-certification.mjs +98 -0
package/dist/routine.js
ADDED
|
@@ -0,0 +1,764 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Routines: standing orders that fire on a schedule (Phase C).
|
|
3
|
+
*
|
|
4
|
+
* A routine is a pre-approved template — goal, exclusions, touches,
|
|
5
|
+
* requirements, schedule, budget — whose approval is what makes every
|
|
6
|
+
* instance legitimately automatic. The template's digest covers EVERY term
|
|
7
|
+
* that constrains the standing order, not just the scope trio: a schedule
|
|
8
|
+
* moved from weekly to hourly, or a budget quietly removed, changes what the
|
|
9
|
+
* operator agreed to exactly as much as a rewritten goal would.
|
|
10
|
+
*
|
|
11
|
+
* The schedule algebra lives here, pure, because its two failure modes are
|
|
12
|
+
* subtle enough to deserve names (Codex planning/routines review, finding
|
|
13
|
+
* 10): advancing one interval after downtime leaves the next firing in the
|
|
14
|
+
* past — a catch-up burst; advancing to `now + interval` drifts the cadence
|
|
15
|
+
* forever. The rule is ALIGNED advancement: the next occurrence strictly
|
|
16
|
+
* after now, anchored to the occurrence that just fired (for `every`) or to
|
|
17
|
+
* the clock itself (for `daily`). Missed slots while nothing was running
|
|
18
|
+
* fire ONCE — the overdue occurrence — and are never backfilled.
|
|
19
|
+
*/
|
|
20
|
+
import { createHash } from "node:crypto";
|
|
21
|
+
import { hasForbiddenControls } from "./decision.js";
|
|
22
|
+
import { BUILT_IN, parseCapabilityKey } from "./store.js";
|
|
23
|
+
import { authenticateApprover, digestOf, profileDigestOf, parseAcceptanceCriteria, canonicalAcceptance, acceptanceWords } from "./scope.js";
|
|
24
|
+
import { reportsCost } from "./provider.js";
|
|
25
|
+
import { agentsSummary, legOf, postureWords, routeDigestOf, routeProblems } from "./phase-routing.js";
|
|
26
|
+
import { resolveRoutineAuthority } from "./agentconfig.js";
|
|
27
|
+
export const WEEKDAYS = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
|
|
28
|
+
export function validTimezone(value) {
|
|
29
|
+
if (value.trim() !== value)
|
|
30
|
+
return false;
|
|
31
|
+
if (!/^[A-Za-z0-9_+/-]{1,80}$/.test(value))
|
|
32
|
+
return false;
|
|
33
|
+
try {
|
|
34
|
+
new Intl.DateTimeFormat("en", { timeZone: value }).format(0);
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/** Bounds a person would pick on purpose: 5 minutes to 7 days. */
|
|
42
|
+
export const MIN_EVERY_MINUTES = 5;
|
|
43
|
+
export const MAX_EVERY_MINUTES = 7 * 24 * 60;
|
|
44
|
+
/**
|
|
45
|
+
* Existing daily schedules remain UTC. Calendar schedules may name an IANA
|
|
46
|
+
* timezone, whose wall-clock time stays fixed across daylight saving changes.
|
|
47
|
+
* No cron expressions in v1 — a schedule the operator cannot read at a
|
|
48
|
+
* glance is a schedule they cannot honestly approve.
|
|
49
|
+
*/
|
|
50
|
+
export function parseSchedule(text) {
|
|
51
|
+
if (text.trim() !== text)
|
|
52
|
+
return null;
|
|
53
|
+
const every = /^every:([0-9]{1,5})$/.exec(text);
|
|
54
|
+
if (every !== null) {
|
|
55
|
+
const minutes = Number(every[1]);
|
|
56
|
+
if (minutes < MIN_EVERY_MINUTES || minutes > MAX_EVERY_MINUTES)
|
|
57
|
+
return null;
|
|
58
|
+
return { kind: "every", minutes };
|
|
59
|
+
}
|
|
60
|
+
const calendar = /^(daily|weekdays|weekly:[0-6]):([01][0-9]|2[0-3]):([0-5][0-9])(?:@([A-Za-z0-9_+/-]{1,80}))?$/.exec(text);
|
|
61
|
+
if (calendar !== null) {
|
|
62
|
+
const timezone = calendar[4];
|
|
63
|
+
if (timezone !== undefined && !validTimezone(timezone))
|
|
64
|
+
return null;
|
|
65
|
+
const time = { hhmm: `${calendar[2]}:${calendar[3]}`, ...(timezone === undefined ? {} : { timezone }) };
|
|
66
|
+
return calendar[1] === "daily" ? { kind: "daily", ...time } : calendar[1] === "weekdays" ? { kind: "weekdays", ...time } : { kind: "weekly", day: Number(calendar[1].slice(-1)), ...time };
|
|
67
|
+
}
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
export function scheduleText(schedule) {
|
|
71
|
+
return schedule.kind === "every" ? `every:${schedule.minutes}` : `${schedule.kind === "weekly" ? `weekly:${schedule.day}` : schedule.kind}:${schedule.hhmm}${schedule.timezone === undefined ? "" : `@${schedule.timezone}`}`;
|
|
72
|
+
}
|
|
73
|
+
/** The schedule, in words an operator agrees to. */
|
|
74
|
+
export function describeSchedule(schedule) {
|
|
75
|
+
if (schedule.kind !== "every")
|
|
76
|
+
return `${schedule.kind === "daily" ? "daily" : schedule.kind === "weekdays" ? "weekdays" : `every ${WEEKDAYS[schedule.day]}`} at ${schedule.hhmm} ${schedule.timezone ?? "UTC"}`;
|
|
77
|
+
const { minutes } = schedule;
|
|
78
|
+
if (minutes % (24 * 60) === 0)
|
|
79
|
+
return `every ${minutes / (24 * 60)} day(s)`;
|
|
80
|
+
if (minutes % 60 === 0)
|
|
81
|
+
return `every ${minutes / 60} hour(s)`;
|
|
82
|
+
return `every ${minutes} minutes`;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* The first occurrence after approval: the schedule starts counting from the
|
|
86
|
+
* yes, because "approved at 14:07, every 60 minutes" firing instantly would
|
|
87
|
+
* spend before the approver's hand left the keyboard.
|
|
88
|
+
*/
|
|
89
|
+
export function firstFireAt(schedule, now) {
|
|
90
|
+
if (schedule.kind === "every") {
|
|
91
|
+
return new Date(now.getTime() + schedule.minutes * 60_000).toISOString();
|
|
92
|
+
}
|
|
93
|
+
return nextCalendar(schedule, now);
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Aligned advancement: the smallest cadence occurrence STRICTLY after now.
|
|
97
|
+
* `anchor` is the occurrence that just fired (or was just skipped) — the
|
|
98
|
+
* cadence grid grows from it, so a pass that ran late does not tilt every
|
|
99
|
+
* later firing by its lateness.
|
|
100
|
+
*/
|
|
101
|
+
export function nextFireAt(schedule, anchorIso, now) {
|
|
102
|
+
if (schedule.kind !== "every")
|
|
103
|
+
return nextCalendar(schedule, now);
|
|
104
|
+
const interval = schedule.minutes * 60_000;
|
|
105
|
+
const anchor = new Date(anchorIso).getTime();
|
|
106
|
+
const elapsed = now.getTime() - anchor;
|
|
107
|
+
// Strictly after now: an occurrence landing exactly on now already fired.
|
|
108
|
+
const steps = Math.max(1, Math.floor(elapsed / interval) + 1);
|
|
109
|
+
return new Date(anchor + steps * interval).toISOString();
|
|
110
|
+
}
|
|
111
|
+
function nextCalendar(schedule, now) {
|
|
112
|
+
const format = new Intl.DateTimeFormat("en-US", { timeZone: schedule.timezone ?? "UTC", year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", hourCycle: "h23" });
|
|
113
|
+
// Represent wall-clock components as a UTC number solely for arithmetic.
|
|
114
|
+
const wall = (instant) => {
|
|
115
|
+
const parts = Object.fromEntries(format.formatToParts(instant).map(part => [part.type, part.value]));
|
|
116
|
+
return Date.UTC(Number(parts.year), Number(parts.month) - 1, Number(parts.day), Number(parts.hour), Number(parts.minute));
|
|
117
|
+
};
|
|
118
|
+
const localNow = new Date(wall(now.getTime()));
|
|
119
|
+
const [hour, minute] = schedule.hhmm.split(":").map(Number);
|
|
120
|
+
const today = Date.UTC(localNow.getUTCFullYear(), localNow.getUTCMonth(), localNow.getUTCDate(), hour, minute);
|
|
121
|
+
for (let days = 0; days <= 15; days++) {
|
|
122
|
+
const target = today + days * 86_400_000;
|
|
123
|
+
if (schedule.kind === "weekly" && new Date(target).getUTCDay() !== schedule.day)
|
|
124
|
+
continue;
|
|
125
|
+
if (schedule.kind === "weekdays" && (new Date(target).getUTCDay() === 0 || new Date(target).getUTCDay() === 6))
|
|
126
|
+
continue;
|
|
127
|
+
// Nearby dates expose both offsets around a daylight-saving transition.
|
|
128
|
+
// A missing clock time skips its slot. An ambiguous time uses the first
|
|
129
|
+
// occurrence only, even if the scheduler advances during the repeated hour.
|
|
130
|
+
const offsets = [...new Set([-36, 0, 36].map(hours => { const sample = target + hours * 3_600_000; return wall(sample) - sample; }))];
|
|
131
|
+
const matches = offsets.map(offset => target - offset).filter(instant => wall(instant) === target);
|
|
132
|
+
if (matches.length > 0) {
|
|
133
|
+
const first = Math.min(...matches);
|
|
134
|
+
if (first > now.getTime())
|
|
135
|
+
return new Date(first).toISOString();
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
throw new Error("No calendar occurrence within the next fifteen days");
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* The exact standing order an operator agreed to, as 128 bits. Same shape
|
|
142
|
+
* and length as a scope digest, and for the same reason: approval binds to
|
|
143
|
+
* this value, and editing any term strands the old yes.
|
|
144
|
+
*/
|
|
145
|
+
export function routineDigestOf(terms, profile, route) {
|
|
146
|
+
return createHash("sha256")
|
|
147
|
+
.update(JSON.stringify({
|
|
148
|
+
repo: terms.repo,
|
|
149
|
+
goal: terms.goal.trim(),
|
|
150
|
+
outOfScope: terms.outOfScope?.trim() ?? null,
|
|
151
|
+
touches: [...terms.touches].sort(),
|
|
152
|
+
// v39: the same absent/[] equivalence a scope digest gives its
|
|
153
|
+
// rubric — a routine created before this migration, or one whose
|
|
154
|
+
// stored acceptance somehow reads back empty, digests unchanged.
|
|
155
|
+
...(terms.acceptance.length === 0 ? {} : { acceptance: canonicalAcceptance(terms.acceptance) }),
|
|
156
|
+
requirements: [...terms.requirements].sort(),
|
|
157
|
+
schedule: terms.schedule,
|
|
158
|
+
singleFlight: terms.singleFlight,
|
|
159
|
+
costCeilingUsd: terms.costCeilingUsd,
|
|
160
|
+
...(terms.budgetPerRunMicrousd == null ? {} : { budgetPerRun: terms.budgetPerRunMicrousd }),
|
|
161
|
+
// v24: absent and null identical — legacy routine digests keep
|
|
162
|
+
// their bytes; profile-bearing routines bind their routing.
|
|
163
|
+
...(profile == null ? {} : { profileDigest: profileDigestOf(profile) }),
|
|
164
|
+
// v48: the four-role agent route, under its own key — absent on a
|
|
165
|
+
// routine filed before routing froze (its digest bytes stand; it
|
|
166
|
+
// can no longer be approved or fired until filed again).
|
|
167
|
+
...(route == null ? {} : { route: routeDigestOf(route) }),
|
|
168
|
+
}), "utf8")
|
|
169
|
+
.digest("hex")
|
|
170
|
+
.slice(0, 32);
|
|
171
|
+
}
|
|
172
|
+
/** Instance ids are `<name>-YYYYMMDD-HHMM`, so the name must be an id stem. */
|
|
173
|
+
export const ROUTINE_NAME = /^[a-z0-9][a-z0-9-]{0,40}$/;
|
|
174
|
+
/**
|
|
175
|
+
* Fail closed on every field at once, like plan parsing: an operator fixing
|
|
176
|
+
* a routine definition should learn everything wrong in one round.
|
|
177
|
+
*/
|
|
178
|
+
export function validateRoutineTerms(terms, options = {}) {
|
|
179
|
+
const problems = [];
|
|
180
|
+
if (options.name !== undefined && !ROUTINE_NAME.test(options.name)) {
|
|
181
|
+
problems.push({ field: "name", problem: "a lowercase id stem — letters, digits, and dashes, at most 41 characters" });
|
|
182
|
+
}
|
|
183
|
+
if (terms.goal.trim() === "" || terms.goal.length > 2_000 || hasForbiddenControls(terms.goal)) {
|
|
184
|
+
problems.push({ field: "goal", problem: "required, at most 2000 characters, no control characters" });
|
|
185
|
+
}
|
|
186
|
+
if (terms.outOfScope !== null && (terms.outOfScope.length > 2_000 || hasForbiddenControls(terms.outOfScope))) {
|
|
187
|
+
problems.push({ field: "outOfScope", problem: "at most 2000 characters, no control characters" });
|
|
188
|
+
}
|
|
189
|
+
if (terms.touches.length > 50 || terms.touches.some(one => one.trim() === "" || one.length > 200 || hasForbiddenControls(one))) {
|
|
190
|
+
problems.push({ field: "touches", problem: "at most 50 paths, each non-empty and under 200 characters" });
|
|
191
|
+
}
|
|
192
|
+
if (terms.requirements.length > 20 || terms.requirements.some(one => parseCapabilityKey(one) === null)) {
|
|
193
|
+
problems.push({ field: "requirements", problem: "capability keys, `kind:name`, at most 20" });
|
|
194
|
+
}
|
|
195
|
+
// v39: this is the ONLY place a routine's rubric is authored — every
|
|
196
|
+
// firing copies it forward unchanged, never re-asks. A track that never
|
|
197
|
+
// gains one never gets past this validation — filed, migrated, or
|
|
198
|
+
// corrupted alike (final authority closure): an empty rubric is invalid
|
|
199
|
+
// wherever a stored row is read back, so a routine that predates
|
|
200
|
+
// rubrics (a migrated NULL acceptance_json) cannot refresh, take a yes,
|
|
201
|
+
// or fire `acceptance: []` until valid terms are filed again.
|
|
202
|
+
const acceptanceParse = parseAcceptanceCriteria(terms.acceptance);
|
|
203
|
+
if (acceptanceParse.problems.length > 0) {
|
|
204
|
+
problems.push({ field: "acceptance", problem: acceptanceParse.problems.map(p => p.message).join("; ") });
|
|
205
|
+
}
|
|
206
|
+
else if (acceptanceParse.criteria.length === 0) {
|
|
207
|
+
problems.push({ field: "acceptance", problem: "a standing order needs at least one signed acceptance criterion" });
|
|
208
|
+
}
|
|
209
|
+
if (parseSchedule(terms.schedule) === null) {
|
|
210
|
+
problems.push({
|
|
211
|
+
field: "schedule",
|
|
212
|
+
problem: `\`every:<minutes>\` (${MIN_EVERY_MINUTES}–${MAX_EVERY_MINUTES}), \`daily:<HH:MM>\`, or \`weekly:<0–6>:<HH:MM>\`, with an optional @IANA/timezone`,
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
if (terms.costCeilingUsd !== null && (!Number.isFinite(terms.costCeilingUsd) || terms.costCeilingUsd <= 0)) {
|
|
216
|
+
problems.push({ field: "costCeilingUsd", problem: "a positive dollar amount, or absent for no ceiling" });
|
|
217
|
+
}
|
|
218
|
+
if (terms.budgetPerRunMicrousd != null && (!Number.isSafeInteger(terms.budgetPerRunMicrousd) || terms.budgetPerRunMicrousd <= 0)) {
|
|
219
|
+
problems.push({ field: "budgetPerRunMicrousd", problem: "a positive whole micro-dollar amount, or absent for no per-run cap" });
|
|
220
|
+
}
|
|
221
|
+
// v1 is one-at-a-time, period: every approval surface SAYS so, and a
|
|
222
|
+
// stored false would make the ceremony describe behavior the firing does
|
|
223
|
+
// not have (Codex Phase C review, M4). The column stays for a future that
|
|
224
|
+
// designs the concurrent case; until then it is not accepted.
|
|
225
|
+
if (terms.singleFlight !== true) {
|
|
226
|
+
problems.push({ field: "singleFlight", problem: "v1 routines run one instance at a time — singleFlight must be true" });
|
|
227
|
+
}
|
|
228
|
+
return problems;
|
|
229
|
+
}
|
|
230
|
+
/** The stored row's terms, for re-proving the digest against what is actually there. */
|
|
231
|
+
export function termsOf(routine) {
|
|
232
|
+
return {
|
|
233
|
+
repo: routine.repo,
|
|
234
|
+
goal: routine.goal,
|
|
235
|
+
outOfScope: routine.outOfScope,
|
|
236
|
+
touches: routine.touches,
|
|
237
|
+
acceptance: routine.acceptance,
|
|
238
|
+
requirements: routine.requirements,
|
|
239
|
+
schedule: routine.schedule,
|
|
240
|
+
singleFlight: routine.singleFlight,
|
|
241
|
+
costCeilingUsd: routine.costCeilingUsd,
|
|
242
|
+
budgetPerRunMicrousd: routine.budgetPerRunMicrousd,
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
/** The instance task id: the routine's name stamped with its scheduled slot. */
|
|
246
|
+
export function instanceId(name, scheduledForIso) {
|
|
247
|
+
const stamp = scheduledForIso.replace(/[-:]/g, "").slice(0, 13).replace("T", "-");
|
|
248
|
+
return `${name}-${stamp}`;
|
|
249
|
+
}
|
|
250
|
+
/** The rolling budget window: seven days, and every surface says so. */
|
|
251
|
+
export const BUDGET_WINDOW_MS = 7 * 24 * 60 * 60_000;
|
|
252
|
+
/**
|
|
253
|
+
* A person agrees to the standing order — schedule, budget, and "each
|
|
254
|
+
* firing builds without asking" included, because the digest binds them.
|
|
255
|
+
* Same ceremony as a scope approval: credential re-proved inside the same
|
|
256
|
+
* transaction as the write, digest named rather than assumed.
|
|
257
|
+
*/
|
|
258
|
+
export function approveRoutine(store, routineId, by, now, sawDigest, token) {
|
|
259
|
+
return store.transact(() => {
|
|
260
|
+
const authenticated = authenticateApprover(store, by, token, store.getRoutine(routineId)?.repo ?? null);
|
|
261
|
+
if (!authenticated.ok)
|
|
262
|
+
return authenticated;
|
|
263
|
+
const routine = store.getRoutine(routineId);
|
|
264
|
+
if (routine === null)
|
|
265
|
+
return { ok: false, reason: "no-such-routine" };
|
|
266
|
+
if (sawDigest !== routine.digest)
|
|
267
|
+
return { ok: false, reason: "changed" };
|
|
268
|
+
// ONE projection gates the yes (v48 authority repair): a routine that cannot say exactly
|
|
269
|
+
// what would run is unapprovable (v24, same rule as scopes) —
|
|
270
|
+
// restatement is the road. Since v48 that means all four roles: a
|
|
271
|
+
// routine with no frozen route (filed before routing froze, or under a
|
|
272
|
+
// configuration that could not make one exact) cannot be agreed to
|
|
273
|
+
// until it is filed again. The digest is re-derived from the stored
|
|
274
|
+
// terms inside the projection, never trusted as a column (Codex Phase
|
|
275
|
+
// C review, H1): a row whose digest does not match its own terms is
|
|
276
|
+
// not something a person can meaningfully agree to.
|
|
277
|
+
const integrity = routineIntegrity(routine);
|
|
278
|
+
if (!integrity.agents.approvable) {
|
|
279
|
+
return { ok: false, reason: integrity.agents.state === "unverified" ? "changed" : "profile-unresolved" };
|
|
280
|
+
}
|
|
281
|
+
const schedule = parseSchedule(routine.schedule);
|
|
282
|
+
if (schedule === null)
|
|
283
|
+
return { ok: false, reason: "changed" };
|
|
284
|
+
// v102: a project that refuses the requester refuses the standing order's maker too (each firing is their task).
|
|
285
|
+
if (store.approvalRules(routine.repo).notRequester && routine.createdBy !== null && routine.createdBy === by) {
|
|
286
|
+
return { ok: false, reason: "requester" };
|
|
287
|
+
}
|
|
288
|
+
// The schedule starts counting from the yes — never retroactively.
|
|
289
|
+
store.stampRoutineApproval(routineId, by, routine.digest, firstFireAt(schedule, now), now);
|
|
290
|
+
return { ok: true, routine: store.getRoutine(routineId) };
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Fire one routine — or record, durably, why it did not.
|
|
295
|
+
*
|
|
296
|
+
* One store-owned transaction re-proves EVERYTHING before any write (Codex
|
|
297
|
+
* planning/routines review, finding 4): approval-digest equality, unpaused,
|
|
298
|
+
* due, single-flight, and budget are read inside the same BEGIN IMMEDIATE
|
|
299
|
+
* that creates the instance, so a template edited between an eligibility
|
|
300
|
+
* scan and this call simply refuses — the scan only nominates.
|
|
301
|
+
*
|
|
302
|
+
* The instance's scope is copied from the row proven approved HERE, and its
|
|
303
|
+
* approval identity is the template's: approvedBy/At come from the template
|
|
304
|
+
* stamp, and approvedDigest is the digest of the very terms copied in this
|
|
305
|
+
* transaction. No caller supplies approval fields; there is nothing to
|
|
306
|
+
* manufacture.
|
|
307
|
+
*
|
|
308
|
+
* A scheduled slot that cannot fire is SKIPPED ON THE LEDGER and the
|
|
309
|
+
* schedule advances past it (finding 10 — no backfill, no silent gap);
|
|
310
|
+
* blocked tracks page once per blocking fact (finding 9), and the episode
|
|
311
|
+
* resolves when a later firing succeeds. A manual run-now is different on
|
|
312
|
+
* exactly those points: it refuses to the person's face, records nothing,
|
|
313
|
+
* advances nothing, and pages nobody.
|
|
314
|
+
*/
|
|
315
|
+
/** A firing that must not stand: thrown INSIDE the fire transaction so
|
|
316
|
+
* every row it wrote rolls back, caught at the edge and returned as the
|
|
317
|
+
* outcome it names. Nothing half-fired survives. */
|
|
318
|
+
class FiringRolledBack extends Error {
|
|
319
|
+
outcome;
|
|
320
|
+
page;
|
|
321
|
+
constructor(outcome, page) {
|
|
322
|
+
super(outcome.detail ?? outcome.reason);
|
|
323
|
+
this.outcome = outcome;
|
|
324
|
+
this.page = page;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
export function fireRoutine(store, routineId, now, options = {}) {
|
|
328
|
+
const manual = options.manual === true;
|
|
329
|
+
try {
|
|
330
|
+
return fireRoutineInTransaction(store, routineId, now, manual);
|
|
331
|
+
}
|
|
332
|
+
catch (error) {
|
|
333
|
+
if (!(error instanceof FiringRolledBack))
|
|
334
|
+
throw error;
|
|
335
|
+
// The instance is gone with the transaction; the slot stays due. A
|
|
336
|
+
// scheduled firing pages once per blocking fact (its episode keys on
|
|
337
|
+
// the routine, so a later successful firing resolves it).
|
|
338
|
+
if (!manual && error.page !== null) {
|
|
339
|
+
store.enqueueRoutineEpisode(`routine-route:${routineId}`, { source: { project: store.getRoutine(routineId)?.repo ?? "" }, kind: "routine-blocked", pushClass: "attention", link: `/routines/${routineId}`, subject: error.page.subject, body: error.page.body }, now.toISOString(), now);
|
|
340
|
+
}
|
|
341
|
+
return error.outcome;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
function fireRoutineInTransaction(store, routineId, now, manual) {
|
|
345
|
+
return store.transact(() => {
|
|
346
|
+
const routine = store.getRoutine(routineId);
|
|
347
|
+
if (routine === null)
|
|
348
|
+
return { ok: false, reason: "no-such-routine" };
|
|
349
|
+
// Approval is proved THREE ways: a stamp exists, it matches the stored
|
|
350
|
+
// digest, and — because a digest column can be written by any store
|
|
351
|
+
// caller — the digest is re-derived from the stored terms themselves
|
|
352
|
+
// (Codex Phase C review, H1). Terms edited under a reused digest fire
|
|
353
|
+
// nothing, whatever the columns claim. ONE projection answers (v48 authority repair),
|
|
354
|
+
// read here before anything is written.
|
|
355
|
+
const integrity = routineIntegrity(routine);
|
|
356
|
+
if (!integrity.approved) {
|
|
357
|
+
// Not approved is a plain read: nothing is written, nothing is
|
|
358
|
+
// paged — terms that cannot be read exactly (raw authority repair)
|
|
359
|
+
// refuse here too, leaving the routine, its slots, ledger, tasks,
|
|
360
|
+
// notifications, and next-fire time exactly as they were.
|
|
361
|
+
return {
|
|
362
|
+
ok: false,
|
|
363
|
+
reason: "not-approved",
|
|
364
|
+
detail: routine.termsProblem != null
|
|
365
|
+
? `the stored terms cannot be read exactly (${routine.termsProblem}) — file this standing order again`
|
|
366
|
+
: integrity.agents.state === "unverified" && integrity.agents.problem !== null && integrity.agents.problem.startsWith("the stored terms are not ones this code files")
|
|
367
|
+
? integrity.agents.problem
|
|
368
|
+
: routine.approvedAt === null
|
|
369
|
+
? "nobody has agreed to this standing order"
|
|
370
|
+
: "the template was edited after approval — approve it again",
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
if (routine.paused)
|
|
374
|
+
return { ok: false, reason: "paused" };
|
|
375
|
+
const schedule = parseSchedule(routine.schedule);
|
|
376
|
+
if (schedule === null)
|
|
377
|
+
return { ok: false, reason: "bad-schedule" };
|
|
378
|
+
const occurredAt = manual ? now.toISOString() : routine.nextFireAt;
|
|
379
|
+
if (occurredAt === null || (!manual && occurredAt > now.toISOString())) {
|
|
380
|
+
return { ok: false, reason: "not-due" };
|
|
381
|
+
}
|
|
382
|
+
// THE FROZEN ROUTE (v48): the instance runs on exactly the four agents
|
|
383
|
+
// the approval sealed — planner, builder, repair, reviewer — copied
|
|
384
|
+
// from the approved snapshot, never recommended afresh. A routine whose
|
|
385
|
+
// approval predates routing (no sealed route) fires nothing: the slot
|
|
386
|
+
// stays due, the operator is paged once, and approving the standing
|
|
387
|
+
// order again — under agents it now names — is the road. No later
|
|
388
|
+
// `config set` can reach a firing.
|
|
389
|
+
// THE FROZEN SNAPSHOT IS RE-HASHED (v48, authority repair) by the same projection: the
|
|
390
|
+
// approved profile and the approved four-role route must read back,
|
|
391
|
+
// hash with the stored terms to the very digest the approver signed —
|
|
392
|
+
// a snapshot column rewritten after the yes fires nothing — carry no
|
|
393
|
+
// stated leg problem, agree with the sealed profile's build and
|
|
394
|
+
// repair pairs, and BE the working pair. Anything short of live
|
|
395
|
+
// refuses HERE — before the slot is read, before a stale pointer is
|
|
396
|
+
// healed, before a blocker is ledgered, before any instance, page, or
|
|
397
|
+
// next-fire time is written (v48 integrity): the refusal rides out of
|
|
398
|
+
// the transaction as a rollback, so the only thing a not-live firing
|
|
399
|
+
// leaves behind is the once-per-episode page at the edge. Two facts
|
|
400
|
+
// are said apart on the page: a snapshot never taken (approved before
|
|
401
|
+
// routing froze) and one whose bytes cannot be read (corrupt). The
|
|
402
|
+
// road is the same — refresh the agents, read them, approve again.
|
|
403
|
+
if (!integrity.live) {
|
|
404
|
+
const state = integrity.agents.state;
|
|
405
|
+
const subject = state === "unreadable"
|
|
406
|
+
? `${routine.name} needs filing again: its frozen agents cannot be read`
|
|
407
|
+
: state === "unfrozen"
|
|
408
|
+
? `${routine.name} needs approving again: its agents were never frozen`
|
|
409
|
+
: `${routine.name} needs approving again: its frozen agents do not verify`;
|
|
410
|
+
const body = state === "unreadable"
|
|
411
|
+
? `The agents this standing order's approval froze cannot be read back. Open it, refresh its agents from today's configuration, read them, and approve it again; until then its firings wait.`
|
|
412
|
+
: state === "unfrozen"
|
|
413
|
+
? `This standing order was approved before Toolroll froze which agents plan, build, repair, and review each firing. Open it, refresh its agents, read the agents it now names, and approve it again; until then its firings wait.`
|
|
414
|
+
: `The agents this standing order's approval froze do not verify (${integrity.liveProblem ?? "the frozen snapshot is not whole"}). Open it, refresh its agents from today's configuration, read them, and approve it again; until then its firings wait.`;
|
|
415
|
+
// A snapshot never taken (approved before routing froze) is a real
|
|
416
|
+
// state a person must act on, and it pages once at the edge. A
|
|
417
|
+
// snapshot whose bytes cannot be read or do not verify is CORRUPTION
|
|
418
|
+
// (atomic authority closure): scheduled or manual, it writes nothing
|
|
419
|
+
// at all — not a page — and the routine page says so in its words.
|
|
420
|
+
throw new FiringRolledBack({
|
|
421
|
+
ok: false,
|
|
422
|
+
reason: "route-unfrozen",
|
|
423
|
+
detail: state === "unreadable"
|
|
424
|
+
? "the agents this standing order's approval froze cannot be read — refresh its agents and approve it again to fire it"
|
|
425
|
+
: state === "unfrozen"
|
|
426
|
+
? "this standing order was approved before its agents were frozen — refresh its agents and approve it again to fire it"
|
|
427
|
+
: `${integrity.liveProblem ?? "the approved agents do not verify"} — refresh its agents and approve it again to fire it`,
|
|
428
|
+
}, state === "unfrozen" ? { subject, body } : null);
|
|
429
|
+
}
|
|
430
|
+
const frozenRoute = routine.approvedRoute;
|
|
431
|
+
const frozenProfile = routine.approvedProfile;
|
|
432
|
+
// A manual firing has its own ledger identity: it must never claim a
|
|
433
|
+
// scheduled slot's key, or a run-now landing exactly on the due instant
|
|
434
|
+
// would strand the schedule on that slot forever (Codex review, M1).
|
|
435
|
+
const slotKey = manual ? `manual:${occurredAt}` : occurredAt;
|
|
436
|
+
const scheduledFor = occurredAt;
|
|
437
|
+
// The slot check is a plain read because the whole function is one
|
|
438
|
+
// IMMEDIATE transaction — two passes finding the same due slot queue on
|
|
439
|
+
// the lock, and the second sees the first's row.
|
|
440
|
+
if (!store.routineSlotOpen(routineId, slotKey)) {
|
|
441
|
+
// A scheduled pointer aimed at an already-recorded slot is stale —
|
|
442
|
+
// heal it by advancing past the slot, or this refusal repeats forever.
|
|
443
|
+
if (!manual) {
|
|
444
|
+
store.setRoutineNextFire(routineId, nextFireAt(schedule, occurredAt, now), now);
|
|
445
|
+
}
|
|
446
|
+
return { ok: false, reason: "slot-taken" };
|
|
447
|
+
}
|
|
448
|
+
/** A due slot that cannot fire: ledger the skip, advance, page once. */
|
|
449
|
+
const skip = (reason, ledgerReason, page, detail) => {
|
|
450
|
+
if (manual)
|
|
451
|
+
return { ok: false, reason, detail };
|
|
452
|
+
store.recordRoutineFire({ routineId, scheduledFor: slotKey, outcome: "skipped", reason: ledgerReason, instanceTaskRef: null }, now);
|
|
453
|
+
store.setRoutineNextFire(routineId, nextFireAt(schedule, scheduledFor, now), now);
|
|
454
|
+
if (page !== null) {
|
|
455
|
+
// One page per blocking EPISODE: an open episode nags nobody twice,
|
|
456
|
+
// and a resolved one is history that must not suppress the next
|
|
457
|
+
// recurrence (Codex review, L1) — the slot stamp keys each afresh.
|
|
458
|
+
store.enqueueRoutineEpisode(page.prefix, { source: { project: routine.repo }, kind: "routine-blocked", subject: page.subject, body: page.body, pushClass: "attention", link: `/routines/${routineId}` }, scheduledFor, now);
|
|
459
|
+
}
|
|
460
|
+
return { ok: false, reason, detail };
|
|
461
|
+
};
|
|
462
|
+
// Single-flight is unconditional in v1 (Codex review, M4) — and a live
|
|
463
|
+
// claim blocks REGARDLESS of task state, so a state string written over
|
|
464
|
+
// a running build cannot conjure a twin beside it (H2).
|
|
465
|
+
const blocker = store.routineBlocker(routineId, now);
|
|
466
|
+
if (blocker !== null) {
|
|
467
|
+
return skip("single-flight", `single-flight:${blocker.taskId}`, {
|
|
468
|
+
prefix: `routine-singleflight:${routineId}:${blocker.taskId}`,
|
|
469
|
+
subject: `${routine.name} has stopped: ${blocker.taskId} is stuck`,
|
|
470
|
+
body: `The ${routine.name} track skipped its scheduled run because its last instance (${blocker.taskId}, ${blocker.state}) has not finished. The track stays stopped until that instance completes or is cancelled.`,
|
|
471
|
+
}, `instance ${blocker.taskId} (${blocker.state}) has not finished`);
|
|
472
|
+
}
|
|
473
|
+
const frozenBuild = legOf(frozenRoute, "build");
|
|
474
|
+
// The instance's agent IS the frozen build leg — never resolved from
|
|
475
|
+
// flags or configuration at fire time (Codex provider review, critical
|
|
476
|
+
// finding; v48 makes the whole route the pin).
|
|
477
|
+
const agentProvider = frozenBuild.provider;
|
|
478
|
+
// A ceiling against a provider that reports no dollars would fail
|
|
479
|
+
// closed on the SECOND firing anyway — refuse the first, say why, and
|
|
480
|
+
// skip the slot honestly (Codex provider review, Q6: re-proved at
|
|
481
|
+
// dispatch, not only at approval).
|
|
482
|
+
if (routine.costCeilingUsd !== null && !reportsCost(agentProvider)) {
|
|
483
|
+
return skip("unmeasured", `unmeasured-provider:${agentProvider}`, {
|
|
484
|
+
prefix: `routine-unmeasured:${routineId}`,
|
|
485
|
+
subject: `${routine.name} is blocked: ${agentProvider} cannot honor a cost ceiling`,
|
|
486
|
+
body: `The track's $${routine.costCeilingUsd.toFixed(2)} ceiling needs a provider that reports dollar cost, and ${agentProvider} does not. Drop the ceiling, or run the track on claude.`,
|
|
487
|
+
}, `${agentProvider} reports no dollar cost against a $${routine.costCeilingUsd.toFixed(2)} ceiling`);
|
|
488
|
+
}
|
|
489
|
+
if (routine.costCeilingUsd !== null) {
|
|
490
|
+
const spend = store.routineSpend(routineId, new Date(now.getTime() - BUDGET_WINDOW_MS).toISOString());
|
|
491
|
+
// Fail closed (finding 5): a paid run whose cost never landed is not
|
|
492
|
+
// headroom — it is an unknown liability, and it blocks the track.
|
|
493
|
+
if (spend.unmeasuredRuns > 0) {
|
|
494
|
+
return skip("unmeasured", "unmeasured", {
|
|
495
|
+
prefix: `routine-unmeasured:${routineId}`,
|
|
496
|
+
subject: `${routine.name} is blocked: spend is unmeasured`,
|
|
497
|
+
body: `${spend.unmeasuredRuns} paid run(s) in the last 7 days recorded no cost, so the $${routine.costCeilingUsd.toFixed(2)} ceiling cannot be honestly enforced. The track skips its firings until the window rolls past them.`,
|
|
498
|
+
}, `${spend.unmeasuredRuns} paid run(s) with no recorded cost`);
|
|
499
|
+
}
|
|
500
|
+
if (spend.costUsd >= routine.costCeilingUsd) {
|
|
501
|
+
return skip("budget", "budget", {
|
|
502
|
+
prefix: `routine-budget:${routineId}`,
|
|
503
|
+
subject: `${routine.name} hit its budget`,
|
|
504
|
+
body: `Instances spent $${spend.costUsd.toFixed(2)} of the $${routine.costCeilingUsd.toFixed(2)} ceiling in the last 7 days. Firings skip until the window rolls; raise the ceiling to resume sooner (that edit voids the approval, on purpose).`,
|
|
505
|
+
}, `$${spend.costUsd.toFixed(2)} spent of $${routine.costCeilingUsd.toFixed(2)}`);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
// All proofs held: spawn the instance. Uniquified inside this same
|
|
509
|
+
// transaction; the ledger row makes a same-slot twin impossible anyway.
|
|
510
|
+
let taskId = instanceId(routine.name, scheduledFor);
|
|
511
|
+
for (let n = 2; store.getTask(taskId) !== null; n++) {
|
|
512
|
+
taskId = `${instanceId(routine.name, scheduledFor)}-${n}`;
|
|
513
|
+
}
|
|
514
|
+
const title = `${routine.name} · ${scheduledFor.slice(0, 16).replace("T", " ")} UTC`;
|
|
515
|
+
store.createTask({ id: taskId, title, filedBy: routine.createdBy === null ? { name: null, kind: "automation" } : { name: routine.createdBy, kind: "person" } }, now);
|
|
516
|
+
const ref = store.refFor(BUILT_IN, taskId, "ours");
|
|
517
|
+
// Placement BEFORE the scope exists — placeTask refuses to move scoped
|
|
518
|
+
// work, and this ordering is what keeps that guard out of the way here.
|
|
519
|
+
store.placeTask(ref.id, routine.repo);
|
|
520
|
+
store.linkRoutineInstance(ref.id, routine.id);
|
|
521
|
+
// The pin: this instance builds on exactly this agent, whatever flags a
|
|
522
|
+
// later pass carries — the routine's APPROVED profile when one was
|
|
523
|
+
// sealed (v47: the pin and the profile are one exact authority), else
|
|
524
|
+
// the resolved agent.
|
|
525
|
+
store.pinTaskAgent(ref.id, frozenProfile.provider, frozenProfile.model);
|
|
526
|
+
if (routine.requirements.length > 0) {
|
|
527
|
+
store.setRequirements(ref.id, routine.requirements);
|
|
528
|
+
}
|
|
529
|
+
// The instance scope: the template's terms, byte for byte, stamped
|
|
530
|
+
// approved by the person who approved the template (finding 4). The
|
|
531
|
+
// digest is computed from the copied terms IN this transaction, so the
|
|
532
|
+
// approval can only ever cover exactly what was proven approved above.
|
|
533
|
+
const draft = {
|
|
534
|
+
goal: routine.goal,
|
|
535
|
+
outOfScope: routine.outOfScope,
|
|
536
|
+
touches: [...routine.touches],
|
|
537
|
+
acceptance: [...routine.acceptance],
|
|
538
|
+
...(routine.budgetPerRunMicrousd == null ? {} : { budgetMicrousd: routine.budgetPerRunMicrousd }),
|
|
539
|
+
};
|
|
540
|
+
// v24 (foundations 3d): the instance's profile is the routine's
|
|
541
|
+
// APPROVED snapshot — never fresh resolution. The instance digest
|
|
542
|
+
// binds it, the approval stamp covers it, and saveScope is handed the
|
|
543
|
+
// profile EXPLICITLY so its own resolution never runs here.
|
|
544
|
+
// v48: the frozen ROUTE rides beside it, verbatim — saveScope files it
|
|
545
|
+
// instead of recommending one from today's configuration, and the
|
|
546
|
+
// instance digest binds both, so the seal below covers exactly the
|
|
547
|
+
// four agents the routine's approver agreed to.
|
|
548
|
+
const instanceProfile = frozenProfile;
|
|
549
|
+
const instanceDigest = digestOf(draft, instanceProfile, frozenRoute);
|
|
550
|
+
store.saveScope({
|
|
551
|
+
taskId,
|
|
552
|
+
...draft,
|
|
553
|
+
proposedAt: now.toISOString(),
|
|
554
|
+
digest: instanceDigest,
|
|
555
|
+
budgetMicrousd: routine.budgetPerRunMicrousd ?? null,
|
|
556
|
+
approvedAt: routine.approvedAt,
|
|
557
|
+
approvedBy: routine.approvedBy,
|
|
558
|
+
approvedDigest: instanceDigest,
|
|
559
|
+
}, {}, { profile: instanceProfile, route: frozenRoute });
|
|
560
|
+
// THE SEAL, OR NOTHING (v48): the instance's approval is real only once
|
|
561
|
+
// the seal copies the frozen route and profile into the approved
|
|
562
|
+
// snapshot and that snapshot reads back as the sealed route. A seal the
|
|
563
|
+
// store refuses (an unreadable route, a profile that disagrees) rolls
|
|
564
|
+
// the whole firing back — no instance, no ledger row, no advanced slot
|
|
565
|
+
// — and says why.
|
|
566
|
+
// v102: a firing is sealed by the standing order's approval, not a person voting now — protected work waits for two people.
|
|
567
|
+
const sealed = store.sealScopeApproval(taskId, routine.approvedBy ?? "routine", now, {}, undefined, "automation");
|
|
568
|
+
const sealedRoute = sealed ? store.sealedRouteOf(taskId) : null;
|
|
569
|
+
if (!sealed || sealedRoute === null || !sealedRoute.ok || routeDigestOf(sealedRoute.route) !== routeDigestOf(frozenRoute)) {
|
|
570
|
+
const why = !sealed ? "the store refused to seal the instance's approval" : sealedRoute !== null && !sealedRoute.ok ? sealedRoute.detail : "the sealed route is not the frozen route";
|
|
571
|
+
throw new FiringRolledBack({ ok: false, reason: "route-unfrozen", detail: `the instance could not be sealed under the frozen agents (${why}) — nothing fired; file the standing order again` }, { subject: `${routine.name} could not fire: its frozen agents did not seal`, body: `The firing was rolled back because ${why}. Open the standing order, refresh its agents, and approve it again; until then its firings wait.` });
|
|
572
|
+
}
|
|
573
|
+
store.recordRoutineFire({ routineId, scheduledFor: slotKey, outcome: "fired", reason: manual ? "manual" : null, instanceTaskRef: ref.id }, now);
|
|
574
|
+
if (!manual) {
|
|
575
|
+
store.setRoutineNextFire(routineId, nextFireAt(schedule, scheduledFor, now), now);
|
|
576
|
+
}
|
|
577
|
+
// Success is the proof the blockers are gone; their episodes resolve.
|
|
578
|
+
store.resolveRoutineEpisodes(routineId, now);
|
|
579
|
+
return { ok: true, taskId, scheduledFor };
|
|
580
|
+
});
|
|
581
|
+
}
|
|
582
|
+
export function routineIntegrity(routine) {
|
|
583
|
+
// THE RAW TERMS FIRST (raw authority repair): a row whose stored terms
|
|
584
|
+
// do not read back exactly — a touch or requirement that is not a
|
|
585
|
+
// string, a rubric entry with a key this code never writes or one that
|
|
586
|
+
// does not parse, a flag or ceiling that is not the shape it was written
|
|
587
|
+
// as — is NOT approved, not live, not approvable, and not refreshable,
|
|
588
|
+
// whatever its columns say: the filtered reading of it must never hash,
|
|
589
|
+
// fire, or be re-filed as if it were the terms the approver read.
|
|
590
|
+
if (routine.termsProblem != null) {
|
|
591
|
+
const problem = `the stored terms cannot be read exactly (${routine.termsProblem}) — file this standing order again`;
|
|
592
|
+
return { approved: false, live: false, liveProblem: null, agents: { state: "unverified", approvable: false, refresh: false, problem } };
|
|
593
|
+
}
|
|
594
|
+
const terms = termsOf(routine);
|
|
595
|
+
// THE SEMANTIC TERMS NEXT (atomic authority closure): terms that read
|
|
596
|
+
// back exactly can still say something this code never files — a
|
|
597
|
+
// single-flight flag of 0, a cost ceiling of −1, a schedule no parser
|
|
598
|
+
// holds, an empty rubric. Every filing door validates these; the
|
|
599
|
+
// projection validates them AGAIN so a rehashed row is never approvable
|
|
600
|
+
// or live on terms the filing door would have refused.
|
|
601
|
+
const semantic = validateRoutineTerms(terms, { name: routine.name });
|
|
602
|
+
if (semantic.length > 0) {
|
|
603
|
+
const problem = `the stored terms are not ones this code files (${semantic.map(one => `${one.field}: ${one.problem}`).join("; ")}) — file this standing order again`;
|
|
604
|
+
return { approved: false, live: false, liveProblem: null, agents: { state: "unverified", approvable: false, refresh: false, problem } };
|
|
605
|
+
}
|
|
606
|
+
const stamped = routine.approvedAt !== null && routine.approvedDigest !== null && routine.approvedDigest === routine.digest;
|
|
607
|
+
const workingRehashes = routineDigestOf(terms, routine.profile ?? null, routine.route ?? null) === routine.digest;
|
|
608
|
+
const approved = stamped && workingRehashes;
|
|
609
|
+
const closed = (state, problem, refresh = true) => ({
|
|
610
|
+
approved,
|
|
611
|
+
live: false,
|
|
612
|
+
liveProblem: approved ? problem : null,
|
|
613
|
+
agents: { state, approvable: false, refresh, problem },
|
|
614
|
+
});
|
|
615
|
+
if (approved) {
|
|
616
|
+
if (routine.approvedRouteUnreadable === true || routine.approvedProfileUnreadable === true) {
|
|
617
|
+
return closed("unreadable", "the agents this approval froze cannot be read back");
|
|
618
|
+
}
|
|
619
|
+
const route = routine.approvedRoute ?? null;
|
|
620
|
+
const profile = routine.approvedProfile ?? null;
|
|
621
|
+
if (route === null || profile === null)
|
|
622
|
+
return closed("unfrozen", "approved before agents were frozen");
|
|
623
|
+
if (routineDigestOf(terms, profile, route) !== routine.approvedDigest) {
|
|
624
|
+
return closed("unverified", "the approved agents do not hash to the approval this standing order carries");
|
|
625
|
+
}
|
|
626
|
+
// WORKING/APPROVED PARITY (v48 integrity): the approval stands on the
|
|
627
|
+
// stored digest, and that digest binds the WORKING route and profile —
|
|
628
|
+
// so the frozen snapshot must BE the working one, byte for byte. A
|
|
629
|
+
// frozen pair that hashes to the approval while the working pair says
|
|
630
|
+
// something else is a row two authorities wrote; nothing fires on it.
|
|
631
|
+
if (routine.routeUnreadable === true || routine.route == null || routine.profile == null) {
|
|
632
|
+
return closed("unverified", "the approved agents are frozen but the working agents cannot be read back");
|
|
633
|
+
}
|
|
634
|
+
if (routeDigestOf(routine.route) !== routeDigestOf(route) || profileDigestOf(routine.profile) !== profileDigestOf(profile)) {
|
|
635
|
+
return closed("unverified", "the approved agents are not the working agents this standing order files");
|
|
636
|
+
}
|
|
637
|
+
const problems = routeProblems(route);
|
|
638
|
+
if (problems.length > 0)
|
|
639
|
+
return closed("unresolved", problems.join("; "));
|
|
640
|
+
const build = legOf(route, "build");
|
|
641
|
+
const repair = legOf(route, "repair");
|
|
642
|
+
const repairModel = profile.repairModel === "inherit" ? profile.model : profile.repairModel;
|
|
643
|
+
if (build.provider !== profile.provider || build.model !== profile.model || repair.provider !== profile.provider || repair.model !== repairModel) {
|
|
644
|
+
return closed("unverified", `the approved agents (${profile.provider} · ${profile.model}, repair ${profile.provider} · ${repairModel}) disagree with the approved route (${build.provider} · ${build.model}, repair ${repair.provider} · ${repair.model})`);
|
|
645
|
+
}
|
|
646
|
+
return { approved: true, live: true, liveProblem: null, agents: { state: "frozen", approvable: false, refresh: false, problem: null } };
|
|
647
|
+
}
|
|
648
|
+
if (routine.routeUnreadable === true)
|
|
649
|
+
return closed("unreadable", "the filed agents cannot be read back");
|
|
650
|
+
const route = routine.route ?? null;
|
|
651
|
+
const profile = routine.profile ?? null;
|
|
652
|
+
if (route === null || profile === null)
|
|
653
|
+
return closed("unresolved", "this standing order does not name an exact agent for every role");
|
|
654
|
+
const problems = routeProblems(route);
|
|
655
|
+
if (problems.length > 0)
|
|
656
|
+
return closed("unresolved", problems.join("; "));
|
|
657
|
+
if (!workingRehashes)
|
|
658
|
+
return closed("unverified", "the filed terms do not hash to this standing order's reference — file it again");
|
|
659
|
+
// BUILD/REPAIR PARITY BEFORE THE YES (atomic authority closure): the
|
|
660
|
+
// pending pair must agree exactly as the approved pair must — a working
|
|
661
|
+
// profile of claude · sonnet beside a rehashed route that builds on
|
|
662
|
+
// codex is a row two authorities wrote, and no yes lands on it.
|
|
663
|
+
const build = legOf(route, "build");
|
|
664
|
+
const repair = legOf(route, "repair");
|
|
665
|
+
const repairModel = profile.repairModel === "inherit" ? profile.model : profile.repairModel;
|
|
666
|
+
if (build.provider !== profile.provider || build.model !== profile.model || repair.provider !== profile.provider || repair.model !== repairModel) {
|
|
667
|
+
return closed("unverified", `the filed agents (${profile.provider} · ${profile.model}, repair ${profile.provider} · ${repairModel}) disagree with the filed route (${build.provider} · ${build.model}, repair ${repair.provider} · ${repair.model}) — refresh the agents and file again`);
|
|
668
|
+
}
|
|
669
|
+
return { approved: false, live: false, liveProblem: null, agents: { state: "pending", approvable: true, refresh: false, problem: null } };
|
|
670
|
+
}
|
|
671
|
+
/** The agents word every surface uses — one reading of the projection. */
|
|
672
|
+
export function routineAgentsState(routine) {
|
|
673
|
+
return routineIntegrity(routine).agents;
|
|
674
|
+
}
|
|
675
|
+
/**
|
|
676
|
+
* THE RECOVERY ROAD (v48): re-resolve a standing order's agents — the
|
|
677
|
+
* four-role route and the profile that restates its build and repair legs
|
|
678
|
+
* — from today's configuration, and file them as the order's WORKING
|
|
679
|
+
* agents under a digest that binds them. Nothing is approved here: a
|
|
680
|
+
* refreshed order reads "edited — approve again", the approver reads the
|
|
681
|
+
* exact agents it now names and agrees to them with the password, and
|
|
682
|
+
* only that yes freezes the snapshot a firing copies. A routine whose
|
|
683
|
+
* terms already bind these exact agents is left byte-for-byte alone.
|
|
684
|
+
* A configuration that still cannot name an exact, runnable agent for
|
|
685
|
+
* every role answers with the words and changes nothing.
|
|
686
|
+
*/
|
|
687
|
+
export function refreshRoutineAgents(store, routineId, now) {
|
|
688
|
+
return store.transact(() => {
|
|
689
|
+
const routine = store.getRoutine(routineId);
|
|
690
|
+
if (routine === null)
|
|
691
|
+
return { ok: false, reason: "no-such-routine", problem: "no such routine" };
|
|
692
|
+
// Terms that do not read back exactly are never re-filed from their
|
|
693
|
+
// filtered reading (raw authority repair): the refresh refuses in
|
|
694
|
+
// words and writes nothing.
|
|
695
|
+
if (routine.termsProblem != null) {
|
|
696
|
+
return { ok: false, reason: "unresolved", problem: `the stored terms cannot be read exactly (${routine.termsProblem}) — file this standing order again` };
|
|
697
|
+
}
|
|
698
|
+
// Terms this code never files (a single-flight flag of 0, a negative
|
|
699
|
+
// ceiling) are not re-filed either (atomic authority closure): the
|
|
700
|
+
// refresh cannot mend terms, and rehashing them would only launder
|
|
701
|
+
// the corruption into a digest a person could sign.
|
|
702
|
+
const semantic = validateRoutineTerms(termsOf(routine), { name: routine.name });
|
|
703
|
+
if (semantic.length > 0) {
|
|
704
|
+
return { ok: false, reason: "unresolved", problem: `the stored terms are not ones this code files (${semantic.map(one => `${one.field}: ${one.problem}`).join("; ")}) — file this standing order again` };
|
|
705
|
+
}
|
|
706
|
+
const authority = resolveRoutineAuthority(store, routine.repo, routine.acceptance, now);
|
|
707
|
+
if (!authority.ok)
|
|
708
|
+
return { ok: false, reason: "unresolved", problem: authority.problem };
|
|
709
|
+
const terms = termsOf(routine);
|
|
710
|
+
const digest = routineDigestOf(terms, authority.profile, authority.route);
|
|
711
|
+
// The projection is read BEFORE any write (v48 authority repair): an approval that is
|
|
712
|
+
// not live — never frozen, unreadable, or a snapshot that no longer
|
|
713
|
+
// verifies — is WITHDRAWN here, even when the working agents already
|
|
714
|
+
// bind these exact terms, so a corrupt frozen snapshot can never keep
|
|
715
|
+
// reading as approved. A live approval under unchanged terms is left
|
|
716
|
+
// byte-for-byte alone.
|
|
717
|
+
const integrity = routineIntegrity(routine);
|
|
718
|
+
const unchanged = digest === routine.digest && routine.route != null && routine.profile != null && !routine.routeUnreadable;
|
|
719
|
+
if (unchanged && (integrity.live || !integrity.approved) && integrity.agents.state !== "unverified") {
|
|
720
|
+
return { ok: true, routine, changed: false };
|
|
721
|
+
}
|
|
722
|
+
if (integrity.approved && !integrity.live)
|
|
723
|
+
store.withdrawRoutineApproval(routineId, now);
|
|
724
|
+
if (!unchanged || integrity.agents.state === "unverified") {
|
|
725
|
+
store.updateRoutineTerms(routineId, { ...terms, digest, profile: authority.profile, route: authority.route }, now);
|
|
726
|
+
}
|
|
727
|
+
return { ok: true, routine: store.getRoutine(routineId), changed: true };
|
|
728
|
+
});
|
|
729
|
+
}
|
|
730
|
+
/** The standing order, in the words an operator has to agree to. */
|
|
731
|
+
export function describeRoutine(routine) {
|
|
732
|
+
const schedule = parseSchedule(routine.schedule);
|
|
733
|
+
return [
|
|
734
|
+
` goal ${routine.goal}`,
|
|
735
|
+
...(routine.outOfScope === null ? [] : [` not this ${routine.outOfScope}`]),
|
|
736
|
+
...(routine.touches.length === 0 ? [] : [` touches ${routine.touches.join(", ")}`]),
|
|
737
|
+
...acceptanceWords(routine.acceptance),
|
|
738
|
+
...(routine.requirements.length === 0 ? [] : [` needs ${routine.requirements.join(", ")}`]),
|
|
739
|
+
` project ${routine.repo}`,
|
|
740
|
+
...routineAgentsWords(routine),
|
|
741
|
+
` schedule ${schedule === null ? routine.schedule : describeSchedule(schedule)}`,
|
|
742
|
+
` budget ${routine.costCeilingUsd === null ? "no ceiling" : `$${routine.costCeilingUsd.toFixed(2)} per rolling 7 days`}`,
|
|
743
|
+
` one at a time — a firing skips while the previous instance is unfinished`,
|
|
744
|
+
` reference ${routine.digest}`,
|
|
745
|
+
` approving this means EACH FIRING BUILDS WITHOUT ASKING, inside exactly these terms`,
|
|
746
|
+
];
|
|
747
|
+
}
|
|
748
|
+
/** The agents a standing order's approval freezes, in the same words the
|
|
749
|
+
* task page and chat use — or why it cannot say. */
|
|
750
|
+
export function routineAgentsWords(routine) {
|
|
751
|
+
const approved = routine.approvedAt !== null && routine.approvedDigest === routine.digest;
|
|
752
|
+
const agents = routineAgentsState(routine);
|
|
753
|
+
const route = approved ? routine.approvedRoute ?? null : routine.route ?? null;
|
|
754
|
+
if (route === null || agents.state === "unreadable" || agents.state === "unresolved" || agents.state === "unverified") {
|
|
755
|
+
return [
|
|
756
|
+
` agents ${agents.state === "unreadable" ? "cannot be read" : agents.state === "unfrozen" ? "not frozen" : agents.state === "unverified" ? "do not verify" : "not resolved"} — ${agents.problem ?? "this standing order does not name an exact agent for every role"}`,
|
|
757
|
+
` refresh them with \`routine refresh <name>\`, read the agents it then names, and approve it again`,
|
|
758
|
+
];
|
|
759
|
+
}
|
|
760
|
+
return [
|
|
761
|
+
` agents ${agentsSummary(route)} — ${postureWords(route)}`,
|
|
762
|
+
...(approved ? [` frozen by the approval: a configuration change cannot re-route a firing`] : [` frozen when you approve: a configuration change cannot re-route a firing`]),
|
|
763
|
+
];
|
|
764
|
+
}
|