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/names.js
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The product's internal name, and every older one it still answers to.
|
|
3
|
+
*
|
|
4
|
+
* Toolroll was Standing Orders, which was nightorders. A rename must never
|
|
5
|
+
* orphan an installation: new things use the new name, while a folder,
|
|
6
|
+
* variable, branch, launchd job or skill that already exists under an older
|
|
7
|
+
* name keeps being found. Nothing is moved; the old one is used until one
|
|
8
|
+
* exists under the new name. Strings that feed a hash, a digest or a stored
|
|
9
|
+
* format id are not names in this sense and stay byte-identical.
|
|
10
|
+
*/
|
|
11
|
+
import { existsSync } from "node:fs";
|
|
12
|
+
import { join } from "node:path";
|
|
13
|
+
export const NAME = "toolroll";
|
|
14
|
+
/** Older names, newest first. */
|
|
15
|
+
export const LEGACY_NAMES = ["standing-orders", "nightorders"];
|
|
16
|
+
export const NAMES = [NAME, ...LEGACY_NAMES];
|
|
17
|
+
/**
|
|
18
|
+
* A file inside the product's folder under `base` (~/.config, ~/.cache, or a
|
|
19
|
+
* home dotfolder when `dot` is set). The first name that already holds the
|
|
20
|
+
* file wins; otherwise the file goes into the folder that holds the database,
|
|
21
|
+
* else the first folder that exists, else a new-name folder — so one install
|
|
22
|
+
* keeps one folder, and a fresh one gets ~/.config/toolroll.
|
|
23
|
+
*/
|
|
24
|
+
export function namedPath(base, rest, options = {}) {
|
|
25
|
+
const names = options.names ?? NAMES;
|
|
26
|
+
const folder = (name) => join(base, options.dot ? `.${name}` : name);
|
|
27
|
+
for (const name of names)
|
|
28
|
+
if (existsSync(join(folder(name), ...rest)))
|
|
29
|
+
return join(folder(name), ...rest);
|
|
30
|
+
return join(namedFolder(base, options), ...rest);
|
|
31
|
+
}
|
|
32
|
+
/** The product's folder under `base`: see namedPath. */
|
|
33
|
+
export function namedFolder(base, options = {}) {
|
|
34
|
+
const names = options.names ?? NAMES;
|
|
35
|
+
const folder = (name) => join(base, options.dot ? `.${name}` : name);
|
|
36
|
+
const chosen = names.find(name => existsSync(join(folder(name), "orders.db"))) ?? names.find(name => existsSync(folder(name))) ?? names[0];
|
|
37
|
+
return folder(chosen);
|
|
38
|
+
}
|
|
39
|
+
/** Every folder under `base` that any name has made, for fencing and cleanup. */
|
|
40
|
+
export function existingFolders(base, options = {}) {
|
|
41
|
+
return NAMES.map(name => join(base, options.dot ? `.${name}` : name)).filter(path => existsSync(path));
|
|
42
|
+
}
|
|
43
|
+
/** ~/.config (or $XDG_CONFIG_HOME). */
|
|
44
|
+
export function configBase(env, home) {
|
|
45
|
+
const xdg = env["XDG_CONFIG_HOME"];
|
|
46
|
+
return xdg !== undefined && xdg !== "" ? xdg : join(home, ".config");
|
|
47
|
+
}
|
|
48
|
+
export const ENV_PREFIX = "TOOLROLL_";
|
|
49
|
+
export const LEGACY_ENV_PREFIX = "STANDING_ORDERS_";
|
|
50
|
+
/**
|
|
51
|
+
* An environment setting by its suffix ("DB" → TOOLROLL_DB). The new name
|
|
52
|
+
* wins whenever it is set; the old STANDING_ORDERS_ one is the fallback.
|
|
53
|
+
*/
|
|
54
|
+
export function envValue(env, suffix) {
|
|
55
|
+
// Empty counts as unset: a blank TOOLROLL_DB must not hide an isolating STANDING_ORDERS_DB.
|
|
56
|
+
const current = env[ENV_PREFIX + suffix];
|
|
57
|
+
return current !== undefined && current !== "" ? current : env[LEGACY_ENV_PREFIX + suffix];
|
|
58
|
+
}
|
|
59
|
+
/** The Telegram bot token's variable, and the name it had before the rename: agents never see either.
|
|
60
|
+
* Kept here, in a module with no imports of its own, so builder.ts can read it at load time. */
|
|
61
|
+
export const TELEGRAM_TOKEN_ENV = `${ENV_PREFIX}TELEGRAM_TOKEN`;
|
|
62
|
+
export const TELEGRAM_TOKEN_ENVS = [TELEGRAM_TOKEN_ENV, `${LEGACY_ENV_PREFIX}TELEGRAM_TOKEN`];
|
|
63
|
+
/** Both names of a setting, for a child the plane starts: older tools read the old one. */
|
|
64
|
+
export function envTwins(suffix, value) {
|
|
65
|
+
return { [ENV_PREFIX + suffix]: value, [LEGACY_ENV_PREFIX + suffix]: value };
|
|
66
|
+
}
|
|
67
|
+
/** True for a variable of either name ("TOOLROLL_DB", "STANDING_ORDERS_DB"). */
|
|
68
|
+
export function isOwnEnv(key) {
|
|
69
|
+
return /^(TOOLROLL|STANDING_ORDERS)_/i.test(key);
|
|
70
|
+
}
|
|
71
|
+
/** New task branches are toolroll/<id>; the plane's older ones stay its own. */
|
|
72
|
+
export const BRANCH_PREFIX = `${NAME}/`;
|
|
73
|
+
export const BRANCH_PREFIXES = [BRANCH_PREFIX, "standing-orders/"];
|
|
74
|
+
export function taskBranch(id) {
|
|
75
|
+
return `${BRANCH_PREFIX}${id}`;
|
|
76
|
+
}
|
|
77
|
+
/** A branch the plane made, under either name. */
|
|
78
|
+
export function isOwnBranch(branch) {
|
|
79
|
+
return BRANCH_PREFIXES.some(prefix => branch.startsWith(prefix));
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Whether a head branch is inside a publication grant's prefix. A grant made
|
|
83
|
+
* for the plane's own branches under one name covers the same branches under
|
|
84
|
+
* the other: a standing-orders/ grant still publishes toolroll/<id> work.
|
|
85
|
+
*/
|
|
86
|
+
export function headWithin(head, prefix) {
|
|
87
|
+
if (head.startsWith(prefix))
|
|
88
|
+
return true;
|
|
89
|
+
const own = BRANCH_PREFIXES.find(one => prefix.startsWith(one));
|
|
90
|
+
return own !== undefined && BRANCH_PREFIXES.some(one => head.startsWith(one + prefix.slice(own.length)));
|
|
91
|
+
}
|
|
92
|
+
/** Every name a task's own branch may have, newest first. */
|
|
93
|
+
export function taskBranches(id) {
|
|
94
|
+
return BRANCH_PREFIXES.map(prefix => `${prefix}${id}`);
|
|
95
|
+
}
|
|
96
|
+
/** The first candidate branch that already exists (a retry reuses its branch), else the first — the new name. */
|
|
97
|
+
export async function existingOrFirst(candidates, exists) {
|
|
98
|
+
for (const candidate of candidates)
|
|
99
|
+
if (await exists(candidate))
|
|
100
|
+
return candidate;
|
|
101
|
+
return candidates[0];
|
|
102
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { Runner } from "./builder.js";
|
|
2
|
+
import type { Store } from "./store.js";
|
|
3
|
+
export declare const OBSERVATION_MAILBOX = "STANDING-ORDERS-OBSERVATIONS.json";
|
|
4
|
+
export declare const OBSERVATION_CAPTURE = "machine focused observations v1";
|
|
5
|
+
export declare const OBSERVATION_FILE = "REVIEW-OBSERVATIONS.json";
|
|
6
|
+
export type ObservationBrief = {
|
|
7
|
+
kind: "evidence-observation";
|
|
8
|
+
sourceTask: string;
|
|
9
|
+
sourceRun: number;
|
|
10
|
+
sourceScopeDigest: string;
|
|
11
|
+
head: string;
|
|
12
|
+
originalBase: string;
|
|
13
|
+
unresolved: {
|
|
14
|
+
id: string;
|
|
15
|
+
statement: string;
|
|
16
|
+
detail: string[];
|
|
17
|
+
}[];
|
|
18
|
+
gateDigest: string;
|
|
19
|
+
};
|
|
20
|
+
export declare function focusedTestCommandSupported(command: string): boolean;
|
|
21
|
+
export type ObservationCase = {
|
|
22
|
+
criterion: string;
|
|
23
|
+
at: "base" | "head";
|
|
24
|
+
testPath: string;
|
|
25
|
+
testName: string;
|
|
26
|
+
};
|
|
27
|
+
export declare function originalTaskBase(store: Store, taskId: string): string | null;
|
|
28
|
+
/** The existing sealed revision brief identifies this narrower kind of work.
|
|
29
|
+
* A title, agent-authored mailbox, or handoff can never elect gate reuse. */
|
|
30
|
+
export declare function observationBrief(store: Store, root: string, taskRef: number): ObservationBrief | null;
|
|
31
|
+
export declare function parseObservationCases(raw: string, ids: readonly string[]): ObservationCase[];
|
|
32
|
+
export type ObservationEvidence = {
|
|
33
|
+
artifact: number;
|
|
34
|
+
sha256: string;
|
|
35
|
+
content: string;
|
|
36
|
+
};
|
|
37
|
+
export declare function readObservationEvidence(store: Store, root: string, runId: number): ObservationEvidence | null;
|
|
38
|
+
/** The supplied runner applies the build's existing stop, custody, environment
|
|
39
|
+
* and approved command timeout. Only fixed argv is constructed here. */
|
|
40
|
+
export declare function collectObservations(store: Store, root: string, runId: number, worktree: string, brief: ObservationBrief, cases: readonly ObservationCase[], execute: Runner, now: () => Date): Promise<void>;
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
/** Focused observations are new evidence about an unchanged candidate. They
|
|
2
|
+
* never replace the original gate or give an agent a new shell authority. */
|
|
3
|
+
import { createHash } from "node:crypto";
|
|
4
|
+
import { lstatSync, mkdirSync, mkdtempSync, readFileSync, realpathSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
|
|
5
|
+
import { dirname, join } from "node:path";
|
|
6
|
+
import { stripVTControlCharacters } from "node:util";
|
|
7
|
+
import { canonicalAcceptance } from "./scope.js";
|
|
8
|
+
import { readVerifiedArtifact, scanForSecrets, storeEvidence } from "./evidence.js";
|
|
9
|
+
export const OBSERVATION_MAILBOX = "STANDING-ORDERS-OBSERVATIONS.json";
|
|
10
|
+
export const OBSERVATION_CAPTURE = "machine focused observations v1";
|
|
11
|
+
export const OBSERVATION_FILE = "REVIEW-OBSERVATIONS.json";
|
|
12
|
+
const sha = (bytes) => createHash("sha256").update(bytes).digest("hex");
|
|
13
|
+
const revision = (value) => typeof value === "string" && /^[0-9a-f]{40,64}$/.test(value);
|
|
14
|
+
const pathOkay = (value) => typeof value === "string" && value.length > 0 && value.length <= 500 &&
|
|
15
|
+
!value.startsWith("-") && !/[\\\u0000-\u0020\u007f:*?\[\]]/.test(value) && value.split("/").every(p => p !== "" && p !== "." && p !== "..");
|
|
16
|
+
export function focusedTestCommandSupported(command) {
|
|
17
|
+
const parts = command.trim().split(/\s*&&\s*/);
|
|
18
|
+
// Only simple argv joined by && can establish this narrower authority.
|
|
19
|
+
// Quotes, expansion, substitutions, redirects and other shell syntax need
|
|
20
|
+
// explicit support; text inside an echo/printf argument is not a command.
|
|
21
|
+
if (parts.some(c => !/^[A-Za-z0-9_./=:-]+(?: +[A-Za-z0-9_./=:-]+)*$/.test(c)))
|
|
22
|
+
return false;
|
|
23
|
+
return parts.some(c => /^npm (?:test|run test)(?: -- (?:--run|--reporter=\w+|--no-file-parallelism)(?: (?:--run|--reporter=\w+|--no-file-parallelism))*)?$/.test(c));
|
|
24
|
+
}
|
|
25
|
+
export function originalTaskBase(store, taskId) {
|
|
26
|
+
const lineage = store.repairLineageOf(taskId);
|
|
27
|
+
if (lineage.problem)
|
|
28
|
+
return null;
|
|
29
|
+
const ref = store.lookupRef(lineage.rootTask);
|
|
30
|
+
const first = ref && store.runsFor(ref.id).filter(r => r.role === "builder" && r.branch && r.baseRevision).sort((a, b) => a.id - b.id)[0];
|
|
31
|
+
return first?.branch ? store.firstBuilderBase(first.taskRef, first.branch) ?? first.baseRevision : null;
|
|
32
|
+
}
|
|
33
|
+
/** The existing sealed revision brief identifies this narrower kind of work.
|
|
34
|
+
* A title, agent-authored mailbox, or handoff can never elect gate reuse. */
|
|
35
|
+
export function observationBrief(store, root, taskRef) {
|
|
36
|
+
const ref = store.refById(taskRef);
|
|
37
|
+
const source = store.revisionSourceOf(taskRef);
|
|
38
|
+
if (!ref || !source)
|
|
39
|
+
return null;
|
|
40
|
+
const full = store.lookupRef(ref.externalId);
|
|
41
|
+
const artifact = full?.revisionBriefArtifact ? store.getArtifact(full.revisionBriefArtifact) : null;
|
|
42
|
+
if (!artifact)
|
|
43
|
+
return null;
|
|
44
|
+
const read = readVerifiedArtifact(root, artifact);
|
|
45
|
+
if (!read.ok)
|
|
46
|
+
throw Error("The observation brief no longer verifies.");
|
|
47
|
+
const brief = JSON.parse(read.content.toString("utf8"));
|
|
48
|
+
if (brief.kind !== "evidence-observation")
|
|
49
|
+
return null;
|
|
50
|
+
const parent = store.getRun(source.sourceRun), parentScope = store.getScope(source.sourceTask), scope = store.getScope(ref.externalId);
|
|
51
|
+
if (!parent || !parentScope || !scope || brief.sourceRun !== parent.id || brief.sourceTask !== source.sourceTask ||
|
|
52
|
+
brief.sourceScopeDigest !== parent.scopeDigest || parentScope.digest !== parent.scopeDigest ||
|
|
53
|
+
!revision(brief.head) || brief.head !== parent.headRevision || !revision(brief.originalBase) ||
|
|
54
|
+
brief.originalBase !== originalTaskBase(store, source.sourceTask) || !/^[0-9a-f]{64}$/.test(brief.gateDigest) ||
|
|
55
|
+
JSON.stringify(canonicalAcceptance(scope.acceptance)) !== JSON.stringify(canonicalAcceptance(parentScope.acceptance)) ||
|
|
56
|
+
!Array.isArray(brief.unresolved) || brief.unresolved.length === 0 ||
|
|
57
|
+
brief.unresolved.some((row) => !parentScope.acceptance.some(c => c.id === row.id)))
|
|
58
|
+
throw Error("The observation source, candidate or signed criteria changed.");
|
|
59
|
+
return brief;
|
|
60
|
+
}
|
|
61
|
+
export function parseObservationCases(raw, ids) {
|
|
62
|
+
if (Buffer.byteLength(raw) > 16 * 1024)
|
|
63
|
+
throw Error("The observation request is too large.");
|
|
64
|
+
const parsed = JSON.parse(raw);
|
|
65
|
+
if (parsed?.version !== 1 || !Array.isArray(parsed.observations) || parsed.observations.length < 1 || parsed.observations.length > 4 ||
|
|
66
|
+
Object.keys(parsed).some(k => k !== "version" && k !== "observations"))
|
|
67
|
+
throw Error("Use one to four focused test observations.");
|
|
68
|
+
const cases = parsed.observations;
|
|
69
|
+
for (const item of cases) {
|
|
70
|
+
if (!item || Object.keys(item).some(k => !["criterion", "at", "testPath", "testName"].includes(k)) || !ids.includes(item.criterion) ||
|
|
71
|
+
!["base", "head"].includes(item.at) || !pathOkay(item.testPath) || !/\.(test|spec)\.[cm]?[jt]sx?$/.test(item.testPath) ||
|
|
72
|
+
typeof item.testName !== "string" || item.testName.trim() === "" || item.testName.length > 500 || /[\u0000-\u001f\u007f]/.test(item.testName)) {
|
|
73
|
+
throw Error("An observation must name a requested criterion, base/head, one test file and one test name. Shell commands are not accepted.");
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if (new Set(cases.map(c => JSON.stringify(c))).size !== cases.length || ids.some(id => !cases.some(c => c.criterion === id)))
|
|
77
|
+
throw Error("Every requested criterion needs a distinct observation.");
|
|
78
|
+
return cases;
|
|
79
|
+
}
|
|
80
|
+
function parseTree(text) {
|
|
81
|
+
if (!text.endsWith("\0") || Buffer.byteLength(text) > 4 * 1024 * 1024)
|
|
82
|
+
throw Error("The complete source inventory is unavailable.");
|
|
83
|
+
return text.slice(0, -1).split("\0").map(line => {
|
|
84
|
+
const m = /^(100644|100755) blob ([0-9a-f]{40})\t(.+)$/.exec(line);
|
|
85
|
+
if (!m || !pathOkay(m[3]))
|
|
86
|
+
throw Error("Observation snapshots require regular, normalized source files; links and submodules need separate setup.");
|
|
87
|
+
return { path: m[3], blob: m[2], executable: m[1] === "100755" };
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
function gitBlob(bytes) { return createHash("sha1").update(`blob ${bytes.length}\0`).update(bytes).digest("hex"); }
|
|
91
|
+
export function readObservationEvidence(store, root, runId) {
|
|
92
|
+
const found = store.artifactsFor(runId).filter(a => a.kind === "structured-output" && a.capture === OBSERVATION_CAPTURE);
|
|
93
|
+
if (found.length === 0)
|
|
94
|
+
return null;
|
|
95
|
+
const artifact = found[0];
|
|
96
|
+
if (found.length !== 1 || artifact.truncated || artifact.redacted || artifact.captureStatus !== "ok")
|
|
97
|
+
throw Error("The focused observation inventory is incomplete.");
|
|
98
|
+
const read = readVerifiedArtifact(root, artifact);
|
|
99
|
+
if (!read.ok)
|
|
100
|
+
throw Error("The focused observations no longer verify.");
|
|
101
|
+
const receipt = JSON.parse(read.content.toString("utf8")), run = store.getRun(runId), brief = run ? observationBrief(store, root, run.taskRef) : null;
|
|
102
|
+
if (!run || !brief || receipt.version !== 1 || receipt.run !== runId || receipt.head !== run.headRevision || receipt.head !== brief.head ||
|
|
103
|
+
receipt.scopeDigest !== run.scopeDigest || receipt.sourceRun !== brief.sourceRun || receipt.originalBase !== brief.originalBase ||
|
|
104
|
+
!Array.isArray(receipt.observations) || receipt.observations.length < 1 || receipt.observations.length > 4)
|
|
105
|
+
throw Error("The observations no longer match this exact candidate and scope.");
|
|
106
|
+
return { artifact: artifact.id, sha256: artifact.sha256, content: read.content.toString("utf8") };
|
|
107
|
+
}
|
|
108
|
+
/** The supplied runner applies the build's existing stop, custody, environment
|
|
109
|
+
* and approved command timeout. Only fixed argv is constructed here. */
|
|
110
|
+
export async function collectObservations(store, root, runId, worktree, brief, cases, execute, now) {
|
|
111
|
+
const source = store.getRun(runId), ref = source && store.refById(source.taskRef);
|
|
112
|
+
const command = ref?.repo ? store.liveVerifyCommand(ref.repo) : null;
|
|
113
|
+
// This is deliberately a narrow first collector. Approval for npm test
|
|
114
|
+
// does not authorize arbitrary shell text supplied by an agent or review.
|
|
115
|
+
if (!source || !command || !focusedTestCommandSupported(command.command)) {
|
|
116
|
+
throw Error("Focused collection requires an already approved npm test command.");
|
|
117
|
+
}
|
|
118
|
+
const call = async (file, args, cwd) => {
|
|
119
|
+
const result = await execute(file, args, { cwd, timeoutMs: command.timeoutMs, maxBuffer: 4 * 1024 * 1024 });
|
|
120
|
+
if (result.notFound || result.timedOut || result.code !== 0)
|
|
121
|
+
throw Error(`Observation preparation failed: ${file} (${result.code}).`);
|
|
122
|
+
return result.stdout;
|
|
123
|
+
};
|
|
124
|
+
const headTree = parseTree(await call("git", ["ls-tree", "-rz", brief.head], worktree));
|
|
125
|
+
const baseTree = parseTree(await call("git", ["ls-tree", "-rz", brief.originalBase], worktree));
|
|
126
|
+
const changed = new Set((await call("git", ["diff", "--name-only", "-z", brief.originalBase, brief.head], worktree)).split("\0").filter(Boolean));
|
|
127
|
+
const packageAt = async (rev) => JSON.parse(await call("git", ["show", `${rev}:package.json`], worktree));
|
|
128
|
+
for (const rev of [brief.originalBase, brief.head]) {
|
|
129
|
+
const pkg = await packageAt(rev);
|
|
130
|
+
if (!/^vitest(?: run)?$/.test(pkg.scripts?.test ?? "") || pkg.scripts?.pretest || pkg.scripts?.posttest)
|
|
131
|
+
throw Error("This project needs a supported focused test command; only a direct Vitest test script is currently supported.");
|
|
132
|
+
}
|
|
133
|
+
const headLock = headTree.find(f => f.path === "package-lock.json"), baseLock = baseTree.find(f => f.path === "package-lock.json");
|
|
134
|
+
if (!headLock || headLock.blob !== baseLock?.blob || gitBlob(readFileSync(join(worktree, headLock.path))) !== headLock.blob)
|
|
135
|
+
throw Error("The original and candidate dependency locks must match for this focused comparison.");
|
|
136
|
+
const dependencyPath = realpathSync(join(worktree, "node_modules"));
|
|
137
|
+
if (!lstatSync(join(dependencyPath, "vitest", "vitest.mjs")).isFile())
|
|
138
|
+
throw Error("The project's installed Vitest runner is unavailable.");
|
|
139
|
+
const parent = join(root, String(runId));
|
|
140
|
+
mkdirSync(parent, { recursive: true });
|
|
141
|
+
const scratch = mkdtempSync(join(parent, "observation-"));
|
|
142
|
+
const observations = [];
|
|
143
|
+
try {
|
|
144
|
+
for (const [index, item] of cases.entries()) {
|
|
145
|
+
const at = item.at === "base" ? brief.originalBase : brief.head, tree = item.at === "base" ? baseTree : headTree;
|
|
146
|
+
const test = headTree.find(f => f.path === item.testPath);
|
|
147
|
+
if (!test || (item.at === "base" && !changed.has(item.testPath)))
|
|
148
|
+
throw Error("The baseline test overlay must be a test in the candidate's whole-task change set.");
|
|
149
|
+
const snapshot = join(scratch, String(index));
|
|
150
|
+
mkdirSync(snapshot);
|
|
151
|
+
const archive = join(scratch, `${index}.tar`);
|
|
152
|
+
await call("git", ["archive", "--format=tar", `--output=${archive}`, at], worktree);
|
|
153
|
+
await call("tar", ["-xf", archive, "-C", snapshot], worktree);
|
|
154
|
+
if (item.at === "base") {
|
|
155
|
+
const bytes = await call("git", ["show", `${brief.head}:${item.testPath}`], worktree);
|
|
156
|
+
if (gitBlob(Buffer.from(bytes)) !== test.blob)
|
|
157
|
+
throw Error("The complete candidate test bytes are unavailable.");
|
|
158
|
+
mkdirSync(dirname(join(snapshot, item.testPath)), { recursive: true });
|
|
159
|
+
writeFileSync(join(snapshot, item.testPath), bytes);
|
|
160
|
+
}
|
|
161
|
+
const verifySnapshot = () => {
|
|
162
|
+
for (const file of tree) {
|
|
163
|
+
const path = join(snapshot, file.path), expected = file.path === item.testPath ? test.blob : file.blob;
|
|
164
|
+
if (!lstatSync(path).isFile() || gitBlob(readFileSync(path)) !== expected)
|
|
165
|
+
throw Error("The observation changed or lost a source file: " + file.path);
|
|
166
|
+
}
|
|
167
|
+
if (gitBlob(readFileSync(join(snapshot, item.testPath))) !== test.blob)
|
|
168
|
+
throw Error("The observation test changed.");
|
|
169
|
+
};
|
|
170
|
+
verifySnapshot();
|
|
171
|
+
symlinkSync(dependencyPath, join(snapshot, "node_modules"), "junction");
|
|
172
|
+
// Vitest treats its name filter as a regular expression. The manifest
|
|
173
|
+
// supplies a literal name; also verify the actual selected identity so
|
|
174
|
+
// a suffix, duplicate name or another matching file cannot stand in.
|
|
175
|
+
const pattern = `(?:^| )${item.testName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}$`;
|
|
176
|
+
const reportPath = join(scratch, `${index}.json`);
|
|
177
|
+
const argv = [join(dependencyPath, "vitest", "vitest.mjs"), "run", item.testPath, "--testNamePattern", pattern,
|
|
178
|
+
"--reporter=verbose", "--reporter=json", `--outputFile.json=${reportPath}`];
|
|
179
|
+
const startedAt = now().toISOString();
|
|
180
|
+
const result = await execute(process.execPath, argv, { cwd: snapshot, timeoutMs: command.timeoutMs, maxBuffer: 64 * 1024 });
|
|
181
|
+
verifySnapshot();
|
|
182
|
+
if (result.notFound || result.timedOut || result.code < 0 || result.code > 1 || Buffer.byteLength(result.stdout + result.stderr) > 48 * 1024)
|
|
183
|
+
throw Error("The focused test did not produce complete bounded output.");
|
|
184
|
+
const summary = stripVTControlCharacters(result.stdout + "\n" + result.stderr);
|
|
185
|
+
if (!/Tests\s+[1-9]\d* (?:passed|failed)/.test(summary) || /No test files found|No tests found/.test(summary))
|
|
186
|
+
throw Error("The focused command did not execute a test.");
|
|
187
|
+
if (!lstatSync(reportPath).isFile() || lstatSync(reportPath).size > 1024 * 1024)
|
|
188
|
+
throw Error("The focused test identity report is unavailable or too large.");
|
|
189
|
+
const report = JSON.parse(readFileSync(reportPath, "utf8"));
|
|
190
|
+
const selected = (report.testResults ?? []).flatMap((file) => (file.assertionResults ?? []).filter(test => test.status === "passed" || test.status === "failed").map(test => ({ path: file.name, title: test.title, fullName: test.fullName, status: test.status })));
|
|
191
|
+
const selectedTest = selected[0];
|
|
192
|
+
if (selected.length !== 1 || !selectedTest || realpathSync(selectedTest.path) !== realpathSync(join(snapshot, item.testPath)) ||
|
|
193
|
+
(selectedTest.title !== item.testName && selectedTest.fullName !== item.testName) ||
|
|
194
|
+
(selectedTest.status === "passed" ? result.code !== 0 : result.code !== 1))
|
|
195
|
+
throw Error("The focused command did not execute exactly the requested test.");
|
|
196
|
+
observations.push({ ...item, revision: at, testBlob: test.blob, testSha256: sha(readFileSync(join(snapshot, item.testPath))),
|
|
197
|
+
selectedTest: { ...selectedTest, path: item.testPath },
|
|
198
|
+
overlay: item.at === "base" ? [item.testPath] : [], lockBlob: headLock.blob, command: [process.execPath, ...argv],
|
|
199
|
+
approvedCommand: { id: command.id, digest: command.digest }, startedAt, finishedAt: now().toISOString(),
|
|
200
|
+
exitCode: result.code, stdout: result.stdout, stderr: result.stderr, sourceFilesVerified: tree.length });
|
|
201
|
+
}
|
|
202
|
+
const bytes = JSON.stringify({ version: 1, run: runId, head: brief.head, originalBase: brief.originalBase,
|
|
203
|
+
sourceRun: brief.sourceRun, scopeDigest: source.scopeDigest, observations }, null, 1);
|
|
204
|
+
if (Buffer.byteLength(bytes) > 64 * 1024 || scanForSecrets(bytes).length)
|
|
205
|
+
throw Error("The observations exceed storage limits or contain sensitive output; no incomplete evidence was accepted.");
|
|
206
|
+
storeEvidence(store, root, runId, "structured-output", "observations.json", Buffer.from(bytes), OBSERVATION_CAPTURE, now(), { captureStatus: "ok" });
|
|
207
|
+
}
|
|
208
|
+
finally {
|
|
209
|
+
rmSync(scratch, { recursive: true, force: true });
|
|
210
|
+
}
|
|
211
|
+
}
|
package/dist/oidc.d.ts
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
export type GroupRule = {
|
|
2
|
+
group: string;
|
|
3
|
+
role: "operator" | "viewer";
|
|
4
|
+
projects: "all" | string[];
|
|
5
|
+
};
|
|
6
|
+
export type OidcSettings = {
|
|
7
|
+
issuer: string;
|
|
8
|
+
clientId: string;
|
|
9
|
+
clientSecret: string | null;
|
|
10
|
+
/** What the sign-in button says: "Okta", "Microsoft", "Google". */
|
|
11
|
+
label: string;
|
|
12
|
+
scopes: string;
|
|
13
|
+
/** The ID token claim that lists a person's groups. */
|
|
14
|
+
groupsClaim: string;
|
|
15
|
+
rules: GroupRule[];
|
|
16
|
+
/** Who may still sign in with a Toolroll password once sign-in with the provider is on. */
|
|
17
|
+
passwords: "everyone" | "operators";
|
|
18
|
+
};
|
|
19
|
+
export type OidcProvider = {
|
|
20
|
+
issuer: string;
|
|
21
|
+
authorize: string;
|
|
22
|
+
token: string;
|
|
23
|
+
jwks: string;
|
|
24
|
+
};
|
|
25
|
+
export type OidcClaims = {
|
|
26
|
+
sub: string;
|
|
27
|
+
email: string | null;
|
|
28
|
+
name: string | null;
|
|
29
|
+
username: string | null;
|
|
30
|
+
groups: string[];
|
|
31
|
+
authTime: number | null;
|
|
32
|
+
};
|
|
33
|
+
type Fetch = typeof fetch;
|
|
34
|
+
/** The provider's endpoints, from its discovery document; its issuer must be the one configured. */
|
|
35
|
+
export declare function discoverOidc(issuer: string, fetcher?: Fetch): Promise<{
|
|
36
|
+
ok: true;
|
|
37
|
+
provider: OidcProvider;
|
|
38
|
+
} | {
|
|
39
|
+
ok: false;
|
|
40
|
+
said: string;
|
|
41
|
+
}>;
|
|
42
|
+
/** The pieces a sign-in needs to come back to: kept by the console, and its state in a cookie on the browser that started it. */
|
|
43
|
+
export type OidcVisit = {
|
|
44
|
+
state: string;
|
|
45
|
+
nonce: string;
|
|
46
|
+
verifier: string;
|
|
47
|
+
};
|
|
48
|
+
export declare function newOidcVisit(): OidcVisit;
|
|
49
|
+
/** Where to send the person to sign in. `reauth` asks the provider to check them again now (a step-up). */
|
|
50
|
+
export declare function oidcAuthorizeUrl(provider: OidcProvider, settings: OidcSettings, visit: OidcVisit, redirectUri: string, reauth?: boolean): string;
|
|
51
|
+
/** The code, traded for the ID token. */
|
|
52
|
+
export declare function exchangeOidcCode(provider: OidcProvider, settings: OidcSettings, code: string, visit: OidcVisit, redirectUri: string, fetcher?: Fetch): Promise<{
|
|
53
|
+
ok: true;
|
|
54
|
+
idToken: string;
|
|
55
|
+
} | {
|
|
56
|
+
ok: false;
|
|
57
|
+
said: string;
|
|
58
|
+
}>;
|
|
59
|
+
/** Check the ID token: its signature by one of the provider's keys, then who issued it, for whom, until when, and for which sign-in. */
|
|
60
|
+
export declare function verifyIdToken(idToken: string, provider: OidcProvider, settings: OidcSettings, nonce: string, now: Date, fetcher?: Fetch): Promise<{
|
|
61
|
+
ok: true;
|
|
62
|
+
claims: OidcClaims;
|
|
63
|
+
} | {
|
|
64
|
+
ok: false;
|
|
65
|
+
said: string;
|
|
66
|
+
}>;
|
|
67
|
+
/** A person's role and projects from their groups: the first rule that matches ("*" matches everyone), or none. */
|
|
68
|
+
export declare function accessFromGroups(groups: readonly string[], rules: readonly GroupRule[]): GroupRule | null;
|
|
69
|
+
/** An account name for someone new: from their username or email, in the characters names allow, made unique. */
|
|
70
|
+
export declare function accountNameFor(claims: OidcClaims, taken: (name: string) => boolean): string;
|
|
71
|
+
export {};
|
package/dist/oidc.js
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sign-in with an identity provider (v100): OpenID Connect, the protocol
|
|
3
|
+
* Okta, Microsoft Entra, Google Workspace, Auth0, Keycloak and JumpCloud all
|
|
4
|
+
* speak. Discovery finds the provider's endpoints and keys; sign-in is the
|
|
5
|
+
* authorization-code flow with PKCE, a state and a nonce; the ID token's
|
|
6
|
+
* signature is checked against the provider's published keys (RS256, PS256,
|
|
7
|
+
* ES256) along with its issuer, audience, expiry and nonce. Groups from the
|
|
8
|
+
* token decide a person's role and projects, first matching rule wins.
|
|
9
|
+
*
|
|
10
|
+
* Addresses are https, except a provider on this computer (tests and local
|
|
11
|
+
* development), the same loopback exception OAuth makes.
|
|
12
|
+
*/
|
|
13
|
+
import { createHash, createPublicKey, randomBytes, verify, constants } from "node:crypto";
|
|
14
|
+
const loopback = (value) => { try {
|
|
15
|
+
const url = new URL(value);
|
|
16
|
+
return url.protocol === "http:" && ["127.0.0.1", "localhost", "[::1]"].includes(url.hostname);
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return false;
|
|
20
|
+
} };
|
|
21
|
+
const secure = (value, issuer) => {
|
|
22
|
+
if (typeof value !== "string")
|
|
23
|
+
return false;
|
|
24
|
+
try {
|
|
25
|
+
return new URL(value).protocol === "https:" || (loopback(issuer) && loopback(value));
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
const trim = (url) => url.replace(/\/+$/, "");
|
|
32
|
+
/** The provider's endpoints, from its discovery document; its issuer must be the one configured. */
|
|
33
|
+
export async function discoverOidc(issuer, fetcher = fetch) {
|
|
34
|
+
if (!secure(issuer, issuer))
|
|
35
|
+
return { ok: false, said: "The issuer must be an https address." };
|
|
36
|
+
let document;
|
|
37
|
+
try {
|
|
38
|
+
const answer = await fetcher(`${trim(issuer)}/.well-known/openid-configuration`, { headers: { accept: "application/json" }, signal: AbortSignal.timeout(10_000) });
|
|
39
|
+
if (!answer.ok)
|
|
40
|
+
return { ok: false, said: `The provider's discovery document answered ${answer.status}.` };
|
|
41
|
+
document = await answer.json();
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return { ok: false, said: "The provider couldn't be reached." };
|
|
45
|
+
}
|
|
46
|
+
if (typeof document["issuer"] !== "string" || trim(document["issuer"]) !== trim(issuer))
|
|
47
|
+
return { ok: false, said: "The provider names a different issuer than the one set here." };
|
|
48
|
+
const [authorize, token, jwks] = [document["authorization_endpoint"], document["token_endpoint"], document["jwks_uri"]];
|
|
49
|
+
if (!secure(authorize, issuer) || !secure(token, issuer) || !secure(jwks, issuer))
|
|
50
|
+
return { ok: false, said: "The provider's discovery document is missing its endpoints." };
|
|
51
|
+
return { ok: true, provider: { issuer: document["issuer"], authorize, token, jwks } };
|
|
52
|
+
}
|
|
53
|
+
export function newOidcVisit() {
|
|
54
|
+
return { state: randomBytes(24).toString("base64url"), nonce: randomBytes(24).toString("base64url"), verifier: randomBytes(32).toString("base64url") };
|
|
55
|
+
}
|
|
56
|
+
/** Where to send the person to sign in. `reauth` asks the provider to check them again now (a step-up). */
|
|
57
|
+
export function oidcAuthorizeUrl(provider, settings, visit, redirectUri, reauth = false) {
|
|
58
|
+
const url = new URL(provider.authorize);
|
|
59
|
+
url.searchParams.set("response_type", "code");
|
|
60
|
+
url.searchParams.set("client_id", settings.clientId);
|
|
61
|
+
url.searchParams.set("redirect_uri", redirectUri);
|
|
62
|
+
url.searchParams.set("scope", settings.scopes);
|
|
63
|
+
url.searchParams.set("state", visit.state);
|
|
64
|
+
url.searchParams.set("nonce", visit.nonce);
|
|
65
|
+
url.searchParams.set("code_challenge", createHash("sha256").update(visit.verifier).digest("base64url"));
|
|
66
|
+
url.searchParams.set("code_challenge_method", "S256");
|
|
67
|
+
if (reauth) {
|
|
68
|
+
url.searchParams.set("prompt", "login");
|
|
69
|
+
url.searchParams.set("max_age", "0");
|
|
70
|
+
}
|
|
71
|
+
return url.toString();
|
|
72
|
+
}
|
|
73
|
+
/** The code, traded for the ID token. */
|
|
74
|
+
export async function exchangeOidcCode(provider, settings, code, visit, redirectUri, fetcher = fetch) {
|
|
75
|
+
try {
|
|
76
|
+
const answer = await fetcher(provider.token, { method: "POST", headers: { "content-type": "application/x-www-form-urlencoded", accept: "application/json" }, signal: AbortSignal.timeout(15_000),
|
|
77
|
+
body: new URLSearchParams({ grant_type: "authorization_code", code, redirect_uri: redirectUri, client_id: settings.clientId, code_verifier: visit.verifier, ...(settings.clientSecret === null ? {} : { client_secret: settings.clientSecret }) }) });
|
|
78
|
+
const body = await answer.json();
|
|
79
|
+
if (!answer.ok || typeof body["id_token"] !== "string")
|
|
80
|
+
return { ok: false, said: `The provider didn't finish the sign-in (${typeof body["error"] === "string" ? body["error"] : `HTTP ${answer.status}`}).` };
|
|
81
|
+
return { ok: true, idToken: body["id_token"] };
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
return { ok: false, said: "The provider couldn't be reached to finish the sign-in." };
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
const keyCache = new Map();
|
|
88
|
+
async function providerKeys(jwks, fetcher, fresh) {
|
|
89
|
+
const held = keyCache.get(jwks);
|
|
90
|
+
if (!fresh && held !== undefined && Date.now() - held.at < 10 * 60_000)
|
|
91
|
+
return held.keys;
|
|
92
|
+
const answer = await fetcher(jwks, { headers: { accept: "application/json" }, signal: AbortSignal.timeout(10_000) });
|
|
93
|
+
const body = await answer.json();
|
|
94
|
+
const keys = Array.isArray(body.keys) ? body.keys.filter((one) => one !== null && typeof one === "object") : [];
|
|
95
|
+
keyCache.set(jwks, { at: Date.now(), keys });
|
|
96
|
+
return keys;
|
|
97
|
+
}
|
|
98
|
+
const ALGORITHMS = {
|
|
99
|
+
RS256: { hash: "sha256", kty: "RSA" },
|
|
100
|
+
PS256: { hash: "sha256", padding: constants.RSA_PKCS1_PSS_PADDING, saltLength: 32, kty: "RSA" },
|
|
101
|
+
ES256: { hash: "sha256", dsaEncoding: "ieee-p1363", kty: "EC" },
|
|
102
|
+
};
|
|
103
|
+
const decode = (part) => {
|
|
104
|
+
try {
|
|
105
|
+
const value = JSON.parse(Buffer.from(part, "base64url").toString("utf8"));
|
|
106
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
/** Check the ID token: its signature by one of the provider's keys, then who issued it, for whom, until when, and for which sign-in. */
|
|
113
|
+
export async function verifyIdToken(idToken, provider, settings, nonce, now, fetcher = fetch) {
|
|
114
|
+
const parts = idToken.split(".");
|
|
115
|
+
if (parts.length !== 3)
|
|
116
|
+
return { ok: false, said: "The provider's token isn't a signed token." };
|
|
117
|
+
const header = decode(parts[0]), claims = decode(parts[1]);
|
|
118
|
+
if (header === null || claims === null)
|
|
119
|
+
return { ok: false, said: "The provider's token couldn't be read." };
|
|
120
|
+
const algorithm = typeof header["alg"] === "string" ? ALGORITHMS[header["alg"]] : undefined;
|
|
121
|
+
if (algorithm === undefined)
|
|
122
|
+
return { ok: false, said: `The provider signs with ${String(header["alg"])}, which isn't accepted.` };
|
|
123
|
+
const kid = typeof header["kid"] === "string" ? header["kid"] : null;
|
|
124
|
+
const pick = (keys) => keys.filter(key => key.kty === algorithm.kty && (kid === null || key.kid === kid) && (key.use === undefined || key.use === "sig"));
|
|
125
|
+
let candidates;
|
|
126
|
+
try {
|
|
127
|
+
candidates = pick(await providerKeys(provider.jwks, fetcher, false));
|
|
128
|
+
if (candidates.length === 0)
|
|
129
|
+
candidates = pick(await providerKeys(provider.jwks, fetcher, true)); // the provider may have rotated its keys
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
return { ok: false, said: "The provider's keys couldn't be read." };
|
|
133
|
+
}
|
|
134
|
+
const signed = Buffer.from(`${parts[0]}.${parts[1]}`), signature = Buffer.from(parts[2], "base64url");
|
|
135
|
+
const good = candidates.some(jwk => {
|
|
136
|
+
try {
|
|
137
|
+
const key = createPublicKey({ key: jwk, format: "jwk" });
|
|
138
|
+
return verify(algorithm.hash, signed, { key, ...(algorithm.padding === undefined ? {} : { padding: algorithm.padding, saltLength: algorithm.saltLength }), ...(algorithm.dsaEncoding === undefined ? {} : { dsaEncoding: algorithm.dsaEncoding }) }, signature);
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
if (!good)
|
|
145
|
+
return { ok: false, said: "The provider's token isn't signed by the provider." };
|
|
146
|
+
const seconds = Math.floor(now.getTime() / 1000);
|
|
147
|
+
const audience = Array.isArray(claims["aud"]) ? claims["aud"] : [claims["aud"]];
|
|
148
|
+
if (typeof claims["iss"] !== "string" || trim(claims["iss"]) !== trim(provider.issuer))
|
|
149
|
+
return { ok: false, said: "The token comes from a different issuer." };
|
|
150
|
+
if (!audience.includes(settings.clientId) || (audience.length > 1 && claims["azp"] !== undefined && claims["azp"] !== settings.clientId))
|
|
151
|
+
return { ok: false, said: "The token is for a different app." };
|
|
152
|
+
if (typeof claims["exp"] !== "number" || claims["exp"] < seconds - 60)
|
|
153
|
+
return { ok: false, said: "The sign-in expired. Try again." };
|
|
154
|
+
if (typeof claims["iat"] === "number" && claims["iat"] > seconds + 300)
|
|
155
|
+
return { ok: false, said: "The token's time is in the future. Check this computer's clock." };
|
|
156
|
+
if (claims["nonce"] !== nonce)
|
|
157
|
+
return { ok: false, said: "The sign-in didn't match. Try again." };
|
|
158
|
+
if (typeof claims["sub"] !== "string" || claims["sub"] === "")
|
|
159
|
+
return { ok: false, said: "The token doesn't say who signed in." };
|
|
160
|
+
const text = (key) => typeof claims[key] === "string" && claims[key] !== "" ? claims[key] : null;
|
|
161
|
+
const rawGroups = claims[settings.groupsClaim];
|
|
162
|
+
const groups = Array.isArray(rawGroups) ? rawGroups.filter((one) => typeof one === "string") : typeof rawGroups === "string" ? [rawGroups] : [];
|
|
163
|
+
// An address the provider hasn't checked names no one.
|
|
164
|
+
const email = claims["email_verified"] === false ? null : text("email");
|
|
165
|
+
return { ok: true, claims: { sub: claims["sub"], email, name: text("name"), username: text("preferred_username"), groups: groups.slice(0, 200), authTime: typeof claims["auth_time"] === "number" ? claims["auth_time"] : null } };
|
|
166
|
+
}
|
|
167
|
+
/** A person's role and projects from their groups: the first rule that matches ("*" matches everyone), or none. */
|
|
168
|
+
export function accessFromGroups(groups, rules) {
|
|
169
|
+
return rules.find(rule => rule.group === "*" || groups.includes(rule.group)) ?? null;
|
|
170
|
+
}
|
|
171
|
+
/** An account name for someone new: from their username or email, in the characters names allow, made unique. */
|
|
172
|
+
export function accountNameFor(claims, taken) {
|
|
173
|
+
const source = claims.username ?? claims.email ?? claims.name ?? "person";
|
|
174
|
+
const local = source.includes("@") ? source.slice(0, source.indexOf("@")) : source;
|
|
175
|
+
const base = local.normalize("NFKD").replace(/[^A-Za-z0-9._-]+/g, "-").replace(/^[^A-Za-z0-9]+/, "").replace(/-+$/, "").slice(0, 36) || "person";
|
|
176
|
+
if (!taken(base))
|
|
177
|
+
return base;
|
|
178
|
+
for (let n = 2; n < 1000; n++)
|
|
179
|
+
if (!taken(`${base}-${n}`))
|
|
180
|
+
return `${base}-${n}`;
|
|
181
|
+
return `${base.slice(0, 27)}-${randomBytes(4).toString("hex")}`;
|
|
182
|
+
}
|