@allocator-one/rcl 4.4.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +1415 -0
- package/dist/ci.d.ts +16 -0
- package/dist/ci.js +42 -0
- package/dist/ci.js.map +1 -0
- package/dist/config/data-dir.d.ts +6 -0
- package/dist/config/data-dir.js +12 -0
- package/dist/config/data-dir.js.map +1 -0
- package/dist/config/defaults.d.ts +82 -0
- package/dist/config/defaults.js +89 -0
- package/dist/config/defaults.js.map +1 -0
- package/dist/config/harness.d.ts +40 -0
- package/dist/config/harness.js +151 -0
- package/dist/config/harness.js.map +1 -0
- package/dist/config/loader.d.ts +13 -0
- package/dist/config/loader.js +131 -0
- package/dist/config/loader.js.map +1 -0
- package/dist/config/provider-concurrency.d.ts +4 -0
- package/dist/config/provider-concurrency.js +6 -0
- package/dist/config/provider-concurrency.js.map +1 -0
- package/dist/config/providers.d.ts +3 -0
- package/dist/config/providers.js +9 -0
- package/dist/config/providers.js.map +1 -0
- package/dist/config/schema.d.ts +179 -0
- package/dist/config/schema.js +115 -0
- package/dist/config/schema.js.map +1 -0
- package/dist/consensus/claim-contract.d.ts +9 -0
- package/dist/consensus/claim-contract.js +307 -0
- package/dist/consensus/claim-contract.js.map +1 -0
- package/dist/consensus/claim-identity.d.ts +5 -0
- package/dist/consensus/claim-identity.js +39 -0
- package/dist/consensus/claim-identity.js.map +1 -0
- package/dist/consensus/deduper.d.ts +43 -0
- package/dist/consensus/deduper.js +1052 -0
- package/dist/consensus/deduper.js.map +1 -0
- package/dist/consensus/finding-identity.d.ts +20 -0
- package/dist/consensus/finding-identity.js +49 -0
- package/dist/consensus/finding-identity.js.map +1 -0
- package/dist/consensus/gating.d.ts +215 -0
- package/dist/consensus/gating.js +699 -0
- package/dist/consensus/gating.js.map +1 -0
- package/dist/consensus/parser.d.ts +22 -0
- package/dist/consensus/parser.js +255 -0
- package/dist/consensus/parser.js.map +1 -0
- package/dist/consensus/semantic-deduper.d.ts +10 -0
- package/dist/consensus/semantic-deduper.js +70 -0
- package/dist/consensus/semantic-deduper.js.map +1 -0
- package/dist/consensus/types.d.ts +196 -0
- package/dist/consensus/types.js +2 -0
- package/dist/consensus/types.js.map +1 -0
- package/dist/consensus/verification-contract.d.ts +8 -0
- package/dist/consensus/verification-contract.js +95 -0
- package/dist/consensus/verification-contract.js.map +1 -0
- package/dist/consensus/voter.d.ts +29 -0
- package/dist/consensus/voter.js +394 -0
- package/dist/consensus/voter.js.map +1 -0
- package/dist/converge/attempt-budget.d.ts +88 -0
- package/dist/converge/attempt-budget.js +717 -0
- package/dist/converge/attempt-budget.js.map +1 -0
- package/dist/converge/bound-fix-recovery-source.d.ts +24 -0
- package/dist/converge/bound-fix-recovery-source.js +25 -0
- package/dist/converge/bound-fix-recovery-source.js.map +1 -0
- package/dist/converge/bound-fix-recovery.d.ts +15 -0
- package/dist/converge/bound-fix-recovery.js +65 -0
- package/dist/converge/bound-fix-recovery.js.map +1 -0
- package/dist/converge/correction-anchors.d.ts +1 -0
- package/dist/converge/correction-anchors.js +2 -0
- package/dist/converge/correction-anchors.js.map +1 -0
- package/dist/converge/cycle-remote.d.ts +6 -0
- package/dist/converge/cycle-remote.js +41 -0
- package/dist/converge/cycle-remote.js.map +1 -0
- package/dist/converge/delivery-reconciliation.d.ts +17 -0
- package/dist/converge/delivery-reconciliation.js +53 -0
- package/dist/converge/delivery-reconciliation.js.map +1 -0
- package/dist/converge/finding-identity.d.ts +6 -0
- package/dist/converge/finding-identity.js +7 -0
- package/dist/converge/finding-identity.js.map +1 -0
- package/dist/converge/fresh-review.d.ts +37 -0
- package/dist/converge/fresh-review.js +351 -0
- package/dist/converge/fresh-review.js.map +1 -0
- package/dist/converge/launch-guard.d.ts +55 -0
- package/dist/converge/launch-guard.js +363 -0
- package/dist/converge/launch-guard.js.map +1 -0
- package/dist/converge/launch-preflight.d.ts +5 -0
- package/dist/converge/launch-preflight.js +41 -0
- package/dist/converge/launch-preflight.js.map +1 -0
- package/dist/converge/launch-record.d.ts +136 -0
- package/dist/converge/launch-record.js +86 -0
- package/dist/converge/launch-record.js.map +1 -0
- package/dist/converge/legacy-launch-health.d.ts +311 -0
- package/dist/converge/legacy-launch-health.js +159 -0
- package/dist/converge/legacy-launch-health.js.map +1 -0
- package/dist/converge/legacy-roster.d.ts +29 -0
- package/dist/converge/legacy-roster.js +268 -0
- package/dist/converge/legacy-roster.js.map +1 -0
- package/dist/converge/native-lock.d.ts +18 -0
- package/dist/converge/native-lock.js +122 -0
- package/dist/converge/native-lock.js.map +1 -0
- package/dist/converge/ordinary-pending-export.d.ts +67 -0
- package/dist/converge/ordinary-pending-export.js +175 -0
- package/dist/converge/ordinary-pending-export.js.map +1 -0
- package/dist/converge/ordinary-pending-package.d.ts +28 -0
- package/dist/converge/ordinary-pending-package.js +36 -0
- package/dist/converge/ordinary-pending-package.js.map +1 -0
- package/dist/converge/pending-legacy-resume.d.ts +119 -0
- package/dist/converge/pending-legacy-resume.js +720 -0
- package/dist/converge/pending-legacy-resume.js.map +1 -0
- package/dist/converge/pending-recovery-source.d.ts +131 -0
- package/dist/converge/pending-recovery-source.js +71 -0
- package/dist/converge/pending-recovery-source.js.map +1 -0
- package/dist/converge/recovery-launch.d.ts +66 -0
- package/dist/converge/recovery-launch.js +518 -0
- package/dist/converge/recovery-launch.js.map +1 -0
- package/dist/converge/retained-report.d.ts +29 -0
- package/dist/converge/retained-report.js +218 -0
- package/dist/converge/retained-report.js.map +1 -0
- package/dist/converge/retry-source.d.ts +25 -0
- package/dist/converge/retry-source.js +13 -0
- package/dist/converge/retry-source.js.map +1 -0
- package/dist/converge/review-cycle.d.ts +49 -0
- package/dist/converge/review-cycle.js +26 -0
- package/dist/converge/review-cycle.js.map +1 -0
- package/dist/converge/round-gap-schema.d.ts +66 -0
- package/dist/converge/round-gap-schema.js +51 -0
- package/dist/converge/round-gap-schema.js.map +1 -0
- package/dist/converge/round-gap.d.ts +38 -0
- package/dist/converge/round-gap.js +334 -0
- package/dist/converge/round-gap.js.map +1 -0
- package/dist/converge/run-state.d.ts +224 -0
- package/dist/converge/run-state.js +564 -0
- package/dist/converge/run-state.js.map +1 -0
- package/dist/converge/stale-report-schema.d.ts +42 -0
- package/dist/converge/stale-report-schema.js +79 -0
- package/dist/converge/stale-report-schema.js.map +1 -0
- package/dist/converge/stale-report-storage.d.ts +47 -0
- package/dist/converge/stale-report-storage.js +172 -0
- package/dist/converge/stale-report-storage.js.map +1 -0
- package/dist/converge/stale-report.d.ts +14 -0
- package/dist/converge/stale-report.js +213 -0
- package/dist/converge/stale-report.js.map +1 -0
- package/dist/converge/target-ownership.d.ts +29 -0
- package/dist/converge/target-ownership.js +95 -0
- package/dist/converge/target-ownership.js.map +1 -0
- package/dist/converge/terminal-rejection-schema.d.ts +42 -0
- package/dist/converge/terminal-rejection-schema.js +39 -0
- package/dist/converge/terminal-rejection-schema.js.map +1 -0
- package/dist/converge/terminal-rejection.d.ts +13 -0
- package/dist/converge/terminal-rejection.js +275 -0
- package/dist/converge/terminal-rejection.js.map +1 -0
- package/dist/coordination/registry-lock.d.ts +82 -0
- package/dist/coordination/registry-lock.js +498 -0
- package/dist/coordination/registry-lock.js.map +1 -0
- package/dist/discuss.d.ts +53 -0
- package/dist/discuss.js +112 -0
- package/dist/discuss.js.map +1 -0
- package/dist/dispatch/adapter.d.ts +32 -0
- package/dist/dispatch/adapter.js +2 -0
- package/dist/dispatch/adapter.js.map +1 -0
- package/dist/dispatch/anthropic.d.ts +10 -0
- package/dist/dispatch/anthropic.js +235 -0
- package/dist/dispatch/anthropic.js.map +1 -0
- package/dist/dispatch/async-lane.d.ts +127 -0
- package/dist/dispatch/async-lane.js +414 -0
- package/dist/dispatch/async-lane.js.map +1 -0
- package/dist/dispatch/capture-council.d.ts +63 -0
- package/dist/dispatch/capture-council.js +137 -0
- package/dist/dispatch/capture-council.js.map +1 -0
- package/dist/dispatch/captured-async.d.ts +67 -0
- package/dist/dispatch/captured-async.js +40 -0
- package/dist/dispatch/captured-async.js.map +1 -0
- package/dist/dispatch/captured-inputs.d.ts +50 -0
- package/dist/dispatch/captured-inputs.js +190 -0
- package/dist/dispatch/captured-inputs.js.map +1 -0
- package/dist/dispatch/checkpoint-async-context.d.ts +27 -0
- package/dist/dispatch/checkpoint-async-context.js +51 -0
- package/dist/dispatch/checkpoint-async-context.js.map +1 -0
- package/dist/dispatch/checkpoint-async-execution.d.ts +22 -0
- package/dist/dispatch/checkpoint-async-execution.js +130 -0
- package/dist/dispatch/checkpoint-async-execution.js.map +1 -0
- package/dist/dispatch/checkpoint-async-store.d.ts +134 -0
- package/dist/dispatch/checkpoint-async-store.js +534 -0
- package/dist/dispatch/checkpoint-async-store.js.map +1 -0
- package/dist/dispatch/checkpoint-async-unknown.d.ts +19 -0
- package/dist/dispatch/checkpoint-async-unknown.js +46 -0
- package/dist/dispatch/checkpoint-async-unknown.js.map +1 -0
- package/dist/dispatch/checkpoint-async.d.ts +252 -0
- package/dist/dispatch/checkpoint-async.js +270 -0
- package/dist/dispatch/checkpoint-async.js.map +1 -0
- package/dist/dispatch/checkpoint-gating-execution.d.ts +26 -0
- package/dist/dispatch/checkpoint-gating-execution.js +84 -0
- package/dist/dispatch/checkpoint-gating-execution.js.map +1 -0
- package/dist/dispatch/checkpoint-phase-limits.d.ts +5 -0
- package/dist/dispatch/checkpoint-phase-limits.js +11 -0
- package/dist/dispatch/checkpoint-phase-limits.js.map +1 -0
- package/dist/dispatch/checkpoint-verification-context.d.ts +15 -0
- package/dist/dispatch/checkpoint-verification-context.js +48 -0
- package/dist/dispatch/checkpoint-verification-context.js.map +1 -0
- package/dist/dispatch/checkpoint-verification.d.ts +213 -0
- package/dist/dispatch/checkpoint-verification.js +381 -0
- package/dist/dispatch/checkpoint-verification.js.map +1 -0
- package/dist/dispatch/checkpoint.d.ts +329 -0
- package/dist/dispatch/checkpoint.js +1418 -0
- package/dist/dispatch/checkpoint.js.map +1 -0
- package/dist/dispatch/google.d.ts +10 -0
- package/dist/dispatch/google.js +158 -0
- package/dist/dispatch/google.js.map +1 -0
- package/dist/dispatch/late-audit.d.ts +23 -0
- package/dist/dispatch/late-audit.js +133 -0
- package/dist/dispatch/late-audit.js.map +1 -0
- package/dist/dispatch/merge.d.ts +29 -0
- package/dist/dispatch/merge.js +112 -0
- package/dist/dispatch/merge.js.map +1 -0
- package/dist/dispatch/openai-compat.d.ts +30 -0
- package/dist/dispatch/openai-compat.js +166 -0
- package/dist/dispatch/openai-compat.js.map +1 -0
- package/dist/dispatch/openai.d.ts +10 -0
- package/dist/dispatch/openai.js +159 -0
- package/dist/dispatch/openai.js.map +1 -0
- package/dist/dispatch/original-execution.d.ts +30 -0
- package/dist/dispatch/original-execution.js +91 -0
- package/dist/dispatch/original-execution.js.map +1 -0
- package/dist/dispatch/original-launch.d.ts +37 -0
- package/dist/dispatch/original-launch.js +117 -0
- package/dist/dispatch/original-launch.js.map +1 -0
- package/dist/dispatch/quorum.d.ts +15 -0
- package/dist/dispatch/quorum.js +33 -0
- package/dist/dispatch/quorum.js.map +1 -0
- package/dist/dispatch/recovery-operation.d.ts +44 -0
- package/dist/dispatch/recovery-operation.js +146 -0
- package/dist/dispatch/recovery-operation.js.map +1 -0
- package/dist/dispatch/recovery-policy.d.ts +54 -0
- package/dist/dispatch/recovery-policy.js +157 -0
- package/dist/dispatch/recovery-policy.js.map +1 -0
- package/dist/dispatch/recovery.d.ts +78 -0
- package/dist/dispatch/recovery.js +299 -0
- package/dist/dispatch/recovery.js.map +1 -0
- package/dist/dispatch/retained-async.d.ts +13 -0
- package/dist/dispatch/retained-async.js +101 -0
- package/dist/dispatch/retained-async.js.map +1 -0
- package/dist/dispatch/reviewer-identity.d.ts +10 -0
- package/dist/dispatch/reviewer-identity.js +19 -0
- package/dist/dispatch/reviewer-identity.js.map +1 -0
- package/dist/dispatch/runner.d.ts +76 -0
- package/dist/dispatch/runner.js +415 -0
- package/dist/dispatch/runner.js.map +1 -0
- package/dist/dispatch/utils.d.ts +159 -0
- package/dist/dispatch/utils.js +381 -0
- package/dist/dispatch/utils.js.map +1 -0
- package/dist/dispatch/verification-execution.d.ts +48 -0
- package/dist/dispatch/verification-execution.js +252 -0
- package/dist/dispatch/verification-execution.js.map +1 -0
- package/dist/dispatch/verification-late-audit.d.ts +17 -0
- package/dist/dispatch/verification-late-audit.js +117 -0
- package/dist/dispatch/verification-late-audit.js.map +1 -0
- package/dist/evidence/claim-recovery/authenticated-carrier-projection.d.ts +30 -0
- package/dist/evidence/claim-recovery/authenticated-carrier-projection.js +100 -0
- package/dist/evidence/claim-recovery/authenticated-carrier-projection.js.map +1 -0
- package/dist/evidence/claim-recovery/authenticated-receipts.d.ts +46 -0
- package/dist/evidence/claim-recovery/authenticated-receipts.js +120 -0
- package/dist/evidence/claim-recovery/authenticated-receipts.js.map +1 -0
- package/dist/evidence/claim-recovery/carrier-inventory.d.ts +49 -0
- package/dist/evidence/claim-recovery/carrier-inventory.js +340 -0
- package/dist/evidence/claim-recovery/carrier-inventory.js.map +1 -0
- package/dist/evidence/claim-recovery/claim-index.d.ts +12 -0
- package/dist/evidence/claim-recovery/claim-index.js +56 -0
- package/dist/evidence/claim-recovery/claim-index.js.map +1 -0
- package/dist/evidence/claim-recovery/confirmation.d.ts +53 -0
- package/dist/evidence/claim-recovery/confirmation.js +151 -0
- package/dist/evidence/claim-recovery/confirmation.js.map +1 -0
- package/dist/evidence/claim-recovery/context.d.ts +15 -0
- package/dist/evidence/claim-recovery/context.js +40 -0
- package/dist/evidence/claim-recovery/context.js.map +1 -0
- package/dist/evidence/claim-recovery/proof-storage.d.ts +6 -0
- package/dist/evidence/claim-recovery/proof-storage.js +97 -0
- package/dist/evidence/claim-recovery/proof-storage.js.map +1 -0
- package/dist/evidence/claim-recovery/validation/anchors.d.ts +20 -0
- package/dist/evidence/claim-recovery/validation/anchors.js +22 -0
- package/dist/evidence/claim-recovery/validation/anchors.js.map +1 -0
- package/dist/evidence/claim-recovery/validation/carrier-projection.d.ts +9 -0
- package/dist/evidence/claim-recovery/validation/carrier-projection.js +364 -0
- package/dist/evidence/claim-recovery/validation/carrier-projection.js.map +1 -0
- package/dist/evidence/claim-recovery/validation/carrier-types.d.ts +83 -0
- package/dist/evidence/claim-recovery/validation/carrier-types.js +2 -0
- package/dist/evidence/claim-recovery/validation/carrier-types.js.map +1 -0
- package/dist/evidence/claim-recovery/validation/claim-split.d.ts +57 -0
- package/dist/evidence/claim-recovery/validation/claim-split.js +209 -0
- package/dist/evidence/claim-recovery/validation/claim-split.js.map +1 -0
- package/dist/evidence/claim-recovery/validation/claims.d.ts +20 -0
- package/dist/evidence/claim-recovery/validation/claims.js +108 -0
- package/dist/evidence/claim-recovery/validation/claims.js.map +1 -0
- package/dist/evidence/claim-recovery/validation/current-projection.d.ts +36 -0
- package/dist/evidence/claim-recovery/validation/current-projection.js +424 -0
- package/dist/evidence/claim-recovery/validation/current-projection.js.map +1 -0
- package/dist/evidence/claim-recovery/validation/lexical.d.ts +18 -0
- package/dist/evidence/claim-recovery/validation/lexical.js +91 -0
- package/dist/evidence/claim-recovery/validation/lexical.js.map +1 -0
- package/dist/evidence/claim-recovery/validation/materials.d.ts +19 -0
- package/dist/evidence/claim-recovery/validation/materials.js +160 -0
- package/dist/evidence/claim-recovery/validation/materials.js.map +1 -0
- package/dist/evidence/claim-recovery/validation/native-material.d.ts +27 -0
- package/dist/evidence/claim-recovery/validation/native-material.js +35 -0
- package/dist/evidence/claim-recovery/validation/native-material.js.map +1 -0
- package/dist/evidence/claim-recovery/validation/native-occurrences.d.ts +79 -0
- package/dist/evidence/claim-recovery/validation/native-occurrences.js +187 -0
- package/dist/evidence/claim-recovery/validation/native-occurrences.js.map +1 -0
- package/dist/evidence/claim-recovery/validation/native-state.d.ts +45 -0
- package/dist/evidence/claim-recovery/validation/native-state.js +385 -0
- package/dist/evidence/claim-recovery/validation/native-state.js.map +1 -0
- package/dist/evidence/claim-recovery/validation/obligations.d.ts +15 -0
- package/dist/evidence/claim-recovery/validation/obligations.js +65 -0
- package/dist/evidence/claim-recovery/validation/obligations.js.map +1 -0
- package/dist/evidence/claim-recovery/validation/occurrence-source.d.ts +45 -0
- package/dist/evidence/claim-recovery/validation/occurrence-source.js +208 -0
- package/dist/evidence/claim-recovery/validation/occurrence-source.js.map +1 -0
- package/dist/evidence/claim-recovery/validation/occurrence-types.d.ts +101 -0
- package/dist/evidence/claim-recovery/validation/occurrence-types.js +2 -0
- package/dist/evidence/claim-recovery/validation/occurrence-types.js.map +1 -0
- package/dist/evidence/claim-recovery/validation/occurrence.d.ts +30 -0
- package/dist/evidence/claim-recovery/validation/occurrence.js +227 -0
- package/dist/evidence/claim-recovery/validation/occurrence.js.map +1 -0
- package/dist/evidence/claim-recovery/validation/primitives.d.ts +11 -0
- package/dist/evidence/claim-recovery/validation/primitives.js +41 -0
- package/dist/evidence/claim-recovery/validation/primitives.js.map +1 -0
- package/dist/evidence/claim-recovery/validation/receipts.d.ts +37 -0
- package/dist/evidence/claim-recovery/validation/receipts.js +83 -0
- package/dist/evidence/claim-recovery/validation/receipts.js.map +1 -0
- package/dist/evidence/claim-recovery/validation/recovery-json.d.ts +8 -0
- package/dist/evidence/claim-recovery/validation/recovery-json.js +42 -0
- package/dist/evidence/claim-recovery/validation/recovery-json.js.map +1 -0
- package/dist/evidence/claim-recovery/validation/semantic-cache.d.ts +3 -0
- package/dist/evidence/claim-recovery/validation/semantic-cache.js +22 -0
- package/dist/evidence/claim-recovery/validation/semantic-cache.js.map +1 -0
- package/dist/evidence/claim-recovery/validation/semantic-validation.d.ts +9 -0
- package/dist/evidence/claim-recovery/validation/semantic-validation.js +347 -0
- package/dist/evidence/claim-recovery/validation/semantic-validation.js.map +1 -0
- package/dist/evidence/claim-recovery/validation/sighting.d.ts +2 -0
- package/dist/evidence/claim-recovery/validation/sighting.js +15 -0
- package/dist/evidence/claim-recovery/validation/sighting.js.map +1 -0
- package/dist/evidence/claim-recovery/validation/sources.d.ts +15 -0
- package/dist/evidence/claim-recovery/validation/sources.js +2 -0
- package/dist/evidence/claim-recovery/validation/sources.js.map +1 -0
- package/dist/evidence/claim-recovery/validation/types.d.ts +126 -0
- package/dist/evidence/claim-recovery/validation/types.js +2 -0
- package/dist/evidence/claim-recovery/validation/types.js.map +1 -0
- package/dist/evidence/claim-split.d.ts +1 -0
- package/dist/evidence/claim-split.js +2 -0
- package/dist/evidence/claim-split.js.map +1 -0
- package/dist/evidence/event-receipts.d.ts +16 -0
- package/dist/evidence/event-receipts.js +41 -0
- package/dist/evidence/event-receipts.js.map +1 -0
- package/dist/evidence/finding-recovery.d.ts +23 -0
- package/dist/evidence/finding-recovery.js +74 -0
- package/dist/evidence/finding-recovery.js.map +1 -0
- package/dist/evidence/finding-retriage.d.ts +14 -0
- package/dist/evidence/finding-retriage.js +57 -0
- package/dist/evidence/finding-retriage.js.map +1 -0
- package/dist/evidence/format.d.ts +18 -0
- package/dist/evidence/format.js +152 -0
- package/dist/evidence/format.js.map +1 -0
- package/dist/evidence/original-run/decode.d.ts +30 -0
- package/dist/evidence/original-run/decode.js +208 -0
- package/dist/evidence/original-run/decode.js.map +1 -0
- package/dist/evidence/original-run/journal.d.ts +31 -0
- package/dist/evidence/original-run/journal.js +157 -0
- package/dist/evidence/original-run/journal.js.map +1 -0
- package/dist/evidence/original-run/lock-path.d.ts +17 -0
- package/dist/evidence/original-run/lock-path.js +116 -0
- package/dist/evidence/original-run/lock-path.js.map +1 -0
- package/dist/evidence/original-run/lock-scope.d.ts +21 -0
- package/dist/evidence/original-run/lock-scope.js +61 -0
- package/dist/evidence/original-run/lock-scope.js.map +1 -0
- package/dist/evidence/original-run/lock.d.ts +33 -0
- package/dist/evidence/original-run/lock.js +259 -0
- package/dist/evidence/original-run/lock.js.map +1 -0
- package/dist/evidence/original-run/remote.d.ts +23 -0
- package/dist/evidence/original-run/remote.js +110 -0
- package/dist/evidence/original-run/remote.js.map +1 -0
- package/dist/evidence/original-run/source.d.ts +61 -0
- package/dist/evidence/original-run/source.js +267 -0
- package/dist/evidence/original-run/source.js.map +1 -0
- package/dist/evidence/reads.d.ts +11 -0
- package/dist/evidence/reads.js +16 -0
- package/dist/evidence/reads.js.map +1 -0
- package/dist/evidence/recover-finding.d.ts +17 -0
- package/dist/evidence/recover-finding.js +65 -0
- package/dist/evidence/recover-finding.js.map +1 -0
- package/dist/evidence/recover-run.d.ts +26 -0
- package/dist/evidence/recover-run.js +180 -0
- package/dist/evidence/recover-run.js.map +1 -0
- package/dist/evidence/retained-time-budget.d.ts +9 -0
- package/dist/evidence/retained-time-budget.js +14 -0
- package/dist/evidence/retained-time-budget.js.map +1 -0
- package/dist/evidence/retriage-finding.d.ts +12 -0
- package/dist/evidence/retriage-finding.js +67 -0
- package/dist/evidence/retriage-finding.js.map +1 -0
- package/dist/evidence/reviewer-lineage.d.ts +35 -0
- package/dist/evidence/reviewer-lineage.js +293 -0
- package/dist/evidence/reviewer-lineage.js.map +1 -0
- package/dist/evidence/reviewer-original.d.ts +67 -0
- package/dist/evidence/reviewer-original.js +148 -0
- package/dist/evidence/reviewer-original.js.map +1 -0
- package/dist/evidence/reviewer-recovery.d.ts +74 -0
- package/dist/evidence/reviewer-recovery.js +179 -0
- package/dist/evidence/reviewer-recovery.js.map +1 -0
- package/dist/evidence/reviewer-status.d.ts +145 -0
- package/dist/evidence/reviewer-status.js +302 -0
- package/dist/evidence/reviewer-status.js.map +1 -0
- package/dist/evidence/show.d.ts +10 -0
- package/dist/evidence/show.js +41 -0
- package/dist/evidence/show.js.map +1 -0
- package/dist/evidence/status.d.ts +38 -0
- package/dist/evidence/status.js +73 -0
- package/dist/evidence/status.js.map +1 -0
- package/dist/evidence/target.d.ts +17 -0
- package/dist/evidence/target.js +74 -0
- package/dist/evidence/target.js.map +1 -0
- package/dist/evidence/types.d.ts +176 -0
- package/dist/evidence/types.js +109 -0
- package/dist/evidence/types.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2688 -0
- package/dist/index.js.map +1 -0
- package/dist/models/seed.d.ts +44 -0
- package/dist/models/seed.js +175 -0
- package/dist/models/seed.js.map +1 -0
- package/dist/models/server-stats.d.ts +80 -0
- package/dist/models/server-stats.js +109 -0
- package/dist/models/server-stats.js.map +1 -0
- package/dist/models/stats-store.d.ts +84 -0
- package/dist/models/stats-store.js +572 -0
- package/dist/models/stats-store.js.map +1 -0
- package/dist/output/artifacts.d.ts +16 -0
- package/dist/output/artifacts.js +29 -0
- package/dist/output/artifacts.js.map +1 -0
- package/dist/output/assembly-refusal.d.ts +8 -0
- package/dist/output/assembly-refusal.js +14 -0
- package/dist/output/assembly-refusal.js.map +1 -0
- package/dist/output/github.d.ts +12 -0
- package/dist/output/github.js +214 -0
- package/dist/output/github.js.map +1 -0
- package/dist/output/json.d.ts +3 -0
- package/dist/output/json.js +8 -0
- package/dist/output/json.js.map +1 -0
- package/dist/output/markdown.d.ts +3 -0
- package/dist/output/markdown.js +281 -0
- package/dist/output/markdown.js.map +1 -0
- package/dist/output/progress.d.ts +55 -0
- package/dist/output/progress.js +180 -0
- package/dist/output/progress.js.map +1 -0
- package/dist/output/sanitize.d.ts +18 -0
- package/dist/output/sanitize.js +46 -0
- package/dist/output/sanitize.js.map +1 -0
- package/dist/output/terminal.d.ts +2 -0
- package/dist/output/terminal.js +172 -0
- package/dist/output/terminal.js.map +1 -0
- package/dist/prepare/chunker.d.ts +18 -0
- package/dist/prepare/chunker.js +325 -0
- package/dist/prepare/chunker.js.map +1 -0
- package/dist/prepare/language.d.ts +1 -0
- package/dist/prepare/language.js +99 -0
- package/dist/prepare/language.js.map +1 -0
- package/dist/prepare/prompt-builder.d.ts +44 -0
- package/dist/prepare/prompt-builder.js +102 -0
- package/dist/prepare/prompt-builder.js.map +1 -0
- package/dist/prepare/unified-diff.d.ts +40 -0
- package/dist/prepare/unified-diff.js +135 -0
- package/dist/prepare/unified-diff.js.map +1 -0
- package/dist/prompts/base.d.ts +3 -0
- package/dist/prompts/base.js +53 -0
- package/dist/prompts/base.js.map +1 -0
- package/dist/prompts/hardening.d.ts +19 -0
- package/dist/prompts/hardening.js +67 -0
- package/dist/prompts/hardening.js.map +1 -0
- package/dist/prompts/languages/elixir.d.ts +1 -0
- package/dist/prompts/languages/elixir.js +13 -0
- package/dist/prompts/languages/elixir.js.map +1 -0
- package/dist/prompts/languages/generic.d.ts +1 -0
- package/dist/prompts/languages/generic.js +11 -0
- package/dist/prompts/languages/generic.js.map +1 -0
- package/dist/prompts/languages/python.d.ts +1 -0
- package/dist/prompts/languages/python.js +14 -0
- package/dist/prompts/languages/python.js.map +1 -0
- package/dist/prompts/languages/typescript.d.ts +1 -0
- package/dist/prompts/languages/typescript.js +13 -0
- package/dist/prompts/languages/typescript.js.map +1 -0
- package/dist/prompts/plan.d.ts +15 -0
- package/dist/prompts/plan.js +55 -0
- package/dist/prompts/plan.js.map +1 -0
- package/dist/report/aggregation-inputs.d.ts +52 -0
- package/dist/report/aggregation-inputs.js +114 -0
- package/dist/report/aggregation-inputs.js.map +1 -0
- package/dist/report/assembly.d.ts +43 -0
- package/dist/report/assembly.js +133 -0
- package/dist/report/assembly.js.map +1 -0
- package/dist/report/blocking-health.d.ts +64 -0
- package/dist/report/blocking-health.js +156 -0
- package/dist/report/blocking-health.js.map +1 -0
- package/dist/report/checkpoint-assembly.d.ts +10 -0
- package/dist/report/checkpoint-assembly.js +17 -0
- package/dist/report/checkpoint-assembly.js.map +1 -0
- package/dist/report/checkpoint-consensus.d.ts +59 -0
- package/dist/report/checkpoint-consensus.js +197 -0
- package/dist/report/checkpoint-consensus.js.map +1 -0
- package/dist/report/checkpoint-gating.d.ts +36 -0
- package/dist/report/checkpoint-gating.js +110 -0
- package/dist/report/checkpoint-gating.js.map +1 -0
- package/dist/report/checkpoint-projection.d.ts +76 -0
- package/dist/report/checkpoint-projection.js +122 -0
- package/dist/report/checkpoint-projection.js.map +1 -0
- package/dist/report/consensus-assembly.d.ts +37 -0
- package/dist/report/consensus-assembly.js +33 -0
- package/dist/report/consensus-assembly.js.map +1 -0
- package/dist/report/recovery-source.d.ts +7 -0
- package/dist/report/recovery-source.js +7 -0
- package/dist/report/recovery-source.js.map +1 -0
- package/dist/report/reviewer-artifact.d.ts +118 -0
- package/dist/report/reviewer-artifact.js +381 -0
- package/dist/report/reviewer-artifact.js.map +1 -0
- package/dist/report/reviewer-evidence-schema.d.ts +42 -0
- package/dist/report/reviewer-evidence-schema.js +28 -0
- package/dist/report/reviewer-evidence-schema.js.map +1 -0
- package/dist/report/reviewer-evidence.d.ts +54 -0
- package/dist/report/reviewer-evidence.js +229 -0
- package/dist/report/reviewer-evidence.js.map +1 -0
- package/dist/report/reviewer-health.d.ts +62 -0
- package/dist/report/reviewer-health.js +105 -0
- package/dist/report/reviewer-health.js.map +1 -0
- package/dist/report/run-header.d.ts +219 -0
- package/dist/report/run-header.js +307 -0
- package/dist/report/run-header.js.map +1 -0
- package/dist/report/supplemental-async.d.ts +19 -0
- package/dist/report/supplemental-async.js +85 -0
- package/dist/report/supplemental-async.js.map +1 -0
- package/dist/report/uuid.d.ts +16 -0
- package/dist/report/uuid.js +41 -0
- package/dist/report/uuid.js.map +1 -0
- package/dist/resolver/git.d.ts +16 -0
- package/dist/resolver/git.js +77 -0
- package/dist/resolver/git.js.map +1 -0
- package/dist/resolver/github-client.d.ts +82 -0
- package/dist/resolver/github-client.js +27 -0
- package/dist/resolver/github-client.js.map +1 -0
- package/dist/resolver/github.d.ts +29 -0
- package/dist/resolver/github.js +166 -0
- package/dist/resolver/github.js.map +1 -0
- package/dist/resolver/local.d.ts +3 -0
- package/dist/resolver/local.js +75 -0
- package/dist/resolver/local.js.map +1 -0
- package/dist/resolver/plan.d.ts +8 -0
- package/dist/resolver/plan.js +44 -0
- package/dist/resolver/plan.js.map +1 -0
- package/dist/resolver/target.d.ts +35 -0
- package/dist/resolver/target.js +112 -0
- package/dist/resolver/target.js.map +1 -0
- package/dist/resolver/types.d.ts +43 -0
- package/dist/resolver/types.js +2 -0
- package/dist/resolver/types.js.map +1 -0
- package/dist/roles/builtin.d.ts +4 -0
- package/dist/roles/builtin.js +282 -0
- package/dist/roles/builtin.js.map +1 -0
- package/dist/roles/dispatcher.d.ts +26 -0
- package/dist/roles/dispatcher.js +96 -0
- package/dist/roles/dispatcher.js.map +1 -0
- package/dist/roles/legacy-4.1.10.d.ts +2 -0
- package/dist/roles/legacy-4.1.10.js +320 -0
- package/dist/roles/legacy-4.1.10.js.map +1 -0
- package/dist/roles/legacy-4.4.9.d.ts +4 -0
- package/dist/roles/legacy-4.4.9.js +282 -0
- package/dist/roles/legacy-4.4.9.js.map +1 -0
- package/dist/roles/loader.d.ts +10 -0
- package/dist/roles/loader.js +104 -0
- package/dist/roles/loader.js.map +1 -0
- package/dist/roles/types.d.ts +13 -0
- package/dist/roles/types.js +2 -0
- package/dist/roles/types.js.map +1 -0
- package/dist/telemetry/abort-signal.d.ts +13 -0
- package/dist/telemetry/abort-signal.js +40 -0
- package/dist/telemetry/abort-signal.js.map +1 -0
- package/dist/telemetry/attest.d.ts +98 -0
- package/dist/telemetry/attest.js +331 -0
- package/dist/telemetry/attest.js.map +1 -0
- package/dist/telemetry/attested-retry.d.ts +79 -0
- package/dist/telemetry/attested-retry.js +195 -0
- package/dist/telemetry/attested-retry.js.map +1 -0
- package/dist/telemetry/attested-reviewer-delivery.d.ts +58 -0
- package/dist/telemetry/attested-reviewer-delivery.js +238 -0
- package/dist/telemetry/attested-reviewer-delivery.js.map +1 -0
- package/dist/telemetry/backfill.d.ts +95 -0
- package/dist/telemetry/backfill.js +460 -0
- package/dist/telemetry/backfill.js.map +1 -0
- package/dist/telemetry/credentials.d.ts +44 -0
- package/dist/telemetry/credentials.js +87 -0
- package/dist/telemetry/credentials.js.map +1 -0
- package/dist/telemetry/deliver.d.ts +152 -0
- package/dist/telemetry/deliver.js +662 -0
- package/dist/telemetry/deliver.js.map +1 -0
- package/dist/telemetry/envelope-validation.d.ts +13 -0
- package/dist/telemetry/envelope-validation.js +196 -0
- package/dist/telemetry/envelope-validation.js.map +1 -0
- package/dist/telemetry/envelope.d.ts +159 -0
- package/dist/telemetry/envelope.js +288 -0
- package/dist/telemetry/envelope.js.map +1 -0
- package/dist/telemetry/events.d.ts +68 -0
- package/dist/telemetry/events.js +79 -0
- package/dist/telemetry/events.js.map +1 -0
- package/dist/telemetry/notice.d.ts +15 -0
- package/dist/telemetry/notice.js +67 -0
- package/dist/telemetry/notice.js.map +1 -0
- package/dist/telemetry/outbox.d.ts +166 -0
- package/dist/telemetry/outbox.js +741 -0
- package/dist/telemetry/outbox.js.map +1 -0
- package/dist/telemetry/quarantine.d.ts +105 -0
- package/dist/telemetry/quarantine.js +224 -0
- package/dist/telemetry/quarantine.js.map +1 -0
- package/dist/telemetry/read-sink.d.ts +31 -0
- package/dist/telemetry/read-sink.js +25 -0
- package/dist/telemetry/read-sink.js.map +1 -0
- package/dist/telemetry/recovery/command.d.ts +16 -0
- package/dist/telemetry/recovery/command.js +70 -0
- package/dist/telemetry/recovery/command.js.map +1 -0
- package/dist/telemetry/recovery/discovery.d.ts +8 -0
- package/dist/telemetry/recovery/discovery.js +277 -0
- package/dist/telemetry/recovery/discovery.js.map +1 -0
- package/dist/telemetry/recovery/files.d.ts +20 -0
- package/dist/telemetry/recovery/files.js +123 -0
- package/dist/telemetry/recovery/files.js.map +1 -0
- package/dist/telemetry/recovery/plan.d.ts +10 -0
- package/dist/telemetry/recovery/plan.js +325 -0
- package/dist/telemetry/recovery/plan.js.map +1 -0
- package/dist/telemetry/recovery/source.d.ts +328 -0
- package/dist/telemetry/recovery/source.js +253 -0
- package/dist/telemetry/recovery/source.js.map +1 -0
- package/dist/telemetry/recovery/types.d.ts +100 -0
- package/dist/telemetry/recovery/types.js +2 -0
- package/dist/telemetry/recovery/types.js.map +1 -0
- package/dist/telemetry/recovery-request-budget.d.ts +39 -0
- package/dist/telemetry/recovery-request-budget.js +102 -0
- package/dist/telemetry/recovery-request-budget.js.map +1 -0
- package/dist/telemetry/report-consistency.d.ts +11 -0
- package/dist/telemetry/report-consistency.js +111 -0
- package/dist/telemetry/report-consistency.js.map +1 -0
- package/dist/telemetry/reviewer-delivery.d.ts +39 -0
- package/dist/telemetry/reviewer-delivery.js +279 -0
- package/dist/telemetry/reviewer-delivery.js.map +1 -0
- package/dist/telemetry/reviewer-preflight.d.ts +13 -0
- package/dist/telemetry/reviewer-preflight.js +56 -0
- package/dist/telemetry/reviewer-preflight.js.map +1 -0
- package/dist/telemetry/scrub.d.ts +53 -0
- package/dist/telemetry/scrub.js +205 -0
- package/dist/telemetry/scrub.js.map +1 -0
- package/dist/telemetry/sink.d.ts +212 -0
- package/dist/telemetry/sink.js +813 -0
- package/dist/telemetry/sink.js.map +1 -0
- package/dist/telemetry/terminal-reviewer-delivery.d.ts +18 -0
- package/dist/telemetry/terminal-reviewer-delivery.js +27 -0
- package/dist/telemetry/terminal-reviewer-delivery.js.map +1 -0
- package/dist/telemetry/transient-input.d.ts +2 -0
- package/dist/telemetry/transient-input.js +37 -0
- package/dist/telemetry/transient-input.js.map +1 -0
- package/dist/telemetry/verification.d.ts +9 -0
- package/dist/telemetry/verification.js +12 -0
- package/dist/telemetry/verification.js.map +1 -0
- package/package.json +73 -0
|
@@ -0,0 +1,1418 @@
|
|
|
1
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
2
|
+
import { constants } from 'node:fs';
|
|
3
|
+
import { link, lstat, mkdir, open, readdir, realpath, unlink } from 'node:fs/promises';
|
|
4
|
+
import { join, relative, resolve, basename } from 'node:path';
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
import { originalRawFindingSchema } from '../telemetry/recovery/source.js';
|
|
7
|
+
import { MAX_ARTIFACT_BYTES } from '../telemetry/envelope-validation.js';
|
|
8
|
+
import { MAX_VERIFICATION_PHASE_RECORDS } from './checkpoint-phase-limits.js';
|
|
9
|
+
import { syncNativeDirectory } from '../converge/native-lock.js';
|
|
10
|
+
import { verificationContextFromValidatedCheckpoint } from './checkpoint-verification-context.js';
|
|
11
|
+
import { appendVerificationRecordWithSuccessor, cloneValidatedVerificationRecords, encodeVerificationProof, parseVerificationAnswer, snapshotVerificationEvent, validateVerificationRecordsForAppend, verificationDigest, } from './checkpoint-verification.js';
|
|
12
|
+
import { assertNativeTargetOwnership, ownedNativeTargetCommonDir, withOwnedNativeOperation, } from '../converge/target-ownership.js';
|
|
13
|
+
const VERSION = 1;
|
|
14
|
+
const MAX_FILE_BYTES = 8 * 1024 * 1024;
|
|
15
|
+
export const MAX_CHECKPOINT_PROOF_BYTES = 25 * 1024 * 1024;
|
|
16
|
+
// Ordinary reads may retain more than a portable proof, but never unbounded
|
|
17
|
+
// results. Count original file bytes, including whitespace, before allocation.
|
|
18
|
+
const MAX_CHECKPOINT_READ_BYTES = 64 * 1024 * 1024;
|
|
19
|
+
const integer = z.number().int().nonnegative().safe();
|
|
20
|
+
const digestSchema = z.string().regex(/^[a-f0-9]{64}$/);
|
|
21
|
+
const bindingNameSchema = z.enum(['captured-inputs', 'source', 'operation', 'launch', 'supplemental-async']);
|
|
22
|
+
const bindingReferenceSchema = z.object({ name: bindingNameSchema, file: z.string(), sha256: z.string().regex(/^[a-f0-9]{64}$/) }).strict()
|
|
23
|
+
.refine(binding => binding.file === bindingFile(binding.name));
|
|
24
|
+
const attemptSchema = z.object({ id: z.string().regex(/^[A-Za-z0-9._:-]{1,160}$/), kind: z.enum(['paid', 'unknown']) }).strict();
|
|
25
|
+
const rawFindingSchema = originalRawFindingSchema.extend({
|
|
26
|
+
locationProvenance: z.object({
|
|
27
|
+
version: z.literal(1), source: z.literal('parser'), reason: z.literal('reversed_range'),
|
|
28
|
+
originalStartLine: integer, originalEndLine: integer,
|
|
29
|
+
}).strict().optional(),
|
|
30
|
+
}).strict().refine(finding => finding.endLine >= finding.startLine && (!finding.locationProvenance ||
|
|
31
|
+
finding.locationProvenance.originalStartLine > finding.locationProvenance.originalEndLine &&
|
|
32
|
+
finding.startLine === finding.locationProvenance.originalEndLine && finding.endLine === finding.locationProvenance.originalStartLine));
|
|
33
|
+
const reviewSchema = z.object({
|
|
34
|
+
model: z.string().min(1), role: z.string().min(1), provider: z.string().min(1),
|
|
35
|
+
findings: z.array(rawFindingSchema), durationMs: z.number().finite().nonnegative(),
|
|
36
|
+
status: z.enum(['success', 'timeout', 'error', 'parse_failed', 'canceled']),
|
|
37
|
+
usage: z.object({ inputTokens: integer.optional(), outputTokens: integer.optional(), reasoningTokens: integer.optional() }).strict().optional(),
|
|
38
|
+
error: z.string().optional(), droppedFindings: integer.optional(), warnings: z.array(z.string()).optional(),
|
|
39
|
+
async: z.literal(false).optional(),
|
|
40
|
+
adapterAttempts: integer.optional(),
|
|
41
|
+
}).strict();
|
|
42
|
+
/** Strict raw blocking-review wire schema, shared by supplemental async capture. */
|
|
43
|
+
export const blockingCheckpointReviewSchema = reviewSchema;
|
|
44
|
+
const resultSchema = z.discriminatedUnion('kind', [
|
|
45
|
+
z.object({ kind: z.literal('success'), chunk: integer, reviewBytes: z.string().min(1) }).strict(),
|
|
46
|
+
z.object({ kind: z.literal('failure'), chunk: integer, reviewBytes: z.string().min(1), possiblyBilled: z.boolean() }).strict(),
|
|
47
|
+
]);
|
|
48
|
+
const resultReferenceSchema = z.discriminatedUnion('kind', [
|
|
49
|
+
z.object({ kind: z.literal('success'), chunk: integer, reviewSha256: digestSchema, resultFile: z.string() }).strict(),
|
|
50
|
+
z.object({ kind: z.literal('failure'), chunk: integer, reviewSha256: digestSchema, resultFile: z.string(), possiblyBilled: z.boolean() }).strict(),
|
|
51
|
+
]);
|
|
52
|
+
const recordBase = { sequence: integer.min(1), previousDigest: digestSchema, digest: digestSchema };
|
|
53
|
+
const attemptRecord = { cell: z.string().min(1), paidAttempt: attemptSchema };
|
|
54
|
+
const journalRecordSchema = z.discriminatedUnion('type', [
|
|
55
|
+
z.object({ ...recordBase, type: z.literal('binding'), binding: bindingReferenceSchema }).strict(),
|
|
56
|
+
z.object({ ...recordBase, ...attemptRecord, type: z.literal('intent') }).strict(),
|
|
57
|
+
z.object({ ...recordBase, ...attemptRecord, type: z.literal('result'), result: resultReferenceSchema }).strict(),
|
|
58
|
+
z.object({ ...recordBase, ...attemptRecord, type: z.literal('uncertain'), reason: z.string().refine(value => !!value.trim() && !/[\0\r\n]/.test(value)) }).strict(),
|
|
59
|
+
z.object({ ...recordBase, type: z.literal('finalization'), finalizedDigest: digestSchema }).strict(),
|
|
60
|
+
]);
|
|
61
|
+
const verificationLateRecordSchema = z.object({ sequence: integer.positive(), previousDigest: digestSchema, digest: digestSchema,
|
|
62
|
+
version: z.literal(1), type: z.literal('late-verification-result'), verificationTerminalDigest: digestSchema,
|
|
63
|
+
result: z.object({ batchIndex: integer, attemptId: z.string().regex(/^[A-Za-z0-9._:-]{1,160}$/), finishedAtMs: integer, answerBytes: z.string().min(1) }).strict(),
|
|
64
|
+
}).strict();
|
|
65
|
+
const lateRecordSchema = z.object({ ...recordBase, ...attemptRecord,
|
|
66
|
+
version: z.literal(1), type: z.literal('late-result'), planDigest: digestSchema,
|
|
67
|
+
finalizationDigest: digestSchema, intentDigest: digestSchema, reviewSha256: digestSchema, reviewBytes: z.string().min(1),
|
|
68
|
+
}).strict();
|
|
69
|
+
// Inline JSON escaping can enlarge a valid raw 8 MiB review. Main file bounds stay unchanged.
|
|
70
|
+
const lateFileOptions = Object.freeze({ maxBytes: MAX_ARTIFACT_BYTES, singleLink: true });
|
|
71
|
+
const terminalPayloadSchema = z.object({ reportBytes: z.string().min(1), reviewerArtifactBytes: z.string().min(1) }).strict();
|
|
72
|
+
const terminalManifestSchema = z.object({ version: z.literal(1), planDigest: digestSchema, finalizationDigest: digestSchema,
|
|
73
|
+
reportSha256: digestSchema, reviewerArtifactSha256: digestSchema,
|
|
74
|
+
reportByteLength: integer.min(1).max(MAX_ARTIFACT_BYTES), reviewerArtifactByteLength: integer.min(1).max(MAX_ARTIFACT_BYTES),
|
|
75
|
+
}).strict();
|
|
76
|
+
const terminalPayloadOptions = Object.freeze({ maxBytes: MAX_ARTIFACT_BYTES, singleLink: true });
|
|
77
|
+
const terminalManifestOptions = Object.freeze({ singleLink: true });
|
|
78
|
+
const terminalFileNames = ['manifest.json', 'report.json', 'reviewer-artifact.json'];
|
|
79
|
+
const bindingsSchema = z.object({ 'captured-inputs': z.string().optional(), source: z.string().optional(),
|
|
80
|
+
operation: z.string().optional(), launch: z.string().optional(), 'supplemental-async': z.string().optional() }).strict();
|
|
81
|
+
const proofWireSchema = z.object({ version: z.literal(1), plan: z.unknown(), records: z.array(z.unknown()),
|
|
82
|
+
outcomes: z.array(z.object({ resultFile: z.string(), reviewBytes: z.string() }).strict()), bindings: bindingsSchema }).strict();
|
|
83
|
+
const frozenPlanSchema = z.object({
|
|
84
|
+
version: z.union([z.literal(1), z.literal(2)]), digest: digestSchema, target: z.string(), headSha: z.string(), mergeBaseSha: z.string(),
|
|
85
|
+
patchSha256: digestSchema, configSha256: digestSchema, specSha256: digestSchema, contextSha256: digestSchema, toolsSha256: digestSchema,
|
|
86
|
+
parser: z.object({ name: z.string(), version: integer.min(1) }).strict(),
|
|
87
|
+
roster: z.array(z.object({ seat: z.string(), model: z.string(), role: z.string(), route: z.string(), lane: z.enum(['blocking', 'secondary']).optional() }).strict()),
|
|
88
|
+
chunks: z.array(z.object({ index: integer, total: integer.min(1), digest: digestSchema }).strict()),
|
|
89
|
+
prompts: z.array(z.object({ seat: z.string(), chunk: integer, systemSha256: digestSchema, userSha256: digestSchema }).strict()),
|
|
90
|
+
cells: z.array(z.object({ id: z.string(), seat: z.string(), chunk: integer, route: z.string(), model: z.string(), role: z.string(), chunkDigest: digestSchema, systemPromptSha256: digestSchema, userPromptSha256: digestSchema }).strict()),
|
|
91
|
+
}).strict();
|
|
92
|
+
const validatedProofs = new WeakSet();
|
|
93
|
+
function sha256(value) { return createHash('sha256').update(value).digest('hex'); }
|
|
94
|
+
function canonical(value) {
|
|
95
|
+
if (value === null || typeof value !== 'object')
|
|
96
|
+
return JSON.stringify(value);
|
|
97
|
+
if (Array.isArray(value))
|
|
98
|
+
return `[${value.map(canonical).join(',')}]`;
|
|
99
|
+
return `{${Object.keys(value).sort().map(key => `${JSON.stringify(key)}:${canonical(value[key])}`).join(',')}}`;
|
|
100
|
+
}
|
|
101
|
+
function recordDigest(value) { return sha256(canonical(value)); }
|
|
102
|
+
function requireText(value, name) { if (typeof value !== 'string' || !value.trim() || /[\0\r\n]/.test(value))
|
|
103
|
+
throw new Error(`checkpoint_invalid_${name}`); return value; }
|
|
104
|
+
function requireDigest(value, name) { if (!/^[a-f0-9]{64}$/.test(value))
|
|
105
|
+
throw new Error(`checkpoint_invalid_${name}`); return value; }
|
|
106
|
+
function directoryKey(target) { return sha256(target).slice(0, 32); }
|
|
107
|
+
export function checkpointPath(commonDir, target, namespace) {
|
|
108
|
+
if (!/^[a-z0-9][a-z0-9._-]{0,63}$/.test(namespace))
|
|
109
|
+
throw new Error('checkpoint_invalid_namespace');
|
|
110
|
+
return join(resolve(commonDir), 'rcl-checkpoints', directoryKey(target), namespace);
|
|
111
|
+
}
|
|
112
|
+
function planPayload(plan, cells) {
|
|
113
|
+
return { version: plan.version ?? VERSION, target: plan.target, headSha: plan.headSha, mergeBaseSha: plan.mergeBaseSha, patchSha256: plan.patchSha256, configSha256: plan.configSha256, specSha256: plan.specSha256, contextSha256: plan.contextSha256, toolsSha256: plan.toolsSha256, parser: { ...plan.parser }, roster: plan.roster.map(item => ({ ...item })), chunks: plan.chunks.map(item => ({ ...item })), prompts: plan.prompts.map(item => ({ ...item })), cells };
|
|
114
|
+
}
|
|
115
|
+
function deepFreeze(value) { if (value && typeof value === 'object' && !Object.isFrozen(value)) {
|
|
116
|
+
Object.freeze(value);
|
|
117
|
+
for (const child of Object.values(value))
|
|
118
|
+
deepFreeze(child);
|
|
119
|
+
} return value; }
|
|
120
|
+
export function freezeCheckpointPlan(input) {
|
|
121
|
+
requireText(input.target, 'target');
|
|
122
|
+
for (const [name, value] of Object.entries({ head: input.headSha, merge_base: input.mergeBaseSha }))
|
|
123
|
+
if (!/^[a-f0-9]{40}$/.test(value))
|
|
124
|
+
throw new Error(`checkpoint_invalid_${name}`);
|
|
125
|
+
for (const [name, value] of Object.entries({ patch: input.patchSha256, config: input.configSha256, spec: input.specSha256, context: input.contextSha256, tools: input.toolsSha256 }))
|
|
126
|
+
requireDigest(value, name);
|
|
127
|
+
if (!Number.isSafeInteger(input.parser.version) || input.parser.version < 1)
|
|
128
|
+
throw new Error('checkpoint_invalid_parser');
|
|
129
|
+
requireText(input.parser.name, 'parser');
|
|
130
|
+
if (!input.roster.length)
|
|
131
|
+
throw new Error('checkpoint_missing_roster');
|
|
132
|
+
const version = input.version ?? 1;
|
|
133
|
+
if (version !== 1 && version !== 2 || input.roster.some(seat => version === 2
|
|
134
|
+
? seat.lane !== 'blocking' && seat.lane !== 'secondary' : seat.lane !== undefined))
|
|
135
|
+
throw new Error('checkpoint_invalid_lane_binding');
|
|
136
|
+
const seats = new Set();
|
|
137
|
+
for (const item of input.roster) {
|
|
138
|
+
requireText(item.seat, 'seat');
|
|
139
|
+
requireText(item.model, 'model');
|
|
140
|
+
requireText(item.role, 'role');
|
|
141
|
+
requireText(item.route, 'route');
|
|
142
|
+
if (seats.has(item.seat))
|
|
143
|
+
throw new Error('checkpoint_duplicate_cell');
|
|
144
|
+
seats.add(item.seat);
|
|
145
|
+
}
|
|
146
|
+
const chunks = [...input.chunks].sort((a, b) => a.index - b.index);
|
|
147
|
+
if (!chunks.length)
|
|
148
|
+
throw new Error('checkpoint_missing_chunks');
|
|
149
|
+
const total = chunks[0].total;
|
|
150
|
+
if (!Number.isSafeInteger(total) || total < 1 || chunks.length !== total || chunks.some((chunk, index) => chunk.index !== index || chunk.total !== total))
|
|
151
|
+
throw new Error('checkpoint_incomplete_chunks');
|
|
152
|
+
const prompts = new Map();
|
|
153
|
+
for (const prompt of input.prompts) {
|
|
154
|
+
if (!seats.has(prompt.seat) || !Number.isSafeInteger(prompt.chunk) || prompt.chunk < 0 || prompt.chunk >= total)
|
|
155
|
+
throw new Error('checkpoint_invalid_prompt');
|
|
156
|
+
const key = `${prompt.seat}:${prompt.chunk}`;
|
|
157
|
+
if (prompts.has(key))
|
|
158
|
+
throw new Error('checkpoint_duplicate_cell');
|
|
159
|
+
requireDigest(prompt.systemSha256, 'system_prompt');
|
|
160
|
+
requireDigest(prompt.userSha256, 'user_prompt');
|
|
161
|
+
prompts.set(key, { systemSha256: prompt.systemSha256, userSha256: prompt.userSha256 });
|
|
162
|
+
}
|
|
163
|
+
const cells = [];
|
|
164
|
+
for (const chunk of chunks) {
|
|
165
|
+
requireDigest(chunk.digest, 'chunk_digest');
|
|
166
|
+
for (const seat of input.roster) {
|
|
167
|
+
const id = `${seat.seat}:${chunk.index}`, prompt = prompts.get(id);
|
|
168
|
+
if (!prompt)
|
|
169
|
+
throw new Error('checkpoint_missing_prompt');
|
|
170
|
+
cells.push({ id, seat: seat.seat, chunk: chunk.index, route: seat.route, model: seat.model, role: seat.role, chunkDigest: chunk.digest, systemPromptSha256: prompt.systemSha256, userPromptSha256: prompt.userSha256 });
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
if (prompts.size !== cells.length)
|
|
174
|
+
throw new Error('checkpoint_extra_prompt');
|
|
175
|
+
const payload = planPayload(input, cells), plan = { ...payload, digest: sha256(canonical(payload)) };
|
|
176
|
+
if (!frozenPlanSchema.safeParse(plan).success)
|
|
177
|
+
throw new Error('checkpoint_invalid_plan');
|
|
178
|
+
return deepFreeze(plan);
|
|
179
|
+
}
|
|
180
|
+
/** Original seat IDs, never merged model-role opinions, define retained quorum. */
|
|
181
|
+
export function blockingCheckpointSeatIds(plan) {
|
|
182
|
+
return plan.roster.filter(seat => plan.version === 1 || seat.lane === 'blocking').map(seat => seat.seat);
|
|
183
|
+
}
|
|
184
|
+
/** Keep wire cells unchanged; bind internal eligibility to their immutable roster lane. */
|
|
185
|
+
export function checkpointRecoveryCells(plan) {
|
|
186
|
+
const lanes = new Map(plan.roster.map(seat => [seat.seat, plan.version === 1 ? 'blocking' : seat.lane]));
|
|
187
|
+
return plan.cells.map(cell => ({ ...cell, lane: lanes.get(cell.seat) }));
|
|
188
|
+
}
|
|
189
|
+
/** Old bytes remain inspectable, but cannot authorize a new retained operation. */
|
|
190
|
+
export function requireReviewerLaneBinding(plan) {
|
|
191
|
+
if (plan.version !== 2)
|
|
192
|
+
throw new Error('reviewer_lane_binding_required');
|
|
193
|
+
}
|
|
194
|
+
async function inspectDirectory(path) {
|
|
195
|
+
const stat = await lstat(path);
|
|
196
|
+
if (!stat.isDirectory() || stat.isSymbolicLink() || await realpath(path) !== path || (stat.mode & 0o7777) !== 0o700 || (process.geteuid && stat.uid !== process.geteuid()))
|
|
197
|
+
throw new Error('checkpoint_unsafe_directory');
|
|
198
|
+
}
|
|
199
|
+
async function ensurePrivateChild(parent, child) {
|
|
200
|
+
if (await realpath(parent) !== parent)
|
|
201
|
+
throw new Error('checkpoint_ancestor_alias');
|
|
202
|
+
const path = join(parent, child);
|
|
203
|
+
try {
|
|
204
|
+
await mkdir(path, { mode: 0o700 });
|
|
205
|
+
}
|
|
206
|
+
catch (error) {
|
|
207
|
+
if (error.code !== 'EEXIST')
|
|
208
|
+
throw error;
|
|
209
|
+
}
|
|
210
|
+
await inspectDirectory(path);
|
|
211
|
+
await syncNativeDirectory(parent);
|
|
212
|
+
return path;
|
|
213
|
+
}
|
|
214
|
+
async function readSafe(path, preserveBom = false, options = {}) {
|
|
215
|
+
const entry = await lstat(path);
|
|
216
|
+
if (!entry.isFile() || entry.isSymbolicLink() || entry.size > (options.maxBytes ?? MAX_FILE_BYTES) || options.singleLink && entry.nlink !== 1 || (entry.mode & 0o7777) !== 0o600 || (process.geteuid && entry.uid !== process.geteuid()))
|
|
217
|
+
throw new Error('checkpoint_symlink');
|
|
218
|
+
if (options.budget && entry.size > options.budget.remaining)
|
|
219
|
+
throw new Error(options.budget.error);
|
|
220
|
+
const handle = await open(path, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
|
|
221
|
+
try {
|
|
222
|
+
const before = await handle.stat();
|
|
223
|
+
if (!before.isFile() || before.size !== entry.size || before.ino !== entry.ino || before.dev !== entry.dev || before.mtimeMs !== entry.mtimeMs || before.ctimeMs !== entry.ctimeMs || options.singleLink && before.nlink !== 1)
|
|
224
|
+
throw new Error('checkpoint_changing_source');
|
|
225
|
+
const bytes = Buffer.alloc(before.size);
|
|
226
|
+
let offset = 0;
|
|
227
|
+
while (offset < bytes.length) {
|
|
228
|
+
const next = await handle.read(bytes, offset, bytes.length - offset, offset);
|
|
229
|
+
if (!next.bytesRead)
|
|
230
|
+
break;
|
|
231
|
+
offset += next.bytesRead;
|
|
232
|
+
}
|
|
233
|
+
const after = await handle.stat(), current = await lstat(path);
|
|
234
|
+
if (offset !== before.size || after.size !== before.size || after.mtimeMs !== before.mtimeMs || after.ctimeMs !== before.ctimeMs || current.ino !== before.ino || current.dev !== before.dev || current.mtimeMs !== before.mtimeMs || current.ctimeMs !== before.ctimeMs || current.isSymbolicLink() || options.singleLink && (after.nlink !== 1 || current.nlink !== 1))
|
|
235
|
+
throw new Error('checkpoint_changing_source');
|
|
236
|
+
const decoded = new TextDecoder('utf-8', { fatal: true, ignoreBOM: preserveBom }).decode(bytes);
|
|
237
|
+
if (options.budget)
|
|
238
|
+
options.budget.remaining -= before.size;
|
|
239
|
+
return decoded;
|
|
240
|
+
}
|
|
241
|
+
finally {
|
|
242
|
+
await handle.close();
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
/** Event records are staged outside the observed journal namespace before an exclusive link publishes them. */
|
|
246
|
+
async function publishEventExclusive(journalPath, name, bytes, maxBytes = MAX_FILE_BYTES) {
|
|
247
|
+
await publishExclusive(resolve(journalPath, '..'), join(journalPath, 'events', name), bytes, maxBytes);
|
|
248
|
+
}
|
|
249
|
+
function publicationPrefix(root, path, bytes) {
|
|
250
|
+
return `${sha256(relative(root, path))}-${sha256(bytes)}-`;
|
|
251
|
+
}
|
|
252
|
+
/** Complete bytes become visible without replacing any existing destination. */
|
|
253
|
+
async function publishExclusive(root, published, bytes, maxBytes = MAX_FILE_BYTES) {
|
|
254
|
+
boundedBytes(bytes, maxBytes);
|
|
255
|
+
const directory = resolve(published, '..');
|
|
256
|
+
await inspectDirectory(directory);
|
|
257
|
+
const stagingDirectory = await ensurePrivateChild(root, '.staging');
|
|
258
|
+
const staged = join(stagingDirectory, `${publicationPrefix(root, published, bytes)}${randomUUID()}.pending`);
|
|
259
|
+
const handle = await open(staged, constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY | constants.O_NOFOLLOW, 0o600);
|
|
260
|
+
const identity = await handle.stat();
|
|
261
|
+
let linked = false;
|
|
262
|
+
try {
|
|
263
|
+
try {
|
|
264
|
+
await handle.writeFile(bytes);
|
|
265
|
+
await handle.sync();
|
|
266
|
+
}
|
|
267
|
+
finally {
|
|
268
|
+
await handle.close();
|
|
269
|
+
}
|
|
270
|
+
await syncNativeDirectory(stagingDirectory);
|
|
271
|
+
await link(staged, published);
|
|
272
|
+
linked = true;
|
|
273
|
+
await syncNativeDirectory(directory);
|
|
274
|
+
}
|
|
275
|
+
finally {
|
|
276
|
+
// Only this invocation's exclusively created inode may be removed here.
|
|
277
|
+
const current = await lstat(staged);
|
|
278
|
+
if (current.isSymbolicLink() || current.ino !== identity.ino || current.dev !== identity.dev || current.nlink !== (linked ? 2 : 1))
|
|
279
|
+
throw new Error('checkpoint_changing_source');
|
|
280
|
+
await unlink(staged);
|
|
281
|
+
await syncNativeDirectory(stagingDirectory);
|
|
282
|
+
}
|
|
283
|
+
if (linked)
|
|
284
|
+
await syncExisting(published, bytes, true, { maxBytes, singleLink: true });
|
|
285
|
+
}
|
|
286
|
+
/** An owned caller may inspect a qualified staging pair, but must validate its full history before cleanup. */
|
|
287
|
+
async function readPublished(path, root, options, pending) {
|
|
288
|
+
if (!pending || (await lstat(path)).nlink === 1)
|
|
289
|
+
return readSafe(path, true, options);
|
|
290
|
+
const entry = await lstat(path);
|
|
291
|
+
if (entry.nlink !== 2)
|
|
292
|
+
throw new Error('checkpoint_symlink');
|
|
293
|
+
const bytes = await readSafe(path, true, { maxBytes: options.maxBytes });
|
|
294
|
+
const staging = join(root, '.staging');
|
|
295
|
+
try {
|
|
296
|
+
await inspectDirectory(staging);
|
|
297
|
+
}
|
|
298
|
+
catch {
|
|
299
|
+
throw new Error('checkpoint_symlink');
|
|
300
|
+
}
|
|
301
|
+
const prefix = publicationPrefix(root, path, bytes);
|
|
302
|
+
const names = (await readdir(staging)).filter(name => name.startsWith(prefix) && /^[a-f0-9-]{36}\.pending$/.test(name.slice(prefix.length)));
|
|
303
|
+
if (names.length !== 1)
|
|
304
|
+
throw new Error('checkpoint_symlink');
|
|
305
|
+
const staged = join(staging, names[0]), counterpart = await lstat(staged);
|
|
306
|
+
if (counterpart.nlink !== 2 || counterpart.ino !== entry.ino || counterpart.dev !== entry.dev ||
|
|
307
|
+
await readSafe(staged, true, { maxBytes: options.maxBytes }) !== bytes)
|
|
308
|
+
throw new Error('checkpoint_symlink');
|
|
309
|
+
pending.push({ path, staged, bytes, maxBytes: options.maxBytes ?? MAX_FILE_BYTES, ino: entry.ino, dev: entry.dev });
|
|
310
|
+
return bytes;
|
|
311
|
+
}
|
|
312
|
+
/** Called only inside owned writes; read-only readers never repair filesystem state. */
|
|
313
|
+
async function recoverPublications(read) {
|
|
314
|
+
const pending = [], value = await read(pending);
|
|
315
|
+
// The caller's full semantic validation has succeeded before any link is removed.
|
|
316
|
+
for (const item of pending) {
|
|
317
|
+
const current = await lstat(item.path), staged = await lstat(item.staged);
|
|
318
|
+
if (current.nlink !== 2 || staged.nlink !== 2 || current.ino !== item.ino || staged.ino !== item.ino ||
|
|
319
|
+
current.dev !== item.dev || staged.dev !== item.dev || current.isSymbolicLink() || staged.isSymbolicLink())
|
|
320
|
+
throw new Error('checkpoint_changing_source');
|
|
321
|
+
await syncExisting(item.path, item.bytes, true, { maxBytes: item.maxBytes });
|
|
322
|
+
if (await readSafe(item.staged, true, { maxBytes: item.maxBytes }) !== item.bytes)
|
|
323
|
+
throw new Error('checkpoint_changing_source');
|
|
324
|
+
await unlink(item.staged);
|
|
325
|
+
await syncNativeDirectory(resolve(item.staged, '..'));
|
|
326
|
+
await syncExisting(item.path, item.bytes, true, { maxBytes: item.maxBytes, singleLink: true });
|
|
327
|
+
}
|
|
328
|
+
// Re-read strictly after cleanup; no validated disk state is cached across calls.
|
|
329
|
+
return pending.length ? read() : value;
|
|
330
|
+
}
|
|
331
|
+
function boundedBytes(bytes, maxBytes = MAX_FILE_BYTES) {
|
|
332
|
+
if (Buffer.byteLength(bytes, 'utf8') > maxBytes)
|
|
333
|
+
throw new Error('checkpoint_file_too_large');
|
|
334
|
+
}
|
|
335
|
+
/** An identical visible file is not proof that a previous fsync succeeded. */
|
|
336
|
+
async function syncExisting(path, expected, preserveBom = false, options = {}) {
|
|
337
|
+
await inspectDirectory(resolve(path, '..'));
|
|
338
|
+
const entry = await lstat(path);
|
|
339
|
+
const bytes = await readSafe(path, preserveBom, options);
|
|
340
|
+
if (expected !== undefined && bytes !== expected)
|
|
341
|
+
throw new Error('checkpoint_changing_source');
|
|
342
|
+
const handle = await open(path, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
|
|
343
|
+
try {
|
|
344
|
+
const opened = await handle.stat();
|
|
345
|
+
if (!opened.isFile() || opened.ino !== entry.ino || opened.dev !== entry.dev || opened.size !== entry.size ||
|
|
346
|
+
opened.mtimeMs !== entry.mtimeMs || opened.ctimeMs !== entry.ctimeMs || options.singleLink && opened.nlink !== 1)
|
|
347
|
+
throw new Error('checkpoint_changing_source');
|
|
348
|
+
await handle.sync();
|
|
349
|
+
const current = await lstat(path);
|
|
350
|
+
if (current.ino !== opened.ino || current.dev !== opened.dev || current.mtimeMs !== opened.mtimeMs ||
|
|
351
|
+
current.ctimeMs !== opened.ctimeMs || current.isSymbolicLink() || options.singleLink && current.nlink !== 1)
|
|
352
|
+
throw new Error('checkpoint_changing_source');
|
|
353
|
+
}
|
|
354
|
+
finally {
|
|
355
|
+
await handle.close();
|
|
356
|
+
}
|
|
357
|
+
await syncNativeDirectory(resolve(path, '..'));
|
|
358
|
+
}
|
|
359
|
+
function decodePlan(text) {
|
|
360
|
+
boundedBytes(text);
|
|
361
|
+
let plan;
|
|
362
|
+
try {
|
|
363
|
+
plan = frozenPlanSchema.parse(JSON.parse(text));
|
|
364
|
+
}
|
|
365
|
+
catch {
|
|
366
|
+
throw new Error('checkpoint_invalid_plan');
|
|
367
|
+
}
|
|
368
|
+
const recalculated = freezeCheckpointPlan(plan);
|
|
369
|
+
if (plan.digest !== recalculated.digest || canonical(plan) !== canonical(recalculated))
|
|
370
|
+
throw new Error('checkpoint_invalid_plan');
|
|
371
|
+
return recalculated;
|
|
372
|
+
}
|
|
373
|
+
function matchingPlan(actual, expected) { return actual.digest === expected.digest && canonical(actual) === canonical(expected); }
|
|
374
|
+
function eventFile(sequence) { return `${String(sequence).padStart(8, '0')}.json`; }
|
|
375
|
+
function bindingFile(name) { return `binding-${name}.data`; }
|
|
376
|
+
function validAttempt(value) { return attemptSchema.safeParse(value).success; }
|
|
377
|
+
function snapshotAttempt(value) {
|
|
378
|
+
const parsed = attemptSchema.safeParse(value);
|
|
379
|
+
if (!parsed.success)
|
|
380
|
+
throw new Error('checkpoint_invalid_attempt');
|
|
381
|
+
return parsed.data;
|
|
382
|
+
}
|
|
383
|
+
function snapshotResult(value) {
|
|
384
|
+
const parsed = resultSchema.safeParse(value);
|
|
385
|
+
if (!parsed.success)
|
|
386
|
+
throw new Error('checkpoint_invalid_result');
|
|
387
|
+
boundedBytes(parsed.data.reviewBytes);
|
|
388
|
+
return parsed.data;
|
|
389
|
+
}
|
|
390
|
+
function resultFileFor(cell, attempt) { return `${sha256(cell).slice(0, 16)}-${sha256(attempt.id).slice(0, 16)}.json`; }
|
|
391
|
+
function validResultReference(value, cell, attempt) { return Number.isSafeInteger(value.chunk) && value.chunk >= 0 && (value.kind === 'success' || value.kind === 'failure') && /^[a-f0-9]{64}$/.test(value.reviewSha256) && value.resultFile === resultFileFor(cell, attempt) && basename(value.resultFile) === value.resultFile && (value.kind !== 'failure' || typeof value.possiblyBilled === 'boolean'); }
|
|
392
|
+
function validateReviewBytes(bytes, cell) {
|
|
393
|
+
if (typeof bytes !== 'string' || !bytes)
|
|
394
|
+
throw new Error('checkpoint_invalid_result');
|
|
395
|
+
boundedBytes(bytes);
|
|
396
|
+
let review;
|
|
397
|
+
try {
|
|
398
|
+
review = JSON.parse(bytes);
|
|
399
|
+
}
|
|
400
|
+
catch {
|
|
401
|
+
throw new Error('checkpoint_invalid_result');
|
|
402
|
+
}
|
|
403
|
+
const parsed = reviewSchema.safeParse(review);
|
|
404
|
+
// The retained executor disables internal adapter retries: each durable
|
|
405
|
+
// physical intent can bind at most one observed SDK invocation. Preserve
|
|
406
|
+
// that counter in exact raw bytes; do not relabel it as billing or a claim.
|
|
407
|
+
if (!parsed.success || (parsed.data.adapterAttempts ?? 0) > 1)
|
|
408
|
+
throw new Error('checkpoint_invalid_result');
|
|
409
|
+
const item = parsed.data;
|
|
410
|
+
if (item.model !== cell.model || item.role !== cell.role || item.provider !== cell.route)
|
|
411
|
+
throw new Error('checkpoint_result_cell_mismatch');
|
|
412
|
+
return item;
|
|
413
|
+
}
|
|
414
|
+
function validateResult(result, cell) {
|
|
415
|
+
if (!Number.isSafeInteger(result.chunk) || result.chunk !== cell.chunk || typeof result.reviewBytes !== 'string' || !result.reviewBytes)
|
|
416
|
+
throw new Error('checkpoint_result_cell_mismatch');
|
|
417
|
+
const item = validateReviewBytes(result.reviewBytes, cell);
|
|
418
|
+
if ((result.kind === 'success') !== (item.status === 'success'))
|
|
419
|
+
throw new Error('checkpoint_result_cell_mismatch');
|
|
420
|
+
}
|
|
421
|
+
function boundedOpaqueBytes(bytes) {
|
|
422
|
+
boundedBytes(bytes);
|
|
423
|
+
if (Buffer.from(bytes, 'utf8').toString('utf8') !== bytes)
|
|
424
|
+
throw new Error('checkpoint_invalid_bytes');
|
|
425
|
+
}
|
|
426
|
+
function validateRecordChain(input, plan) {
|
|
427
|
+
let previous = plan.digest;
|
|
428
|
+
return input.map((value, index) => {
|
|
429
|
+
const parsed = journalRecordSchema.safeParse(value);
|
|
430
|
+
if (!parsed.success)
|
|
431
|
+
throw new Error('checkpoint_invalid_record');
|
|
432
|
+
const record = parsed.data, { digest, ...unsigned } = record;
|
|
433
|
+
boundedBytes(canonical(record));
|
|
434
|
+
if (record.sequence !== index + 1 || record.previousDigest !== previous || digest !== recordDigest(unsigned))
|
|
435
|
+
throw new Error('checkpoint_invalid_record');
|
|
436
|
+
if (record.type === 'result' && !validResultReference(record.result, record.cell, record.paidAttempt))
|
|
437
|
+
throw new Error('checkpoint_invalid_record');
|
|
438
|
+
previous = digest;
|
|
439
|
+
return record;
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
/** One semantic validator for both private files and portable proof bytes. */
|
|
443
|
+
function validateHistory(plan, records, resultBytes, suppliedBindings) {
|
|
444
|
+
const cells = new Set(plan.cells.map(cell => cell.id));
|
|
445
|
+
const intents = new Map(), terminalAttempts = new Map(), outcomes = [], successes = [];
|
|
446
|
+
const successfulCells = new Set(), attemptIds = new Set(), bindings = {};
|
|
447
|
+
let finalized = false;
|
|
448
|
+
for (let index = 0; index < records.length; index++) {
|
|
449
|
+
const record = records[index];
|
|
450
|
+
if (record.type === 'binding') {
|
|
451
|
+
const parsed = bindingReferenceSchema.safeParse(record.binding);
|
|
452
|
+
if (!parsed.success || Object.keys(record).some(key => !['sequence', 'previousDigest', 'digest', 'type', 'binding'].includes(key)))
|
|
453
|
+
throw new Error('checkpoint_invalid_binding');
|
|
454
|
+
if (finalized || intents.size)
|
|
455
|
+
throw new Error('checkpoint_binding_closed');
|
|
456
|
+
const binding = parsed.data;
|
|
457
|
+
if (Object.hasOwn(bindings, binding.name))
|
|
458
|
+
throw new Error('checkpoint_duplicate_binding');
|
|
459
|
+
const bytes = suppliedBindings[binding.name];
|
|
460
|
+
if (typeof bytes !== 'string')
|
|
461
|
+
throw new Error('checkpoint_missing_binding');
|
|
462
|
+
boundedOpaqueBytes(bytes);
|
|
463
|
+
if (sha256(bytes) !== binding.sha256)
|
|
464
|
+
throw new Error('checkpoint_binding_tampered');
|
|
465
|
+
bindings[binding.name] = bytes;
|
|
466
|
+
continue;
|
|
467
|
+
}
|
|
468
|
+
if (Object.hasOwn(record, 'binding'))
|
|
469
|
+
throw new Error('checkpoint_invalid_record');
|
|
470
|
+
if (record.type === 'finalization') {
|
|
471
|
+
if (finalized || !record.finalizedDigest || index !== records.length - 1 || record.finalizedDigest !== sha256(canonical(records.slice(0, -1))))
|
|
472
|
+
throw new Error('checkpoint_invalid_finalization');
|
|
473
|
+
finalized = true;
|
|
474
|
+
continue;
|
|
475
|
+
}
|
|
476
|
+
if (finalized || !record.cell || !record.paidAttempt || !cells.has(record.cell) || !validAttempt(record.paidAttempt))
|
|
477
|
+
throw new Error('checkpoint_invalid_record');
|
|
478
|
+
const key = `${record.cell}\0${record.paidAttempt.id}`;
|
|
479
|
+
if (record.type === 'intent') {
|
|
480
|
+
if (successfulCells.has(record.cell))
|
|
481
|
+
throw new Error('checkpoint_success_immutable');
|
|
482
|
+
if (intents.has(key) || attemptIds.has(record.paidAttempt.id))
|
|
483
|
+
throw new Error('checkpoint_duplicate_attempt');
|
|
484
|
+
intents.set(key, record.paidAttempt);
|
|
485
|
+
attemptIds.add(record.paidAttempt.id);
|
|
486
|
+
continue;
|
|
487
|
+
}
|
|
488
|
+
if (!intents.has(key) || intents.get(key).kind !== record.paidAttempt.kind)
|
|
489
|
+
throw new Error('checkpoint_missing_intent');
|
|
490
|
+
if (record.type === 'uncertain') {
|
|
491
|
+
if (terminalAttempts.has(key))
|
|
492
|
+
throw new Error('checkpoint_terminal_result_exists');
|
|
493
|
+
if (successfulCells.has(record.cell))
|
|
494
|
+
throw new Error('checkpoint_success_immutable');
|
|
495
|
+
continue;
|
|
496
|
+
}
|
|
497
|
+
if (record.type === 'result') {
|
|
498
|
+
if (!record.result || terminalAttempts.has(key) || successfulCells.has(record.cell) || !validResultReference(record.result, record.cell, record.paidAttempt))
|
|
499
|
+
throw new Error('checkpoint_invalid_record');
|
|
500
|
+
terminalAttempts.set(key, record);
|
|
501
|
+
const bytes = resultBytes.get(record.result.resultFile);
|
|
502
|
+
if (typeof bytes !== 'string')
|
|
503
|
+
throw new Error('checkpoint_missing_result');
|
|
504
|
+
boundedOpaqueBytes(bytes);
|
|
505
|
+
if (sha256(bytes) !== record.result.reviewSha256)
|
|
506
|
+
throw new Error('checkpoint_result_tampered');
|
|
507
|
+
const cell = plan.cells.find(candidate => candidate.id === record.cell);
|
|
508
|
+
const result = record.result.kind === 'success' ? { kind: 'success', chunk: record.result.chunk, reviewBytes: bytes } : { kind: 'failure', chunk: record.result.chunk, reviewBytes: bytes, possiblyBilled: record.result.possiblyBilled === true };
|
|
509
|
+
validateResult(result, cell);
|
|
510
|
+
outcomes.push({ cell: record.cell, paidAttempt: record.paidAttempt, result });
|
|
511
|
+
if (record.result.kind === 'success') {
|
|
512
|
+
successfulCells.add(record.cell);
|
|
513
|
+
successes.push({ cell: record.cell, paidAttempt: record.paidAttempt, reviewBytes: bytes });
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
const uncertain = [...intents.entries()].filter(([key]) => !terminalAttempts.has(key)).map(([key, paidAttempt]) => ({ cell: key.split('\0')[0], paidAttempt }));
|
|
518
|
+
if (Object.keys(bindings).length !== Object.keys(suppliedBindings).length || terminalAttempts.size !== resultBytes.size)
|
|
519
|
+
throw new Error('checkpoint_unreferenced_bytes');
|
|
520
|
+
return { state: { records, outcomes, successes, uncertain, finalized }, bindings };
|
|
521
|
+
}
|
|
522
|
+
/** Structural integrity only: this brand does not attest providers or source authority. */
|
|
523
|
+
export function isCheckpointProof(value) {
|
|
524
|
+
return typeof value === 'object' && value !== null && validatedProofs.has(value);
|
|
525
|
+
}
|
|
526
|
+
/** Decode canonical portable bytes using the same plan, chain and outcome validation as disk reads. */
|
|
527
|
+
export function decodeCheckpointProof(bytes, expectedPlan) {
|
|
528
|
+
if (typeof bytes !== 'string')
|
|
529
|
+
throw new Error('checkpoint_invalid_proof');
|
|
530
|
+
if (Buffer.byteLength(bytes, 'utf8') > MAX_CHECKPOINT_PROOF_BYTES)
|
|
531
|
+
throw new Error('checkpoint_proof_too_large');
|
|
532
|
+
let value;
|
|
533
|
+
try {
|
|
534
|
+
value = JSON.parse(bytes);
|
|
535
|
+
}
|
|
536
|
+
catch {
|
|
537
|
+
throw new Error('checkpoint_invalid_proof');
|
|
538
|
+
}
|
|
539
|
+
const parsed = proofWireSchema.safeParse(value);
|
|
540
|
+
if (!parsed.success)
|
|
541
|
+
throw new Error('checkpoint_invalid_proof');
|
|
542
|
+
const wire = parsed.data;
|
|
543
|
+
// Validate the untrusted branches before recursively canonicalizing the
|
|
544
|
+
// portable envelope. The wire schema intentionally leaves plan and records
|
|
545
|
+
// opaque until their dedicated validators run.
|
|
546
|
+
const suppliedPlan = frozenPlanSchema.safeParse(wire.plan);
|
|
547
|
+
if (!suppliedPlan.success)
|
|
548
|
+
throw new Error('checkpoint_invalid_plan');
|
|
549
|
+
const plan = decodePlan(canonical(suppliedPlan.data));
|
|
550
|
+
if (expectedPlan && !matchingPlan(plan, decodePlan(canonical(expectedPlan))))
|
|
551
|
+
throw new Error('checkpoint_plan_mismatch');
|
|
552
|
+
const records = validateRecordChain(wire.records, plan);
|
|
553
|
+
const references = records.filter(record => record.type === 'result');
|
|
554
|
+
if (references.length !== wire.outcomes.length)
|
|
555
|
+
throw new Error('checkpoint_proof_outcomes_mismatch');
|
|
556
|
+
const resultBytes = new Map();
|
|
557
|
+
for (const [index, outcome] of wire.outcomes.entries()) {
|
|
558
|
+
if (references[index].result.resultFile !== outcome.resultFile || resultBytes.has(outcome.resultFile))
|
|
559
|
+
throw new Error('checkpoint_proof_outcomes_mismatch');
|
|
560
|
+
resultBytes.set(outcome.resultFile, outcome.reviewBytes);
|
|
561
|
+
}
|
|
562
|
+
const { state, bindings } = validateHistory(plan, records, resultBytes, wire.bindings);
|
|
563
|
+
if (!state.finalized)
|
|
564
|
+
throw new Error('checkpoint_proof_unsealed');
|
|
565
|
+
if (canonical(wire) !== bytes)
|
|
566
|
+
throw new Error('checkpoint_proof_noncanonical');
|
|
567
|
+
const proof = deepFreeze({ version: 1, bytes, digest: sha256(bytes), plan, state, bindings });
|
|
568
|
+
validatedProofs.add(proof);
|
|
569
|
+
return proof;
|
|
570
|
+
}
|
|
571
|
+
/** Export only a validated sealed journal. No live provider or lineage authority is inferred. */
|
|
572
|
+
export function exportCheckpointProof(journal) {
|
|
573
|
+
return journal.exportProof();
|
|
574
|
+
}
|
|
575
|
+
export class CheckpointJournal {
|
|
576
|
+
path;
|
|
577
|
+
plan;
|
|
578
|
+
commonDir;
|
|
579
|
+
needsResync = false;
|
|
580
|
+
verificationAcknowledgment;
|
|
581
|
+
verificationPrefixCache;
|
|
582
|
+
constructor(path, plan, commonDir) {
|
|
583
|
+
this.path = path;
|
|
584
|
+
this.plan = plan;
|
|
585
|
+
this.commonDir = commonDir;
|
|
586
|
+
}
|
|
587
|
+
static async create(input) {
|
|
588
|
+
const { namespace, ownership, commonDir: requestedCommonDir } = input;
|
|
589
|
+
const plan = freezeCheckpointPlan(input.plan);
|
|
590
|
+
if (canonical(plan) !== canonical(input.plan))
|
|
591
|
+
throw new Error('checkpoint_invalid_plan');
|
|
592
|
+
const planBytes = `${canonical(plan)}\n`;
|
|
593
|
+
boundedBytes(planBytes);
|
|
594
|
+
checkpointPath(requestedCommonDir, plan.target, namespace);
|
|
595
|
+
const commonDir = await ownedNativeTargetCommonDir(ownership, requestedCommonDir, plan.target);
|
|
596
|
+
return withOwnedNativeOperation(ownership, commonDir, plan.target, async (active) => {
|
|
597
|
+
await assertNativeTargetOwnership(active, commonDir, plan.target);
|
|
598
|
+
const path = checkpointPath(commonDir, plan.target, namespace);
|
|
599
|
+
if (relative(commonDir, path).startsWith('..'))
|
|
600
|
+
throw new Error('checkpoint_root_outside_common_dir');
|
|
601
|
+
const root = await ensurePrivateChild(commonDir, 'rcl-checkpoints');
|
|
602
|
+
const targetRoot = await ensurePrivateChild(root, directoryKey(plan.target));
|
|
603
|
+
if (relative(targetRoot, path).startsWith('..'))
|
|
604
|
+
throw new Error('checkpoint_root_outside_common_dir');
|
|
605
|
+
await ensurePrivateChild(targetRoot, namespace);
|
|
606
|
+
await ensurePrivateChild(path, 'events');
|
|
607
|
+
await ensurePrivateChild(path, 'results');
|
|
608
|
+
await publishExclusive(path, join(path, 'plan.json'), planBytes);
|
|
609
|
+
return new CheckpointJournal(path, plan, commonDir);
|
|
610
|
+
});
|
|
611
|
+
}
|
|
612
|
+
static async openRead(path, expected) {
|
|
613
|
+
const frozenExpected = freezeCheckpointPlan(expected);
|
|
614
|
+
if (canonical(frozenExpected) !== canonical(expected))
|
|
615
|
+
throw new Error('checkpoint_invalid_plan');
|
|
616
|
+
const canonicalPath = resolve(path);
|
|
617
|
+
await inspectDirectory(canonicalPath);
|
|
618
|
+
await inspectDirectory(join(canonicalPath, 'events'));
|
|
619
|
+
await inspectDirectory(join(canonicalPath, 'results'));
|
|
620
|
+
const actual = decodePlan((await readSafe(join(canonicalPath, 'plan.json'))).trim());
|
|
621
|
+
if (!matchingPlan(actual, frozenExpected))
|
|
622
|
+
throw new Error('checkpoint_plan_mismatch');
|
|
623
|
+
const journal = new CheckpointJournal(canonicalPath, actual);
|
|
624
|
+
await journal.read();
|
|
625
|
+
return journal;
|
|
626
|
+
}
|
|
627
|
+
/**
|
|
628
|
+
* Structural inspection only: validate the stored plan and complete history.
|
|
629
|
+
* This supplies no freshness, source authority or recovery eligibility proof.
|
|
630
|
+
* Callers needing an expected input binding must still use openRead(expected).
|
|
631
|
+
*/
|
|
632
|
+
static async inspectRead(path) {
|
|
633
|
+
const canonicalPath = resolve(path);
|
|
634
|
+
await inspectDirectory(canonicalPath);
|
|
635
|
+
const stored = decodePlan((await readSafe(join(canonicalPath, 'plan.json'))).trim());
|
|
636
|
+
return CheckpointJournal.openRead(canonicalPath, stored);
|
|
637
|
+
}
|
|
638
|
+
static async openWrite(input) {
|
|
639
|
+
const { namespace, ownership, commonDir: requestedCommonDir } = input;
|
|
640
|
+
const plan = freezeCheckpointPlan(input.plan);
|
|
641
|
+
if (canonical(plan) !== canonical(input.plan))
|
|
642
|
+
throw new Error('checkpoint_invalid_plan');
|
|
643
|
+
const commonDir = await ownedNativeTargetCommonDir(ownership, requestedCommonDir, plan.target);
|
|
644
|
+
return withOwnedNativeOperation(ownership, commonDir, plan.target, async (active) => {
|
|
645
|
+
await assertNativeTargetOwnership(active, commonDir, plan.target);
|
|
646
|
+
const journal = await CheckpointJournal.openRead(checkpointPath(commonDir, plan.target, namespace), plan);
|
|
647
|
+
const writable = new CheckpointJournal(journal.path, plan, commonDir);
|
|
648
|
+
await recoverPublications(async (pending) => {
|
|
649
|
+
const bytes = await readPublished(join(writable.path, 'plan.json'), writable.path, { singleLink: true }, pending);
|
|
650
|
+
if (!matchingPlan(decodePlan(bytes.trim()), plan))
|
|
651
|
+
throw new Error('checkpoint_plan_mismatch');
|
|
652
|
+
});
|
|
653
|
+
await writable.syncHistory(await writable.read());
|
|
654
|
+
return writable;
|
|
655
|
+
});
|
|
656
|
+
}
|
|
657
|
+
getPlan() { return this.plan; }
|
|
658
|
+
async read() { return (await this.readValidated()).state; }
|
|
659
|
+
/** Exact immutable metadata bytes; validates the entire journal without writing. */
|
|
660
|
+
async readBindings() { return (await this.readValidated()).bindings; }
|
|
661
|
+
/**
|
|
662
|
+
* Freeze a distinct, explicitly capped verifier phase after reviewer sealing.
|
|
663
|
+
* This storage contract never expands reviewer-cell caps or establishes spend,
|
|
664
|
+
* capture, provider or server authority. The executor must regenerate the plan
|
|
665
|
+
* from validated captures, authorize its separate cap and disable SDK retries.
|
|
666
|
+
*/
|
|
667
|
+
async beginVerification(input, ownership) {
|
|
668
|
+
await this.appendVerification({ type: 'plan', plan: input }, ownership);
|
|
669
|
+
}
|
|
670
|
+
/**
|
|
671
|
+
* True only for a newly durable intent. False means a prior intent exists and
|
|
672
|
+
* must NEVER be launched again, even when its first fsync acknowledgment failed.
|
|
673
|
+
* A true result is a storage claim, not independent provider launch authority.
|
|
674
|
+
*/
|
|
675
|
+
async recordVerificationIntent(input, ownership, afterIntent) {
|
|
676
|
+
return this.appendVerification({ type: 'intent', intent: input }, ownership, afterIntent);
|
|
677
|
+
}
|
|
678
|
+
/** Late verifier answers are audit-only and never change the sealed verifier proof. */
|
|
679
|
+
async readLateVerificationAudit() {
|
|
680
|
+
const main = await this.readValidated(), context = this.verificationContext(main);
|
|
681
|
+
const verification = await this.readVerificationValidated(context);
|
|
682
|
+
if (!verification?.terminal)
|
|
683
|
+
throw new Error('checkpoint_verification_late_requires_terminal');
|
|
684
|
+
return this.readLateVerificationAuditValidated(verification);
|
|
685
|
+
}
|
|
686
|
+
async recordLateVerificationResult(input, ownership) {
|
|
687
|
+
const snapshot = snapshotVerificationEvent({ type: 'result', result: input });
|
|
688
|
+
if (snapshot.type !== 'result')
|
|
689
|
+
throw new Error('checkpoint_verification_late_invalid_result');
|
|
690
|
+
const result = snapshot.result;
|
|
691
|
+
return this.write(ownership, async () => {
|
|
692
|
+
const main = await this.readValidated(), context = this.verificationContext(main);
|
|
693
|
+
const verification = await recoverPublications(pending => this.readVerificationValidated(context, pending));
|
|
694
|
+
if (!verification?.terminal)
|
|
695
|
+
throw new Error('checkpoint_verification_late_requires_terminal');
|
|
696
|
+
parseVerificationAnswer(result.answerBytes, verification.plan);
|
|
697
|
+
const intent = verification.intents.find(item => item.batchIndex === result.batchIndex && item.attemptId === result.attemptId);
|
|
698
|
+
if (!intent || result.finishedAtMs < intent.startedAtMs ||
|
|
699
|
+
verification.notDispatched.some(item => item.attemptId === result.attemptId))
|
|
700
|
+
throw new Error('checkpoint_verification_late_invalid_result');
|
|
701
|
+
const prior = await recoverPublications(pending => this.readLateVerificationAuditValidated(verification, pending));
|
|
702
|
+
const existing = prior.find(item => item.result.attemptId === result.attemptId);
|
|
703
|
+
if (existing) {
|
|
704
|
+
if (existing.result.batchIndex !== result.batchIndex || existing.result.finishedAtMs !== result.finishedAtMs || existing.result.answerBytes !== result.answerBytes)
|
|
705
|
+
throw new Error('checkpoint_verification_late_conflict');
|
|
706
|
+
const events = join(this.path, 'verification-late-audit', 'events');
|
|
707
|
+
for (const record of prior)
|
|
708
|
+
await syncExisting(join(events, eventFile(record.sequence)), `${canonical(record)}\n`, true, { maxBytes: MAX_ARTIFACT_BYTES, singleLink: true });
|
|
709
|
+
return;
|
|
710
|
+
}
|
|
711
|
+
if (prior.length >= verification.intents.length - verification.notDispatched.length)
|
|
712
|
+
throw new Error('checkpoint_verification_late_call_cap');
|
|
713
|
+
const directory = await ensurePrivateChild(this.path, 'verification-late-audit');
|
|
714
|
+
await ensurePrivateChild(directory, 'events');
|
|
715
|
+
const terminalDigest = verification.records.at(-1).digest;
|
|
716
|
+
const unsigned = { sequence: prior.length + 1, previousDigest: prior.at(-1)?.digest ?? terminalDigest,
|
|
717
|
+
version: 1, type: 'late-verification-result', verificationTerminalDigest: terminalDigest, result };
|
|
718
|
+
const record = { ...unsigned, digest: verificationDigest(canonical(unsigned)) };
|
|
719
|
+
const total = prior.reduce((sum, item) => sum + Buffer.byteLength(`${canonical(item)}\n`, 'utf8'), 0) + Buffer.byteLength(`${canonical(record)}\n`, 'utf8');
|
|
720
|
+
if (total > MAX_ARTIFACT_BYTES)
|
|
721
|
+
throw new Error('checkpoint_verification_late_too_large');
|
|
722
|
+
const events = join(directory, 'events');
|
|
723
|
+
for (const existingRecord of prior)
|
|
724
|
+
await syncExisting(join(events, eventFile(existingRecord.sequence)), `${canonical(existingRecord)}\n`, true, { maxBytes: MAX_ARTIFACT_BYTES, singleLink: true });
|
|
725
|
+
await publishEventExclusive(directory, eventFile(record.sequence), `${canonical(record)}\n`, MAX_ARTIFACT_BYTES);
|
|
726
|
+
});
|
|
727
|
+
}
|
|
728
|
+
async readLateVerificationAuditValidated(verification, pending) {
|
|
729
|
+
const directory = join(this.path, 'verification-late-audit');
|
|
730
|
+
try {
|
|
731
|
+
await lstat(directory);
|
|
732
|
+
}
|
|
733
|
+
catch (error) {
|
|
734
|
+
if (error.code === 'ENOENT')
|
|
735
|
+
return Object.freeze([]);
|
|
736
|
+
throw error;
|
|
737
|
+
}
|
|
738
|
+
await inspectDirectory(directory);
|
|
739
|
+
const rootEntries = await readdir(directory);
|
|
740
|
+
if (rootEntries.some(name => name !== 'events'))
|
|
741
|
+
throw new Error('checkpoint_verification_late_unknown_entry');
|
|
742
|
+
if (!verification.terminal)
|
|
743
|
+
throw new Error('checkpoint_verification_late_requires_terminal');
|
|
744
|
+
const terminalDigest = verification.records.at(-1).digest;
|
|
745
|
+
const events = join(directory, 'events');
|
|
746
|
+
try {
|
|
747
|
+
await lstat(events);
|
|
748
|
+
}
|
|
749
|
+
catch (error) {
|
|
750
|
+
if (error.code === 'ENOENT')
|
|
751
|
+
return Object.freeze([]);
|
|
752
|
+
throw error;
|
|
753
|
+
}
|
|
754
|
+
await inspectDirectory(events);
|
|
755
|
+
const names = (await readdir(events)).sort();
|
|
756
|
+
if (names.length > verification.intents.length - verification.notDispatched.length)
|
|
757
|
+
throw new Error('checkpoint_verification_late_call_cap');
|
|
758
|
+
const records = [];
|
|
759
|
+
const attempts = new Set();
|
|
760
|
+
let previous = terminalDigest;
|
|
761
|
+
let total = 0;
|
|
762
|
+
for (const [index, name] of names.entries()) {
|
|
763
|
+
if (name !== eventFile(index + 1))
|
|
764
|
+
throw new Error('checkpoint_verification_late_unknown_entry');
|
|
765
|
+
const bytes = await readPublished(join(events, name), this.path, { maxBytes: MAX_ARTIFACT_BYTES, singleLink: true }, pending);
|
|
766
|
+
total += Buffer.byteLength(bytes, 'utf8');
|
|
767
|
+
if (total > MAX_ARTIFACT_BYTES)
|
|
768
|
+
throw new Error('checkpoint_verification_late_too_large');
|
|
769
|
+
let value;
|
|
770
|
+
try {
|
|
771
|
+
value = JSON.parse(bytes);
|
|
772
|
+
}
|
|
773
|
+
catch {
|
|
774
|
+
throw new Error('checkpoint_verification_late_invalid_record');
|
|
775
|
+
}
|
|
776
|
+
const parsed = verificationLateRecordSchema.safeParse(value);
|
|
777
|
+
if (!parsed.success)
|
|
778
|
+
throw new Error('checkpoint_verification_late_invalid_record');
|
|
779
|
+
const record = parsed.data, { digest: hash, ...unsigned } = record;
|
|
780
|
+
if (record.sequence !== index + 1 || record.previousDigest !== previous || record.verificationTerminalDigest !== terminalDigest ||
|
|
781
|
+
hash !== verificationDigest(canonical(unsigned)) || bytes !== `${canonical(record)}\n` || attempts.has(record.result.attemptId))
|
|
782
|
+
throw new Error('checkpoint_verification_late_invalid_record');
|
|
783
|
+
const intent = verification.intents.find(item => item.batchIndex === record.result.batchIndex && item.attemptId === record.result.attemptId);
|
|
784
|
+
if (!intent || record.result.finishedAtMs < intent.startedAtMs ||
|
|
785
|
+
verification.notDispatched.some(item => item.attemptId === record.result.attemptId))
|
|
786
|
+
throw new Error('checkpoint_verification_late_invalid_record');
|
|
787
|
+
parseVerificationAnswer(record.result.answerBytes, verification.plan);
|
|
788
|
+
attempts.add(record.result.attemptId);
|
|
789
|
+
records.push(deepFreeze(record));
|
|
790
|
+
previous = hash;
|
|
791
|
+
}
|
|
792
|
+
return Object.freeze(records);
|
|
793
|
+
}
|
|
794
|
+
/** Exact observed adapter bytes; failed/late answers remain physical history. */
|
|
795
|
+
async recordVerificationResult(input, ownership) {
|
|
796
|
+
await this.appendVerification({ type: 'result', result: input }, ownership);
|
|
797
|
+
}
|
|
798
|
+
/** A terminal storage outcome, never a finding verdict or approval. */
|
|
799
|
+
async finalizeVerification(input, ownership) {
|
|
800
|
+
await this.appendVerification({ type: 'terminal', terminal: input }, ownership);
|
|
801
|
+
}
|
|
802
|
+
async readVerification() {
|
|
803
|
+
const directory = join(this.path, 'verification');
|
|
804
|
+
// Validate main history even when no phase has been created.
|
|
805
|
+
const main = await this.readValidated();
|
|
806
|
+
try {
|
|
807
|
+
await lstat(directory);
|
|
808
|
+
}
|
|
809
|
+
catch (error) {
|
|
810
|
+
if (error.code === 'ENOENT')
|
|
811
|
+
return undefined;
|
|
812
|
+
throw error;
|
|
813
|
+
}
|
|
814
|
+
return this.readVerificationValidated(this.verificationContext(main));
|
|
815
|
+
}
|
|
816
|
+
async exportVerificationProof() {
|
|
817
|
+
const context = this.verificationContext(await this.readValidated());
|
|
818
|
+
const state = await this.readVerificationValidated(context);
|
|
819
|
+
if (!state?.terminal)
|
|
820
|
+
throw new Error('checkpoint_verification_unsealed');
|
|
821
|
+
return encodeVerificationProof(state, context);
|
|
822
|
+
}
|
|
823
|
+
verificationContext(main) {
|
|
824
|
+
return verificationContextFromValidatedCheckpoint(this.plan, main);
|
|
825
|
+
}
|
|
826
|
+
async readVerificationValidated(context, pending) {
|
|
827
|
+
return (await this.readVerificationValidatedForAppend(context, pending)).snapshot.state;
|
|
828
|
+
}
|
|
829
|
+
async readVerificationValidatedForAppend(context, pending, ownership) {
|
|
830
|
+
const directory = join(this.path, 'verification');
|
|
831
|
+
try {
|
|
832
|
+
await lstat(directory);
|
|
833
|
+
}
|
|
834
|
+
catch (error) {
|
|
835
|
+
if (error.code === 'ENOENT')
|
|
836
|
+
return { snapshot: validateVerificationRecordsForAppend([], context), bytes: [] };
|
|
837
|
+
throw error;
|
|
838
|
+
}
|
|
839
|
+
await inspectDirectory(directory);
|
|
840
|
+
const entries = await readdir(directory);
|
|
841
|
+
if (entries.some(name => name !== 'events'))
|
|
842
|
+
throw new Error('checkpoint_verification_unknown_entry');
|
|
843
|
+
// A crash while publishing the empty directories consumed no intent.
|
|
844
|
+
if (!entries.length)
|
|
845
|
+
return { snapshot: validateVerificationRecordsForAppend([], context), bytes: [] };
|
|
846
|
+
const events = join(directory, 'events');
|
|
847
|
+
await inspectDirectory(events);
|
|
848
|
+
const names = (await readdir(events)).sort(), bytesBySequence = [];
|
|
849
|
+
if (names.length > MAX_VERIFICATION_PHASE_RECORDS)
|
|
850
|
+
throw new Error('checkpoint_verification_too_many_records');
|
|
851
|
+
let totalBytes = 0;
|
|
852
|
+
for (const [index, name] of names.entries()) {
|
|
853
|
+
if (name !== eventFile(index + 1))
|
|
854
|
+
throw new Error('checkpoint_verification_sequence_gap');
|
|
855
|
+
const bytes = await readPublished(join(events, name), this.path, { maxBytes: MAX_ARTIFACT_BYTES, singleLink: true }, pending);
|
|
856
|
+
totalBytes += Buffer.byteLength(bytes, 'utf8');
|
|
857
|
+
if (totalBytes > MAX_ARTIFACT_BYTES)
|
|
858
|
+
throw new Error('checkpoint_verification_too_large');
|
|
859
|
+
let value;
|
|
860
|
+
try {
|
|
861
|
+
value = JSON.parse(bytes);
|
|
862
|
+
}
|
|
863
|
+
catch {
|
|
864
|
+
throw new Error('checkpoint_verification_invalid_record');
|
|
865
|
+
}
|
|
866
|
+
if (bytes !== `${canonical(value)}\n`)
|
|
867
|
+
throw new Error('checkpoint_verification_noncanonical');
|
|
868
|
+
bytesBySequence.push(bytes);
|
|
869
|
+
}
|
|
870
|
+
const cache = this.verificationPrefixCache;
|
|
871
|
+
if (cache && cache.ownership === ownership && cache.context.runId === context.runId && cache.context.planDigest === context.planDigest &&
|
|
872
|
+
cache.bytes.length === bytesBySequence.length && cache.bytes.every((bytes, index) => bytes === bytesBySequence[index])) {
|
|
873
|
+
try {
|
|
874
|
+
return { snapshot: cloneValidatedVerificationRecords(cache.snapshot, context), bytes: bytesBySequence };
|
|
875
|
+
}
|
|
876
|
+
catch {
|
|
877
|
+
this.verificationPrefixCache = undefined;
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
else if (cache)
|
|
881
|
+
this.verificationPrefixCache = undefined;
|
|
882
|
+
const values = bytesBySequence.map(bytes => JSON.parse(bytes));
|
|
883
|
+
return { snapshot: validateVerificationRecordsForAppend(values, context), bytes: bytesBySequence };
|
|
884
|
+
}
|
|
885
|
+
async appendVerification(input, ownership, afterIntent) {
|
|
886
|
+
const event = snapshotVerificationEvent(input);
|
|
887
|
+
return this.write(ownership, async () => {
|
|
888
|
+
const context = this.verificationContext(await this.readValidated());
|
|
889
|
+
const read = await recoverPublications(pending => this.readVerificationValidatedForAppend(context, pending, ownership)), snapshot = read.snapshot, state = snapshot.state, records = state?.records ?? [];
|
|
890
|
+
const prior = records.find(row => row.event.type === event.type &&
|
|
891
|
+
(event.type === 'intent' ? row.event.type === 'intent' && row.event.intent.attemptId === event.intent.attemptId
|
|
892
|
+
: event.type === 'result' || event.type === 'not-dispatched'
|
|
893
|
+
? row.event.type === event.type && row.event.result.attemptId === event.result.attemptId : true));
|
|
894
|
+
if (prior && canonical(prior.event) !== canonical(event))
|
|
895
|
+
throw new Error('checkpoint_verification_conflict');
|
|
896
|
+
// An immutable report, including a surviving partial publication, closes
|
|
897
|
+
// this run to new paid work. Only identical records of a sealed phase can
|
|
898
|
+
// be re-acknowledged afterward; they never return a new launch claim.
|
|
899
|
+
if (await this.terminalReportEntries() !== undefined && (!state?.terminal || !prior)) {
|
|
900
|
+
throw new Error('checkpoint_verification_report_finalized');
|
|
901
|
+
}
|
|
902
|
+
const appended = prior ? undefined : appendVerificationRecordWithSuccessor(snapshot, event, context);
|
|
903
|
+
const record = appended?.record;
|
|
904
|
+
if (!state && event.type !== 'plan')
|
|
905
|
+
throw new Error('checkpoint_verification_missing_plan');
|
|
906
|
+
const directory = await ensurePrivateChild(this.path, 'verification');
|
|
907
|
+
await ensurePrivateChild(directory, 'events');
|
|
908
|
+
const tip = records.at(-1);
|
|
909
|
+
const acknowledged = this.verificationAcknowledgment;
|
|
910
|
+
const canSkipHistorySync = tip !== undefined && acknowledged?.ownership === ownership &&
|
|
911
|
+
acknowledged.sequence === tip.sequence && acknowledged.digest === tip.digest;
|
|
912
|
+
try {
|
|
913
|
+
if (!canSkipHistorySync) {
|
|
914
|
+
for (const existing of records) {
|
|
915
|
+
await syncExisting(join(directory, 'events', eventFile(existing.sequence)), `${canonical(existing)}\n`, true, { maxBytes: MAX_ARTIFACT_BYTES, singleLink: true });
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
if (!record)
|
|
919
|
+
return false;
|
|
920
|
+
const recordBytes = `${canonical(record)}\n`;
|
|
921
|
+
await publishEventExclusive(directory, eventFile(record.sequence), recordBytes, MAX_ARTIFACT_BYTES);
|
|
922
|
+
let finalRecord = record, finalSnapshot = appended.successor, finalBytes = [...read.bytes, recordBytes];
|
|
923
|
+
if (event.type === 'intent' && afterIntent) {
|
|
924
|
+
const result = afterIntent(event.intent);
|
|
925
|
+
if (result) {
|
|
926
|
+
if (result.batchIndex !== event.intent.batchIndex || result.attemptId !== event.intent.attemptId) {
|
|
927
|
+
throw new Error('checkpoint_verification_not_dispatched_mismatch');
|
|
928
|
+
}
|
|
929
|
+
const resultEvent = snapshotVerificationEvent({ type: 'not-dispatched', result });
|
|
930
|
+
const successor = appendVerificationRecordWithSuccessor(finalSnapshot, resultEvent, context);
|
|
931
|
+
const resultBytes = `${canonical(successor.record)}\n`;
|
|
932
|
+
await publishEventExclusive(directory, eventFile(successor.record.sequence), resultBytes, MAX_ARTIFACT_BYTES);
|
|
933
|
+
finalRecord = successor.record;
|
|
934
|
+
finalSnapshot = successor.successor;
|
|
935
|
+
finalBytes = [...finalBytes, resultBytes];
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
this.verificationAcknowledgment = { ownership, sequence: finalRecord.sequence, digest: finalRecord.digest };
|
|
939
|
+
this.verificationPrefixCache = { ownership, context, bytes: finalBytes, snapshot: finalSnapshot };
|
|
940
|
+
return true;
|
|
941
|
+
}
|
|
942
|
+
catch (error) {
|
|
943
|
+
this.verificationAcknowledgment = undefined;
|
|
944
|
+
this.verificationPrefixCache = undefined;
|
|
945
|
+
throw error;
|
|
946
|
+
}
|
|
947
|
+
}).catch(error => {
|
|
948
|
+
this.verificationAcknowledgment = undefined;
|
|
949
|
+
this.verificationPrefixCache = undefined;
|
|
950
|
+
throw error;
|
|
951
|
+
});
|
|
952
|
+
}
|
|
953
|
+
/**
|
|
954
|
+
* Retain opaque terminal bytes after sealing, under the caller's ownership.
|
|
955
|
+
* Exact orphan payloads can resume; no existing bytes are replaced. The final
|
|
956
|
+
* manifest publishes the pair and does not confer producer authority.
|
|
957
|
+
*/
|
|
958
|
+
async retainTerminalReport(input, ownership) {
|
|
959
|
+
const parsed = terminalPayloadSchema.safeParse(input);
|
|
960
|
+
if (!parsed.success)
|
|
961
|
+
throw new Error('checkpoint_invalid_terminal_report');
|
|
962
|
+
const captured = parsed.data;
|
|
963
|
+
for (const bytes of Object.values(captured)) {
|
|
964
|
+
boundedBytes(bytes, MAX_ARTIFACT_BYTES);
|
|
965
|
+
if (Buffer.from(bytes, 'utf8').toString('utf8') !== bytes)
|
|
966
|
+
throw new Error('checkpoint_invalid_bytes');
|
|
967
|
+
}
|
|
968
|
+
return this.write(ownership, async () => {
|
|
969
|
+
const state = await this.read();
|
|
970
|
+
if (!state.finalized)
|
|
971
|
+
throw new Error('checkpoint_terminal_report_requires_finalization');
|
|
972
|
+
let verification;
|
|
973
|
+
let hasVerification = false;
|
|
974
|
+
try {
|
|
975
|
+
await lstat(join(this.path, 'verification'));
|
|
976
|
+
hasVerification = true;
|
|
977
|
+
}
|
|
978
|
+
catch (error) {
|
|
979
|
+
if (error.code !== 'ENOENT')
|
|
980
|
+
throw error;
|
|
981
|
+
}
|
|
982
|
+
if (hasVerification) {
|
|
983
|
+
const context = this.verificationContext(await this.readValidated());
|
|
984
|
+
verification = await recoverPublications(pending => this.readVerificationValidated(context, pending));
|
|
985
|
+
}
|
|
986
|
+
if (verification && !verification.terminal)
|
|
987
|
+
throw new Error('checkpoint_terminal_report_verification_pending');
|
|
988
|
+
const directory = join(this.path, 'terminal-report');
|
|
989
|
+
const payloads = [['report.json', captured.reportBytes], ['reviewer-artifact.json', captured.reviewerArtifactBytes]];
|
|
990
|
+
await recoverPublications(async (pending) => {
|
|
991
|
+
const entries = await this.terminalReportEntries();
|
|
992
|
+
if (entries?.includes('manifest.json')) {
|
|
993
|
+
const report = await this.readTerminalReportValidated(state, pending);
|
|
994
|
+
if (report.reportBytes !== captured.reportBytes || report.reviewerArtifactBytes !== captured.reviewerArtifactBytes)
|
|
995
|
+
throw new Error('checkpoint_terminal_report_conflict');
|
|
996
|
+
}
|
|
997
|
+
else {
|
|
998
|
+
for (const [name, bytes] of payloads) {
|
|
999
|
+
if (entries?.includes(name) && await readPublished(join(directory, name), this.path, terminalPayloadOptions, pending) !== bytes)
|
|
1000
|
+
throw new Error('checkpoint_terminal_report_conflict');
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
});
|
|
1004
|
+
const names = await this.terminalReportEntries();
|
|
1005
|
+
await ensurePrivateChild(this.path, 'terminal-report');
|
|
1006
|
+
for (const [name, bytes] of payloads) {
|
|
1007
|
+
const path = join(directory, name);
|
|
1008
|
+
if (!names?.includes(name))
|
|
1009
|
+
await publishExclusive(this.path, path, bytes, MAX_ARTIFACT_BYTES);
|
|
1010
|
+
await syncExisting(path, bytes, true, terminalPayloadOptions);
|
|
1011
|
+
}
|
|
1012
|
+
const manifest = { version: 1, planDigest: this.plan.digest, finalizationDigest: state.records.at(-1).digest,
|
|
1013
|
+
reportSha256: sha256(captured.reportBytes), reviewerArtifactSha256: sha256(captured.reviewerArtifactBytes),
|
|
1014
|
+
reportByteLength: Buffer.byteLength(captured.reportBytes, 'utf8'), reviewerArtifactByteLength: Buffer.byteLength(captured.reviewerArtifactBytes, 'utf8') };
|
|
1015
|
+
const bytes = `${canonical(manifest)}\n`, path = join(directory, 'manifest.json');
|
|
1016
|
+
if (!names?.includes('manifest.json'))
|
|
1017
|
+
await publishExclusive(this.path, path, bytes);
|
|
1018
|
+
await syncExisting(path, bytes, true, terminalManifestOptions);
|
|
1019
|
+
});
|
|
1020
|
+
}
|
|
1021
|
+
/** Exact private payloads only after complete publication; no report semantics are inferred. */
|
|
1022
|
+
async readTerminalReport() {
|
|
1023
|
+
return this.readTerminalReportValidated(await this.read());
|
|
1024
|
+
}
|
|
1025
|
+
async terminalReportEntries() {
|
|
1026
|
+
const path = join(this.path, 'terminal-report');
|
|
1027
|
+
try {
|
|
1028
|
+
await lstat(path);
|
|
1029
|
+
}
|
|
1030
|
+
catch (error) {
|
|
1031
|
+
if (error.code === 'ENOENT')
|
|
1032
|
+
return undefined;
|
|
1033
|
+
throw error;
|
|
1034
|
+
}
|
|
1035
|
+
await inspectDirectory(path);
|
|
1036
|
+
const names = (await readdir(path)).sort();
|
|
1037
|
+
if (names.some(name => !terminalFileNames.includes(name)))
|
|
1038
|
+
throw new Error('checkpoint_terminal_report_unknown_entry');
|
|
1039
|
+
return names;
|
|
1040
|
+
}
|
|
1041
|
+
async readTerminalReportValidated(state, pending) {
|
|
1042
|
+
const names = await this.terminalReportEntries();
|
|
1043
|
+
if (names === undefined)
|
|
1044
|
+
return undefined;
|
|
1045
|
+
if (!state.finalized)
|
|
1046
|
+
throw new Error('checkpoint_terminal_report_requires_finalization');
|
|
1047
|
+
if (names.length !== terminalFileNames.length)
|
|
1048
|
+
throw new Error('checkpoint_terminal_report_incomplete');
|
|
1049
|
+
const path = join(this.path, 'terminal-report'), bytes = await readPublished(join(path, 'manifest.json'), this.path, terminalManifestOptions, pending);
|
|
1050
|
+
let value;
|
|
1051
|
+
try {
|
|
1052
|
+
value = JSON.parse(bytes);
|
|
1053
|
+
}
|
|
1054
|
+
catch {
|
|
1055
|
+
throw new Error('checkpoint_invalid_terminal_manifest');
|
|
1056
|
+
}
|
|
1057
|
+
const parsed = terminalManifestSchema.safeParse(value);
|
|
1058
|
+
if (!parsed.success)
|
|
1059
|
+
throw new Error('checkpoint_invalid_terminal_manifest');
|
|
1060
|
+
const manifest = parsed.data;
|
|
1061
|
+
if (bytes !== `${canonical(manifest)}\n` || manifest.planDigest !== this.plan.digest ||
|
|
1062
|
+
manifest.finalizationDigest !== state.records.at(-1).digest)
|
|
1063
|
+
throw new Error('checkpoint_invalid_terminal_manifest');
|
|
1064
|
+
const reportBytes = await readPublished(join(path, 'report.json'), this.path, terminalPayloadOptions, pending);
|
|
1065
|
+
const reviewerArtifactBytes = await readPublished(join(path, 'reviewer-artifact.json'), this.path, terminalPayloadOptions, pending);
|
|
1066
|
+
if (Buffer.byteLength(reportBytes, 'utf8') !== manifest.reportByteLength || Buffer.byteLength(reviewerArtifactBytes, 'utf8') !== manifest.reviewerArtifactByteLength ||
|
|
1067
|
+
sha256(reportBytes) !== manifest.reportSha256 || sha256(reviewerArtifactBytes) !== manifest.reviewerArtifactSha256)
|
|
1068
|
+
throw new Error('checkpoint_terminal_report_tampered');
|
|
1069
|
+
return Object.freeze({ reportBytes, reviewerArtifactBytes, reportSha256: manifest.reportSha256, reviewerArtifactSha256: manifest.reviewerArtifactSha256 });
|
|
1070
|
+
}
|
|
1071
|
+
/** Late observations are audit-only and never enter the sealed main proof or accounting. */
|
|
1072
|
+
async readLateAudit() {
|
|
1073
|
+
return this.readLateAuditValidated(await this.read());
|
|
1074
|
+
}
|
|
1075
|
+
async readLateAuditValidated(state, pending) {
|
|
1076
|
+
const path = join(this.path, 'late-audit');
|
|
1077
|
+
try {
|
|
1078
|
+
await lstat(path);
|
|
1079
|
+
}
|
|
1080
|
+
catch (error) {
|
|
1081
|
+
if (error.code === 'ENOENT')
|
|
1082
|
+
return Object.freeze([]);
|
|
1083
|
+
throw error;
|
|
1084
|
+
}
|
|
1085
|
+
await inspectDirectory(path);
|
|
1086
|
+
if (!state.finalized)
|
|
1087
|
+
throw new Error('checkpoint_late_requires_finalization');
|
|
1088
|
+
const finalization = state.records.at(-1);
|
|
1089
|
+
const names = (await readdir(path)).sort(), records = [], attempts = new Set();
|
|
1090
|
+
let previous = finalization.digest;
|
|
1091
|
+
for (const [index, name] of names.entries()) {
|
|
1092
|
+
if (name !== eventFile(index + 1))
|
|
1093
|
+
throw new Error('checkpoint_late_unknown_entry');
|
|
1094
|
+
const bytes = await readPublished(join(path, name), this.path, lateFileOptions, pending);
|
|
1095
|
+
let value;
|
|
1096
|
+
try {
|
|
1097
|
+
value = JSON.parse(bytes);
|
|
1098
|
+
}
|
|
1099
|
+
catch {
|
|
1100
|
+
throw new Error('checkpoint_invalid_late_record');
|
|
1101
|
+
}
|
|
1102
|
+
const parsed = lateRecordSchema.safeParse(value);
|
|
1103
|
+
if (!parsed.success)
|
|
1104
|
+
throw new Error('checkpoint_invalid_late_record');
|
|
1105
|
+
const record = parsed.data, { digest, ...unsigned } = record;
|
|
1106
|
+
if (record.sequence !== index + 1 || record.previousDigest !== previous || digest !== sha256(canonical(unsigned)) ||
|
|
1107
|
+
bytes !== `${canonical(record)}\n` || record.planDigest !== this.plan.digest || record.finalizationDigest !== finalization.digest || attempts.has(record.paidAttempt.id))
|
|
1108
|
+
throw new Error('checkpoint_invalid_late_record');
|
|
1109
|
+
const intent = state.records.find(row => row.type === 'intent' && row.cell === record.cell && row.paidAttempt?.id === record.paidAttempt.id && row.paidAttempt.kind === record.paidAttempt.kind);
|
|
1110
|
+
const cell = this.plan.cells.find(cell => cell.id === record.cell);
|
|
1111
|
+
if (!intent || !cell || intent.digest !== record.intentDigest)
|
|
1112
|
+
throw new Error('checkpoint_late_missing_intent');
|
|
1113
|
+
boundedOpaqueBytes(record.reviewBytes);
|
|
1114
|
+
validateReviewBytes(record.reviewBytes, cell);
|
|
1115
|
+
if (sha256(record.reviewBytes) !== record.reviewSha256)
|
|
1116
|
+
throw new Error('checkpoint_late_tampered');
|
|
1117
|
+
attempts.add(record.paidAttempt.id);
|
|
1118
|
+
previous = digest;
|
|
1119
|
+
records.push(record);
|
|
1120
|
+
}
|
|
1121
|
+
return deepFreeze(records);
|
|
1122
|
+
}
|
|
1123
|
+
async syncLateRecord(record) {
|
|
1124
|
+
await syncExisting(join(this.path, 'late-audit', eventFile(record.sequence)), `${canonical(record)}\n`, true, lateFileOptions);
|
|
1125
|
+
}
|
|
1126
|
+
/** Caller retains live original ownership while draining; no ownership is created here. */
|
|
1127
|
+
async recordLateResult(cell, paidAttempt, rawReviewBytes, ownership) {
|
|
1128
|
+
const attempt = snapshotAttempt(paidAttempt);
|
|
1129
|
+
this.cell(cell);
|
|
1130
|
+
validateReviewBytes(rawReviewBytes, this.plan.cells.find(candidate => candidate.id === cell));
|
|
1131
|
+
boundedOpaqueBytes(rawReviewBytes);
|
|
1132
|
+
return this.write(ownership, async () => {
|
|
1133
|
+
const state = await this.read();
|
|
1134
|
+
if (!state.finalized)
|
|
1135
|
+
throw new Error('checkpoint_late_requires_finalization');
|
|
1136
|
+
const intent = state.records.find(row => row.type === 'intent' && row.cell === cell && row.paidAttempt?.id === attempt.id && row.paidAttempt.kind === attempt.kind);
|
|
1137
|
+
if (!intent)
|
|
1138
|
+
throw new Error('checkpoint_late_missing_intent');
|
|
1139
|
+
const records = await recoverPublications(pending => this.readLateAuditValidated(state, pending));
|
|
1140
|
+
const prior = records.find(record => record.paidAttempt.id === attempt.id);
|
|
1141
|
+
if (prior && prior.reviewBytes !== rawReviewBytes)
|
|
1142
|
+
throw new Error('checkpoint_late_conflict');
|
|
1143
|
+
const finalizationDigest = state.records.at(-1).digest;
|
|
1144
|
+
const unsigned = { version: 1, type: 'late-result', sequence: records.length + 1, previousDigest: records.at(-1)?.digest ?? finalizationDigest,
|
|
1145
|
+
planDigest: this.plan.digest, finalizationDigest, intentDigest: intent.digest, cell, paidAttempt: attempt,
|
|
1146
|
+
reviewSha256: sha256(rawReviewBytes), reviewBytes: rawReviewBytes };
|
|
1147
|
+
const record = { ...unsigned, digest: sha256(canonical(unsigned)) };
|
|
1148
|
+
const bytes = `${canonical(record)}\n`;
|
|
1149
|
+
boundedBytes(bytes, lateFileOptions.maxBytes);
|
|
1150
|
+
const path = await ensurePrivateChild(this.path, 'late-audit');
|
|
1151
|
+
// Reflush acknowledged history too: a prior process may have lost an fsync acknowledgment.
|
|
1152
|
+
for (const existing of records)
|
|
1153
|
+
await this.syncLateRecord(existing);
|
|
1154
|
+
if (prior)
|
|
1155
|
+
return;
|
|
1156
|
+
await publishExclusive(this.path, join(path, eventFile(record.sequence)), bytes, lateFileOptions.maxBytes);
|
|
1157
|
+
await this.syncLateRecord(record);
|
|
1158
|
+
});
|
|
1159
|
+
}
|
|
1160
|
+
async exportProof() {
|
|
1161
|
+
const { state, bindings } = await this.readValidated(MAX_CHECKPOINT_PROOF_BYTES);
|
|
1162
|
+
if (!state.finalized)
|
|
1163
|
+
throw new Error('checkpoint_proof_unsealed');
|
|
1164
|
+
let outcomeIndex = 0;
|
|
1165
|
+
const outcomes = state.records.filter(record => record.type === 'result').map(record => ({
|
|
1166
|
+
resultFile: record.result.resultFile, reviewBytes: state.outcomes[outcomeIndex++].result.reviewBytes,
|
|
1167
|
+
}));
|
|
1168
|
+
const bytes = canonical({ version: 1, plan: this.plan, records: state.records, outcomes, bindings });
|
|
1169
|
+
return decodeCheckpointProof(bytes, this.plan);
|
|
1170
|
+
}
|
|
1171
|
+
async readValidated(maxReadBytes = MAX_CHECKPOINT_READ_BYTES) {
|
|
1172
|
+
await inspectDirectory(this.path);
|
|
1173
|
+
await inspectDirectory(join(this.path, 'events'));
|
|
1174
|
+
await inspectDirectory(join(this.path, 'results'));
|
|
1175
|
+
const budget = { remaining: maxReadBytes, error: maxReadBytes === MAX_CHECKPOINT_PROOF_BYTES ? 'checkpoint_proof_too_large' : 'checkpoint_journal_too_large' };
|
|
1176
|
+
const actual = decodePlan((await readSafe(join(this.path, 'plan.json'), false, { budget })).trim());
|
|
1177
|
+
if (!matchingPlan(actual, this.plan))
|
|
1178
|
+
throw new Error('checkpoint_plan_mismatch');
|
|
1179
|
+
const names = (await readdir(join(this.path, 'events'))).sort();
|
|
1180
|
+
const rawRecords = [];
|
|
1181
|
+
for (let i = 0; i < names.length; i++) {
|
|
1182
|
+
if (names[i] !== eventFile(i + 1))
|
|
1183
|
+
throw new Error('checkpoint_sequence_gap');
|
|
1184
|
+
let bytes = '';
|
|
1185
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
1186
|
+
try {
|
|
1187
|
+
bytes = await readSafe(join(this.path, 'events', names[i]), false, { budget });
|
|
1188
|
+
break;
|
|
1189
|
+
}
|
|
1190
|
+
catch (error) {
|
|
1191
|
+
if (error instanceof Error && error.message === 'checkpoint_changing_source' && attempt < 2)
|
|
1192
|
+
continue;
|
|
1193
|
+
if (error instanceof Error && (error.message === 'checkpoint_symlink' || error.message === budget.error))
|
|
1194
|
+
throw error;
|
|
1195
|
+
throw new Error('checkpoint_invalid_record');
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
let record;
|
|
1199
|
+
try {
|
|
1200
|
+
record = JSON.parse(bytes);
|
|
1201
|
+
}
|
|
1202
|
+
catch {
|
|
1203
|
+
throw new Error('checkpoint_invalid_record');
|
|
1204
|
+
}
|
|
1205
|
+
rawRecords.push(record);
|
|
1206
|
+
}
|
|
1207
|
+
const records = validateRecordChain(rawRecords, this.plan);
|
|
1208
|
+
const resultBytes = new Map(), suppliedBindings = {};
|
|
1209
|
+
for (const record of records) {
|
|
1210
|
+
if (record.binding)
|
|
1211
|
+
suppliedBindings[record.binding.name] = await readSafe(join(this.path, record.binding.file), true, { budget });
|
|
1212
|
+
if (record.result)
|
|
1213
|
+
resultBytes.set(record.result.resultFile, await readSafe(join(this.path, 'results', record.result.resultFile), true, { budget }));
|
|
1214
|
+
}
|
|
1215
|
+
return { ...validateHistory(this.plan, records, resultBytes, suppliedBindings), bytesRead: maxReadBytes - budget.remaining };
|
|
1216
|
+
}
|
|
1217
|
+
async write(ownership, operation) {
|
|
1218
|
+
if (!this.commonDir)
|
|
1219
|
+
throw new Error('checkpoint_read_only');
|
|
1220
|
+
return withOwnedNativeOperation(ownership, this.commonDir, this.plan.target, async (active) => {
|
|
1221
|
+
await assertNativeTargetOwnership(active, this.commonDir, this.plan.target);
|
|
1222
|
+
try {
|
|
1223
|
+
if (this.needsResync) {
|
|
1224
|
+
await this.syncHistory(await this.read());
|
|
1225
|
+
this.needsResync = false;
|
|
1226
|
+
}
|
|
1227
|
+
return await operation(active);
|
|
1228
|
+
}
|
|
1229
|
+
catch (error) {
|
|
1230
|
+
this.needsResync = true;
|
|
1231
|
+
throw error;
|
|
1232
|
+
}
|
|
1233
|
+
});
|
|
1234
|
+
}
|
|
1235
|
+
async syncRecord(record) {
|
|
1236
|
+
if (record.binding) {
|
|
1237
|
+
const path = join(this.path, record.binding.file), bytes = await readSafe(path, true);
|
|
1238
|
+
if (sha256(bytes) !== record.binding.sha256)
|
|
1239
|
+
throw new Error('checkpoint_binding_tampered');
|
|
1240
|
+
await syncExisting(path, bytes, true);
|
|
1241
|
+
}
|
|
1242
|
+
if (record.result) {
|
|
1243
|
+
const path = join(this.path, 'results', record.result.resultFile), bytes = await readSafe(path, true);
|
|
1244
|
+
if (sha256(bytes) !== record.result.reviewSha256)
|
|
1245
|
+
throw new Error('checkpoint_result_tampered');
|
|
1246
|
+
await syncExisting(path, bytes, true);
|
|
1247
|
+
}
|
|
1248
|
+
await syncExisting(join(this.path, 'events', eventFile(record.sequence)), `${canonical(record)}\n`);
|
|
1249
|
+
}
|
|
1250
|
+
async syncHistory(state) {
|
|
1251
|
+
await syncExisting(join(this.path, 'plan.json'), `${canonical(this.plan)}\n`);
|
|
1252
|
+
for (const record of state.records)
|
|
1253
|
+
await this.syncRecord(record);
|
|
1254
|
+
}
|
|
1255
|
+
async append(record, snapshot) {
|
|
1256
|
+
const { state, bytesRead } = snapshot;
|
|
1257
|
+
const previousDigest = state.records.at(-1)?.digest ?? this.plan.digest;
|
|
1258
|
+
const unsigned = { ...record, sequence: state.records.length + 1, previousDigest };
|
|
1259
|
+
const complete = { ...unsigned, digest: recordDigest(unsigned) };
|
|
1260
|
+
const bytes = `${canonical(complete)}\n`;
|
|
1261
|
+
const addedFile = record.binding ? join(this.path, record.binding.file) : record.result ? join(this.path, 'results', record.result.resultFile) : undefined;
|
|
1262
|
+
const addedBytes = addedFile ? (await lstat(addedFile)).size : 0;
|
|
1263
|
+
if (bytesRead + addedBytes + Buffer.byteLength(bytes, 'utf8') > MAX_CHECKPOINT_READ_BYTES)
|
|
1264
|
+
throw new Error('checkpoint_journal_too_large');
|
|
1265
|
+
await publishEventExclusive(this.path, eventFile(complete.sequence), bytes);
|
|
1266
|
+
}
|
|
1267
|
+
cell(cell) { if (!this.plan.cells.some(candidate => candidate.id === cell))
|
|
1268
|
+
throw new Error('checkpoint_unknown_cell'); }
|
|
1269
|
+
/** Bind opaque UTF-8 bytes before work begins. Identical replay re-establishes durability. */
|
|
1270
|
+
async bind(name, bytes, ownership) {
|
|
1271
|
+
const parsed = bindingNameSchema.safeParse(name);
|
|
1272
|
+
if (!parsed.success || typeof bytes !== 'string')
|
|
1273
|
+
throw new Error('checkpoint_invalid_binding');
|
|
1274
|
+
boundedBytes(bytes);
|
|
1275
|
+
if (Buffer.from(bytes, 'utf8').toString('utf8') !== bytes)
|
|
1276
|
+
throw new Error('checkpoint_invalid_binding');
|
|
1277
|
+
const binding = { name: parsed.data, file: bindingFile(parsed.data), sha256: sha256(bytes) };
|
|
1278
|
+
return this.write(ownership, async () => {
|
|
1279
|
+
const snapshot = await this.readValidated();
|
|
1280
|
+
const { state, bindings } = snapshot;
|
|
1281
|
+
const prior = state.records.find(record => record.type === 'binding' && record.binding?.name === binding.name);
|
|
1282
|
+
if (prior) {
|
|
1283
|
+
if (bindings[binding.name] !== bytes)
|
|
1284
|
+
throw new Error('checkpoint_binding_conflict');
|
|
1285
|
+
await this.syncRecord(prior);
|
|
1286
|
+
return;
|
|
1287
|
+
}
|
|
1288
|
+
if (state.finalized)
|
|
1289
|
+
throw new Error('checkpoint_finalized');
|
|
1290
|
+
if (state.records.some(record => record.type === 'intent'))
|
|
1291
|
+
throw new Error('checkpoint_binding_closed');
|
|
1292
|
+
const path = join(this.path, binding.file);
|
|
1293
|
+
try {
|
|
1294
|
+
await publishExclusive(this.path, path, bytes);
|
|
1295
|
+
}
|
|
1296
|
+
catch (error) {
|
|
1297
|
+
if (error.code !== 'EEXIST')
|
|
1298
|
+
throw error;
|
|
1299
|
+
await recoverPublications(async (pending) => {
|
|
1300
|
+
if (await readPublished(path, this.path, { singleLink: true }, pending) !== bytes)
|
|
1301
|
+
throw new Error('checkpoint_binding_conflict');
|
|
1302
|
+
});
|
|
1303
|
+
await syncExisting(path, bytes, true, { singleLink: true });
|
|
1304
|
+
}
|
|
1305
|
+
await this.append({ type: 'binding', binding }, snapshot);
|
|
1306
|
+
});
|
|
1307
|
+
}
|
|
1308
|
+
async recordIntent(cell, paidAttempt, ownership) {
|
|
1309
|
+
const attempt = snapshotAttempt(paidAttempt);
|
|
1310
|
+
return this.write(ownership, async () => {
|
|
1311
|
+
this.cell(cell);
|
|
1312
|
+
const snapshot = await this.readValidated();
|
|
1313
|
+
const state = snapshot.state;
|
|
1314
|
+
if (state.finalized)
|
|
1315
|
+
throw new Error('checkpoint_finalized');
|
|
1316
|
+
const prior = state.records.find(record => record.type === 'intent' && record.paidAttempt?.id === attempt.id);
|
|
1317
|
+
if (prior) {
|
|
1318
|
+
if (prior.cell !== cell || prior.paidAttempt?.kind !== attempt.kind)
|
|
1319
|
+
throw new Error('checkpoint_duplicate_attempt');
|
|
1320
|
+
await this.syncRecord(prior);
|
|
1321
|
+
return;
|
|
1322
|
+
}
|
|
1323
|
+
if (state.successes.some(success => success.cell === cell))
|
|
1324
|
+
throw new Error('checkpoint_success_immutable');
|
|
1325
|
+
await this.append({ type: 'intent', cell, paidAttempt: attempt }, snapshot);
|
|
1326
|
+
});
|
|
1327
|
+
}
|
|
1328
|
+
async recordUncertain(cell, paidAttempt, reason, ownership) {
|
|
1329
|
+
const attempt = snapshotAttempt(paidAttempt);
|
|
1330
|
+
requireText(reason, 'reason');
|
|
1331
|
+
return this.write(ownership, async () => {
|
|
1332
|
+
this.cell(cell);
|
|
1333
|
+
const snapshot = await this.readValidated();
|
|
1334
|
+
const state = snapshot.state;
|
|
1335
|
+
if (state.finalized)
|
|
1336
|
+
throw new Error('checkpoint_finalized');
|
|
1337
|
+
if (!state.records.some(record => record.type === 'intent' && record.cell === cell && record.paidAttempt?.id === attempt.id && record.paidAttempt.kind === attempt.kind))
|
|
1338
|
+
throw new Error('checkpoint_missing_intent');
|
|
1339
|
+
const prior = state.records.find(record => record.type === 'uncertain' && record.cell === cell && record.paidAttempt?.id === attempt.id && record.reason === reason);
|
|
1340
|
+
if (prior) {
|
|
1341
|
+
await this.syncRecord(prior);
|
|
1342
|
+
return;
|
|
1343
|
+
}
|
|
1344
|
+
if (state.records.some(record => record.type === 'result' && record.cell === cell && record.paidAttempt?.id === attempt.id))
|
|
1345
|
+
throw new Error('checkpoint_terminal_result_exists');
|
|
1346
|
+
if (state.successes.some(success => success.cell === cell))
|
|
1347
|
+
throw new Error('checkpoint_success_immutable');
|
|
1348
|
+
await this.append({ type: 'uncertain', cell, paidAttempt: attempt, reason }, snapshot);
|
|
1349
|
+
});
|
|
1350
|
+
}
|
|
1351
|
+
async recordResult(cell, paidAttempt, result, ownership) {
|
|
1352
|
+
const attempt = snapshotAttempt(paidAttempt), captured = snapshotResult(result);
|
|
1353
|
+
this.cell(cell);
|
|
1354
|
+
validateResult(captured, this.plan.cells.find(candidate => candidate.id === cell));
|
|
1355
|
+
return this.write(ownership, async () => {
|
|
1356
|
+
const snapshot = await this.readValidated();
|
|
1357
|
+
const state = snapshot.state;
|
|
1358
|
+
if (state.finalized)
|
|
1359
|
+
throw new Error('checkpoint_finalized');
|
|
1360
|
+
const existing = state.records.find(record => record.type === 'result' && record.cell === cell && record.paidAttempt?.id === attempt.id);
|
|
1361
|
+
if (existing) {
|
|
1362
|
+
if (existing.result?.reviewSha256 === sha256(captured.reviewBytes) && existing.result.kind === captured.kind && existing.result.chunk === captured.chunk && existing.paidAttempt?.kind === attempt.kind && (captured.kind !== 'failure' || existing.result.possiblyBilled === captured.possiblyBilled)) {
|
|
1363
|
+
await this.syncRecord(existing);
|
|
1364
|
+
return;
|
|
1365
|
+
}
|
|
1366
|
+
throw new Error('checkpoint_terminal_result_exists');
|
|
1367
|
+
}
|
|
1368
|
+
if (state.successes.some(success => success.cell === cell))
|
|
1369
|
+
throw new Error('checkpoint_success_immutable');
|
|
1370
|
+
if (!state.records.some(record => record.type === 'intent' && record.cell === cell && record.paidAttempt?.id === attempt.id && record.paidAttempt.kind === attempt.kind))
|
|
1371
|
+
throw new Error('checkpoint_missing_intent');
|
|
1372
|
+
const resultFile = resultFileFor(cell, attempt), resultPath = join(this.path, 'results', resultFile);
|
|
1373
|
+
try {
|
|
1374
|
+
await publishExclusive(this.path, resultPath, captured.reviewBytes);
|
|
1375
|
+
}
|
|
1376
|
+
catch (error) {
|
|
1377
|
+
if (error.code !== 'EEXIST')
|
|
1378
|
+
throw error;
|
|
1379
|
+
await recoverPublications(async (pending) => {
|
|
1380
|
+
if (await readPublished(resultPath, this.path, { singleLink: true }, pending) !== captured.reviewBytes)
|
|
1381
|
+
throw new Error('checkpoint_changing_source');
|
|
1382
|
+
});
|
|
1383
|
+
await syncExisting(resultPath, captured.reviewBytes, true, { singleLink: true });
|
|
1384
|
+
}
|
|
1385
|
+
await this.append({ type: 'result', cell, paidAttempt: attempt, result: { kind: captured.kind, chunk: captured.chunk, reviewSha256: sha256(captured.reviewBytes), resultFile, ...(captured.kind === 'failure' ? { possiblyBilled: captured.possiblyBilled } : {}) } }, snapshot);
|
|
1386
|
+
});
|
|
1387
|
+
}
|
|
1388
|
+
finalize(ownership) {
|
|
1389
|
+
return this.write(ownership, async (active) => {
|
|
1390
|
+
// Retained async delegates share a short phase lock. Close admission
|
|
1391
|
+
// before publishing immutable reviewer history; never await providers.
|
|
1392
|
+
let asyncPhase = false;
|
|
1393
|
+
try {
|
|
1394
|
+
await lstat(join(this.path, 'async'));
|
|
1395
|
+
asyncPhase = true;
|
|
1396
|
+
}
|
|
1397
|
+
catch (error) {
|
|
1398
|
+
if (error.code !== 'ENOENT')
|
|
1399
|
+
throw error;
|
|
1400
|
+
}
|
|
1401
|
+
if (asyncPhase) {
|
|
1402
|
+
const { sealAsyncPhase } = await import('./checkpoint-async-store.js');
|
|
1403
|
+
await sealAsyncPhase({ commonDir: this.commonDir, namespace: basename(this.path), plan: this.plan, ownership: active });
|
|
1404
|
+
}
|
|
1405
|
+
const snapshot = await this.readValidated();
|
|
1406
|
+
const state = snapshot.state;
|
|
1407
|
+
for (const record of state.records)
|
|
1408
|
+
if (record.type === 'binding')
|
|
1409
|
+
await this.syncRecord(record);
|
|
1410
|
+
if (state.finalized) {
|
|
1411
|
+
await this.syncRecord(state.records.at(-1));
|
|
1412
|
+
return;
|
|
1413
|
+
}
|
|
1414
|
+
await this.append({ type: 'finalization', finalizedDigest: sha256(canonical(state.records)) }, snapshot);
|
|
1415
|
+
});
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
//# sourceMappingURL=checkpoint.js.map
|