@smthrs/engine-store 0.0.0-stage → 1.0.0-rc.3
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/CHANGELOG.md +247 -0
- package/LICENSE +21 -0
- package/README.md +146 -2
- package/dist/cjs/ArtifactGc.d.ts +235 -0
- package/dist/cjs/ArtifactGc.d.ts.map +1 -0
- package/dist/cjs/ArtifactGc.js +183 -0
- package/dist/cjs/ArtifactGc.js.map +7 -0
- package/dist/cjs/ArtifactSync.d.ts +164 -0
- package/dist/cjs/ArtifactSync.d.ts.map +1 -0
- package/dist/cjs/ArtifactSync.js +144 -0
- package/dist/cjs/ArtifactSync.js.map +7 -0
- package/dist/cjs/CacheSync.d.ts +112 -0
- package/dist/cjs/CacheSync.d.ts.map +1 -0
- package/dist/cjs/CacheSync.js +64 -0
- package/dist/cjs/CacheSync.js.map +7 -0
- package/dist/cjs/DeferredClockFold.d.ts +354 -0
- package/dist/cjs/DeferredClockFold.d.ts.map +1 -0
- package/dist/cjs/DeferredClockFold.js +335 -0
- package/dist/cjs/DeferredClockFold.js.map +7 -0
- package/dist/cjs/DisasterRecovery.d.ts +431 -0
- package/dist/cjs/DisasterRecovery.d.ts.map +1 -0
- package/dist/cjs/DisasterRecovery.js +453 -0
- package/dist/cjs/DisasterRecovery.js.map +7 -0
- package/dist/cjs/DurableEngineState.d.ts +776 -0
- package/dist/cjs/DurableEngineState.d.ts.map +1 -0
- package/dist/cjs/DurableEngineState.js +1536 -0
- package/dist/cjs/DurableEngineState.js.map +7 -0
- package/dist/cjs/EngineStore.d.ts +158 -0
- package/dist/cjs/EngineStore.d.ts.map +1 -0
- package/dist/cjs/EngineStore.js +398 -0
- package/dist/cjs/EngineStore.js.map +7 -0
- package/dist/cjs/EngineStoreMetrics.d.ts +276 -0
- package/dist/cjs/EngineStoreMetrics.d.ts.map +1 -0
- package/dist/cjs/EngineStoreMetrics.js +135 -0
- package/dist/cjs/EngineStoreMetrics.js.map +7 -0
- package/dist/cjs/Errors.d.ts +22 -0
- package/dist/cjs/Errors.d.ts.map +1 -0
- package/dist/cjs/Errors.js +39 -0
- package/dist/cjs/Errors.js.map +7 -0
- package/dist/cjs/EventTypes.d.ts +27 -0
- package/dist/cjs/EventTypes.d.ts.map +1 -0
- package/dist/cjs/EventTypes.js +37 -0
- package/dist/cjs/EventTypes.js.map +7 -0
- package/dist/cjs/ExecutionFacts.d.ts +59 -0
- package/dist/cjs/ExecutionFacts.d.ts.map +1 -0
- package/dist/cjs/ExecutionFacts.js +123 -0
- package/dist/cjs/ExecutionFacts.js.map +7 -0
- package/dist/cjs/ExecutionSnapshot.d.ts +117 -0
- package/dist/cjs/ExecutionSnapshot.d.ts.map +1 -0
- package/dist/cjs/ExecutionSnapshot.js +111 -0
- package/dist/cjs/ExecutionSnapshot.js.map +7 -0
- package/dist/cjs/Inconsistency.d.ts +154 -0
- package/dist/cjs/Inconsistency.d.ts.map +1 -0
- package/dist/cjs/Inconsistency.js +135 -0
- package/dist/cjs/Inconsistency.js.map +7 -0
- package/dist/cjs/Migrations.d.ts +56 -0
- package/dist/cjs/Migrations.d.ts.map +1 -0
- package/dist/cjs/Migrations.js +78 -0
- package/dist/cjs/Migrations.js.map +7 -0
- package/dist/cjs/OwnerIdentity.d.ts +91 -0
- package/dist/cjs/OwnerIdentity.d.ts.map +1 -0
- package/dist/cjs/OwnerIdentity.js +65 -0
- package/dist/cjs/OwnerIdentity.js.map +7 -0
- package/dist/cjs/PlanInputStore.d.ts +110 -0
- package/dist/cjs/PlanInputStore.d.ts.map +1 -0
- package/dist/cjs/PlanInputStore.js +224 -0
- package/dist/cjs/PlanInputStore.js.map +7 -0
- package/dist/cjs/PlanMergeStore.d.ts +113 -0
- package/dist/cjs/PlanMergeStore.d.ts.map +1 -0
- package/dist/cjs/PlanMergeStore.js +255 -0
- package/dist/cjs/PlanMergeStore.js.map +7 -0
- package/dist/cjs/PlanScheduler.d.ts +363 -0
- package/dist/cjs/PlanScheduler.d.ts.map +1 -0
- package/dist/cjs/PlanScheduler.js +1090 -0
- package/dist/cjs/PlanScheduler.js.map +7 -0
- package/dist/cjs/Reconciliation.d.ts +175 -0
- package/dist/cjs/Reconciliation.d.ts.map +1 -0
- package/dist/cjs/Reconciliation.js +119 -0
- package/dist/cjs/Reconciliation.js.map +7 -0
- package/dist/cjs/ReplayOnly.d.ts +91 -0
- package/dist/cjs/ReplayOnly.d.ts.map +1 -0
- package/dist/cjs/ReplayOnly.js +52 -0
- package/dist/cjs/ReplayOnly.js.map +7 -0
- package/dist/cjs/Retention.d.ts +113 -0
- package/dist/cjs/Retention.d.ts.map +1 -0
- package/dist/cjs/Retention.js +99 -0
- package/dist/cjs/Retention.js.map +7 -0
- package/dist/cjs/RunCatalogRead.d.ts +17 -0
- package/dist/cjs/RunCatalogRead.d.ts.map +1 -0
- package/dist/cjs/RunCatalogRead.js +33 -0
- package/dist/cjs/RunCatalogRead.js.map +7 -0
- package/dist/cjs/RunChangeFeed.d.ts +75 -0
- package/dist/cjs/RunChangeFeed.d.ts.map +1 -0
- package/dist/cjs/RunChangeFeed.js +96 -0
- package/dist/cjs/RunChangeFeed.js.map +7 -0
- package/dist/cjs/RunState.d.ts +116 -0
- package/dist/cjs/RunState.d.ts.map +1 -0
- package/dist/cjs/RunState.js +76 -0
- package/dist/cjs/RunState.js.map +7 -0
- package/dist/cjs/Selection.d.ts +495 -0
- package/dist/cjs/Selection.d.ts.map +1 -0
- package/dist/cjs/Selection.js +288 -0
- package/dist/cjs/Selection.js.map +7 -0
- package/dist/cjs/SelectionStore.d.ts +141 -0
- package/dist/cjs/SelectionStore.d.ts.map +1 -0
- package/dist/cjs/SelectionStore.js +229 -0
- package/dist/cjs/SelectionStore.js.map +7 -0
- package/dist/cjs/StepBoundary.d.ts +501 -0
- package/dist/cjs/StepBoundary.d.ts.map +1 -0
- package/dist/cjs/StepBoundary.js +619 -0
- package/dist/cjs/StepBoundary.js.map +7 -0
- package/dist/cjs/StepCacheFold.d.ts +148 -0
- package/dist/cjs/StepCacheFold.d.ts.map +1 -0
- package/dist/cjs/StepCacheFold.js +131 -0
- package/dist/cjs/StepCacheFold.js.map +7 -0
- package/dist/cjs/StepSandbox.d.ts +78 -0
- package/dist/cjs/StepSandbox.d.ts.map +1 -0
- package/dist/cjs/StepSandbox.js +75 -0
- package/dist/cjs/StepSandbox.js.map +7 -0
- package/dist/cjs/WakeBus.d.ts +116 -0
- package/dist/cjs/WakeBus.d.ts.map +1 -0
- package/dist/cjs/WakeBus.js +89 -0
- package/dist/cjs/WakeBus.js.map +7 -0
- package/dist/cjs/WorkspaceSandbox.d.ts +619 -0
- package/dist/cjs/WorkspaceSandbox.d.ts.map +1 -0
- package/dist/cjs/WorkspaceSandbox.js +711 -0
- package/dist/cjs/WorkspaceSandbox.js.map +7 -0
- package/dist/cjs/index.d.ts +157 -0
- package/dist/cjs/index.d.ts.map +1 -0
- package/dist/cjs/index.js +95 -0
- package/dist/cjs/index.js.map +7 -0
- package/dist/cjs/internal/ActionPersistence.d.ts +276 -0
- package/dist/cjs/internal/ActionPersistence.d.ts.map +1 -0
- package/dist/cjs/internal/ActionPersistence.js +1395 -0
- package/dist/cjs/internal/ActionPersistence.js.map +7 -0
- package/dist/cjs/internal/ArtifactRoots.d.ts +38 -0
- package/dist/cjs/internal/ArtifactRoots.d.ts.map +1 -0
- package/dist/cjs/internal/ArtifactRoots.js +89 -0
- package/dist/cjs/internal/ArtifactRoots.js.map +7 -0
- package/dist/cjs/internal/AttemptAdmission.d.ts +53 -0
- package/dist/cjs/internal/AttemptAdmission.d.ts.map +1 -0
- package/dist/cjs/internal/AttemptAdmission.js +52 -0
- package/dist/cjs/internal/AttemptAdmission.js.map +7 -0
- package/dist/cjs/internal/AttemptLifecycle.d.ts +245 -0
- package/dist/cjs/internal/AttemptLifecycle.d.ts.map +1 -0
- package/dist/cjs/internal/AttemptLifecycle.js +112 -0
- package/dist/cjs/internal/AttemptLifecycle.js.map +7 -0
- package/dist/cjs/internal/AttemptProbe.d.ts +53 -0
- package/dist/cjs/internal/AttemptProbe.d.ts.map +1 -0
- package/dist/cjs/internal/AttemptProbe.js +66 -0
- package/dist/cjs/internal/AttemptProbe.js.map +7 -0
- package/dist/cjs/internal/CacheAdmission.d.ts +79 -0
- package/dist/cjs/internal/CacheAdmission.d.ts.map +1 -0
- package/dist/cjs/internal/CacheAdmission.js +71 -0
- package/dist/cjs/internal/CacheAdmission.js.map +7 -0
- package/dist/cjs/internal/CacheAgeHistory.d.ts +27 -0
- package/dist/cjs/internal/CacheAgeHistory.d.ts.map +1 -0
- package/dist/cjs/internal/CacheAgeHistory.js +61 -0
- package/dist/cjs/internal/CacheAgeHistory.js.map +7 -0
- package/dist/cjs/internal/CacheAgeVerdicts.d.ts +15 -0
- package/dist/cjs/internal/CacheAgeVerdicts.d.ts.map +1 -0
- package/dist/cjs/internal/CacheAgeVerdicts.js +73 -0
- package/dist/cjs/internal/CacheAgeVerdicts.js.map +7 -0
- package/dist/cjs/internal/CacheOutputPolicy.d.ts +24 -0
- package/dist/cjs/internal/CacheOutputPolicy.d.ts.map +1 -0
- package/dist/cjs/internal/CacheOutputPolicy.js +75 -0
- package/dist/cjs/internal/CacheOutputPolicy.js.map +7 -0
- package/dist/cjs/internal/CachePublication.d.ts +97 -0
- package/dist/cjs/internal/CachePublication.d.ts.map +1 -0
- package/dist/cjs/internal/CachePublication.js +80 -0
- package/dist/cjs/internal/CachePublication.js.map +7 -0
- package/dist/cjs/internal/CallFacts.d.ts +15 -0
- package/dist/cjs/internal/CallFacts.d.ts.map +1 -0
- package/dist/cjs/internal/CallFacts.js +97 -0
- package/dist/cjs/internal/CallFacts.js.map +7 -0
- package/dist/cjs/internal/CopiedRecord.d.ts +61 -0
- package/dist/cjs/internal/CopiedRecord.d.ts.map +1 -0
- package/dist/cjs/internal/CopiedRecord.js +134 -0
- package/dist/cjs/internal/CopiedRecord.js.map +7 -0
- package/dist/cjs/internal/DeferredPersistence.d.ts +108 -0
- package/dist/cjs/internal/DeferredPersistence.d.ts.map +1 -0
- package/dist/cjs/internal/DeferredPersistence.js +270 -0
- package/dist/cjs/internal/DeferredPersistence.js.map +7 -0
- package/dist/cjs/internal/EffectRecords.d.ts +92 -0
- package/dist/cjs/internal/EffectRecords.d.ts.map +1 -0
- package/dist/cjs/internal/EffectRecords.js +63 -0
- package/dist/cjs/internal/EffectRecords.js.map +7 -0
- package/dist/cjs/internal/EngineJj.d.ts +30 -0
- package/dist/cjs/internal/EngineJj.d.ts.map +1 -0
- package/dist/cjs/internal/EngineJj.js +42 -0
- package/dist/cjs/internal/EngineJj.js.map +7 -0
- package/dist/cjs/internal/EngineStateSchema.d.ts +68 -0
- package/dist/cjs/internal/EngineStateSchema.d.ts.map +1 -0
- package/dist/cjs/internal/EngineStateSchema.js +58 -0
- package/dist/cjs/internal/EngineStateSchema.js.map +7 -0
- package/dist/cjs/internal/ExecutionSnapshotRead.d.ts +162 -0
- package/dist/cjs/internal/ExecutionSnapshotRead.d.ts.map +1 -0
- package/dist/cjs/internal/ExecutionSnapshotRead.js +199 -0
- package/dist/cjs/internal/ExecutionSnapshotRead.js.map +7 -0
- package/dist/cjs/internal/ExecutionSnapshotRelations.d.ts +39 -0
- package/dist/cjs/internal/ExecutionSnapshotRelations.d.ts.map +1 -0
- package/dist/cjs/internal/ExecutionSnapshotRelations.js +105 -0
- package/dist/cjs/internal/ExecutionSnapshotRelations.js.map +7 -0
- package/dist/cjs/internal/ExitEncoding.d.ts +180 -0
- package/dist/cjs/internal/ExitEncoding.d.ts.map +1 -0
- package/dist/cjs/internal/ExitEncoding.js +236 -0
- package/dist/cjs/internal/ExitEncoding.js.map +7 -0
- package/dist/cjs/internal/FileBoundarySnapshot.d.ts +14 -0
- package/dist/cjs/internal/FileBoundarySnapshot.d.ts.map +1 -0
- package/dist/cjs/internal/FileBoundarySnapshot.js +51 -0
- package/dist/cjs/internal/FileBoundarySnapshot.js.map +7 -0
- package/dist/cjs/internal/FileEnumeration.d.ts +127 -0
- package/dist/cjs/internal/FileEnumeration.d.ts.map +1 -0
- package/dist/cjs/internal/FileEnumeration.js +194 -0
- package/dist/cjs/internal/FileEnumeration.js.map +7 -0
- package/dist/cjs/internal/HostReflection.d.ts +80 -0
- package/dist/cjs/internal/HostReflection.d.ts.map +1 -0
- package/dist/cjs/internal/HostReflection.js +43 -0
- package/dist/cjs/internal/HostReflection.js.map +7 -0
- package/dist/cjs/internal/JournalRecords.d.ts +325 -0
- package/dist/cjs/internal/JournalRecords.d.ts.map +1 -0
- package/dist/cjs/internal/JournalRecords.js +122 -0
- package/dist/cjs/internal/JournalRecords.js.map +7 -0
- package/dist/cjs/internal/NodeJournal.d.ts +157 -0
- package/dist/cjs/internal/NodeJournal.d.ts.map +1 -0
- package/dist/cjs/internal/NodeJournal.js +192 -0
- package/dist/cjs/internal/NodeJournal.js.map +7 -0
- package/dist/cjs/internal/Ordering.d.ts +14 -0
- package/dist/cjs/internal/Ordering.d.ts.map +1 -0
- package/dist/cjs/internal/Ordering.js +25 -0
- package/dist/cjs/internal/Ordering.js.map +7 -0
- package/dist/cjs/internal/PlanInputs.d.ts +14 -0
- package/dist/cjs/internal/PlanInputs.d.ts.map +1 -0
- package/dist/cjs/internal/PlanInputs.js +78 -0
- package/dist/cjs/internal/PlanInputs.js.map +7 -0
- package/dist/cjs/internal/PlanMerges.d.ts +178 -0
- package/dist/cjs/internal/PlanMerges.d.ts.map +1 -0
- package/dist/cjs/internal/PlanMerges.js +125 -0
- package/dist/cjs/internal/PlanMerges.js.map +7 -0
- package/dist/cjs/internal/ProvenanceSlot.d.ts +30 -0
- package/dist/cjs/internal/ProvenanceSlot.d.ts.map +1 -0
- package/dist/cjs/internal/ProvenanceSlot.js +53 -0
- package/dist/cjs/internal/ProvenanceSlot.js.map +7 -0
- package/dist/cjs/internal/ResultEnvelope.d.ts +88 -0
- package/dist/cjs/internal/ResultEnvelope.d.ts.map +1 -0
- package/dist/cjs/internal/ResultEnvelope.js +90 -0
- package/dist/cjs/internal/ResultEnvelope.js.map +7 -0
- package/dist/cjs/internal/RetentionOps.d.ts +414 -0
- package/dist/cjs/internal/RetentionOps.d.ts.map +1 -0
- package/dist/cjs/internal/RetentionOps.js +354 -0
- package/dist/cjs/internal/RetentionOps.js.map +7 -0
- package/dist/cjs/internal/RunCatalogOps.d.ts +118 -0
- package/dist/cjs/internal/RunCatalogOps.d.ts.map +1 -0
- package/dist/cjs/internal/RunCatalogOps.js +85 -0
- package/dist/cjs/internal/RunCatalogOps.js.map +7 -0
- package/dist/cjs/internal/RunCoordinator.d.ts +78 -0
- package/dist/cjs/internal/RunCoordinator.d.ts.map +1 -0
- package/dist/cjs/internal/RunCoordinator.js +123 -0
- package/dist/cjs/internal/RunCoordinator.js.map +7 -0
- package/dist/cjs/internal/RunDriver.d.ts +192 -0
- package/dist/cjs/internal/RunDriver.d.ts.map +1 -0
- package/dist/cjs/internal/RunDriver.js +1584 -0
- package/dist/cjs/internal/RunDriver.js.map +7 -0
- package/dist/cjs/internal/RunLifecycle.d.ts +22 -0
- package/dist/cjs/internal/RunLifecycle.d.ts.map +1 -0
- package/dist/cjs/internal/RunLifecycle.js +63 -0
- package/dist/cjs/internal/RunLifecycle.js.map +7 -0
- package/dist/cjs/internal/RunListing.d.ts +78 -0
- package/dist/cjs/internal/RunListing.d.ts.map +1 -0
- package/dist/cjs/internal/RunListing.js +171 -0
- package/dist/cjs/internal/RunListing.js.map +7 -0
- package/dist/cjs/internal/RunListingError.d.ts +35 -0
- package/dist/cjs/internal/RunListingError.d.ts.map +1 -0
- package/dist/cjs/internal/RunListingError.js +47 -0
- package/dist/cjs/internal/RunListingError.js.map +7 -0
- package/dist/cjs/internal/RuntimeGraph.d.ts +73 -0
- package/dist/cjs/internal/RuntimeGraph.d.ts.map +1 -0
- package/dist/cjs/internal/RuntimeGraph.js +180 -0
- package/dist/cjs/internal/RuntimeGraph.js.map +7 -0
- package/dist/cjs/internal/SandboxedExecution.d.ts +80 -0
- package/dist/cjs/internal/SandboxedExecution.d.ts.map +1 -0
- package/dist/cjs/internal/SandboxedExecution.js +104 -0
- package/dist/cjs/internal/SandboxedExecution.js.map +7 -0
- package/dist/cjs/internal/StateTransaction.d.ts +44 -0
- package/dist/cjs/internal/StateTransaction.d.ts.map +1 -0
- package/dist/cjs/internal/StateTransaction.js +52 -0
- package/dist/cjs/internal/StateTransaction.js.map +7 -0
- package/dist/cjs/internal/StepFacts.d.ts +14 -0
- package/dist/cjs/internal/StepFacts.d.ts.map +1 -0
- package/dist/cjs/internal/StepFacts.js +58 -0
- package/dist/cjs/internal/StepFacts.js.map +7 -0
- package/dist/cjs/internal/TypedEvents.d.ts +14 -0
- package/dist/cjs/internal/TypedEvents.d.ts.map +1 -0
- package/dist/cjs/internal/TypedEvents.js +37 -0
- package/dist/cjs/internal/TypedEvents.js.map +7 -0
- package/dist/cjs/migrations/0001_initial.d.ts +17 -0
- package/dist/cjs/migrations/0001_initial.d.ts.map +1 -0
- package/dist/cjs/migrations/0001_initial.js +61 -0
- package/dist/cjs/migrations/0001_initial.js.map +7 -0
- package/dist/cjs/migrations/0002_selection_store.d.ts +15 -0
- package/dist/cjs/migrations/0002_selection_store.d.ts.map +1 -0
- package/dist/cjs/migrations/0002_selection_store.js +56 -0
- package/dist/cjs/migrations/0002_selection_store.js.map +7 -0
- package/dist/cjs/migrations/0003_plan_inputs.d.ts +13 -0
- package/dist/cjs/migrations/0003_plan_inputs.d.ts.map +1 -0
- package/dist/cjs/migrations/0003_plan_inputs.js +90 -0
- package/dist/cjs/migrations/0003_plan_inputs.js.map +7 -0
- package/dist/cjs/migrations/0004_plan_environment.d.ts +14 -0
- package/dist/cjs/migrations/0004_plan_environment.d.ts.map +1 -0
- package/dist/cjs/migrations/0004_plan_environment.js +56 -0
- package/dist/cjs/migrations/0004_plan_environment.js.map +7 -0
- package/dist/cjs/migrations/0005_plan_merges.d.ts +14 -0
- package/dist/cjs/migrations/0005_plan_merges.d.ts.map +1 -0
- package/dist/cjs/migrations/0005_plan_merges.js +82 -0
- package/dist/cjs/migrations/0005_plan_merges.js.map +7 -0
- package/dist/cjs/migrations/0006_execution_listing.d.ts +15 -0
- package/dist/cjs/migrations/0006_execution_listing.d.ts.map +1 -0
- package/dist/cjs/migrations/0006_execution_listing.js +101 -0
- package/dist/cjs/migrations/0006_execution_listing.js.map +7 -0
- package/dist/cjs/migrations/0007_run_parent_sequence.d.ts +15 -0
- package/dist/cjs/migrations/0007_run_parent_sequence.d.ts.map +1 -0
- package/dist/cjs/migrations/0007_run_parent_sequence.js +40 -0
- package/dist/cjs/migrations/0007_run_parent_sequence.js.map +7 -0
- package/dist/cjs/migrations/0008_deferred_consumption.d.ts +16 -0
- package/dist/cjs/migrations/0008_deferred_consumption.d.ts.map +1 -0
- package/dist/cjs/migrations/0008_deferred_consumption.js +45 -0
- package/dist/cjs/migrations/0008_deferred_consumption.js.map +7 -0
- package/dist/cjs/migrations/0009_deferred_clock_facts.d.ts +24 -0
- package/dist/cjs/migrations/0009_deferred_clock_facts.d.ts.map +1 -0
- package/dist/cjs/migrations/0009_deferred_clock_facts.js +141 -0
- package/dist/cjs/migrations/0009_deferred_clock_facts.js.map +7 -0
- package/dist/cjs/migrations/0010_pending_cancellation.d.ts +15 -0
- package/dist/cjs/migrations/0010_pending_cancellation.d.ts.map +1 -0
- package/dist/cjs/migrations/0010_pending_cancellation.js +41 -0
- package/dist/cjs/migrations/0010_pending_cancellation.js.map +7 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/test/TestStores.d.ts +89 -0
- package/dist/cjs/test/TestStores.d.ts.map +1 -0
- package/dist/cjs/test/TestStores.js +98 -0
- package/dist/cjs/test/TestStores.js.map +7 -0
- package/dist/esm/ArtifactGc.d.ts +235 -0
- package/dist/esm/ArtifactGc.d.ts.map +1 -0
- package/dist/esm/ArtifactGc.js +268 -0
- package/dist/esm/ArtifactGc.js.map +1 -0
- package/dist/esm/ArtifactSync.d.ts +164 -0
- package/dist/esm/ArtifactSync.d.ts.map +1 -0
- package/dist/esm/ArtifactSync.js +176 -0
- package/dist/esm/ArtifactSync.js.map +1 -0
- package/dist/esm/CacheSync.d.ts +112 -0
- package/dist/esm/CacheSync.d.ts.map +1 -0
- package/dist/esm/CacheSync.js +94 -0
- package/dist/esm/CacheSync.js.map +1 -0
- package/dist/esm/DeferredClockFold.d.ts +354 -0
- package/dist/esm/DeferredClockFold.d.ts.map +1 -0
- package/dist/esm/DeferredClockFold.js +472 -0
- package/dist/esm/DeferredClockFold.js.map +1 -0
- package/dist/esm/DisasterRecovery.d.ts +431 -0
- package/dist/esm/DisasterRecovery.d.ts.map +1 -0
- package/dist/esm/DisasterRecovery.js +523 -0
- package/dist/esm/DisasterRecovery.js.map +1 -0
- package/dist/esm/DurableEngineState.d.ts +776 -0
- package/dist/esm/DurableEngineState.d.ts.map +1 -0
- package/dist/esm/DurableEngineState.js +1648 -0
- package/dist/esm/DurableEngineState.js.map +1 -0
- package/dist/esm/EngineStore.d.ts +158 -0
- package/dist/esm/EngineStore.d.ts.map +1 -0
- package/dist/esm/EngineStore.js +403 -0
- package/dist/esm/EngineStore.js.map +1 -0
- package/dist/esm/EngineStoreMetrics.d.ts +276 -0
- package/dist/esm/EngineStoreMetrics.d.ts.map +1 -0
- package/dist/esm/EngineStoreMetrics.js +260 -0
- package/dist/esm/EngineStoreMetrics.js.map +1 -0
- package/dist/esm/Errors.d.ts +22 -0
- package/dist/esm/Errors.d.ts.map +1 -0
- package/dist/esm/Errors.js +22 -0
- package/dist/esm/Errors.js.map +1 -0
- package/dist/esm/EventTypes.d.ts +27 -0
- package/dist/esm/EventTypes.d.ts.map +1 -0
- package/dist/esm/EventTypes.js +27 -0
- package/dist/esm/EventTypes.js.map +1 -0
- package/dist/esm/ExecutionFacts.d.ts +59 -0
- package/dist/esm/ExecutionFacts.d.ts.map +1 -0
- package/dist/esm/ExecutionFacts.js +96 -0
- package/dist/esm/ExecutionFacts.js.map +1 -0
- package/dist/esm/ExecutionSnapshot.d.ts +117 -0
- package/dist/esm/ExecutionSnapshot.d.ts.map +1 -0
- package/dist/esm/ExecutionSnapshot.js +95 -0
- package/dist/esm/ExecutionSnapshot.js.map +1 -0
- package/dist/esm/Inconsistency.d.ts +154 -0
- package/dist/esm/Inconsistency.d.ts.map +1 -0
- package/dist/esm/Inconsistency.js +133 -0
- package/dist/esm/Inconsistency.js.map +1 -0
- package/dist/esm/Migrations.d.ts +56 -0
- package/dist/esm/Migrations.d.ts.map +1 -0
- package/dist/esm/Migrations.js +91 -0
- package/dist/esm/Migrations.js.map +1 -0
- package/dist/esm/OwnerIdentity.d.ts +91 -0
- package/dist/esm/OwnerIdentity.d.ts.map +1 -0
- package/dist/esm/OwnerIdentity.js +99 -0
- package/dist/esm/OwnerIdentity.js.map +1 -0
- package/dist/esm/PlanInputStore.d.ts +110 -0
- package/dist/esm/PlanInputStore.d.ts.map +1 -0
- package/dist/esm/PlanInputStore.js +207 -0
- package/dist/esm/PlanInputStore.js.map +1 -0
- package/dist/esm/PlanMergeStore.d.ts +113 -0
- package/dist/esm/PlanMergeStore.d.ts.map +1 -0
- package/dist/esm/PlanMergeStore.js +244 -0
- package/dist/esm/PlanMergeStore.js.map +1 -0
- package/dist/esm/PlanScheduler.d.ts +363 -0
- package/dist/esm/PlanScheduler.d.ts.map +1 -0
- package/dist/esm/PlanScheduler.js +1321 -0
- package/dist/esm/PlanScheduler.js.map +1 -0
- package/dist/esm/Reconciliation.d.ts +175 -0
- package/dist/esm/Reconciliation.d.ts.map +1 -0
- package/dist/esm/Reconciliation.js +175 -0
- package/dist/esm/Reconciliation.js.map +1 -0
- package/dist/esm/ReplayOnly.d.ts +91 -0
- package/dist/esm/ReplayOnly.d.ts.map +1 -0
- package/dist/esm/ReplayOnly.js +59 -0
- package/dist/esm/ReplayOnly.js.map +1 -0
- package/dist/esm/Retention.d.ts +113 -0
- package/dist/esm/Retention.d.ts.map +1 -0
- package/dist/esm/Retention.js +139 -0
- package/dist/esm/Retention.js.map +1 -0
- package/dist/esm/RunCatalogRead.d.ts +17 -0
- package/dist/esm/RunCatalogRead.d.ts.map +1 -0
- package/dist/esm/RunCatalogRead.js +17 -0
- package/dist/esm/RunCatalogRead.js.map +1 -0
- package/dist/esm/RunChangeFeed.d.ts +75 -0
- package/dist/esm/RunChangeFeed.d.ts.map +1 -0
- package/dist/esm/RunChangeFeed.js +81 -0
- package/dist/esm/RunChangeFeed.js.map +1 -0
- package/dist/esm/RunState.d.ts +116 -0
- package/dist/esm/RunState.d.ts.map +1 -0
- package/dist/esm/RunState.js +109 -0
- package/dist/esm/RunState.js.map +1 -0
- package/dist/esm/Selection.d.ts +495 -0
- package/dist/esm/Selection.d.ts.map +1 -0
- package/dist/esm/Selection.js +482 -0
- package/dist/esm/Selection.js.map +1 -0
- package/dist/esm/SelectionStore.d.ts +141 -0
- package/dist/esm/SelectionStore.d.ts.map +1 -0
- package/dist/esm/SelectionStore.js +215 -0
- package/dist/esm/SelectionStore.js.map +1 -0
- package/dist/esm/StepBoundary.d.ts +501 -0
- package/dist/esm/StepBoundary.d.ts.map +1 -0
- package/dist/esm/StepBoundary.js +919 -0
- package/dist/esm/StepBoundary.js.map +1 -0
- package/dist/esm/StepCacheFold.d.ts +148 -0
- package/dist/esm/StepCacheFold.d.ts.map +1 -0
- package/dist/esm/StepCacheFold.js +201 -0
- package/dist/esm/StepCacheFold.js.map +1 -0
- package/dist/esm/StepSandbox.d.ts +78 -0
- package/dist/esm/StepSandbox.d.ts.map +1 -0
- package/dist/esm/StepSandbox.js +70 -0
- package/dist/esm/StepSandbox.js.map +1 -0
- package/dist/esm/WakeBus.d.ts +116 -0
- package/dist/esm/WakeBus.d.ts.map +1 -0
- package/dist/esm/WakeBus.js +121 -0
- package/dist/esm/WakeBus.js.map +1 -0
- package/dist/esm/WorkspaceSandbox.d.ts +619 -0
- package/dist/esm/WorkspaceSandbox.d.ts.map +1 -0
- package/dist/esm/WorkspaceSandbox.js +985 -0
- package/dist/esm/WorkspaceSandbox.js.map +1 -0
- package/dist/esm/index.d.ts +157 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +157 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/internal/ActionPersistence.d.ts +276 -0
- package/dist/esm/internal/ActionPersistence.d.ts.map +1 -0
- package/dist/esm/internal/ActionPersistence.js +2306 -0
- package/dist/esm/internal/ActionPersistence.js.map +1 -0
- package/dist/esm/internal/ArtifactRoots.d.ts +38 -0
- package/dist/esm/internal/ArtifactRoots.d.ts.map +1 -0
- package/dist/esm/internal/ArtifactRoots.js +76 -0
- package/dist/esm/internal/ArtifactRoots.js.map +1 -0
- package/dist/esm/internal/AttemptAdmission.d.ts +53 -0
- package/dist/esm/internal/AttemptAdmission.d.ts.map +1 -0
- package/dist/esm/internal/AttemptAdmission.js +52 -0
- package/dist/esm/internal/AttemptAdmission.js.map +1 -0
- package/dist/esm/internal/AttemptLifecycle.d.ts +245 -0
- package/dist/esm/internal/AttemptLifecycle.d.ts.map +1 -0
- package/dist/esm/internal/AttemptLifecycle.js +104 -0
- package/dist/esm/internal/AttemptLifecycle.js.map +1 -0
- package/dist/esm/internal/AttemptProbe.d.ts +53 -0
- package/dist/esm/internal/AttemptProbe.d.ts.map +1 -0
- package/dist/esm/internal/AttemptProbe.js +83 -0
- package/dist/esm/internal/AttemptProbe.js.map +1 -0
- package/dist/esm/internal/CacheAdmission.d.ts +79 -0
- package/dist/esm/internal/CacheAdmission.d.ts.map +1 -0
- package/dist/esm/internal/CacheAdmission.js +72 -0
- package/dist/esm/internal/CacheAdmission.js.map +1 -0
- package/dist/esm/internal/CacheAgeHistory.d.ts +27 -0
- package/dist/esm/internal/CacheAgeHistory.d.ts.map +1 -0
- package/dist/esm/internal/CacheAgeHistory.js +49 -0
- package/dist/esm/internal/CacheAgeHistory.js.map +1 -0
- package/dist/esm/internal/CacheAgeVerdicts.d.ts +15 -0
- package/dist/esm/internal/CacheAgeVerdicts.d.ts.map +1 -0
- package/dist/esm/internal/CacheAgeVerdicts.js +54 -0
- package/dist/esm/internal/CacheAgeVerdicts.js.map +1 -0
- package/dist/esm/internal/CacheOutputPolicy.d.ts +24 -0
- package/dist/esm/internal/CacheOutputPolicy.d.ts.map +1 -0
- package/dist/esm/internal/CacheOutputPolicy.js +73 -0
- package/dist/esm/internal/CacheOutputPolicy.js.map +1 -0
- package/dist/esm/internal/CachePublication.d.ts +97 -0
- package/dist/esm/internal/CachePublication.d.ts.map +1 -0
- package/dist/esm/internal/CachePublication.js +116 -0
- package/dist/esm/internal/CachePublication.js.map +1 -0
- package/dist/esm/internal/CallFacts.d.ts +15 -0
- package/dist/esm/internal/CallFacts.d.ts.map +1 -0
- package/dist/esm/internal/CallFacts.js +83 -0
- package/dist/esm/internal/CallFacts.js.map +1 -0
- package/dist/esm/internal/CopiedRecord.d.ts +61 -0
- package/dist/esm/internal/CopiedRecord.d.ts.map +1 -0
- package/dist/esm/internal/CopiedRecord.js +165 -0
- package/dist/esm/internal/CopiedRecord.js.map +1 -0
- package/dist/esm/internal/DeferredPersistence.d.ts +108 -0
- package/dist/esm/internal/DeferredPersistence.d.ts.map +1 -0
- package/dist/esm/internal/DeferredPersistence.js +240 -0
- package/dist/esm/internal/DeferredPersistence.js.map +1 -0
- package/dist/esm/internal/EffectRecords.d.ts +92 -0
- package/dist/esm/internal/EffectRecords.d.ts.map +1 -0
- package/dist/esm/internal/EffectRecords.js +94 -0
- package/dist/esm/internal/EffectRecords.js.map +1 -0
- package/dist/esm/internal/EngineJj.d.ts +30 -0
- package/dist/esm/internal/EngineJj.d.ts.map +1 -0
- package/dist/esm/internal/EngineJj.js +29 -0
- package/dist/esm/internal/EngineJj.js.map +1 -0
- package/dist/esm/internal/EngineStateSchema.d.ts +68 -0
- package/dist/esm/internal/EngineStateSchema.d.ts.map +1 -0
- package/dist/esm/internal/EngineStateSchema.js +53 -0
- package/dist/esm/internal/EngineStateSchema.js.map +1 -0
- package/dist/esm/internal/ExecutionSnapshotRead.d.ts +162 -0
- package/dist/esm/internal/ExecutionSnapshotRead.d.ts.map +1 -0
- package/dist/esm/internal/ExecutionSnapshotRead.js +218 -0
- package/dist/esm/internal/ExecutionSnapshotRead.js.map +1 -0
- package/dist/esm/internal/ExecutionSnapshotRelations.d.ts +39 -0
- package/dist/esm/internal/ExecutionSnapshotRelations.d.ts.map +1 -0
- package/dist/esm/internal/ExecutionSnapshotRelations.js +81 -0
- package/dist/esm/internal/ExecutionSnapshotRelations.js.map +1 -0
- package/dist/esm/internal/ExitEncoding.d.ts +180 -0
- package/dist/esm/internal/ExitEncoding.d.ts.map +1 -0
- package/dist/esm/internal/ExitEncoding.js +361 -0
- package/dist/esm/internal/ExitEncoding.js.map +1 -0
- package/dist/esm/internal/FileBoundarySnapshot.d.ts +14 -0
- package/dist/esm/internal/FileBoundarySnapshot.d.ts.map +1 -0
- package/dist/esm/internal/FileBoundarySnapshot.js +29 -0
- package/dist/esm/internal/FileBoundarySnapshot.js.map +1 -0
- package/dist/esm/internal/FileEnumeration.d.ts +127 -0
- package/dist/esm/internal/FileEnumeration.d.ts.map +1 -0
- package/dist/esm/internal/FileEnumeration.js +236 -0
- package/dist/esm/internal/FileEnumeration.js.map +1 -0
- package/dist/esm/internal/HostReflection.d.ts +80 -0
- package/dist/esm/internal/HostReflection.d.ts.map +1 -0
- package/dist/esm/internal/HostReflection.js +98 -0
- package/dist/esm/internal/HostReflection.js.map +1 -0
- package/dist/esm/internal/JournalRecords.d.ts +325 -0
- package/dist/esm/internal/JournalRecords.d.ts.map +1 -0
- package/dist/esm/internal/JournalRecords.js +306 -0
- package/dist/esm/internal/JournalRecords.js.map +1 -0
- package/dist/esm/internal/NodeJournal.d.ts +157 -0
- package/dist/esm/internal/NodeJournal.d.ts.map +1 -0
- package/dist/esm/internal/NodeJournal.js +298 -0
- package/dist/esm/internal/NodeJournal.js.map +1 -0
- package/dist/esm/internal/Ordering.d.ts +14 -0
- package/dist/esm/internal/Ordering.d.ts.map +1 -0
- package/dist/esm/internal/Ordering.js +14 -0
- package/dist/esm/internal/Ordering.js.map +1 -0
- package/dist/esm/internal/PlanInputs.d.ts +14 -0
- package/dist/esm/internal/PlanInputs.d.ts.map +1 -0
- package/dist/esm/internal/PlanInputs.js +57 -0
- package/dist/esm/internal/PlanInputs.js.map +1 -0
- package/dist/esm/internal/PlanMerges.d.ts +178 -0
- package/dist/esm/internal/PlanMerges.d.ts.map +1 -0
- package/dist/esm/internal/PlanMerges.js +113 -0
- package/dist/esm/internal/PlanMerges.js.map +1 -0
- package/dist/esm/internal/ProvenanceSlot.d.ts +30 -0
- package/dist/esm/internal/ProvenanceSlot.d.ts.map +1 -0
- package/dist/esm/internal/ProvenanceSlot.js +39 -0
- package/dist/esm/internal/ProvenanceSlot.js.map +1 -0
- package/dist/esm/internal/ResultEnvelope.d.ts +88 -0
- package/dist/esm/internal/ResultEnvelope.d.ts.map +1 -0
- package/dist/esm/internal/ResultEnvelope.js +83 -0
- package/dist/esm/internal/ResultEnvelope.js.map +1 -0
- package/dist/esm/internal/RetentionOps.d.ts +414 -0
- package/dist/esm/internal/RetentionOps.d.ts.map +1 -0
- package/dist/esm/internal/RetentionOps.js +576 -0
- package/dist/esm/internal/RetentionOps.js.map +1 -0
- package/dist/esm/internal/RunCatalogOps.d.ts +118 -0
- package/dist/esm/internal/RunCatalogOps.d.ts.map +1 -0
- package/dist/esm/internal/RunCatalogOps.js +120 -0
- package/dist/esm/internal/RunCatalogOps.js.map +1 -0
- package/dist/esm/internal/RunCoordinator.d.ts +78 -0
- package/dist/esm/internal/RunCoordinator.d.ts.map +1 -0
- package/dist/esm/internal/RunCoordinator.js +115 -0
- package/dist/esm/internal/RunCoordinator.js.map +1 -0
- package/dist/esm/internal/RunDriver.d.ts +192 -0
- package/dist/esm/internal/RunDriver.d.ts.map +1 -0
- package/dist/esm/internal/RunDriver.js +2334 -0
- package/dist/esm/internal/RunDriver.js.map +1 -0
- package/dist/esm/internal/RunLifecycle.d.ts +22 -0
- package/dist/esm/internal/RunLifecycle.d.ts.map +1 -0
- package/dist/esm/internal/RunLifecycle.js +49 -0
- package/dist/esm/internal/RunLifecycle.js.map +1 -0
- package/dist/esm/internal/RunListing.d.ts +78 -0
- package/dist/esm/internal/RunListing.d.ts.map +1 -0
- package/dist/esm/internal/RunListing.js +146 -0
- package/dist/esm/internal/RunListing.js.map +1 -0
- package/dist/esm/internal/RunListingError.d.ts +35 -0
- package/dist/esm/internal/RunListingError.d.ts.map +1 -0
- package/dist/esm/internal/RunListingError.js +27 -0
- package/dist/esm/internal/RunListingError.js.map +1 -0
- package/dist/esm/internal/RuntimeGraph.d.ts +73 -0
- package/dist/esm/internal/RuntimeGraph.d.ts.map +1 -0
- package/dist/esm/internal/RuntimeGraph.js +182 -0
- package/dist/esm/internal/RuntimeGraph.js.map +1 -0
- package/dist/esm/internal/SandboxedExecution.d.ts +80 -0
- package/dist/esm/internal/SandboxedExecution.d.ts.map +1 -0
- package/dist/esm/internal/SandboxedExecution.js +107 -0
- package/dist/esm/internal/SandboxedExecution.js.map +1 -0
- package/dist/esm/internal/StateTransaction.d.ts +44 -0
- package/dist/esm/internal/StateTransaction.d.ts.map +1 -0
- package/dist/esm/internal/StateTransaction.js +54 -0
- package/dist/esm/internal/StateTransaction.js.map +1 -0
- package/dist/esm/internal/StepFacts.d.ts +14 -0
- package/dist/esm/internal/StepFacts.d.ts.map +1 -0
- package/dist/esm/internal/StepFacts.js +35 -0
- package/dist/esm/internal/StepFacts.js.map +1 -0
- package/dist/esm/internal/TypedEvents.d.ts +14 -0
- package/dist/esm/internal/TypedEvents.d.ts.map +1 -0
- package/dist/esm/internal/TypedEvents.js +14 -0
- package/dist/esm/internal/TypedEvents.js.map +1 -0
- package/dist/esm/migrations/0001_initial.d.ts +17 -0
- package/dist/esm/migrations/0001_initial.d.ts.map +1 -0
- package/dist/esm/migrations/0001_initial.js +41 -0
- package/dist/esm/migrations/0001_initial.js.map +1 -0
- package/dist/esm/migrations/0002_selection_store.d.ts +15 -0
- package/dist/esm/migrations/0002_selection_store.d.ts.map +1 -0
- package/dist/esm/migrations/0002_selection_store.js +34 -0
- package/dist/esm/migrations/0002_selection_store.js.map +1 -0
- package/dist/esm/migrations/0003_plan_inputs.d.ts +13 -0
- package/dist/esm/migrations/0003_plan_inputs.d.ts.map +1 -0
- package/dist/esm/migrations/0003_plan_inputs.js +70 -0
- package/dist/esm/migrations/0003_plan_inputs.js.map +1 -0
- package/dist/esm/migrations/0004_plan_environment.d.ts +14 -0
- package/dist/esm/migrations/0004_plan_environment.d.ts.map +1 -0
- package/dist/esm/migrations/0004_plan_environment.js +33 -0
- package/dist/esm/migrations/0004_plan_environment.js.map +1 -0
- package/dist/esm/migrations/0005_plan_merges.d.ts +14 -0
- package/dist/esm/migrations/0005_plan_merges.d.ts.map +1 -0
- package/dist/esm/migrations/0005_plan_merges.js +59 -0
- package/dist/esm/migrations/0005_plan_merges.js.map +1 -0
- package/dist/esm/migrations/0006_execution_listing.d.ts +15 -0
- package/dist/esm/migrations/0006_execution_listing.d.ts.map +1 -0
- package/dist/esm/migrations/0006_execution_listing.js +81 -0
- package/dist/esm/migrations/0006_execution_listing.js.map +1 -0
- package/dist/esm/migrations/0007_run_parent_sequence.d.ts +15 -0
- package/dist/esm/migrations/0007_run_parent_sequence.d.ts.map +1 -0
- package/dist/esm/migrations/0007_run_parent_sequence.js +18 -0
- package/dist/esm/migrations/0007_run_parent_sequence.js.map +1 -0
- package/dist/esm/migrations/0008_deferred_consumption.d.ts +16 -0
- package/dist/esm/migrations/0008_deferred_consumption.d.ts.map +1 -0
- package/dist/esm/migrations/0008_deferred_consumption.js +24 -0
- package/dist/esm/migrations/0008_deferred_consumption.js.map +1 -0
- package/dist/esm/migrations/0009_deferred_clock_facts.d.ts +24 -0
- package/dist/esm/migrations/0009_deferred_clock_facts.d.ts.map +1 -0
- package/dist/esm/migrations/0009_deferred_clock_facts.js +127 -0
- package/dist/esm/migrations/0009_deferred_clock_facts.js.map +1 -0
- package/dist/esm/migrations/0010_pending_cancellation.d.ts +15 -0
- package/dist/esm/migrations/0010_pending_cancellation.d.ts.map +1 -0
- package/dist/esm/migrations/0010_pending_cancellation.js +19 -0
- package/dist/esm/migrations/0010_pending_cancellation.js.map +1 -0
- package/dist/esm/test/TestStores.d.ts +89 -0
- package/dist/esm/test/TestStores.d.ts.map +1 -0
- package/dist/esm/test/TestStores.js +103 -0
- package/dist/esm/test/TestStores.js.map +1 -0
- package/docs/README.md +113 -0
- package/docs/api.md +1494 -0
- package/docs/concepts/attempts-and-replay.md +149 -0
- package/docs/concepts/cache-admission.md +265 -0
- package/docs/concepts/call-facts.md +86 -0
- package/docs/concepts/durable-waits.md +175 -0
- package/docs/concepts/execution-facts.md +90 -0
- package/docs/concepts/ownership-and-fencing.md +128 -0
- package/docs/concepts/step-boundaries.md +148 -0
- package/docs/concepts/workspace-transactions.md +199 -0
- package/docs/event-types.md +14 -0
- package/docs/guides/back-up-and-restore.md +164 -0
- package/docs/guides/collect-unreferenced-artifacts.md +128 -0
- package/docs/guides/compose-a-durable-engine.md +146 -0
- package/docs/guides/coordinate-two-processes.md +120 -0
- package/docs/guides/defer-work-with-selection.md +171 -0
- package/docs/guides/delete-old-run-history.md +154 -0
- package/docs/guides/drive-a-plan.md +314 -0
- package/docs/guides/observe-engine-metrics.md +98 -0
- package/docs/guides/observe-executions.md +187 -0
- package/docs/guides/reclaim-runs-from-a-dead-host.md +133 -0
- package/docs/guides/share-a-cache-across-machines.md +134 -0
- package/docs/guides/testing.md +123 -0
- package/docs/installation.md +102 -0
- package/docs/quickstart.md +215 -0
- package/docs/troubleshooting.md +347 -0
- package/package.json +438 -3
- package/src/ArtifactGc.ts +408 -0
- package/src/ArtifactSync.ts +278 -0
- package/src/CacheSync.ts +131 -0
- package/src/DeferredClockFold.ts +586 -0
- package/src/DisasterRecovery.ts +888 -0
- package/src/DurableEngineState.ts +2656 -0
- package/src/EngineStore.ts +656 -0
- package/src/EngineStoreMetrics.ts +331 -0
- package/src/Errors.ts +28 -0
- package/src/EventTypes.ts +27 -0
- package/src/ExecutionFacts.ts +125 -0
- package/src/ExecutionSnapshot.ts +186 -0
- package/src/Inconsistency.ts +253 -0
- package/src/Migrations.ts +95 -0
- package/src/OwnerIdentity.ts +125 -0
- package/src/PlanInputStore.ts +292 -0
- package/src/PlanMergeStore.ts +330 -0
- package/src/PlanScheduler.ts +1806 -0
- package/src/Reconciliation.ts +224 -0
- package/src/ReplayOnly.ts +95 -0
- package/src/Retention.ts +214 -0
- package/src/RunCatalogRead.ts +27 -0
- package/src/RunChangeFeed.ts +125 -0
- package/src/RunState.ts +137 -0
- package/src/Selection.ts +683 -0
- package/src/SelectionStore.ts +353 -0
- package/src/StepBoundary.ts +1165 -0
- package/src/StepCacheFold.ts +244 -0
- package/src/StepSandbox.ts +110 -0
- package/src/WakeBus.ts +167 -0
- package/src/WorkspaceSandbox.ts +1604 -0
- package/src/index.ts +185 -0
- package/src/internal/ActionPersistence.ts +2687 -0
- package/src/internal/ArtifactRoots.ts +92 -0
- package/src/internal/AttemptAdmission.ts +76 -0
- package/src/internal/AttemptLifecycle.ts +163 -0
- package/src/internal/AttemptProbe.ts +90 -0
- package/src/internal/CacheAdmission.ts +133 -0
- package/src/internal/CacheAgeHistory.ts +58 -0
- package/src/internal/CacheAgeVerdicts.ts +61 -0
- package/src/internal/CacheOutputPolicy.ts +82 -0
- package/src/internal/CachePublication.ts +153 -0
- package/src/internal/CallFacts.ts +87 -0
- package/src/internal/CopiedRecord.ts +201 -0
- package/src/internal/DeferredPersistence.ts +459 -0
- package/src/internal/EffectRecords.ts +136 -0
- package/src/internal/EngineJj.ts +30 -0
- package/src/internal/EngineStateSchema.ts +92 -0
- package/src/internal/ExecutionSnapshotRead.ts +298 -0
- package/src/internal/ExecutionSnapshotRelations.ts +121 -0
- package/src/internal/ExitEncoding.ts +453 -0
- package/src/internal/FileBoundarySnapshot.ts +39 -0
- package/src/internal/FileEnumeration.ts +334 -0
- package/src/internal/HostReflection.ts +128 -0
- package/src/internal/JournalRecords.ts +396 -0
- package/src/internal/NodeJournal.ts +340 -0
- package/src/internal/Ordering.ts +14 -0
- package/src/internal/PlanInputs.ts +62 -0
- package/src/internal/PlanMerges.ts +123 -0
- package/src/internal/ProvenanceSlot.ts +52 -0
- package/src/internal/ResultEnvelope.ts +118 -0
- package/src/internal/RetentionOps.ts +865 -0
- package/src/internal/RunCatalogOps.ts +174 -0
- package/src/internal/RunCoordinator.ts +215 -0
- package/src/internal/RunDriver.ts +3152 -0
- package/src/internal/RunLifecycle.ts +53 -0
- package/src/internal/RunListing.ts +210 -0
- package/src/internal/RunListingError.ts +37 -0
- package/src/internal/RuntimeGraph.ts +253 -0
- package/src/internal/SandboxedExecution.ts +165 -0
- package/src/internal/StateTransaction.ts +71 -0
- package/src/internal/StepFacts.ts +36 -0
- package/src/internal/TypedEvents.ts +29 -0
- package/src/migrations/0001_initial.ts +51 -0
- package/src/migrations/0002_selection_store.ts +38 -0
- package/src/migrations/0003_plan_inputs.ts +75 -0
- package/src/migrations/0004_plan_environment.ts +34 -0
- package/src/migrations/0005_plan_merges.ts +60 -0
- package/src/migrations/0006_execution_listing.ts +86 -0
- package/src/migrations/0007_run_parent_sequence.ts +19 -0
- package/src/migrations/0008_deferred_consumption.ts +27 -0
- package/src/migrations/0009_deferred_clock_facts.ts +163 -0
- package/src/migrations/0010_pending_cancellation.ts +20 -0
- package/src/test/TestStores.ts +145 -0
|
@@ -0,0 +1,1165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hermetic step boundary contracts, the filesystem-backed production
|
|
3
|
+
* implementation, and the deterministic test implementation.
|
|
4
|
+
*
|
|
5
|
+
* @since 0.1.0
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import * as ArtifactStore from "@smthrs/artifacts/ArtifactStore"
|
|
9
|
+
import { Sha256 } from "@smthrs/crypto"
|
|
10
|
+
import { FileBoundary } from "@smthrs/flow/FileBoundary"
|
|
11
|
+
import { FileInput } from "@smthrs/flow/FileInput"
|
|
12
|
+
import * as KernelFileSystem from "@smthrs/kernel/FileSystem"
|
|
13
|
+
import { Workspace as KernelWorkspace } from "@smthrs/kernel/Workspace"
|
|
14
|
+
import { DerivedKey } from "@smthrs/keys"
|
|
15
|
+
import * as FileSet from "@smthrs/plan/FileSet"
|
|
16
|
+
import * as Context from "effect/Context"
|
|
17
|
+
import type * as Crypto from "effect/Crypto"
|
|
18
|
+
import * as Effect from "effect/Effect"
|
|
19
|
+
import * as Encoding from "effect/Encoding"
|
|
20
|
+
import * as FileSystem from "effect/FileSystem"
|
|
21
|
+
import * as Layer from "effect/Layer"
|
|
22
|
+
import * as Path from "effect/Path"
|
|
23
|
+
import * as Result from "effect/Result"
|
|
24
|
+
import * as Schema from "effect/Schema"
|
|
25
|
+
import * as FileBoundarySnapshot from "./internal/FileBoundarySnapshot.ts"
|
|
26
|
+
import * as FileEnumeration from "./internal/FileEnumeration.ts"
|
|
27
|
+
import { compareText } from "./internal/Ordering.ts"
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* A file boundary that has been measured but not yet run: the caller's
|
|
31
|
+
* declaration, paired with what the host actually found on disk for it.
|
|
32
|
+
*
|
|
33
|
+
* `prepare` produces one of these before a step executes and `settle` consumes
|
|
34
|
+
* it afterwards, which is what lets the boundary say whether the world moved
|
|
35
|
+
* underneath the declaration. Check it with {@link readSetMatches}.
|
|
36
|
+
*
|
|
37
|
+
* @since 0.1.0
|
|
38
|
+
* @category schemas
|
|
39
|
+
*/
|
|
40
|
+
export const PreparedBoundary = Schema.Struct({
|
|
41
|
+
descriptor: FileBoundary,
|
|
42
|
+
/**
|
|
43
|
+
* What the host actually measured for the declared read set at prepare
|
|
44
|
+
* time. The declared digests are caller metadata folded into the step key;
|
|
45
|
+
* this is the evidence that they still describe reality, and a sealed
|
|
46
|
+
* cache hit is refused when the two disagree (issue #90).
|
|
47
|
+
*/
|
|
48
|
+
readSnapshot: Schema.Array(FileInput)
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* The value form of {@link PreparedBoundary}.
|
|
53
|
+
*
|
|
54
|
+
* @since 0.1.0
|
|
55
|
+
* @category models
|
|
56
|
+
*/
|
|
57
|
+
export type PreparedBoundary = typeof PreparedBoundary.Type
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Exact read inputs from a boundary after ignoring declarations that still need expansion.
|
|
61
|
+
*
|
|
62
|
+
* @category accessors
|
|
63
|
+
* @since 0.1.0
|
|
64
|
+
*/
|
|
65
|
+
export const exactReads = (descriptor: FileBoundary): ReadonlyArray<FileInput> =>
|
|
66
|
+
descriptor.readSet.filter((entry): entry is FileInput => !FileSet.isGlob(entry))
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Whether every declared read still matches what the host measured.
|
|
70
|
+
*
|
|
71
|
+
* Reads the host reports but the declaration never claimed are ignored: the
|
|
72
|
+
* declaration is the dependency edge set, and an incidental extra read is not
|
|
73
|
+
* one of its edges. A declared path missing from the measurement counts as a
|
|
74
|
+
* mismatch — its value is unknown, so reuse cannot be justified.
|
|
75
|
+
*
|
|
76
|
+
* @since 0.1.0
|
|
77
|
+
* @category predicates
|
|
78
|
+
*/
|
|
79
|
+
export const readSetMatches = (prepared: PreparedBoundary): boolean => {
|
|
80
|
+
const digestsByPath = new Map<string, Set<string>>()
|
|
81
|
+
for (const measured of prepared.readSnapshot) {
|
|
82
|
+
const digests = digestsByPath.get(measured.path)
|
|
83
|
+
if (digests === undefined) digestsByPath.set(measured.path, new Set([measured.digest]))
|
|
84
|
+
else digests.add(measured.digest)
|
|
85
|
+
}
|
|
86
|
+
return prepared.descriptor.readSet.every((entry) =>
|
|
87
|
+
!FileSet.isGlob(entry) &&
|
|
88
|
+
digestsByPath.get(entry.path)?.has(entry.digest) === true
|
|
89
|
+
)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* The paths a step wrote that its `expected` write set did not name, together
|
|
94
|
+
* with the diff identity they were observed under.
|
|
95
|
+
*
|
|
96
|
+
* An expected-mode boundary reports these rather than failing: the declaration
|
|
97
|
+
* is a prediction, and a wrong prediction is evidence to journal, not an
|
|
98
|
+
* error. A hard-mode boundary raises {@link UndeclaredWrite} for the same
|
|
99
|
+
* observation.
|
|
100
|
+
*
|
|
101
|
+
* @since 0.1.0
|
|
102
|
+
* @category schemas
|
|
103
|
+
*/
|
|
104
|
+
export const ExpectedSetDeviation = Schema.TaggedStruct("ExpectedSetDeviation", {
|
|
105
|
+
paths: Schema.Array(Schema.String),
|
|
106
|
+
diffIdentity: Schema.NonEmptyString
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Declared writes an expected-mode step did not produce, and did not declare
|
|
111
|
+
* as removals.
|
|
112
|
+
*
|
|
113
|
+
* The absence itself is the defect: recording it as valid evidence caches the
|
|
114
|
+
* claim "this file should not exist", which `replayOutputs` then acts on by
|
|
115
|
+
* deleting the path on a workspace that never ran the step. A hard-mode
|
|
116
|
+
* boundary raises {@link MissingDeclaredOutput} for the same observation.
|
|
117
|
+
*
|
|
118
|
+
* Expected mode records it rather than failing because the declaration there is
|
|
119
|
+
* a prediction — and because a deviation of any variant already bars the
|
|
120
|
+
* evidence from the shared cache (`ActionPersistence` gates `recordCache` on
|
|
121
|
+
* `deviation === undefined`), so an unexplained absence never reaches another
|
|
122
|
+
* host either way.
|
|
123
|
+
*
|
|
124
|
+
* @since 0.1.0
|
|
125
|
+
* @category schemas
|
|
126
|
+
*/
|
|
127
|
+
export const MissingOutputDeviation = Schema.TaggedStruct("MissingDeclaredOutput", {
|
|
128
|
+
paths: Schema.Array(Schema.String),
|
|
129
|
+
diffIdentity: Schema.NonEmptyString
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Declared removals an expected-mode step left in place.
|
|
134
|
+
*
|
|
135
|
+
* The dual of {@link MissingOutputDeviation}: a removal is a promise about the
|
|
136
|
+
* post-state exactly as a write is. A path that was promised absent but is
|
|
137
|
+
* still present — possibly mutated — must not settle as valid evidence, or
|
|
138
|
+
* the mutation is cached under a declaration that disclaimed it and replay
|
|
139
|
+
* materializes it everywhere. A hard-mode boundary raises
|
|
140
|
+
* {@link SurvivingDeclaredRemoval} for the same observation.
|
|
141
|
+
*
|
|
142
|
+
* @since 0.1.0
|
|
143
|
+
* @category schemas
|
|
144
|
+
*/
|
|
145
|
+
export const SurvivingRemovalDeviation = Schema.TaggedStruct("SurvivingDeclaredRemoval", {
|
|
146
|
+
paths: Schema.Array(Schema.String),
|
|
147
|
+
diffIdentity: Schema.NonEmptyString
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* What a settled boundary observed that its declaration did not predict.
|
|
152
|
+
*
|
|
153
|
+
* @since 0.1.0
|
|
154
|
+
* @category schemas
|
|
155
|
+
*/
|
|
156
|
+
export const BoundaryDeviation = Schema.Union([ExpectedSetDeviation, MissingOutputDeviation, SurvivingRemovalDeviation])
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* The value form of {@link BoundaryDeviation}.
|
|
160
|
+
*
|
|
161
|
+
* @since 0.1.0
|
|
162
|
+
* @category models
|
|
163
|
+
*/
|
|
164
|
+
export type BoundaryDeviation = typeof BoundaryDeviation.Type
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* What a settled boundary proved about a step's writes — the record the
|
|
168
|
+
* journal keeps and a later cache hit is justified against.
|
|
169
|
+
*
|
|
170
|
+
* `diffIdentity` names the post-state the step produced; `declaredOutputs`
|
|
171
|
+
* carries whatever the implementation needs to reproduce it on a workspace
|
|
172
|
+
* that never ran the step. The two optional fields are the honest gaps:
|
|
173
|
+
* `wholeTreeWritesVerified` is present only when the boundary could observe
|
|
174
|
+
* the entire execution tree, and `deviation` only when it saw writes the
|
|
175
|
+
* declaration did not predict.
|
|
176
|
+
*
|
|
177
|
+
* @since 0.1.0
|
|
178
|
+
* @category schemas
|
|
179
|
+
*/
|
|
180
|
+
export const BoundaryEvidence = Schema.Struct({
|
|
181
|
+
declaredOutputs: Schema.Unknown,
|
|
182
|
+
diffIdentity: Schema.NonEmptyString,
|
|
183
|
+
/**
|
|
184
|
+
* The boundary observed the whole execution tree and proved that every
|
|
185
|
+
* write was covered by the descriptor's write set. Only evidence carrying
|
|
186
|
+
* this explicit proof may enter the cross-run cache. Older evidence and
|
|
187
|
+
* boundaries that can inspect declared paths only omit it.
|
|
188
|
+
*/
|
|
189
|
+
wholeTreeWritesVerified: Schema.optional(Schema.Literal(true)),
|
|
190
|
+
/** The body could observe only declared filesystem inputs. */
|
|
191
|
+
hermeticReadsVerified: Schema.optional(Schema.Literal(true)),
|
|
192
|
+
deviation: Schema.optional(BoundaryDeviation)
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* The value form of {@link BoundaryEvidence}.
|
|
197
|
+
*
|
|
198
|
+
* @since 0.1.0
|
|
199
|
+
* @category models
|
|
200
|
+
*/
|
|
201
|
+
export type BoundaryEvidence = typeof BoundaryEvidence.Type
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* A hard-mode step wrote outside its declared write set.
|
|
205
|
+
*
|
|
206
|
+
* Hard mode treats the declaration as a contract, so this is a refusal, not a
|
|
207
|
+
* report: the step's evidence is never journaled and never becomes a cache
|
|
208
|
+
* entry. An expected-mode boundary records the same observation as a
|
|
209
|
+
* {@link BoundaryDeviation} instead.
|
|
210
|
+
*
|
|
211
|
+
* @since 0.1.0
|
|
212
|
+
* @category errors
|
|
213
|
+
*/
|
|
214
|
+
export class UndeclaredWrite extends Schema.TaggedError<UndeclaredWrite>()(
|
|
215
|
+
"@smthrs/engine-store/UndeclaredWrite",
|
|
216
|
+
{
|
|
217
|
+
code: Schema.Literal("undeclared_write"),
|
|
218
|
+
paths: Schema.Array(Schema.String),
|
|
219
|
+
diffIdentity: Schema.NonEmptyString
|
|
220
|
+
}
|
|
221
|
+
) {}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* A step finished without producing a path its write set declared, and did not
|
|
225
|
+
* declare that path as a removal.
|
|
226
|
+
*
|
|
227
|
+
* Bazel hard-fails the same observation — `SkyframeActionExecutor.checkOutputs`
|
|
228
|
+
* reports "output was not created" — and for the same reason: the alternative
|
|
229
|
+
* is to record `digest: null` as valid evidence, which caches the claim "this
|
|
230
|
+
* file should not exist" and makes every later replay delete the path. A step
|
|
231
|
+
* that crashed after declaring its writes would poison the cache with an
|
|
232
|
+
* eraser.
|
|
233
|
+
*
|
|
234
|
+
* Declaring the path in {@link FileBoundary}'s `removes` is how a deliberate
|
|
235
|
+
* deletion says so, and it is the only thing that makes the absence legitimate.
|
|
236
|
+
*
|
|
237
|
+
* @since 0.1.0
|
|
238
|
+
* @category errors
|
|
239
|
+
*/
|
|
240
|
+
export class MissingDeclaredOutput extends Schema.TaggedError<MissingDeclaredOutput>()(
|
|
241
|
+
"@smthrs/engine-store/MissingDeclaredOutput",
|
|
242
|
+
{
|
|
243
|
+
code: Schema.Literal("missing_declared_output"),
|
|
244
|
+
paths: Schema.Array(Schema.String),
|
|
245
|
+
diffIdentity: Schema.NonEmptyString
|
|
246
|
+
}
|
|
247
|
+
) {}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* A hard-mode step left a declared removal in place.
|
|
251
|
+
*
|
|
252
|
+
* The dual of {@link MissingDeclaredOutput}: `removes` promises the path is
|
|
253
|
+
* absent afterwards, and a path that survived — or was quietly rewritten —
|
|
254
|
+
* is a post-state the declaration disclaimed. Settling it as evidence would
|
|
255
|
+
* cache the surviving bytes under a removal and hand them to every replay.
|
|
256
|
+
*
|
|
257
|
+
* @since 0.1.0
|
|
258
|
+
* @category errors
|
|
259
|
+
*/
|
|
260
|
+
export class SurvivingDeclaredRemoval extends Schema.TaggedError<SurvivingDeclaredRemoval>()(
|
|
261
|
+
"@smthrs/engine-store/SurvivingDeclaredRemoval",
|
|
262
|
+
{
|
|
263
|
+
code: Schema.Literal("surviving_declared_removal"),
|
|
264
|
+
paths: Schema.Array(Schema.String),
|
|
265
|
+
diffIdentity: Schema.NonEmptyString
|
|
266
|
+
}
|
|
267
|
+
) {}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* The host could not honour the boundary at all — a filesystem that cannot be
|
|
271
|
+
* measured, a path that cannot be read, a transient I/O failure.
|
|
272
|
+
*
|
|
273
|
+
* It is the catch-all host refusal, deliberately kept distinct from the two
|
|
274
|
+
* refusals a caller can act on: {@link BoundaryCorruption} (the bytes changed
|
|
275
|
+
* under a recorded digest) and {@link MissingArtifact} (the bytes might be
|
|
276
|
+
* fetchable from a shared tier).
|
|
277
|
+
*
|
|
278
|
+
* @since 0.1.0
|
|
279
|
+
* @category errors
|
|
280
|
+
*/
|
|
281
|
+
export class UnsupportedBoundary extends Schema.TaggedError<UnsupportedBoundary>()(
|
|
282
|
+
"@smthrs/engine-store/UnsupportedBoundary",
|
|
283
|
+
{
|
|
284
|
+
code: Schema.Literal("unsupported_boundary"),
|
|
285
|
+
message: Schema.String,
|
|
286
|
+
/**
|
|
287
|
+
* The refusing host or artifact-store failure, carried whole rather than
|
|
288
|
+
* flattened into the message (`PlatformError`'s own convention), so the
|
|
289
|
+
* journal record and a live debugger both see the original error.
|
|
290
|
+
*/
|
|
291
|
+
cause: Schema.optional(Schema.Defect())
|
|
292
|
+
}
|
|
293
|
+
) {}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Recorded boundary evidence whose bytes no longer match their recorded
|
|
297
|
+
* digest — an integrity violation of the store's strongest invariant, as
|
|
298
|
+
* opposed to a transient host failure (issue #150). The two classes were
|
|
299
|
+
* previously conflated under one `UnsupportedBoundary` tag, so a failing
|
|
300
|
+
* disk corrupting many blobs journalled identically to a one-off EIO.
|
|
301
|
+
*
|
|
302
|
+
* @since 0.1.0
|
|
303
|
+
* @category errors
|
|
304
|
+
*/
|
|
305
|
+
export class BoundaryCorruption extends Schema.TaggedError<BoundaryCorruption>()(
|
|
306
|
+
"@smthrs/engine-store/BoundaryCorruption",
|
|
307
|
+
{
|
|
308
|
+
code: Schema.Literal("boundary_corruption"),
|
|
309
|
+
path: Schema.String,
|
|
310
|
+
recordedDigest: Schema.String,
|
|
311
|
+
measuredDigest: Schema.String
|
|
312
|
+
}
|
|
313
|
+
) {}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Recorded evidence references an artifact this host's store does not hold
|
|
317
|
+
* (issue #172). Distinct from {@link UnsupportedBoundary} because it is the one
|
|
318
|
+
* replay refusal a *shared artifact tier* can repair: the caller fetches the
|
|
319
|
+
* blob from the remote store, writes it back locally, and retries the replay
|
|
320
|
+
* once before falling through to a real execution. Every other host refusal
|
|
321
|
+
* says nothing about where the bytes might be.
|
|
322
|
+
*
|
|
323
|
+
* @since 0.1.0
|
|
324
|
+
* @category errors
|
|
325
|
+
*/
|
|
326
|
+
export class MissingArtifact extends Schema.TaggedError<MissingArtifact>()(
|
|
327
|
+
"@smthrs/engine-store/MissingArtifact",
|
|
328
|
+
{
|
|
329
|
+
code: Schema.Literal("missing_artifact"),
|
|
330
|
+
path: Schema.String,
|
|
331
|
+
digest: Schema.String
|
|
332
|
+
}
|
|
333
|
+
) {}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* The three-call lifecycle of a hermetic step boundary.
|
|
337
|
+
*
|
|
338
|
+
* `prepare` measures the declared read set before the step runs; `settle`
|
|
339
|
+
* measures the writes afterwards and turns them into {@link BoundaryEvidence};
|
|
340
|
+
* `replayOutputs` reproduces a previous step's outputs from that evidence, so
|
|
341
|
+
* a cache hit can materialize results on a workspace that never executed
|
|
342
|
+
* anything.
|
|
343
|
+
*
|
|
344
|
+
* @since 0.1.0
|
|
345
|
+
* @category services
|
|
346
|
+
*/
|
|
347
|
+
export interface Service {
|
|
348
|
+
readonly prepare: (
|
|
349
|
+
descriptor: FileBoundary
|
|
350
|
+
) => Effect.Effect<PreparedBoundary, UnsupportedBoundary, Crypto.Crypto>
|
|
351
|
+
readonly settle: (
|
|
352
|
+
prepared: PreparedBoundary
|
|
353
|
+
) => Effect.Effect<
|
|
354
|
+
BoundaryEvidence,
|
|
355
|
+
UndeclaredWrite | MissingDeclaredOutput | SurvivingDeclaredRemoval | UnsupportedBoundary | BoundaryCorruption,
|
|
356
|
+
Crypto.Crypto
|
|
357
|
+
>
|
|
358
|
+
readonly replayOutputs: (
|
|
359
|
+
evidence: BoundaryEvidence
|
|
360
|
+
) => Effect.Effect<void, UnsupportedBoundary | BoundaryCorruption | MissingArtifact, Crypto.Crypto>
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* The service key for a {@link Service}. Provide it with `layer` for
|
|
365
|
+
* a real workspace or `layerTest` for a deterministic in-memory one.
|
|
366
|
+
*
|
|
367
|
+
* @since 0.1.0
|
|
368
|
+
* @category services
|
|
369
|
+
*/
|
|
370
|
+
export const StepBoundary: Context.Service<Service, Service> = Context.Service<Service>(
|
|
371
|
+
"@smthrs/engine-store/StepBoundary"
|
|
372
|
+
)
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Brands a {@link Service} implementation as a `StepBoundary`, so a new
|
|
376
|
+
* boundary backend is type-checked where it is written rather than where it is
|
|
377
|
+
* provided.
|
|
378
|
+
*
|
|
379
|
+
* @since 0.1.0
|
|
380
|
+
* @category constructors
|
|
381
|
+
*/
|
|
382
|
+
export const make = (service: Service): Service => StepBoundary.of(service)
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* The digest a measurement reports for a declared path that does not exist.
|
|
386
|
+
* Declared digests are hex SHA-256 strings, so the sentinel can never
|
|
387
|
+
* collide with a real measurement — a declaration naming a vanished file
|
|
388
|
+
* always mismatches and the cache hit is refused.
|
|
389
|
+
*/
|
|
390
|
+
const absentDigest = "absent"
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* The persisted shape of the production layer's `declaredOutputs`: the
|
|
394
|
+
* declared write set's materialized post-state, so a cache-hit replay can
|
|
395
|
+
* reproduce the outputs on a workspace that never ran the step.
|
|
396
|
+
*
|
|
397
|
+
* Outputs are recorded by content digest, never inlined unbounded
|
|
398
|
+
* (issue #113): a `null` digest records that the path did not exist at
|
|
399
|
+
* settle time; `content` (base64) is present only when the payload fits the
|
|
400
|
+
* configured inline bound, otherwise the bytes live in the host's
|
|
401
|
+
* content-addressed object directory under the digest and the row carries
|
|
402
|
+
* only the reference. Temporal's blob-size limits are the prior art — the
|
|
403
|
+
* evidence is persisted into every attempt row and cache entry.
|
|
404
|
+
*
|
|
405
|
+
* The digest is decoded against the store's strict address schema
|
|
406
|
+
* (`ArtifactStore.Digest`), so a persisted row carrying a malformed address
|
|
407
|
+
* fails the decode here instead of reaching the store or the sync RPCs.
|
|
408
|
+
*/
|
|
409
|
+
const DigestReferencedOutput = Schema.Struct({
|
|
410
|
+
path: Schema.String,
|
|
411
|
+
digest: Schema.NullOr(ArtifactStore.Digest),
|
|
412
|
+
sizeBytes: Schema.optional(Schema.Number),
|
|
413
|
+
content: Schema.optional(Schema.String)
|
|
414
|
+
})
|
|
415
|
+
|
|
416
|
+
const MaterializedOutputs = Schema.Struct({
|
|
417
|
+
outputs: Schema.Array(DigestReferencedOutput),
|
|
418
|
+
trees: Schema.optional(Schema.Array(Schema.Struct({
|
|
419
|
+
path: Schema.String,
|
|
420
|
+
identity: Schema.String
|
|
421
|
+
})))
|
|
422
|
+
})
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* The directory portion of a slash-separated path, or `undefined` for a
|
|
426
|
+
* bare filename (nothing to create). Boundary paths are workspace-relative
|
|
427
|
+
* and slash-separated — the same normalization the kernel `FileSystem`
|
|
428
|
+
* layer applies.
|
|
429
|
+
*/
|
|
430
|
+
const parentDirectory = (path: string): string | undefined => {
|
|
431
|
+
const index = path.lastIndexOf("/")
|
|
432
|
+
return index <= 0 ? undefined : path.slice(0, index)
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
const hostFailure = (cause: unknown): UnsupportedBoundary =>
|
|
436
|
+
new UnsupportedBoundary({
|
|
437
|
+
code: "unsupported_boundary",
|
|
438
|
+
message: "the host filesystem could not enforce the step boundary",
|
|
439
|
+
cause
|
|
440
|
+
})
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* An artifact store that refused the operation outright — a failing disk, an
|
|
444
|
+
* unreachable shared tier, an address that is not a content address. Unlike a
|
|
445
|
+
* miss or a corruption it says nothing about the artifact, so it stays the
|
|
446
|
+
* ordinary retryable host refusal the issue-#107 call sites already handle.
|
|
447
|
+
*/
|
|
448
|
+
const artifactFailure = (cause: ArtifactStore.ArtifactStoreError): UnsupportedBoundary =>
|
|
449
|
+
new UnsupportedBoundary({
|
|
450
|
+
code: "unsupported_boundary",
|
|
451
|
+
message: `the artifact store could not serve the step boundary: ${cause.message}`,
|
|
452
|
+
cause
|
|
453
|
+
})
|
|
454
|
+
|
|
455
|
+
/**
|
|
456
|
+
* Corruption of an *inline* evidence payload (issue #159): the recorded
|
|
457
|
+
* base64 no longer decodes, so there are no bytes to measure a digest of —
|
|
458
|
+
* the `invalid_base64` sentinel records that the measurement itself was
|
|
459
|
+
* impossible, which is still cache-origin corruption and never a transient
|
|
460
|
+
* host refusal.
|
|
461
|
+
*/
|
|
462
|
+
const inlineCorruption = (path: string, recordedDigest: string): BoundaryCorruption =>
|
|
463
|
+
new BoundaryCorruption({
|
|
464
|
+
code: "boundary_corruption",
|
|
465
|
+
path,
|
|
466
|
+
recordedDigest,
|
|
467
|
+
measuredDigest: "invalid_base64"
|
|
468
|
+
})
|
|
469
|
+
|
|
470
|
+
/**
|
|
471
|
+
* The two size bounds that decide when a settled output is inlined into
|
|
472
|
+
* boundary evidence and when it is spilled to the `ArtifactStore` by digest.
|
|
473
|
+
*
|
|
474
|
+
* Both default to values that keep an evidence row small enough to journal;
|
|
475
|
+
* raise them only when the workspace's outputs are known to be small and the
|
|
476
|
+
* extra artifact round-trip is measurably costing something.
|
|
477
|
+
*
|
|
478
|
+
* @since 0.1.0
|
|
479
|
+
* @category models
|
|
480
|
+
*/
|
|
481
|
+
export interface FileSystemOptions {
|
|
482
|
+
/**
|
|
483
|
+
* The largest output (in bytes) inlined into the boundary evidence
|
|
484
|
+
* itself. Anything larger is handed to the `ArtifactStore` and recorded by
|
|
485
|
+
* digest reference only (issue #113). Defaults to 1 MiB.
|
|
486
|
+
*/
|
|
487
|
+
readonly maxInlineBytes?: number | undefined
|
|
488
|
+
/**
|
|
489
|
+
* The largest *aggregate* inline payload (in bytes) a single settle may
|
|
490
|
+
* fold into its boundary evidence (issue #122). A per-output bound alone
|
|
491
|
+
* still let a wide write set multiply many individually-small payloads
|
|
492
|
+
* into an unbounded evidence row; once an output would push the running
|
|
493
|
+
* total past this bound it is spilled to the `ArtifactStore` and
|
|
494
|
+
* recorded by digest reference only, even though it individually fits
|
|
495
|
+
* {@link maxInlineBytes}. Defaults to 8 MiB.
|
|
496
|
+
*/
|
|
497
|
+
readonly maxTotalInlineBytes?: number | undefined
|
|
498
|
+
/**
|
|
499
|
+
* The workspace root cache replay confines its writes, removals, and prune
|
|
500
|
+
* walk to. A filesystem already confined to a root (the kernel-guarded
|
|
501
|
+
* layer's service, or a `confined` view) keeps its own root and ignores
|
|
502
|
+
* this. Otherwise it defaults to the process working directory, the root a
|
|
503
|
+
* path-based host resolves `prepare` and `settle` against. {@link layer}
|
|
504
|
+
* passes the kernel `Workspace` root when one is in context.
|
|
505
|
+
*/
|
|
506
|
+
readonly root?: string | undefined
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
const defaultMaxInlineBytes = 1024 * 1024
|
|
510
|
+
const defaultMaxTotalInlineBytes = 8 * 1024 * 1024
|
|
511
|
+
interface MeasuredDigest {
|
|
512
|
+
readonly digest: ArtifactStore.Digest
|
|
513
|
+
readonly bytes: Uint8Array
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
interface BatchedDigest {
|
|
517
|
+
readonly digest: ArtifactStore.Digest
|
|
518
|
+
readonly sizeBytes: number
|
|
519
|
+
readonly bytes?: Uint8Array
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
type MaterializedOutput = typeof DigestReferencedOutput.Type
|
|
523
|
+
|
|
524
|
+
/**
|
|
525
|
+
* The digests this evidence references rather than inlines — the set that must
|
|
526
|
+
* be durable in a shared artifact tier before the evidence's cache entry
|
|
527
|
+
* becomes observable there, and the set a replay on a fresh host has to fetch.
|
|
528
|
+
*
|
|
529
|
+
* Evidence recorded by a foreign boundary implementation references nothing
|
|
530
|
+
* this store can name, so it yields the empty list rather than failing: the
|
|
531
|
+
* caller's replay path already refuses such evidence with
|
|
532
|
+
* {@link UnsupportedBoundary}.
|
|
533
|
+
*
|
|
534
|
+
* @since 0.1.0
|
|
535
|
+
* @category accessors
|
|
536
|
+
*/
|
|
537
|
+
export const referencedDigests = (evidence: BoundaryEvidence): ReadonlyArray<ArtifactStore.Digest> => {
|
|
538
|
+
const decoded = Schema.decodeUnknownResult(MaterializedOutputs)(evidence.declaredOutputs)
|
|
539
|
+
if (decoded._tag === "Failure") return []
|
|
540
|
+
const digests: Array<ArtifactStore.Digest> = []
|
|
541
|
+
for (const output of decoded.success.outputs) {
|
|
542
|
+
if (output.digest === null || output.content !== undefined) continue
|
|
543
|
+
digests.push(output.digest)
|
|
544
|
+
}
|
|
545
|
+
return digests
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
/**
|
|
549
|
+
* Builds the filesystem-backed boundary service.
|
|
550
|
+
*
|
|
551
|
+
* The blob mechanics — content addressing, atomic publication, digest
|
|
552
|
+
* verification, dedupe — belong to `artifacts` and were extracted into
|
|
553
|
+
* `@smthrs/artifacts` (`packages/smithers/flows/artifacts/docs/api.md`). What stays here
|
|
554
|
+
* is the *policy* that decides which outputs become blobs at all: the
|
|
555
|
+
* inline-versus-spill budgets are a property of how large an evidence row may
|
|
556
|
+
* get, not of how bytes are stored.
|
|
557
|
+
*
|
|
558
|
+
* @since 0.1.0
|
|
559
|
+
* @category constructors
|
|
560
|
+
*/
|
|
561
|
+
export const makeFileSystem = (
|
|
562
|
+
fs: FileSystem.FileSystem,
|
|
563
|
+
platformPath: Path.Path,
|
|
564
|
+
artifacts: ArtifactStore.Service,
|
|
565
|
+
options: FileSystemOptions = {}
|
|
566
|
+
): Service => {
|
|
567
|
+
const maxInlineBytes = options.maxInlineBytes ?? defaultMaxInlineBytes
|
|
568
|
+
const maxTotalInlineBytes = options.maxTotalInlineBytes ?? defaultMaxTotalInlineBytes
|
|
569
|
+
// Stat tuples are not content identities: a same-size rewrite can preserve
|
|
570
|
+
// mtime, device, and inode on every supported filesystem.
|
|
571
|
+
const readDigestFrom = Effect.fn("StepBoundary.readDigest")(function*(host: FileSystem.FileSystem, path: string) {
|
|
572
|
+
const bytes = yield* host.readFile(path)
|
|
573
|
+
const digest = yield* Schema.decodeUnknownEffect(Sha256)(bytes).pipe(Effect.orDie)
|
|
574
|
+
return { digest, bytes } satisfies MeasuredDigest
|
|
575
|
+
})
|
|
576
|
+
const readDigest = (path: string) => readDigestFrom(fs, path)
|
|
577
|
+
const batch = KernelFileSystem.batch(fs)
|
|
578
|
+
const measurements = Effect.fn("StepBoundary.measurements")(
|
|
579
|
+
function*(paths: ReadonlyArray<string>, content: boolean) {
|
|
580
|
+
if (batch === undefined) {
|
|
581
|
+
return yield* Effect.forEach(paths, (path) =>
|
|
582
|
+
readDigest(path).pipe(
|
|
583
|
+
Effect.map((value): BatchedDigest => ({
|
|
584
|
+
digest: value.digest,
|
|
585
|
+
sizeBytes: value.bytes.length,
|
|
586
|
+
...(content ? { bytes: value.bytes } : {})
|
|
587
|
+
})),
|
|
588
|
+
Effect.catchReason("PlatformError", "NotFound", () => Effect.succeed(undefined)),
|
|
589
|
+
Effect.mapError(hostFailure)
|
|
590
|
+
), { concurrency: KernelFileSystem.fallbackConcurrency })
|
|
591
|
+
}
|
|
592
|
+
const values: Array<BatchedDigest | undefined> = []
|
|
593
|
+
for (let offset = 0; offset < paths.length; offset += batch.maxSize) {
|
|
594
|
+
const response = yield* batch.execute(
|
|
595
|
+
paths.slice(offset, offset + batch.maxSize).map((path) => ({
|
|
596
|
+
operation: "digest",
|
|
597
|
+
path,
|
|
598
|
+
content
|
|
599
|
+
}))
|
|
600
|
+
).pipe(Effect.mapError(hostFailure))
|
|
601
|
+
for (const entry of [...response.entries].sort((a, b) => a.index - b.index)) {
|
|
602
|
+
const measured = yield* Effect.fromResult(entry.result).pipe(
|
|
603
|
+
Effect.catchReason("PlatformError", "NotFound", () => Effect.succeed(undefined)),
|
|
604
|
+
Effect.mapError(hostFailure)
|
|
605
|
+
)
|
|
606
|
+
if (measured === undefined) values.push(undefined)
|
|
607
|
+
else if (measured.operation !== "digest") {
|
|
608
|
+
return yield* Effect.fail(hostFailure(new Error("host returned a non-digest batch result")))
|
|
609
|
+
} else values.push({ ...measured, digest: measured.digest as ArtifactStore.Digest })
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
return values
|
|
613
|
+
}
|
|
614
|
+
)
|
|
615
|
+
/**
|
|
616
|
+
* One host call, never two. An `exists` probe ahead of the read asked
|
|
617
|
+
* exactly what the read itself answers — a path that is not there refuses
|
|
618
|
+
* with `NotFound` — and every declared read was therefore measured twice.
|
|
619
|
+
* On the confined host that is two CPython forks per path
|
|
620
|
+
* (`@smthrs/platform-node/AtomicFileSystem` spawns one interpreter per
|
|
621
|
+
* operation, ~130 ms each), so the probe doubled the cost of every
|
|
622
|
+
* `prepare`, every post-body change check, and every materialization.
|
|
623
|
+
*/
|
|
624
|
+
const measure = (
|
|
625
|
+
path: string,
|
|
626
|
+
host: FileSystem.FileSystem = fs
|
|
627
|
+
): Effect.Effect<string, UnsupportedBoundary, Crypto.Crypto> =>
|
|
628
|
+
readDigestFrom(host, path).pipe(
|
|
629
|
+
Effect.map((measured) => measured.digest),
|
|
630
|
+
Effect.catchReason("PlatformError", "NotFound", () => Effect.succeed(absentDigest)),
|
|
631
|
+
Effect.mapError(hostFailure)
|
|
632
|
+
)
|
|
633
|
+
// Both expansions enumerate through `FileEnumeration`, never the host
|
|
634
|
+
// `glob`: host results are absolute under the kernel FileSystem and skip
|
|
635
|
+
// dotfiles under Node's matcher, so trusting them silently emptied every
|
|
636
|
+
// workspace-relative expansion and let a tree replay delete dotfiles the
|
|
637
|
+
// producer wrote.
|
|
638
|
+
const expandGlob = Effect.fn("StepBoundary.expandGlob")(function*(glob: FileSet.Glob) {
|
|
639
|
+
return yield* FileEnumeration.expandGlob(fs, glob).pipe(Effect.mapError(hostFailure))
|
|
640
|
+
})
|
|
641
|
+
const treeEntries = Effect.fn("StepBoundary.treeEntries")(function*(host: FileSystem.FileSystem, path: string) {
|
|
642
|
+
return yield* FileEnumeration.entriesUnder(host, path).pipe(Effect.mapError(hostFailure))
|
|
643
|
+
})
|
|
644
|
+
// Replay writes and deletes what evidence names, so the lexical check alone
|
|
645
|
+
// is not a boundary: a symlink planted on a lexically valid path would carry
|
|
646
|
+
// the mutation outside the workspace. Every replay mutation, its probes, and
|
|
647
|
+
// its prune walk go through a view pinned to the root, where each call is
|
|
648
|
+
// one descriptor-relative, no-follow request and a symlink anywhere on the
|
|
649
|
+
// path is refused rather than followed. `prepare` and `settle` only read,
|
|
650
|
+
// and keep the caller's filesystem.
|
|
651
|
+
const replayRoot = KernelFileSystem.confinedRoot(fs) ?? options.root ?? "."
|
|
652
|
+
const replayHost: Effect.Effect<FileSystem.FileSystem, UnsupportedBoundary> = KernelFileSystem.isConfinable(fs)
|
|
653
|
+
? KernelFileSystem.confined(fs, replayRoot).pipe(
|
|
654
|
+
Effect.provideService(Path.Path, platformPath),
|
|
655
|
+
Effect.mapError(hostFailure)
|
|
656
|
+
)
|
|
657
|
+
: Effect.fail(
|
|
658
|
+
new UnsupportedBoundary({
|
|
659
|
+
code: "unsupported_boundary",
|
|
660
|
+
message:
|
|
661
|
+
"cache replay needs a confined host filesystem; the host carries no descriptor-relative executor or isolation attestation"
|
|
662
|
+
})
|
|
663
|
+
)
|
|
664
|
+
const capture = Effect.fn("StepBoundary.capture")(
|
|
665
|
+
function*(path: string, inlineBudget: number, measured: BatchedDigest | undefined) {
|
|
666
|
+
yield* Effect.annotateCurrentSpan({ path })
|
|
667
|
+
// As in `measure`: the read reports absence itself, so the output is
|
|
668
|
+
// captured in one host call rather than an `exists` probe plus a read.
|
|
669
|
+
if (measured === undefined) {
|
|
670
|
+
return { output: { path, digest: null } satisfies MaterializedOutput, inlinedBytes: 0 }
|
|
671
|
+
}
|
|
672
|
+
const bytes = measured.bytes
|
|
673
|
+
if (bytes === undefined) return yield* Effect.fail(hostFailure(new Error("host omitted requested batch content")))
|
|
674
|
+
const digest = measured.digest
|
|
675
|
+
// Inline only while both bounds hold (issue #122): the per-output bound
|
|
676
|
+
// and the settle-wide aggregate budget the caller threads through.
|
|
677
|
+
if (bytes.length <= maxInlineBytes && bytes.length <= inlineBudget) {
|
|
678
|
+
return {
|
|
679
|
+
output: {
|
|
680
|
+
path,
|
|
681
|
+
digest,
|
|
682
|
+
sizeBytes: bytes.length,
|
|
683
|
+
content: Encoding.encodeBase64(bytes)
|
|
684
|
+
} satisfies MaterializedOutput,
|
|
685
|
+
inlinedBytes: bytes.length
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
// Over the inline bound: the payload goes to the content-addressed
|
|
689
|
+
// artifact store and the persisted row carries only the reference. Every
|
|
690
|
+
// property that used to live here — atomic publication, verify-once
|
|
691
|
+
// dedupe, healing rewrite of a corrupt address — is now the store's, and
|
|
692
|
+
// is tested there (`@smthrs/artifacts`).
|
|
693
|
+
const stored = yield* artifacts.put(bytes).pipe(Effect.mapError(artifactFailure))
|
|
694
|
+
if (stored !== digest) {
|
|
695
|
+
return yield* Effect.fail(
|
|
696
|
+
new BoundaryCorruption({
|
|
697
|
+
code: "boundary_corruption",
|
|
698
|
+
path,
|
|
699
|
+
recordedDigest: `${stored}`,
|
|
700
|
+
measuredDigest: digest
|
|
701
|
+
})
|
|
702
|
+
)
|
|
703
|
+
}
|
|
704
|
+
return { output: { path, digest, sizeBytes: bytes.length } satisfies MaterializedOutput, inlinedBytes: 0 }
|
|
705
|
+
}
|
|
706
|
+
)
|
|
707
|
+
const materialize = Effect.fn("StepBoundary.materialize")(function*(
|
|
708
|
+
host: FileSystem.FileSystem,
|
|
709
|
+
output: MaterializedOutput
|
|
710
|
+
) {
|
|
711
|
+
yield* Effect.annotateCurrentSpan({ path: output.path })
|
|
712
|
+
// The filesystem is the cheapest source of truth for a warm workspace.
|
|
713
|
+
// Probe before decoding inline bytes or consulting the artifact store; a
|
|
714
|
+
// transient probe refusal merely forfeits the optimization and the
|
|
715
|
+
// ordinary materialization path retains its existing typed failures.
|
|
716
|
+
const current = yield* measure(output.path, host).pipe(
|
|
717
|
+
Effect.map((digest) => digest !== absentDigest && digest === output.digest),
|
|
718
|
+
Effect.catch(() => Effect.succeed(false))
|
|
719
|
+
)
|
|
720
|
+
if (current) return
|
|
721
|
+
let bytes: Uint8Array
|
|
722
|
+
if (output.content !== undefined) {
|
|
723
|
+
const decoded = Encoding.decodeBase64(output.content)
|
|
724
|
+
if (Result.isFailure(decoded)) {
|
|
725
|
+
// Inline content is cache-origin bytes: an undecodable payload is a
|
|
726
|
+
// tampered durable row, classified exactly like a digest mismatch so
|
|
727
|
+
// the caller routes it to the Inconsistency receiver instead of
|
|
728
|
+
// retrying it as a transient host refusal (issue #159).
|
|
729
|
+
return yield* Effect.fail(inlineCorruption(output.path, `${output.digest}`))
|
|
730
|
+
}
|
|
731
|
+
bytes = decoded.success
|
|
732
|
+
// Inline evidence is verified here because nothing else can: the bytes
|
|
733
|
+
// travel inside the row rather than at a content address. A referenced
|
|
734
|
+
// artifact is verified by the store on the way out, so the check below
|
|
735
|
+
// would be a second read+hash of the same bytes.
|
|
736
|
+
const measured = yield* Schema.decodeUnknownEffect(Sha256)(bytes).pipe(Effect.orDie)
|
|
737
|
+
if (measured !== output.digest) {
|
|
738
|
+
return yield* Effect.fail(
|
|
739
|
+
new BoundaryCorruption({
|
|
740
|
+
code: "boundary_corruption",
|
|
741
|
+
path: output.path,
|
|
742
|
+
recordedDigest: `${output.digest}`,
|
|
743
|
+
measuredDigest: measured
|
|
744
|
+
})
|
|
745
|
+
)
|
|
746
|
+
}
|
|
747
|
+
} else {
|
|
748
|
+
bytes = yield* artifacts.get(`${output.digest}`).pipe(
|
|
749
|
+
Effect.catchTags({
|
|
750
|
+
// A reference this host cannot resolve is not a host refusal: a
|
|
751
|
+
// shared artifact tier may still hold it, so the caller gets a
|
|
752
|
+
// typed, repairable failure and fetches before falling back to a
|
|
753
|
+
// real execution (issue #172).
|
|
754
|
+
"@smthrs/artifacts/ArtifactMissing": (missing) =>
|
|
755
|
+
Effect.fail(
|
|
756
|
+
new MissingArtifact({ code: "missing_artifact", path: output.path, digest: missing.digest })
|
|
757
|
+
),
|
|
758
|
+
// Corruption is a distinct typed failure from a transient host
|
|
759
|
+
// error (issue #150): the caller routes it to the Inconsistency
|
|
760
|
+
// receiver instead of treating it as an ordinary retryable refusal.
|
|
761
|
+
"@smthrs/artifacts/ArtifactCorruption": (corrupt) =>
|
|
762
|
+
Effect.fail(
|
|
763
|
+
new BoundaryCorruption({
|
|
764
|
+
code: "boundary_corruption",
|
|
765
|
+
path: output.path,
|
|
766
|
+
recordedDigest: corrupt.recordedDigest,
|
|
767
|
+
measuredDigest: corrupt.measuredDigest
|
|
768
|
+
})
|
|
769
|
+
),
|
|
770
|
+
"@smthrs/artifacts/ArtifactStoreError": (failure) => Effect.fail(artifactFailure(failure))
|
|
771
|
+
})
|
|
772
|
+
)
|
|
773
|
+
}
|
|
774
|
+
// The original body may have created the output's directory itself; a
|
|
775
|
+
// fresh workspace replaying the evidence has no such directory and
|
|
776
|
+
// `writeFile` does not create parents (issue #107).
|
|
777
|
+
const parent = parentDirectory(output.path)
|
|
778
|
+
if (parent !== undefined) {
|
|
779
|
+
yield* host.makeDirectory(parent, { recursive: true }).pipe(Effect.mapError(hostFailure))
|
|
780
|
+
}
|
|
781
|
+
yield* host.writeFile(output.path, bytes).pipe(Effect.mapError(hostFailure))
|
|
782
|
+
})
|
|
783
|
+
return make({
|
|
784
|
+
prepare: Effect.fn("StepBoundary.prepare")(function*(descriptor) {
|
|
785
|
+
const boundary = FileBoundarySnapshot.make(descriptor)
|
|
786
|
+
yield* Effect.annotateCurrentSpan({
|
|
787
|
+
reads: boundary.readSet.length,
|
|
788
|
+
boundaryMode: boundary.boundaryMode
|
|
789
|
+
})
|
|
790
|
+
// The dirty check's evidence (issue #90): what the host actually
|
|
791
|
+
// measured for every declared read, never the declaration itself —
|
|
792
|
+
// defaulting the snapshot to the declaration made `readSetMatches`
|
|
793
|
+
// compare the declaration against itself and pass unconditionally
|
|
794
|
+
// (issue #104).
|
|
795
|
+
const readSnapshot: Array<FileInput> = []
|
|
796
|
+
const paths: Array<string> = []
|
|
797
|
+
for (const entry of boundary.readSet) {
|
|
798
|
+
if (FileSet.isGlob(entry)) {
|
|
799
|
+
paths.push(...yield* expandGlob(entry))
|
|
800
|
+
} else paths.push(entry.path)
|
|
801
|
+
}
|
|
802
|
+
const measured = yield* measurements(paths, false)
|
|
803
|
+
for (const [index, path] of paths.entries()) {
|
|
804
|
+
readSnapshot.push({ path, digest: measured[index]?.digest ?? absentDigest })
|
|
805
|
+
}
|
|
806
|
+
readSnapshot.sort((left, right) => compareText(left.path, right.path))
|
|
807
|
+
return Object.freeze({
|
|
808
|
+
descriptor: boundary,
|
|
809
|
+
readSnapshot: Object.freeze(readSnapshot.map((entry) => Object.freeze({ ...entry })))
|
|
810
|
+
})
|
|
811
|
+
}),
|
|
812
|
+
settle: Effect.fn("StepBoundary.settle")(function*(prepared) {
|
|
813
|
+
yield* Effect.annotateCurrentSpan({
|
|
814
|
+
writes: prepared.descriptor.writeSet.length,
|
|
815
|
+
boundaryMode: prepared.descriptor.boundaryMode
|
|
816
|
+
})
|
|
817
|
+
// Undeclared-write detection is scoped to the declared read set: a
|
|
818
|
+
// declared read whose content moved during the body and is not also a
|
|
819
|
+
// declared write was mutated outside the contract. Whole-tree change
|
|
820
|
+
// detection needs the jj diff surface and stays a documented
|
|
821
|
+
// limitation of this layer rather than a silent pass — the read set
|
|
822
|
+
// is exactly the material cacheability rests on.
|
|
823
|
+
const removes = prepared.descriptor.removes ?? []
|
|
824
|
+
const declaredWrites = [...prepared.descriptor.writeSet, ...removes]
|
|
825
|
+
const declaredCovers = (path: string) =>
|
|
826
|
+
declaredWrites.some((entry) =>
|
|
827
|
+
typeof entry === "string"
|
|
828
|
+
? entry === path
|
|
829
|
+
: entry._tag === "TreeArtifact"
|
|
830
|
+
? path === entry.path || path.startsWith(`${entry.path}/`)
|
|
831
|
+
: FileSet.matchesGlob(entry, path)
|
|
832
|
+
)
|
|
833
|
+
const undeclared: Array<string> = []
|
|
834
|
+
const checkedReads = prepared.readSnapshot.filter((entry) => !declaredCovers(entry.path))
|
|
835
|
+
const currentReads = yield* measurements(checkedReads.map((entry) => entry.path), false)
|
|
836
|
+
for (const [index, entry] of checkedReads.entries()) {
|
|
837
|
+
if ((currentReads[index]?.digest ?? absentDigest) !== entry.digest) undeclared.push(entry.path)
|
|
838
|
+
}
|
|
839
|
+
const outputs: Array<MaterializedOutput> = []
|
|
840
|
+
// Declared removals are captured on the same path and under the same
|
|
841
|
+
// `digest: null` semantics they have always had — the difference is that
|
|
842
|
+
// here the absence was declared, so it is evidence rather than a defect.
|
|
843
|
+
const missing: Array<string> = []
|
|
844
|
+
const surviving: Array<string> = []
|
|
845
|
+
let inlinedBytes = 0
|
|
846
|
+
const outputPaths: Array<string> = []
|
|
847
|
+
const treeMembers: Array<{ readonly path: string; readonly files: ReadonlyArray<string> }> = []
|
|
848
|
+
for (const entry of prepared.descriptor.writeSet) {
|
|
849
|
+
if (typeof entry === "string") outputPaths.push(entry)
|
|
850
|
+
else if (entry._tag === "Glob") outputPaths.push(...yield* expandGlob(entry))
|
|
851
|
+
else {
|
|
852
|
+
const files = (yield* treeEntries(fs, entry.path)).files
|
|
853
|
+
outputPaths.push(...files)
|
|
854
|
+
treeMembers.push({ path: entry.path, files })
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
outputPaths.push(...removes)
|
|
858
|
+
const captured = new Map<string, MaterializedOutput>()
|
|
859
|
+
const sortedPaths = [...new Set(outputPaths)].sort(compareText)
|
|
860
|
+
// Digest-only preflight bounds content batches by bytes as well as path
|
|
861
|
+
// count. Both measurements share the guarded host's pinned root identity;
|
|
862
|
+
// a rewrite between them is a refusal, never mismatched cached content.
|
|
863
|
+
const sizes = batch === undefined ? undefined : yield* measurements(sortedPaths, false)
|
|
864
|
+
for (let offset = 0; offset < sortedPaths.length;) {
|
|
865
|
+
let end = offset
|
|
866
|
+
let responseBytes = 256
|
|
867
|
+
const size = batch?.maxSize ?? KernelFileSystem.fallbackConcurrency
|
|
868
|
+
while (end < sortedPaths.length && end - offset < size) {
|
|
869
|
+
const estimate = 4096 + new TextEncoder().encode(sortedPaths[end]).length * 6 +
|
|
870
|
+
Math.ceil((sizes?.[end]?.sizeBytes ?? 0) / 3) * 4
|
|
871
|
+
if (end > offset && batch !== undefined && responseBytes + estimate > batch.maxResponseBytes) break
|
|
872
|
+
responseBytes += estimate
|
|
873
|
+
end++
|
|
874
|
+
}
|
|
875
|
+
const group = sortedPaths.slice(offset, end)
|
|
876
|
+
const contents = yield* measurements(group, true)
|
|
877
|
+
for (const [index, path] of group.entries()) {
|
|
878
|
+
const measured = contents[index]
|
|
879
|
+
if (sizes !== undefined && measured?.digest !== sizes[offset + index]?.digest) {
|
|
880
|
+
return yield* Effect.fail(hostFailure(new Error(`output changed during measurement: ${path}`)))
|
|
881
|
+
}
|
|
882
|
+
const output = yield* capture(path, maxTotalInlineBytes - inlinedBytes, measured)
|
|
883
|
+
// `capture` reports the bytes it actually inlined (zero for a
|
|
884
|
+
// digest-only reference), so the aggregate budget never has to
|
|
885
|
+
// re-derive them from the row it just built.
|
|
886
|
+
inlinedBytes += output.inlinedBytes
|
|
887
|
+
outputs.push(output.output)
|
|
888
|
+
captured.set(path, output.output)
|
|
889
|
+
if (
|
|
890
|
+
output.output.digest === null &&
|
|
891
|
+
!removes.includes(path) &&
|
|
892
|
+
prepared.descriptor.writeSet.some((entry) => typeof entry === "string" && entry === path)
|
|
893
|
+
) missing.push(path)
|
|
894
|
+
// The dual check: a removal promised the path absent, and it is still
|
|
895
|
+
// here — possibly rewritten. Settling it would cache the surviving
|
|
896
|
+
// bytes under a declaration that disclaimed them.
|
|
897
|
+
if (output.output.digest !== null && removes.includes(path)) surviving.push(path)
|
|
898
|
+
}
|
|
899
|
+
offset = end
|
|
900
|
+
}
|
|
901
|
+
// A tree member is already one of the captured outputs, so its identity
|
|
902
|
+
// is folded from the digest that capture measured rather than from a
|
|
903
|
+
// second read of the same bytes. `capture` spells an absent member
|
|
904
|
+
// `null` where `measure` spells it `absentDigest`; the identity keeps
|
|
905
|
+
// the `measure` spelling, so a tree recorded before this and one
|
|
906
|
+
// recorded after hash identically.
|
|
907
|
+
const trees: Array<{ readonly path: string; readonly identity: string }> = []
|
|
908
|
+
for (const tree of treeMembers) {
|
|
909
|
+
const pairs = tree.files.map((path) =>
|
|
910
|
+
[path.slice(tree.path.length + 1), captured.get(path)!.digest ?? absentDigest] as const
|
|
911
|
+
)
|
|
912
|
+
trees.push({
|
|
913
|
+
path: tree.path,
|
|
914
|
+
identity: yield* Schema.decodeUnknownEffect(DerivedKey)({ kind: "tree-artifact", files: pairs }).pipe(
|
|
915
|
+
Effect.orDie
|
|
916
|
+
)
|
|
917
|
+
})
|
|
918
|
+
}
|
|
919
|
+
// Through `DerivedKey` — the repo's one hashing chokepoint — so the identity is
|
|
920
|
+
// a digest of the RFC 8785 canonical form rather than of whatever
|
|
921
|
+
// `JSON.stringify` happened to emit for this shape.
|
|
922
|
+
const diffIdentity = yield* Schema.decodeUnknownEffect(DerivedKey)({
|
|
923
|
+
kind: "diff-identity",
|
|
924
|
+
outputs: outputs.map((output) => [output.path, output.digest]),
|
|
925
|
+
trees
|
|
926
|
+
}).pipe(Effect.orDie)
|
|
927
|
+
yield* Effect.annotateCurrentSpan({ diffIdentity })
|
|
928
|
+
if (prepared.descriptor.boundaryMode === "hard") {
|
|
929
|
+
if (undeclared.length > 0) {
|
|
930
|
+
return yield* Effect.fail(
|
|
931
|
+
new UndeclaredWrite({ code: "undeclared_write", paths: undeclared, diffIdentity })
|
|
932
|
+
)
|
|
933
|
+
}
|
|
934
|
+
// A declared output that was never produced is a defect, not evidence
|
|
935
|
+
// (Bazel's `checkOutputs`). Recording it would cache `digest: null`,
|
|
936
|
+
// which every later `replayOutputs` reads as "delete this path".
|
|
937
|
+
if (missing.length > 0) {
|
|
938
|
+
return yield* Effect.fail(
|
|
939
|
+
new MissingDeclaredOutput({ code: "missing_declared_output", paths: missing, diffIdentity })
|
|
940
|
+
)
|
|
941
|
+
}
|
|
942
|
+
if (surviving.length > 0) {
|
|
943
|
+
return yield* Effect.fail(
|
|
944
|
+
new SurvivingDeclaredRemoval({ code: "surviving_declared_removal", paths: surviving, diffIdentity })
|
|
945
|
+
)
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
return {
|
|
949
|
+
declaredOutputs: { outputs, ...(trees.length === 0 ? {} : { trees }) },
|
|
950
|
+
diffIdentity,
|
|
951
|
+
// This filesystem-only boundary cannot observe writes elsewhere in
|
|
952
|
+
// the tree. Omission is deliberate: ActionPersistence treats the
|
|
953
|
+
// result as run-local and will not publish it to the shared cache.
|
|
954
|
+
//
|
|
955
|
+
// An undeclared write is reported ahead of a missing output, and a
|
|
956
|
+
// missing output ahead of a surviving removal: each is the stronger
|
|
957
|
+
// claim about the same execution, and every variant bars the evidence
|
|
958
|
+
// from the shared cache identically.
|
|
959
|
+
...(undeclared.length > 0
|
|
960
|
+
? { deviation: { _tag: "ExpectedSetDeviation" as const, paths: undeclared, diffIdentity } }
|
|
961
|
+
: missing.length > 0
|
|
962
|
+
? { deviation: { _tag: "MissingDeclaredOutput" as const, paths: missing, diffIdentity } }
|
|
963
|
+
: surviving.length > 0
|
|
964
|
+
? { deviation: { _tag: "SurvivingDeclaredRemoval" as const, paths: surviving, diffIdentity } }
|
|
965
|
+
: {})
|
|
966
|
+
}
|
|
967
|
+
}),
|
|
968
|
+
replayOutputs: Effect.fn("StepBoundary.replayOutputs")(function*(evidence) {
|
|
969
|
+
yield* Effect.annotateCurrentSpan({ diffIdentity: evidence.diffIdentity })
|
|
970
|
+
const decoded = Schema.decodeUnknownResult(MaterializedOutputs)(evidence.declaredOutputs)
|
|
971
|
+
if (decoded._tag === "Failure") {
|
|
972
|
+
// Evidence recorded by a different boundary implementation carries
|
|
973
|
+
// no materializable outputs; refusing is honest — the caller's
|
|
974
|
+
// dispatch path falls back to a real execution or fails visibly.
|
|
975
|
+
return yield* Effect.fail(
|
|
976
|
+
new UnsupportedBoundary({
|
|
977
|
+
code: "unsupported_boundary",
|
|
978
|
+
message: "the recorded boundary evidence carries no materializable outputs"
|
|
979
|
+
})
|
|
980
|
+
)
|
|
981
|
+
}
|
|
982
|
+
// Replay writes and DELETES whatever the evidence names, so a path is
|
|
983
|
+
// honored only in the coordinate system declarations are written in.
|
|
984
|
+
// Evidence can arrive from a foreign producer through cache sync; an
|
|
985
|
+
// absolute or upward spelling is an eraser aimed outside the workspace,
|
|
986
|
+
// and refusing it here is what keeps that a refusal instead of a wipe.
|
|
987
|
+
const foreign = [
|
|
988
|
+
...(decoded.success.trees ?? []).map((tree) => tree.path),
|
|
989
|
+
...decoded.success.outputs.map((output) => output.path)
|
|
990
|
+
].filter((path) => !FileSet.workspaceRelative(path))
|
|
991
|
+
if (foreign.length > 0) {
|
|
992
|
+
return yield* Effect.fail(
|
|
993
|
+
new UnsupportedBoundary({
|
|
994
|
+
code: "unsupported_boundary",
|
|
995
|
+
message: `the recorded boundary evidence names paths outside the workspace: ${foreign.join(", ")}`
|
|
996
|
+
})
|
|
997
|
+
)
|
|
998
|
+
}
|
|
999
|
+
// Before any mutation: a host that cannot be confined refuses the whole
|
|
1000
|
+
// replay, and the caller falls back to a real execution.
|
|
1001
|
+
const host = yield* replayHost
|
|
1002
|
+
const emptyDirectoryCandidates = new Set<string>()
|
|
1003
|
+
for (const tree of decoded.success.trees ?? []) {
|
|
1004
|
+
const prefix = `${tree.path}/`.replace(/\/{2,}$/g, "/")
|
|
1005
|
+
const recorded = new Set(
|
|
1006
|
+
decoded.success.outputs
|
|
1007
|
+
.map((output) => output.path)
|
|
1008
|
+
.filter((path) => path.startsWith(prefix))
|
|
1009
|
+
)
|
|
1010
|
+
const entries = yield* treeEntries(host, tree.path)
|
|
1011
|
+
for (const path of entries.files) {
|
|
1012
|
+
if (!recorded.has(path)) yield* host.remove(path).pipe(Effect.mapError(hostFailure))
|
|
1013
|
+
}
|
|
1014
|
+
for (const directory of entries.directories) emptyDirectoryCandidates.add(directory)
|
|
1015
|
+
}
|
|
1016
|
+
for (const output of decoded.success.outputs) {
|
|
1017
|
+
if (output.digest === null) {
|
|
1018
|
+
// `force` IS the probe: the evidence says this path must not exist,
|
|
1019
|
+
// and a removal that finds it already gone has done its job. The
|
|
1020
|
+
// `exists` call that used to precede it was a second host call — a
|
|
1021
|
+
// second process on the confined host — for the same answer.
|
|
1022
|
+
yield* host.remove(output.path, { force: true }).pipe(Effect.mapError(hostFailure))
|
|
1023
|
+
} else {
|
|
1024
|
+
yield* materialize(host, output)
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
// Remove only directories proven empty after pruning and
|
|
1028
|
+
// materialization. Deepest-first preserves every directory that still
|
|
1029
|
+
// contains a recorded child while clearing stale empty scaffolding.
|
|
1030
|
+
const directories = [...emptyDirectoryCandidates].sort((left, right) =>
|
|
1031
|
+
right.length - left.length || compareText(left, right)
|
|
1032
|
+
)
|
|
1033
|
+
for (const directory of directories) {
|
|
1034
|
+
const entries = yield* host.readDirectory(directory).pipe(Effect.mapError(hostFailure))
|
|
1035
|
+
if (entries.length === 0) yield* host.remove(directory).pipe(Effect.mapError(hostFailure))
|
|
1036
|
+
}
|
|
1037
|
+
})
|
|
1038
|
+
})
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
/**
|
|
1042
|
+
* Provides the filesystem-backed production boundary: `prepare` measures the
|
|
1043
|
+
* declared read set for real, `settle` detects reads mutated outside the
|
|
1044
|
+
* declared write set and captures the write set's post-state, and
|
|
1045
|
+
* `replayOutputs` re-materializes those outputs on cache-hit replay.
|
|
1046
|
+
*
|
|
1047
|
+
* Host access arrives through Effect's `FileSystem` tag, which the capability
|
|
1048
|
+
* kernel decorates in place — the same seam every host implementation (node,
|
|
1049
|
+
* bun, browser, sandbox) already provides. Blob storage arrives through
|
|
1050
|
+
* `@smthrs/artifacts`, so the same boundary runs over a purely local store or
|
|
1051
|
+
* over a local-plus-shared composition without knowing which it got.
|
|
1052
|
+
*
|
|
1053
|
+
* Replay confines its own mutations rather than trusting the composition:
|
|
1054
|
+
* they run through `@smthrs/kernel/FileSystem`'s `confined` view, rooted at
|
|
1055
|
+
* the kernel `Workspace` when one is in context. A symlink on a replayed path
|
|
1056
|
+
* (a parent, the final component, or a member of a replayed tree) and a host
|
|
1057
|
+
* with no descriptor-relative executor or isolation attestation both refuse
|
|
1058
|
+
* with {@link UnsupportedBoundary}, which the caller journals as
|
|
1059
|
+
* `replay_failed` before executing the step for real.
|
|
1060
|
+
*
|
|
1061
|
+
* @since 0.1.0
|
|
1062
|
+
* @category layers
|
|
1063
|
+
*/
|
|
1064
|
+
export const layer: Layer.Layer<
|
|
1065
|
+
Service,
|
|
1066
|
+
never,
|
|
1067
|
+
FileSystem.FileSystem | Path.Path | ArtifactStore.ArtifactStore
|
|
1068
|
+
> = Layer.effect(
|
|
1069
|
+
StepBoundary,
|
|
1070
|
+
Effect.gen(function*() {
|
|
1071
|
+
const fs = yield* FileSystem.FileSystem
|
|
1072
|
+
const platformPath = yield* Path.Path
|
|
1073
|
+
const artifacts = yield* ArtifactStore.ArtifactStore
|
|
1074
|
+
const workspace = yield* Effect.serviceOption(KernelWorkspace)
|
|
1075
|
+
return makeFileSystem(fs, platformPath, artifacts, workspace._tag === "Some" ? { root: workspace.value.root } : {})
|
|
1076
|
+
})
|
|
1077
|
+
)
|
|
1078
|
+
|
|
1079
|
+
/**
|
|
1080
|
+
* What the deterministic test boundary should pretend the host observed.
|
|
1081
|
+
*
|
|
1082
|
+
* Every field is a fixture, not a classification rule: a test supplies settlement
|
|
1083
|
+
* evidence or failure, the read snapshot, and whether the host claims
|
|
1084
|
+
* to support the boundary at all — then asserts on the resulting evidence or
|
|
1085
|
+
* refusal. Defaults describe a well-behaved, fully-supported host.
|
|
1086
|
+
*
|
|
1087
|
+
* @since 0.1.0
|
|
1088
|
+
* @category models
|
|
1089
|
+
*/
|
|
1090
|
+
export interface TestOptions {
|
|
1091
|
+
/** A settlement failure supplied verbatim by an integration fixture. */
|
|
1092
|
+
readonly failure?: UndeclaredWrite | MissingDeclaredOutput | SurvivingDeclaredRemoval | undefined
|
|
1093
|
+
/** Settlement evidence supplied verbatim; classification belongs to the real layer. */
|
|
1094
|
+
readonly deviation?: BoundaryDeviation | undefined
|
|
1095
|
+
/**
|
|
1096
|
+
* What `prepare` reports as measured for the declared read set. Defaults
|
|
1097
|
+
* to the declaration itself; a test supplies a different snapshot to stand
|
|
1098
|
+
* for a file whose content moved out from under a stale declaration
|
|
1099
|
+
* (issue #90).
|
|
1100
|
+
*/
|
|
1101
|
+
readonly readSnapshot?: ReadonlyArray<FileInput> | undefined
|
|
1102
|
+
/** The opaque replay payload recorded in the evidence. */
|
|
1103
|
+
readonly declaredOutputs?: unknown
|
|
1104
|
+
/** The post-state identity the evidence names. Defaults to a fixed string. */
|
|
1105
|
+
readonly diffIdentity?: string | undefined
|
|
1106
|
+
/** When false, every call fails with {@link UnsupportedBoundary}. Defaults to true. */
|
|
1107
|
+
readonly supported?: boolean | undefined
|
|
1108
|
+
/** Whether the fixture attests a whole-tree observation. Defaults to true. */
|
|
1109
|
+
readonly wholeTreeWriteDetection?: boolean | undefined
|
|
1110
|
+
/** Whether the fixture models an isolated read surface. Defaults to true. */
|
|
1111
|
+
readonly hermeticReadDetection?: boolean | undefined
|
|
1112
|
+
/** Observes each `replayOutputs` call, so a test can assert replay happened. */
|
|
1113
|
+
readonly onReplay?: (evidence: BoundaryEvidence) => void
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
const unsupported = (): UnsupportedBoundary =>
|
|
1117
|
+
new UnsupportedBoundary({
|
|
1118
|
+
code: "unsupported_boundary",
|
|
1119
|
+
message: "the host cannot enforce the declared step boundary"
|
|
1120
|
+
})
|
|
1121
|
+
|
|
1122
|
+
/**
|
|
1123
|
+
* Deterministic in-memory boundary suitable only for tests. Production
|
|
1124
|
+
* wiring uses {@link layer}, the filesystem-backed implementation.
|
|
1125
|
+
*
|
|
1126
|
+
* Whole-tree change detection is supplied by sandboxed settlement, not by any
|
|
1127
|
+
* boundary layer: an isolated execution's transaction IS the tree, so
|
|
1128
|
+
* `ActionPersistence` compares its diff against the declared sets and sets
|
|
1129
|
+
* `wholeTreeWritesVerified` structurally (the retired piece-6 limitation).
|
|
1130
|
+
* {@link layer} still detects mutations within the declared read set only,
|
|
1131
|
+
* which is why unsandboxed evidence stays run-local.
|
|
1132
|
+
*
|
|
1133
|
+
* @since 0.1.0
|
|
1134
|
+
* @category layers
|
|
1135
|
+
*/
|
|
1136
|
+
export const layerTest = (options: TestOptions = {}): Layer.Layer<Service> => {
|
|
1137
|
+
const diffIdentity = options.diffIdentity ?? "test-diff"
|
|
1138
|
+
const service = make({
|
|
1139
|
+
prepare: Effect.fn("StepBoundary.prepare")(function*(descriptor) {
|
|
1140
|
+
if (options.supported === false) return yield* Effect.fail(unsupported())
|
|
1141
|
+
return {
|
|
1142
|
+
descriptor,
|
|
1143
|
+
readSnapshot: options.readSnapshot ?? exactReads(descriptor)
|
|
1144
|
+
}
|
|
1145
|
+
}),
|
|
1146
|
+
settle: Effect.fn("StepBoundary.settle")(function*(prepared) {
|
|
1147
|
+
if (options.supported === false) return yield* Effect.fail(unsupported())
|
|
1148
|
+
if (options.failure !== undefined) return yield* Effect.fail(options.failure)
|
|
1149
|
+
return {
|
|
1150
|
+
declaredOutputs: options.declaredOutputs ?? { paths: prepared.descriptor.writeSet },
|
|
1151
|
+
diffIdentity,
|
|
1152
|
+
...(options.wholeTreeWriteDetection === false ? {} : { wholeTreeWritesVerified: true as const }),
|
|
1153
|
+
...(options.hermeticReadDetection === false ? {} : { hermeticReadsVerified: true as const }),
|
|
1154
|
+
...(options.deviation === undefined ? {} : { deviation: options.deviation })
|
|
1155
|
+
}
|
|
1156
|
+
}),
|
|
1157
|
+
replayOutputs: Effect.fn("StepBoundary.replayOutputs")(function*(evidence) {
|
|
1158
|
+
if (options.supported === false) return yield* Effect.fail(unsupported())
|
|
1159
|
+
yield* Effect.sync(() => {
|
|
1160
|
+
options.onReplay?.(evidence)
|
|
1161
|
+
})
|
|
1162
|
+
})
|
|
1163
|
+
})
|
|
1164
|
+
return Layer.succeed(StepBoundary, service)
|
|
1165
|
+
}
|