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/proof.js
ADDED
|
@@ -0,0 +1,1140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The build's own claim, adjudicated against evidence the plane captured
|
|
3
|
+
* itself (`docs/PRIORITIES.md` Priority 2). Modelled line-for-line on the
|
|
4
|
+
* scout's report (`scout-report.ts`): fail closed, every problem reported at
|
|
5
|
+
* once, stable reasons, byte caps, control-character rejection on every
|
|
6
|
+
* string.
|
|
7
|
+
*
|
|
8
|
+
* The proof is a distinct deliverable from the terminal handoff every
|
|
9
|
+
* attempt writes (including `failed` ones, whose lists are deliberately
|
|
10
|
+
* compacted prose) — only a `completed` attempt owes one, and the plane
|
|
11
|
+
* judges it. A missing or malformed proof never fails the attempt or
|
|
12
|
+
* touches the commit; it only keeps the verdict off "verified".
|
|
13
|
+
*
|
|
14
|
+
* `adjudicate` is pure and does no I/O: every fact it reasons over — whether
|
|
15
|
+
* the proof artifact exists, whether the handoff and terminal diff exist,
|
|
16
|
+
* what the sealed diff-stat says, whether a screenshot's bytes actually are
|
|
17
|
+
* a PNG or JPEG, whether an approved verification command ran and what it
|
|
18
|
+
* returned — is gathered by the caller (`builder.ts`) and handed in as data,
|
|
19
|
+
* so the review itself stays testable without a filesystem or a database.
|
|
20
|
+
*/
|
|
21
|
+
import { hasForbiddenControls } from "./decision.js";
|
|
22
|
+
import { EVIDENCE_KINDS } from "./scope.js";
|
|
23
|
+
/** Shared by preflight and final adjudication; presentation text is never a rubric. */
|
|
24
|
+
function criterionContractProblems(approved, answer) {
|
|
25
|
+
if (answer === undefined)
|
|
26
|
+
return [{ kind: "missing", message: `the proof does not answer approved criterion "${approved.id}"` }];
|
|
27
|
+
const problems = [];
|
|
28
|
+
if (answer.statement.trim() !== approved.statement.trim()) {
|
|
29
|
+
problems.push({ kind: "statement", message: `criterion "${approved.id}" was signed as "${approved.statement}" and the proof restates it as "${answer.statement}"` });
|
|
30
|
+
}
|
|
31
|
+
for (const kind of approved.evidence) {
|
|
32
|
+
if (!answer.evidence.some(ref => ref.kind === kind)) {
|
|
33
|
+
problems.push({ kind: "evidence", message: `criterion "${approved.id}" requires ${kind} evidence, which the proof does not reference` });
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return problems;
|
|
37
|
+
}
|
|
38
|
+
/** Only submission defects. A truthful not-met answer is not malformed. */
|
|
39
|
+
export function proofSubmissionProblems(proof, rubric) {
|
|
40
|
+
const answers = new Map(proof.criteria.map(one => [one.id, one]));
|
|
41
|
+
const problems = rubric.flatMap(one => criterionContractProblems(one, answers.get(one.id)).map(problem => problem.message));
|
|
42
|
+
const checks = new Set(proof.checks.map(one => one.command));
|
|
43
|
+
const changed = new Set(proof.changed);
|
|
44
|
+
const shots = new Set(proof.screenshots.map(one => one.path));
|
|
45
|
+
for (const criterion of proof.criteria) {
|
|
46
|
+
if (criterion.verdict === "pending-verification" && !rubric.some(one => one.id === criterion.id && one.evidence.includes("check"))) {
|
|
47
|
+
problems.push(`criterion ${criterion.id} can wait for the final check only when its signed requirements include check evidence`);
|
|
48
|
+
}
|
|
49
|
+
for (const evidence of criterion.evidence) {
|
|
50
|
+
const resolves = evidence.kind === "check" ? checks.has(evidence.ref)
|
|
51
|
+
: evidence.kind === "changed-path" ? changed.has(evidence.ref)
|
|
52
|
+
: evidence.kind === "screenshot" ? shots.has(evidence.ref) : true;
|
|
53
|
+
if (!resolves)
|
|
54
|
+
problems.push(`criterion ${criterion.id} names a ${evidence.kind} ref that resolves to nothing — ${JSON.stringify(evidence.ref)}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
problems.push(...failedMetChecks(proof).map(failedCheckWords));
|
|
58
|
+
return problems;
|
|
59
|
+
}
|
|
60
|
+
/** Caps are BYTES of UTF-8, matching the scout report's rule: a payload is
|
|
61
|
+
* the same size whatever script it is written in. */
|
|
62
|
+
export const PROOF_LIMITS = {
|
|
63
|
+
payload: 64 * 1024,
|
|
64
|
+
criteria: 12,
|
|
65
|
+
criterionId: 40,
|
|
66
|
+
criterionStatement: 300,
|
|
67
|
+
criterionHow: 500,
|
|
68
|
+
evidencePerCriterion: 4,
|
|
69
|
+
evidenceRef: 300,
|
|
70
|
+
checks: 12,
|
|
71
|
+
checkCommand: 300,
|
|
72
|
+
checkSummary: 300,
|
|
73
|
+
changed: 64,
|
|
74
|
+
changedPath: 300,
|
|
75
|
+
caveats: 8,
|
|
76
|
+
caveat: 300,
|
|
77
|
+
screenshots: 8,
|
|
78
|
+
screenshotPath: 300,
|
|
79
|
+
screenshotCaption: 300,
|
|
80
|
+
};
|
|
81
|
+
function refuse(reason, message) {
|
|
82
|
+
return { ok: false, problems: [{ reason, message }] };
|
|
83
|
+
}
|
|
84
|
+
function describe(value) {
|
|
85
|
+
if (value === undefined)
|
|
86
|
+
return "nothing";
|
|
87
|
+
if (value === null)
|
|
88
|
+
return "null";
|
|
89
|
+
if (typeof value === "string")
|
|
90
|
+
return `a ${value.length}-char string`;
|
|
91
|
+
return `a ${Array.isArray(value) ? "array" : typeof value}`;
|
|
92
|
+
}
|
|
93
|
+
function prose(value, field, cap, problems) {
|
|
94
|
+
if (value === undefined || value === null || (typeof value === "string" && value.trim() === "")) {
|
|
95
|
+
problems.push({ reason: `missing-${field}`, message: `${field} is required` });
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
if (typeof value !== "string") {
|
|
99
|
+
problems.push({ reason: `bad-${field}`, message: `${field} must be a string (got ${describe(value)})` });
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
if (Buffer.byteLength(value, "utf8") > cap) {
|
|
103
|
+
problems.push({ reason: `${field}-too-long`, message: `${field} is over ${cap} bytes` });
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
if (hasForbiddenControls(value)) {
|
|
107
|
+
problems.push({ reason: `${field}-controls`, message: `${field} carries control characters that could become terminal escapes` });
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
return value;
|
|
111
|
+
}
|
|
112
|
+
/** A repository-relative path: no leading slash, no drive letter, no `.`/`..`
|
|
113
|
+
* segment, no backslash, no control character. The same shape
|
|
114
|
+
* `readVerifiedArtifact` enforces on an evidence key, applied here to a
|
|
115
|
+
* path an agent claims rather than one the machine already wrote. */
|
|
116
|
+
function relativePath(value, field, cap, problems) {
|
|
117
|
+
const raw = prose(value, field, cap, problems);
|
|
118
|
+
if (raw === null)
|
|
119
|
+
return null;
|
|
120
|
+
const segments = raw.split("/");
|
|
121
|
+
const wellFormed = !raw.startsWith("/") &&
|
|
122
|
+
segments.every(segment => segment.length > 0 && segment !== "." && segment !== ".." && !segment.includes("\\"));
|
|
123
|
+
if (!wellFormed) {
|
|
124
|
+
problems.push({ reason: `${field}-not-relative`, message: `${field} must be a normalized repository-relative path (got ${describe(value)})` });
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
return raw;
|
|
128
|
+
}
|
|
129
|
+
/** `evidence` is optional on a criterion (absent → `[]`, exactly like every
|
|
130
|
+
* other list here) — the mandatory PRESENCE of an answer for a SIGNED
|
|
131
|
+
* criterion is `adjudicate`'s concern, not the parser's; this only proves
|
|
132
|
+
* the shape of what is there. */
|
|
133
|
+
function parseEvidenceRefs(value, field, problems) {
|
|
134
|
+
if (value === undefined || value === null)
|
|
135
|
+
return [];
|
|
136
|
+
if (!Array.isArray(value)) {
|
|
137
|
+
problems.push({ reason: `bad-${field}`, message: `${field} must be an array (got ${describe(value)})` });
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
if (value.length > PROOF_LIMITS.evidencePerCriterion) {
|
|
141
|
+
problems.push({ reason: `${field}-too-many`, message: `${field} lists ${value.length} — cap is ${PROOF_LIMITS.evidencePerCriterion}` });
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
const refs = [];
|
|
145
|
+
let bad = false;
|
|
146
|
+
for (const [index, entry] of value.entries()) {
|
|
147
|
+
if (typeof entry !== "object" || entry === null || Array.isArray(entry)) {
|
|
148
|
+
problems.push({ reason: `${field}[${index}]-shape`, message: `${field}[${index}] must be an object` });
|
|
149
|
+
bad = true;
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
const one = entry;
|
|
153
|
+
const kind = one["kind"];
|
|
154
|
+
if (typeof kind !== "string" || !EVIDENCE_KINDS.includes(kind)) {
|
|
155
|
+
problems.push({
|
|
156
|
+
reason: `${field}[${index}]-bad-kind`,
|
|
157
|
+
message: `${field}[${index}].kind must draw from ${EVIDENCE_KINDS.join(", ")} (got ${describe(kind)})`,
|
|
158
|
+
});
|
|
159
|
+
bad = true;
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
const ref = prose(one["ref"], `${field}[${index}].ref`, PROOF_LIMITS.evidenceRef, problems);
|
|
163
|
+
if (ref === null) {
|
|
164
|
+
bad = true;
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
refs.push({ kind: kind, ref });
|
|
168
|
+
}
|
|
169
|
+
return bad ? null : refs;
|
|
170
|
+
}
|
|
171
|
+
function parseCriteria(value, problems) {
|
|
172
|
+
if (value === undefined || value === null)
|
|
173
|
+
return [];
|
|
174
|
+
if (!Array.isArray(value)) {
|
|
175
|
+
problems.push({ reason: "bad-criteria", message: `criteria must be an array (got ${describe(value)})` });
|
|
176
|
+
return [];
|
|
177
|
+
}
|
|
178
|
+
if (value.length > PROOF_LIMITS.criteria) {
|
|
179
|
+
problems.push({ reason: "criteria-too-many", message: `criteria lists ${value.length} — cap is ${PROOF_LIMITS.criteria}` });
|
|
180
|
+
return [];
|
|
181
|
+
}
|
|
182
|
+
const criteria = [];
|
|
183
|
+
const seen = new Set();
|
|
184
|
+
for (const [index, entry] of value.entries()) {
|
|
185
|
+
if (typeof entry !== "object" || entry === null || Array.isArray(entry)) {
|
|
186
|
+
problems.push({ reason: `criteria[${index}]-shape`, message: `criteria[${index}] must be an object` });
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
const one = entry;
|
|
190
|
+
const id = prose(one["id"], `criteria[${index}].id`, PROOF_LIMITS.criterionId, problems);
|
|
191
|
+
if (id !== null && seen.has(id)) {
|
|
192
|
+
problems.push({ reason: `criteria[${index}]-duplicate-id`, message: `criterion id "${id}" appears twice` });
|
|
193
|
+
}
|
|
194
|
+
else if (id !== null) {
|
|
195
|
+
seen.add(id);
|
|
196
|
+
}
|
|
197
|
+
const statement = prose(one["statement"], `criteria[${index}].statement`, PROOF_LIMITS.criterionStatement, problems);
|
|
198
|
+
const how = prose(one["how"], `criteria[${index}].how`, PROOF_LIMITS.criterionHow, problems);
|
|
199
|
+
const verdict = one["verdict"];
|
|
200
|
+
if (verdict !== "met" && verdict !== "not-met" && verdict !== "not-checked" && verdict !== "pending-verification") {
|
|
201
|
+
problems.push({
|
|
202
|
+
reason: `criteria[${index}]-bad-verdict`,
|
|
203
|
+
message: `criteria[${index}].verdict must be "met", "not-met", "not-checked", or "pending-verification" (got ${describe(verdict)})`,
|
|
204
|
+
});
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
const evidence = parseEvidenceRefs(one["evidence"], `criteria[${index}].evidence`, problems);
|
|
208
|
+
if (id === null || statement === null || how === null || evidence === null)
|
|
209
|
+
continue;
|
|
210
|
+
criteria.push({ id, statement, how, verdict, evidence });
|
|
211
|
+
}
|
|
212
|
+
return criteria;
|
|
213
|
+
}
|
|
214
|
+
function parseChecks(value, problems) {
|
|
215
|
+
if (value === undefined || value === null)
|
|
216
|
+
return [];
|
|
217
|
+
if (!Array.isArray(value)) {
|
|
218
|
+
problems.push({ reason: "bad-checks", message: `checks must be an array (got ${describe(value)})` });
|
|
219
|
+
return [];
|
|
220
|
+
}
|
|
221
|
+
if (value.length > PROOF_LIMITS.checks) {
|
|
222
|
+
problems.push({ reason: "checks-too-many", message: `checks lists ${value.length} — cap is ${PROOF_LIMITS.checks}` });
|
|
223
|
+
return [];
|
|
224
|
+
}
|
|
225
|
+
const checks = [];
|
|
226
|
+
for (const [index, entry] of value.entries()) {
|
|
227
|
+
if (typeof entry !== "object" || entry === null || Array.isArray(entry)) {
|
|
228
|
+
problems.push({ reason: `checks[${index}]-shape`, message: `checks[${index}] must be an object` });
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
const one = entry;
|
|
232
|
+
const command = prose(one["command"], `checks[${index}].command`, PROOF_LIMITS.checkCommand, problems);
|
|
233
|
+
const summary = prose(one["summary"], `checks[${index}].summary`, PROOF_LIMITS.checkSummary, problems);
|
|
234
|
+
const exitCode = one["exitCode"];
|
|
235
|
+
if (typeof exitCode !== "number" || !Number.isInteger(exitCode) || exitCode < 0 || exitCode > 255) {
|
|
236
|
+
problems.push({
|
|
237
|
+
reason: `checks[${index}]-bad-exit-code`,
|
|
238
|
+
message: `checks[${index}].exitCode must be an integer 0-255 (got ${describe(exitCode)})`,
|
|
239
|
+
});
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
if (command === null || summary === null)
|
|
243
|
+
continue;
|
|
244
|
+
checks.push({ command, summary, exitCode });
|
|
245
|
+
}
|
|
246
|
+
return checks;
|
|
247
|
+
}
|
|
248
|
+
function parseStringList(value, field, cap, itemCap, problems) {
|
|
249
|
+
if (value === undefined || value === null)
|
|
250
|
+
return [];
|
|
251
|
+
if (!Array.isArray(value)) {
|
|
252
|
+
problems.push({ reason: `bad-${field}`, message: `${field} must be an array (got ${describe(value)})` });
|
|
253
|
+
return [];
|
|
254
|
+
}
|
|
255
|
+
if (value.length > cap) {
|
|
256
|
+
problems.push({ reason: `${field}-too-many`, message: `${field} lists ${value.length} — cap is ${cap}` });
|
|
257
|
+
return [];
|
|
258
|
+
}
|
|
259
|
+
const out = [];
|
|
260
|
+
for (const [index, entry] of value.entries()) {
|
|
261
|
+
const item = prose(entry, `${field}[${index}]`, itemCap, problems);
|
|
262
|
+
if (item !== null)
|
|
263
|
+
out.push(item);
|
|
264
|
+
}
|
|
265
|
+
return out;
|
|
266
|
+
}
|
|
267
|
+
function parseScreenshots(value, problems) {
|
|
268
|
+
if (value === undefined || value === null)
|
|
269
|
+
return [];
|
|
270
|
+
if (!Array.isArray(value)) {
|
|
271
|
+
problems.push({ reason: "bad-screenshots", message: `screenshots must be an array (got ${describe(value)})` });
|
|
272
|
+
return [];
|
|
273
|
+
}
|
|
274
|
+
if (value.length > PROOF_LIMITS.screenshots) {
|
|
275
|
+
problems.push({ reason: "screenshots-too-many", message: `screenshots lists ${value.length} — cap is ${PROOF_LIMITS.screenshots}` });
|
|
276
|
+
return [];
|
|
277
|
+
}
|
|
278
|
+
const screenshots = [];
|
|
279
|
+
const seen = new Set();
|
|
280
|
+
for (const [index, entry] of value.entries()) {
|
|
281
|
+
if (typeof entry !== "object" || entry === null || Array.isArray(entry)) {
|
|
282
|
+
problems.push({ reason: `screenshots[${index}]-shape`, message: `screenshots[${index}] must be an object` });
|
|
283
|
+
continue;
|
|
284
|
+
}
|
|
285
|
+
const one = entry;
|
|
286
|
+
const path = relativePath(one["path"], `screenshots[${index}].path`, PROOF_LIMITS.screenshotPath, problems);
|
|
287
|
+
const caption = prose(one["caption"], `screenshots[${index}].caption`, PROOF_LIMITS.screenshotCaption, problems);
|
|
288
|
+
if (path !== null && seen.has(path)) {
|
|
289
|
+
problems.push({ reason: `screenshots[${index}]-duplicate-path`, message: `screenshot path "${path}" appears twice` });
|
|
290
|
+
continue;
|
|
291
|
+
}
|
|
292
|
+
if (path === null || caption === null)
|
|
293
|
+
continue;
|
|
294
|
+
seen.add(path);
|
|
295
|
+
screenshots.push({ path, caption });
|
|
296
|
+
}
|
|
297
|
+
return screenshots;
|
|
298
|
+
}
|
|
299
|
+
export function parseProof(raw) {
|
|
300
|
+
if (Buffer.byteLength(raw, "utf8") > PROOF_LIMITS.payload) {
|
|
301
|
+
return refuse("too-large", `the payload is over ${PROOF_LIMITS.payload} bytes`);
|
|
302
|
+
}
|
|
303
|
+
let parsed;
|
|
304
|
+
try {
|
|
305
|
+
parsed = JSON.parse(raw);
|
|
306
|
+
}
|
|
307
|
+
catch (error) {
|
|
308
|
+
return refuse("not-json", `the payload is not JSON: ${error instanceof Error ? error.message : String(error)}`);
|
|
309
|
+
}
|
|
310
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
311
|
+
return refuse("not-an-object", "the payload must be one JSON object");
|
|
312
|
+
}
|
|
313
|
+
const body = parsed;
|
|
314
|
+
const problems = [];
|
|
315
|
+
if (body["version"] !== 1) {
|
|
316
|
+
problems.push({ reason: "bad-version", message: `version must be 1 (got ${describe(body["version"])})` });
|
|
317
|
+
}
|
|
318
|
+
const criteria = parseCriteria(body["criteria"], problems);
|
|
319
|
+
const checks = parseChecks(body["checks"], problems);
|
|
320
|
+
const changed = parseStringList(body["changed"], "changed", PROOF_LIMITS.changed, PROOF_LIMITS.changedPath, problems);
|
|
321
|
+
const caveats = parseStringList(body["caveats"], "caveats", PROOF_LIMITS.caveats, PROOF_LIMITS.caveat, problems);
|
|
322
|
+
const screenshots = parseScreenshots(body["screenshots"], problems);
|
|
323
|
+
if (problems.length > 0)
|
|
324
|
+
return { ok: false, problems };
|
|
325
|
+
return {
|
|
326
|
+
ok: true,
|
|
327
|
+
proof: { version: 1, criteria, checks, changed, caveats, screenshots },
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
/** Re-serialize the validated shape, never the agent's raw bytes (the
|
|
331
|
+
* scout report's rule, `scout.ts`): what is stored and later hash-verified
|
|
332
|
+
* is exactly what this parser admitted, key order and all. */
|
|
333
|
+
export function serializeProof(proof) {
|
|
334
|
+
return JSON.stringify(proof, null, 2);
|
|
335
|
+
}
|
|
336
|
+
function caveatNames(caveat, id) {
|
|
337
|
+
const escaped = id.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
338
|
+
return new RegExp(`(^|[^\\p{L}\\p{N}_-])${escaped}(?![\\p{L}\\p{N}_-])`, "u").test(caveat);
|
|
339
|
+
}
|
|
340
|
+
/** Every (caveat, criterion) pair where the caveat names a criterion the
|
|
341
|
+
* proof marks `met` — the proof contradicts itself and cannot verify. In
|
|
342
|
+
* caveat order, then criterion order; `[]` for a proof at peace with its
|
|
343
|
+
* own caveats. */
|
|
344
|
+
export function blockingCaveats(proof) {
|
|
345
|
+
const met = proof.criteria.filter(one => one.verdict === "met" || one.verdict === "pending-verification");
|
|
346
|
+
const out = [];
|
|
347
|
+
proof.caveats.forEach((caveat, index) => {
|
|
348
|
+
for (const criterion of met)
|
|
349
|
+
if (caveatNames(caveat, criterion.id))
|
|
350
|
+
out.push({ caveat, index, criterionId: criterion.id });
|
|
351
|
+
});
|
|
352
|
+
return out;
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Every check a criterion marked `met` cites that the proof's own `checks`
|
|
356
|
+
* list says exited nonzero (proof preflight closure): a met criterion
|
|
357
|
+
* resting on a failing command is not evidence, it is a contradiction the
|
|
358
|
+
* adjudicator fails the row for. A not-met or not-checked criterion may
|
|
359
|
+
* cite a failed check honestly — it is reporting, not claiming — so only
|
|
360
|
+
* `met` and `pending-verification` count here: pending asserts the agent's
|
|
361
|
+
* own checks already passed. A ref that resolves to no check is a different
|
|
362
|
+
* problem (an unresolved ref), not this one. In criterion order, then
|
|
363
|
+
* evidence order.
|
|
364
|
+
*/
|
|
365
|
+
export function failedMetChecks(proof) {
|
|
366
|
+
const checksByCommand = new Map(proof.checks.map(c => [c.command, c]));
|
|
367
|
+
const out = [];
|
|
368
|
+
for (const criterion of proof.criteria) {
|
|
369
|
+
if (criterion.verdict !== "met" && criterion.verdict !== "pending-verification")
|
|
370
|
+
continue;
|
|
371
|
+
for (const evidence of criterion.evidence) {
|
|
372
|
+
if (evidence.kind !== "check")
|
|
373
|
+
continue;
|
|
374
|
+
const check = checksByCommand.get(evidence.ref);
|
|
375
|
+
if (check !== undefined && check.exitCode !== 0)
|
|
376
|
+
out.push({ criterionId: criterion.id, ref: evidence.ref, exitCode: check.exitCode });
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
return out;
|
|
380
|
+
}
|
|
381
|
+
/** The one actionable fact a failed check citation is reported as — the
|
|
382
|
+
* adjudicator's matrix detail and the agent's exit preflight say exactly
|
|
383
|
+
* this, so what the preflight refuses is what the plane would have said. */
|
|
384
|
+
export function failedCheckWords(one) {
|
|
385
|
+
return `criterion "${one.criterionId}"'s check "${one.ref}" exited ${one.exitCode}`;
|
|
386
|
+
}
|
|
387
|
+
/** The leading tag list of a caveat — `c1: …`, `c1, c4: …`, `(c2) …` — as
|
|
388
|
+
* the tokens it names before its first colon or after its parentheses.
|
|
389
|
+
* A caveat with no such prefix has no tags; its attribution rests on any
|
|
390
|
+
* known id appearing as a standalone token anywhere in it. */
|
|
391
|
+
function leadingTags(caveat) {
|
|
392
|
+
const separator = String.raw `(?:\s*[,/&+]\s*(?:and\s+)?|\s+and\s+)`;
|
|
393
|
+
const prefixed = new RegExp(String.raw `^\s*\(?\s*([\p{L}\p{N}_-]+(?:${separator}[\p{L}\p{N}_-]+)*)\s*\)?\s*:`, "u").exec(caveat);
|
|
394
|
+
if (prefixed === null)
|
|
395
|
+
return [];
|
|
396
|
+
return prefixed[1].split(new RegExp(separator, "u")).map(one => one.trim()).filter(one => one !== "");
|
|
397
|
+
}
|
|
398
|
+
export function caveatAttributionProblems(proof, signedIds = []) {
|
|
399
|
+
const own = new Set(proof.criteria.map(one => one.id));
|
|
400
|
+
const known = new Set(signedIds.length > 0 ? signedIds : [...own]);
|
|
401
|
+
const out = [];
|
|
402
|
+
proof.caveats.forEach((caveat, index) => {
|
|
403
|
+
const named = [...known].filter(id => caveatNames(caveat, id));
|
|
404
|
+
const tags = leadingTags(caveat);
|
|
405
|
+
const unknown = tags.filter(tag => !known.has(tag));
|
|
406
|
+
const answered = signedIds.length > 0 ? unknown.filter(tag => own.has(tag)) : [];
|
|
407
|
+
if (unknown.length > 0)
|
|
408
|
+
out.push({ caveat, index, kind: "unknown", tags: unknown, ...(answered.length > 0 ? { answered } : {}) });
|
|
409
|
+
else if (named.length === 0)
|
|
410
|
+
out.push({ caveat, index, kind: "unassigned", tags: [] });
|
|
411
|
+
});
|
|
412
|
+
return out;
|
|
413
|
+
}
|
|
414
|
+
/** The words one attribution problem refuses in — shared by the plane's
|
|
415
|
+
* adjudication and the agent's exit preflight so both say the same thing. */
|
|
416
|
+
export function caveatAttributionWords(problem) {
|
|
417
|
+
if (problem.kind !== "unknown") {
|
|
418
|
+
return `caveat ${problem.index + 1} names no criterion — every caveat is an exception to exactly one signed criterion, named by its exact id (an unrelated idea belongs in the handoff's follow-ups): ${problem.caveat}`;
|
|
419
|
+
}
|
|
420
|
+
const quoted = problem.tags.map(tag => `"${tag}"`).join(", ");
|
|
421
|
+
const answered = problem.answered ?? [];
|
|
422
|
+
return answered.length > 0 && answered.length === problem.tags.length
|
|
423
|
+
? `caveat ${problem.index + 1} names ${quoted}, a criterion the proof authored for itself that nobody signed — a proof-only id is no signed authority; every caveat names a signed criterion's exact id: ${problem.caveat}`
|
|
424
|
+
: `caveat ${problem.index + 1} names ${quoted}, which is no signed or answered criterion — every caveat names an exact criterion id: ${problem.caveat}`;
|
|
425
|
+
}
|
|
426
|
+
/** v39: the floor a criterion's screenshot evidence must clear — "real,
|
|
427
|
+
* not placeholder-sized" in exact numbers (scope: "meaningful byte size
|
|
428
|
+
* and at least 320 by 200 dimensions"). */
|
|
429
|
+
export const SCREENSHOT_EVIDENCE_MIN_BYTES = 1024;
|
|
430
|
+
export const SCREENSHOT_EVIDENCE_MIN_WIDTH = 320;
|
|
431
|
+
export const SCREENSHOT_EVIDENCE_MIN_HEIGHT = 200;
|
|
432
|
+
export function changedListProblems(changed, stat) {
|
|
433
|
+
if (stat === null || !stat.captured || stat.truncated)
|
|
434
|
+
return { problems: [], recoverable: false, sealed: null };
|
|
435
|
+
const claimed = new Set(changed);
|
|
436
|
+
const problems = [];
|
|
437
|
+
let unexplained = false;
|
|
438
|
+
for (const path of changed) {
|
|
439
|
+
if (stat.paths.has(path))
|
|
440
|
+
continue;
|
|
441
|
+
const destination = stat.renames?.get(path);
|
|
442
|
+
if (destination !== undefined && stat.paths.has(destination)) {
|
|
443
|
+
problems.push(`changed lists ${JSON.stringify(path)}, the old name of a move the sealed diff records once as ${JSON.stringify(destination)} — list the destination only`);
|
|
444
|
+
}
|
|
445
|
+
else {
|
|
446
|
+
unexplained = true;
|
|
447
|
+
problems.push(`changed lists ${JSON.stringify(path)}, which the sealed diff does not contain`);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
for (const path of stat.paths) {
|
|
451
|
+
if (!claimed.has(path))
|
|
452
|
+
problems.push(`changed omits ${JSON.stringify(path)}, which the sealed diff contains`);
|
|
453
|
+
}
|
|
454
|
+
const sealed = [...stat.paths].sort();
|
|
455
|
+
return { problems, recoverable: problems.length > 0 && !unexplained, sealed };
|
|
456
|
+
}
|
|
457
|
+
/** What a receipt-only correction may never touch: the exact criterion
|
|
458
|
+
* id/verdict pairs the agent submitted (comment 397, run 1648). A
|
|
459
|
+
* correction restates a signed statement or repairs an evidence reference
|
|
460
|
+
* against the exact rubric; it cannot move an answer (not-met or
|
|
461
|
+
* not-checked becoming pending-verification, or any other change of
|
|
462
|
+
* word), drop a criterion — an extra negative answer is a finding, not a
|
|
463
|
+
* formatting defect — or add one. One line per frozen pair broken. */
|
|
464
|
+
export function frozenCriterionProblems(submitted, corrected) {
|
|
465
|
+
const problems = [];
|
|
466
|
+
const after = new Map(corrected.criteria.map(one => [one.id, one.verdict]));
|
|
467
|
+
for (const prior of submitted.criteria) {
|
|
468
|
+
const verdict = after.get(prior.id);
|
|
469
|
+
if (verdict === undefined) {
|
|
470
|
+
problems.push(`criterion ${prior.id} (${prior.verdict}) was dropped; every submitted criterion and its verdict are frozen by a receipt-only correction`);
|
|
471
|
+
}
|
|
472
|
+
else if (verdict !== prior.verdict) {
|
|
473
|
+
problems.push(`criterion ${prior.id} was submitted as ${prior.verdict}; a receipt-only correction cannot change it to ${verdict}`);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
const before = new Set(submitted.criteria.map(one => one.id));
|
|
477
|
+
for (const one of corrected.criteria) {
|
|
478
|
+
if (!before.has(one.id))
|
|
479
|
+
problems.push(`criterion ${one.id} was added; a receipt-only correction answers exactly the submitted criteria`);
|
|
480
|
+
}
|
|
481
|
+
return problems;
|
|
482
|
+
}
|
|
483
|
+
/** Whether two readings of the sealed diff-stat state the same facts:
|
|
484
|
+
* the same capture standing, the same paths and the same rename
|
|
485
|
+
* provenance. Settlement reads the stat once before the receipt
|
|
486
|
+
* correction and re-reads it after that correction and after the final
|
|
487
|
+
* gate; a reading that differs is never adjudicated from the cached one. */
|
|
488
|
+
export function sameDiffStatFacts(a, b) {
|
|
489
|
+
if (a === null || b === null)
|
|
490
|
+
return a === b;
|
|
491
|
+
const restate = (one) => JSON.stringify({
|
|
492
|
+
captured: one.captured,
|
|
493
|
+
truncated: one.truncated,
|
|
494
|
+
paths: [...one.paths].sort(),
|
|
495
|
+
renames: [...(one.renames ?? [])].sort(),
|
|
496
|
+
});
|
|
497
|
+
return restate(a) === restate(b);
|
|
498
|
+
}
|
|
499
|
+
export const GOAL_ASSESSMENT_PENDING = "The saved evidence is awaiting independent goal assessment.";
|
|
500
|
+
/** Legacy folds could only lower a verdict. Direct assessment also records an
|
|
501
|
+
* ordinary wait between green machine checks and the goal judgement. */
|
|
502
|
+
export function reviewConflict(matrix, machine, verdict) {
|
|
503
|
+
return matrix.some(row => row.assessment !== undefined) ? matrix.some(row => row.review?.judgement === "contradicts")
|
|
504
|
+
: machine !== null && machine !== verdict;
|
|
505
|
+
}
|
|
506
|
+
/** Human sign-off is owed, with no other recorded evidence failure. This is
|
|
507
|
+
* presentation only; it never changes a verdict or grants acceptance. */
|
|
508
|
+
export function manualReviewOnly(proof) {
|
|
509
|
+
return proof?.verdict === "short" && (proof.matrix === undefined || (proof.matrix.some(row => row.state === "manual-review")
|
|
510
|
+
&& proof.matrix.every(row => row.state === "pass" || row.state === "manual-review")))
|
|
511
|
+
&& proof.reasons.length > 0
|
|
512
|
+
&& proof.reasons.every(reason => /^criterion "[^"]+" requires manual-review evidence — an operator must accept it before this can verify$/.test(reason));
|
|
513
|
+
}
|
|
514
|
+
/** Set equality, exactly — the "must equal the complete sealed git diff
|
|
515
|
+
* exactly" rule (v39) needs both directions, unlike the legacy subset
|
|
516
|
+
* check. */
|
|
517
|
+
function setEquals(a, b) {
|
|
518
|
+
if (a.size !== b.size)
|
|
519
|
+
return false;
|
|
520
|
+
for (const one of a)
|
|
521
|
+
if (!b.has(one))
|
|
522
|
+
return false;
|
|
523
|
+
return true;
|
|
524
|
+
}
|
|
525
|
+
/**
|
|
526
|
+
* The criterion-to-evidence matrix, computed once and shared by every
|
|
527
|
+
* surface (v39) — best-effort from whatever is available: `proof` is null
|
|
528
|
+
* when no proof parsed at all, in which case every approved criterion is
|
|
529
|
+
* simply unanswered. `[]` whenever no rubric was signed, so a
|
|
530
|
+
* grandfathered run renders no matrix at all rather than a wall of
|
|
531
|
+
* "missing" rows nobody signed up for.
|
|
532
|
+
*/
|
|
533
|
+
function criterionMatrix(approvedCriteria, proof, diffStat, screenshots) {
|
|
534
|
+
if (approvedCriteria.length === 0)
|
|
535
|
+
return [];
|
|
536
|
+
const answers = new Map((proof?.criteria ?? []).map(c => [c.id, c]));
|
|
537
|
+
const screenshotsByPath = new Map(screenshots.map(s => [s.path, s]));
|
|
538
|
+
const checksByCommand = new Map((proof?.checks ?? []).map(c => [c.command, c]));
|
|
539
|
+
const changedSet = new Set(proof?.changed ?? []);
|
|
540
|
+
// null = cannot know either way (diff unavailable or truncated) — never
|
|
541
|
+
// treated as agreement, per "an unavailable or truncated diff cannot verify".
|
|
542
|
+
const diffExact = diffStat !== null && diffStat.captured && !diffStat.truncated ? setEquals(changedSet, diffStat.paths) : null;
|
|
543
|
+
return approvedCriteria.map((approved) => {
|
|
544
|
+
const base = { id: approved.id, statement: approved.statement, requiredEvidence: approved.evidence, review: null };
|
|
545
|
+
const answer = answers.get(approved.id);
|
|
546
|
+
const contract = criterionContractProblems(approved, answer);
|
|
547
|
+
if (answer === undefined) {
|
|
548
|
+
return { ...base, state: "missing", detail: contract.map(one => one.message), answered: [] };
|
|
549
|
+
}
|
|
550
|
+
if (answer.statement.trim() !== approved.statement.trim()) {
|
|
551
|
+
return {
|
|
552
|
+
...base,
|
|
553
|
+
state: "failed",
|
|
554
|
+
detail: contract.filter(one => one.kind === "statement").map(one => one.message),
|
|
555
|
+
answered: answer.evidence,
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
const detail = [];
|
|
559
|
+
let anyMissing = false;
|
|
560
|
+
let anyFailed = false;
|
|
561
|
+
let anyManual = false;
|
|
562
|
+
// Only adjudicate's trusted successful final check resolves this state.
|
|
563
|
+
// Until then the shared row must not misleadingly read as passed.
|
|
564
|
+
if (answer.verdict === "pending-verification") {
|
|
565
|
+
anyMissing = true;
|
|
566
|
+
detail.push(`criterion "${approved.id}" is waiting for the final check`);
|
|
567
|
+
}
|
|
568
|
+
// The proof's own caveats outrank its verdict word: a criterion marked
|
|
569
|
+
// `met` that a caveat names is not met by the proof's own admission.
|
|
570
|
+
for (const blocking of proof === null ? [] : blockingCaveats(proof)) {
|
|
571
|
+
if (blocking.criterionId !== approved.id)
|
|
572
|
+
continue;
|
|
573
|
+
anyFailed = true;
|
|
574
|
+
detail.push(`criterion "${approved.id}" is marked met, but caveat ${blocking.index + 1} admits an exception to it: ${blocking.caveat}`);
|
|
575
|
+
}
|
|
576
|
+
for (const kind of approved.evidence) {
|
|
577
|
+
const ref = answer.evidence.find(e => e.kind === kind);
|
|
578
|
+
if (ref === undefined) {
|
|
579
|
+
anyMissing = true;
|
|
580
|
+
detail.push(...contract.filter(one => one.kind === "evidence" && one.message.includes(`requires ${kind} evidence`)).map(one => one.message));
|
|
581
|
+
continue;
|
|
582
|
+
}
|
|
583
|
+
if (kind === "manual-review") {
|
|
584
|
+
anyManual = true;
|
|
585
|
+
detail.push(`criterion "${approved.id}" requires manual-review evidence — an operator must accept it before this can verify`);
|
|
586
|
+
continue;
|
|
587
|
+
}
|
|
588
|
+
if (kind === "check") {
|
|
589
|
+
const check = checksByCommand.get(ref.ref);
|
|
590
|
+
if (check === undefined) {
|
|
591
|
+
anyFailed = true;
|
|
592
|
+
detail.push(`criterion "${approved.id}"'s check evidence "${ref.ref}" does not match any reported check`);
|
|
593
|
+
}
|
|
594
|
+
else if (check.exitCode !== 0) {
|
|
595
|
+
anyFailed = true;
|
|
596
|
+
detail.push(failedCheckWords({ criterionId: approved.id, ref: ref.ref, exitCode: check.exitCode }));
|
|
597
|
+
}
|
|
598
|
+
continue;
|
|
599
|
+
}
|
|
600
|
+
if (kind === "screenshot") {
|
|
601
|
+
const shot = screenshotsByPath.get(ref.ref);
|
|
602
|
+
if (shot === undefined || !shot.ok) {
|
|
603
|
+
anyFailed = true;
|
|
604
|
+
detail.push(`criterion "${approved.id}"'s screenshot evidence "${ref.ref}" could not be verified${shot?.problem ? `: ${shot.problem}` : ""}`);
|
|
605
|
+
}
|
|
606
|
+
else if ((shot.bytes ?? 0) < SCREENSHOT_EVIDENCE_MIN_BYTES ||
|
|
607
|
+
shot.dims == null ||
|
|
608
|
+
shot.dims.width < SCREENSHOT_EVIDENCE_MIN_WIDTH ||
|
|
609
|
+
shot.dims.height < SCREENSHOT_EVIDENCE_MIN_HEIGHT) {
|
|
610
|
+
anyFailed = true;
|
|
611
|
+
detail.push(`criterion "${approved.id}"'s screenshot "${ref.ref}" is missing or placeholder-sized (needs a real PNG or JPEG of at least ${SCREENSHOT_EVIDENCE_MIN_BYTES} bytes and ${SCREENSHOT_EVIDENCE_MIN_WIDTH}x${SCREENSHOT_EVIDENCE_MIN_HEIGHT})`);
|
|
612
|
+
}
|
|
613
|
+
continue;
|
|
614
|
+
}
|
|
615
|
+
// changed-path
|
|
616
|
+
if (!changedSet.has(ref.ref)) {
|
|
617
|
+
anyFailed = true;
|
|
618
|
+
detail.push(`criterion "${approved.id}"'s changed-path evidence "${ref.ref}" is not among the proof's claimed changed paths`);
|
|
619
|
+
}
|
|
620
|
+
else if (diffExact !== true) {
|
|
621
|
+
anyFailed = true;
|
|
622
|
+
detail.push(diffExact === null
|
|
623
|
+
? `criterion "${approved.id}"'s changed-path evidence cannot verify — the sealed diff is unavailable or truncated`
|
|
624
|
+
: `criterion "${approved.id}"'s changed-path evidence cannot verify — the proof's changed paths do not exactly match the sealed diff`);
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
const state = anyMissing ? "missing" : anyFailed ? "failed" : anyManual ? "manual-review" : "pass";
|
|
628
|
+
return { ...base, state, detail, answered: answer.evidence };
|
|
629
|
+
});
|
|
630
|
+
}
|
|
631
|
+
/** Evidence readiness only. The signed criteria are copied from the scope,
|
|
632
|
+
* never restated by the builder; an independent assessment must settle them. */
|
|
633
|
+
function assessCapturedEvidence(input) {
|
|
634
|
+
const completeDiff = input.diffStat !== null && input.diffStat.captured && !input.diffStat.truncated;
|
|
635
|
+
const check = input.verifyCommand;
|
|
636
|
+
const checked = check.configured && check.ran && check.exitCode === 0;
|
|
637
|
+
let machineVerdict = checked ? "verified" : "attested";
|
|
638
|
+
const problems = [];
|
|
639
|
+
if (!input.handoffPresent)
|
|
640
|
+
problems.push("the terminal handoff is missing");
|
|
641
|
+
if (!input.terminalDiffPresent || input.terminalDiffCaptureStatus !== "ok")
|
|
642
|
+
problems.push("the machine-captured diff is missing or failed to capture");
|
|
643
|
+
if (!completeDiff)
|
|
644
|
+
problems.push("the sealed diff is unavailable or truncated; the exact candidate cannot be assessed");
|
|
645
|
+
if (check.configured && !check.ran)
|
|
646
|
+
problems.push(verificationFailureWords(check.failure));
|
|
647
|
+
if (problems.length > 0)
|
|
648
|
+
machineVerdict = "short";
|
|
649
|
+
if (check.configured && check.ran && check.exitCode !== 0) {
|
|
650
|
+
machineVerdict = "refuted";
|
|
651
|
+
problems.push(`the repository's approved verification command exited ${check.exitCode}`);
|
|
652
|
+
}
|
|
653
|
+
const matrix = (input.approvedCriteria ?? []).map((criterion) => {
|
|
654
|
+
const detail = [];
|
|
655
|
+
const answered = [];
|
|
656
|
+
let missing = false, failed = false, manual = false;
|
|
657
|
+
for (const kind of criterion.evidence) {
|
|
658
|
+
if (kind === "check") {
|
|
659
|
+
if (checked)
|
|
660
|
+
answered.push({ kind, ref: input.verificationCommand ?? "approved project check" });
|
|
661
|
+
else {
|
|
662
|
+
failed ||= check.configured && check.ran && check.exitCode !== 0;
|
|
663
|
+
missing ||= !check.configured || !check.ran;
|
|
664
|
+
detail.push(`criterion "${criterion.id}" needs a passing approved project check`);
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
else if (kind === "changed-path") {
|
|
668
|
+
if (completeDiff) {
|
|
669
|
+
for (const path of input.diffStat.paths)
|
|
670
|
+
answered.push({ kind, ref: path });
|
|
671
|
+
}
|
|
672
|
+
else {
|
|
673
|
+
missing = true;
|
|
674
|
+
detail.push(`criterion "${criterion.id}" needs the complete saved source inventory`);
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
else if (kind === "screenshot") {
|
|
678
|
+
// Screenshots must have been explicitly captured and validated, never
|
|
679
|
+
// guessed from arbitrary files or inferred from a passing test.
|
|
680
|
+
const shots = input.screenshots.filter(shot => shot.ok && (shot.bytes ?? 0) >= SCREENSHOT_EVIDENCE_MIN_BYTES &&
|
|
681
|
+
shot.dims != null && shot.dims.width >= SCREENSHOT_EVIDENCE_MIN_WIDTH && shot.dims.height >= SCREENSHOT_EVIDENCE_MIN_HEIGHT);
|
|
682
|
+
if (shots.length === 0) {
|
|
683
|
+
missing = true;
|
|
684
|
+
detail.push(`criterion "${criterion.id}" needs a captured screenshot of the required behavior`);
|
|
685
|
+
}
|
|
686
|
+
else
|
|
687
|
+
for (const shot of shots)
|
|
688
|
+
answered.push({ kind, ref: shot.path });
|
|
689
|
+
}
|
|
690
|
+
else {
|
|
691
|
+
manual = true;
|
|
692
|
+
detail.push(`criterion "${criterion.id}" requires manual-review evidence — an operator must accept it before this can verify`);
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
const evidenceState = failed ? "failed" : missing ? "missing" : manual ? "manual-review" : "pass";
|
|
696
|
+
const coverage = input.reviewContext?.find(one => one.id === criterion.id);
|
|
697
|
+
return {
|
|
698
|
+
id: criterion.id, statement: criterion.statement, requiredEvidence: criterion.evidence,
|
|
699
|
+
assessment: { evidenceState, detail }, state: evidenceState === "pass" ? "missing" : evidenceState,
|
|
700
|
+
detail: [...detail, `criterion "${criterion.id}" awaits independent goal assessment`], answered, review: null,
|
|
701
|
+
...(coverage === undefined ? {} : { coverage: { state: coverage.state, inherited: coverage.inherited, items: [...coverage.items], gaps: [...coverage.gaps], priorSupport: coverage.priorSupport } }),
|
|
702
|
+
};
|
|
703
|
+
});
|
|
704
|
+
return { machineVerdict, verdict: machineVerdict === "refuted" ? "refuted" : "short", matrix,
|
|
705
|
+
reasons: problems.length > 0 ? problems : [GOAL_ASSESSMENT_PENDING] };
|
|
706
|
+
}
|
|
707
|
+
/** Only direct assessments can settle a pending goal. Machine failures and
|
|
708
|
+
* required evidence remain authoritative; this never upgrades a legacy proof. */
|
|
709
|
+
function foldGoalAssessment(base, judgements) {
|
|
710
|
+
const byId = new Map(judgements.map(one => [one.id, one]));
|
|
711
|
+
const matrix = base.matrix.map((row) => {
|
|
712
|
+
const evidence = row.assessment;
|
|
713
|
+
const judgement = byId.get(row.id);
|
|
714
|
+
const review = judgement === undefined ? row.review : { judgement: judgement.judgement, note: judgement.note, author: judgement.author };
|
|
715
|
+
const contradicted = review?.judgement === "contradicts";
|
|
716
|
+
const upheld = review?.judgement === "upholds";
|
|
717
|
+
return { ...row, review,
|
|
718
|
+
state: contradicted ? "failed" : evidence.evidenceState === "pass" ? upheld ? "pass" : "missing" : evidence.evidenceState,
|
|
719
|
+
detail: [...evidence.detail, ...(upheld ? [] : [review === null
|
|
720
|
+
? `criterion "${row.id}" awaits independent goal assessment`
|
|
721
|
+
: `${review.author} ${contradicted ? "contradicts" : "needs more evidence for"} criterion "${row.id}": ${review.note}`])],
|
|
722
|
+
};
|
|
723
|
+
});
|
|
724
|
+
const machineReady = base.machineVerdict === "verified" || base.machineVerdict === "attested";
|
|
725
|
+
const contradicted = matrix.some(row => row.review?.judgement === "contradicts");
|
|
726
|
+
const passed = machineReady && matrix.every(row => row.state === "pass" && row.review?.judgement === "upholds");
|
|
727
|
+
const verdict = base.machineVerdict === "refuted" || contradicted ? "refuted" : passed ? base.machineVerdict : "short";
|
|
728
|
+
return { ...base, verdict, matrix, reasons: passed ? ["The saved evidence and independent assessment satisfy the approved goal."]
|
|
729
|
+
: [...(!machineReady ? base.reasons : []), ...matrix.flatMap(row => row.detail)] };
|
|
730
|
+
}
|
|
731
|
+
function verificationFailureWords(failure) {
|
|
732
|
+
return (() => {
|
|
733
|
+
switch (failure) {
|
|
734
|
+
case "dependency-missing":
|
|
735
|
+
return "the approved verification command could not start because a required project executable was unavailable and no approved recovery was enabled";
|
|
736
|
+
case "setup-stale":
|
|
737
|
+
return "automatic recovery stopped because the project setup or check changed";
|
|
738
|
+
case "setup-failed":
|
|
739
|
+
return "the approved setup command failed during automatic recovery";
|
|
740
|
+
case "tracked-files-changed":
|
|
741
|
+
return "automatic recovery stopped because tracked files no longer matched the built result";
|
|
742
|
+
case "setup-changed-files":
|
|
743
|
+
return "automatic recovery stopped because the setup command changed tracked files after the build";
|
|
744
|
+
case "checkout-moved":
|
|
745
|
+
return "automatic recovery stopped because the checkout moved away from the built commit";
|
|
746
|
+
case "cleanliness-unavailable":
|
|
747
|
+
return "automatic recovery stopped because Toolroll could not confirm that the built checkout was unchanged";
|
|
748
|
+
case "dependency-still-missing":
|
|
749
|
+
return "the required project executable was still unavailable after replaying the approved setup command";
|
|
750
|
+
case "retry-spawn-failed":
|
|
751
|
+
return "the retried verification command could not be started after automatic recovery";
|
|
752
|
+
case "retry-timed-out":
|
|
753
|
+
return "the retried verification command timed out after automatic recovery";
|
|
754
|
+
case "custody-lost":
|
|
755
|
+
return "automatic recovery stopped because this worker no longer owned the build";
|
|
756
|
+
case "timed-out":
|
|
757
|
+
return "the approved verification command timed out before checks finished";
|
|
758
|
+
case "spawn-failed":
|
|
759
|
+
default:
|
|
760
|
+
return "the approved verification command could not be run";
|
|
761
|
+
}
|
|
762
|
+
})();
|
|
763
|
+
}
|
|
764
|
+
/**
|
|
765
|
+
* Ordered rules; the first that fires wins. Every reason is a sentence the
|
|
766
|
+
* surfaces print verbatim — this function is the only place that decides
|
|
767
|
+
* wording, so the task page, run page, board, CLI, and chat cards cannot
|
|
768
|
+
* drift from each other (Priority 2, the "one surface, six places" rule).
|
|
769
|
+
*
|
|
770
|
+
* v39: `input.approvedCriteria` empty runs the rules below byte for byte —
|
|
771
|
+
* the grandfathering promise. Non-empty inserts the rubric rules AFTER the
|
|
772
|
+
* diff-stat and verify-command rules and BEFORE the legacy unmet check,
|
|
773
|
+
* exactly as approved in the v2 plan: a proof that alters a signed
|
|
774
|
+
* criterion's statement is REFUTED (the same severity as any other
|
|
775
|
+
* altered term); an unanswered criterion, an evidence reference that does
|
|
776
|
+
* not resolve, or a criterion capped at manual-review is SHORT (a gap, or
|
|
777
|
+
* a review a machine cannot finish, never a lie). The legacy self-declared
|
|
778
|
+
* `verdict` field still runs afterward and can only downgrade further,
|
|
779
|
+
* never upgrade past what the evidence proved.
|
|
780
|
+
*
|
|
781
|
+
* The changed[] vs. sealed-diff exactness check (review finding, post-v39)
|
|
782
|
+
* runs GLOBALLY, ahead of the rubric rules and independent of whether any
|
|
783
|
+
* criterion cites changed-path evidence at all — a rubric with none of
|
|
784
|
+
* those must not let an untruthful or incomplete changed[] through
|
|
785
|
+
* unchecked. An unavailable or truncated diff-stat cannot prove either
|
|
786
|
+
* direction, so it can only ever produce SHORT, never REFUTED.
|
|
787
|
+
*/
|
|
788
|
+
/** An optional screenshot inventory carries no builder-authored completion
|
|
789
|
+
* claims. The existing bounded parser still validates every supplied field. */
|
|
790
|
+
export function artifactManifestOnly(proof) {
|
|
791
|
+
return proof.criteria.length === 0 && proof.checks.length === 0 && proof.changed.length === 0 && proof.caveats.length === 0;
|
|
792
|
+
}
|
|
793
|
+
export function adjudicate(input) {
|
|
794
|
+
const approvedCriteria = input.approvedCriteria ?? [];
|
|
795
|
+
if (input.directAssessment && approvedCriteria.length > 0 && (!input.proofArtifactPresent ||
|
|
796
|
+
(input.proofParse?.ok && artifactManifestOnly(input.proofParse.proof))))
|
|
797
|
+
return assessCapturedEvidence(input);
|
|
798
|
+
const coverageById = new Map((input.reviewContext ?? []).map(one => [one.id, one]));
|
|
799
|
+
const matrixOf = (proof) => criterionMatrix(approvedCriteria, proof, input.diffStat, input.screenshots).map(row => {
|
|
800
|
+
if (input.reviewContext === undefined)
|
|
801
|
+
return row;
|
|
802
|
+
const found = coverageById.get(row.id);
|
|
803
|
+
// A criterion the inventory never covered is a gap the surfaces
|
|
804
|
+
// must show, never a silent "judged from the patch".
|
|
805
|
+
const coverage = found === undefined
|
|
806
|
+
? { state: "gap", inherited: false, items: [], gaps: ["the sealed review context carries no coverage entry for this criterion"], priorSupport: "none" }
|
|
807
|
+
: { state: found.state, inherited: found.inherited, items: [...found.items], gaps: [...found.gaps], priorSupport: found.priorSupport };
|
|
808
|
+
return { ...row, coverage };
|
|
809
|
+
});
|
|
810
|
+
if (!input.proofArtifactPresent) {
|
|
811
|
+
return { verdict: "short", reasons: ["no proof was written"], matrix: matrixOf(null) };
|
|
812
|
+
}
|
|
813
|
+
if (input.proofParse === null || !input.proofParse.ok) {
|
|
814
|
+
const detail = input.proofParse !== null && !input.proofParse.ok
|
|
815
|
+
? input.proofParse.problems.map(p => p.message).join("; ")
|
|
816
|
+
: "the proof could not be read";
|
|
817
|
+
return { verdict: "short", reasons: [`the proof is malformed: ${detail}`], matrix: matrixOf(null) };
|
|
818
|
+
}
|
|
819
|
+
const submitted = input.proofParse.proof;
|
|
820
|
+
const finalCheckPassed = input.verifyCommand.configured && input.verifyCommand.ran && input.verifyCommand.exitCode === 0;
|
|
821
|
+
const pendingContractValid = proofSubmissionProblems(submitted, approvedCriteria).length === 0;
|
|
822
|
+
// Resolve only an explicit conditional claim, against THIS run's machine
|
|
823
|
+
// facts. Never infer intent from prose, modify the sealed receipt, or turn
|
|
824
|
+
// a genuine not-met/not-checked answer into success. All evidence, caveat,
|
|
825
|
+
// manual-review and artifact checks below still apply to the resolved copy.
|
|
826
|
+
const proof = {
|
|
827
|
+
...submitted,
|
|
828
|
+
criteria: submitted.criteria.map(one => one.verdict === "pending-verification" && finalCheckPassed && pendingContractValid &&
|
|
829
|
+
approvedCriteria.some(approved => approved.id === one.id && approved.evidence.includes("check") && approved.statement.trim() === one.statement.trim())
|
|
830
|
+
? { ...one, verdict: "met" } : one),
|
|
831
|
+
};
|
|
832
|
+
const matrix = matrixOf(proof);
|
|
833
|
+
if (!input.handoffPresent) {
|
|
834
|
+
return { verdict: "short", reasons: ["the terminal handoff is missing"], matrix };
|
|
835
|
+
}
|
|
836
|
+
if (!input.terminalDiffPresent || input.terminalDiffCaptureStatus === "failed") {
|
|
837
|
+
return { verdict: "short", reasons: ["the machine-captured diff is missing or failed to capture"], matrix };
|
|
838
|
+
}
|
|
839
|
+
// The claimed changed[] must equal the COMPLETE sealed diff exactly —
|
|
840
|
+
// globally, whether or not any signed criterion cites changed-path
|
|
841
|
+
// evidence at all (a rubric with none of those would otherwise let an
|
|
842
|
+
// untruthful or incomplete changed[] through unchecked). An unavailable
|
|
843
|
+
// or truncated stat cannot prove either direction, so it cannot verify:
|
|
844
|
+
// short, never refuted — "we could not check" is not "the claim is
|
|
845
|
+
// false" (the same ruling the verify-command facts make below).
|
|
846
|
+
if (input.diffStat === null || !input.diffStat.captured || input.diffStat.truncated) {
|
|
847
|
+
return {
|
|
848
|
+
verdict: "short",
|
|
849
|
+
reasons: ["the sealed diff is unavailable or truncated; the claimed changed paths cannot be verified against it"],
|
|
850
|
+
matrix,
|
|
851
|
+
};
|
|
852
|
+
}
|
|
853
|
+
{
|
|
854
|
+
const diffPaths = input.diffStat.paths;
|
|
855
|
+
const claimed = new Set(proof.changed);
|
|
856
|
+
const overclaimed = proof.changed.filter(path => !diffPaths.has(path));
|
|
857
|
+
if (overclaimed.length > 0) {
|
|
858
|
+
return {
|
|
859
|
+
verdict: "refuted",
|
|
860
|
+
reasons: [`claimed changed path${overclaimed.length > 1 ? "s" : ""} not in the sealed diff: ${overclaimed.join(", ")}`],
|
|
861
|
+
matrix,
|
|
862
|
+
};
|
|
863
|
+
}
|
|
864
|
+
const underclaimed = [...diffPaths].filter(path => !claimed.has(path));
|
|
865
|
+
if (underclaimed.length > 0) {
|
|
866
|
+
return {
|
|
867
|
+
verdict: "short",
|
|
868
|
+
reasons: [`the sealed diff touched path${underclaimed.length > 1 ? "s" : ""} the proof never claimed as changed: ${underclaimed.join(", ")}`],
|
|
869
|
+
matrix,
|
|
870
|
+
};
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
// Altering a signed requirement is an integrity contradiction, not an
|
|
874
|
+
// evidence gap. It outranks an environment failure: an unavailable check
|
|
875
|
+
// must never downgrade changed authority from refuted to short.
|
|
876
|
+
if (approvedCriteria.length > 0) {
|
|
877
|
+
const restated = matrix.filter(row => row.detail.some(d => d.includes("was signed as")));
|
|
878
|
+
if (restated.length > 0) {
|
|
879
|
+
return { verdict: "refuted", reasons: restated.flatMap(row => row.detail), matrix };
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
// A proof whose caveat admits an exception to a criterion it marks met
|
|
883
|
+
// contradicts itself (atomic authority closure): the verdict must agree
|
|
884
|
+
// with the caveats, and a self-disagreeing proof is conflicting
|
|
885
|
+
// evidence — refuted, never verified, whether or not a rubric was
|
|
886
|
+
// signed. It ranks with the altered-statement rule above: an
|
|
887
|
+
// environment failure must never soften a contradiction to short.
|
|
888
|
+
{
|
|
889
|
+
const blocking = blockingCaveats(proof);
|
|
890
|
+
if (blocking.length > 0) {
|
|
891
|
+
return {
|
|
892
|
+
verdict: "refuted",
|
|
893
|
+
reasons: blocking.map(one => `criterion "${one.criterionId}" is marked met, but caveat ${one.index + 1} admits an exception to it: ${one.caveat}`),
|
|
894
|
+
matrix,
|
|
895
|
+
};
|
|
896
|
+
}
|
|
897
|
+
// EVERY caveat is attributed (final authority closure): one that names
|
|
898
|
+
// no known criterion, or a tag nobody signed, is an exception the
|
|
899
|
+
// verdict cannot be reconciled with — the proof's own words do not
|
|
900
|
+
// say which criterion they qualify, and the plane refutes rather than
|
|
901
|
+
// guesses. Known ids are the signed rubric's alone when one was
|
|
902
|
+
// signed (a proof-authored extra id is no authority); the proof's own
|
|
903
|
+
// only when nothing was signed.
|
|
904
|
+
const unattributed = caveatAttributionProblems(proof, approvedCriteria.map(one => one.id));
|
|
905
|
+
if (unattributed.length > 0) {
|
|
906
|
+
return { verdict: "refuted", reasons: unattributed.map(caveatAttributionWords), matrix };
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
// A command that could not exercise the product is missing evidence, not
|
|
910
|
+
// contradictory evidence. This precedes criterion-state checks so the
|
|
911
|
+
// actionable environment cause is never buried under a generic gap.
|
|
912
|
+
if (input.verifyCommand.configured && !input.verifyCommand.ran) {
|
|
913
|
+
const reason = verificationFailureWords(input.verifyCommand.failure);
|
|
914
|
+
return { verdict: "short", reasons: [reason], matrix };
|
|
915
|
+
}
|
|
916
|
+
if (input.verifyCommand.configured && input.verifyCommand.ran && input.verifyCommand.exitCode !== 0) {
|
|
917
|
+
return {
|
|
918
|
+
verdict: "refuted",
|
|
919
|
+
reasons: [
|
|
920
|
+
input.verifyCommand.setupReplayed === true
|
|
921
|
+
? `the repository's approved verification command exited ${input.verifyCommand.exitCode} after the approved setup command was replayed`
|
|
922
|
+
: `the repository's approved verification command exited ${input.verifyCommand.exitCode}`,
|
|
923
|
+
],
|
|
924
|
+
matrix,
|
|
925
|
+
};
|
|
926
|
+
}
|
|
927
|
+
if (approvedCriteria.length > 0) {
|
|
928
|
+
// manual-review folds in here too (v39 review finding): a row that
|
|
929
|
+
// needs a human's eyes is never machine-verifiable, so it must never
|
|
930
|
+
// reach "verified" or "attested" on its own — it stays "short" until
|
|
931
|
+
// an operator explicitly accepts the proof (the same act that already
|
|
932
|
+
// lets a short/refuted run read as done).
|
|
933
|
+
const unresolved = matrix.filter(row => row.state === "missing" || row.state === "failed" || row.state === "manual-review");
|
|
934
|
+
if (unresolved.length > 0) {
|
|
935
|
+
return { verdict: "short", reasons: unresolved.flatMap(row => row.detail), matrix };
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
const unmet = proof.criteria.filter(c => c.verdict !== "met");
|
|
939
|
+
if (unmet.length > 0) {
|
|
940
|
+
return {
|
|
941
|
+
verdict: "short",
|
|
942
|
+
reasons: unmet.map(c => `criterion "${c.statement}" is ${c.verdict === "not-met" ? "not met" : c.verdict === "pending-verification" ? "waiting for the final check" : "not checked"}`),
|
|
943
|
+
matrix,
|
|
944
|
+
};
|
|
945
|
+
}
|
|
946
|
+
const badScreenshots = input.screenshots.filter(s => !s.ok);
|
|
947
|
+
if (badScreenshots.length > 0) {
|
|
948
|
+
return {
|
|
949
|
+
verdict: "short",
|
|
950
|
+
reasons: badScreenshots.map(s => `claimed screenshot "${s.path}" could not be verified${s.problem ? `: ${s.problem}` : ""}`),
|
|
951
|
+
matrix,
|
|
952
|
+
};
|
|
953
|
+
}
|
|
954
|
+
if (input.verifyCommand.configured && input.verifyCommand.ran && input.verifyCommand.exitCode === 0) {
|
|
955
|
+
return {
|
|
956
|
+
verdict: "verified",
|
|
957
|
+
reasons: [
|
|
958
|
+
input.verifyCommand.setupReplayed === true
|
|
959
|
+
? "the approved verification command passed after the approved setup command ran"
|
|
960
|
+
: "the approved verification command passed",
|
|
961
|
+
],
|
|
962
|
+
matrix,
|
|
963
|
+
};
|
|
964
|
+
}
|
|
965
|
+
return { verdict: "attested", reasons: ["the proof agrees with the sealed diff; no verification command is configured to re-run"], matrix };
|
|
966
|
+
}
|
|
967
|
+
/**
|
|
968
|
+
* Folds an independent reviewer's per-criterion judgements into an ALREADY
|
|
969
|
+
* ADJUDICATED result (v40, evidence-review-v1) — pure, and never re-derives
|
|
970
|
+
* verdict facts that were not persisted (verify-command outcome, screenshot
|
|
971
|
+
* bytes): it folds over the STORED result, it does not re-run `adjudicate`.
|
|
972
|
+
*
|
|
973
|
+
* Three rules, and they are the whole contract:
|
|
974
|
+
* - `contradicts` -> refuted. A second reader saying a signed term is not
|
|
975
|
+
* met is the same severity as a proof that altered the term: the
|
|
976
|
+
* matching row becomes `failed`, with the reviewer's note as an
|
|
977
|
+
* attributed `detail` line.
|
|
978
|
+
* - `cannot-tell` changes nothing. Recorded and rendered, never moves the
|
|
979
|
+
* verdict — "we could not check" is not "the claim is false" (the same
|
|
980
|
+
* maxim `adjudicate` already lives by for an unavailable diff or a
|
|
981
|
+
* failed verify-command attempt).
|
|
982
|
+
* - `upholds` never upgrades. A `short` (or `attested`) run stays exactly
|
|
983
|
+
* what it was — this is the law that stops a second model laundering a
|
|
984
|
+
* bad proof, and the reason `foldReview` can only LOWER a verdict, never
|
|
985
|
+
* raise one.
|
|
986
|
+
*
|
|
987
|
+
* A judgement naming a criterion absent from `base.matrix` is ignored —
|
|
988
|
+
* `parseReview` already refuses a payload naming an unsigned id, so this is
|
|
989
|
+
* belt and suspenders, never a live path.
|
|
990
|
+
*/
|
|
991
|
+
export function foldReview(base, judgements) {
|
|
992
|
+
if (judgements.length === 0)
|
|
993
|
+
return base;
|
|
994
|
+
if (base.matrix.length > 0 && base.matrix.every(row => row.assessment !== undefined)) {
|
|
995
|
+
return foldGoalAssessment(base, judgements);
|
|
996
|
+
}
|
|
997
|
+
const byId = new Map(judgements.map(j => [j.id, j]));
|
|
998
|
+
let anyContradiction = false;
|
|
999
|
+
const matrix = base.matrix.map((row) => {
|
|
1000
|
+
const judgement = byId.get(row.id);
|
|
1001
|
+
if (judgement === undefined)
|
|
1002
|
+
return row;
|
|
1003
|
+
const review = { judgement: judgement.judgement, note: judgement.note, author: judgement.author };
|
|
1004
|
+
if (judgement.judgement !== "contradicts")
|
|
1005
|
+
return { ...row, review };
|
|
1006
|
+
anyContradiction = true;
|
|
1007
|
+
return {
|
|
1008
|
+
...row,
|
|
1009
|
+
state: "failed",
|
|
1010
|
+
detail: [...row.detail, `${judgement.author} contradicts criterion "${row.id}": ${judgement.note}`],
|
|
1011
|
+
review,
|
|
1012
|
+
};
|
|
1013
|
+
});
|
|
1014
|
+
if (!anyContradiction)
|
|
1015
|
+
return { ...base, matrix };
|
|
1016
|
+
const contradictions = matrix.filter(row => row.review?.judgement === "contradicts");
|
|
1017
|
+
return {
|
|
1018
|
+
verdict: "refuted",
|
|
1019
|
+
reasons: [...base.reasons, ...contradictions.flatMap(row => (row.review === null ? [] : [`${row.review.author} contradicts criterion "${row.id}": ${row.review.note}`]))],
|
|
1020
|
+
matrix,
|
|
1021
|
+
};
|
|
1022
|
+
}
|
|
1023
|
+
export function semanticCoverage(matrix, policy) {
|
|
1024
|
+
const judged = (word) => matrix.filter(row => row.review?.judgement === word).map(row => row.id);
|
|
1025
|
+
const upheld = judged("upholds");
|
|
1026
|
+
const contradicted = judged("contradicts");
|
|
1027
|
+
const uncertain = judged("cannot-tell");
|
|
1028
|
+
const unreviewed = matrix.filter(row => row.review === null).map(row => row.id);
|
|
1029
|
+
// Every named gap is listed, including one on a row the patch still
|
|
1030
|
+
// judges: what could not be sealed is said, whatever else was.
|
|
1031
|
+
const contextGaps = matrix.flatMap(row => (row.coverage !== undefined && row.coverage.gaps.length > 0 ? [{ id: row.id, gaps: [...row.coverage.gaps] }] : []));
|
|
1032
|
+
const reviewed = matrix.length - unreviewed.length;
|
|
1033
|
+
return {
|
|
1034
|
+
policy,
|
|
1035
|
+
required: policy === "strict" || matrix.some(row => row.assessment !== undefined),
|
|
1036
|
+
total: matrix.length,
|
|
1037
|
+
upheld,
|
|
1038
|
+
contradicted,
|
|
1039
|
+
uncertain,
|
|
1040
|
+
unreviewed,
|
|
1041
|
+
contextGaps,
|
|
1042
|
+
satisfied: matrix.length === 0 || reviewed === 0 ? null : upheld.length === matrix.length,
|
|
1043
|
+
};
|
|
1044
|
+
}
|
|
1045
|
+
/** The coverage in plain words — one line every surface prints the same
|
|
1046
|
+
* way. `[]` for a run with no rubric: nothing to cover. */
|
|
1047
|
+
export function coverageWords(coverage) {
|
|
1048
|
+
if (coverage.total === 0)
|
|
1049
|
+
return [];
|
|
1050
|
+
const lines = [];
|
|
1051
|
+
// No review has folded: nothing is waited for (model review is retired),
|
|
1052
|
+
// so only sealed context gaps are worth a line.
|
|
1053
|
+
if (coverage.satisfied === null) {
|
|
1054
|
+
for (const gap of coverage.contextGaps)
|
|
1055
|
+
lines.push(`context gap ${gap.id}: ${gap.gaps.join("; ")}`);
|
|
1056
|
+
return lines;
|
|
1057
|
+
}
|
|
1058
|
+
const standing = coverage.satisfied === null
|
|
1059
|
+
? coverage.required ? "required under strict quality — no independent review has settled yet" : "independent review is optional under default quality — none has settled"
|
|
1060
|
+
: coverage.satisfied
|
|
1061
|
+
? coverage.required ? "required under strict quality — satisfied" : "optional under default quality — every criterion upheld"
|
|
1062
|
+
: coverage.required
|
|
1063
|
+
? `required under strict quality — NOT satisfied (${[...(coverage.uncertain.length > 0 ? [`cannot-tell never counts: ${coverage.uncertain.join(", ")}`] : []), ...(coverage.contradicted.length > 0 ? [`contradicted: ${coverage.contradicted.join(", ")}`] : []), ...(coverage.unreviewed.length > 0 ? [`unreviewed: ${coverage.unreviewed.join(", ")}`] : [])].join("; ")})`
|
|
1064
|
+
: `optional under default quality — ${coverage.upheld.length}/${coverage.total} upheld${coverage.uncertain.length > 0 ? `, cannot-tell: ${coverage.uncertain.join(", ")}` : ""}${coverage.contradicted.length > 0 ? `, contradicted: ${coverage.contradicted.join(", ")}` : ""}`;
|
|
1065
|
+
lines.push(`semantic coverage: ${coverage.upheld.length}/${coverage.total} upheld by an independent reviewer — ${coverage.policy === "default" && coverage.required ? standing.replaceAll("under strict quality", "for goal assessment") : standing}`);
|
|
1066
|
+
for (const gap of coverage.contextGaps)
|
|
1067
|
+
lines.push(`context gap ${gap.id}: ${gap.gaps.join("; ")}`);
|
|
1068
|
+
return lines;
|
|
1069
|
+
}
|
|
1070
|
+
/** The pass fraction of a criterion matrix — the one number every list
|
|
1071
|
+
* surface (board, chat) needs, shared so "N/M criteria" is computed in
|
|
1072
|
+
* exactly one place (v40 closes the two hand-rolled copies). */
|
|
1073
|
+
export function passFraction(matrix) {
|
|
1074
|
+
return { passed: matrix.filter(row => row.state === "pass").length, total: matrix.length };
|
|
1075
|
+
}
|
|
1076
|
+
/** The matrix in plain lines, for a text surface (the CLI, `brief`) — the
|
|
1077
|
+
* same shared vocabulary `criterionMatrixHtml` renders in the console, so
|
|
1078
|
+
* the words never drift between the two (Priority 2's rule, extended). */
|
|
1079
|
+
export function matrixWords(matrix) {
|
|
1080
|
+
if (matrix.length === 0)
|
|
1081
|
+
return [];
|
|
1082
|
+
const lines = [" acceptance matrix"];
|
|
1083
|
+
for (const row of matrix) {
|
|
1084
|
+
lines.push(` [${row.state}] ${row.id}: ${row.statement} (requires: ${row.requiredEvidence.join(", ")})`);
|
|
1085
|
+
if (row.answered.length > 0) {
|
|
1086
|
+
lines.push(` answered: ${row.answered.map(a => `${a.kind}: ${a.ref}`).join("; ")}`);
|
|
1087
|
+
}
|
|
1088
|
+
for (const detail of row.detail)
|
|
1089
|
+
lines.push(` ${detail}`);
|
|
1090
|
+
if (row.review !== null) {
|
|
1091
|
+
lines.push(` review (${row.review.author}): ${row.review.judgement} — ${row.review.note}`);
|
|
1092
|
+
}
|
|
1093
|
+
if (row.coverage !== undefined) {
|
|
1094
|
+
lines.push(` context: ${coverageStateWords(row.coverage)}`);
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
return lines;
|
|
1098
|
+
}
|
|
1099
|
+
/** One criterion's context standing in words — shared by the CLI matrix
|
|
1100
|
+
* and the console badge title so the two never drift. */
|
|
1101
|
+
export function coverageStateWords(coverage) {
|
|
1102
|
+
const prior = coverage.priorSupport === "eligible" ? "; an earlier review of the source may be weighed as context (never as this run's verdict)" : coverage.priorSupport === "invalid" ? "; the earlier source review no longer supports it" : "";
|
|
1103
|
+
switch (coverage.state) {
|
|
1104
|
+
case "patch":
|
|
1105
|
+
return `judged from this run's own patch${coverage.gaps.length > 0 ? ` (${coverage.gaps.join("; ")})` : ""}${prior}`;
|
|
1106
|
+
case "context":
|
|
1107
|
+
return `inherited — sealed context ${coverage.items.join(", ")}${prior}`;
|
|
1108
|
+
case "gap":
|
|
1109
|
+
return `GAP — inherited context is missing: ${coverage.gaps.join("; ")}${coverage.items.length > 0 ? ` (partial: ${coverage.items.join(", ")})` : ""}${prior}`;
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
/** Plain words for a verdict, shared by every surface (the `summary.ts`
|
|
1113
|
+
* pattern) so the task page, run page, board, CLI, and chat cannot say
|
|
1114
|
+
* three different things about the same run. */
|
|
1115
|
+
export function verdictWords(verdict, reasons) {
|
|
1116
|
+
const detail = reasons.length > 0 ? reasons.join("; ") : "";
|
|
1117
|
+
switch (verdict) {
|
|
1118
|
+
case "verified":
|
|
1119
|
+
return { word: "complete — verified", detail };
|
|
1120
|
+
case "attested":
|
|
1121
|
+
return { word: "complete — evidence attested", detail };
|
|
1122
|
+
case "short":
|
|
1123
|
+
return { word: "missing evidence", detail };
|
|
1124
|
+
case "refuted":
|
|
1125
|
+
return { word: "conflicting evidence", detail };
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
/** The `data-dispatch-status` token a page's CSS and tests key off. */
|
|
1129
|
+
export function dispatchStatusToken(verdict) {
|
|
1130
|
+
switch (verdict) {
|
|
1131
|
+
case "verified":
|
|
1132
|
+
return "complete-verified";
|
|
1133
|
+
case "attested":
|
|
1134
|
+
return "complete-with-evidence";
|
|
1135
|
+
case "short":
|
|
1136
|
+
return "needs-verification";
|
|
1137
|
+
case "refuted":
|
|
1138
|
+
return "proof-refuted";
|
|
1139
|
+
}
|
|
1140
|
+
}
|