oh-my-super-agent 0.1.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/.agents/agents/advisor.md +15 -0
- package/.agents/agents/conductor.md +32 -0
- package/.agents/agents/dreamer.md +13 -0
- package/.agents/agents/executor.md +17 -0
- package/.agents/agents/grader.md +15 -0
- package/.agents/config/endurance-policy.json +8 -0
- package/.agents/config/oms-config.json +119 -0
- package/.agents/config/release-policy.json +12 -0
- package/.agents/config/secret-scan-policy.json +7 -0
- package/.agents/config/verification-plan.json +33 -0
- package/.agents/registry.json +11 -0
- package/.agents/rules/constitution.md +7 -0
- package/.agents/skills/conductor/SKILL.md +23 -0
- package/.agents/skills/dream/SKILL.md +14 -0
- package/.agents/skills/executor/SKILL.md +13 -0
- package/.agents/skills/oms/SKILL.md +52 -0
- package/.agents/skills/quota-aware/SKILL.md +12 -0
- package/.agents/skills/schedule/SKILL.md +13 -0
- package/.agents/skills/team-protocol/SKILL.md +20 -0
- package/LICENSE +21 -0
- package/README.md +146 -0
- package/dist/src/audit/run-ledger.d.ts +183 -0
- package/dist/src/audit/run-ledger.js +1655 -0
- package/dist/src/audit/run-ledger.js.map +1 -0
- package/dist/src/cli.d.ts +61 -0
- package/dist/src/cli.js +2096 -0
- package/dist/src/cli.js.map +1 -0
- package/dist/src/commands/github-queue-probe.d.ts +26 -0
- package/dist/src/commands/github-queue-probe.js +51 -0
- package/dist/src/commands/github-queue-probe.js.map +1 -0
- package/dist/src/commands/trace-fixture.d.ts +20 -0
- package/dist/src/commands/trace-fixture.js +72 -0
- package/dist/src/commands/trace-fixture.js.map +1 -0
- package/dist/src/conductor/activity.d.ts +46 -0
- package/dist/src/conductor/activity.js +224 -0
- package/dist/src/conductor/activity.js.map +1 -0
- package/dist/src/conductor/bridge.d.ts +53 -0
- package/dist/src/conductor/bridge.js +652 -0
- package/dist/src/conductor/bridge.js.map +1 -0
- package/dist/src/conductor/notifications.d.ts +59 -0
- package/dist/src/conductor/notifications.js +381 -0
- package/dist/src/conductor/notifications.js.map +1 -0
- package/dist/src/conductor/recovery.d.ts +29 -0
- package/dist/src/conductor/recovery.js +198 -0
- package/dist/src/conductor/recovery.js.map +1 -0
- package/dist/src/conductor/start.d.ts +73 -0
- package/dist/src/conductor/start.js +519 -0
- package/dist/src/conductor/start.js.map +1 -0
- package/dist/src/conductor/waiter.d.ts +88 -0
- package/dist/src/conductor/waiter.js +427 -0
- package/dist/src/conductor/waiter.js.map +1 -0
- package/dist/src/config.d.ts +9 -0
- package/dist/src/config.js +40 -0
- package/dist/src/config.js.map +1 -0
- package/dist/src/dispatch/advice-source.d.ts +61 -0
- package/dist/src/dispatch/advice-source.js +198 -0
- package/dist/src/dispatch/advice-source.js.map +1 -0
- package/dist/src/dispatch/advice-store.d.ts +41 -0
- package/dist/src/dispatch/advice-store.js +201 -0
- package/dist/src/dispatch/advice-store.js.map +1 -0
- package/dist/src/dispatch/advice.d.ts +34 -0
- package/dist/src/dispatch/advice.js +371 -0
- package/dist/src/dispatch/advice.js.map +1 -0
- package/dist/src/dispatch/authority.d.ts +124 -0
- package/dist/src/dispatch/authority.js +1202 -0
- package/dist/src/dispatch/authority.js.map +1 -0
- package/dist/src/dispatch/intent.d.ts +66 -0
- package/dist/src/dispatch/intent.js +71 -0
- package/dist/src/dispatch/intent.js.map +1 -0
- package/dist/src/dispatch/runtime-authority.d.ts +42 -0
- package/dist/src/dispatch/runtime-authority.js +52 -0
- package/dist/src/dispatch/runtime-authority.js.map +1 -0
- package/dist/src/doctor.d.ts +20 -0
- package/dist/src/doctor.js +85 -0
- package/dist/src/doctor.js.map +1 -0
- package/dist/src/domain/records.d.ts +44 -0
- package/dist/src/domain/records.js +138 -0
- package/dist/src/domain/records.js.map +1 -0
- package/dist/src/domain/role-policy.d.ts +67 -0
- package/dist/src/domain/role-policy.js +547 -0
- package/dist/src/domain/role-policy.js.map +1 -0
- package/dist/src/events/bus.d.ts +32 -0
- package/dist/src/events/bus.js +115 -0
- package/dist/src/events/bus.js.map +1 -0
- package/dist/src/events/kinds.d.ts +126 -0
- package/dist/src/events/kinds.js +14 -0
- package/dist/src/events/kinds.js.map +1 -0
- package/dist/src/evidence/immutable-file.d.ts +12 -0
- package/dist/src/evidence/immutable-file.js +141 -0
- package/dist/src/evidence/immutable-file.js.map +1 -0
- package/dist/src/evidence/store.d.ts +125 -0
- package/dist/src/evidence/store.js +1189 -0
- package/dist/src/evidence/store.js.map +1 -0
- package/dist/src/evidence/verdict.d.ts +17 -0
- package/dist/src/evidence/verdict.js +23 -0
- package/dist/src/evidence/verdict.js.map +1 -0
- package/dist/src/generation/agent.d.ts +5 -0
- package/dist/src/generation/agent.js +34 -0
- package/dist/src/generation/agent.js.map +1 -0
- package/dist/src/generation/frontmatter.d.ts +7 -0
- package/dist/src/generation/frontmatter.js +33 -0
- package/dist/src/generation/frontmatter.js.map +1 -0
- package/dist/src/github/adapter.d.ts +44 -0
- package/dist/src/github/adapter.js +592 -0
- package/dist/src/github/adapter.js.map +1 -0
- package/dist/src/github/dispatch-eligibility.d.ts +9 -0
- package/dist/src/github/dispatch-eligibility.js +94 -0
- package/dist/src/github/dispatch-eligibility.js.map +1 -0
- package/dist/src/github/projector.d.ts +23 -0
- package/dist/src/github/projector.js +181 -0
- package/dist/src/github/projector.js.map +1 -0
- package/dist/src/github/review.d.ts +39 -0
- package/dist/src/github/review.js +1450 -0
- package/dist/src/github/review.js.map +1 -0
- package/dist/src/github/sync.d.ts +21 -0
- package/dist/src/github/sync.js +518 -0
- package/dist/src/github/sync.js.map +1 -0
- package/dist/src/github/types.d.ts +134 -0
- package/dist/src/github/types.js +11 -0
- package/dist/src/github/types.js.map +1 -0
- package/dist/src/goals/artifact-store.d.ts +31 -0
- package/dist/src/goals/artifact-store.js +241 -0
- package/dist/src/goals/artifact-store.js.map +1 -0
- package/dist/src/goals/authority.d.ts +112 -0
- package/dist/src/goals/authority.js +621 -0
- package/dist/src/goals/authority.js.map +1 -0
- package/dist/src/goals/native-snapshot.d.ts +12 -0
- package/dist/src/goals/native-snapshot.js +53 -0
- package/dist/src/goals/native-snapshot.js.map +1 -0
- package/dist/src/goals/runtime-authority.d.ts +7 -0
- package/dist/src/goals/runtime-authority.js +20 -0
- package/dist/src/goals/runtime-authority.js.map +1 -0
- package/dist/src/health/release-assessment.d.ts +11 -0
- package/dist/src/health/release-assessment.js +83 -0
- package/dist/src/health/release-assessment.js.map +1 -0
- package/dist/src/health/release-policy.d.ts +16 -0
- package/dist/src/health/release-policy.js +40 -0
- package/dist/src/health/release-policy.js.map +1 -0
- package/dist/src/hooks/native.d.ts +30 -0
- package/dist/src/hooks/native.js +495 -0
- package/dist/src/hooks/native.js.map +1 -0
- package/dist/src/hooks/provider-runtime.d.ts +55 -0
- package/dist/src/hooks/provider-runtime.js +479 -0
- package/dist/src/hooks/provider-runtime.js.map +1 -0
- package/dist/src/hooks/provider.d.ts +102 -0
- package/dist/src/hooks/provider.js +358 -0
- package/dist/src/hooks/provider.js.map +1 -0
- package/dist/src/live/contact-journal.d.ts +73 -0
- package/dist/src/live/contact-journal.js +439 -0
- package/dist/src/live/contact-journal.js.map +1 -0
- package/dist/src/live-probes/gh-adapter.d.ts +28 -0
- package/dist/src/live-probes/gh-adapter.js +188 -0
- package/dist/src/live-probes/gh-adapter.js.map +1 -0
- package/dist/src/live-probes/github-queue.d.ts +1 -0
- package/dist/src/live-probes/github-queue.js +2 -0
- package/dist/src/live-probes/github-queue.js.map +1 -0
- package/dist/src/notifications/slack-request.d.ts +13 -0
- package/dist/src/notifications/slack-request.js +40 -0
- package/dist/src/notifications/slack-request.js.map +1 -0
- package/dist/src/outbox/processor.d.ts +27 -0
- package/dist/src/outbox/processor.js +63 -0
- package/dist/src/outbox/processor.js.map +1 -0
- package/dist/src/outbox/store.d.ts +158 -0
- package/dist/src/outbox/store.js +834 -0
- package/dist/src/outbox/store.js.map +1 -0
- package/dist/src/package-root.d.ts +1 -0
- package/dist/src/package-root.js +26 -0
- package/dist/src/package-root.js.map +1 -0
- package/dist/src/project.d.ts +12 -0
- package/dist/src/project.js +60 -0
- package/dist/src/project.js.map +1 -0
- package/dist/src/projection/generated-file.d.ts +15 -0
- package/dist/src/projection/generated-file.js +72 -0
- package/dist/src/projection/generated-file.js.map +1 -0
- package/dist/src/projection/project-workspace.d.ts +11 -0
- package/dist/src/projection/project-workspace.js +94 -0
- package/dist/src/projection/project-workspace.js.map +1 -0
- package/dist/src/projection/registry.d.ts +15 -0
- package/dist/src/projection/registry.js +59 -0
- package/dist/src/projection/registry.js.map +1 -0
- package/dist/src/projection/safe-path.d.ts +4 -0
- package/dist/src/projection/safe-path.js +106 -0
- package/dist/src/projection/safe-path.js.map +1 -0
- package/dist/src/providers/claude-interactive.d.ts +38 -0
- package/dist/src/providers/claude-interactive.js +287 -0
- package/dist/src/providers/claude-interactive.js.map +1 -0
- package/dist/src/providers/claude-structured-bridge.d.ts +32 -0
- package/dist/src/providers/claude-structured-bridge.js +215 -0
- package/dist/src/providers/claude-structured-bridge.js.map +1 -0
- package/dist/src/providers/claude-structured-completion.d.ts +23 -0
- package/dist/src/providers/claude-structured-completion.js +229 -0
- package/dist/src/providers/claude-structured-completion.js.map +1 -0
- package/dist/src/providers/claude-structured-pane.d.ts +23 -0
- package/dist/src/providers/claude-structured-pane.js +156 -0
- package/dist/src/providers/claude-structured-pane.js.map +1 -0
- package/dist/src/providers/claude-structured-store.d.ts +48 -0
- package/dist/src/providers/claude-structured-store.js +212 -0
- package/dist/src/providers/claude-structured-store.js.map +1 -0
- package/dist/src/providers/claude-structured.d.ts +56 -0
- package/dist/src/providers/claude-structured.js +250 -0
- package/dist/src/providers/claude-structured.js.map +1 -0
- package/dist/src/providers/codex-exec.d.ts +29 -0
- package/dist/src/providers/codex-exec.js +127 -0
- package/dist/src/providers/codex-exec.js.map +1 -0
- package/dist/src/providers/grading.d.ts +2 -0
- package/dist/src/providers/grading.js +20 -0
- package/dist/src/providers/grading.js.map +1 -0
- package/dist/src/providers/native-dispatch.d.ts +24 -0
- package/dist/src/providers/native-dispatch.js +34 -0
- package/dist/src/providers/native-dispatch.js.map +1 -0
- package/dist/src/providers/preflight.d.ts +65 -0
- package/dist/src/providers/preflight.js +256 -0
- package/dist/src/providers/preflight.js.map +1 -0
- package/dist/src/providers/process.d.ts +18 -0
- package/dist/src/providers/process.js +21 -0
- package/dist/src/providers/process.js.map +1 -0
- package/dist/src/queue/model.d.ts +37 -0
- package/dist/src/queue/model.js +51 -0
- package/dist/src/queue/model.js.map +1 -0
- package/dist/src/queue/service.d.ts +1 -0
- package/dist/src/queue/service.js +2 -0
- package/dist/src/queue/service.js.map +1 -0
- package/dist/src/reflections/store.d.ts +21 -0
- package/dist/src/reflections/store.js +60 -0
- package/dist/src/reflections/store.js.map +1 -0
- package/dist/src/reports/daily-trust.d.ts +11 -0
- package/dist/src/reports/daily-trust.js +98 -0
- package/dist/src/reports/daily-trust.js.map +1 -0
- package/dist/src/runs/controller.d.ts +187 -0
- package/dist/src/runs/controller.js +1182 -0
- package/dist/src/runs/controller.js.map +1 -0
- package/dist/src/runtime/composition.d.ts +9 -0
- package/dist/src/runtime/composition.js +35 -0
- package/dist/src/runtime/composition.js.map +1 -0
- package/dist/src/runtime/endurance-authorities.d.ts +44 -0
- package/dist/src/runtime/endurance-authorities.js +101 -0
- package/dist/src/runtime/endurance-authorities.js.map +1 -0
- package/dist/src/runtime/endurance-policy.d.ts +12 -0
- package/dist/src/runtime/endurance-policy.js +13 -0
- package/dist/src/runtime/endurance-policy.js.map +1 -0
- package/dist/src/runtime/lock.d.ts +63 -0
- package/dist/src/runtime/lock.js +577 -0
- package/dist/src/runtime/lock.js.map +1 -0
- package/dist/src/runtime/process-identity.d.ts +10 -0
- package/dist/src/runtime/process-identity.js +43 -0
- package/dist/src/runtime/process-identity.js.map +1 -0
- package/dist/src/runtime/quota.d.ts +130 -0
- package/dist/src/runtime/quota.js +590 -0
- package/dist/src/runtime/quota.js.map +1 -0
- package/dist/src/runtime/record-store.d.ts +45 -0
- package/dist/src/runtime/record-store.js +272 -0
- package/dist/src/runtime/record-store.js.map +1 -0
- package/dist/src/runtime/schema.d.ts +28 -0
- package/dist/src/runtime/schema.js +194 -0
- package/dist/src/runtime/schema.js.map +1 -0
- package/dist/src/runtime/stable-id.d.ts +3 -0
- package/dist/src/runtime/stable-id.js +27 -0
- package/dist/src/runtime/stable-id.js.map +1 -0
- package/dist/src/runtime/system-clock.d.ts +8 -0
- package/dist/src/runtime/system-clock.js +35 -0
- package/dist/src/runtime/system-clock.js.map +1 -0
- package/dist/src/schedule/service-plan.d.ts +58 -0
- package/dist/src/schedule/service-plan.js +327 -0
- package/dist/src/schedule/service-plan.js.map +1 -0
- package/dist/src/schedule/user-installation.d.ts +62 -0
- package/dist/src/schedule/user-installation.js +701 -0
- package/dist/src/schedule/user-installation.js.map +1 -0
- package/dist/src/schedule/user-plan.d.ts +47 -0
- package/dist/src/schedule/user-plan.js +286 -0
- package/dist/src/schedule/user-plan.js.map +1 -0
- package/dist/src/scheduler/minute.d.ts +31 -0
- package/dist/src/scheduler/minute.js +441 -0
- package/dist/src/scheduler/minute.js.map +1 -0
- package/dist/src/schemas/registry.d.ts +10 -0
- package/dist/src/schemas/registry.js +36 -0
- package/dist/src/schemas/registry.js.map +1 -0
- package/dist/src/security/secret-scan.d.ts +50 -0
- package/dist/src/security/secret-scan.js +384 -0
- package/dist/src/security/secret-scan.js.map +1 -0
- package/dist/src/sessions/registry.d.ts +89 -0
- package/dist/src/sessions/registry.js +443 -0
- package/dist/src/sessions/registry.js.map +1 -0
- package/dist/src/state.d.ts +27 -0
- package/dist/src/state.js +235 -0
- package/dist/src/state.js.map +1 -0
- package/dist/src/status/hud.d.ts +37 -0
- package/dist/src/status/hud.js +88 -0
- package/dist/src/status/hud.js.map +1 -0
- package/dist/src/team/plan.d.ts +31 -0
- package/dist/src/team/plan.js +184 -0
- package/dist/src/team/plan.js.map +1 -0
- package/dist/src/team/projection.d.ts +24 -0
- package/dist/src/team/projection.js +79 -0
- package/dist/src/team/projection.js.map +1 -0
- package/dist/src/tickets/admission.d.ts +34 -0
- package/dist/src/tickets/admission.js +169 -0
- package/dist/src/tickets/admission.js.map +1 -0
- package/dist/src/tickets/materializer.d.ts +1 -0
- package/dist/src/tickets/materializer.js +2 -0
- package/dist/src/tickets/materializer.js.map +1 -0
- package/dist/src/tickets/runtime-authority.d.ts +32 -0
- package/dist/src/tickets/runtime-authority.js +46 -0
- package/dist/src/tickets/runtime-authority.js.map +1 -0
- package/dist/src/tickets/store.d.ts +221 -0
- package/dist/src/tickets/store.js +2025 -0
- package/dist/src/tickets/store.js.map +1 -0
- package/dist/src/tracer/one-ticket.d.ts +58 -0
- package/dist/src/tracer/one-ticket.js +578 -0
- package/dist/src/tracer/one-ticket.js.map +1 -0
- package/dist/src/trust/assessment.d.ts +24 -0
- package/dist/src/trust/assessment.js +124 -0
- package/dist/src/trust/assessment.js.map +1 -0
- package/dist/src/trust/runs.d.ts +13 -0
- package/dist/src/trust/runs.js +46 -0
- package/dist/src/trust/runs.js.map +1 -0
- package/dist/src/types.d.ts +6 -0
- package/dist/src/types.js +2 -0
- package/dist/src/types.js.map +1 -0
- package/dist/src/utils/fs.d.ts +13 -0
- package/dist/src/utils/fs.js +188 -0
- package/dist/src/utils/fs.js.map +1 -0
- package/dist/src/verification/authority.d.ts +28 -0
- package/dist/src/verification/authority.js +153 -0
- package/dist/src/verification/authority.js.map +1 -0
- package/dist/src/verification/grade-source.d.ts +17 -0
- package/dist/src/verification/grade-source.js +30 -0
- package/dist/src/verification/grade-source.js.map +1 -0
- package/dist/src/verification/plan.d.ts +20 -0
- package/dist/src/verification/plan.js +128 -0
- package/dist/src/verification/plan.js.map +1 -0
- package/dist/src/verification/protected-runner.d.ts +50 -0
- package/dist/src/verification/protected-runner.js +171 -0
- package/dist/src/verification/protected-runner.js.map +1 -0
- package/dist/src/verification/result-gate-store.d.ts +72 -0
- package/dist/src/verification/result-gate-store.js +193 -0
- package/dist/src/verification/result-gate-store.js.map +1 -0
- package/dist/src/verification/result-gate.d.ts +41 -0
- package/dist/src/verification/result-gate.js +731 -0
- package/dist/src/verification/result-gate.js.map +1 -0
- package/dist/src/verification/runner.d.ts +38 -0
- package/dist/src/verification/runner.js +143 -0
- package/dist/src/verification/runner.js.map +1 -0
- package/dist/src/verification/sandbox.d.ts +16 -0
- package/dist/src/verification/sandbox.js +199 -0
- package/dist/src/verification/sandbox.js.map +1 -0
- package/dist/src/verify.d.ts +8 -0
- package/dist/src/verify.js +35 -0
- package/dist/src/verify.js.map +1 -0
- package/dist/src/watchdog/reconcile.d.ts +42 -0
- package/dist/src/watchdog/reconcile.js +440 -0
- package/dist/src/watchdog/reconcile.js.map +1 -0
- package/dist/src/workers/bridge.d.ts +35 -0
- package/dist/src/workers/bridge.js +426 -0
- package/dist/src/workers/bridge.js.map +1 -0
- package/dist/src/workers/delivery.d.ts +31 -0
- package/dist/src/workers/delivery.js +876 -0
- package/dist/src/workers/delivery.js.map +1 -0
- package/dist/src/workers/panes.d.ts +32 -0
- package/dist/src/workers/panes.js +212 -0
- package/dist/src/workers/panes.js.map +1 -0
- package/dist/src/workers/registry.d.ts +80 -0
- package/dist/src/workers/registry.js +351 -0
- package/dist/src/workers/registry.js.map +1 -0
- package/dist/src/workers/runtime.d.ts +44 -0
- package/dist/src/workers/runtime.js +21 -0
- package/dist/src/workers/runtime.js.map +1 -0
- package/dist/src/worktrees/manager.d.ts +35 -0
- package/dist/src/worktrees/manager.js +315 -0
- package/dist/src/worktrees/manager.js.map +1 -0
- package/docs/ARCHITECTURE.md +323 -0
- package/docs/PLAN.md +216 -0
- package/docs/REFERENCES.md +42 -0
- package/docs/ROADMAP.md +327 -0
- package/docs/SECURITY.md +242 -0
- package/docs/adr/0001-tracer-first-file-backed-certification.md +41 -0
- package/docs/adr/0002-conductor-first-typed-dispatch.md +102 -0
- package/docs/adr/0003-mandatory-pre-dispatch-advice.md +59 -0
- package/docs/compatibility/claude-code-2.1.207.md +25 -0
- package/loop/contract.md +102 -0
- package/package.json +54 -0
- package/schemas/advisor-evidence.schema.json +54 -0
- package/schemas/advisor-output.schema.json +31 -0
- package/schemas/conductor-activity-payload.schema.json +38 -0
- package/schemas/conductor-decision.schema.json +29 -0
- package/schemas/conductor-dispatch-intent.schema.json +71 -0
- package/schemas/conductor-dispatch-preparation.schema.json +46 -0
- package/schemas/conductor-dispatch-proposal.schema.json +82 -0
- package/schemas/conductor-goal-proposal.schema.json +30 -0
- package/schemas/conductor-manage-worker.schema.json +17 -0
- package/schemas/conductor-notification-payload.schema.json +39 -0
- package/schemas/conductor-process-result.schema.json +14 -0
- package/schemas/conductor-route.schema.json +51 -0
- package/schemas/conductor-start-team.schema.json +16 -0
- package/schemas/conductor-waiter-payload.schema.json +68 -0
- package/schemas/deterministic-check.schema.json +107 -0
- package/schemas/dispatch-acknowledgement.schema.json +15 -0
- package/schemas/dispatch-advice-payload.schema.json +64 -0
- package/schemas/dispatch-payload.schema.json +131 -0
- package/schemas/dispatch-result.schema.json +28 -0
- package/schemas/endurance-policy.schema.json +16 -0
- package/schemas/events/dispatch-delivery-requested.schema.json +17 -0
- package/schemas/events/github-observed.schema.json +28 -0
- package/schemas/events/native-hook-observed.schema.json +35 -0
- package/schemas/events/provider-observed.schema.json +64 -0
- package/schemas/events/run-heartbeat-due.schema.json +10 -0
- package/schemas/events/slack-notification-requested.schema.json +14 -0
- package/schemas/events/structured-role-completed.schema.json +54 -0
- package/schemas/events/ticket-resume-due.schema.json +28 -0
- package/schemas/events/watchdog-lease-expired.schema.json +14 -0
- package/schemas/events/watchdog-outbox-stalled.schema.json +12 -0
- package/schemas/events/watchdog-session-stalled.schema.json +11 -0
- package/schemas/evidence-record.schema.json +37 -0
- package/schemas/executor-result.schema.json +17 -0
- package/schemas/github-observation.schema.json +33 -0
- package/schemas/github-watermark.schema.json +66 -0
- package/schemas/goal-artifact.schema.json +19 -0
- package/schemas/goal-create-input.schema.json +14 -0
- package/schemas/goal-executor-result.schema.json +18 -0
- package/schemas/goal-payload.schema.json +95 -0
- package/schemas/grader-output.schema.json +33 -0
- package/schemas/lease-expiry.schema.json +19 -0
- package/schemas/live-disclosure.schema.json +16 -0
- package/schemas/live-outcome.schema.json +52 -0
- package/schemas/native-goal-snapshot.schema.json +27 -0
- package/schemas/provider-observed.schema.json +64 -0
- package/schemas/quota-observation.schema.json +72 -0
- package/schemas/reflection-evidence.schema.json +61 -0
- package/schemas/reflection.schema.json +13 -0
- package/schemas/regression-request.schema.json +54 -0
- package/schemas/release-assessment.schema.json +34 -0
- package/schemas/release-policy.schema.json +30 -0
- package/schemas/result-gate-payload.schema.json +244 -0
- package/schemas/review-delivery-payload.schema.json +42 -0
- package/schemas/role-policy.schema.json +111 -0
- package/schemas/run-abort-input.schema.json +11 -0
- package/schemas/run-completion-proof.schema.json +62 -0
- package/schemas/run-ledger-entry.schema.json +78 -0
- package/schemas/run-manifest.schema.json +74 -0
- package/schemas/run-start-intent.schema.json +74 -0
- package/schemas/runtime-record.schema.json +111 -0
- package/schemas/service-plan.schema.json +35 -0
- package/schemas/session-payload.schema.json +80 -0
- package/schemas/structured-role-read-request.schema.json +14 -0
- package/schemas/structured-role-request.schema.json +44 -0
- package/schemas/structured-role-result.schema.json +38 -0
- package/schemas/structured-role-submit-result.schema.json +15 -0
- package/schemas/team-checkpoint.schema.json +42 -0
- package/schemas/team-plan.schema.json +30 -0
- package/schemas/ticket-content.schema.json +38 -0
- package/schemas/ticket-queue-payload.schema.json +191 -0
- package/schemas/trust-report.schema.json +68 -0
- package/schemas/trust-run.schema.json +47 -0
- package/schemas/ultragoal-checkpoint.schema.json +49 -0
- package/schemas/user-schedule-installation-v1.schema.json +32 -0
- package/schemas/user-schedule-installation.schema.json +38 -0
- package/schemas/user-schedule-plan.schema.json +39 -0
- package/schemas/verification-plan-evidence.schema.json +37 -0
- package/schemas/worker-acknowledge-dispatch.schema.json +12 -0
- package/schemas/worker-payload.schema.json +47 -0
- package/schemas/worker-read-dispatch.schema.json +11 -0
- package/schemas/worker-record-dispatch-result.schema.json +14 -0
|
@@ -0,0 +1,1450 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { isDeepStrictEqual } from "node:util";
|
|
4
|
+
import { assertActorId, assertOpaqueId, assertRecordId, assertTransitionNonce, } from "../domain/records.js";
|
|
5
|
+
import { assertFileInside, openStableDirectory, readImmutableFile, writeImmutableFile, } from "../evidence/immutable-file.js";
|
|
6
|
+
import { discloseLiveContact, listLiveContacts, readLiveContact, readLiveDeliveryAuthorization, } from "../live/contact-journal.js";
|
|
7
|
+
import { runProcess } from "../providers/process.js";
|
|
8
|
+
import { canonicalTimestamp } from "../queue/model.js";
|
|
9
|
+
import { createRecordStore } from "../runtime/record-store.js";
|
|
10
|
+
import { assertSchema, loadJsonSchema } from "../runtime/schema.js";
|
|
11
|
+
import { createStableId } from "../runtime/stable-id.js";
|
|
12
|
+
import { runSecretScan, readSecretScan, } from "../security/secret-scan.js";
|
|
13
|
+
import { readJsonFileNoFollow, isRecord, validateExistingDirectory } from "../utils/fs.js";
|
|
14
|
+
import { createResultGateStore } from "../verification/result-gate-store.js";
|
|
15
|
+
import { createRuntimeWorkerAuthorities } from "../workers/runtime.js";
|
|
16
|
+
import { deriveWorktreeId } from "../dispatch/authority.js";
|
|
17
|
+
import { readGoal } from "../goals/authority.js";
|
|
18
|
+
import { parseTicketWorktreeRecord } from "../worktrees/manager.js";
|
|
19
|
+
import { normalizeGithubRemoteRepository, normalizeGithubSyncTarget } from "./adapter.js";
|
|
20
|
+
const BASE_BRANCH = "main";
|
|
21
|
+
const MAX_GITHUB_BYTES = 2 * 1024 * 1024;
|
|
22
|
+
const MAX_PROOF_BYTES = 512 * 1024;
|
|
23
|
+
const SHA_PATTERN = /^[a-f0-9]{40}(?:[a-f0-9]{24})?$/u;
|
|
24
|
+
const SHA256_PATTERN = /^[a-f0-9]{64}$/u;
|
|
25
|
+
const BRANCH_PATTERN = /^oms-ticket-[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$/u;
|
|
26
|
+
const REQUIRED_CHECK_PATTERN = /^[A-Za-z0-9][A-Za-z0-9 ._:/-]{0,127}$/u;
|
|
27
|
+
export const OMS_REVIEW_REQUIRED_CHECK = "OMS deterministic verification";
|
|
28
|
+
const REVIEW_QUERY = `query OmsReviewRead($owner: String!, $name: String!, $branchRef: String!) {
|
|
29
|
+
repository(owner: $owner, name: $name) {
|
|
30
|
+
id
|
|
31
|
+
ref(qualifiedName: $branchRef) { target { oid } }
|
|
32
|
+
pullRequests(first: 100, states: [OPEN], orderBy: {field: UPDATED_AT, direction: DESC}) {
|
|
33
|
+
nodes {
|
|
34
|
+
id number url state isDraft headRefName baseRefName
|
|
35
|
+
commits(last: 1) { totalCount nodes { commit { oid } } }
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
rateLimit { remaining resetAt }
|
|
40
|
+
}`;
|
|
41
|
+
const CREATE_PR_MUTATION = `mutation OmsCreateDraftPullRequest(
|
|
42
|
+
$repositoryId: ID!, $baseRefName: String!, $headRefName: String!, $title: String!, $body: String!
|
|
43
|
+
) {
|
|
44
|
+
createPullRequest(input: {
|
|
45
|
+
repositoryId: $repositoryId,
|
|
46
|
+
baseRefName: $baseRefName,
|
|
47
|
+
headRefName: $headRefName,
|
|
48
|
+
title: $title,
|
|
49
|
+
body: $body,
|
|
50
|
+
draft: true
|
|
51
|
+
}) {
|
|
52
|
+
pullRequest {
|
|
53
|
+
id number url state isDraft headRefName baseRefName
|
|
54
|
+
commits(last: 1) { totalCount nodes { commit { oid } } }
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}`;
|
|
58
|
+
const LEGAL_EDGES = Object.freeze({
|
|
59
|
+
planned: ["push-pending", "pushed", "blocked"],
|
|
60
|
+
"push-pending": ["push-pending", "pushed", "blocked"],
|
|
61
|
+
pushed: ["pr-pending", "awaiting-check", "blocked"],
|
|
62
|
+
"pr-pending": ["pr-pending", "awaiting-check", "blocked"],
|
|
63
|
+
"awaiting-check": ["ready", "blocked"],
|
|
64
|
+
ready: [],
|
|
65
|
+
blocked: [],
|
|
66
|
+
});
|
|
67
|
+
export function createReviewDeliveryService(options) {
|
|
68
|
+
const packageRoot = validateExistingDirectory(options.packageRoot, "review package root");
|
|
69
|
+
const configRoot = validateExistingDirectory(options.configRoot ?? packageRoot, "review configuration root");
|
|
70
|
+
const workspaceRoot = validateExistingDirectory(options.workspaceRoot, "review workspace root");
|
|
71
|
+
const stateRoot = validateExistingDirectory(options.stateRoot, "review state root");
|
|
72
|
+
const requiredCheck = validateRequiredCheck(options.requiredCheckName);
|
|
73
|
+
const clock = options.clock ?? (() => new Date().toISOString());
|
|
74
|
+
const env = options.env ?? process.env;
|
|
75
|
+
const runner = options.runner ?? runProcess;
|
|
76
|
+
const secretScanner = options.secretScanner ?? runSecretScan;
|
|
77
|
+
const runtime = options.runtime ?? createRuntimeWorkerAuthorities({
|
|
78
|
+
packageRoot,
|
|
79
|
+
configRoot,
|
|
80
|
+
workspaceRoot,
|
|
81
|
+
stateRoot,
|
|
82
|
+
actorId: "oms-conductor",
|
|
83
|
+
clock,
|
|
84
|
+
});
|
|
85
|
+
const gates = createResultGateStore({ packageRoot, stateRoot });
|
|
86
|
+
const deliveries = createReviewDeliveryStore({ packageRoot, stateRoot, clock });
|
|
87
|
+
function advance(dispatchId, result) {
|
|
88
|
+
if (runtime.tickets.lifecycle.read(result.ticket_id).payload.source !== "github")
|
|
89
|
+
return null;
|
|
90
|
+
const canonical = captureCanonicalSubject(dispatchId, result);
|
|
91
|
+
const deliveryId = createStableId("review", canonical.subject.dispatch_id, canonical.subject.expected_commit);
|
|
92
|
+
let delivery = deliveries.readIfExists(deliveryId);
|
|
93
|
+
if (!delivery)
|
|
94
|
+
delivery = planDelivery(deliveryId, canonical);
|
|
95
|
+
assertReplayBinding(delivery, canonical);
|
|
96
|
+
for (let step = 0; step < 8; step += 1) {
|
|
97
|
+
if (delivery.state === "ready") {
|
|
98
|
+
assertReadyAttachments(delivery);
|
|
99
|
+
return toProgress(delivery);
|
|
100
|
+
}
|
|
101
|
+
if (delivery.state === "blocked")
|
|
102
|
+
return toProgress(delivery);
|
|
103
|
+
if (delivery.state === "planned") {
|
|
104
|
+
const advanced = advancePush(delivery);
|
|
105
|
+
delivery = advanced.record;
|
|
106
|
+
if (advanced.waiting)
|
|
107
|
+
return toProgress(delivery);
|
|
108
|
+
}
|
|
109
|
+
else if (delivery.state === "push-pending")
|
|
110
|
+
delivery = recoverPush(delivery);
|
|
111
|
+
else if (delivery.state === "pushed") {
|
|
112
|
+
const advanced = advancePullRequest(delivery);
|
|
113
|
+
delivery = advanced.record;
|
|
114
|
+
if (advanced.waiting)
|
|
115
|
+
return toProgress(delivery);
|
|
116
|
+
}
|
|
117
|
+
else if (delivery.state === "pr-pending")
|
|
118
|
+
delivery = recoverPullRequest(delivery);
|
|
119
|
+
else if (delivery.state === "awaiting-check") {
|
|
120
|
+
const completed = finishAfterTypedEvents(delivery, canonical.issueScanContent);
|
|
121
|
+
if (!completed)
|
|
122
|
+
return toProgress(delivery);
|
|
123
|
+
delivery = completed;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return toProgress(delivery);
|
|
127
|
+
}
|
|
128
|
+
function captureCanonicalSubject(dispatchId, supplied) {
|
|
129
|
+
const dispatch = runtime.dispatch.read(assertRecordId(dispatchId, "review dispatch id"));
|
|
130
|
+
const gate = gates.read(assertRecordId(supplied.gate_id, "review result-gate id"));
|
|
131
|
+
const ticket = runtime.tickets.lifecycle.read(gate.payload.subject.ticket_id);
|
|
132
|
+
if (supplied.status !== "completed"
|
|
133
|
+
|| supplied.gate_state !== "finalized"
|
|
134
|
+
|| supplied.ticket_state !== "completed"
|
|
135
|
+
|| supplied.reason !== null
|
|
136
|
+
|| gate.state !== "finalized"
|
|
137
|
+
|| gate.payload.outcome !== "completed"
|
|
138
|
+
|| gate.payload.subject.dispatch_id !== dispatch.record_id
|
|
139
|
+
|| supplied.ticket_id !== ticket.record_id
|
|
140
|
+
|| supplied.deterministic_evidence === null
|
|
141
|
+
|| supplied.grade_evidence === null
|
|
142
|
+
|| !sameReference(supplied.deterministic_evidence, gate.payload.deterministic_evidence)
|
|
143
|
+
|| !sameReference(supplied.grade_evidence, gate.payload.grade_evidence))
|
|
144
|
+
throw new Error("review delivery requires the canonical completed protected result");
|
|
145
|
+
const subject = gate.payload.subject;
|
|
146
|
+
const goal = readGoal(runtime.goals.authority, subject.goal_id);
|
|
147
|
+
const worker = runtime.workers.read(subject.dispatch_id);
|
|
148
|
+
const session = runtime.sessions.read(subject.session_id);
|
|
149
|
+
const worktree = parseTicketWorktreeRecord(readJsonFileNoFollow(join(stateRoot, "worktree-records", `${subject.ticket_id}.json`), 64 * 1024), "review worktree record");
|
|
150
|
+
const deterministic = runtime.tickets.evidenceStore.read(supplied.deterministic_evidence);
|
|
151
|
+
const grade = runtime.tickets.evidenceStore.read(supplied.grade_evidence);
|
|
152
|
+
if (dispatch.state !== "resolved"
|
|
153
|
+
|| dispatch.payload.resolution !== "done"
|
|
154
|
+
|| dispatch.payload.result?.status !== "done"
|
|
155
|
+
|| dispatch.version !== subject.dispatch_version
|
|
156
|
+
|| dispatch.payload.goal_id !== subject.goal_id
|
|
157
|
+
|| dispatch.payload.goal_version !== subject.goal_version
|
|
158
|
+
|| dispatch.payload.ticket_id !== subject.ticket_id
|
|
159
|
+
|| dispatch.payload.attempt !== subject.attempt
|
|
160
|
+
|| dispatch.payload.lease_token !== subject.lease_token
|
|
161
|
+
|| dispatch.payload.worker_record_id !== subject.worker_record_id
|
|
162
|
+
|| dispatch.payload.worker_id !== subject.worker_id
|
|
163
|
+
|| dispatch.payload.session_id !== subject.session_id
|
|
164
|
+
|| dispatch.payload.worktree_id !== subject.worktree_id
|
|
165
|
+
|| dispatch.payload.worktree_path !== subject.worktree_root
|
|
166
|
+
|| dispatch.payload.worktree_branch !== subject.worktree_branch
|
|
167
|
+
|| dispatch.payload.worktree_base_commit !== subject.worktree_base_commit
|
|
168
|
+
|| dispatch.payload.result.worktree_commit !== subject.expected_commit
|
|
169
|
+
|| ticket.state !== "completed"
|
|
170
|
+
|| ticket.attempt !== subject.attempt
|
|
171
|
+
|| ticket.payload.source !== "github"
|
|
172
|
+
|| !sameReference(ticket.payload.verification_evidence, supplied.deterministic_evidence)
|
|
173
|
+
|| !sameReference(ticket.payload.grade_evidence, supplied.grade_evidence)
|
|
174
|
+
|| goal.version !== subject.goal_version
|
|
175
|
+
|| !["active", "completed"].includes(goal.state)
|
|
176
|
+
|| goal.payload.repository_scope !== workspaceRoot
|
|
177
|
+
|| worker.state !== "done"
|
|
178
|
+
|| worker.record_id !== subject.worker_record_id
|
|
179
|
+
|| worker.payload.external_worker_id !== subject.worker_id
|
|
180
|
+
|| worker.payload.session_id !== subject.session_id
|
|
181
|
+
|| worker.payload.ticket_id !== subject.ticket_id
|
|
182
|
+
|| session.payload.ticket_id !== subject.ticket_id
|
|
183
|
+
|| session.payload.native_session_ref !== subject.session_id
|
|
184
|
+
|| worktree.ticket_id !== subject.ticket_id
|
|
185
|
+
|| worktree.repository_root !== workspaceRoot
|
|
186
|
+
|| worktree.worktree_root !== subject.worktree_root
|
|
187
|
+
|| worktree.branch !== subject.worktree_branch
|
|
188
|
+
|| worktree.base_commit !== subject.worktree_base_commit
|
|
189
|
+
|| deriveWorktreeId(worktree) !== subject.worktree_id
|
|
190
|
+
|| worktree.requested_base !== BASE_BRANCH
|
|
191
|
+
|| !BRANCH_PATTERN.test(worktree.branch)
|
|
192
|
+
|| deterministic.evidence_kind !== "deterministic-check"
|
|
193
|
+
|| deterministic.ticket_id !== subject.ticket_id
|
|
194
|
+
|| deterministic.payload.certification !== "live"
|
|
195
|
+
|| deterministic.payload.passed !== true
|
|
196
|
+
|| grade.evidence_kind !== "grader-output"
|
|
197
|
+
|| grade.ticket_id !== subject.ticket_id
|
|
198
|
+
|| grade.payload.verdict !== "pass"
|
|
199
|
+
|| !sameReference(grade.payload.deterministic_evidence, supplied.deterministic_evidence))
|
|
200
|
+
throw new Error("review delivery authority binding no longer matches the protected result chain");
|
|
201
|
+
if (!isDeepStrictEqual(deterministic.payload.binding, protectedBinding(subject))) {
|
|
202
|
+
throw new Error("deterministic evidence binding does not match the result-gate subject");
|
|
203
|
+
}
|
|
204
|
+
const content = runtime.tickets.materializer.readContent({
|
|
205
|
+
ticket_id: ticket.record_id,
|
|
206
|
+
sha256: ticket.payload.content_sha256,
|
|
207
|
+
file_name: ticket.payload.content_file,
|
|
208
|
+
});
|
|
209
|
+
if (content.repository === null || content.issue_node_id === null || content.url === null) {
|
|
210
|
+
throw new Error("review delivery requires one canonical GitHub ticket");
|
|
211
|
+
}
|
|
212
|
+
const repository = normalizeGithubSyncTarget(content.repository).repository;
|
|
213
|
+
const issueNumber = parseIssueNumber(content.url, repository);
|
|
214
|
+
const authorization = readLiveDeliveryAuthorization(stateRoot);
|
|
215
|
+
if (!authorization || authorization.repository !== repository) {
|
|
216
|
+
throw new Error("review delivery lacks matching human live authorization");
|
|
217
|
+
}
|
|
218
|
+
return {
|
|
219
|
+
subject: Object.freeze({
|
|
220
|
+
goal_id: subject.goal_id,
|
|
221
|
+
goal_version: subject.goal_version,
|
|
222
|
+
dispatch_id: subject.dispatch_id,
|
|
223
|
+
dispatch_version: subject.dispatch_version,
|
|
224
|
+
ticket_id: subject.ticket_id,
|
|
225
|
+
attempt: subject.attempt,
|
|
226
|
+
lease_token: subject.lease_token,
|
|
227
|
+
worker_record_id: subject.worker_record_id,
|
|
228
|
+
worker_id: subject.worker_id,
|
|
229
|
+
session_id: subject.session_id,
|
|
230
|
+
worktree_id: subject.worktree_id,
|
|
231
|
+
worktree_root: subject.worktree_root,
|
|
232
|
+
worktree_branch: subject.worktree_branch,
|
|
233
|
+
worktree_base_commit: subject.worktree_base_commit,
|
|
234
|
+
expected_commit: subject.expected_commit,
|
|
235
|
+
deterministic_evidence: supplied.deterministic_evidence,
|
|
236
|
+
grade_evidence: supplied.grade_evidence,
|
|
237
|
+
}),
|
|
238
|
+
gate,
|
|
239
|
+
repository,
|
|
240
|
+
issueNumber,
|
|
241
|
+
issueNodeId: content.issue_node_id,
|
|
242
|
+
issueUrlSha256: sha256(content.url),
|
|
243
|
+
sourceRefSha256: ticket.payload.source_ref_sha256,
|
|
244
|
+
issueScanContent: Object.freeze({
|
|
245
|
+
title: content.title,
|
|
246
|
+
body: content.body,
|
|
247
|
+
labels: Object.freeze([...content.labels]),
|
|
248
|
+
url: content.url,
|
|
249
|
+
}),
|
|
250
|
+
authorizationId: authorization.authorization_id,
|
|
251
|
+
conductorSessionId: authorization.conductor_session_id,
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
function planDelivery(deliveryId, canonical) {
|
|
255
|
+
const createdAt = canonicalTimestamp(clock(), "review delivery timestamp");
|
|
256
|
+
assertAuthorizedOrigin(canonical.repository, canonical.subject.worktree_root, env, runner);
|
|
257
|
+
assertCommitAncestry(canonical.subject, env, runner);
|
|
258
|
+
const outbound = outboundPlan(canonical.repository, canonical.issueNumber, canonical.issueNodeId, canonical.issueScanContent, canonical.subject, requiredCheck);
|
|
259
|
+
const scan = secretScanner({
|
|
260
|
+
packageRoot,
|
|
261
|
+
stateRoot,
|
|
262
|
+
ticketId: canonical.subject.ticket_id,
|
|
263
|
+
phase: "preflight",
|
|
264
|
+
worktreeRoot: canonical.subject.worktree_root,
|
|
265
|
+
baseCommit: canonical.subject.worktree_base_commit,
|
|
266
|
+
verifiedCommit: canonical.subject.expected_commit,
|
|
267
|
+
outbound,
|
|
268
|
+
env,
|
|
269
|
+
runner,
|
|
270
|
+
clock: () => createdAt,
|
|
271
|
+
});
|
|
272
|
+
assertSecretScan(scan.result, "preflight", canonical.subject);
|
|
273
|
+
return deliveries.create({
|
|
274
|
+
deliveryId,
|
|
275
|
+
at: createdAt,
|
|
276
|
+
payload: {
|
|
277
|
+
delivery_id: deliveryId,
|
|
278
|
+
repository: canonical.repository,
|
|
279
|
+
issue_number: canonical.issueNumber,
|
|
280
|
+
issue_node_id: canonical.issueNodeId,
|
|
281
|
+
issue_url_sha256: canonical.issueUrlSha256,
|
|
282
|
+
source_ref_sha256: canonical.sourceRefSha256,
|
|
283
|
+
subject: canonical.subject,
|
|
284
|
+
result_gate_id: canonical.gate.record_id,
|
|
285
|
+
authorization_id: canonical.authorizationId,
|
|
286
|
+
conductor_session_id: canonical.conductorSessionId,
|
|
287
|
+
base_branch: BASE_BRANCH,
|
|
288
|
+
review_branch: canonical.subject.worktree_branch,
|
|
289
|
+
required_check: requiredCheck,
|
|
290
|
+
created_at: createdAt,
|
|
291
|
+
updated_at: createdAt,
|
|
292
|
+
preflight_secret_scan: scan.reference,
|
|
293
|
+
final_secret_scan: null,
|
|
294
|
+
contacts: [],
|
|
295
|
+
pull_request: null,
|
|
296
|
+
pull_request_event: null,
|
|
297
|
+
check_event: null,
|
|
298
|
+
proof: null,
|
|
299
|
+
blocked_reason: null,
|
|
300
|
+
},
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
function advancePush(record) {
|
|
304
|
+
const readback = readRemote(record, "before-push");
|
|
305
|
+
if (readback.snapshot.branch_head_oid !== null) {
|
|
306
|
+
if (readback.snapshot.branch_head_oid !== record.payload.subject.expected_commit) {
|
|
307
|
+
return { record: block(record, "remote review branch does not match the verified commit", readback.receipt), waiting: false };
|
|
308
|
+
}
|
|
309
|
+
return { record: deliveries.transition(record, "branch-already-pushed", "pushed", [readback.receipt]), waiting: false };
|
|
310
|
+
}
|
|
311
|
+
const request = pushRequest(record, env);
|
|
312
|
+
const contact = disclose(record, "git-branch-push", "git-origin", "push", request);
|
|
313
|
+
const claimed = deliveries.claim(record, "push-intent", "push-pending", [
|
|
314
|
+
readback.receipt,
|
|
315
|
+
{ phase: "push", operation: "git-branch-push", contact, response_sha256: null },
|
|
316
|
+
]);
|
|
317
|
+
if (claimed.status === "replayed")
|
|
318
|
+
return { record: claimed.record, waiting: true };
|
|
319
|
+
let pending = claimed.record;
|
|
320
|
+
const result = safeRun(runner, request);
|
|
321
|
+
const responseSha256 = processResultDigest(result);
|
|
322
|
+
pending = deliveries.transition(pending, `push-result-${responseSha256.slice(0, 12)}`, "push-pending", [], (payload) => ({
|
|
323
|
+
...payload,
|
|
324
|
+
contacts: replacePendingReceipt(payload.contacts, contact, responseSha256),
|
|
325
|
+
}));
|
|
326
|
+
if (processSucceeded(result)) {
|
|
327
|
+
return { record: deliveries.transition(pending, "push-confirmed", "pushed"), waiting: false };
|
|
328
|
+
}
|
|
329
|
+
const recovered = readRemote(pending, "after-ambiguous-push");
|
|
330
|
+
if (recovered.snapshot.branch_head_oid === pending.payload.subject.expected_commit) {
|
|
331
|
+
return { record: deliveries.transition(pending, "push-readback-confirmed", "pushed", [recovered.receipt]), waiting: false };
|
|
332
|
+
}
|
|
333
|
+
if (recovered.snapshot.branch_head_oid !== null) {
|
|
334
|
+
return { record: block(pending, "ambiguous push resolved to a different remote commit", recovered.receipt), waiting: false };
|
|
335
|
+
}
|
|
336
|
+
return { record: pending, waiting: true };
|
|
337
|
+
}
|
|
338
|
+
function recoverPush(record) {
|
|
339
|
+
const readback = readRemote(record, "recover-push");
|
|
340
|
+
if (readback.snapshot.branch_head_oid === record.payload.subject.expected_commit) {
|
|
341
|
+
return deliveries.transition(record, "recovered-push", "pushed", [readback.receipt]);
|
|
342
|
+
}
|
|
343
|
+
if (readback.snapshot.branch_head_oid !== null) {
|
|
344
|
+
return block(record, "recovered push points at a different remote commit", readback.receipt);
|
|
345
|
+
}
|
|
346
|
+
return block(record, "push contact is ambiguous and remote readback found no branch", readback.receipt);
|
|
347
|
+
}
|
|
348
|
+
function advancePullRequest(record) {
|
|
349
|
+
const readback = readRemote(record, "before-pr");
|
|
350
|
+
if (readback.snapshot.branch_head_oid !== record.payload.subject.expected_commit) {
|
|
351
|
+
return { record: block(record, "verified review branch disappeared or changed before PR creation", readback.receipt), waiting: false };
|
|
352
|
+
}
|
|
353
|
+
if (readback.snapshot.pull_request) {
|
|
354
|
+
return { record: deliveries.transition(record, "existing-draft-pr", "awaiting-check", [readback.receipt], (payload) => ({
|
|
355
|
+
...payload,
|
|
356
|
+
pull_request: readback.snapshot.pull_request,
|
|
357
|
+
})), waiting: false };
|
|
358
|
+
}
|
|
359
|
+
const request = createPullRequestRequest(record, readback.snapshot.repository_node_id, env);
|
|
360
|
+
const contact = disclose(record, "github-pr-create", "github-api-via-gh", "create-pr", request);
|
|
361
|
+
const claimed = deliveries.claim(record, "pr-create-intent", "pr-pending", [
|
|
362
|
+
readback.receipt,
|
|
363
|
+
{ phase: "create-pr", operation: "github-pr-create", contact, response_sha256: null },
|
|
364
|
+
]);
|
|
365
|
+
if (claimed.status === "replayed")
|
|
366
|
+
return { record: claimed.record, waiting: true };
|
|
367
|
+
let pending = claimed.record;
|
|
368
|
+
const result = safeRun(runner, request);
|
|
369
|
+
const responseSha256 = processResultDigest(result);
|
|
370
|
+
pending = deliveries.transition(pending, `pr-result-${responseSha256.slice(0, 12)}`, "pr-pending", [], (payload) => ({
|
|
371
|
+
...payload,
|
|
372
|
+
contacts: replacePendingReceipt(payload.contacts, contact, responseSha256),
|
|
373
|
+
}));
|
|
374
|
+
if (processSucceeded(result))
|
|
375
|
+
parseCreatedPullRequest(result, pending);
|
|
376
|
+
const recovered = recoverPullRequest(pending);
|
|
377
|
+
return { record: recovered, waiting: recovered.state === "pr-pending" };
|
|
378
|
+
}
|
|
379
|
+
function recoverPullRequest(record) {
|
|
380
|
+
const readback = readRemote(record, "recover-pr");
|
|
381
|
+
if (readback.snapshot.branch_head_oid !== record.payload.subject.expected_commit) {
|
|
382
|
+
return block(record, "review branch changed while recovering draft PR", readback.receipt);
|
|
383
|
+
}
|
|
384
|
+
if (!readback.snapshot.pull_request) {
|
|
385
|
+
return block(record, "ambiguous draft PR creation found no matching remote PR", readback.receipt);
|
|
386
|
+
}
|
|
387
|
+
return deliveries.transition(record, "pr-readback-confirmed", "awaiting-check", [readback.receipt], (payload) => ({
|
|
388
|
+
...payload,
|
|
389
|
+
pull_request: readback.snapshot.pull_request,
|
|
390
|
+
}));
|
|
391
|
+
}
|
|
392
|
+
function finishAfterTypedEvents(record, issueScanContent) {
|
|
393
|
+
const events = findRequiredEvents(record);
|
|
394
|
+
if (!events)
|
|
395
|
+
return undefined;
|
|
396
|
+
if (events.outcome === "blocked")
|
|
397
|
+
return block(record, events.reason);
|
|
398
|
+
const readback = readRemote(record, "final-review-read");
|
|
399
|
+
if (readback.snapshot.branch_head_oid !== record.payload.subject.expected_commit
|
|
400
|
+
|| !readback.snapshot.pull_request
|
|
401
|
+
|| !samePullRequest(readback.snapshot.pull_request, record.payload.pull_request))
|
|
402
|
+
return block(record, "final GitHub readback no longer matches the verified draft PR", readback.receipt);
|
|
403
|
+
const finalReadDisclosure = readLiveContact(stateRoot, readback.receipt.contact);
|
|
404
|
+
const issueEvent = findIssueEvent(record, issueScanContent);
|
|
405
|
+
const contactEvidence = requiredContactEvidence(record, issueEvent.observed_at, events, finalReadDisclosure.disclosed_at);
|
|
406
|
+
const finalizedAt = timestampAfter(record.payload.updated_at, issueEvent.observed_at, events.pull_request_observed_at, events.check_observed_at, finalReadDisclosure.disclosed_at, ...contactEvidence.contacts.map((contact) => contact.disclosed_at));
|
|
407
|
+
const proofCandidate = buildProof(record, events, readback, issueEvent, contactEvidence, finalizedAt);
|
|
408
|
+
const scan = secretScanner({
|
|
409
|
+
packageRoot,
|
|
410
|
+
stateRoot,
|
|
411
|
+
ticketId: record.payload.subject.ticket_id,
|
|
412
|
+
phase: "final",
|
|
413
|
+
worktreeRoot: record.payload.subject.worktree_root,
|
|
414
|
+
baseCommit: record.payload.subject.worktree_base_commit,
|
|
415
|
+
verifiedCommit: record.payload.subject.expected_commit,
|
|
416
|
+
outbound: {
|
|
417
|
+
delivery: outboundPlan(record.payload.repository, record.payload.issue_number, record.payload.issue_node_id, issueScanContent, record.payload.subject, record.payload.required_check),
|
|
418
|
+
proof: proofCandidate,
|
|
419
|
+
},
|
|
420
|
+
env,
|
|
421
|
+
runner,
|
|
422
|
+
clock: () => finalizedAt,
|
|
423
|
+
});
|
|
424
|
+
assertSecretScan(scan.result, "final", record.payload.subject);
|
|
425
|
+
const proof = writeProof(proofCandidate, scan.reference);
|
|
426
|
+
return deliveries.transition(record, "review-ready", "ready", [readback.receipt], (payload) => ({
|
|
427
|
+
...payload,
|
|
428
|
+
final_secret_scan: scan.reference,
|
|
429
|
+
pull_request_event: events.pullRequest,
|
|
430
|
+
check_event: events.check,
|
|
431
|
+
proof,
|
|
432
|
+
}));
|
|
433
|
+
}
|
|
434
|
+
function readRemote(record, phase) {
|
|
435
|
+
const request = reviewReadRequest(record, env);
|
|
436
|
+
const contact = disclose(record, "github-review-read", "github-api-via-gh", phase, request);
|
|
437
|
+
const result = safeRun(runner, request);
|
|
438
|
+
if (!processSucceeded(result))
|
|
439
|
+
throw new Error("GitHub review read failed");
|
|
440
|
+
return {
|
|
441
|
+
snapshot: parseReviewRead(result, record),
|
|
442
|
+
receipt: {
|
|
443
|
+
phase,
|
|
444
|
+
operation: "github-review-read",
|
|
445
|
+
contact,
|
|
446
|
+
response_sha256: processResultDigest(result),
|
|
447
|
+
},
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
function disclose(record, operation, contactSurface, phase, request) {
|
|
451
|
+
const authorization = readLiveDeliveryAuthorization(stateRoot);
|
|
452
|
+
if (!authorization
|
|
453
|
+
|| authorization.authorization_id !== record.payload.authorization_id
|
|
454
|
+
|| authorization.conductor_session_id !== record.payload.conductor_session_id
|
|
455
|
+
|| authorization.repository !== record.payload.repository)
|
|
456
|
+
throw new Error("review delivery live authorization changed");
|
|
457
|
+
return discloseLiveContact({
|
|
458
|
+
stateRoot,
|
|
459
|
+
authorization,
|
|
460
|
+
operation,
|
|
461
|
+
contactSurface,
|
|
462
|
+
subjectId: createStableId("review-contact", record.record_id, phase, String(record.version)),
|
|
463
|
+
request: publicProcessRequest(request),
|
|
464
|
+
disclosedAt: nextTimestamp(record.payload.updated_at, clock),
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
function findRequiredEvents(record) {
|
|
468
|
+
if (!record.payload.pull_request)
|
|
469
|
+
throw new Error("awaiting-check delivery lacks its draft PR");
|
|
470
|
+
let pullRequest;
|
|
471
|
+
let check;
|
|
472
|
+
const removals = [];
|
|
473
|
+
for (const event of runtime.outbox.listEvents()) {
|
|
474
|
+
if (event.event_kind !== "github-observed" || event.payload.repository !== record.payload.repository)
|
|
475
|
+
continue;
|
|
476
|
+
if (typeof event.payload.payload_sha256 !== "string")
|
|
477
|
+
continue;
|
|
478
|
+
const digest = event.payload.payload_sha256;
|
|
479
|
+
const fileName = event.payload.snapshot_file;
|
|
480
|
+
if (!SHA256_PATTERN.test(digest) || fileName !== `${digest}.json`)
|
|
481
|
+
continue;
|
|
482
|
+
const snapshot = readJsonFileNoFollow(join(stateRoot, "github-snapshots", fileName), MAX_GITHUB_BYTES);
|
|
483
|
+
if (sha256(canonicalJson(snapshot)) !== digest || !isRecord(snapshot))
|
|
484
|
+
continue;
|
|
485
|
+
const baseCandidate = {
|
|
486
|
+
reference: { event_id: event.event_id, payload_sha256: digest },
|
|
487
|
+
observationKey: String(event.payload.observation_key),
|
|
488
|
+
observedAt: canonicalTimestamp(event.occurred_at, "review GitHub event timestamp"),
|
|
489
|
+
eventId: event.event_id,
|
|
490
|
+
change: event.payload.change,
|
|
491
|
+
snapshot,
|
|
492
|
+
ambiguous: false,
|
|
493
|
+
};
|
|
494
|
+
if (event.payload.change === "removed") {
|
|
495
|
+
const candidate = { ...baseCandidate, effectiveAt: baseCandidate.observedAt };
|
|
496
|
+
removals.push(candidate);
|
|
497
|
+
continue;
|
|
498
|
+
}
|
|
499
|
+
if (event.payload.observation_kind === "pull_request"
|
|
500
|
+
&& snapshot.number === record.payload.pull_request.number
|
|
501
|
+
&& snapshot.head_ref_name === record.payload.review_branch
|
|
502
|
+
&& snapshot.base_ref_name === BASE_BRANCH) {
|
|
503
|
+
const candidate = {
|
|
504
|
+
...baseCandidate,
|
|
505
|
+
effectiveAt: eventEffectiveTimestamp(snapshot.updated_at, baseCandidate.observedAt),
|
|
506
|
+
};
|
|
507
|
+
pullRequest = selectLatestCandidate(pullRequest, candidate);
|
|
508
|
+
}
|
|
509
|
+
if (event.payload.observation_kind === "check"
|
|
510
|
+
&& snapshot.pull_request_number === record.payload.pull_request.number
|
|
511
|
+
&& snapshot.commit_oid === record.payload.subject.expected_commit
|
|
512
|
+
&& snapshot.name === record.payload.required_check) {
|
|
513
|
+
const candidate = {
|
|
514
|
+
...baseCandidate,
|
|
515
|
+
effectiveAt: eventEffectiveTimestamp(snapshot.updated_at ?? snapshot.completed_at ?? snapshot.started_at, baseCandidate.observedAt),
|
|
516
|
+
};
|
|
517
|
+
check = selectLatestCandidate(check, candidate);
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
for (const removal of removals) {
|
|
521
|
+
if (pullRequest?.observationKey === removal.observationKey) {
|
|
522
|
+
pullRequest = selectLatestCandidate(pullRequest, removal);
|
|
523
|
+
}
|
|
524
|
+
if (check?.observationKey === removal.observationKey)
|
|
525
|
+
check = selectLatestCandidate(check, removal);
|
|
526
|
+
}
|
|
527
|
+
if (!pullRequest || !check)
|
|
528
|
+
return undefined;
|
|
529
|
+
if (pullRequest.ambiguous || check.ambiguous) {
|
|
530
|
+
return { outcome: "blocked", reason: "required GitHub review evidence is chronologically ambiguous" };
|
|
531
|
+
}
|
|
532
|
+
if (pullRequest.change !== "present"
|
|
533
|
+
|| pullRequest.snapshot.state !== "OPEN"
|
|
534
|
+
|| pullRequest.snapshot.is_draft !== true
|
|
535
|
+
|| pullRequest.snapshot.head_oid !== record.payload.subject.expected_commit)
|
|
536
|
+
return { outcome: "blocked", reason: "required draft PR event is no longer current" };
|
|
537
|
+
const checkPassed = (check.snapshot.status === "COMPLETED" && check.snapshot.conclusion === "SUCCESS")
|
|
538
|
+
|| (check.snapshot.check_type === "status_context" && check.snapshot.status === "SUCCESS");
|
|
539
|
+
if (!checkPassed) {
|
|
540
|
+
const checkTerminal = check.change !== "present"
|
|
541
|
+
|| check.snapshot.status === "COMPLETED"
|
|
542
|
+
|| (check.snapshot.check_type === "status_context"
|
|
543
|
+
&& ["ERROR", "FAILURE"].includes(String(check.snapshot.status)));
|
|
544
|
+
return checkTerminal
|
|
545
|
+
? { outcome: "blocked", reason: "latest required GitHub check did not pass" }
|
|
546
|
+
: undefined;
|
|
547
|
+
}
|
|
548
|
+
return {
|
|
549
|
+
outcome: "ready",
|
|
550
|
+
pullRequest: pullRequest.reference,
|
|
551
|
+
check: check.reference,
|
|
552
|
+
pull_request_observed_at: pullRequest.observedAt,
|
|
553
|
+
check_observed_at: check.observedAt,
|
|
554
|
+
};
|
|
555
|
+
}
|
|
556
|
+
function assertReadyAttachments(record) {
|
|
557
|
+
const finalScan = record.payload.final_secret_scan;
|
|
558
|
+
const proofRef = record.payload.proof;
|
|
559
|
+
if (!finalScan || !proofRef || !record.payload.pull_request_event || !record.payload.check_event) {
|
|
560
|
+
throw new Error("ready review delivery is missing its evidence attachments");
|
|
561
|
+
}
|
|
562
|
+
validateStoredScan(record.payload.preflight_secret_scan, record.payload, stateRoot, "preflight");
|
|
563
|
+
const finalScanResult = validateStoredScan(finalScan, record.payload, stateRoot, "final");
|
|
564
|
+
const currentEvents = findRequiredEvents(record);
|
|
565
|
+
if (!currentEvents
|
|
566
|
+
|| currentEvents.outcome !== "ready"
|
|
567
|
+
|| canonicalJson(currentEvents.pullRequest) !== canonicalJson(record.payload.pull_request_event)
|
|
568
|
+
|| canonicalJson(currentEvents.check) !== canonicalJson(record.payload.check_event))
|
|
569
|
+
throw new Error("ready review delivery typed-event evidence is missing or stale");
|
|
570
|
+
const proof = readJsonFileNoFollow(join(stateRoot, "review-proofs", proofRef.file_name), MAX_PROOF_BYTES);
|
|
571
|
+
if (!isRecord(proof) || !isRecord(proof.contact_journal) || !Array.isArray(proof.contact_journal.contacts)) {
|
|
572
|
+
throw new Error("ready review proof lacks its contact-journal snapshot");
|
|
573
|
+
}
|
|
574
|
+
const ticket = runtime.tickets.lifecycle.read(record.payload.subject.ticket_id);
|
|
575
|
+
const issueContent = runtime.tickets.materializer.readContent({
|
|
576
|
+
ticket_id: ticket.record_id,
|
|
577
|
+
sha256: ticket.payload.content_sha256,
|
|
578
|
+
file_name: ticket.payload.content_file,
|
|
579
|
+
});
|
|
580
|
+
const issueEvent = findIssueEvent(record, {
|
|
581
|
+
title: issueContent.title,
|
|
582
|
+
body: issueContent.body,
|
|
583
|
+
labels: issueContent.labels,
|
|
584
|
+
url: issueContent.url ?? "",
|
|
585
|
+
});
|
|
586
|
+
if (!isRecord(proof.issue) || canonicalJson(proof.issue.event) !== canonicalJson(issueEvent.reference)) {
|
|
587
|
+
throw new Error("ready review proof issue event is missing or stale");
|
|
588
|
+
}
|
|
589
|
+
if (proof.contact_journal.authorization_id !== record.payload.authorization_id) {
|
|
590
|
+
throw new Error("ready review proof contact authorization changed");
|
|
591
|
+
}
|
|
592
|
+
const finalReadReceipts = record.payload.contacts.filter((receipt) => receipt.phase === "final-review-read");
|
|
593
|
+
if (finalReadReceipts.length !== 1)
|
|
594
|
+
throw new Error("ready review proof lacks its final GitHub read contact");
|
|
595
|
+
const finalReadDisclosure = readLiveContact(stateRoot, finalReadReceipts[0].contact);
|
|
596
|
+
const contactEvidence = requiredContactEvidence(record, issueEvent.observed_at, currentEvents, finalReadDisclosure.disclosed_at);
|
|
597
|
+
if (proof.contact_journal.cutoff_at !== contactEvidence.cutoffAt
|
|
598
|
+
|| canonicalJson(proof.contact_journal.sync_event_times) !== canonicalJson(contactEvidence.syncEventTimes)
|
|
599
|
+
|| canonicalJson(proof.contact_journal.contacts) !== canonicalJson(contactEvidence.contacts))
|
|
600
|
+
throw new Error("ready review proof contact evidence changed");
|
|
601
|
+
const proofCreatedAt = canonicalTimestamp(String(proof.created_at), "review proof timestamp");
|
|
602
|
+
const latestBoundTimestamp = Math.max(Date.parse(issueEvent.observed_at), Date.parse(currentEvents.pull_request_observed_at), Date.parse(currentEvents.check_observed_at), Date.parse(finalReadDisclosure.disclosed_at), ...contactEvidence.contacts.map((contact) => Date.parse(contact.disclosed_at)));
|
|
603
|
+
if (proofCreatedAt !== finalScanResult.created_at
|
|
604
|
+
|| Date.parse(proofCreatedAt) <= latestBoundTimestamp)
|
|
605
|
+
throw new Error("ready review proof chronology changed");
|
|
606
|
+
}
|
|
607
|
+
function findIssueEvent(record, issue) {
|
|
608
|
+
let selected;
|
|
609
|
+
for (const event of runtime.outbox.listEvents()) {
|
|
610
|
+
if (event.event_kind !== "github-observed"
|
|
611
|
+
|| event.payload.repository !== record.payload.repository
|
|
612
|
+
|| event.payload.observation_kind !== "issue"
|
|
613
|
+
|| event.payload.change !== "present"
|
|
614
|
+
|| typeof event.payload.payload_sha256 !== "string")
|
|
615
|
+
continue;
|
|
616
|
+
const digest = event.payload.payload_sha256;
|
|
617
|
+
const fileName = event.payload.snapshot_file;
|
|
618
|
+
if (!SHA256_PATTERN.test(digest) || fileName !== `${digest}.json`)
|
|
619
|
+
continue;
|
|
620
|
+
const snapshot = readJsonFileNoFollow(join(stateRoot, "github-snapshots", fileName), MAX_GITHUB_BYTES);
|
|
621
|
+
if (sha256(canonicalJson(snapshot)) !== digest
|
|
622
|
+
|| !isRecord(snapshot)
|
|
623
|
+
|| snapshot.issue_node_id !== record.payload.issue_node_id
|
|
624
|
+
|| snapshot.title !== issue.title
|
|
625
|
+
|| snapshot.body !== issue.body
|
|
626
|
+
|| canonicalJson(snapshot.labels) !== canonicalJson(issue.labels)
|
|
627
|
+
|| snapshot.url !== issue.url)
|
|
628
|
+
continue;
|
|
629
|
+
const candidate = {
|
|
630
|
+
reference: { event_id: event.event_id, payload_sha256: digest },
|
|
631
|
+
observed_at: canonicalTimestamp(event.occurred_at, "review issue event timestamp"),
|
|
632
|
+
effective_at: eventEffectiveTimestamp(snapshot.updated_at, event.occurred_at),
|
|
633
|
+
event_id: event.event_id,
|
|
634
|
+
};
|
|
635
|
+
if (!selected
|
|
636
|
+
|| Date.parse(candidate.effective_at) > Date.parse(selected.effective_at)
|
|
637
|
+
|| (candidate.effective_at === selected.effective_at && candidate.event_id.localeCompare(selected.event_id) > 0))
|
|
638
|
+
selected = candidate;
|
|
639
|
+
}
|
|
640
|
+
if (!selected)
|
|
641
|
+
throw new Error("review proof lacks the typed issue event for its canonical ticket content");
|
|
642
|
+
return { reference: selected.reference, observed_at: selected.observed_at };
|
|
643
|
+
}
|
|
644
|
+
function buildProof(record, events, readback, issueEvent, contactEvidence, finalizedAt) {
|
|
645
|
+
const pullRequest = readback.snapshot.pull_request;
|
|
646
|
+
const finalReadReceipt = {
|
|
647
|
+
...readback.receipt,
|
|
648
|
+
response_sha256: sha256(canonicalJson(readback.snapshot)),
|
|
649
|
+
};
|
|
650
|
+
return {
|
|
651
|
+
schema_version: 1,
|
|
652
|
+
proof_id: createStableId("review-proof", record.record_id, record.payload.subject.expected_commit),
|
|
653
|
+
delivery_id: record.record_id,
|
|
654
|
+
repository: record.payload.repository,
|
|
655
|
+
issue: {
|
|
656
|
+
number: record.payload.issue_number,
|
|
657
|
+
node_id: record.payload.issue_node_id,
|
|
658
|
+
url_sha256: record.payload.issue_url_sha256,
|
|
659
|
+
ticket_id: record.payload.subject.ticket_id,
|
|
660
|
+
source_ref_sha256: record.payload.source_ref_sha256,
|
|
661
|
+
event: issueEvent.reference,
|
|
662
|
+
},
|
|
663
|
+
goal: { id: record.payload.subject.goal_id, version: record.payload.subject.goal_version },
|
|
664
|
+
dispatch: { id: record.payload.subject.dispatch_id, version: record.payload.subject.dispatch_version },
|
|
665
|
+
session: { id: record.payload.subject.session_id },
|
|
666
|
+
worker: { record_id: record.payload.subject.worker_record_id, id: record.payload.subject.worker_id },
|
|
667
|
+
worktree: {
|
|
668
|
+
id: record.payload.subject.worktree_id,
|
|
669
|
+
root_sha256: sha256(record.payload.subject.worktree_root),
|
|
670
|
+
branch: record.payload.review_branch,
|
|
671
|
+
base_commit: record.payload.subject.worktree_base_commit,
|
|
672
|
+
verified_commit: record.payload.subject.expected_commit,
|
|
673
|
+
},
|
|
674
|
+
checks: {
|
|
675
|
+
deterministic_evidence: record.payload.subject.deterministic_evidence,
|
|
676
|
+
grade_evidence: record.payload.subject.grade_evidence,
|
|
677
|
+
required_remote_check: { name: record.payload.required_check, event: events.check },
|
|
678
|
+
},
|
|
679
|
+
github: {
|
|
680
|
+
base_branch: BASE_BRANCH,
|
|
681
|
+
branch: record.payload.review_branch,
|
|
682
|
+
pull_request: { ...pullRequest, event: events.pullRequest },
|
|
683
|
+
contacts: [...record.payload.contacts, finalReadReceipt],
|
|
684
|
+
},
|
|
685
|
+
contact_journal: {
|
|
686
|
+
authorization_id: record.payload.authorization_id,
|
|
687
|
+
cutoff_at: contactEvidence.cutoffAt,
|
|
688
|
+
sync_event_times: contactEvidence.syncEventTimes,
|
|
689
|
+
contacts: contactEvidence.contacts,
|
|
690
|
+
},
|
|
691
|
+
secret_scans: { preflight: record.payload.preflight_secret_scan },
|
|
692
|
+
release: { status: "not_stable", merge_disposition: "human_hold", automatic_merge: false },
|
|
693
|
+
created_at: finalizedAt,
|
|
694
|
+
};
|
|
695
|
+
}
|
|
696
|
+
function requiredContactEvidence(record, issueObservedAt, events, cutoffAt) {
|
|
697
|
+
const gradeRequestId = gates.read(record.payload.result_gate_id).payload.grade_request?.request_id;
|
|
698
|
+
if (!gradeRequestId)
|
|
699
|
+
throw new Error("review proof lacks its opposite-engine grade request identity");
|
|
700
|
+
const syncEventTimes = [...new Set([
|
|
701
|
+
issueObservedAt,
|
|
702
|
+
events.pull_request_observed_at,
|
|
703
|
+
events.check_observed_at,
|
|
704
|
+
])].sort();
|
|
705
|
+
const canonicalCutoff = canonicalTimestamp(cutoffAt, "review contact evidence cutoff");
|
|
706
|
+
const providerBindings = [
|
|
707
|
+
{ operation: "conductor-session", subjectId: record.payload.conductor_session_id },
|
|
708
|
+
{ operation: "worker-session", subjectId: record.payload.subject.session_id },
|
|
709
|
+
{ operation: "opposite-engine-grade", subjectId: gradeRequestId },
|
|
710
|
+
];
|
|
711
|
+
const selected = listLiveContacts(stateRoot)
|
|
712
|
+
.filter(({ disclosure }) => (disclosure.authorization_id === record.payload.authorization_id
|
|
713
|
+
&& disclosure.repository === record.payload.repository
|
|
714
|
+
&& Date.parse(disclosure.disclosed_at) <= Date.parse(canonicalCutoff)
|
|
715
|
+
&& (providerBindings.some((binding) => (disclosure.operation === binding.operation
|
|
716
|
+
&& disclosure.subject_id === binding.subjectId))
|
|
717
|
+
|| (disclosure.operation === "github-event-sync"
|
|
718
|
+
&& syncEventTimes.includes(disclosure.disclosed_at)))));
|
|
719
|
+
for (const binding of providerBindings) {
|
|
720
|
+
const matching = selected.filter(({ disclosure }) => (disclosure.operation === binding.operation
|
|
721
|
+
&& disclosure.subject_id === binding.subjectId));
|
|
722
|
+
if (matching.length === 0) {
|
|
723
|
+
throw new Error(`review contact journal lacks ${binding.operation} evidence`);
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
for (const eventTime of syncEventTimes) {
|
|
727
|
+
if (!selected.some(({ disclosure }) => (disclosure.operation === "github-event-sync"
|
|
728
|
+
&& disclosure.disclosed_at === eventTime)))
|
|
729
|
+
throw new Error(`review contact journal lacks github-event-sync evidence for ${eventTime}`);
|
|
730
|
+
}
|
|
731
|
+
const contacts = selected
|
|
732
|
+
.map(({ reference, disclosure }) => ({
|
|
733
|
+
reference,
|
|
734
|
+
operation: disclosure.operation,
|
|
735
|
+
subject_id: disclosure.subject_id,
|
|
736
|
+
disclosed_at: disclosure.disclosed_at,
|
|
737
|
+
}))
|
|
738
|
+
.sort((left, right) => left.reference.contact_id.localeCompare(right.reference.contact_id));
|
|
739
|
+
return Object.freeze({
|
|
740
|
+
cutoffAt: canonicalCutoff,
|
|
741
|
+
syncEventTimes: Object.freeze(syncEventTimes),
|
|
742
|
+
contacts: Object.freeze(contacts),
|
|
743
|
+
});
|
|
744
|
+
}
|
|
745
|
+
function writeProof(candidate, finalScan) {
|
|
746
|
+
const proof = {
|
|
747
|
+
...candidate,
|
|
748
|
+
secret_scans: { ...candidate.secret_scans, final: finalScan },
|
|
749
|
+
};
|
|
750
|
+
const bytes = canonicalBytes(proof);
|
|
751
|
+
if (bytes.byteLength > MAX_PROOF_BYTES)
|
|
752
|
+
throw new Error("review proof exceeds its size limit");
|
|
753
|
+
const proofId = String(proof.proof_id);
|
|
754
|
+
const root = openStableDirectory(join(stateRoot, "review-proofs"), "review proof root");
|
|
755
|
+
try {
|
|
756
|
+
const fileName = `${proofId}.json`;
|
|
757
|
+
const path = join(root.path, fileName);
|
|
758
|
+
assertFileInside(root, path);
|
|
759
|
+
writeImmutableFile(path, bytes);
|
|
760
|
+
const stored = readImmutableFile(path, MAX_PROOF_BYTES);
|
|
761
|
+
if (!stored.equals(bytes))
|
|
762
|
+
throw new Error("review proof replay changed content");
|
|
763
|
+
return Object.freeze({ proof_id: proofId, sha256: sha256(stored), file_name: fileName });
|
|
764
|
+
}
|
|
765
|
+
finally {
|
|
766
|
+
root.close();
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
function block(record, reason, receipt) {
|
|
770
|
+
return deliveries.transition(record, `blocked-${sha256(reason).slice(0, 12)}`, "blocked", receipt ? [receipt] : [], (payload) => ({
|
|
771
|
+
...payload,
|
|
772
|
+
blocked_reason: reason,
|
|
773
|
+
}));
|
|
774
|
+
}
|
|
775
|
+
return Object.freeze({ advance });
|
|
776
|
+
}
|
|
777
|
+
function createReviewDeliveryStore(options) {
|
|
778
|
+
const payloadSchema = loadJsonSchema(join(options.packageRoot, "schemas", "review-delivery-payload.schema.json"));
|
|
779
|
+
const actor = assertActorId("oms-system");
|
|
780
|
+
const store = createRecordStore({
|
|
781
|
+
recordsRoot: join(options.stateRoot, "review-deliveries"),
|
|
782
|
+
locksRoot: join(options.stateRoot, "locks"),
|
|
783
|
+
recordValidator(raw) {
|
|
784
|
+
if (raw.record_kind !== "review-delivery" || !Object.hasOwn(LEGAL_EDGES, raw.state)) {
|
|
785
|
+
throw new Error("invalid review-delivery runtime record");
|
|
786
|
+
}
|
|
787
|
+
assertSchema(raw.payload, payloadSchema, "review-delivery payload");
|
|
788
|
+
validateReviewRecord(raw, options.stateRoot);
|
|
789
|
+
},
|
|
790
|
+
createValidator(raw) {
|
|
791
|
+
if (raw.version !== 0 || raw.state !== "planned" || raw.actor !== actor) {
|
|
792
|
+
throw new Error("review delivery must begin planned under OMS system authority");
|
|
793
|
+
}
|
|
794
|
+
},
|
|
795
|
+
transitionValidator(current, next) {
|
|
796
|
+
const before = current;
|
|
797
|
+
const after = next;
|
|
798
|
+
if (!LEGAL_EDGES[before.state].includes(after.state)) {
|
|
799
|
+
throw new Error(`illegal review-delivery transition: ${before.state} -> ${after.state}`);
|
|
800
|
+
}
|
|
801
|
+
for (const field of [
|
|
802
|
+
"delivery_id", "repository", "issue_number", "issue_node_id", "issue_url_sha256", "source_ref_sha256",
|
|
803
|
+
"subject", "result_gate_id", "authorization_id", "conductor_session_id", "base_branch", "review_branch",
|
|
804
|
+
"required_check", "created_at", "preflight_secret_scan",
|
|
805
|
+
]) {
|
|
806
|
+
if (canonicalJson(before.payload[field]) !== canonicalJson(after.payload[field])) {
|
|
807
|
+
throw new Error(`review-delivery ${field} is immutable`);
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
if (Date.parse(after.payload.updated_at) <= Date.parse(before.payload.updated_at)) {
|
|
811
|
+
throw new Error("review-delivery update time must advance");
|
|
812
|
+
}
|
|
813
|
+
if (after.payload.contacts.length < before.payload.contacts.length)
|
|
814
|
+
throw new Error("review contacts cannot be removed");
|
|
815
|
+
for (let index = 0; index < before.payload.contacts.length; index += 1) {
|
|
816
|
+
const oldReceipt = before.payload.contacts[index];
|
|
817
|
+
const newReceipt = after.payload.contacts[index];
|
|
818
|
+
const pendingCompletion = oldReceipt.response_sha256 === null
|
|
819
|
+
&& newReceipt.response_sha256 !== null
|
|
820
|
+
&& canonicalJson({ ...oldReceipt, response_sha256: null }) === canonicalJson({ ...newReceipt, response_sha256: null });
|
|
821
|
+
if (!pendingCompletion && canonicalJson(oldReceipt) !== canonicalJson(newReceipt)) {
|
|
822
|
+
throw new Error("review contact receipts are append-only");
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
monotonicObject("pull request", before.payload.pull_request, after.payload.pull_request);
|
|
826
|
+
monotonicObject("pull request event", before.payload.pull_request_event, after.payload.pull_request_event);
|
|
827
|
+
monotonicObject("check event", before.payload.check_event, after.payload.check_event);
|
|
828
|
+
monotonicObject("final secret scan", before.payload.final_secret_scan, after.payload.final_secret_scan);
|
|
829
|
+
monotonicObject("proof", before.payload.proof, after.payload.proof);
|
|
830
|
+
},
|
|
831
|
+
});
|
|
832
|
+
function applyTransition(record, step, toState, receipts, update) {
|
|
833
|
+
return store.transition({
|
|
834
|
+
recordId: record.record_id,
|
|
835
|
+
ownerId: actor,
|
|
836
|
+
expectedVersion: record.version,
|
|
837
|
+
nonce: createStableId("review-step", record.record_id, step),
|
|
838
|
+
expectedActor: record.actor,
|
|
839
|
+
expectedAttempt: record.attempt,
|
|
840
|
+
expectedLeaseToken: record.lease_token,
|
|
841
|
+
fromStates: [record.state],
|
|
842
|
+
toState,
|
|
843
|
+
updatePayload: (current) => {
|
|
844
|
+
const changed = update(current.payload);
|
|
845
|
+
return {
|
|
846
|
+
...changed,
|
|
847
|
+
updated_at: nextTimestamp(current.payload.updated_at, options.clock),
|
|
848
|
+
contacts: [...changed.contacts, ...receipts],
|
|
849
|
+
};
|
|
850
|
+
},
|
|
851
|
+
});
|
|
852
|
+
}
|
|
853
|
+
return {
|
|
854
|
+
create(input) {
|
|
855
|
+
const id = assertRecordId(input.deliveryId, "review delivery id");
|
|
856
|
+
const record = {
|
|
857
|
+
schema_version: 1,
|
|
858
|
+
record_id: id,
|
|
859
|
+
record_kind: assertOpaqueId("review-delivery"),
|
|
860
|
+
version: 0,
|
|
861
|
+
state: "planned",
|
|
862
|
+
attempt: 1,
|
|
863
|
+
max_attempts: 1,
|
|
864
|
+
actor,
|
|
865
|
+
lease_token: null,
|
|
866
|
+
transition_nonce: assertTransitionNonce(createStableId("review-create", id)),
|
|
867
|
+
payload: input.payload,
|
|
868
|
+
transition_outcomes: [],
|
|
869
|
+
};
|
|
870
|
+
record.transition_outcomes.push({
|
|
871
|
+
nonce: record.transition_nonce,
|
|
872
|
+
version: 0,
|
|
873
|
+
state: record.state,
|
|
874
|
+
attempt: 1,
|
|
875
|
+
actor,
|
|
876
|
+
lease_token: null,
|
|
877
|
+
});
|
|
878
|
+
return store.create(record);
|
|
879
|
+
},
|
|
880
|
+
readIfExists(deliveryId) {
|
|
881
|
+
return store.readIfExists(deliveryId);
|
|
882
|
+
},
|
|
883
|
+
transition(record, step, toState, receipts = [], update = (payload) => payload) {
|
|
884
|
+
return applyTransition(record, step, toState, receipts, update).record;
|
|
885
|
+
},
|
|
886
|
+
claim(record, step, toState, receipts) {
|
|
887
|
+
const result = applyTransition(record, step, toState, receipts, (payload) => payload);
|
|
888
|
+
return { status: result.status, record: result.record };
|
|
889
|
+
},
|
|
890
|
+
};
|
|
891
|
+
}
|
|
892
|
+
function validateReviewRecord(record, stateRoot) {
|
|
893
|
+
const payload = record.payload;
|
|
894
|
+
if (payload.delivery_id !== record.record_id
|
|
895
|
+
|| record.actor !== "oms-system"
|
|
896
|
+
|| record.lease_token !== null
|
|
897
|
+
|| record.attempt !== 1
|
|
898
|
+
|| record.max_attempts !== 1
|
|
899
|
+
|| payload.base_branch !== BASE_BRANCH
|
|
900
|
+
|| !BRANCH_PATTERN.test(payload.review_branch)
|
|
901
|
+
|| payload.review_branch !== payload.subject.worktree_branch
|
|
902
|
+
|| payload.subject.dispatch_id === payload.subject.ticket_id
|
|
903
|
+
|| payload.subject.deterministic_evidence === null
|
|
904
|
+
|| payload.subject.grade_evidence === null)
|
|
905
|
+
throw new Error("review-delivery identity mismatch");
|
|
906
|
+
assertOpaqueId(payload.result_gate_id, "review result-gate id");
|
|
907
|
+
assertOpaqueId(payload.authorization_id, "review authorization id");
|
|
908
|
+
validateRequiredCheck(payload.required_check);
|
|
909
|
+
canonicalTimestamp(payload.created_at, "review creation timestamp");
|
|
910
|
+
canonicalTimestamp(payload.updated_at, "review update timestamp");
|
|
911
|
+
if (Date.parse(payload.updated_at) < Date.parse(payload.created_at))
|
|
912
|
+
throw new Error("review update predates creation");
|
|
913
|
+
validateReviewSubject(payload.subject);
|
|
914
|
+
validateStoredScan(payload.preflight_secret_scan, payload, stateRoot, "preflight");
|
|
915
|
+
if (payload.final_secret_scan)
|
|
916
|
+
validateStoredScan(payload.final_secret_scan, payload, stateRoot, "final");
|
|
917
|
+
for (const receipt of payload.contacts)
|
|
918
|
+
validateContactReceipt(receipt, payload, stateRoot);
|
|
919
|
+
if (payload.pull_request)
|
|
920
|
+
validatePullRequest(payload.pull_request);
|
|
921
|
+
if (payload.pull_request_event)
|
|
922
|
+
validateEventReference(payload.pull_request_event);
|
|
923
|
+
if (payload.check_event)
|
|
924
|
+
validateEventReference(payload.check_event);
|
|
925
|
+
if (payload.proof)
|
|
926
|
+
validateProofReference(payload.proof, stateRoot);
|
|
927
|
+
if (record.state === "ready" && (payload.proof === null
|
|
928
|
+
|| payload.final_secret_scan === null
|
|
929
|
+
|| payload.pull_request === null
|
|
930
|
+
|| payload.pull_request_event === null
|
|
931
|
+
|| payload.check_event === null
|
|
932
|
+
|| payload.blocked_reason !== null))
|
|
933
|
+
throw new Error("ready review delivery lacks final proof evidence");
|
|
934
|
+
if (record.state === "blocked" && payload.blocked_reason === null) {
|
|
935
|
+
throw new Error("blocked review delivery lacks a reason");
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
function validateReviewSubject(subject) {
|
|
939
|
+
exactRecord(subject, [
|
|
940
|
+
"attempt", "deterministic_evidence", "dispatch_id", "dispatch_version", "expected_commit",
|
|
941
|
+
"goal_id", "goal_version", "grade_evidence", "lease_token", "session_id", "ticket_id",
|
|
942
|
+
"worker_id", "worker_record_id", "worktree_base_commit", "worktree_branch", "worktree_id", "worktree_root",
|
|
943
|
+
], "review subject");
|
|
944
|
+
for (const [label, value] of [
|
|
945
|
+
["goal id", subject.goal_id], ["dispatch id", subject.dispatch_id], ["ticket id", subject.ticket_id],
|
|
946
|
+
["lease token", subject.lease_token], ["worker record id", subject.worker_record_id],
|
|
947
|
+
["worker id", subject.worker_id], ["session id", subject.session_id], ["worktree id", subject.worktree_id],
|
|
948
|
+
])
|
|
949
|
+
assertOpaqueId(value, `review ${label}`);
|
|
950
|
+
if (!Number.isSafeInteger(subject.goal_version) || subject.goal_version < 0)
|
|
951
|
+
throw new Error("invalid review goal version");
|
|
952
|
+
if (!Number.isSafeInteger(subject.dispatch_version) || subject.dispatch_version < 0)
|
|
953
|
+
throw new Error("invalid review dispatch version");
|
|
954
|
+
positiveInteger(subject.attempt, "review attempt");
|
|
955
|
+
if (!BRANCH_PATTERN.test(subject.worktree_branch) || !subject.worktree_root.startsWith("/"))
|
|
956
|
+
throw new Error("invalid review worktree");
|
|
957
|
+
commit(subject.worktree_base_commit, "review base commit");
|
|
958
|
+
commit(subject.expected_commit, "review verified commit");
|
|
959
|
+
validateEvidenceReference(subject.deterministic_evidence);
|
|
960
|
+
validateEvidenceReference(subject.grade_evidence);
|
|
961
|
+
}
|
|
962
|
+
function validateEvidenceReference(reference) {
|
|
963
|
+
exactRecord(reference, ["evidence_id", "file_name", "sha256"], "review evidence reference");
|
|
964
|
+
assertOpaqueId(reference.evidence_id, "review evidence id");
|
|
965
|
+
if (!SHA256_PATTERN.test(reference.sha256) || reference.file_name !== `${reference.evidence_id}.json`) {
|
|
966
|
+
throw new Error("invalid review evidence reference");
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
function validateScanReference(reference) {
|
|
970
|
+
exactRecord(reference, ["file_name", "scan_id", "sha256"], "review secret scan reference");
|
|
971
|
+
assertOpaqueId(reference.scan_id, "review secret scan id");
|
|
972
|
+
if (!SHA256_PATTERN.test(reference.sha256) || reference.file_name !== `${reference.scan_id}.json`) {
|
|
973
|
+
throw new Error("invalid review secret scan reference");
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
function validateStoredScan(reference, payload, stateRoot, phase) {
|
|
977
|
+
validateScanReference(reference);
|
|
978
|
+
const scan = readSecretScan(stateRoot, reference);
|
|
979
|
+
if (scan.phase !== phase
|
|
980
|
+
|| scan.ticket_id !== payload.subject.ticket_id
|
|
981
|
+
|| scan.base_commit !== payload.subject.worktree_base_commit
|
|
982
|
+
|| scan.verified_commit !== payload.subject.expected_commit
|
|
983
|
+
|| scan.passed !== true)
|
|
984
|
+
throw new Error(`review ${phase} secret scan binding is invalid`);
|
|
985
|
+
return scan;
|
|
986
|
+
}
|
|
987
|
+
function validateContactReceipt(receipt, payload, stateRoot) {
|
|
988
|
+
exactRecord(receipt, ["contact", "operation", "phase", "response_sha256"], "review contact receipt");
|
|
989
|
+
if (!/^[a-z0-9-]{1,64}$/u.test(receipt.phase) || !["git-branch-push", "github-review-read", "github-pr-create"].includes(receipt.operation)) {
|
|
990
|
+
throw new Error("invalid review contact receipt");
|
|
991
|
+
}
|
|
992
|
+
exactRecord(receipt.contact, ["contact_id", "file_name", "sha256"], "review contact reference");
|
|
993
|
+
assertOpaqueId(receipt.contact.contact_id, "review contact id");
|
|
994
|
+
if (!SHA256_PATTERN.test(receipt.contact.sha256)
|
|
995
|
+
|| receipt.contact.file_name !== `${receipt.contact.contact_id}.json`
|
|
996
|
+
|| (receipt.response_sha256 !== null && !SHA256_PATTERN.test(receipt.response_sha256)))
|
|
997
|
+
throw new Error("invalid review contact reference");
|
|
998
|
+
const disclosure = readLiveContact(stateRoot, receipt.contact);
|
|
999
|
+
if (disclosure.operation !== receipt.operation
|
|
1000
|
+
|| disclosure.authorization_id !== payload.authorization_id
|
|
1001
|
+
|| disclosure.conductor_session_id !== payload.conductor_session_id
|
|
1002
|
+
|| disclosure.repository !== payload.repository)
|
|
1003
|
+
throw new Error("review contact disclosure does not match its delivery authority");
|
|
1004
|
+
}
|
|
1005
|
+
function validatePullRequest(pullRequest) {
|
|
1006
|
+
exactRecord(pullRequest, ["head_oid", "node_id", "number", "url_sha256"], "review pull request reference");
|
|
1007
|
+
nodeId(pullRequest.node_id, "review PR node id");
|
|
1008
|
+
positiveInteger(pullRequest.number, "review PR number");
|
|
1009
|
+
commit(pullRequest.head_oid, "review PR head");
|
|
1010
|
+
if (!SHA256_PATTERN.test(pullRequest.url_sha256))
|
|
1011
|
+
throw new Error("invalid review PR URL digest");
|
|
1012
|
+
}
|
|
1013
|
+
function validateEventReference(reference) {
|
|
1014
|
+
exactRecord(reference, ["event_id", "payload_sha256"], "review event reference");
|
|
1015
|
+
assertOpaqueId(reference.event_id, "review event id");
|
|
1016
|
+
if (!SHA256_PATTERN.test(reference.payload_sha256))
|
|
1017
|
+
throw new Error("invalid review event digest");
|
|
1018
|
+
}
|
|
1019
|
+
function validateProofReference(reference, stateRoot) {
|
|
1020
|
+
exactRecord(reference, ["file_name", "proof_id", "sha256"], "review proof reference");
|
|
1021
|
+
assertOpaqueId(reference.proof_id, "review proof id");
|
|
1022
|
+
if (!SHA256_PATTERN.test(reference.sha256) || reference.file_name !== `${reference.proof_id}.json`) {
|
|
1023
|
+
throw new Error("invalid review proof reference");
|
|
1024
|
+
}
|
|
1025
|
+
const path = join(stateRoot, "review-proofs", reference.file_name);
|
|
1026
|
+
if (sha256(readImmutableFile(path, MAX_PROOF_BYTES)) !== reference.sha256)
|
|
1027
|
+
throw new Error("review proof digest mismatch");
|
|
1028
|
+
}
|
|
1029
|
+
function assertReplayBinding(record, canonical) {
|
|
1030
|
+
if (canonicalJson(record.payload.subject) !== canonicalJson(canonical.subject)
|
|
1031
|
+
|| record.payload.repository !== canonical.repository
|
|
1032
|
+
|| record.payload.issue_number !== canonical.issueNumber
|
|
1033
|
+
|| record.payload.issue_node_id !== canonical.issueNodeId
|
|
1034
|
+
|| record.payload.issue_url_sha256 !== canonical.issueUrlSha256
|
|
1035
|
+
|| record.payload.source_ref_sha256 !== canonical.sourceRefSha256
|
|
1036
|
+
|| record.payload.authorization_id !== canonical.authorizationId
|
|
1037
|
+
|| record.payload.conductor_session_id !== canonical.conductorSessionId)
|
|
1038
|
+
throw new Error("review delivery replay changed its protected authority binding");
|
|
1039
|
+
}
|
|
1040
|
+
function reviewReadRequest(record, env) {
|
|
1041
|
+
const target = normalizeGithubSyncTarget(record.payload.repository);
|
|
1042
|
+
return githubRequest(record.payload.subject.worktree_root, {
|
|
1043
|
+
query: REVIEW_QUERY,
|
|
1044
|
+
variables: { owner: target.owner, name: target.name, branchRef: `refs/heads/${record.payload.review_branch}` },
|
|
1045
|
+
}, env);
|
|
1046
|
+
}
|
|
1047
|
+
function createPullRequestRequest(record, repositoryId, env) {
|
|
1048
|
+
return githubRequest(record.payload.subject.worktree_root, {
|
|
1049
|
+
query: CREATE_PR_MUTATION,
|
|
1050
|
+
variables: {
|
|
1051
|
+
repositoryId,
|
|
1052
|
+
baseRefName: BASE_BRANCH,
|
|
1053
|
+
headRefName: record.payload.review_branch,
|
|
1054
|
+
title: draftPullRequestTitle(record.payload.issue_number),
|
|
1055
|
+
body: draftPullRequestBody(record.payload.issue_number, record.payload.subject),
|
|
1056
|
+
},
|
|
1057
|
+
}, env);
|
|
1058
|
+
}
|
|
1059
|
+
function pushRequest(record, env) {
|
|
1060
|
+
const sshTarget = `git@github.com:${record.payload.repository}.git`;
|
|
1061
|
+
return {
|
|
1062
|
+
command: "git",
|
|
1063
|
+
args: [
|
|
1064
|
+
"push",
|
|
1065
|
+
"--porcelain",
|
|
1066
|
+
"--no-verify",
|
|
1067
|
+
sshTarget,
|
|
1068
|
+
`${record.payload.subject.expected_commit}:refs/heads/${record.payload.review_branch}`,
|
|
1069
|
+
],
|
|
1070
|
+
cwd: record.payload.subject.worktree_root,
|
|
1071
|
+
env: gitEnvironment(env),
|
|
1072
|
+
timeoutMs: 60_000,
|
|
1073
|
+
maxBufferBytes: 512 * 1024,
|
|
1074
|
+
};
|
|
1075
|
+
}
|
|
1076
|
+
function githubRequest(cwd, input, env) {
|
|
1077
|
+
return {
|
|
1078
|
+
command: "gh",
|
|
1079
|
+
args: ["api", "graphql", "--input", "-"],
|
|
1080
|
+
cwd,
|
|
1081
|
+
env: githubEnvironment(env),
|
|
1082
|
+
input: JSON.stringify(input),
|
|
1083
|
+
timeoutMs: 30_000,
|
|
1084
|
+
maxBufferBytes: MAX_GITHUB_BYTES,
|
|
1085
|
+
};
|
|
1086
|
+
}
|
|
1087
|
+
function parseReviewRead(result, record) {
|
|
1088
|
+
const root = parseGraphql(result.stdout, "review read");
|
|
1089
|
+
const data = exactRecord(root.data, ["repository", "rateLimit"], "review read data");
|
|
1090
|
+
assertRateLimit(data.rateLimit);
|
|
1091
|
+
const repository = exactRecord(data.repository, ["id", "pullRequests", "ref"], "review repository");
|
|
1092
|
+
const repositoryNodeId = nodeId(repository.id, "review repository id");
|
|
1093
|
+
let branchHead = null;
|
|
1094
|
+
if (repository.ref !== null) {
|
|
1095
|
+
const ref = exactRecord(repository.ref, ["target"], "review ref");
|
|
1096
|
+
const target = exactRecord(ref.target, ["oid"], "review ref target");
|
|
1097
|
+
branchHead = commit(target.oid, "review branch head");
|
|
1098
|
+
}
|
|
1099
|
+
const pullRequestsConnection = exactRecord(repository.pullRequests, ["nodes"], "review pull requests");
|
|
1100
|
+
if (!Array.isArray(pullRequestsConnection.nodes))
|
|
1101
|
+
throw new Error("review pull request nodes are invalid");
|
|
1102
|
+
let pullRequest = null;
|
|
1103
|
+
for (const value of pullRequestsConnection.nodes) {
|
|
1104
|
+
const candidate = parsePullRequest(value, record);
|
|
1105
|
+
if (!candidate)
|
|
1106
|
+
continue;
|
|
1107
|
+
if (pullRequest)
|
|
1108
|
+
throw new Error("multiple open draft PRs match the OMS review branch");
|
|
1109
|
+
pullRequest = candidate;
|
|
1110
|
+
}
|
|
1111
|
+
return {
|
|
1112
|
+
repository_node_id: repositoryNodeId,
|
|
1113
|
+
branch_head_oid: branchHead,
|
|
1114
|
+
pull_request: pullRequest,
|
|
1115
|
+
};
|
|
1116
|
+
}
|
|
1117
|
+
function parseCreatedPullRequest(result, record) {
|
|
1118
|
+
const root = parseGraphql(result.stdout, "draft PR create");
|
|
1119
|
+
const data = exactRecord(root.data, ["createPullRequest"], "draft PR data");
|
|
1120
|
+
const created = exactRecord(data.createPullRequest, ["pullRequest"], "draft PR mutation");
|
|
1121
|
+
const candidate = parsePullRequest(created.pullRequest, record);
|
|
1122
|
+
if (!candidate)
|
|
1123
|
+
throw new Error("created PR does not match the protected review delivery");
|
|
1124
|
+
return candidate;
|
|
1125
|
+
}
|
|
1126
|
+
function parsePullRequest(value, record) {
|
|
1127
|
+
const pr = exactRecord(value, [
|
|
1128
|
+
"baseRefName", "commits", "headRefName", "id", "isDraft", "number", "state", "url",
|
|
1129
|
+
], "review pull request");
|
|
1130
|
+
if (pr.headRefName !== record.payload.review_branch || pr.baseRefName !== BASE_BRANCH || pr.state !== "OPEN")
|
|
1131
|
+
return null;
|
|
1132
|
+
if (pr.isDraft !== true)
|
|
1133
|
+
throw new Error("matching review PR must remain draft");
|
|
1134
|
+
const commits = exactRecord(pr.commits, ["nodes", "totalCount"], "review PR commits");
|
|
1135
|
+
if (!Number.isSafeInteger(commits.totalCount) || Number(commits.totalCount) < 1 || !Array.isArray(commits.nodes) || commits.nodes.length !== 1) {
|
|
1136
|
+
throw new Error("review PR head commit is ambiguous");
|
|
1137
|
+
}
|
|
1138
|
+
const node = exactRecord(commits.nodes[0], ["commit"], "review PR commit node");
|
|
1139
|
+
const commitNode = exactRecord(node.commit, ["oid"], "review PR commit");
|
|
1140
|
+
const headOid = commit(commitNode.oid, "review PR head");
|
|
1141
|
+
if (headOid !== record.payload.subject.expected_commit)
|
|
1142
|
+
throw new Error("review PR head differs from the verified commit");
|
|
1143
|
+
const number = positiveInteger(pr.number, "review PR number");
|
|
1144
|
+
const url = githubUrl(pr.url, "review PR URL");
|
|
1145
|
+
return {
|
|
1146
|
+
node_id: nodeId(pr.id, "review PR id"),
|
|
1147
|
+
number,
|
|
1148
|
+
url_sha256: sha256(url),
|
|
1149
|
+
head_oid: headOid,
|
|
1150
|
+
};
|
|
1151
|
+
}
|
|
1152
|
+
function parseGraphql(stdout, label) {
|
|
1153
|
+
if (Buffer.byteLength(stdout, "utf8") > MAX_GITHUB_BYTES)
|
|
1154
|
+
throw new Error(`${label} exceeds its output limit`);
|
|
1155
|
+
let value;
|
|
1156
|
+
try {
|
|
1157
|
+
value = JSON.parse(stdout);
|
|
1158
|
+
}
|
|
1159
|
+
catch {
|
|
1160
|
+
throw new Error(`${label} returned malformed JSON`);
|
|
1161
|
+
}
|
|
1162
|
+
const root = exactRecord(value, ["data"], label);
|
|
1163
|
+
return root;
|
|
1164
|
+
}
|
|
1165
|
+
function assertRateLimit(value) {
|
|
1166
|
+
const rate = exactRecord(value, ["remaining", "resetAt"], "GitHub rate limit");
|
|
1167
|
+
if (!Number.isSafeInteger(rate.remaining) || Number(rate.remaining) < 1)
|
|
1168
|
+
throw new Error("GitHub rate limit is exhausted");
|
|
1169
|
+
canonicalTimestamp(String(rate.resetAt), "GitHub rate reset timestamp");
|
|
1170
|
+
}
|
|
1171
|
+
function outboundPlan(repository, issueNumber, issueNodeId, issueScanContent, subject, requiredCheck) {
|
|
1172
|
+
const target = normalizeGithubSyncTarget(repository);
|
|
1173
|
+
const branch = subject.worktree_branch;
|
|
1174
|
+
const title = draftPullRequestTitle(issueNumber);
|
|
1175
|
+
const body = draftPullRequestBody(issueNumber, subject);
|
|
1176
|
+
return {
|
|
1177
|
+
repository,
|
|
1178
|
+
issue: {
|
|
1179
|
+
number: issueNumber,
|
|
1180
|
+
node_id: issueNodeId,
|
|
1181
|
+
title: issueScanContent.title,
|
|
1182
|
+
body: issueScanContent.body,
|
|
1183
|
+
labels: issueScanContent.labels,
|
|
1184
|
+
url: issueScanContent.url,
|
|
1185
|
+
},
|
|
1186
|
+
branch,
|
|
1187
|
+
base_branch: BASE_BRANCH,
|
|
1188
|
+
expected_commit: subject.expected_commit,
|
|
1189
|
+
required_check: requiredCheck,
|
|
1190
|
+
process_argv: [
|
|
1191
|
+
["git", "remote", "get-url", "--push", "origin"],
|
|
1192
|
+
["git", "merge-base", "--is-ancestor", subject.worktree_base_commit, subject.expected_commit],
|
|
1193
|
+
["git", "push", "--porcelain", "--no-verify", `git@github.com:${repository}.git`, `${subject.expected_commit}:refs/heads/${branch}`],
|
|
1194
|
+
["gh", "api", "graphql", "--input", "-"],
|
|
1195
|
+
],
|
|
1196
|
+
github_inputs: [
|
|
1197
|
+
{ query: REVIEW_QUERY, variables: { owner: target.owner, name: target.name, branchRef: `refs/heads/${branch}` } },
|
|
1198
|
+
{
|
|
1199
|
+
query: CREATE_PR_MUTATION,
|
|
1200
|
+
variables: {
|
|
1201
|
+
repositoryId: "<read-back-node-id>", baseRefName: BASE_BRANCH, headRefName: branch, title, body,
|
|
1202
|
+
},
|
|
1203
|
+
},
|
|
1204
|
+
],
|
|
1205
|
+
release: { status: "not_stable", merge_disposition: "human_hold", automatic_merge: false },
|
|
1206
|
+
};
|
|
1207
|
+
}
|
|
1208
|
+
function draftPullRequestTitle(issueNumber) {
|
|
1209
|
+
return `OMS ticket #${issueNumber}`;
|
|
1210
|
+
}
|
|
1211
|
+
function draftPullRequestBody(issueNumber, subject) {
|
|
1212
|
+
return [
|
|
1213
|
+
`Automated draft for issue #${issueNumber}.`,
|
|
1214
|
+
`Verified commit: ${subject.expected_commit}`,
|
|
1215
|
+
`Deterministic evidence: ${subject.deterministic_evidence.evidence_id} (${subject.deterministic_evidence.sha256})`,
|
|
1216
|
+
`Opposite-engine grade: ${subject.grade_evidence.evidence_id} (${subject.grade_evidence.sha256})`,
|
|
1217
|
+
"Status: READY_FOR_HUMAN after required checks are observed.",
|
|
1218
|
+
"Merge, ready-for-review, tag, publish, and release remain human actions.",
|
|
1219
|
+
].join("\n\n");
|
|
1220
|
+
}
|
|
1221
|
+
function protectedBinding(subject) {
|
|
1222
|
+
return {
|
|
1223
|
+
goal_id: subject.goal_id,
|
|
1224
|
+
goal_version: subject.goal_version,
|
|
1225
|
+
dispatch_id: subject.dispatch_id,
|
|
1226
|
+
dispatch_version: subject.dispatch_version,
|
|
1227
|
+
ticket_id: subject.ticket_id,
|
|
1228
|
+
attempt: subject.attempt,
|
|
1229
|
+
lease_token: subject.lease_token,
|
|
1230
|
+
session_id: subject.session_id,
|
|
1231
|
+
worker_record_id: subject.worker_record_id,
|
|
1232
|
+
worker_id: subject.worker_id,
|
|
1233
|
+
worktree_id: subject.worktree_id,
|
|
1234
|
+
worktree_root: subject.worktree_root,
|
|
1235
|
+
worktree_branch: subject.worktree_branch,
|
|
1236
|
+
worktree_base_commit: subject.worktree_base_commit,
|
|
1237
|
+
expected_commit: subject.expected_commit,
|
|
1238
|
+
executor_role: subject.executor_role,
|
|
1239
|
+
executor_engine: subject.executor_engine,
|
|
1240
|
+
executor_evidence: subject.executor_evidence,
|
|
1241
|
+
};
|
|
1242
|
+
}
|
|
1243
|
+
function assertAuthorizedOrigin(repository, cwd, env, runner) {
|
|
1244
|
+
const result = runner({
|
|
1245
|
+
command: "git",
|
|
1246
|
+
args: ["remote", "get-url", "--push", "origin"],
|
|
1247
|
+
cwd,
|
|
1248
|
+
env: gitEnvironment(env),
|
|
1249
|
+
timeoutMs: 10_000,
|
|
1250
|
+
maxBufferBytes: 64 * 1024,
|
|
1251
|
+
});
|
|
1252
|
+
if (!processSucceeded(result))
|
|
1253
|
+
throw new Error("unable to verify the review push remote");
|
|
1254
|
+
const normalized = normalizeGithubRemoteRepository(result.stdout.trim());
|
|
1255
|
+
if (normalized !== repository)
|
|
1256
|
+
throw new Error("review push remote does not match the authorized GitHub repository");
|
|
1257
|
+
}
|
|
1258
|
+
function assertCommitAncestry(subject, env, runner) {
|
|
1259
|
+
const result = runner({
|
|
1260
|
+
command: "git",
|
|
1261
|
+
args: ["merge-base", "--is-ancestor", subject.worktree_base_commit, subject.expected_commit],
|
|
1262
|
+
cwd: subject.worktree_root,
|
|
1263
|
+
env: gitEnvironment(env),
|
|
1264
|
+
timeoutMs: 10_000,
|
|
1265
|
+
maxBufferBytes: 64 * 1024,
|
|
1266
|
+
});
|
|
1267
|
+
if (!processSucceeded(result))
|
|
1268
|
+
throw new Error("verified review commit does not descend from its frozen base");
|
|
1269
|
+
}
|
|
1270
|
+
function assertSecretScan(result, phase, subject) {
|
|
1271
|
+
if (result.phase !== phase
|
|
1272
|
+
|| result.ticket_id !== subject.ticket_id
|
|
1273
|
+
|| result.base_commit !== subject.worktree_base_commit
|
|
1274
|
+
|| result.verified_commit !== subject.expected_commit
|
|
1275
|
+
|| result.passed !== true)
|
|
1276
|
+
throw new Error(`${phase} secret scan does not match the protected review delivery`);
|
|
1277
|
+
}
|
|
1278
|
+
function toProgress(record) {
|
|
1279
|
+
return Object.freeze({
|
|
1280
|
+
status: record.state === "ready" ? "ready" : record.state === "blocked" ? "blocked" : record.state === "awaiting-check" || record.state === "pr-pending" ? "waiting" : "working",
|
|
1281
|
+
delivery_id: record.record_id,
|
|
1282
|
+
state: record.state,
|
|
1283
|
+
ticket_id: record.payload.subject.ticket_id,
|
|
1284
|
+
branch: record.payload.review_branch,
|
|
1285
|
+
pull_request_number: record.payload.pull_request?.number ?? null,
|
|
1286
|
+
proof: record.payload.proof,
|
|
1287
|
+
reason: record.payload.blocked_reason,
|
|
1288
|
+
});
|
|
1289
|
+
}
|
|
1290
|
+
function parseIssueNumber(url, repository) {
|
|
1291
|
+
const escaped = repository.split("/").map(escapeRegExp).join("/");
|
|
1292
|
+
const match = new RegExp(`^https://github\\.com/${escaped}/issues/([1-9][0-9]*)$`, "u").exec(url.toLowerCase());
|
|
1293
|
+
if (!match)
|
|
1294
|
+
throw new Error("GitHub ticket URL does not match its authorized repository");
|
|
1295
|
+
return positiveInteger(Number(match[1]), "GitHub issue number");
|
|
1296
|
+
}
|
|
1297
|
+
function replacePendingReceipt(receipts, contact, responseSha256) {
|
|
1298
|
+
let replaced = false;
|
|
1299
|
+
const next = receipts.map((receipt) => {
|
|
1300
|
+
if (receipt.contact.contact_id !== contact.contact_id || receipt.response_sha256 !== null)
|
|
1301
|
+
return receipt;
|
|
1302
|
+
replaced = true;
|
|
1303
|
+
return { ...receipt, response_sha256: responseSha256 };
|
|
1304
|
+
});
|
|
1305
|
+
if (!replaced)
|
|
1306
|
+
throw new Error("review contact result lacks its pending intent");
|
|
1307
|
+
return next;
|
|
1308
|
+
}
|
|
1309
|
+
function sameReference(left, right) {
|
|
1310
|
+
return canonicalJson(left) === canonicalJson(right);
|
|
1311
|
+
}
|
|
1312
|
+
function samePullRequest(left, right) {
|
|
1313
|
+
return left !== null && right !== null && canonicalJson(left) === canonicalJson(right);
|
|
1314
|
+
}
|
|
1315
|
+
function selectLatestCandidate(current, candidate) {
|
|
1316
|
+
if (!current)
|
|
1317
|
+
return candidate;
|
|
1318
|
+
const timeDelta = Date.parse(candidate.effectiveAt) - Date.parse(current.effectiveAt);
|
|
1319
|
+
if (timeDelta > 0)
|
|
1320
|
+
return candidate;
|
|
1321
|
+
if (timeDelta < 0)
|
|
1322
|
+
return current;
|
|
1323
|
+
if (candidate.change === current.change && canonicalJson(candidate.snapshot) === canonicalJson(current.snapshot)) {
|
|
1324
|
+
return candidate.eventId.localeCompare(current.eventId) > 0 ? candidate : current;
|
|
1325
|
+
}
|
|
1326
|
+
return { ...current, ambiguous: true };
|
|
1327
|
+
}
|
|
1328
|
+
function eventEffectiveTimestamp(value, fallback) {
|
|
1329
|
+
return value === null || value === undefined
|
|
1330
|
+
? fallback
|
|
1331
|
+
: canonicalTimestamp(String(value), "review GitHub snapshot timestamp");
|
|
1332
|
+
}
|
|
1333
|
+
function monotonicObject(label, before, after) {
|
|
1334
|
+
if (before !== null && canonicalJson(before) !== canonicalJson(after))
|
|
1335
|
+
throw new Error(`${label} is immutable once attached`);
|
|
1336
|
+
}
|
|
1337
|
+
function safeRun(runner, request) {
|
|
1338
|
+
try {
|
|
1339
|
+
return runner(request);
|
|
1340
|
+
}
|
|
1341
|
+
catch (error) {
|
|
1342
|
+
return { status: null, signal: null, stdout: "", stderr: "", error: error instanceof Error ? error : new Error("process runner threw") };
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
function processSucceeded(result) {
|
|
1346
|
+
return result.error === undefined && result.status === 0 && result.signal === null;
|
|
1347
|
+
}
|
|
1348
|
+
function processResultDigest(result) {
|
|
1349
|
+
return sha256(canonicalJson({
|
|
1350
|
+
status: result.status,
|
|
1351
|
+
signal: result.signal,
|
|
1352
|
+
stdout_sha256: sha256(result.stdout),
|
|
1353
|
+
stderr_sha256: sha256(result.stderr),
|
|
1354
|
+
error_name: result.error?.name ?? null,
|
|
1355
|
+
}));
|
|
1356
|
+
}
|
|
1357
|
+
function publicProcessRequest(request) {
|
|
1358
|
+
return {
|
|
1359
|
+
command: request.command,
|
|
1360
|
+
args: request.args,
|
|
1361
|
+
cwd_sha256: sha256(request.cwd),
|
|
1362
|
+
input_sha256: request.input === undefined ? null : sha256(request.input),
|
|
1363
|
+
timeout_ms: request.timeoutMs,
|
|
1364
|
+
max_buffer_bytes: request.maxBufferBytes,
|
|
1365
|
+
};
|
|
1366
|
+
}
|
|
1367
|
+
function githubEnvironment(source) {
|
|
1368
|
+
if (source.GH_HOST && source.GH_HOST !== "github.com")
|
|
1369
|
+
throw new Error("review delivery supports github.com only");
|
|
1370
|
+
const env = {};
|
|
1371
|
+
for (const key of ["PATH", "HOME", "GH_TOKEN", "GITHUB_TOKEN", "GH_HOST", "NO_COLOR"]) {
|
|
1372
|
+
if (source[key] !== undefined)
|
|
1373
|
+
env[key] = source[key];
|
|
1374
|
+
}
|
|
1375
|
+
return env;
|
|
1376
|
+
}
|
|
1377
|
+
function gitEnvironment(source) {
|
|
1378
|
+
const env = {};
|
|
1379
|
+
for (const key of ["PATH", "HOME", "TMPDIR", "TMP", "TEMP", "LANG", "LC_ALL", "SYSTEMROOT", "SSH_AUTH_SOCK", "SSH_AGENT_PID"]) {
|
|
1380
|
+
if (source[key] !== undefined)
|
|
1381
|
+
env[key] = source[key];
|
|
1382
|
+
}
|
|
1383
|
+
env.GIT_TERMINAL_PROMPT = "0";
|
|
1384
|
+
env.GIT_CONFIG_NOSYSTEM = "1";
|
|
1385
|
+
env.GIT_CONFIG_GLOBAL = "/dev/null";
|
|
1386
|
+
return env;
|
|
1387
|
+
}
|
|
1388
|
+
function exactRecord(value, keys, label) {
|
|
1389
|
+
if (!isRecord(value) || Object.keys(value).sort().join("\0") !== [...keys].sort().join("\0")) {
|
|
1390
|
+
throw new Error(`${label} has an invalid shape`);
|
|
1391
|
+
}
|
|
1392
|
+
return value;
|
|
1393
|
+
}
|
|
1394
|
+
function nodeId(value, label) {
|
|
1395
|
+
if (typeof value !== "string" || !/^[A-Za-z0-9_=-]{1,256}$/u.test(value))
|
|
1396
|
+
throw new Error(`invalid ${label}`);
|
|
1397
|
+
return value;
|
|
1398
|
+
}
|
|
1399
|
+
function commit(value, label) {
|
|
1400
|
+
if (typeof value !== "string" || !SHA_PATTERN.test(value))
|
|
1401
|
+
throw new Error(`invalid ${label}`);
|
|
1402
|
+
return value;
|
|
1403
|
+
}
|
|
1404
|
+
function githubUrl(value, label) {
|
|
1405
|
+
if (typeof value !== "string" || !/^https:\/\/github\.com\/[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+\/pull\/[1-9][0-9]*$/u.test(value)) {
|
|
1406
|
+
throw new Error(`invalid ${label}`);
|
|
1407
|
+
}
|
|
1408
|
+
return value;
|
|
1409
|
+
}
|
|
1410
|
+
function positiveInteger(value, label) {
|
|
1411
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 1)
|
|
1412
|
+
throw new Error(`invalid ${label}`);
|
|
1413
|
+
return value;
|
|
1414
|
+
}
|
|
1415
|
+
function validateRequiredCheck(value) {
|
|
1416
|
+
if (!REQUIRED_CHECK_PATTERN.test(value))
|
|
1417
|
+
throw new Error("invalid required review check name");
|
|
1418
|
+
return value;
|
|
1419
|
+
}
|
|
1420
|
+
function nextTimestamp(previous, clock) {
|
|
1421
|
+
const candidate = canonicalTimestamp(clock(), "review timestamp");
|
|
1422
|
+
return new Date(Math.max(Date.parse(candidate), Date.parse(previous) + 1)).toISOString();
|
|
1423
|
+
}
|
|
1424
|
+
function timestampAfter(...timestamps) {
|
|
1425
|
+
if (timestamps.length === 0)
|
|
1426
|
+
throw new Error("review finalization requires evidence timestamps");
|
|
1427
|
+
const latest = Math.max(...timestamps.map((timestamp) => (Date.parse(canonicalTimestamp(timestamp, "review evidence timestamp")))));
|
|
1428
|
+
return new Date(latest + 1).toISOString();
|
|
1429
|
+
}
|
|
1430
|
+
function escapeRegExp(value) {
|
|
1431
|
+
return value.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
|
|
1432
|
+
}
|
|
1433
|
+
function canonicalBytes(value) {
|
|
1434
|
+
return Buffer.from(`${canonicalJson(value)}\n`, "utf8");
|
|
1435
|
+
}
|
|
1436
|
+
function canonicalJson(value) {
|
|
1437
|
+
if (value === undefined)
|
|
1438
|
+
throw new Error("review delivery data cannot be undefined");
|
|
1439
|
+
if (value === null || typeof value === "boolean" || typeof value === "number" || typeof value === "string")
|
|
1440
|
+
return JSON.stringify(value);
|
|
1441
|
+
if (Array.isArray(value))
|
|
1442
|
+
return `[${value.map(canonicalJson).join(",")}]`;
|
|
1443
|
+
if (!isRecord(value))
|
|
1444
|
+
throw new Error("review delivery data must be JSON");
|
|
1445
|
+
return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson(value[key])}`).join(",")}}`;
|
|
1446
|
+
}
|
|
1447
|
+
function sha256(value) {
|
|
1448
|
+
return createHash("sha256").update(value).digest("hex");
|
|
1449
|
+
}
|
|
1450
|
+
//# sourceMappingURL=review.js.map
|