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,834 @@
|
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
+
import { closeSync, constants, fstatSync, fsyncSync, linkSync, lstatSync, openSync, readdirSync, unlinkSync, writeFileSync, } from "node:fs";
|
|
3
|
+
import { basename, join } from "node:path";
|
|
4
|
+
import { assertActorId, assertOpaqueId } from "../domain/records.js";
|
|
5
|
+
import { acquireDirectoryLock, readLockOwner, recoverDirectoryLock, } from "../runtime/lock.js";
|
|
6
|
+
import { assertSchema } from "../runtime/schema.js";
|
|
7
|
+
import { createStableId } from "../runtime/stable-id.js";
|
|
8
|
+
import { systemBootId, systemClockSample } from "../runtime/system-clock.js";
|
|
9
|
+
import { processIsAlive } from "../runtime/process-identity.js";
|
|
10
|
+
import { errorMessage, isRecord, readJsonFileNoFollow, validateExistingDirectory } from "../utils/fs.js";
|
|
11
|
+
const MAX_OUTBOX_BYTES = 1024 * 1024;
|
|
12
|
+
const OUTBOX_STORE = Symbol("OMS outbox store");
|
|
13
|
+
const OUTBOX_STORES = new WeakSet();
|
|
14
|
+
const OUTBOX_INTERNALS = new WeakMap();
|
|
15
|
+
const CERTIFICATION_WRITER = Symbol("OMS run certification authority writer");
|
|
16
|
+
const CERTIFICATION_RESOLVER = Symbol("OMS run certification authority resolver");
|
|
17
|
+
const CERTIFICATION_REFERENCE = Symbol("OMS run certification authority reference");
|
|
18
|
+
const CERTIFICATION_WRITERS = new WeakSet();
|
|
19
|
+
const CERTIFICATION_RESOLVERS = new WeakSet();
|
|
20
|
+
const CERTIFICATION_REFERENCES = new WeakSet();
|
|
21
|
+
const CERTIFICATION_EVENT_KIND = "run-certification-authority";
|
|
22
|
+
const CERTIFICATION_CONSUMER_ID = "run-ledger-authority";
|
|
23
|
+
const SHA256_PATTERN = /^[a-f0-9]{64}$/;
|
|
24
|
+
const RUN_ID_PATTERN = /^run-[a-f0-9]{48}$/;
|
|
25
|
+
const ENTRY_ID_PATTERN = /^audit-[a-f0-9]{48}$/;
|
|
26
|
+
export class ImmutableOutboxConflictError extends Error {
|
|
27
|
+
constructor(kind, id) {
|
|
28
|
+
super(`${kind} identity conflicts with immutable content: ${id}`);
|
|
29
|
+
this.name = "ImmutableOutboxConflictError";
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
const ID_SCHEMA = {
|
|
33
|
+
type: "string",
|
|
34
|
+
pattern: "^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$",
|
|
35
|
+
};
|
|
36
|
+
const EVENT_SCHEMA = {
|
|
37
|
+
type: "object",
|
|
38
|
+
additionalProperties: false,
|
|
39
|
+
required: [
|
|
40
|
+
"schema_version",
|
|
41
|
+
"event_id",
|
|
42
|
+
"event_kind",
|
|
43
|
+
"source_id",
|
|
44
|
+
"source_version",
|
|
45
|
+
"dedupe_key",
|
|
46
|
+
"occurred_at",
|
|
47
|
+
"payload",
|
|
48
|
+
],
|
|
49
|
+
properties: {
|
|
50
|
+
schema_version: { const: 1 },
|
|
51
|
+
event_id: ID_SCHEMA,
|
|
52
|
+
event_kind: ID_SCHEMA,
|
|
53
|
+
source_id: ID_SCHEMA,
|
|
54
|
+
source_version: { type: "integer", minimum: 0 },
|
|
55
|
+
dedupe_key: ID_SCHEMA,
|
|
56
|
+
occurred_at: { type: "string", minLength: 20, maxLength: 24 },
|
|
57
|
+
payload: { type: "object" },
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
const RECEIPT_SCHEMA = {
|
|
61
|
+
type: "object",
|
|
62
|
+
additionalProperties: false,
|
|
63
|
+
required: [
|
|
64
|
+
"schema_version",
|
|
65
|
+
"receipt_id",
|
|
66
|
+
"event_id",
|
|
67
|
+
"consumer_id",
|
|
68
|
+
"operation_nonce",
|
|
69
|
+
"processed_at",
|
|
70
|
+
"result",
|
|
71
|
+
],
|
|
72
|
+
properties: {
|
|
73
|
+
schema_version: { const: 1 },
|
|
74
|
+
receipt_id: ID_SCHEMA,
|
|
75
|
+
event_id: ID_SCHEMA,
|
|
76
|
+
consumer_id: ID_SCHEMA,
|
|
77
|
+
operation_nonce: ID_SCHEMA,
|
|
78
|
+
processed_at: { type: "string", minLength: 20, maxLength: 24 },
|
|
79
|
+
result: { type: "object" },
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
export function createOutboxStore(options) {
|
|
83
|
+
const capturedOptions = {
|
|
84
|
+
eventsRoot: options.eventsRoot,
|
|
85
|
+
receiptsRoot: options.receiptsRoot,
|
|
86
|
+
locksRoot: options.locksRoot,
|
|
87
|
+
};
|
|
88
|
+
const eventsRoot = captureRoot(capturedOptions.eventsRoot, "outbox events root");
|
|
89
|
+
const receiptsRoot = captureRoot(capturedOptions.receiptsRoot, "outbox receipts root");
|
|
90
|
+
const locksRoot = captureRoot(capturedOptions.locksRoot, "outbox locks root");
|
|
91
|
+
if (new Set([eventsRoot.path, receiptsRoot.path, locksRoot.path]).size !== 3) {
|
|
92
|
+
throw new Error("outbox events, receipts, and locks roots must be different directories");
|
|
93
|
+
}
|
|
94
|
+
function readEvent(eventId) {
|
|
95
|
+
const normalizedId = assertOpaqueId(eventId, "event id");
|
|
96
|
+
eventsRoot.assertUnchanged();
|
|
97
|
+
const event = validateEvent(readJsonFileNoFollow(join(eventsRoot.path, `${normalizedId}.json`), MAX_OUTBOX_BYTES));
|
|
98
|
+
eventsRoot.assertUnchanged();
|
|
99
|
+
if (event.event_id !== normalizedId)
|
|
100
|
+
throw new Error("outbox event filename does not match its identity");
|
|
101
|
+
return event;
|
|
102
|
+
}
|
|
103
|
+
function readEventIfExists(eventId) {
|
|
104
|
+
const normalizedId = assertOpaqueId(eventId, "event id");
|
|
105
|
+
const path = join(eventsRoot.path, `${normalizedId}.json`);
|
|
106
|
+
eventsRoot.assertUnchanged();
|
|
107
|
+
if (!pathExistsNoFollow(path))
|
|
108
|
+
return undefined;
|
|
109
|
+
return readEvent(normalizedId);
|
|
110
|
+
}
|
|
111
|
+
function readReceipt(eventId, consumerId) {
|
|
112
|
+
const receiptId = deriveReceiptId(eventId, consumerId);
|
|
113
|
+
receiptsRoot.assertUnchanged();
|
|
114
|
+
const path = join(receiptsRoot.path, `${receiptId}.json`);
|
|
115
|
+
if (!pathExistsNoFollow(path)) {
|
|
116
|
+
receiptsRoot.assertUnchanged();
|
|
117
|
+
return undefined;
|
|
118
|
+
}
|
|
119
|
+
const receipt = validateReceipt(readJsonFileNoFollow(path, MAX_OUTBOX_BYTES));
|
|
120
|
+
receiptsRoot.assertUnchanged();
|
|
121
|
+
assertReceiptBinding(receipt, eventId, consumerId);
|
|
122
|
+
return receipt;
|
|
123
|
+
}
|
|
124
|
+
function listEvents() {
|
|
125
|
+
eventsRoot.assertUnchanged();
|
|
126
|
+
const entries = readdirSync(eventsRoot.path, { withFileTypes: true })
|
|
127
|
+
.sort((left, right) => left.name < right.name ? -1 : left.name > right.name ? 1 : 0);
|
|
128
|
+
eventsRoot.assertUnchanged();
|
|
129
|
+
const events = [];
|
|
130
|
+
for (const entry of entries) {
|
|
131
|
+
if (!entry.name.endsWith(".json"))
|
|
132
|
+
continue;
|
|
133
|
+
eventsRoot.assertUnchanged();
|
|
134
|
+
if (!entry.isFile())
|
|
135
|
+
throw new Error(`unsafe outbox event directory entry: ${entry.name}`);
|
|
136
|
+
const eventId = assertOpaqueId(entry.name.slice(0, -".json".length), "event filename id");
|
|
137
|
+
if (`${eventId}.json` !== entry.name)
|
|
138
|
+
throw new Error(`invalid outbox event filename: ${entry.name}`);
|
|
139
|
+
events.push(readEvent(eventId));
|
|
140
|
+
eventsRoot.assertUnchanged();
|
|
141
|
+
}
|
|
142
|
+
eventsRoot.assertUnchanged();
|
|
143
|
+
return deepFreeze(events);
|
|
144
|
+
}
|
|
145
|
+
function listPending(consumerId) {
|
|
146
|
+
const consumer = assertActorId(consumerId, "consumer id");
|
|
147
|
+
const pending = listEvents().filter((event) => readReceipt(event.event_id, consumer) === undefined);
|
|
148
|
+
eventsRoot.assertUnchanged();
|
|
149
|
+
receiptsRoot.assertUnchanged();
|
|
150
|
+
return deepFreeze(pending);
|
|
151
|
+
}
|
|
152
|
+
function publish(input, certificationCapability) {
|
|
153
|
+
const event = prepareEvent(input);
|
|
154
|
+
if ((event.event_kind === CERTIFICATION_EVENT_KIND) !== certificationCapability) {
|
|
155
|
+
throw new Error(certificationCapability
|
|
156
|
+
? "certification authority may publish only reserved certification events"
|
|
157
|
+
: "reserved certification events require a typed certification authority writer");
|
|
158
|
+
}
|
|
159
|
+
const lock = acquireDirectoryLock({
|
|
160
|
+
locksRoot: locksRoot.path,
|
|
161
|
+
lockId: `oe-${event.event_id}`,
|
|
162
|
+
ownerId: "outbox-publisher",
|
|
163
|
+
note: `publish:${event.event_id}`,
|
|
164
|
+
});
|
|
165
|
+
try {
|
|
166
|
+
eventsRoot.assertUnchanged();
|
|
167
|
+
const status = writeImmutableJson(eventsRoot, `${event.event_id}.json`, event, "event");
|
|
168
|
+
return deepFreeze({ status, event: readEvent(event.event_id) });
|
|
169
|
+
}
|
|
170
|
+
finally {
|
|
171
|
+
lock.release();
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
function commitReceipt(input, certificationCapability) {
|
|
175
|
+
const captured = {
|
|
176
|
+
eventId: input.eventId,
|
|
177
|
+
consumerId: input.consumerId,
|
|
178
|
+
processedAt: input.processedAt,
|
|
179
|
+
result: input.result,
|
|
180
|
+
};
|
|
181
|
+
const event = readEvent(captured.eventId);
|
|
182
|
+
const authorityReceipt = captured.consumerId === CERTIFICATION_CONSUMER_ID;
|
|
183
|
+
if (authorityReceipt !== certificationCapability || (certificationCapability && event.event_kind !== CERTIFICATION_EVENT_KIND)) {
|
|
184
|
+
throw new Error(certificationCapability
|
|
185
|
+
? "certification authority receipts must acknowledge reserved certification events"
|
|
186
|
+
: "reserved certification receipts require a typed certification authority writer");
|
|
187
|
+
}
|
|
188
|
+
const receipt = prepareReceipt(captured);
|
|
189
|
+
receiptsRoot.assertUnchanged();
|
|
190
|
+
const status = writeImmutableJson(receiptsRoot, `${receipt.receipt_id}.json`, receipt, "receipt");
|
|
191
|
+
const stored = readReceipt(receipt.event_id, receipt.consumer_id);
|
|
192
|
+
if (!stored)
|
|
193
|
+
throw new Error(`outbox receipt was not durably stored: ${receipt.receipt_id}`);
|
|
194
|
+
return deepFreeze({ status, receipt: stored });
|
|
195
|
+
}
|
|
196
|
+
const store = Object.freeze({
|
|
197
|
+
[OUTBOX_STORE]: true,
|
|
198
|
+
publish(input) {
|
|
199
|
+
return publish(input, false);
|
|
200
|
+
},
|
|
201
|
+
readEvent,
|
|
202
|
+
readReceipt,
|
|
203
|
+
listEvents,
|
|
204
|
+
listPending,
|
|
205
|
+
acquireProcessingLock(eventId, consumerId) {
|
|
206
|
+
const event = assertOpaqueId(eventId, "event id");
|
|
207
|
+
const consumer = assertActorId(consumerId, "consumer id");
|
|
208
|
+
locksRoot.assertUnchanged();
|
|
209
|
+
return acquireDirectoryLock({
|
|
210
|
+
locksRoot: locksRoot.path,
|
|
211
|
+
lockId: deriveProcessingLockId(event, consumer),
|
|
212
|
+
ownerId: consumer,
|
|
213
|
+
note: `process:${event}:${consumer}`,
|
|
214
|
+
});
|
|
215
|
+
},
|
|
216
|
+
recoverProcessingLock(input) {
|
|
217
|
+
const captured = {
|
|
218
|
+
eventId: input.eventId,
|
|
219
|
+
consumerId: input.consumerId,
|
|
220
|
+
recoveredBy: input.recoveredBy,
|
|
221
|
+
reason: input.reason,
|
|
222
|
+
expectedGenerationId: input.expectedGenerationId,
|
|
223
|
+
};
|
|
224
|
+
locksRoot.assertUnchanged();
|
|
225
|
+
readEvent(captured.eventId);
|
|
226
|
+
if (typeof captured.reason !== "string" || captured.reason.length < 1 || captured.reason.length > 512) {
|
|
227
|
+
throw new Error("invalid outbox lock recovery reason");
|
|
228
|
+
}
|
|
229
|
+
const lockPath = join(locksRoot.path, deriveProcessingLockId(captured.eventId, captured.consumerId));
|
|
230
|
+
const owner = readLockOwner(lockPath);
|
|
231
|
+
if (owner?.created_nonce === captured.expectedGenerationId
|
|
232
|
+
&& owner.boot_id === systemBootId()
|
|
233
|
+
&& processIsAlive(owner.process_id)) {
|
|
234
|
+
throw new Error("refusing to recover an outbox processing lock whose owner process is alive");
|
|
235
|
+
}
|
|
236
|
+
return deepFreeze(recoverDirectoryLock({
|
|
237
|
+
locksRoot: locksRoot.path,
|
|
238
|
+
lockId: deriveProcessingLockId(captured.eventId, captured.consumerId),
|
|
239
|
+
recoveredBy: assertActorId(captured.recoveredBy, "outbox lock recovery actor"),
|
|
240
|
+
reason: captured.reason,
|
|
241
|
+
expectedGenerationId: captured.expectedGenerationId,
|
|
242
|
+
}));
|
|
243
|
+
},
|
|
244
|
+
commitReceipt(input) {
|
|
245
|
+
return commitReceipt(input, false);
|
|
246
|
+
},
|
|
247
|
+
});
|
|
248
|
+
OUTBOX_STORES.add(store);
|
|
249
|
+
OUTBOX_INTERNALS.set(store, {
|
|
250
|
+
publishCertification(input) {
|
|
251
|
+
return publish(input, true);
|
|
252
|
+
},
|
|
253
|
+
commitCertification(input) {
|
|
254
|
+
return commitReceipt(input, true);
|
|
255
|
+
},
|
|
256
|
+
readEventIfExists,
|
|
257
|
+
});
|
|
258
|
+
return store;
|
|
259
|
+
}
|
|
260
|
+
export function assertOutboxStore(value) {
|
|
261
|
+
if (typeof value !== "object" || value === null || !OUTBOX_STORES.has(value)) {
|
|
262
|
+
throw new Error("outbox operation requires a real OMS OutboxStore");
|
|
263
|
+
}
|
|
264
|
+
if (!Object.isFrozen(value))
|
|
265
|
+
throw new Error("outbox store lost its frozen authority boundary");
|
|
266
|
+
}
|
|
267
|
+
export function createRunCertificationAuthority(outbox) {
|
|
268
|
+
assertOutboxStore(outbox);
|
|
269
|
+
const internal = OUTBOX_INTERNALS.get(outbox);
|
|
270
|
+
if (!internal)
|
|
271
|
+
throw new Error("outbox certification internals are unavailable");
|
|
272
|
+
const resolver = Object.freeze({
|
|
273
|
+
[CERTIFICATION_RESOLVER]: true,
|
|
274
|
+
resolve(reference) {
|
|
275
|
+
assertRunCertificationAuthorityReference(reference);
|
|
276
|
+
return resolveCertificationAuthority(outbox, reference.eventId, reference.consumerId);
|
|
277
|
+
},
|
|
278
|
+
hydrate(reference) {
|
|
279
|
+
const captured = { eventId: reference.eventId, consumerId: reference.consumerId };
|
|
280
|
+
const hydrated = makeCertificationReference(captured.eventId, captured.consumerId);
|
|
281
|
+
resolveCertificationAuthority(outbox, hydrated.eventId, hydrated.consumerId);
|
|
282
|
+
return hydrated;
|
|
283
|
+
},
|
|
284
|
+
resolveStored(reference) {
|
|
285
|
+
const captured = { eventId: reference.event_id, consumerId: reference.consumer_id };
|
|
286
|
+
return resolveCertificationAuthority(outbox, captured.eventId, captured.consumerId);
|
|
287
|
+
},
|
|
288
|
+
});
|
|
289
|
+
CERTIFICATION_RESOLVERS.add(resolver);
|
|
290
|
+
const writer = Object.freeze({
|
|
291
|
+
[CERTIFICATION_WRITER]: true,
|
|
292
|
+
authorize(input) {
|
|
293
|
+
const captured = captureAuthorizationInput(input);
|
|
294
|
+
const binding = makeAuthorityBinding(captured);
|
|
295
|
+
const decisionId = assertOpaqueId(captured.decisionId, "certification authority decision id");
|
|
296
|
+
const authorizedBy = assertActorId(captured.authorizedBy, "certification authority actor");
|
|
297
|
+
const expectedEventId = deriveEventId({
|
|
298
|
+
eventKind: CERTIFICATION_EVENT_KIND,
|
|
299
|
+
sourceId: binding.run_id,
|
|
300
|
+
sourceVersion: 1,
|
|
301
|
+
dedupeKey: decisionId,
|
|
302
|
+
});
|
|
303
|
+
const existing = internal.readEventIfExists(expectedEventId);
|
|
304
|
+
const occurredAt = existing?.occurred_at ?? systemClockSample().wallTime;
|
|
305
|
+
const payload = {
|
|
306
|
+
authorized_by: authorizedBy,
|
|
307
|
+
binding,
|
|
308
|
+
binding_sha256: hashCanonical(binding),
|
|
309
|
+
decision_id: decisionId,
|
|
310
|
+
};
|
|
311
|
+
const published = internal.publishCertification({
|
|
312
|
+
eventKind: CERTIFICATION_EVENT_KIND,
|
|
313
|
+
sourceId: binding.run_id,
|
|
314
|
+
sourceVersion: 1,
|
|
315
|
+
dedupeKey: decisionId,
|
|
316
|
+
occurredAt,
|
|
317
|
+
payload,
|
|
318
|
+
});
|
|
319
|
+
const existingReceipt = outbox.readReceipt(published.event.event_id, CERTIFICATION_CONSUMER_ID);
|
|
320
|
+
internal.commitCertification({
|
|
321
|
+
eventId: published.event.event_id,
|
|
322
|
+
consumerId: CERTIFICATION_CONSUMER_ID,
|
|
323
|
+
processedAt: existingReceipt?.processed_at ?? systemClockSample().wallTime,
|
|
324
|
+
result: {
|
|
325
|
+
authority_decision: "authorized",
|
|
326
|
+
binding_sha256: hashCanonical(binding),
|
|
327
|
+
decision_id: decisionId,
|
|
328
|
+
},
|
|
329
|
+
});
|
|
330
|
+
const reference = makeCertificationReference(published.event.event_id, CERTIFICATION_CONSUMER_ID);
|
|
331
|
+
resolver.resolve(reference);
|
|
332
|
+
return reference;
|
|
333
|
+
},
|
|
334
|
+
});
|
|
335
|
+
CERTIFICATION_WRITERS.add(writer);
|
|
336
|
+
return deepFreeze({ writer, resolver });
|
|
337
|
+
}
|
|
338
|
+
export function assertRunCertificationAuthorityWriter(value) {
|
|
339
|
+
if (typeof value !== "object" || value === null || !CERTIFICATION_WRITERS.has(value)) {
|
|
340
|
+
throw new Error("run authorization requires a real OMS RunCertificationAuthorityWriter");
|
|
341
|
+
}
|
|
342
|
+
if (!Object.isFrozen(value))
|
|
343
|
+
throw new Error("run certification authority writer lost its frozen boundary");
|
|
344
|
+
}
|
|
345
|
+
export function assertRunCertificationAuthorityResolver(value) {
|
|
346
|
+
if (typeof value !== "object" || value === null || !CERTIFICATION_RESOLVERS.has(value)) {
|
|
347
|
+
throw new Error("run authorization requires a real OMS RunCertificationAuthorityResolver");
|
|
348
|
+
}
|
|
349
|
+
if (!Object.isFrozen(value))
|
|
350
|
+
throw new Error("run certification authority resolver lost its frozen boundary");
|
|
351
|
+
}
|
|
352
|
+
export function assertRunCertificationAuthorityReference(value) {
|
|
353
|
+
if (typeof value !== "object" || value === null || !CERTIFICATION_REFERENCES.has(value)) {
|
|
354
|
+
throw new Error("run transition requires a resolved OMS certification authority reference");
|
|
355
|
+
}
|
|
356
|
+
if (!Object.isFrozen(value))
|
|
357
|
+
throw new Error("run certification authority reference lost its frozen boundary");
|
|
358
|
+
}
|
|
359
|
+
export function deriveEventId(input) {
|
|
360
|
+
const kind = assertOpaqueId(input.eventKind, "event kind");
|
|
361
|
+
const source = assertOpaqueId(input.sourceId, "event source id");
|
|
362
|
+
const dedupe = assertOpaqueId(input.dedupeKey, "event dedupe key");
|
|
363
|
+
assertNonNegativeInteger(input.sourceVersion, "event source version");
|
|
364
|
+
return createStableId("event", kind, source, String(input.sourceVersion), dedupe);
|
|
365
|
+
}
|
|
366
|
+
export function deriveReceiptId(eventId, consumerId) {
|
|
367
|
+
return createStableId("receipt", assertOpaqueId(eventId, "event id"), assertActorId(consumerId, "consumer id"));
|
|
368
|
+
}
|
|
369
|
+
export function deriveOperationNonce(eventId, consumerId) {
|
|
370
|
+
return createStableId("operation", assertOpaqueId(eventId, "event id"), assertActorId(consumerId, "consumer id"));
|
|
371
|
+
}
|
|
372
|
+
export function deriveProcessingLockId(eventId, consumerId) {
|
|
373
|
+
const processId = createStableId("process", assertOpaqueId(eventId, "event id"), assertActorId(consumerId, "consumer id"));
|
|
374
|
+
return `op-${processId}`;
|
|
375
|
+
}
|
|
376
|
+
function prepareEvent(input) {
|
|
377
|
+
const captured = {
|
|
378
|
+
eventKind: input.eventKind,
|
|
379
|
+
sourceId: input.sourceId,
|
|
380
|
+
sourceVersion: input.sourceVersion,
|
|
381
|
+
dedupeKey: input.dedupeKey,
|
|
382
|
+
occurredAt: input.occurredAt,
|
|
383
|
+
payload: input.payload,
|
|
384
|
+
};
|
|
385
|
+
const payload = canonicalizeObject(captured.payload, "event payload");
|
|
386
|
+
const event = {
|
|
387
|
+
schema_version: 1,
|
|
388
|
+
event_id: deriveEventId(captured),
|
|
389
|
+
event_kind: assertOpaqueId(captured.eventKind, "event kind"),
|
|
390
|
+
source_id: assertOpaqueId(captured.sourceId, "event source id"),
|
|
391
|
+
source_version: captured.sourceVersion,
|
|
392
|
+
dedupe_key: assertOpaqueId(captured.dedupeKey, "event dedupe key"),
|
|
393
|
+
occurred_at: assertOutboxTimestamp(captured.occurredAt, "event timestamp"),
|
|
394
|
+
payload,
|
|
395
|
+
};
|
|
396
|
+
return validateEvent(event);
|
|
397
|
+
}
|
|
398
|
+
function prepareReceipt(input) {
|
|
399
|
+
const captured = {
|
|
400
|
+
eventId: input.eventId,
|
|
401
|
+
consumerId: input.consumerId,
|
|
402
|
+
processedAt: input.processedAt,
|
|
403
|
+
result: input.result,
|
|
404
|
+
};
|
|
405
|
+
const eventId = assertOpaqueId(captured.eventId, "event id");
|
|
406
|
+
const consumerId = assertActorId(captured.consumerId, "consumer id");
|
|
407
|
+
return validateReceipt({
|
|
408
|
+
schema_version: 1,
|
|
409
|
+
receipt_id: deriveReceiptId(eventId, consumerId),
|
|
410
|
+
event_id: eventId,
|
|
411
|
+
consumer_id: consumerId,
|
|
412
|
+
operation_nonce: deriveOperationNonce(eventId, consumerId),
|
|
413
|
+
processed_at: assertOutboxTimestamp(captured.processedAt, "receipt timestamp"),
|
|
414
|
+
result: canonicalizeObject(captured.result, "receipt result"),
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
function validateEvent(value) {
|
|
418
|
+
assertSchema(value, EVENT_SCHEMA, "outbox event");
|
|
419
|
+
const event = value;
|
|
420
|
+
if (event.occurred_at !== assertOutboxTimestamp(event.occurred_at, "event timestamp")) {
|
|
421
|
+
throw new Error("event timestamp must use canonical ISO milliseconds");
|
|
422
|
+
}
|
|
423
|
+
assertJsonValue(event.payload, "event payload");
|
|
424
|
+
if (event.event_id !== deriveEventId({
|
|
425
|
+
eventKind: event.event_kind,
|
|
426
|
+
sourceId: event.source_id,
|
|
427
|
+
sourceVersion: event.source_version,
|
|
428
|
+
dedupeKey: event.dedupe_key,
|
|
429
|
+
})) {
|
|
430
|
+
throw new Error("outbox event id does not match its immutable identity");
|
|
431
|
+
}
|
|
432
|
+
return deepFreeze({ ...event, payload: canonicalizeObject(event.payload, "event payload") });
|
|
433
|
+
}
|
|
434
|
+
function validateReceipt(value) {
|
|
435
|
+
assertSchema(value, RECEIPT_SCHEMA, "outbox receipt");
|
|
436
|
+
const receipt = value;
|
|
437
|
+
if (receipt.processed_at !== assertOutboxTimestamp(receipt.processed_at, "receipt timestamp")) {
|
|
438
|
+
throw new Error("receipt timestamp must use canonical ISO milliseconds");
|
|
439
|
+
}
|
|
440
|
+
assertJsonValue(receipt.result, "receipt result");
|
|
441
|
+
assertReceiptBinding(receipt, receipt.event_id, receipt.consumer_id);
|
|
442
|
+
return deepFreeze({ ...receipt, result: canonicalizeObject(receipt.result, "receipt result") });
|
|
443
|
+
}
|
|
444
|
+
function assertReceiptBinding(receipt, eventId, consumerId) {
|
|
445
|
+
if (receipt.receipt_id !== deriveReceiptId(eventId, consumerId)) {
|
|
446
|
+
throw new Error("outbox receipt id does not match its event and consumer");
|
|
447
|
+
}
|
|
448
|
+
if (receipt.event_id !== eventId || receipt.consumer_id !== consumerId) {
|
|
449
|
+
throw new Error("outbox receipt is bound to a different event or consumer");
|
|
450
|
+
}
|
|
451
|
+
if (receipt.operation_nonce !== deriveOperationNonce(eventId, consumerId)) {
|
|
452
|
+
throw new Error("outbox receipt operation nonce does not match its event and consumer");
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
function writeImmutableJson(root, fileName, value, kind) {
|
|
456
|
+
root.assertUnchanged();
|
|
457
|
+
const bytes = Buffer.from(`${JSON.stringify(canonicalizeJson(value), null, 2)}\n`, "utf8");
|
|
458
|
+
if (bytes.byteLength > MAX_OUTBOX_BYTES)
|
|
459
|
+
throw new Error(`${kind} exceeds ${MAX_OUTBOX_BYTES} bytes`);
|
|
460
|
+
const destination = join(root.path, fileName);
|
|
461
|
+
const temporary = join(root.path, `.${basename(fileName)}.tmp.${process.pid}.${randomUUID()}`);
|
|
462
|
+
let descriptor;
|
|
463
|
+
try {
|
|
464
|
+
descriptor = openSync(temporary, "wx", 0o600);
|
|
465
|
+
writeFileSync(descriptor, bytes);
|
|
466
|
+
fsyncSync(descriptor);
|
|
467
|
+
closeSync(descriptor);
|
|
468
|
+
descriptor = undefined;
|
|
469
|
+
root.assertUnchanged();
|
|
470
|
+
linkSync(temporary, destination);
|
|
471
|
+
unlinkSync(temporary);
|
|
472
|
+
syncDirectory(root);
|
|
473
|
+
return "created";
|
|
474
|
+
}
|
|
475
|
+
catch (error) {
|
|
476
|
+
if (descriptor !== undefined)
|
|
477
|
+
closeSync(descriptor);
|
|
478
|
+
removeTemporary(temporary);
|
|
479
|
+
const code = error instanceof Error && "code" in error ? String(error.code) : undefined;
|
|
480
|
+
if (code !== "EEXIST")
|
|
481
|
+
throw error;
|
|
482
|
+
const existing = readJsonFileNoFollow(destination, MAX_OUTBOX_BYTES);
|
|
483
|
+
const existingBytes = Buffer.from(`${JSON.stringify(canonicalizeJson(existing), null, 2)}\n`, "utf8");
|
|
484
|
+
if (!existingBytes.equals(bytes)) {
|
|
485
|
+
throw new ImmutableOutboxConflictError(kind, fileName.replace(/\.json$/u, ""));
|
|
486
|
+
}
|
|
487
|
+
syncDirectory(root);
|
|
488
|
+
return "replayed";
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
function removeTemporary(path) {
|
|
492
|
+
try {
|
|
493
|
+
unlinkSync(path);
|
|
494
|
+
}
|
|
495
|
+
catch (error) {
|
|
496
|
+
const code = error instanceof Error && "code" in error ? String(error.code) : undefined;
|
|
497
|
+
if (code !== "ENOENT")
|
|
498
|
+
throw error;
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
function captureRoot(path, label) {
|
|
502
|
+
const canonical = validateExistingDirectory(path, label);
|
|
503
|
+
const entry = lstatSync(canonical);
|
|
504
|
+
const identity = { path: canonical, device: entry.dev, inode: entry.ino };
|
|
505
|
+
return {
|
|
506
|
+
...identity,
|
|
507
|
+
assertUnchanged() {
|
|
508
|
+
const currentPath = validateExistingDirectory(identity.path, label);
|
|
509
|
+
const current = lstatSync(currentPath);
|
|
510
|
+
if (currentPath !== identity.path || current.dev !== identity.device || current.ino !== identity.inode) {
|
|
511
|
+
throw new Error(`${label} changed after store creation`);
|
|
512
|
+
}
|
|
513
|
+
},
|
|
514
|
+
};
|
|
515
|
+
}
|
|
516
|
+
function syncDirectory(root) {
|
|
517
|
+
root.assertUnchanged();
|
|
518
|
+
const directoryFlag = typeof constants.O_DIRECTORY === "number" ? constants.O_DIRECTORY : 0;
|
|
519
|
+
const descriptor = openSync(root.path, constants.O_RDONLY | directoryFlag);
|
|
520
|
+
try {
|
|
521
|
+
const opened = fstatSync(descriptor);
|
|
522
|
+
if (!opened.isDirectory() || opened.dev !== root.device || opened.ino !== root.inode) {
|
|
523
|
+
throw new Error("outbox root changed while syncing");
|
|
524
|
+
}
|
|
525
|
+
fsyncSync(descriptor);
|
|
526
|
+
}
|
|
527
|
+
finally {
|
|
528
|
+
closeSync(descriptor);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
function pathExistsNoFollow(path) {
|
|
532
|
+
try {
|
|
533
|
+
lstatSync(path);
|
|
534
|
+
return true;
|
|
535
|
+
}
|
|
536
|
+
catch (error) {
|
|
537
|
+
const code = error instanceof Error && "code" in error ? String(error.code) : undefined;
|
|
538
|
+
if (code === "ENOENT")
|
|
539
|
+
return false;
|
|
540
|
+
throw new Error(`unable to inspect outbox path ${path}: ${errorMessage(error)}`);
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
function canonicalizeObject(value, label) {
|
|
544
|
+
const canonical = canonicalizeJson(value, label);
|
|
545
|
+
if (!isPlainObject(canonical))
|
|
546
|
+
throw new Error(`${label} must be an object`);
|
|
547
|
+
return canonical;
|
|
548
|
+
}
|
|
549
|
+
function canonicalizeJson(value, label = "JSON value", seen = new Set(), depth = 0) {
|
|
550
|
+
if (depth > 32)
|
|
551
|
+
throw new Error(`${label} exceeds maximum nesting depth`);
|
|
552
|
+
if (value === null || typeof value === "string" || typeof value === "boolean")
|
|
553
|
+
return value;
|
|
554
|
+
if (typeof value === "number") {
|
|
555
|
+
if (!Number.isFinite(value))
|
|
556
|
+
throw new Error(`${label} contains a non-finite number`);
|
|
557
|
+
return value;
|
|
558
|
+
}
|
|
559
|
+
if (Array.isArray(value)) {
|
|
560
|
+
if (seen.has(value))
|
|
561
|
+
throw new Error(`${label} contains a cycle`);
|
|
562
|
+
seen.add(value);
|
|
563
|
+
const result = value.map((entry) => canonicalizeJson(entry, label, seen, depth + 1));
|
|
564
|
+
seen.delete(value);
|
|
565
|
+
return result;
|
|
566
|
+
}
|
|
567
|
+
if (!isPlainObject(value))
|
|
568
|
+
throw new Error(`${label} contains a non-JSON value`);
|
|
569
|
+
if (seen.has(value))
|
|
570
|
+
throw new Error(`${label} contains a cycle`);
|
|
571
|
+
seen.add(value);
|
|
572
|
+
const result = {};
|
|
573
|
+
for (const key of Object.keys(value).sort()) {
|
|
574
|
+
const child = value[key];
|
|
575
|
+
if (child === undefined)
|
|
576
|
+
throw new Error(`${label} contains undefined`);
|
|
577
|
+
result[key] = canonicalizeJson(child, label, seen, depth + 1);
|
|
578
|
+
}
|
|
579
|
+
seen.delete(value);
|
|
580
|
+
return result;
|
|
581
|
+
}
|
|
582
|
+
function assertJsonValue(value, label) {
|
|
583
|
+
canonicalizeJson(value, label);
|
|
584
|
+
}
|
|
585
|
+
function isPlainObject(value) {
|
|
586
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
587
|
+
return false;
|
|
588
|
+
const prototype = Object.getPrototypeOf(value);
|
|
589
|
+
return prototype === Object.prototype || prototype === null;
|
|
590
|
+
}
|
|
591
|
+
function deepFreeze(value, seen = new Set()) {
|
|
592
|
+
if (typeof value !== "object" || value === null || seen.has(value))
|
|
593
|
+
return value;
|
|
594
|
+
seen.add(value);
|
|
595
|
+
for (const child of Object.values(value))
|
|
596
|
+
deepFreeze(child, seen);
|
|
597
|
+
return Object.freeze(value);
|
|
598
|
+
}
|
|
599
|
+
function captureAuthorizationInput(input) {
|
|
600
|
+
const captured = {
|
|
601
|
+
action: input.action,
|
|
602
|
+
decisionId: input.decisionId,
|
|
603
|
+
authorizedBy: input.authorizedBy,
|
|
604
|
+
runId: input.runId,
|
|
605
|
+
operationId: input.operationId,
|
|
606
|
+
runKind: input.runKind,
|
|
607
|
+
policyDigest: input.policyDigest,
|
|
608
|
+
ticketSetDigest: input.ticketSetDigest,
|
|
609
|
+
servicePlanDigest: input.servicePlanDigest,
|
|
610
|
+
expectedHead: input.expectedHead,
|
|
611
|
+
previousCanary: input.previousCanary,
|
|
612
|
+
};
|
|
613
|
+
return {
|
|
614
|
+
action: captured.action,
|
|
615
|
+
decisionId: captured.decisionId,
|
|
616
|
+
authorizedBy: captured.authorizedBy,
|
|
617
|
+
runId: captured.runId,
|
|
618
|
+
operationId: captured.operationId,
|
|
619
|
+
runKind: captured.runKind,
|
|
620
|
+
policyDigest: captured.policyDigest,
|
|
621
|
+
ticketSetDigest: captured.ticketSetDigest,
|
|
622
|
+
servicePlanDigest: captured.servicePlanDigest,
|
|
623
|
+
expectedHead: captured.expectedHead === null ? null : captureCanonicalHead(captured.expectedHead),
|
|
624
|
+
previousCanary: captured.previousCanary === null
|
|
625
|
+
? null
|
|
626
|
+
: captureCanonicalCertification(captured.previousCanary),
|
|
627
|
+
};
|
|
628
|
+
}
|
|
629
|
+
function makeAuthorityBinding(input) {
|
|
630
|
+
if (!isCertificationAction(input.action))
|
|
631
|
+
throw new Error("invalid run certification authority action");
|
|
632
|
+
const runId = assertRunId(input.runId);
|
|
633
|
+
const operationId = assertOpaqueId(input.operationId, "authorized run operation id");
|
|
634
|
+
const runKind = assertCertifyingRunKind(input.runKind);
|
|
635
|
+
const expectedHead = input.expectedHead === null ? null : captureCanonicalHead(input.expectedHead);
|
|
636
|
+
const previousCanary = input.previousCanary === null ? null : captureCanonicalCertification(input.previousCanary);
|
|
637
|
+
if ((input.action === "start") !== (expectedHead === null)) {
|
|
638
|
+
throw new Error("only run-start authority may omit the canonical expected head");
|
|
639
|
+
}
|
|
640
|
+
if (expectedHead && expectedHead.run_id !== runId) {
|
|
641
|
+
throw new Error("certification authority expected head belongs to another run");
|
|
642
|
+
}
|
|
643
|
+
if ((runKind === "12h") !== (previousCanary === null)) {
|
|
644
|
+
throw new Error("12h authority must have no prior canary and 24h authority must bind one");
|
|
645
|
+
}
|
|
646
|
+
if (previousCanary?.run_id === runId)
|
|
647
|
+
throw new Error("certification authority prior canary must be another run");
|
|
648
|
+
return deepFreeze({
|
|
649
|
+
schema_version: 1,
|
|
650
|
+
action: input.action,
|
|
651
|
+
completion_semantics: completionSemantics(input.action),
|
|
652
|
+
run_id: runId,
|
|
653
|
+
operation_id: operationId,
|
|
654
|
+
run_kind: runKind,
|
|
655
|
+
policy_sha256: assertSha256(input.policyDigest, "authorized run policy digest"),
|
|
656
|
+
ticket_set_digest: assertSha256(input.ticketSetDigest, "authorized ticket set digest"),
|
|
657
|
+
service_plan_digest: assertSha256(input.servicePlanDigest, "authorized service plan digest"),
|
|
658
|
+
expected_head: expectedHead,
|
|
659
|
+
previous_canary: previousCanary,
|
|
660
|
+
});
|
|
661
|
+
}
|
|
662
|
+
function resolveCertificationAuthority(outbox, eventIdValue, consumerIdValue) {
|
|
663
|
+
assertOutboxStore(outbox);
|
|
664
|
+
const eventId = assertOpaqueId(eventIdValue, "certification authority event id");
|
|
665
|
+
const consumerId = assertActorId(consumerIdValue, "certification authority consumer id");
|
|
666
|
+
if (consumerId !== CERTIFICATION_CONSUMER_ID)
|
|
667
|
+
throw new Error("invalid certification authority consumer");
|
|
668
|
+
const event = outbox.readEvent(eventId);
|
|
669
|
+
const receipt = outbox.readReceipt(eventId, consumerId);
|
|
670
|
+
if (!receipt)
|
|
671
|
+
throw new Error("certification authority event has no immutable decision receipt");
|
|
672
|
+
if (event.event_kind !== CERTIFICATION_EVENT_KIND)
|
|
673
|
+
throw new Error("event is not a certification authority decision");
|
|
674
|
+
assertExactObjectKeys(event.payload, ["authorized_by", "binding", "binding_sha256", "decision_id"], "certification authority event");
|
|
675
|
+
if (!isRecord(event.payload.binding))
|
|
676
|
+
throw new Error("certification authority binding must be an object");
|
|
677
|
+
const binding = parseAuthorityBinding(event.payload.binding);
|
|
678
|
+
const bindingDigest = hashCanonical(binding);
|
|
679
|
+
if (event.payload.binding_sha256 !== bindingDigest)
|
|
680
|
+
throw new Error("certification authority binding digest changed");
|
|
681
|
+
if (event.source_id !== binding.run_id)
|
|
682
|
+
throw new Error("certification authority event source changed");
|
|
683
|
+
const decisionId = assertOpaqueId(event.payload.decision_id, "certification authority decision id");
|
|
684
|
+
const authorizedBy = assertActorId(event.payload.authorized_by, "certification authority actor");
|
|
685
|
+
assertExactObjectKeys(receipt.result, ["authority_decision", "binding_sha256", "decision_id"], "certification authority receipt");
|
|
686
|
+
if (receipt.result.authority_decision !== "authorized"
|
|
687
|
+
|| receipt.result.binding_sha256 !== bindingDigest
|
|
688
|
+
|| receipt.result.decision_id !== decisionId) {
|
|
689
|
+
throw new Error("certification authority receipt does not bind the authorized transition");
|
|
690
|
+
}
|
|
691
|
+
if (Date.parse(receipt.processed_at) < Date.parse(event.occurred_at)) {
|
|
692
|
+
throw new Error("certification authority receipt predates its decision event");
|
|
693
|
+
}
|
|
694
|
+
return deepFreeze({
|
|
695
|
+
reference: { event_id: event.event_id, consumer_id: CERTIFICATION_CONSUMER_ID },
|
|
696
|
+
binding,
|
|
697
|
+
decision_id: decisionId,
|
|
698
|
+
authorized_by: authorizedBy,
|
|
699
|
+
event_sha256: hashCanonical(event),
|
|
700
|
+
receipt_sha256: hashCanonical(receipt),
|
|
701
|
+
occurred_at: event.occurred_at,
|
|
702
|
+
processed_at: receipt.processed_at,
|
|
703
|
+
});
|
|
704
|
+
}
|
|
705
|
+
function parseAuthorityBinding(value) {
|
|
706
|
+
assertExactObjectKeys(value, [
|
|
707
|
+
"action",
|
|
708
|
+
"completion_semantics",
|
|
709
|
+
"expected_head",
|
|
710
|
+
"operation_id",
|
|
711
|
+
"policy_sha256",
|
|
712
|
+
"previous_canary",
|
|
713
|
+
"run_id",
|
|
714
|
+
"run_kind",
|
|
715
|
+
"schema_version",
|
|
716
|
+
"service_plan_digest",
|
|
717
|
+
"ticket_set_digest",
|
|
718
|
+
], "certification authority binding");
|
|
719
|
+
if (value.schema_version !== 1 || !isCertificationAction(value.action)) {
|
|
720
|
+
throw new Error("invalid certification authority binding identity");
|
|
721
|
+
}
|
|
722
|
+
const binding = makeAuthorityBinding({
|
|
723
|
+
action: value.action,
|
|
724
|
+
decisionId: "parse-binding",
|
|
725
|
+
authorizedBy: "parse-binding",
|
|
726
|
+
runId: value.run_id,
|
|
727
|
+
operationId: value.operation_id,
|
|
728
|
+
runKind: value.run_kind,
|
|
729
|
+
policyDigest: value.policy_sha256,
|
|
730
|
+
ticketSetDigest: value.ticket_set_digest,
|
|
731
|
+
servicePlanDigest: value.service_plan_digest,
|
|
732
|
+
expectedHead: value.expected_head,
|
|
733
|
+
previousCanary: value.previous_canary,
|
|
734
|
+
});
|
|
735
|
+
if (value.completion_semantics !== binding.completion_semantics) {
|
|
736
|
+
throw new Error("certification authority completion semantics changed");
|
|
737
|
+
}
|
|
738
|
+
return binding;
|
|
739
|
+
}
|
|
740
|
+
function captureCanonicalHead(value) {
|
|
741
|
+
const captured = {
|
|
742
|
+
entry_id: value.entry_id,
|
|
743
|
+
run_id: value.run_id,
|
|
744
|
+
sequence: value.sequence,
|
|
745
|
+
sha256: value.sha256,
|
|
746
|
+
file_name: value.file_name,
|
|
747
|
+
};
|
|
748
|
+
if (!ENTRY_ID_PATTERN.test(captured.entry_id))
|
|
749
|
+
throw new Error("invalid canonical run head entry id");
|
|
750
|
+
assertRunId(captured.run_id);
|
|
751
|
+
if (!Number.isSafeInteger(captured.sequence) || captured.sequence < 0)
|
|
752
|
+
throw new Error("invalid canonical run head sequence");
|
|
753
|
+
assertSha256(captured.sha256, "canonical run head digest");
|
|
754
|
+
if (captured.file_name !== `${captured.entry_id}.json`)
|
|
755
|
+
throw new Error("invalid canonical run head filename");
|
|
756
|
+
return deepFreeze(captured);
|
|
757
|
+
}
|
|
758
|
+
function captureCanonicalCertification(value) {
|
|
759
|
+
const captured = {
|
|
760
|
+
run_id: value.run_id,
|
|
761
|
+
head: captureCanonicalHead(value.head),
|
|
762
|
+
metrics_sha256: value.metrics_sha256,
|
|
763
|
+
};
|
|
764
|
+
assertRunId(captured.run_id);
|
|
765
|
+
if (captured.head.run_id !== captured.run_id)
|
|
766
|
+
throw new Error("canonical canary head belongs to another run");
|
|
767
|
+
assertSha256(captured.metrics_sha256, "canonical canary metrics digest");
|
|
768
|
+
return deepFreeze(captured);
|
|
769
|
+
}
|
|
770
|
+
function makeCertificationReference(eventIdValue, consumerIdValue) {
|
|
771
|
+
const eventId = assertOpaqueId(eventIdValue, "certification authority event id");
|
|
772
|
+
const consumerId = assertActorId(consumerIdValue, "certification authority consumer id");
|
|
773
|
+
if (consumerId !== CERTIFICATION_CONSUMER_ID)
|
|
774
|
+
throw new Error("invalid certification authority consumer");
|
|
775
|
+
const reference = Object.freeze({
|
|
776
|
+
[CERTIFICATION_REFERENCE]: true,
|
|
777
|
+
eventId,
|
|
778
|
+
consumerId: CERTIFICATION_CONSUMER_ID,
|
|
779
|
+
});
|
|
780
|
+
CERTIFICATION_REFERENCES.add(reference);
|
|
781
|
+
return reference;
|
|
782
|
+
}
|
|
783
|
+
function isCertificationAction(value) {
|
|
784
|
+
return value === "start" || value === "heartbeat" || value === "process-restart" || value === "complete";
|
|
785
|
+
}
|
|
786
|
+
function completionSemantics(action) {
|
|
787
|
+
switch (action) {
|
|
788
|
+
case "start": return "begin-run";
|
|
789
|
+
case "heartbeat": return "append-heartbeat";
|
|
790
|
+
case "process-restart": return "record-process-restart";
|
|
791
|
+
case "complete": return "terminal-run-completed";
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
function assertCertifyingRunKind(value) {
|
|
795
|
+
if (value !== "12h" && value !== "24h")
|
|
796
|
+
throw new Error("invalid certifying run kind");
|
|
797
|
+
return value;
|
|
798
|
+
}
|
|
799
|
+
function assertRunId(value) {
|
|
800
|
+
if (typeof value !== "string" || !RUN_ID_PATTERN.test(value))
|
|
801
|
+
throw new Error("invalid run id");
|
|
802
|
+
return value;
|
|
803
|
+
}
|
|
804
|
+
function assertSha256(value, label) {
|
|
805
|
+
if (typeof value !== "string" || !SHA256_PATTERN.test(value))
|
|
806
|
+
throw new Error(`invalid ${label}`);
|
|
807
|
+
return value;
|
|
808
|
+
}
|
|
809
|
+
function assertExactObjectKeys(value, expected, label) {
|
|
810
|
+
const actual = Object.keys(value).sort();
|
|
811
|
+
const sortedExpected = [...expected].sort();
|
|
812
|
+
if (actual.length !== sortedExpected.length || actual.some((key, index) => key !== sortedExpected[index])) {
|
|
813
|
+
throw new Error(`${label} has invalid fields`);
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
function hashCanonical(value) {
|
|
817
|
+
const bytes = Buffer.from(`${JSON.stringify(canonicalizeJson(value), null, 2)}\n`, "utf8");
|
|
818
|
+
return createHash("sha256").update(bytes).digest("hex");
|
|
819
|
+
}
|
|
820
|
+
export function assertOutboxTimestamp(value, label) {
|
|
821
|
+
if (typeof value !== "string" || !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/.test(value)) {
|
|
822
|
+
throw new Error(`invalid ${label}`);
|
|
823
|
+
}
|
|
824
|
+
const epoch = Date.parse(value);
|
|
825
|
+
const normalized = value.includes(".") ? value : value.replace("Z", ".000Z");
|
|
826
|
+
if (!Number.isFinite(epoch) || new Date(epoch).toISOString() !== normalized)
|
|
827
|
+
throw new Error(`invalid ${label}`);
|
|
828
|
+
return new Date(epoch).toISOString();
|
|
829
|
+
}
|
|
830
|
+
function assertNonNegativeInteger(value, label) {
|
|
831
|
+
if (!Number.isSafeInteger(value) || value < 0)
|
|
832
|
+
throw new Error(`invalid ${label}`);
|
|
833
|
+
}
|
|
834
|
+
//# sourceMappingURL=store.js.map
|