@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,741 @@
|
|
|
1
|
+
import { mkdir, open, readdir, readFile, rename, rm, rmdir, stat, writeFile } from 'fs/promises';
|
|
2
|
+
import { join, resolve, sep } from 'path';
|
|
3
|
+
import { resolveDataDir } from '../config/data-dir.js';
|
|
4
|
+
import { sha256Hex } from './envelope.js';
|
|
5
|
+
import { MAX_ARTIFACT_BYTES, validateRunEnvelope } from './envelope-validation.js';
|
|
6
|
+
import { buildEvent } from './events.js';
|
|
7
|
+
import { verifiedConsensusReportProblem } from './report-consistency.js';
|
|
8
|
+
/**
|
|
9
|
+
* Failed deliveries wait here (epic IO-12475, section 8.4): one directory
|
|
10
|
+
* per run under `<data dir>/outbox/`, holding the envelope, the artifacts
|
|
11
|
+
* and any events that could not be sent. Flushed at the start of every rcl
|
|
12
|
+
* command (bounded) and by `rcl telemetry flush` (to completion). Retried
|
|
13
|
+
* deliveries carry `delivery: {mode: retried, spooled_at}` and keep their
|
|
14
|
+
* original ids, so the server dedupes them.
|
|
15
|
+
*
|
|
16
|
+
* Nothing is evicted silently. Above the size cap the outbox stops spooling
|
|
17
|
+
* artifacts — envelopes and events are small and always kept — and records
|
|
18
|
+
* each affected run as a `loss` event of its own (stable id), reported on
|
|
19
|
+
* the next flush that reaches the server. A file that cannot be read is
|
|
20
|
+
* never treated as delivered: a torn file marks its entry failed, a
|
|
21
|
+
* transient read error leaves it for the next flush.
|
|
22
|
+
*
|
|
23
|
+
* Concurrency: two rcl processes may touch the outbox at once (a review
|
|
24
|
+
* finishing while another command flushes). Files are written atomically
|
|
25
|
+
* (temp + rename) and re-read before use; a flush removes only the files it
|
|
26
|
+
* delivered and never a directory that gained content meanwhile; an entry
|
|
27
|
+
* another process removed first counts as delivered by that process. The
|
|
28
|
+
* size cap is best-effort across processes, which is what a client-side
|
|
29
|
+
* courtesy cap needs to be. An entry whose spool was interrupted (no
|
|
30
|
+
* `meta.json` after a grace period) is listed as failed, never hidden.
|
|
31
|
+
*/
|
|
32
|
+
export const OUTBOX_DIR = 'outbox';
|
|
33
|
+
export const DEFAULT_OUTBOX_CAP_BYTES = 1024 * 1024 * 1024;
|
|
34
|
+
const META_FILE = 'meta.json';
|
|
35
|
+
const ENVELOPE_FILE = 'envelope.json';
|
|
36
|
+
const EVENTS_FILE = 'events.json';
|
|
37
|
+
const ARTIFACTS_DIR = 'artifacts';
|
|
38
|
+
const LOSS_DIR = 'loss';
|
|
39
|
+
const FAILED_MARKER = 'failed.json';
|
|
40
|
+
/** A loss report the server refused stays on disk under this suffix; it is never retried or counted as reported. */
|
|
41
|
+
const LOSS_REFUSED_SUFFIX = '.refused';
|
|
42
|
+
/** Loss reports go out in batches this size, well under the server's event cap. */
|
|
43
|
+
const LOSS_BATCH = 100;
|
|
44
|
+
const LOSS_FILE = /^([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\.json$/i;
|
|
45
|
+
/** A directory without `meta.json` older than this is an interrupted spool, not one in progress. */
|
|
46
|
+
export const INTERRUPTED_SPOOL_MS = 10 * 60 * 1000;
|
|
47
|
+
const ARTIFACT_FILES = { report_json: 'report_json.json', report_md: 'report_md.md' };
|
|
48
|
+
const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
49
|
+
const ENTRY_ID = /^(?:events-)?[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
50
|
+
/** 4.4.5 is the first release with the original-report check and this queued replay guard. */
|
|
51
|
+
function hasGuardedProducer(version) {
|
|
52
|
+
if (typeof version !== 'string')
|
|
53
|
+
return false;
|
|
54
|
+
const match = /^(\d+)\.(\d+)\.(\d+)(?:\+[\w.-]+)?$/.exec(version);
|
|
55
|
+
if (!match)
|
|
56
|
+
return false;
|
|
57
|
+
const [major, minor, patch] = match.slice(1).map(Number);
|
|
58
|
+
if (![major, minor, patch].every(Number.isSafeInteger))
|
|
59
|
+
return false;
|
|
60
|
+
return major > 4 || (major === 4 && (minor > 4 || (minor === 4 && patch >= 5)));
|
|
61
|
+
}
|
|
62
|
+
export class OutboxError extends Error {
|
|
63
|
+
constructor(message) {
|
|
64
|
+
super(message);
|
|
65
|
+
this.name = 'OutboxError';
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/** The entry vanished under us: another process delivered or dropped it. */
|
|
69
|
+
class EntryGone extends Error {
|
|
70
|
+
constructor() {
|
|
71
|
+
super('outbox entry removed concurrently');
|
|
72
|
+
this.name = 'EntryGone';
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
async function readJson(path) {
|
|
76
|
+
let raw;
|
|
77
|
+
try {
|
|
78
|
+
raw = await readFile(path, 'utf8');
|
|
79
|
+
}
|
|
80
|
+
catch (err) {
|
|
81
|
+
if (err.code === 'ENOENT')
|
|
82
|
+
return { kind: 'missing' };
|
|
83
|
+
return { kind: 'error', reason: err instanceof Error ? err.message : String(err) };
|
|
84
|
+
}
|
|
85
|
+
try {
|
|
86
|
+
return { kind: 'ok', value: JSON.parse(raw), raw };
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
return { kind: 'malformed' };
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/** Read no more than the declared artifact bytes, including if a file grows after stat. */
|
|
93
|
+
async function readBoundedJson(path, limit) {
|
|
94
|
+
let file;
|
|
95
|
+
try {
|
|
96
|
+
file = await open(path, 'r');
|
|
97
|
+
if ((await file.stat()).size > limit)
|
|
98
|
+
return { kind: 'oversized' };
|
|
99
|
+
const chunks = [];
|
|
100
|
+
let size = 0;
|
|
101
|
+
while (true) {
|
|
102
|
+
const chunk = Buffer.allocUnsafe(Math.min(64 * 1024, limit - size + 1));
|
|
103
|
+
const { bytesRead } = await file.read(chunk, 0, chunk.length, null);
|
|
104
|
+
if (bytesRead === 0)
|
|
105
|
+
break;
|
|
106
|
+
size += bytesRead;
|
|
107
|
+
if (size > limit)
|
|
108
|
+
return { kind: 'oversized' };
|
|
109
|
+
chunks.push(chunk.subarray(0, bytesRead));
|
|
110
|
+
}
|
|
111
|
+
const raw = Buffer.concat(chunks, size).toString('utf8');
|
|
112
|
+
try {
|
|
113
|
+
return { kind: 'ok', value: JSON.parse(raw), raw };
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
return { kind: 'malformed' };
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
catch (err) {
|
|
120
|
+
if (err.code === 'ENOENT')
|
|
121
|
+
return { kind: 'missing' };
|
|
122
|
+
return { kind: 'error', reason: err instanceof Error ? err.message : String(err) };
|
|
123
|
+
}
|
|
124
|
+
finally {
|
|
125
|
+
await file?.close().catch(() => undefined);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* The events file after a successful POST: gone when it still holds exactly
|
|
130
|
+
* what was sent; otherwise (another process queued more meanwhile) only the
|
|
131
|
+
* delivered ids leave it and the rest waits for the next flush.
|
|
132
|
+
*/
|
|
133
|
+
async function dropDeliveredEvents(path, raw, delivered) {
|
|
134
|
+
let current;
|
|
135
|
+
try {
|
|
136
|
+
current = await readFile(path, 'utf8');
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
if (current === raw) {
|
|
142
|
+
await rm(path, { force: true });
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
let parsed;
|
|
146
|
+
try {
|
|
147
|
+
parsed = JSON.parse(current);
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
return; // Mid-write or torn: leave it for the next flush to judge.
|
|
151
|
+
}
|
|
152
|
+
if (!validEvents(parsed))
|
|
153
|
+
return;
|
|
154
|
+
const sent = new Set(delivered.map((e) => e.id));
|
|
155
|
+
const remaining = parsed.filter((e) => !sent.has(e.id));
|
|
156
|
+
if (remaining.length === 0)
|
|
157
|
+
await rm(path, { force: true });
|
|
158
|
+
else
|
|
159
|
+
await writeJsonAtomic(path, remaining);
|
|
160
|
+
}
|
|
161
|
+
function isEnoent(err) {
|
|
162
|
+
return err.code === 'ENOENT';
|
|
163
|
+
}
|
|
164
|
+
async function readOptionalJson(path) {
|
|
165
|
+
const result = await readJson(path);
|
|
166
|
+
return result.kind === 'ok' ? result.value : undefined;
|
|
167
|
+
}
|
|
168
|
+
/** Write via a temp file and rename, so a reader never sees a torn file. */
|
|
169
|
+
async function writeJsonAtomic(path, value) {
|
|
170
|
+
const tmp = `${path}.${process.pid}.${Date.now()}.tmp`;
|
|
171
|
+
await writeFile(tmp, JSON.stringify(value, null, 2), { encoding: 'utf8', mode: 0o600 });
|
|
172
|
+
await rename(tmp, path);
|
|
173
|
+
}
|
|
174
|
+
async function writeTextAtomic(path, text) {
|
|
175
|
+
const tmp = `${path}.${process.pid}.${Date.now()}.tmp`;
|
|
176
|
+
await writeFile(tmp, text, { encoding: 'utf8', mode: 0o600 });
|
|
177
|
+
await rename(tmp, path);
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Bytes under `dir`, walked one entry at a time (never one `stat` per file in
|
|
181
|
+
* flight — a large outbox must not exhaust descriptors). A file that vanishes
|
|
182
|
+
* mid-walk (a concurrent rename) counts as zero; the cap is a courtesy.
|
|
183
|
+
*/
|
|
184
|
+
async function directorySize(dir) {
|
|
185
|
+
let names;
|
|
186
|
+
try {
|
|
187
|
+
names = await readdir(dir);
|
|
188
|
+
}
|
|
189
|
+
catch {
|
|
190
|
+
return 0;
|
|
191
|
+
}
|
|
192
|
+
let total = 0;
|
|
193
|
+
for (const name of names) {
|
|
194
|
+
const path = join(dir, name);
|
|
195
|
+
const info = await stat(path).catch(() => undefined);
|
|
196
|
+
if (!info)
|
|
197
|
+
continue;
|
|
198
|
+
total += info.isDirectory() ? await directorySize(path) : info.size;
|
|
199
|
+
}
|
|
200
|
+
return total;
|
|
201
|
+
}
|
|
202
|
+
function isRecord(value) {
|
|
203
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
204
|
+
}
|
|
205
|
+
function validEvents(value) {
|
|
206
|
+
return Array.isArray(value) && value.every((e) => isRecord(e) && typeof e['id'] === 'string' && typeof e['kind'] === 'string');
|
|
207
|
+
}
|
|
208
|
+
export class Outbox {
|
|
209
|
+
dir;
|
|
210
|
+
capBytes;
|
|
211
|
+
now;
|
|
212
|
+
constructor(dir = join(resolveDataDir(), OUTBOX_DIR), options = {}) {
|
|
213
|
+
this.dir = resolve(dir);
|
|
214
|
+
this.capBytes = options.capBytes ?? DEFAULT_OUTBOX_CAP_BYTES;
|
|
215
|
+
this.now = options.now ?? Date.now;
|
|
216
|
+
}
|
|
217
|
+
async totalBytes() {
|
|
218
|
+
return directorySize(this.dir);
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Entry ids are run UUIDs or `events-<uuid>`, lower-cased so one id spelt
|
|
222
|
+
* two ways is one entry; nothing else becomes a path.
|
|
223
|
+
*/
|
|
224
|
+
entryDir(id) {
|
|
225
|
+
if (!ENTRY_ID.test(id))
|
|
226
|
+
throw new OutboxError(`Not an outbox entry id: ${JSON.stringify(id)}`);
|
|
227
|
+
const dir = resolve(this.dir, id.toLowerCase());
|
|
228
|
+
if (!dir.startsWith(`${this.dir}${sep}`))
|
|
229
|
+
throw new OutboxError(`Entry escapes the outbox: ${id}`);
|
|
230
|
+
return dir;
|
|
231
|
+
}
|
|
232
|
+
/** Spool a run whose delivery failed (or whose artifacts could not be uploaded). */
|
|
233
|
+
async spoolRun(input) {
|
|
234
|
+
if (!UUID.test(input.runId))
|
|
235
|
+
throw new OutboxError(`Not a run id: ${JSON.stringify(input.runId)}`);
|
|
236
|
+
if (input.envelope.run?.id?.toLowerCase() !== input.runId.toLowerCase()) {
|
|
237
|
+
throw new OutboxError(`Envelope run id ${JSON.stringify(input.envelope.run?.id)} does not match the entry id ${input.runId}.`);
|
|
238
|
+
}
|
|
239
|
+
const dir = this.entryDir(input.runId);
|
|
240
|
+
await mkdir(join(dir, ARTIFACTS_DIR), { recursive: true, mode: 0o700 });
|
|
241
|
+
const existing = await readOptionalJson(join(dir, META_FILE));
|
|
242
|
+
const meta = {
|
|
243
|
+
kind: 'run',
|
|
244
|
+
spooled_at: existing?.spooled_at ?? new Date().toISOString(),
|
|
245
|
+
attempts: existing?.attempts ?? 0,
|
|
246
|
+
...(input.envelopeDelivered || existing?.envelope_delivered ? { envelope_delivered: true } : {}),
|
|
247
|
+
...(input.runUrl !== undefined ? { run_url: input.runUrl } : existing?.run_url ? { run_url: existing.run_url } : {}),
|
|
248
|
+
};
|
|
249
|
+
await writeJsonAtomic(join(dir, ENVELOPE_FILE), input.envelope);
|
|
250
|
+
// Events queued earlier for this run are kept; a re-spool merges by id.
|
|
251
|
+
if (input.events && input.events.length > 0) {
|
|
252
|
+
const previous = await readOptionalJson(join(dir, EVENTS_FILE));
|
|
253
|
+
const merged = new Map();
|
|
254
|
+
for (const event of validEvents(previous) ? previous : [])
|
|
255
|
+
merged.set(event.id, event);
|
|
256
|
+
for (const event of input.events)
|
|
257
|
+
merged.set(event.id, event);
|
|
258
|
+
await writeJsonAtomic(join(dir, EVENTS_FILE), [...merged.values()]);
|
|
259
|
+
}
|
|
260
|
+
const artifactsDropped = [];
|
|
261
|
+
if (input.artifacts) {
|
|
262
|
+
const wanted = new Set(Object.keys(input.artifacts));
|
|
263
|
+
// Kinds no longer pending were delivered; their stale copies go.
|
|
264
|
+
for (const [kind, file] of Object.entries(ARTIFACT_FILES)) {
|
|
265
|
+
if (!wanted.has(kind))
|
|
266
|
+
await rm(join(dir, ARTIFACTS_DIR, file), { force: true });
|
|
267
|
+
}
|
|
268
|
+
// The cap counts everything else in the outbox plus what this entry will hold.
|
|
269
|
+
let used = (await this.totalBytes()) - (await directorySize(join(dir, ARTIFACTS_DIR)));
|
|
270
|
+
for (const [kind, bytes] of Object.entries(input.artifacts)) {
|
|
271
|
+
if (bytes === undefined)
|
|
272
|
+
continue;
|
|
273
|
+
const size = Buffer.byteLength(bytes, 'utf8');
|
|
274
|
+
if (used + size > this.capBytes) {
|
|
275
|
+
artifactsDropped.push(kind);
|
|
276
|
+
await rm(join(dir, ARTIFACTS_DIR, ARTIFACT_FILES[kind]), { force: true });
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
await writeTextAtomic(join(dir, ARTIFACTS_DIR, ARTIFACT_FILES[kind]), bytes);
|
|
280
|
+
used += size;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
if (artifactsDropped.length > 0)
|
|
284
|
+
await this.recordLoss(input.runId, artifactsDropped);
|
|
285
|
+
await writeJsonAtomic(join(dir, META_FILE), meta);
|
|
286
|
+
return { spooled: true, artifactsDropped };
|
|
287
|
+
}
|
|
288
|
+
/** Spool events that belong to no run delivery of their own (converge commands). */
|
|
289
|
+
async spoolEvents(events) {
|
|
290
|
+
const first = events[0];
|
|
291
|
+
if (!first || !UUID.test(first.id))
|
|
292
|
+
throw new OutboxError('Events to spool must carry UUID ids.');
|
|
293
|
+
const id = `events-${first.id.toLowerCase()}`;
|
|
294
|
+
const dir = this.entryDir(id);
|
|
295
|
+
await mkdir(dir, { recursive: true, mode: 0o700 });
|
|
296
|
+
// A batch spooled again lands on the same entry: merge by event id so
|
|
297
|
+
// nothing already waiting is lost, and keep the entry's own history.
|
|
298
|
+
const existing = await readOptionalJson(join(dir, META_FILE));
|
|
299
|
+
const previous = await readOptionalJson(join(dir, EVENTS_FILE));
|
|
300
|
+
const merged = new Map();
|
|
301
|
+
for (const event of validEvents(previous) ? previous : [])
|
|
302
|
+
merged.set(event.id, event);
|
|
303
|
+
for (const event of events)
|
|
304
|
+
merged.set(event.id, event);
|
|
305
|
+
await writeJsonAtomic(join(dir, EVENTS_FILE), [...merged.values()]);
|
|
306
|
+
await writeJsonAtomic(join(dir, META_FILE), {
|
|
307
|
+
kind: 'events',
|
|
308
|
+
spooled_at: existing?.spooled_at ?? new Date().toISOString(),
|
|
309
|
+
attempts: existing?.attempts ?? 0,
|
|
310
|
+
});
|
|
311
|
+
return id;
|
|
312
|
+
}
|
|
313
|
+
/** One file per loss, holding the event that will report it (stable id, no read-modify-write). */
|
|
314
|
+
async recordLoss(runId, kinds) {
|
|
315
|
+
const event = buildEvent({ kind: 'loss', payload: { reason: 'outbox_over_cap', run_id: runId, kinds } });
|
|
316
|
+
await mkdir(join(this.dir, LOSS_DIR), { recursive: true, mode: 0o700 });
|
|
317
|
+
await writeJsonAtomic(join(this.dir, LOSS_DIR, `${event.id}.json`), event);
|
|
318
|
+
}
|
|
319
|
+
/** The loss events still to report. */
|
|
320
|
+
async pendingLoss() {
|
|
321
|
+
return (await this.pendingLossFiles()).map((f) => f.event);
|
|
322
|
+
}
|
|
323
|
+
/** Loss reports the server refused for good, kept on disk (`<id>.json.refused`) and never retried. */
|
|
324
|
+
async refusedLoss() {
|
|
325
|
+
try {
|
|
326
|
+
return (await readdir(join(this.dir, LOSS_DIR))).filter((n) => n.endsWith(`.json${LOSS_REFUSED_SUFFIX}`)).sort();
|
|
327
|
+
}
|
|
328
|
+
catch {
|
|
329
|
+
return [];
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Loss files whose name is `<uuid>.json` and whose event carries that very
|
|
334
|
+
* id — only such a file ever becomes a path again. Anything else in the
|
|
335
|
+
* directory is ignored, never reported and never touched.
|
|
336
|
+
*/
|
|
337
|
+
async pendingLossFiles() {
|
|
338
|
+
let names;
|
|
339
|
+
try {
|
|
340
|
+
names = (await readdir(join(this.dir, LOSS_DIR))).sort();
|
|
341
|
+
}
|
|
342
|
+
catch {
|
|
343
|
+
return [];
|
|
344
|
+
}
|
|
345
|
+
const files = [];
|
|
346
|
+
for (const name of names) {
|
|
347
|
+
const match = LOSS_FILE.exec(name);
|
|
348
|
+
if (!match)
|
|
349
|
+
continue;
|
|
350
|
+
const read = await readJson(join(this.dir, LOSS_DIR, name));
|
|
351
|
+
if (read.kind !== 'ok' || !isRecord(read.value))
|
|
352
|
+
continue;
|
|
353
|
+
const id = read.value['id'];
|
|
354
|
+
if (typeof id !== 'string' || id.toLowerCase() !== match[1].toLowerCase())
|
|
355
|
+
continue;
|
|
356
|
+
files.push({ name, event: read.value });
|
|
357
|
+
}
|
|
358
|
+
return files;
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* Every entry, or — given `only` — that one entry when it exists (a targeted
|
|
362
|
+
* flush reads nothing else). Sizes are walked only when asked for: a flush
|
|
363
|
+
* never reads them, `rcl telemetry status` does.
|
|
364
|
+
*/
|
|
365
|
+
async list(only, options = {}) {
|
|
366
|
+
let names;
|
|
367
|
+
if (only !== undefined) {
|
|
368
|
+
const dir = this.entryDir(only);
|
|
369
|
+
const exists = await stat(dir).then((info) => info.isDirectory()).catch(() => false);
|
|
370
|
+
names = exists ? [only] : [];
|
|
371
|
+
}
|
|
372
|
+
else {
|
|
373
|
+
try {
|
|
374
|
+
names = (await readdir(this.dir, { withFileTypes: true }))
|
|
375
|
+
.filter((d) => d.isDirectory() && ENTRY_ID.test(d.name))
|
|
376
|
+
.map((d) => d.name);
|
|
377
|
+
}
|
|
378
|
+
catch {
|
|
379
|
+
return [];
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
const entries = [];
|
|
383
|
+
for (const id of names.sort()) {
|
|
384
|
+
const dir = this.entryDir(id);
|
|
385
|
+
let meta = await readOptionalJson(join(dir, META_FILE));
|
|
386
|
+
let failed = await readOptionalJson(join(dir, FAILED_MARKER));
|
|
387
|
+
if (!meta) {
|
|
388
|
+
// `meta.json` is written last: a young directory is a spool in
|
|
389
|
+
// progress; an old one was interrupted and is shown, not hidden.
|
|
390
|
+
const info = await stat(dir).catch(() => undefined);
|
|
391
|
+
if (!info || this.now() - info.mtimeMs < INTERRUPTED_SPOOL_MS)
|
|
392
|
+
continue;
|
|
393
|
+
meta = { kind: id.startsWith('events-') ? 'events' : 'run', spooled_at: info.mtime.toISOString(), attempts: 0 };
|
|
394
|
+
failed = { at: new Date(this.now()).toISOString(), reason: 'spool interrupted before meta.json was written' };
|
|
395
|
+
}
|
|
396
|
+
const files = new Set(await readdir(join(dir, ARTIFACTS_DIR)).catch(() => []));
|
|
397
|
+
const events = await readOptionalJson(join(dir, EVENTS_FILE));
|
|
398
|
+
entries.push({
|
|
399
|
+
id,
|
|
400
|
+
meta,
|
|
401
|
+
bytes: options.sizes === false ? 0 : await directorySize(dir),
|
|
402
|
+
artifacts: Object.entries(ARTIFACT_FILES)
|
|
403
|
+
.filter(([, file]) => files.has(file))
|
|
404
|
+
.map(([kind]) => kind),
|
|
405
|
+
events: validEvents(events) ? events.length : 0,
|
|
406
|
+
...(failed ? { failed } : {}),
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
return entries;
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* Deliver what waits. Stops at the first unreachable response (the server
|
|
413
|
+
* is down; hammering it helps nobody) or when the deadline passes. Entries
|
|
414
|
+
* the server refuses for good are marked and skipped afterwards. Pending
|
|
415
|
+
* loss reports go out on every flush that reached the server.
|
|
416
|
+
*/
|
|
417
|
+
async flush(sink, options = {}) {
|
|
418
|
+
const now = options.now ?? this.now;
|
|
419
|
+
const started = now();
|
|
420
|
+
const pastDeadline = () => options.deadlineMs !== undefined && now() - started >= options.deadlineMs;
|
|
421
|
+
// Every request is bounded by what remains of the deadline, so a flush
|
|
422
|
+
// given five seconds cannot sit in one ten-second request.
|
|
423
|
+
const request = () => options.deadlineMs === undefined ? {} : { timeoutMs: Math.max(1, options.deadlineMs - (now() - started)) };
|
|
424
|
+
const summary = { delivered: [], remaining: [], failed: [], dropped: [] };
|
|
425
|
+
const entries = await this.list(options.runId, { sizes: false });
|
|
426
|
+
for (const entry of entries) {
|
|
427
|
+
if (entry.failed) {
|
|
428
|
+
summary.failed.push({ id: entry.id, reason: entry.failed.reason });
|
|
429
|
+
continue;
|
|
430
|
+
}
|
|
431
|
+
if (summary.stopped) {
|
|
432
|
+
summary.remaining.push(entry.id);
|
|
433
|
+
continue;
|
|
434
|
+
}
|
|
435
|
+
if (pastDeadline()) {
|
|
436
|
+
summary.stopped = 'deadline';
|
|
437
|
+
summary.remaining.push(entry.id);
|
|
438
|
+
continue;
|
|
439
|
+
}
|
|
440
|
+
const result = await this.flushEntry(sink, entry, pastDeadline, request).catch((err) => {
|
|
441
|
+
if (err instanceof EntryGone)
|
|
442
|
+
return { kind: 'delivered' };
|
|
443
|
+
throw err;
|
|
444
|
+
});
|
|
445
|
+
switch (result.kind) {
|
|
446
|
+
case 'delivered':
|
|
447
|
+
summary.delivered.push(entry.id);
|
|
448
|
+
break;
|
|
449
|
+
case 'failed':
|
|
450
|
+
summary.failed.push({ id: entry.id, reason: result.reason });
|
|
451
|
+
break;
|
|
452
|
+
case 'dropped':
|
|
453
|
+
summary.dropped.push({ id: entry.id, reason: result.reason });
|
|
454
|
+
break;
|
|
455
|
+
case 'retry':
|
|
456
|
+
summary.remaining.push(entry.id);
|
|
457
|
+
break;
|
|
458
|
+
case 'unavailable':
|
|
459
|
+
summary.stopped = 'unavailable';
|
|
460
|
+
summary.remaining.push(entry.id);
|
|
461
|
+
break;
|
|
462
|
+
case 'deadline':
|
|
463
|
+
summary.stopped = 'deadline';
|
|
464
|
+
summary.remaining.push(entry.id);
|
|
465
|
+
break;
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
if (summary.stopped !== 'unavailable' && options.runId === undefined) {
|
|
469
|
+
const loss = await this.reportLoss(sink, pastDeadline, request);
|
|
470
|
+
if (loss.kind === 'done' && loss.reported > 0)
|
|
471
|
+
summary.lossReported = loss.reported;
|
|
472
|
+
if (loss.kind !== 'done' && !summary.stopped)
|
|
473
|
+
summary.stopped = loss.kind;
|
|
474
|
+
const pending = (await this.pendingLossFiles()).length;
|
|
475
|
+
if (pending > 0)
|
|
476
|
+
summary.lossPending = pending;
|
|
477
|
+
}
|
|
478
|
+
return summary;
|
|
479
|
+
}
|
|
480
|
+
async flushEntry(sink, entry, pastDeadline, request) {
|
|
481
|
+
const dir = this.entryDir(entry.id);
|
|
482
|
+
if (entry.meta.kind !== 'run' && entry.meta.kind !== 'events') {
|
|
483
|
+
return this.markFailed(dir, `meta.json: unknown entry kind ${JSON.stringify(entry.meta.kind)}`);
|
|
484
|
+
}
|
|
485
|
+
const meta = { ...entry.meta, attempts: entry.meta.attempts + 1 };
|
|
486
|
+
const remember = async (error) => {
|
|
487
|
+
if (error !== undefined)
|
|
488
|
+
meta.last_error = error;
|
|
489
|
+
try {
|
|
490
|
+
await writeJsonAtomic(join(dir, META_FILE), meta);
|
|
491
|
+
}
|
|
492
|
+
catch (err) {
|
|
493
|
+
if (isEnoent(err))
|
|
494
|
+
throw new EntryGone();
|
|
495
|
+
throw err;
|
|
496
|
+
}
|
|
497
|
+
};
|
|
498
|
+
await remember();
|
|
499
|
+
if (meta.kind === 'run' && !meta.envelope_delivered) {
|
|
500
|
+
const read = await readJson(join(dir, ENVELOPE_FILE));
|
|
501
|
+
if (read.kind === 'error') {
|
|
502
|
+
await remember(`envelope unreadable: ${read.reason}`);
|
|
503
|
+
return { kind: 'retry' };
|
|
504
|
+
}
|
|
505
|
+
if (read.kind !== 'ok' || !isRecord(read.value) || !isRecord(read.value['run'])) {
|
|
506
|
+
return this.markFailed(dir, 'envelope.json missing or malformed');
|
|
507
|
+
}
|
|
508
|
+
const envelope = read.value;
|
|
509
|
+
envelope.delivery = { mode: 'retried', spooled_at: meta.spooled_at };
|
|
510
|
+
if (envelope.run.gating?.mode === 'verified-consensus') {
|
|
511
|
+
const invalid = validateRunEnvelope(envelope);
|
|
512
|
+
if (invalid.length > 0)
|
|
513
|
+
return this.markFailed(dir, `queued verified-consensus envelope invalid: ${invalid[0].path}`);
|
|
514
|
+
const declared = envelope.artifacts_declared.find(artifact => artifact.kind === 'report_json');
|
|
515
|
+
const report = await readBoundedJson(join(dir, ARTIFACTS_DIR, ARTIFACT_FILES.report_json), Math.min(declared.bytes, MAX_ARTIFACT_BYTES));
|
|
516
|
+
if (report.kind === 'error') {
|
|
517
|
+
await remember(`report_json unreadable: ${report.reason}`);
|
|
518
|
+
return { kind: 'retry' };
|
|
519
|
+
}
|
|
520
|
+
if (report.kind === 'oversized')
|
|
521
|
+
return this.markFailed(dir, 'report_json exceeds declared or maximum artifact bytes');
|
|
522
|
+
if (report.kind === 'malformed')
|
|
523
|
+
return this.markFailed(dir, 'report_json malformed; verified-consensus source cannot be checked');
|
|
524
|
+
if (report.kind === 'ok') {
|
|
525
|
+
if (declared.sha256 !== sha256Hex(report.raw) || declared.bytes !== Buffer.byteLength(report.raw, 'utf8')) {
|
|
526
|
+
return this.markFailed(dir, 'report_json digest or size differs from the queued envelope');
|
|
527
|
+
}
|
|
528
|
+
const problem = verifiedConsensusReportProblem(report.value, envelope, report.raw);
|
|
529
|
+
if (problem)
|
|
530
|
+
return this.markFailed(dir, problem);
|
|
531
|
+
}
|
|
532
|
+
else if (!hasGuardedProducer(envelope.run.rcl_version)) {
|
|
533
|
+
return this.markFailed(dir, 'report_json unavailable; queued verified-consensus producer or envelope cannot prove original gating labels');
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
if (pastDeadline())
|
|
537
|
+
return { kind: 'deadline' };
|
|
538
|
+
const outcome = await sink.postRun(envelope, request());
|
|
539
|
+
switch (outcome.kind) {
|
|
540
|
+
case 'ok':
|
|
541
|
+
meta.envelope_delivered = true;
|
|
542
|
+
meta.run_url = outcome.value.url;
|
|
543
|
+
await remember();
|
|
544
|
+
break;
|
|
545
|
+
case 'unavailable':
|
|
546
|
+
await remember(outcome.reason);
|
|
547
|
+
return { kind: 'unavailable' };
|
|
548
|
+
case 'disabled':
|
|
549
|
+
// The organization switched evidence off; there is nothing to keep waiting for.
|
|
550
|
+
await this.drop(dir);
|
|
551
|
+
return { kind: 'dropped', reason: outcome.message || outcome.reason };
|
|
552
|
+
case 'conflict':
|
|
553
|
+
// The server holds this run id with a different report. The events
|
|
554
|
+
// queued here name that run id too, so they must not be attached to
|
|
555
|
+
// whatever the server has; the whole entry stays for inspection.
|
|
556
|
+
return this.markFailed(dir, `conflict: ${outcome.message}`);
|
|
557
|
+
case 'rejected':
|
|
558
|
+
return this.markFailed(dir, `HTTP ${outcome.httpStatus} ${outcome.error} ${outcome.message}`.trim());
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
if (meta.kind === 'run') {
|
|
562
|
+
for (const kind of entry.artifacts) {
|
|
563
|
+
if (pastDeadline())
|
|
564
|
+
return { kind: 'deadline' };
|
|
565
|
+
const path = join(dir, ARTIFACTS_DIR, ARTIFACT_FILES[kind]);
|
|
566
|
+
let bytes;
|
|
567
|
+
try {
|
|
568
|
+
bytes = await readFile(path, 'utf8');
|
|
569
|
+
}
|
|
570
|
+
catch (err) {
|
|
571
|
+
if (err.code === 'ENOENT')
|
|
572
|
+
continue; // delivered by a concurrent flush
|
|
573
|
+
await remember(`artifact ${kind} unreadable: ${err instanceof Error ? err.message : String(err)}`);
|
|
574
|
+
return { kind: 'retry' };
|
|
575
|
+
}
|
|
576
|
+
const outcome = await sink.putArtifact(entry.id, kind, bytes, request());
|
|
577
|
+
switch (outcome.kind) {
|
|
578
|
+
case 'ok': {
|
|
579
|
+
// Remove exactly the bytes that were uploaded; a re-spool meanwhile stays.
|
|
580
|
+
const current = await readFile(path, 'utf8').catch(() => undefined);
|
|
581
|
+
if (current === bytes)
|
|
582
|
+
await rm(path, { force: true });
|
|
583
|
+
break;
|
|
584
|
+
}
|
|
585
|
+
case 'disabled':
|
|
586
|
+
if (outcome.reason === 'reviews_disabled') {
|
|
587
|
+
await this.drop(dir);
|
|
588
|
+
return { kind: 'dropped', reason: outcome.message || outcome.reason };
|
|
589
|
+
}
|
|
590
|
+
// Artifacts are capped for the org; the envelope stands.
|
|
591
|
+
await rm(path, { force: true });
|
|
592
|
+
break;
|
|
593
|
+
case 'unavailable':
|
|
594
|
+
await remember(outcome.reason);
|
|
595
|
+
return { kind: 'unavailable' };
|
|
596
|
+
case 'conflict':
|
|
597
|
+
case 'rejected':
|
|
598
|
+
return this.markFailed(dir, `artifact ${kind}: ${outcome.kind === 'conflict' ? outcome.message : `HTTP ${outcome.httpStatus} ${outcome.error}`}`);
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
const events = await readJson(join(dir, EVENTS_FILE));
|
|
603
|
+
if (events.kind === 'error') {
|
|
604
|
+
await remember(`events unreadable: ${events.reason}`);
|
|
605
|
+
return { kind: 'retry' };
|
|
606
|
+
}
|
|
607
|
+
if (events.kind === 'malformed' || (events.kind === 'ok' && !validEvents(events.value))) {
|
|
608
|
+
return this.markFailed(dir, 'events.json malformed');
|
|
609
|
+
}
|
|
610
|
+
if (events.kind === 'missing' && meta.kind === 'events') {
|
|
611
|
+
// events.json is written before meta.json, so a missing file means an
|
|
612
|
+
// earlier pass delivered and dropped it and only the directory stayed
|
|
613
|
+
// (something else was in it at the time): finish, do not fail it.
|
|
614
|
+
return (await this.finish(dir, meta)) ? { kind: 'delivered' } : { kind: 'retry' };
|
|
615
|
+
}
|
|
616
|
+
if (events.kind === 'ok' && validEvents(events.value) && events.value.length > 0) {
|
|
617
|
+
if (pastDeadline())
|
|
618
|
+
return { kind: 'deadline' };
|
|
619
|
+
const outcome = await sink.postEvents(events.value, request());
|
|
620
|
+
switch (outcome.kind) {
|
|
621
|
+
case 'ok':
|
|
622
|
+
await dropDeliveredEvents(join(dir, EVENTS_FILE), events.raw, events.value);
|
|
623
|
+
break;
|
|
624
|
+
case 'unavailable':
|
|
625
|
+
await remember(outcome.reason);
|
|
626
|
+
return { kind: 'unavailable' };
|
|
627
|
+
case 'disabled':
|
|
628
|
+
await this.drop(dir);
|
|
629
|
+
return { kind: 'dropped', reason: outcome.message || outcome.reason };
|
|
630
|
+
case 'conflict':
|
|
631
|
+
case 'rejected':
|
|
632
|
+
return this.markFailed(dir, `events: ${outcome.kind === 'conflict' ? outcome.message : `HTTP ${outcome.httpStatus} ${outcome.error} ${outcome.message}`.trim()}`);
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
if (events.kind === 'ok' && Array.isArray(events.value) && events.value.length === 0) {
|
|
636
|
+
await dropDeliveredEvents(join(dir, EVENTS_FILE), events.raw, []);
|
|
637
|
+
}
|
|
638
|
+
// Whatever arrived while flushing stays for the next flush; the entry
|
|
639
|
+
// is only "delivered" once nothing of it remains.
|
|
640
|
+
return (await this.finish(dir, meta)) ? { kind: 'delivered' } : { kind: 'retry' };
|
|
641
|
+
}
|
|
642
|
+
/**
|
|
643
|
+
* Everything this flush read has been delivered: remove exactly those
|
|
644
|
+
* files, then the directory — which stays, listed, if another process
|
|
645
|
+
* put something new into it meanwhile. Returns whether the directory went.
|
|
646
|
+
*/
|
|
647
|
+
async finish(dir, meta) {
|
|
648
|
+
await rm(join(dir, ENVELOPE_FILE), { force: true });
|
|
649
|
+
await rm(join(dir, FAILED_MARKER), { force: true });
|
|
650
|
+
await this.sweepTempFiles(dir);
|
|
651
|
+
await rmdir(join(dir, ARTIFACTS_DIR)).catch(() => undefined);
|
|
652
|
+
await rm(join(dir, META_FILE), { force: true });
|
|
653
|
+
try {
|
|
654
|
+
await rmdir(dir);
|
|
655
|
+
return true;
|
|
656
|
+
}
|
|
657
|
+
catch (err) {
|
|
658
|
+
if (isEnoent(err))
|
|
659
|
+
return true;
|
|
660
|
+
if (err.code !== 'ENOTEMPTY')
|
|
661
|
+
throw err;
|
|
662
|
+
// Something arrived while flushing: keep the entry visible for the next flush.
|
|
663
|
+
await writeJsonAtomic(join(dir, META_FILE), { ...meta, envelope_delivered: true }).catch(() => undefined);
|
|
664
|
+
return false;
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
/** Temp files a crashed writer left behind (a live writer's are younger than the grace period). */
|
|
668
|
+
async sweepTempFiles(dir) {
|
|
669
|
+
for (const sub of [dir, join(dir, ARTIFACTS_DIR)]) {
|
|
670
|
+
const names = await readdir(sub).catch(() => []);
|
|
671
|
+
for (const name of names.filter((n) => n.endsWith('.tmp'))) {
|
|
672
|
+
const path = join(sub, name);
|
|
673
|
+
const info = await stat(path).catch(() => undefined);
|
|
674
|
+
if (info && this.now() - info.mtimeMs >= INTERRUPTED_SPOOL_MS)
|
|
675
|
+
await rm(path, { force: true });
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
/** The organization switched evidence off: nothing in this entry will ever be wanted. */
|
|
680
|
+
async drop(dir) {
|
|
681
|
+
await rm(dir, { recursive: true, force: true });
|
|
682
|
+
}
|
|
683
|
+
async markFailed(dir, reason) {
|
|
684
|
+
try {
|
|
685
|
+
await writeJsonAtomic(join(dir, FAILED_MARKER), { at: new Date().toISOString(), reason });
|
|
686
|
+
}
|
|
687
|
+
catch (err) {
|
|
688
|
+
if (isEnoent(err))
|
|
689
|
+
throw new EntryGone();
|
|
690
|
+
throw err;
|
|
691
|
+
}
|
|
692
|
+
return { kind: 'failed', reason };
|
|
693
|
+
}
|
|
694
|
+
/**
|
|
695
|
+
* Report pending losses with their stable ids, in bounded batches. A batch's
|
|
696
|
+
* files go once the server has taken every event in it (inserted or
|
|
697
|
+
* already known), or when the organization has switched evidence off
|
|
698
|
+
* (there is nobody to report to). A batch the server refuses is kept under
|
|
699
|
+
* a `.refused` suffix — visible on disk and in `rcl telemetry status`,
|
|
700
|
+
* never retried, never counted. An unreachable server or an expired
|
|
701
|
+
* deadline ends the pass with the rest still pending.
|
|
702
|
+
*/
|
|
703
|
+
async reportLoss(sink, pastDeadline, request) {
|
|
704
|
+
const files = await this.pendingLossFiles();
|
|
705
|
+
let reported = 0;
|
|
706
|
+
for (let i = 0; i < files.length; i += LOSS_BATCH) {
|
|
707
|
+
if (pastDeadline())
|
|
708
|
+
return { kind: 'deadline' };
|
|
709
|
+
const batch = files.slice(i, i + LOSS_BATCH);
|
|
710
|
+
const outcome = await sink.postEvents(batch.map((f) => f.event), request());
|
|
711
|
+
switch (outcome.kind) {
|
|
712
|
+
case 'ok':
|
|
713
|
+
// The sink only reads `ok` when the receipt accounts for every event
|
|
714
|
+
// sent (inserted or already known), so the whole batch is done.
|
|
715
|
+
for (const f of batch)
|
|
716
|
+
await rm(join(this.dir, LOSS_DIR, f.name), { force: true }).catch(() => undefined);
|
|
717
|
+
reported += batch.length;
|
|
718
|
+
break;
|
|
719
|
+
case 'disabled':
|
|
720
|
+
for (const f of batch)
|
|
721
|
+
await rm(join(this.dir, LOSS_DIR, f.name), { force: true }).catch(() => undefined);
|
|
722
|
+
break;
|
|
723
|
+
case 'conflict':
|
|
724
|
+
case 'rejected':
|
|
725
|
+
for (const f of batch) {
|
|
726
|
+
const path = join(this.dir, LOSS_DIR, f.name);
|
|
727
|
+
await rename(path, `${path}${LOSS_REFUSED_SUFFIX}`).catch(() => undefined);
|
|
728
|
+
}
|
|
729
|
+
break;
|
|
730
|
+
case 'unavailable':
|
|
731
|
+
return { kind: 'unavailable' };
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
return { kind: 'done', reported };
|
|
735
|
+
}
|
|
736
|
+
/** Remove one entry by id (validated like every other id). */
|
|
737
|
+
async remove(id) {
|
|
738
|
+
await rm(this.entryDir(id), { recursive: true, force: true });
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
//# sourceMappingURL=outbox.js.map
|