@smthrs/engine-store 1.0.0-rc.5 → 1.0.0-rc.7
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/README.md +15 -15
- package/dist/cjs/ArtifactGc.d.ts +1 -1
- package/dist/cjs/ArtifactGc.d.ts.map +1 -1
- package/dist/cjs/ArtifactGc.js +1 -1
- package/dist/cjs/ArtifactGc.js.map +1 -1
- package/dist/cjs/DeferredClockFold.d.ts +4 -4
- package/dist/cjs/DeferredClockFold.d.ts.map +1 -1
- package/dist/cjs/DeferredClockFold.js +1 -1
- package/dist/cjs/DeferredClockFold.js.map +1 -1
- package/dist/cjs/DisasterRecovery.d.ts +1 -1
- package/dist/cjs/DisasterRecovery.d.ts.map +1 -1
- package/dist/cjs/DisasterRecovery.js +1 -1
- package/dist/cjs/DisasterRecovery.js.map +1 -1
- package/dist/cjs/DurableEngineState.d.ts +1 -1
- package/dist/cjs/DurableEngineState.d.ts.map +1 -1
- package/dist/cjs/DurableEngineState.js +1 -1
- package/dist/cjs/DurableEngineState.js.map +1 -1
- package/dist/cjs/ExecutionSnapshot.d.ts +2 -2
- package/dist/cjs/ExecutionSnapshot.d.ts.map +1 -1
- package/dist/cjs/ExecutionSnapshot.js +1 -1
- package/dist/cjs/ExecutionSnapshot.js.map +1 -1
- package/dist/cjs/Migrations.d.ts +2 -2
- package/dist/cjs/Migrations.d.ts.map +1 -1
- package/dist/cjs/OwnerIdentity.js +1 -1
- package/dist/cjs/OwnerIdentity.js.map +2 -2
- package/dist/cjs/PlanInputStore.d.ts +1 -1
- package/dist/cjs/PlanInputStore.d.ts.map +1 -1
- package/dist/cjs/PlanInputStore.js +1 -1
- package/dist/cjs/PlanInputStore.js.map +1 -1
- package/dist/cjs/PlanMergeStore.d.ts +1 -1
- package/dist/cjs/PlanMergeStore.d.ts.map +1 -1
- package/dist/cjs/PlanMergeStore.js +1 -1
- package/dist/cjs/PlanMergeStore.js.map +1 -1
- package/dist/cjs/Retention.d.ts +2 -2
- package/dist/cjs/Retention.d.ts.map +1 -1
- package/dist/cjs/Retention.js +1 -1
- package/dist/cjs/Retention.js.map +1 -1
- package/dist/cjs/RunChangeFeed.d.ts +1 -1
- package/dist/cjs/RunChangeFeed.d.ts.map +1 -1
- package/dist/cjs/RunChangeFeed.js +1 -1
- package/dist/cjs/RunChangeFeed.js.map +1 -1
- package/dist/cjs/SelectionStore.d.ts +1 -1
- package/dist/cjs/SelectionStore.d.ts.map +1 -1
- package/dist/cjs/SelectionStore.js +1 -1
- package/dist/cjs/SelectionStore.js.map +1 -1
- package/dist/cjs/StepBoundary.d.ts +8 -6
- package/dist/cjs/StepBoundary.d.ts.map +1 -1
- package/dist/cjs/StepBoundary.js +13 -10
- package/dist/cjs/StepBoundary.js.map +2 -2
- package/dist/cjs/StepCacheFold.d.ts +2 -2
- package/dist/cjs/StepCacheFold.d.ts.map +1 -1
- package/dist/cjs/StepCacheFold.js +1 -1
- package/dist/cjs/StepCacheFold.js.map +1 -1
- package/dist/cjs/internal/EngineStateSchema.d.ts +1 -1
- package/dist/cjs/internal/EngineStateSchema.d.ts.map +1 -1
- package/dist/cjs/internal/EngineStateSchema.js.map +1 -1
- package/dist/cjs/internal/ExecutionSnapshotRead.d.ts +3 -3
- package/dist/cjs/internal/ExecutionSnapshotRead.d.ts.map +1 -1
- package/dist/cjs/internal/ExecutionSnapshotRead.js.map +1 -1
- package/dist/cjs/internal/ExecutionSnapshotRelations.d.ts +1 -1
- package/dist/cjs/internal/ExecutionSnapshotRelations.d.ts.map +1 -1
- package/dist/cjs/internal/ExecutionSnapshotRelations.js.map +1 -1
- package/dist/cjs/internal/RetentionOps.d.ts +2 -2
- package/dist/cjs/internal/RetentionOps.d.ts.map +1 -1
- package/dist/cjs/internal/RetentionOps.js +1 -1
- package/dist/cjs/internal/RetentionOps.js.map +1 -1
- package/dist/cjs/internal/RunCatalogOps.d.ts +1 -1
- package/dist/cjs/internal/RunCatalogOps.d.ts.map +1 -1
- package/dist/cjs/internal/RunCatalogOps.js +1 -1
- package/dist/cjs/internal/RunCatalogOps.js.map +1 -1
- package/dist/cjs/internal/RunListing.d.ts +1 -1
- package/dist/cjs/internal/RunListing.d.ts.map +1 -1
- package/dist/cjs/internal/RunListing.js +1 -1
- package/dist/cjs/internal/RunListing.js.map +1 -1
- package/dist/cjs/migrations/0001_initial.d.ts +1 -1
- package/dist/cjs/migrations/0001_initial.d.ts.map +1 -1
- package/dist/cjs/migrations/0001_initial.js +1 -1
- package/dist/cjs/migrations/0001_initial.js.map +1 -1
- package/dist/cjs/migrations/0002_selection_store.d.ts +1 -1
- package/dist/cjs/migrations/0002_selection_store.d.ts.map +1 -1
- package/dist/cjs/migrations/0002_selection_store.js +1 -1
- package/dist/cjs/migrations/0002_selection_store.js.map +1 -1
- package/dist/cjs/migrations/0003_plan_inputs.d.ts +1 -1
- package/dist/cjs/migrations/0003_plan_inputs.d.ts.map +1 -1
- package/dist/cjs/migrations/0003_plan_inputs.js +1 -1
- package/dist/cjs/migrations/0003_plan_inputs.js.map +1 -1
- package/dist/cjs/migrations/0004_plan_environment.d.ts +1 -1
- package/dist/cjs/migrations/0004_plan_environment.d.ts.map +1 -1
- package/dist/cjs/migrations/0004_plan_environment.js +1 -1
- package/dist/cjs/migrations/0004_plan_environment.js.map +1 -1
- package/dist/cjs/migrations/0005_plan_merges.d.ts +1 -1
- package/dist/cjs/migrations/0005_plan_merges.d.ts.map +1 -1
- package/dist/cjs/migrations/0005_plan_merges.js +1 -1
- package/dist/cjs/migrations/0005_plan_merges.js.map +1 -1
- package/dist/cjs/migrations/0006_execution_listing.d.ts +1 -1
- package/dist/cjs/migrations/0006_execution_listing.d.ts.map +1 -1
- package/dist/cjs/migrations/0006_execution_listing.js +1 -1
- package/dist/cjs/migrations/0006_execution_listing.js.map +1 -1
- package/dist/cjs/migrations/0007_run_parent_sequence.d.ts +1 -1
- package/dist/cjs/migrations/0007_run_parent_sequence.d.ts.map +1 -1
- package/dist/cjs/migrations/0007_run_parent_sequence.js +1 -1
- package/dist/cjs/migrations/0007_run_parent_sequence.js.map +1 -1
- package/dist/cjs/migrations/0008_deferred_consumption.d.ts +1 -1
- package/dist/cjs/migrations/0008_deferred_consumption.d.ts.map +1 -1
- package/dist/cjs/migrations/0008_deferred_consumption.js +1 -1
- package/dist/cjs/migrations/0008_deferred_consumption.js.map +1 -1
- package/dist/cjs/migrations/0009_deferred_clock_facts.d.ts +1 -1
- package/dist/cjs/migrations/0009_deferred_clock_facts.d.ts.map +1 -1
- package/dist/cjs/migrations/0009_deferred_clock_facts.js +1 -1
- package/dist/cjs/migrations/0009_deferred_clock_facts.js.map +1 -1
- package/dist/cjs/migrations/0010_pending_cancellation.d.ts +1 -1
- package/dist/cjs/migrations/0010_pending_cancellation.d.ts.map +1 -1
- package/dist/cjs/migrations/0010_pending_cancellation.js +1 -1
- package/dist/cjs/migrations/0010_pending_cancellation.js.map +1 -1
- package/dist/cjs/test/TestStores.d.ts +4 -4
- package/dist/cjs/test/TestStores.d.ts.map +1 -1
- package/dist/esm/ArtifactGc.d.ts +1 -1
- package/dist/esm/ArtifactGc.d.ts.map +1 -1
- package/dist/esm/ArtifactGc.js +1 -1
- package/dist/esm/ArtifactGc.js.map +1 -1
- package/dist/esm/DeferredClockFold.d.ts +4 -4
- package/dist/esm/DeferredClockFold.d.ts.map +1 -1
- package/dist/esm/DeferredClockFold.js +1 -1
- package/dist/esm/DeferredClockFold.js.map +1 -1
- package/dist/esm/DisasterRecovery.d.ts +1 -1
- package/dist/esm/DisasterRecovery.d.ts.map +1 -1
- package/dist/esm/DisasterRecovery.js +1 -1
- package/dist/esm/DisasterRecovery.js.map +1 -1
- package/dist/esm/DurableEngineState.d.ts +1 -1
- package/dist/esm/DurableEngineState.d.ts.map +1 -1
- package/dist/esm/DurableEngineState.js +1 -1
- package/dist/esm/DurableEngineState.js.map +1 -1
- package/dist/esm/ExecutionSnapshot.d.ts +2 -2
- package/dist/esm/ExecutionSnapshot.d.ts.map +1 -1
- package/dist/esm/ExecutionSnapshot.js +1 -1
- package/dist/esm/ExecutionSnapshot.js.map +1 -1
- package/dist/esm/Migrations.d.ts +2 -2
- package/dist/esm/Migrations.d.ts.map +1 -1
- package/dist/esm/OwnerIdentity.js +1 -1
- package/dist/esm/OwnerIdentity.js.map +1 -1
- package/dist/esm/PlanInputStore.d.ts +1 -1
- package/dist/esm/PlanInputStore.d.ts.map +1 -1
- package/dist/esm/PlanInputStore.js +1 -1
- package/dist/esm/PlanInputStore.js.map +1 -1
- package/dist/esm/PlanMergeStore.d.ts +1 -1
- package/dist/esm/PlanMergeStore.d.ts.map +1 -1
- package/dist/esm/PlanMergeStore.js +1 -1
- package/dist/esm/PlanMergeStore.js.map +1 -1
- package/dist/esm/Retention.d.ts +2 -2
- package/dist/esm/Retention.d.ts.map +1 -1
- package/dist/esm/Retention.js +1 -1
- package/dist/esm/Retention.js.map +1 -1
- package/dist/esm/RunChangeFeed.d.ts +1 -1
- package/dist/esm/RunChangeFeed.d.ts.map +1 -1
- package/dist/esm/RunChangeFeed.js +1 -1
- package/dist/esm/RunChangeFeed.js.map +1 -1
- package/dist/esm/SelectionStore.d.ts +1 -1
- package/dist/esm/SelectionStore.d.ts.map +1 -1
- package/dist/esm/SelectionStore.js +1 -1
- package/dist/esm/SelectionStore.js.map +1 -1
- package/dist/esm/StepBoundary.d.ts +8 -6
- package/dist/esm/StepBoundary.d.ts.map +1 -1
- package/dist/esm/StepBoundary.js +20 -11
- package/dist/esm/StepBoundary.js.map +1 -1
- package/dist/esm/StepCacheFold.d.ts +2 -2
- package/dist/esm/StepCacheFold.d.ts.map +1 -1
- package/dist/esm/StepCacheFold.js +1 -1
- package/dist/esm/StepCacheFold.js.map +1 -1
- package/dist/esm/internal/EngineStateSchema.d.ts +1 -1
- package/dist/esm/internal/EngineStateSchema.d.ts.map +1 -1
- package/dist/esm/internal/EngineStateSchema.js.map +1 -1
- package/dist/esm/internal/ExecutionSnapshotRead.d.ts +3 -3
- package/dist/esm/internal/ExecutionSnapshotRead.d.ts.map +1 -1
- package/dist/esm/internal/ExecutionSnapshotRead.js.map +1 -1
- package/dist/esm/internal/ExecutionSnapshotRelations.d.ts +1 -1
- package/dist/esm/internal/ExecutionSnapshotRelations.d.ts.map +1 -1
- package/dist/esm/internal/ExecutionSnapshotRelations.js.map +1 -1
- package/dist/esm/internal/RetentionOps.d.ts +2 -2
- package/dist/esm/internal/RetentionOps.d.ts.map +1 -1
- package/dist/esm/internal/RetentionOps.js +1 -1
- package/dist/esm/internal/RetentionOps.js.map +1 -1
- package/dist/esm/internal/RunCatalogOps.d.ts +1 -1
- package/dist/esm/internal/RunCatalogOps.d.ts.map +1 -1
- package/dist/esm/internal/RunCatalogOps.js +1 -1
- package/dist/esm/internal/RunCatalogOps.js.map +1 -1
- package/dist/esm/internal/RunListing.d.ts +1 -1
- package/dist/esm/internal/RunListing.d.ts.map +1 -1
- package/dist/esm/internal/RunListing.js +1 -1
- package/dist/esm/internal/RunListing.js.map +1 -1
- package/dist/esm/migrations/0001_initial.d.ts +1 -1
- package/dist/esm/migrations/0001_initial.d.ts.map +1 -1
- package/dist/esm/migrations/0001_initial.js +1 -1
- package/dist/esm/migrations/0001_initial.js.map +1 -1
- package/dist/esm/migrations/0002_selection_store.d.ts +1 -1
- package/dist/esm/migrations/0002_selection_store.d.ts.map +1 -1
- package/dist/esm/migrations/0002_selection_store.js +1 -1
- package/dist/esm/migrations/0002_selection_store.js.map +1 -1
- package/dist/esm/migrations/0003_plan_inputs.d.ts +1 -1
- package/dist/esm/migrations/0003_plan_inputs.d.ts.map +1 -1
- package/dist/esm/migrations/0003_plan_inputs.js +1 -1
- package/dist/esm/migrations/0003_plan_inputs.js.map +1 -1
- package/dist/esm/migrations/0004_plan_environment.d.ts +1 -1
- package/dist/esm/migrations/0004_plan_environment.d.ts.map +1 -1
- package/dist/esm/migrations/0004_plan_environment.js +1 -1
- package/dist/esm/migrations/0004_plan_environment.js.map +1 -1
- package/dist/esm/migrations/0005_plan_merges.d.ts +1 -1
- package/dist/esm/migrations/0005_plan_merges.d.ts.map +1 -1
- package/dist/esm/migrations/0005_plan_merges.js +1 -1
- package/dist/esm/migrations/0005_plan_merges.js.map +1 -1
- package/dist/esm/migrations/0006_execution_listing.d.ts +1 -1
- package/dist/esm/migrations/0006_execution_listing.d.ts.map +1 -1
- package/dist/esm/migrations/0006_execution_listing.js +1 -1
- package/dist/esm/migrations/0006_execution_listing.js.map +1 -1
- package/dist/esm/migrations/0007_run_parent_sequence.d.ts +1 -1
- package/dist/esm/migrations/0007_run_parent_sequence.d.ts.map +1 -1
- package/dist/esm/migrations/0007_run_parent_sequence.js +1 -1
- package/dist/esm/migrations/0007_run_parent_sequence.js.map +1 -1
- package/dist/esm/migrations/0008_deferred_consumption.d.ts +1 -1
- package/dist/esm/migrations/0008_deferred_consumption.d.ts.map +1 -1
- package/dist/esm/migrations/0008_deferred_consumption.js +1 -1
- package/dist/esm/migrations/0008_deferred_consumption.js.map +1 -1
- package/dist/esm/migrations/0009_deferred_clock_facts.d.ts +1 -1
- package/dist/esm/migrations/0009_deferred_clock_facts.d.ts.map +1 -1
- package/dist/esm/migrations/0009_deferred_clock_facts.js +1 -1
- package/dist/esm/migrations/0009_deferred_clock_facts.js.map +1 -1
- package/dist/esm/migrations/0010_pending_cancellation.d.ts +1 -1
- package/dist/esm/migrations/0010_pending_cancellation.d.ts.map +1 -1
- package/dist/esm/migrations/0010_pending_cancellation.js +1 -1
- package/dist/esm/migrations/0010_pending_cancellation.js.map +1 -1
- package/dist/esm/test/TestStores.d.ts +4 -4
- package/dist/esm/test/TestStores.d.ts.map +1 -1
- package/docs/api.md +19 -16
- package/package.json +22 -22
- package/src/ArtifactGc.ts +1 -1
- package/src/DeferredClockFold.ts +1 -1
- package/src/DisasterRecovery.ts +1 -1
- package/src/DurableEngineState.ts +1 -1
- package/src/ExecutionSnapshot.ts +1 -1
- package/src/OwnerIdentity.ts +1 -1
- package/src/PlanInputStore.ts +1 -1
- package/src/PlanMergeStore.ts +1 -1
- package/src/Retention.ts +2 -2
- package/src/RunChangeFeed.ts +1 -1
- package/src/SelectionStore.ts +1 -1
- package/src/StepBoundary.ts +32 -17
- package/src/StepCacheFold.ts +1 -1
- package/src/internal/EngineStateSchema.ts +1 -1
- package/src/internal/ExecutionSnapshotRead.ts +1 -1
- package/src/internal/ExecutionSnapshotRelations.ts +1 -1
- package/src/internal/RetentionOps.ts +2 -2
- package/src/internal/RunCatalogOps.ts +1 -1
- package/src/internal/RunListing.ts +1 -1
- package/src/migrations/0001_initial.ts +1 -1
- package/src/migrations/0002_selection_store.ts +1 -1
- package/src/migrations/0003_plan_inputs.ts +1 -1
- package/src/migrations/0004_plan_environment.ts +1 -1
- package/src/migrations/0005_plan_merges.ts +1 -1
- package/src/migrations/0006_execution_listing.ts +1 -1
- package/src/migrations/0007_run_parent_sequence.ts +1 -1
- package/src/migrations/0008_deferred_consumption.ts +1 -1
- package/src/migrations/0009_deferred_clock_facts.ts +1 -1
- package/src/migrations/0010_pending_cancellation.ts +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/StepCacheFold.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * The step-cache fold: `flows_step_cache` heads rebuilt from the journal.\n *\n * A head exists only because a `CacheStore.put` inserted it, and\n * `ActionPersistence` journals exactly that insertion as a\n * `flows.engine.cache-provenance` entry with action `admitted`, in the same\n * `journal.transact` as the `put`. A duplicate or a losing first-writer race\n * writes a ledger row but no head, so it journals no `admitted` entry. Every\n * engine eviction is journalled before the fenced `CacheStore.evict` that\n * performs it: `expired`, `stale_read_set`, and `replay_failed` with reason\n * `corruption`, each naming the row it retires. The fold retires a row on any\n * `replay_failed` that names it: a producer slot keeps the first reason it\n * journalled, so a corruption can hide behind an earlier host failure. A row\n * whose replay failed only on the host is then a miss.\n *\n * The fold keeps the admitted rows no eviction retired. A key with one such\n * row gets it back as its head. A key with several lost at least one of them\n * without a journal entry (`sweepExpired`, retention, an unfenced `evict`), and\n * the journal cannot say which one the store still held, so it gets no head.\n *\n * Cache results stay in the append-only `flows_step_cache_recorded` ledger\n * that the `admitted` entry addresses. The journal is redacted and replayed to\n * sync subscribers, so it carries the decision and the provenance, never the\n * executable result (issue #72).\n *\n * A rebuild writes a subset of the live heads, apart from rows removed without\n * a journal entry, whose restored hits still pass the engine's age, read-set,\n * and digest checks. Shared-tier write-backs have no local entry and are not\n * rebuilt. Compaction and rewind delete eviction entries, so a store holding\n * a compacted run, or one where any run was ever rewound, gets no heads at all.\n * Retention is not yet covered: it deletes a run's entries, and with them the\n * compaction floor and any eviction that run journalled of another run's row,\n * so a rebuild after retention can restore a row the engine evicted (issue\n * #2053). A convergence re-record that reuses an evicted row's provenance stays\n * retired, so that key misses.\n *\n * @since 1.0.0\n */\n\nimport { Journal, type JournalEvent, Projection } from \"@smthrs/journal\"\nimport { CacheStore } from \"@smthrs/step-cache\"\nimport * as Effect from \"effect/Effect\"\nimport * as HashMap from \"effect/HashMap\"\nimport * as HashSet from \"effect/HashSet\"\nimport * as SqlClient from \"effect/
|
|
4
|
+
"sourcesContent": ["/**\n * The step-cache fold: `flows_step_cache` heads rebuilt from the journal.\n *\n * A head exists only because a `CacheStore.put` inserted it, and\n * `ActionPersistence` journals exactly that insertion as a\n * `flows.engine.cache-provenance` entry with action `admitted`, in the same\n * `journal.transact` as the `put`. A duplicate or a losing first-writer race\n * writes a ledger row but no head, so it journals no `admitted` entry. Every\n * engine eviction is journalled before the fenced `CacheStore.evict` that\n * performs it: `expired`, `stale_read_set`, and `replay_failed` with reason\n * `corruption`, each naming the row it retires. The fold retires a row on any\n * `replay_failed` that names it: a producer slot keeps the first reason it\n * journalled, so a corruption can hide behind an earlier host failure. A row\n * whose replay failed only on the host is then a miss.\n *\n * The fold keeps the admitted rows no eviction retired. A key with one such\n * row gets it back as its head. A key with several lost at least one of them\n * without a journal entry (`sweepExpired`, retention, an unfenced `evict`), and\n * the journal cannot say which one the store still held, so it gets no head.\n *\n * Cache results stay in the append-only `flows_step_cache_recorded` ledger\n * that the `admitted` entry addresses. The journal is redacted and replayed to\n * sync subscribers, so it carries the decision and the provenance, never the\n * executable result (issue #72).\n *\n * A rebuild writes a subset of the live heads, apart from rows removed without\n * a journal entry, whose restored hits still pass the engine's age, read-set,\n * and digest checks. Shared-tier write-backs have no local entry and are not\n * rebuilt. Compaction and rewind delete eviction entries, so a store holding\n * a compacted run, or one where any run was ever rewound, gets no heads at all.\n * Retention is not yet covered: it deletes a run's entries, and with them the\n * compaction floor and any eviction that run journalled of another run's row,\n * so a rebuild after retention can restore a row the engine evicted (issue\n * #2053). A convergence re-record that reuses an evicted row's provenance stays\n * retired, so that key misses.\n *\n * @since 1.0.0\n */\n\nimport { Journal, type JournalEvent, Projection } from \"@smthrs/journal\"\nimport { CacheStore } from \"@smthrs/step-cache\"\nimport * as Effect from \"effect/Effect\"\nimport * as HashMap from \"effect/HashMap\"\nimport * as HashSet from \"effect/HashSet\"\nimport * as SqlClient from \"effect/sql/SqlClient\"\n\n/**\n * The journal event type the fold reads.\n *\n * @category constants\n * @since 1.0.0\n */\nexport const eventType = \"flows.engine.cache-provenance\"\n\n/**\n * Provenance actions that retire the row they name.\n *\n * @category constants\n * @since 1.0.0\n */\nexport const retiringActions: ReadonlyArray<string> = [\"expired\", \"stale_read_set\", \"replay_failed\"]\n\n/**\n * Admitted rows folded so far, and the ones the journal retired.\n *\n * @category models\n * @since 1.0.0\n */\nexport interface State {\n /** Every row a `put` inserted as a head, by {@link refId}. */\n readonly admitted: HashMap.HashMap<string, CacheStore.RecordedRef>\n /** Rows an engine eviction retired, by {@link refId}. */\n readonly retired: HashSet.HashSet<string>\n}\n\n/**\n * The stable identity of a recorded row.\n *\n * @category models\n * @since 1.0.0\n */\nexport const refId = (ref: CacheStore.RecordedRef): string =>\n JSON.stringify([ref.keyDigest, ref.recordedRunId, ref.recordedEventSeq])\n\nconst record = (value: unknown): Readonly<Record<string, unknown>> | undefined =>\n typeof value === \"object\" && value !== null && !Array.isArray(value)\n ? value as Readonly<Record<string, unknown>>\n : undefined\n\n/** The row a payload names by `keyDigest`/`recordedRunId`/`recordedEventSeq`, if it names one. */\nconst named = (payload: Readonly<Record<string, unknown>>): CacheStore.RecordedRef | undefined =>\n typeof payload.keyDigest === \"string\" && typeof payload.recordedRunId === \"string\" &&\n typeof payload.recordedEventSeq === \"number\"\n ? {\n keyDigest: payload.keyDigest,\n recordedRunId: payload.recordedRunId,\n recordedEventSeq: payload.recordedEventSeq\n }\n : undefined\n\nconst retires = (payload: Readonly<Record<string, unknown>>): boolean =>\n retiringActions.includes(payload.action as string)\n\n/**\n * Folds one committed journal entry.\n *\n * @category folds\n * @since 1.0.0\n */\nexport const reduce = (state: State, entry: JournalEvent.Entry): State => {\n const payload = record(entry.payload)\n const ref = entry.eventType === eventType && payload !== undefined ? named(payload) : undefined\n if (ref === undefined) return state\n if (payload!.action === \"admitted\") return { ...state, admitted: HashMap.set(state.admitted, refId(ref), ref) }\n return retires(payload!) ? { ...state, retired: HashSet.add(state.retired, refId(ref)) } : state\n}\n\n/**\n * The empty fold.\n *\n * @category constants\n * @since 1.0.0\n */\nexport const initial: State = { admitted: HashMap.empty(), retired: HashSet.empty() }\n\n/**\n * The fold as a journal projection, for `Journal.project` over one run.\n *\n * @category folds\n * @since 1.0.0\n */\nexport const projection: Projection.Projection<State> = Projection.make({\n name: \"flows.engine.step-cache\",\n initial,\n reduce: (state, entry) => Effect.succeed(reduce(state, entry))\n})\n\n/**\n * The head each key may be rebuilt with: its one admitted row no eviction\n * retired. A key with more than one such row gets none.\n *\n * @category folds\n * @since 1.0.0\n */\nexport const candidates = (state: State): ReadonlyArray<CacheStore.RecordedRef> => {\n const byKey = new Map<string, Array<CacheStore.RecordedRef>>()\n for (const [id, ref] of state.admitted) {\n if (HashSet.has(state.retired, id)) continue\n const refs = byKey.get(ref.keyDigest)\n if (refs === undefined) byKey.set(ref.keyDigest, [ref])\n else refs.push(ref)\n }\n return [...byKey.values()].flatMap((refs) => refs.length === 1 ? refs : [])\n}\n\n/**\n * What a {@link rebuild} read and wrote.\n *\n * @category models\n * @since 1.0.0\n */\nexport interface Rebuilt {\n /** Runs with cache provenance entries. */\n readonly runs: number\n /** Runs with a compaction floor. Any makes the rebuild write no heads. */\n readonly compacted: number\n /** Runs a rewind truncated. Any makes the rebuild write no heads. */\n readonly rewound: number\n readonly admitted: number\n readonly retired: number\n readonly heads: number\n}\n\nconst pageSize = 512\n\n/** Folds one run's cache provenance entries into `state`, in sequence order. */\nconst foldRun = (journal: Journal.Service, runId: string, start: State) =>\n Effect.gen(function*() {\n let state = start\n let after: JournalEvent.Seq | undefined\n while (true) {\n const page = yield* journal.entries({\n runId: runId as JournalEvent.RunId,\n eventTypes: [eventType],\n limit: pageSize,\n ...(after === undefined ? {} : { after })\n })\n for (const entry of page.entries) {\n state = reduce(state, entry)\n after = entry.seq\n }\n if (!page.hasMore) return state\n }\n })\n\n/**\n * Rebuilds every `flows_step_cache` head from the journal.\n *\n * Reads each run's cache provenance entries in sequence order, folds them,\n * and replaces the heads through `CacheStore.rebuildHeads` in one writer\n * transaction. A store with a compacted or rewound run clears the heads\n * instead: the eviction that retired a row may be gone. A journal read failure, including\n * a compaction that lands during the read, leaves the heads untouched.\n *\n * Run it on a quiescent store, as a restore does: a cache write that commits\n * between the journal read and the rewrite is dropped from the heads (a miss,\n * never a wrong hit).\n *\n * @category operations\n * @since 1.0.0\n */\nexport const rebuild = Effect.fn(\"StepCacheFold.rebuild\")(function*() {\n const journal = yield* Journal.Journal\n const sql = yield* SqlClient.SqlClient\n const floors = yield* sql<{ readonly compacted: number | string }>`\n SELECT COUNT(DISTINCT run_id) AS compacted FROM flows_journal_checkpoints\n WHERE compacted_at_ms IS NOT NULL\n `\n const compacted = Number(floors[0]!.compacted)\n const generations = yield* sql<{ readonly rewound: number | string }>`\n SELECT COUNT(*) AS rewound FROM flows_journal_generations\n `\n const rewound = Number(generations[0]!.rewound)\n const runs = yield* sql<{ readonly run_id: string }>`\n SELECT DISTINCT run_id FROM flows_journal_events\n WHERE event_type = ${eventType}\n ORDER BY run_id\n `\n let state = initial\n if (compacted === 0 && rewound === 0) {\n for (const { run_id } of runs) {\n state = yield* foldRun(journal, run_id, state)\n }\n }\n const heads = yield* CacheStore.rebuildHeads(candidates(state))\n return {\n runs: runs.length,\n compacted,\n rewound,\n admitted: HashMap.size(state.admitted),\n retired: HashSet.size(state.retired),\n heads\n } satisfies Rebuilt\n})\n"],
|
|
5
5
|
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
*/
|
|
19
19
|
import type { Service as WriterService } from "@smthrs/database/DurableWriter";
|
|
20
20
|
import * as Effect from "effect/Effect";
|
|
21
|
-
import type * as SqlClient from "effect/
|
|
21
|
+
import type * as SqlClient from "effect/sql/SqlClient";
|
|
22
22
|
/**
|
|
23
23
|
* SQL dialects a statement is known to be accepted by, verbatim.
|
|
24
24
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EngineStateSchema.d.ts","sourceRoot":"","sources":["../../../src/internal/EngineStateSchema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAGH,OAAO,KAAK,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,gCAAgC,CAAA;AAC9E,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AACvC,OAAO,KAAK,KAAK,SAAS,MAAM
|
|
1
|
+
{"version":3,"file":"EngineStateSchema.d.ts","sourceRoot":"","sources":["../../../src/internal/EngineStateSchema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAGH,OAAO,KAAK,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,gCAAgC,CAAA;AAC9E,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AACvC,OAAO,KAAK,KAAK,SAAS,MAAM,sBAAsB,CAAA;AAEtD;;;;;GAKG;AACH,MAAM,MAAM,OAAO,GAAG,QAAQ,GAAG,UAAU,CAAA;AAE3C;;;;;GAKG;AACH,MAAM,WAAW,SAAS;IACxB,8DAA8D;IAC9D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,uCAAuC;IACvC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB;;;;;;OAMG;IACH,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC,OAAO,CAAC,CAAA;IACzC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;CACrB;AAED;;;;;GAKG;AACH,eAAO,MAAM,UAAU,EAAE,aAAa,CAAC,SAAS,CAa/C,CAAA;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,KAAK,GAAI,KAAK,SAAS,CAAC,SAAS,EAAE,QAAQ,aAAa,KAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAMvF,CAAA"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/internal/EngineStateSchema.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * The schema objects `DurableEngineState` creates outside the journal\n * migration, and the inventory of what porting them to another\n * dialect costs.\n *\n * Each durable package owns its migration ladder. Spawn edges and their GC\n * trigger moved into engine-store migration 0006. The remaining\n * statements are engine-store-owned storage created idempotently at\n * construction instead (issues #40/#41/#79/#81). That is a deliberate lane\n * boundary, not an oversight \u2014 but it left the statements invisible to the\n * pg-porting plan in the database roadmap, which\n * scoped only the journal migration's SQLite-specific DDL. Declaring them here\n * makes the inventory machine-readable: a test diffs the database's schema\n * objects across `make` against this list, so no engine-owned statement can\n * be added without appearing in the porting inventory (issue #92).\n *\n * @since 0.1.0\n */\n\nimport { isReadOnly } from \"@smthrs/database/Dialect\"\nimport type { Service as WriterService } from \"@smthrs/database/DurableWriter\"\nimport * as Effect from \"effect/Effect\"\nimport type * as SqlClient from \"effect/
|
|
4
|
+
"sourcesContent": ["/**\n * The schema objects `DurableEngineState` creates outside the journal\n * migration, and the inventory of what porting them to another\n * dialect costs.\n *\n * Each durable package owns its migration ladder. Spawn edges and their GC\n * trigger moved into engine-store migration 0006. The remaining\n * statements are engine-store-owned storage created idempotently at\n * construction instead (issues #40/#41/#79/#81). That is a deliberate lane\n * boundary, not an oversight \u2014 but it left the statements invisible to the\n * pg-porting plan in the database roadmap, which\n * scoped only the journal migration's SQLite-specific DDL. Declaring them here\n * makes the inventory machine-readable: a test diffs the database's schema\n * objects across `make` against this list, so no engine-owned statement can\n * be added without appearing in the porting inventory (issue #92).\n *\n * @since 0.1.0\n */\n\nimport { isReadOnly } from \"@smthrs/database/Dialect\"\nimport type { Service as WriterService } from \"@smthrs/database/DurableWriter\"\nimport * as Effect from \"effect/Effect\"\nimport type * as SqlClient from \"effect/sql/SqlClient\"\n\n/**\n * SQL dialects a statement is known to be accepted by, verbatim.\n *\n * @since 0.1.0\n * @category models\n */\nexport type Dialect = \"sqlite\" | \"postgres\"\n\n/**\n * One engine-store-owned schema object.\n *\n * @since 0.1.0\n * @category models\n */\nexport interface Statement {\n /** The schema object's name, as it appears in the catalog. */\n readonly name: string\n /** Why the object exists, by issue. */\n readonly reason: string\n /**\n * Dialects that accept this statement unchanged. A statement listing only\n * `\"sqlite\"` must be rewritten before a Postgres/PGlite `Database` layer\n * can construct this service \u2014 `make` pipes every one of them through\n * `Effect.orDie`, so an unported statement is a construction-time defect,\n * not a recoverable error.\n */\n readonly dialects: ReadonlyArray<Dialect>\n readonly ddl: string\n}\n\n/**\n * The porting inventory: every engine-store-owned statement, in creation order.\n *\n * @since 0.1.0\n * @category constants\n */\nexport const statements: ReadonlyArray<Statement> = [\n {\n name: \"flows_runs_stale_running_idx\",\n reason:\n \"Serves the stale-running sweep's per-tick predicate (issue #79): `status = 'running' AND heartbeat_at_ms < cutoff`, ordered by heartbeat. A partial index keeps it tiny \u2014 only live running rows appear.\",\n // Partial indexes are valid Postgres since 9.5, so this one ports as-is.\n dialects: [\"sqlite\", \"postgres\"],\n ddl: `\n CREATE INDEX IF NOT EXISTS flows_runs_stale_running_idx\n ON flows_runs (heartbeat_at_ms)\n WHERE status = 'running'\n `\n }\n]\n\n/**\n * Creates the engine-store-owned objects, idempotently, in inventory order.\n *\n * `flows_runs` must already exist \u2014 every query in this service assumes a\n * migrated database, so `make` composes over one by construction. A read-only\n * client installs nothing.\n *\n * @since 0.1.0\n * @category constructors\n */\nexport const apply = (sql: SqlClient.SqlClient, writer: WriterService): Effect.Effect<void> =>\n // A read-only observer reads the schema it finds and installs nothing.\n isReadOnly(sql) ? Effect.void : Effect.forEach(\n statements,\n (statement) => writer.write(sql.unsafe(statement.ddl)).pipe(Effect.orDie),\n { discard: true }\n )\n"],
|
|
5
5
|
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAmBA,qBAA2B;AAE3B,aAAwB;AAuCjB,MAAM,aAAuC;AAAA,EAClD;AAAA,IACE,MAAM;AAAA,IACN,QACE;AAAA;AAAA,IAEF,UAAU,CAAC,UAAU,UAAU;AAAA,IAC/B,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA,EAKP;AACF;AAYO,MAAM,QAAQ,CAAC,KAA0B;AAAA;AAAA,MAE9C,2BAAW,GAAG,IAAI,OAAO,OAAO,OAAO;AAAA,IACrC;AAAA,IACA,CAAC,cAAc,OAAO,MAAM,IAAI,OAAO,UAAU,GAAG,CAAC,EAAE,KAAK,OAAO,KAAK;AAAA,IACxE,EAAE,SAAS,KAAK;AAAA,EAClB;AAAA;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
import * as Sha256 from "@smthrs/crypto/Sha256";
|
|
7
7
|
import { RunStoreError } from "@smthrs/run-store/RunStore";
|
|
8
8
|
import { Effect, Schema } from "effect";
|
|
9
|
-
import type * as SqlClient from "effect/
|
|
9
|
+
import type * as SqlClient from "effect/sql/SqlClient";
|
|
10
10
|
import type { Batch } from "../ExecutionSnapshot.ts";
|
|
11
11
|
/**
|
|
12
12
|
* Non-negative safe integer for timestamps and revisions.
|
|
@@ -59,7 +59,7 @@ export type Position = typeof Position.Type;
|
|
|
59
59
|
* @since 1.0.0
|
|
60
60
|
* @private
|
|
61
61
|
*/
|
|
62
|
-
export declare const transaction: <A, E, R>(sql: SqlClient.SqlClient, effect: Effect.Effect<A, E, R>) => Effect.Effect<A, import("effect/
|
|
62
|
+
export declare const transaction: <A, E, R>(sql: SqlClient.SqlClient, effect: Effect.Effect<A, E, R>) => Effect.Effect<A, import("effect/sql/SqlError").SqlError | E, R>;
|
|
63
63
|
/**
|
|
64
64
|
* Preserves failure causes and interruption at the read boundary.
|
|
65
65
|
*
|
|
@@ -83,7 +83,7 @@ export declare const decode: <S extends Schema.Top>(schema: S, input: unknown) =
|
|
|
83
83
|
export declare const position: (sql: SqlClient.SqlClient) => Effect.Effect<{
|
|
84
84
|
readonly source: string;
|
|
85
85
|
readonly revision: number;
|
|
86
|
-
}, import("effect/
|
|
86
|
+
}, import("effect/sql/SqlError").SqlError | RunStoreError, never>;
|
|
87
87
|
/**
|
|
88
88
|
* Structured waiting condition; nullable fields preserve older unknown values.
|
|
89
89
|
* The opaque wake token leaves the engine only as its SHA-256 `tokenDigest`,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ExecutionSnapshotRead.d.ts","sourceRoot":"","sources":["../../../src/internal/ExecutionSnapshotRead.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,MAAM,MAAM,uBAAuB,CAAA;AAE/C,OAAO,EAAa,aAAa,EAAE,MAAM,4BAA4B,CAAA;AACrE,OAAO,EAAS,MAAM,EAAgB,MAAM,EAAE,MAAM,QAAQ,CAAA;AAC5D,OAAO,KAAK,KAAK,SAAS,MAAM
|
|
1
|
+
{"version":3,"file":"ExecutionSnapshotRead.d.ts","sourceRoot":"","sources":["../../../src/internal/ExecutionSnapshotRead.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,MAAM,MAAM,uBAAuB,CAAA;AAE/C,OAAO,EAAa,aAAa,EAAE,MAAM,4BAA4B,CAAA;AACrE,OAAO,EAAS,MAAM,EAAgB,MAAM,EAAE,MAAM,QAAQ,CAAA;AAC5D,OAAO,KAAK,KAAK,SAAS,MAAM,sBAAsB,CAAA;AACtD,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,yBAAyB,CAAA;AAGpD;;;;;GAKG;AACH,eAAO,MAAM,OAAO,YAAuF,CAAA;AAC3G;;;;;GAKG;AACH,eAAO,MAAM,UAAU,uBAKtB,CAAA;AACD;;;;;GAKG;AACH,eAAO,MAAM,MAAM,eAA0D,CAAA;AAC7E;;;;;GAKG;AACH,eAAO,MAAM,QAAQ;;;EAAuD,CAAA;AAC5E;;;;;GAKG;AACH,MAAM,MAAM,QAAQ,GAAG,OAAO,QAAQ,CAAC,IAAI,CAAA;AAE3C;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,WAAW,GAAI,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,KAAK,SAAS,CAAC,SAAS,EAAE,QAAQ,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,oEAiB1F,CAAA;AAEH;;;;;GAKG;AACH,eAAO,MAAM,QAAQ,GAAI,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,QAAQ,MAAM,EAAE,QAAQ,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,KAAG,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,aAAa,EAAE,CAAC,CAY/G,CAAA;AAEL;;;;;GAKG;AACH,eAAO,MAAM,MAAM,GAAI,CAAC,SAAS,MAAM,CAAC,GAAG,EAAE,QAAQ,CAAC,EAAE,OAAO,OAAO,mEAQlE,CAAA;AAEJ;;;;;GAKG;AACH,eAAO,MAAM,QAAQ,GAAI,KAAK,SAAS,CAAC,SAAS;;;iEAG9C,CAAA;AAkCH;;;;;;;;GAQG;AACH,MAAM,WAAW,OAAO;IACtB,QAAQ,CAAC,IAAI,EAAE,OAAO,GAAG,QAAQ,GAAG,UAAU,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,CAAA;IAC5E,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IAChC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC,MAAM,GAAG,IAAI,CAAA;IAC1C,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,MAAM,CAAC,IAAI,CAAC,IAAI,GAAG,SAAS,CAAA;CACvD;AAiBD;;;;;GAKG;AACH,eAAO,MAAM,aAAa,yQAEmF,CAAA;AAE7G;;;;;GAKG;AACH,eAAO,MAAM,QAAQ,GAAI,OAAO,OAAO,EAAE,IAAI,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;wBAkDjD,CAAA;AAEJ;;;;;GAKG;AACH,MAAM,MAAM,QAAQ,GAAG,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC,CAAA;AAElE;;;;;GAKG;AACH,eAAO,MAAM,aAAa,GAAI,QAAQ,aAAa,CAAC,MAAM,CAAC,EAAE,OAAO,KAAK,KAAG,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,aAAa,CAmB7G,CAAA"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/internal/ExecutionSnapshotRead.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Shared transactional execution decoding.\n *\n * @since 1.0.0\n */\n\nimport * as Sha256 from \"@smthrs/crypto/Sha256\"\nimport { OwnerId } from \"@smthrs/journal/OwnerId\"\nimport { RunStatus, RunStoreError } from \"@smthrs/run-store/RunStore\"\nimport { Cause, Effect, Exit, Option, Schema } from \"effect\"\nimport type * as SqlClient from \"effect/unstable/sql/SqlClient\"\nimport type { Batch } from \"../ExecutionSnapshot.ts\"\nimport { RunState } from \"../RunState.ts\"\n\n/**\n * Non-negative safe integer for timestamps and revisions.\n *\n * @private\n * @since 1.0.0\n */\nexport const Natural = Schema.Int.check(Schema.isBetween({ minimum: 0, maximum: Number.MAX_SAFE_INTEGER }))\n/**\n * Bounded identifier with a durable text encoding.\n *\n * @private\n * @since 1.0.0\n */\nexport const Identifier = Schema.NonEmptyString.check(\n Schema.isMaxLength(1024),\n Schema.makeFilter((value) =>\n !value.includes(\"\\0\") && !/[\\uD800-\\uDBFF](?![\\uDC00-\\uDFFF])|(?<![\\uD800-\\uDBFF])[\\uDC00-\\uDFFF]/.test(value)\n )\n)\n/**\n * Engine database source identity.\n *\n * @private\n * @since 1.0.0\n */\nexport const Source = Schema.String.check(Schema.isPattern(/^[0-9a-f]{32}$/))\n/**\n * Source identity and observed high-water mark.\n *\n * @private\n * @since 1.0.0\n */\nexport const Position = Schema.Struct({ source: Source, revision: Natural })\n/**\n * Source identity and observed high-water mark.\n *\n * @private\n * @since 1.0.0\n */\nexport type Position = typeof Position.Type\n\n/**\n * A deferred read transaction on the reserved engine connection. The normal\n * writable client's transaction starts IMMEDIATE; a read needs only SQLite's\n * snapshot and must allow a WAL writer to commit while it reads. PostgreSQL\n * reads at REPEATABLE READ so every statement shares the snapshot the position\n * was read from; READ COMMITTED would let a concurrent commit make a row look\n * newer than its position. Reuse the SQL\n * client's transaction service to join an existing transaction. No read body\n * exposes a mutation callback. Connection reservation and interruption release\n * remain scoped, including when BEGIN or the read fails.\n * @since 1.0.0\n * @private\n */\nexport const transaction = <A, E, R>(sql: SqlClient.SqlClient, effect: Effect.Effect<A, E, R>) =>\n Effect.flatMap(\n Effect.serviceOption(sql.transactionService),\n (existing) =>\n Option.isSome(existing) ? effect : Effect.scoped(Effect.gen(function*() {\n const connection = yield* sql.reserve\n return yield* Effect.acquireUseRelease(\n connection.executeUnprepared(\n sql.onDialectOrElse({ pg: () => \"BEGIN ISOLATION LEVEL REPEATABLE READ READ ONLY\", orElse: () => \"BEGIN\" }),\n [],\n undefined\n ),\n () => effect.pipe(Effect.provideService(sql.transactionService, [connection, 0])),\n (_, exit) =>\n connection.executeUnprepared(Exit.isSuccess(exit) ? \"COMMIT\" : \"ROLLBACK\", [], undefined).pipe(Effect.orDie)\n )\n }))\n )\n\n/**\n * Preserves failure causes and interruption at the read boundary.\n *\n * @private\n * @since 1.0.0\n */\nexport const boundary = <A, E, R>(method: string, effect: Effect.Effect<A, E, R>): Effect.Effect<A, RunStoreError, R> =>\n effect.pipe(Effect.catchCause((cause) => {\n if (Cause.hasInterruptsOnly(cause)) return Effect.failCause(cause as Cause.Cause<never>)\n const original = Cause.squash(cause)\n return Effect.fail(\n original instanceof RunStoreError ? original : new RunStoreError({\n method,\n code: \"persistence_failed\",\n message: \"engine execution observation failed\",\n cause: original\n })\n )\n }))\n\n/**\n * Decodes stored SQL values with the original schema error.\n *\n * @private\n * @since 1.0.0\n */\nexport const decode = <S extends Schema.Top>(schema: S, input: unknown) =>\n Schema.decodeUnknownEffect(schema)(input).pipe(Effect.mapError((cause) =>\n new RunStoreError({\n method: \"ExecutionSnapshot.decode\",\n code: \"decode_failed\",\n message: \"stored execution observation is malformed\",\n cause\n })\n ))\n\n/**\n * Reads the engine database identity and revision.\n *\n * @private\n * @since 1.0.0\n */\nexport const position = (sql: SqlClient.SqlClient) =>\n sql`SELECT source, revision FROM flows_run_source WHERE singleton = 1`.pipe(\n Effect.flatMap((rows) => decode(Position, rows[0]))\n )\n\nconst Row = Schema.Struct({\n run_id: Identifier,\n status: RunStatus,\n created_at_ms: Natural,\n started_at_ms: Schema.NullOr(Natural),\n finished_at_ms: Schema.NullOr(Natural),\n owner_host_id: Schema.NullOr(Identifier),\n owner_pid: Schema.NullOr(Natural),\n owner_nonce: Schema.NullOr(Identifier),\n heartbeat_at_ms: Schema.NullOr(Natural),\n state_json: Schema.fromJsonString(RunState),\n parent_run_id: Schema.NullOr(Identifier),\n spawn_parent_id: Schema.NullOr(Identifier),\n spawn_seq: Schema.NullOr(Natural),\n execution_parent_id: Schema.NullOr(Identifier),\n lineage_id: Schema.NullOr(Identifier),\n round_ordinal: Schema.NullOr(Natural),\n cancel_requested_at_ms: Schema.NullOr(Natural),\n cancel_acknowledgement_json: Schema.NullOr(Schema.fromJsonString(Schema.Struct({\n observedAtMs: Natural,\n owner: OwnerId\n }))),\n // These opaque values use DurableEngineState's existing non-empty-string\n // contract. They are not run IDs and must not acquire an identifier ceiling.\n waiting_reason: Schema.NullOr(Schema.NonEmptyString),\n waiting_wake_at_ms: Schema.NullOr(Natural),\n waiting_token: Schema.NullOr(Schema.NonEmptyString),\n waiting_request: Schema.optional(Schema.NullOr(Schema.fromJsonString(Schema.Json))),\n revision: Natural,\n deleted: Schema.Literal(0)\n})\n\n/**\n * Structured waiting condition; nullable fields preserve older unknown values.\n * The opaque wake token leaves the engine only as its SHA-256 `tokenDigest`,\n * the same digest `ExecutionFacts` records: a reader may correlate a wait but\n * never holds the address that resolves it.\n *\n * @private\n * @since 1.0.0\n */\nexport interface Waiting {\n readonly kind: \"timer\" | \"signal\" | \"approval\" | \"quota\" | \"human\" | \"other\"\n readonly reason: string\n readonly wakeAtMs: number | null\n readonly tokenDigest: Sha256.Digest | null\n readonly request?: typeof Schema.Json.Type | undefined\n}\n\nconst waitingKind = (reason: string): Waiting[\"kind\"] => {\n switch (reason) {\n case \"timer\":\n case \"approval\":\n case \"quota\":\n case \"human\":\n return reason\n case \"event\":\n case \"signal\":\n return \"signal\"\n default:\n return \"other\"\n }\n}\n\n/**\n * Page-scoped lifecycle fields and indexed earliest spawn edge.\n *\n * @private\n * @since 1.0.0\n */\nexport const selectColumns = `r.*, c.revision, c.deleted,\n (SELECT parent_id FROM flows_run_parents WHERE child_id = r.run_id ORDER BY seq, parent_id LIMIT 1) AS spawn_parent_id,\n (SELECT seq FROM flows_run_parents WHERE child_id = r.run_id ORDER BY seq, parent_id LIMIT 1) AS spawn_seq`\n\n/**\n * Decodes a run row and validates observation invariants.\n *\n * @private\n * @since 1.0.0\n */\nexport const observed = (input: unknown, at: Position) =>\n Effect.gen(function*() {\n const row = yield* decode(Row, input)\n const parentRunId = row.parent_run_id ?? row.spawn_parent_id\n const ownerFields = [row.owner_host_id, row.owner_pid, row.owner_nonce, row.heartbeat_at_ms]\n const validOwner = row.status === \"running\"\n ? ownerFields.every((value) => value !== null)\n : ownerFields.every((value) => value === null)\n if (\n !validOwner || row.revision === 0 || row.revision > at.revision ||\n parentRunId !== row.execution_parent_id || parentRunId === row.run_id ||\n (row.lineage_id === null) !== (row.round_ordinal === null) ||\n (row.waiting_reason === null && (row.waiting_wake_at_ms !== null || row.waiting_token !== null ||\n row.waiting_request !== undefined && row.waiting_request !== null)) ||\n (row.cancel_acknowledgement_json !== null && row.cancel_requested_at_ms === null)\n ) {\n return yield* Effect.fail(\n new RunStoreError({\n method: \"ExecutionSnapshot.decode\",\n code: \"decode_failed\",\n message: \"stored execution observation violates its invariants\",\n cause: new Error(\"inconsistent execution identity, wait, cancellation or revision\")\n })\n )\n }\n return {\n _tag: \"Observed\" as const,\n runId: row.run_id,\n source: at.source,\n revision: row.revision,\n status: row.status,\n flowName: row.state_json.flowName,\n createdAtMs: row.created_at_ms,\n startedAtMs: row.started_at_ms,\n finishedAtMs: row.finished_at_ms,\n parentRunId,\n lineageId: row.lineage_id ?? row.run_id,\n roundOrdinal: row.round_ordinal ?? 0,\n cancellation: {\n requestedAtMs: row.cancel_requested_at_ms,\n acknowledgement: row.cancel_acknowledgement_json\n },\n waiting: row.waiting_reason === null ? null : {\n kind: waitingKind(row.waiting_reason),\n reason: row.waiting_reason,\n wakeAtMs: row.waiting_wake_at_ms,\n tokenDigest: row.waiting_token === null ? null : Sha256.digestSync(row.waiting_token),\n ...(row.waiting_request === undefined || row.waiting_request === null ? {} : { request: row.waiting_request })\n }\n }\n })\n\n/**\n * The result of a successful execution observation.\n *\n * @private\n * @since 1.0.0\n */\nexport type Observed = Effect.Success<ReturnType<typeof observed>>\n\n/**\n * Validates source, identity, ordering and revision coherence at the batch boundary.\n *\n * @private\n * @since 1.0.0\n */\nexport const coherentBatch = (runIds: ReadonlyArray<string>, batch: Batch): Effect.Effect<Batch, RunStoreError> => {\n const validPosition = Schema.is(Position)\n if (\n !validPosition(batch) || batch.snapshots.length !== runIds.length ||\n batch.snapshots.some((row, index) =>\n !validPosition(row) || row.runId !== runIds[index] || row.source !== batch.source ||\n row.revision > batch.revision\n )\n ) {\n return Effect.fail(\n new RunStoreError({\n method: \"ExecutionSnapshot.read\",\n code: \"persistence_failed\",\n message: \"stored execution batch is incoherent: source, identity, order or revision mismatch\",\n cause: undefined\n })\n )\n }\n return Effect.succeed(batch)\n}\n"],
|
|
4
|
+
"sourcesContent": ["/**\n * Shared transactional execution decoding.\n *\n * @since 1.0.0\n */\n\nimport * as Sha256 from \"@smthrs/crypto/Sha256\"\nimport { OwnerId } from \"@smthrs/journal/OwnerId\"\nimport { RunStatus, RunStoreError } from \"@smthrs/run-store/RunStore\"\nimport { Cause, Effect, Exit, Option, Schema } from \"effect\"\nimport type * as SqlClient from \"effect/sql/SqlClient\"\nimport type { Batch } from \"../ExecutionSnapshot.ts\"\nimport { RunState } from \"../RunState.ts\"\n\n/**\n * Non-negative safe integer for timestamps and revisions.\n *\n * @private\n * @since 1.0.0\n */\nexport const Natural = Schema.Int.check(Schema.isBetween({ minimum: 0, maximum: Number.MAX_SAFE_INTEGER }))\n/**\n * Bounded identifier with a durable text encoding.\n *\n * @private\n * @since 1.0.0\n */\nexport const Identifier = Schema.NonEmptyString.check(\n Schema.isMaxLength(1024),\n Schema.makeFilter((value) =>\n !value.includes(\"\\0\") && !/[\\uD800-\\uDBFF](?![\\uDC00-\\uDFFF])|(?<![\\uD800-\\uDBFF])[\\uDC00-\\uDFFF]/.test(value)\n )\n)\n/**\n * Engine database source identity.\n *\n * @private\n * @since 1.0.0\n */\nexport const Source = Schema.String.check(Schema.isPattern(/^[0-9a-f]{32}$/))\n/**\n * Source identity and observed high-water mark.\n *\n * @private\n * @since 1.0.0\n */\nexport const Position = Schema.Struct({ source: Source, revision: Natural })\n/**\n * Source identity and observed high-water mark.\n *\n * @private\n * @since 1.0.0\n */\nexport type Position = typeof Position.Type\n\n/**\n * A deferred read transaction on the reserved engine connection. The normal\n * writable client's transaction starts IMMEDIATE; a read needs only SQLite's\n * snapshot and must allow a WAL writer to commit while it reads. PostgreSQL\n * reads at REPEATABLE READ so every statement shares the snapshot the position\n * was read from; READ COMMITTED would let a concurrent commit make a row look\n * newer than its position. Reuse the SQL\n * client's transaction service to join an existing transaction. No read body\n * exposes a mutation callback. Connection reservation and interruption release\n * remain scoped, including when BEGIN or the read fails.\n * @since 1.0.0\n * @private\n */\nexport const transaction = <A, E, R>(sql: SqlClient.SqlClient, effect: Effect.Effect<A, E, R>) =>\n Effect.flatMap(\n Effect.serviceOption(sql.transactionService),\n (existing) =>\n Option.isSome(existing) ? effect : Effect.scoped(Effect.gen(function*() {\n const connection = yield* sql.reserve\n return yield* Effect.acquireUseRelease(\n connection.executeUnprepared(\n sql.onDialectOrElse({ pg: () => \"BEGIN ISOLATION LEVEL REPEATABLE READ READ ONLY\", orElse: () => \"BEGIN\" }),\n [],\n undefined\n ),\n () => effect.pipe(Effect.provideService(sql.transactionService, [connection, 0])),\n (_, exit) =>\n connection.executeUnprepared(Exit.isSuccess(exit) ? \"COMMIT\" : \"ROLLBACK\", [], undefined).pipe(Effect.orDie)\n )\n }))\n )\n\n/**\n * Preserves failure causes and interruption at the read boundary.\n *\n * @private\n * @since 1.0.0\n */\nexport const boundary = <A, E, R>(method: string, effect: Effect.Effect<A, E, R>): Effect.Effect<A, RunStoreError, R> =>\n effect.pipe(Effect.catchCause((cause) => {\n if (Cause.hasInterruptsOnly(cause)) return Effect.failCause(cause as Cause.Cause<never>)\n const original = Cause.squash(cause)\n return Effect.fail(\n original instanceof RunStoreError ? original : new RunStoreError({\n method,\n code: \"persistence_failed\",\n message: \"engine execution observation failed\",\n cause: original\n })\n )\n }))\n\n/**\n * Decodes stored SQL values with the original schema error.\n *\n * @private\n * @since 1.0.0\n */\nexport const decode = <S extends Schema.Top>(schema: S, input: unknown) =>\n Schema.decodeUnknownEffect(schema)(input).pipe(Effect.mapError((cause) =>\n new RunStoreError({\n method: \"ExecutionSnapshot.decode\",\n code: \"decode_failed\",\n message: \"stored execution observation is malformed\",\n cause\n })\n ))\n\n/**\n * Reads the engine database identity and revision.\n *\n * @private\n * @since 1.0.0\n */\nexport const position = (sql: SqlClient.SqlClient) =>\n sql`SELECT source, revision FROM flows_run_source WHERE singleton = 1`.pipe(\n Effect.flatMap((rows) => decode(Position, rows[0]))\n )\n\nconst Row = Schema.Struct({\n run_id: Identifier,\n status: RunStatus,\n created_at_ms: Natural,\n started_at_ms: Schema.NullOr(Natural),\n finished_at_ms: Schema.NullOr(Natural),\n owner_host_id: Schema.NullOr(Identifier),\n owner_pid: Schema.NullOr(Natural),\n owner_nonce: Schema.NullOr(Identifier),\n heartbeat_at_ms: Schema.NullOr(Natural),\n state_json: Schema.fromJsonString(RunState),\n parent_run_id: Schema.NullOr(Identifier),\n spawn_parent_id: Schema.NullOr(Identifier),\n spawn_seq: Schema.NullOr(Natural),\n execution_parent_id: Schema.NullOr(Identifier),\n lineage_id: Schema.NullOr(Identifier),\n round_ordinal: Schema.NullOr(Natural),\n cancel_requested_at_ms: Schema.NullOr(Natural),\n cancel_acknowledgement_json: Schema.NullOr(Schema.fromJsonString(Schema.Struct({\n observedAtMs: Natural,\n owner: OwnerId\n }))),\n // These opaque values use DurableEngineState's existing non-empty-string\n // contract. They are not run IDs and must not acquire an identifier ceiling.\n waiting_reason: Schema.NullOr(Schema.NonEmptyString),\n waiting_wake_at_ms: Schema.NullOr(Natural),\n waiting_token: Schema.NullOr(Schema.NonEmptyString),\n waiting_request: Schema.optional(Schema.NullOr(Schema.fromJsonString(Schema.Json))),\n revision: Natural,\n deleted: Schema.Literal(0)\n})\n\n/**\n * Structured waiting condition; nullable fields preserve older unknown values.\n * The opaque wake token leaves the engine only as its SHA-256 `tokenDigest`,\n * the same digest `ExecutionFacts` records: a reader may correlate a wait but\n * never holds the address that resolves it.\n *\n * @private\n * @since 1.0.0\n */\nexport interface Waiting {\n readonly kind: \"timer\" | \"signal\" | \"approval\" | \"quota\" | \"human\" | \"other\"\n readonly reason: string\n readonly wakeAtMs: number | null\n readonly tokenDigest: Sha256.Digest | null\n readonly request?: typeof Schema.Json.Type | undefined\n}\n\nconst waitingKind = (reason: string): Waiting[\"kind\"] => {\n switch (reason) {\n case \"timer\":\n case \"approval\":\n case \"quota\":\n case \"human\":\n return reason\n case \"event\":\n case \"signal\":\n return \"signal\"\n default:\n return \"other\"\n }\n}\n\n/**\n * Page-scoped lifecycle fields and indexed earliest spawn edge.\n *\n * @private\n * @since 1.0.0\n */\nexport const selectColumns = `r.*, c.revision, c.deleted,\n (SELECT parent_id FROM flows_run_parents WHERE child_id = r.run_id ORDER BY seq, parent_id LIMIT 1) AS spawn_parent_id,\n (SELECT seq FROM flows_run_parents WHERE child_id = r.run_id ORDER BY seq, parent_id LIMIT 1) AS spawn_seq`\n\n/**\n * Decodes a run row and validates observation invariants.\n *\n * @private\n * @since 1.0.0\n */\nexport const observed = (input: unknown, at: Position) =>\n Effect.gen(function*() {\n const row = yield* decode(Row, input)\n const parentRunId = row.parent_run_id ?? row.spawn_parent_id\n const ownerFields = [row.owner_host_id, row.owner_pid, row.owner_nonce, row.heartbeat_at_ms]\n const validOwner = row.status === \"running\"\n ? ownerFields.every((value) => value !== null)\n : ownerFields.every((value) => value === null)\n if (\n !validOwner || row.revision === 0 || row.revision > at.revision ||\n parentRunId !== row.execution_parent_id || parentRunId === row.run_id ||\n (row.lineage_id === null) !== (row.round_ordinal === null) ||\n (row.waiting_reason === null && (row.waiting_wake_at_ms !== null || row.waiting_token !== null ||\n row.waiting_request !== undefined && row.waiting_request !== null)) ||\n (row.cancel_acknowledgement_json !== null && row.cancel_requested_at_ms === null)\n ) {\n return yield* Effect.fail(\n new RunStoreError({\n method: \"ExecutionSnapshot.decode\",\n code: \"decode_failed\",\n message: \"stored execution observation violates its invariants\",\n cause: new Error(\"inconsistent execution identity, wait, cancellation or revision\")\n })\n )\n }\n return {\n _tag: \"Observed\" as const,\n runId: row.run_id,\n source: at.source,\n revision: row.revision,\n status: row.status,\n flowName: row.state_json.flowName,\n createdAtMs: row.created_at_ms,\n startedAtMs: row.started_at_ms,\n finishedAtMs: row.finished_at_ms,\n parentRunId,\n lineageId: row.lineage_id ?? row.run_id,\n roundOrdinal: row.round_ordinal ?? 0,\n cancellation: {\n requestedAtMs: row.cancel_requested_at_ms,\n acknowledgement: row.cancel_acknowledgement_json\n },\n waiting: row.waiting_reason === null ? null : {\n kind: waitingKind(row.waiting_reason),\n reason: row.waiting_reason,\n wakeAtMs: row.waiting_wake_at_ms,\n tokenDigest: row.waiting_token === null ? null : Sha256.digestSync(row.waiting_token),\n ...(row.waiting_request === undefined || row.waiting_request === null ? {} : { request: row.waiting_request })\n }\n }\n })\n\n/**\n * The result of a successful execution observation.\n *\n * @private\n * @since 1.0.0\n */\nexport type Observed = Effect.Success<ReturnType<typeof observed>>\n\n/**\n * Validates source, identity, ordering and revision coherence at the batch boundary.\n *\n * @private\n * @since 1.0.0\n */\nexport const coherentBatch = (runIds: ReadonlyArray<string>, batch: Batch): Effect.Effect<Batch, RunStoreError> => {\n const validPosition = Schema.is(Position)\n if (\n !validPosition(batch) || batch.snapshots.length !== runIds.length ||\n batch.snapshots.some((row, index) =>\n !validPosition(row) || row.runId !== runIds[index] || row.source !== batch.source ||\n row.revision > batch.revision\n )\n ) {\n return Effect.fail(\n new RunStoreError({\n method: \"ExecutionSnapshot.read\",\n code: \"persistence_failed\",\n message: \"stored execution batch is incoherent: source, identity, order or revision mismatch\",\n cause: undefined\n })\n )\n }\n return Effect.succeed(batch)\n}\n"],
|
|
5
5
|
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { RunStoreError } from "@smthrs/run-store/RunStore";
|
|
7
7
|
import { Effect } from "effect";
|
|
8
|
-
import type * as SqlClient from "effect/
|
|
8
|
+
import type * as SqlClient from "effect/sql/SqlClient";
|
|
9
9
|
import type { Batch, Snapshot } from "../ExecutionSnapshot.ts";
|
|
10
10
|
/**
|
|
11
11
|
* Child edges and trampoline rounds are different relations.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ExecutionSnapshotRelations.d.ts","sourceRoot":"","sources":["../../../src/internal/ExecutionSnapshotRelations.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAA;AAC1D,OAAO,EAAE,MAAM,EAAU,MAAM,QAAQ,CAAA;AACvC,OAAO,KAAK,KAAK,SAAS,MAAM
|
|
1
|
+
{"version":3,"file":"ExecutionSnapshotRelations.d.ts","sourceRoot":"","sources":["../../../src/internal/ExecutionSnapshotRelations.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAA;AAC1D,OAAO,EAAE,MAAM,EAAU,MAAM,QAAQ,CAAA;AACvC,OAAO,KAAK,KAAK,SAAS,MAAM,sBAAsB,CAAA;AACtD,OAAO,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAA;AAG9D;;;;;GAKG;AACH,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,IAAI,EAAE,UAAU,GAAG,QAAQ,CAAA;IACpC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CACxB;AACD;;;;;GAKG;AACH,MAAM,WAAW,WAAY,SAAQ,KAAK;IACxC,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAA;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;CAC/B;AAkBD;;;;;GAKG;AACH,eAAO,MAAM,IAAI,GACf,KAAK,SAAS,CAAC,SAAS,EACxB,MAAM,CAAC,GAAG,EAAE,aAAa,CAAC,MAAM,CAAC,KAAK,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,aAAa,CAAC,MAE1E,SAAS,cAAc,KAAG,MAAM,CAAC,MAAM,CAAC,WAAW,EAAE,aAAa,CA0DhE,CAAA"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/internal/ExecutionSnapshotRelations.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Page-scoped durable child and round enumeration.\n *\n * @since 1.0.0\n */\n\nimport * as Dialect from \"@smthrs/database/Dialect\"\nimport { RunStoreError } from \"@smthrs/run-store/RunStore\"\nimport { Effect, Schema } from \"effect\"\nimport type * as SqlClient from \"effect/
|
|
4
|
+
"sourcesContent": ["/**\n * Page-scoped durable child and round enumeration.\n *\n * @since 1.0.0\n */\n\nimport * as Dialect from \"@smthrs/database/Dialect\"\nimport { RunStoreError } from \"@smthrs/run-store/RunStore\"\nimport { Effect, Schema } from \"effect\"\nimport type * as SqlClient from \"effect/sql/SqlClient\"\nimport type { Batch, Snapshot } from \"../ExecutionSnapshot.ts\"\nimport * as Read from \"./ExecutionSnapshotRead.ts\"\n\n/**\n * Child edges and trampoline rounds are different relations.\n *\n * @category models\n * @since 1.0.0\n */\nexport interface RelatedOptions {\n readonly runId: string\n readonly kind: \"children\" | \"rounds\"\n readonly cursor?: string\n readonly limit?: number\n}\n/**\n * A requested anchor and its page, observed in one transaction.\n *\n * @category models\n * @since 1.0.0\n */\nexport interface RelatedPage extends Batch {\n readonly anchor: Snapshot\n readonly cursor: string | null\n}\nconst Kind = Schema.Literals([\"children\", \"rounds\"])\nconst Options = Schema.Struct({\n runId: Read.Identifier,\n kind: Kind,\n cursor: Schema.optionalKey(Schema.String.check(Schema.isMaxLength(32768))),\n limit: Schema.optionalKey(Schema.Int.check(Schema.isBetween({ minimum: 1, maximum: 200 })))\n})\nconst Cursor = Schema.fromJsonString(Schema.Struct({\n version: Schema.Literal(1),\n source: Read.Source,\n runId: Read.Identifier,\n kind: Kind,\n ordinal: Read.Natural,\n lastRunId: Read.Identifier\n}))\nconst Relation = Schema.Struct({ run_id: Read.Identifier, ordinal: Read.Natural })\n\n/**\n * Builds a bounded relation reader over the shared snapshot port.\n *\n * @private\n * @since 1.0.0\n */\nexport const make = (\n sql: SqlClient.SqlClient,\n read: (ids: ReadonlyArray<string>) => Effect.Effect<Batch, RunStoreError>\n) =>\n(options: RelatedOptions): Effect.Effect<RelatedPage, RunStoreError> =>\n Read.boundary(\n \"ExecutionSnapshot.related\",\n Effect.gen(function*() {\n const input = yield* Read.decode(Options, options)\n const cursor = input.cursor === undefined ? null : yield* Read.decode(Cursor, input.cursor)\n return yield* Read.transaction(\n sql,\n Effect.gen(function*() {\n const at = yield* Read.position(sql)\n if (\n cursor !== null &&\n (cursor.source !== at.source || cursor.runId !== input.runId || cursor.kind !== input.kind)\n ) {\n return yield* Effect.fail(\n new RunStoreError({\n method: \"ExecutionSnapshot.related\",\n code: \"invalid_run\",\n message: \"relation cursor belongs to another query or source\",\n cause: cursor\n })\n )\n }\n const anchor = (yield* read([input.runId])).snapshots[0]!\n const limit = input.limit ?? 100\n const values: Array<string | number | null> = [input.runId]\n const columns = input.kind === \"children\" ? [\"seq\", \"child_id\"] : [\"COALESCE(round_ordinal, 0)\", \"run_id\"]\n const after = cursor === null ? \"\" : `AND (${columns.join(\", \")}) > (?, ?)`\n if (cursor !== null) values.push(cursor.ordinal, cursor.lastRunId)\n values.push(limit + 1)\n const query = input.kind === \"children\"\n ? `SELECT child_id AS run_id, seq AS ordinal FROM flows_run_parents\n WHERE parent_id = ? ${after} ORDER BY seq, child_id LIMIT ?`\n : `SELECT run_id, COALESCE(round_ordinal, 0) AS ordinal FROM flows_runs\n WHERE execution_lineage = (SELECT execution_lineage FROM flows_runs WHERE run_id = ?)\n ${after} ORDER BY COALESCE(round_ordinal, 0), run_id LIMIT ?`\n const rows = yield* Dialect.query(sql, query, values)\n const decoded = yield* Effect.forEach(rows, (row) => Read.decode(Relation, row))\n const selected = decoded.slice(0, limit)\n const batch = yield* read(selected.map((row) => row.run_id))\n const last = selected[selected.length - 1]\n return {\n ...batch,\n anchor,\n cursor: decoded.length > limit ?\n JSON.stringify({\n version: 1,\n source: at.source,\n runId: input.runId,\n kind: input.kind,\n ordinal: last!.ordinal,\n lastRunId: last!.run_id\n }) :\n null\n }\n })\n )\n })\n )\n"],
|
|
5
5
|
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -53,8 +53,8 @@ import * as Context from "effect/Context";
|
|
|
53
53
|
import * as Effect from "effect/Effect";
|
|
54
54
|
import * as Layer from "effect/Layer";
|
|
55
55
|
import * as Schema from "effect/Schema";
|
|
56
|
-
import * as SqlClient from "effect/
|
|
57
|
-
import type { SqlError } from "effect/
|
|
56
|
+
import * as SqlClient from "effect/sql/SqlClient";
|
|
57
|
+
import type { SqlError } from "effect/sql/SqlError";
|
|
58
58
|
/**
|
|
59
59
|
* Stable error codes returned by retention.
|
|
60
60
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RetentionOps.d.ts","sourceRoot":"","sources":["../../../src/internal/RetentionOps.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiDG;AAGH,OAAO,KAAK,OAAO,MAAM,yBAAyB,CAAA;AAIlD,OAAO,KAAK,OAAO,MAAM,gBAAgB,CAAA;AACzC,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AACvC,OAAO,KAAK,KAAK,MAAM,cAAc,CAAA;AACrC,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AACvC,OAAO,KAAK,SAAS,MAAM
|
|
1
|
+
{"version":3,"file":"RetentionOps.d.ts","sourceRoot":"","sources":["../../../src/internal/RetentionOps.ts"],"names":[],"mappings":"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"}
|
|
@@ -53,7 +53,7 @@ var Context = __toESM(require("effect/Context"), 1);
|
|
|
53
53
|
var Effect = __toESM(require("effect/Effect"), 1);
|
|
54
54
|
var Layer = __toESM(require("effect/Layer"), 1);
|
|
55
55
|
var Schema = __toESM(require("effect/Schema"), 1);
|
|
56
|
-
var SqlClient = __toESM(require("effect/
|
|
56
|
+
var SqlClient = __toESM(require("effect/sql/SqlClient"), 1);
|
|
57
57
|
const RetentionErrorCode = Schema.Literals(["scan_failed", "delete_failed"]);
|
|
58
58
|
class RetentionError extends Schema.TaggedError()(
|
|
59
59
|
"@smthrs/engine-store/RetentionError",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/internal/RetentionOps.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Retention: the one operation that deletes finished run state.\n *\n * Nothing in the composition removed a `flows_runs` row or anything that hangs\n * off one. Every run a workspace ever finished stayed in `.flows/engine.db`\n * with its attempts, its persisted deferred completions and clock deadlines,\n * its journal entries, and its checkpoints, and journal compaction is off by\n * default (`SqlJournal` `Options.compaction`), so the file only grew. This\n * module is the deletion side, and the whole of it: one operation, run\n * explicitly.\n *\n * Three properties make it safe to run against a live workspace.\n *\n * - **One transaction.** Every delete runs inside a single `journal.transact`,\n * so a crash leaves either a run with all of its rows or none of them \u2014\n * never a run row whose history is gone, or history whose run row is gone.\n * - **Terminal and aged only.** A run is a candidate only when its status is\n * `completed`, `failed`, or `cancelled` AND it finished before the cutoff.\n * A running, pending, or suspended run is never a candidate at any age.\n * - **Nothing a live run still needs.** A candidate is retained when a live\n * run stands on either side of it in the lineage, over BOTH relations that\n * make one run the parent of another: the `flows_run_parents` edge a spawned\n * child is recorded under, and the reserved `parent_run_id` column the\n * rounds of one trampoline lineage are chained through. Upward: an aged\n * terminal run with a live ancestor stays, because a parent reads a settled\n * child's result out of its run row (`agent/await`) and a parent parked on\n * an approval, a deferred, or a timer can be parked for longer than the\n * threshold before it ever asks. Downward: an aged terminal run whose\n * descendant is still live stays, because deleting it would leave that\n * descendant's `parent_run_id` pointing at nothing and would drop the live\n * descendant's lineage edges with the `flows_run_parents_gc` trigger.\n * - **Explicit.** Nothing schedules this. Automatic retention stays opt-in,\n * for the reason `ArtifactGc` states: deletion is the irreversible\n * direction, and a human approving a plan must be approving the deletions.\n *\n * Journal history is deleted outright rather than compacted to a checkpoint.\n * `Journal.checkpoint` and `Journal.compact` are owner-fenced \u2014 `SqlJournal`'s\n * `fenceGuard` requires a consensus lease held by the exact owner passed in \u2014\n * so neither can be called for a finished, ownerless run at all. Deleting the run's entries and checkpoints is strictly stronger than\n * compacting them to a checkpoint, and it happens in the same transaction as\n * the run row, which is the property a compaction could not have given.\n *\n * The operation is idempotent. With a positive integer limit and a fixed,\n * finite, acyclic run lineage, each successful non-dry pass deletes at least\n * one collectable run until none remain. Continuation children are selected\n * before parents; parents pinned by excluded children do not consume the\n * bound. Runs protected by age or live lineage remain until eligible.\n *\n * @since 0.1.0\n */\n\nimport * as Dialect from \"@smthrs/database/Dialect\"\nimport * as Journal from \"@smthrs/journal/Journal\"\nimport * as JournalEvent from \"@smthrs/journal/JournalEvent\"\nimport * as RunStore from \"@smthrs/run-store/RunStore\"\nimport * as Clock from \"effect/Clock\"\nimport * as Context from \"effect/Context\"\nimport * as Effect from \"effect/Effect\"\nimport * as Layer from \"effect/Layer\"\nimport * as Schema from \"effect/Schema\"\nimport * as SqlClient from \"effect/unstable/sql/SqlClient\"\nimport type { SqlError } from \"effect/unstable/sql/SqlError\"\n\n/**\n * Stable error codes returned by retention.\n *\n * @category models\n * @since 0.1.0\n */\nexport const RetentionErrorCode = Schema.Literals([\"scan_failed\", \"delete_failed\"])\n\n/**\n * Stable error codes returned by retention.\n *\n * @category models\n * @since 0.1.0\n */\nexport type RetentionErrorCode = typeof RetentionErrorCode.Type\n\n/**\n * A retention pass that could not complete.\n *\n * `scan_failed` means the candidate set could not be computed and nothing was\n * deleted. `delete_failed` means the candidate set held but a delete refused;\n * the transaction rolled back, so nothing was deleted then either, and\n * re-running converges once the cause is fixed.\n *\n * @category errors\n * @since 0.1.0\n */\nexport class RetentionError extends Schema.TaggedError<RetentionError>()(\n \"@smthrs/engine-store/RetentionError\",\n {\n code: RetentionErrorCode,\n message: Schema.String,\n cause: Schema.optional(Schema.Unknown)\n }\n) {}\n\n/**\n * Options for one retention pass.\n *\n * @category models\n * @since 0.1.0\n */\nexport interface RetainOptions {\n /**\n * How long a run must have been finished to be collected, in milliseconds.\n * A run's age is measured from `finished_at_ms`, or from `created_at_ms`\n * for a terminal row that never recorded one. Zero collects every terminal\n * run; a negative value is read as zero.\n */\n readonly olderThanMs: number\n /**\n * Largest number of runs one pass deletes. Defaults to\n * {@link defaultLimit}. Retention is idempotent, so a workspace with more\n * aged runs than the bound converges over repeated passes instead of\n * holding one long write transaction open. A negative value is read as\n * zero, so a pass under a mistyped bound deletes nothing.\n */\n readonly limit?: number | undefined\n /** Compute and report the pass without deleting anything. */\n readonly dryRun?: boolean | undefined\n}\n\n/**\n * What one retention pass did, or \u2014 under `dryRun` \u2014 would do.\n *\n * @category models\n * @since 0.1.0\n */\nexport interface RetainReport {\n /** Runs finished at or before this instant were candidates. */\n readonly cutoffMs: number\n /** The runs deleted, oldest first. */\n readonly runIds: ReadonlyArray<string>\n /**\n * Aged terminal runs left in place because a descendant of theirs is not\n * terminal, or is terminal and is not being deleted by this pass. They\n * become collectable once that descendant is. At most `limit` of them are\n * reported, the oldest first, so the report costs no more than the pass.\n */\n readonly retainedForLiveDescendants: ReadonlyArray<string>\n /**\n * Aged terminal runs left in place because an ancestor of theirs is not\n * terminal. That ancestor can still read the run's settled result through\n * `agent/await`, which answers out of the run row. They become collectable\n * once every ancestor is terminal \u2014 a terminal ancestor holds nothing back,\n * whatever its own age. A run retained for both reasons is reported under\n * {@link retainedForLiveDescendants}, and at most `limit` runs are reported.\n */\n readonly retainedForLiveAncestors: ReadonlyArray<string>\n readonly runs: number\n readonly attempts: number\n readonly clockDeadlines: number\n readonly deferredCompletions: number\n readonly journalEntries: number\n readonly journalCheckpoints: number\n /** Producer identities journal compaction kept after deleting event payloads. */\n readonly journalIdentities: number\n /** Time-travel archive rows; always zero where that table is not installed. */\n readonly archiveEntries: number\n /** Time-travel receipt rows reached through their audits. */\n readonly timeTravelReceipts: number\n readonly dryRun: boolean\n}\n\n/**\n * Explicit run retention.\n *\n * @category models\n * @since 0.1.0\n */\nexport interface Service {\n /**\n * Deletes up to `limit` aged terminal runs and their dependents atomically.\n * With a positive integer limit and a fixed, finite, acyclic lineage, each\n * successful non-dry pass makes progress until all collectable runs are gone,\n * including continuation chains longer than the bound. Age and live-lineage\n * protection still apply. Explicit only; nothing schedules this operation.\n */\n readonly retain: (\n options: RetainOptions\n ) => Effect.Effect<RetainReport, RetentionError | Journal.JournalError>\n}\n\n/**\n * Service tag for run retention.\n *\n * @category services\n * @since 0.1.0\n */\nexport class Retention extends Context.Service<Retention, Service>()(\"@smthrs/engine-store/Retention\") {}\n\n/**\n * Runs deleted by one pass when the caller names no bound. Large enough that\n * an ordinary workspace finishes in one pass, small enough that the write\n * transaction stays short on a workspace that has never been collected.\n *\n * @category constants\n * @since 0.1.0\n */\nexport const defaultLimit = 1000\n\n/**\n * Run ids per `IN (...)` list. SQLite's default host-parameter ceiling is 999.\n *\n * @category constants\n * @since 0.1.0\n */\nconst chunkSize = 500\n\n/**\n * One table a deleted run leaves rows in.\n *\n * @category models\n * @since 1.0.0\n */\nexport interface RunScopedTable {\n readonly table: string\n /** The column naming the run. */\n readonly column: string\n /**\n * Whether the migration ladder this package composes installs the table.\n *\n * A ladder table missing from an engine database is a broken schema, so\n * {@link installedTables} can be asked to keep it and let the delete refuse.\n * A table beyond the ladder belongs to a store the host may simply not have\n * composed, and is skipped when the catalog does not list it.\n */\n readonly ladder: boolean\n /**\n * Whether the table also holds the runs' journal companion streams\n * (`JournalEvent.companionRunId`), which name a run through a derived id\n * rather than the run id itself. {@link companionStreams} lists them.\n */\n readonly companions?: boolean | undefined\n}\n\n/**\n * The journal companion streams a run owns beside its own stream. A deleted\n * run's companion streams go with it.\n *\n * @category constants\n * @since 1.0.0\n */\nexport const companionStreams: ReadonlyArray<string> = [RunStore.companionStream]\n\n/**\n * Every table a deleted run leaves rows in, and the column naming the run.\n *\n * ONE inventory, because two passes read it: {@link Service.retain} over the\n * engine ladder, and `Retention.collect` over whatever a host's file holds. A\n * table listed by only one of them is a table the other leaks forever. Tables\n * are listed rather than discovered from foreign keys because the set spans\n * six packages that migrate into one database, and a silently missing entry is\n * exactly that leak.\n *\n * The run rows themselves are not here: they go last and in generation order,\n * which {@link deleteRuns} does after this list.\n * `flows_time_travel_receipts` is reached through its audit rather than listed\n * here because the upstream table has no run column: its `audit_id` points to\n * `flows_time_travel_audits.id`, whose row carries `run_id`.\n *\n * @category constants\n * @since 0.1.0\n */\nexport const runScopedTables: ReadonlyArray<RunScopedTable> = [\n { table: \"flows_deferred_completions\", column: \"execution_id\", ladder: true },\n { table: \"flows_clock_deadlines\", column: \"execution_id\", ladder: true },\n { table: \"flows_attempts\", column: \"run_id\", ladder: true },\n { table: \"flows_journal_events\", column: \"run_id\", ladder: true, companions: true },\n { table: \"flows_journal_checkpoints\", column: \"run_id\", ladder: true, companions: true },\n { table: \"flows_journal_dedup\", column: \"run_id\", ladder: true, companions: true },\n { table: \"flows_consensus_leases\", column: \"run_id\", ladder: true },\n { table: \"flows_step_cache_recorded\", column: \"recorded_run_id\", ladder: true },\n { table: \"flows_time_travel_archive\", column: \"run_id\", ladder: false },\n { table: \"flows_time_travel_snapshots\", column: \"run_id\", ladder: false },\n { table: \"flows_time_travel_audits\", column: \"run_id\", ladder: false },\n { table: \"flows_time_travel_edges\", column: \"child_run_id\", ladder: false },\n { table: \"control_run_messages\", column: \"run_id\", ladder: false },\n { table: \"control_runs\", column: \"run_id\", ladder: false }\n]\n\n/**\n * The statuses a run can never leave.\n *\n * @category constants\n * @since 1.0.0\n */\nexport const terminalStatuses: ReadonlyArray<string> = [\"completed\", \"failed\", \"cancelled\"]\n\n/** `terminalStatuses` as a SQL list, for the lineage prelude below. */\nconst terminalList = terminalStatuses.map((status) => `'${status}'`).join(\", \")\n\n/**\n * The prelude every scan opens with: which runs a live run stands above, and\n * which it stands below.\n *\n * Two relations make one run the parent of another, and a walk over either one\n * alone misses half the lineage. A SPAWNED child records its parent as a\n * `flows_run_parents` edge, one row per parent so a diamond records both, and\n * leaves `parent_run_id` NULL: the engine reserves that column for the rounds\n * of one trampoline lineage (`RunDriver.continueLineage`). The edge is the\n * relation every `agent/await` depends on. `link` is both relations as one\n * edge list.\n *\n * `under_live` is every run reachable downward from a run that is not\n * terminal. Those runs have a live ancestor, which can still read a settled\n * result out of their run row. `over_live` is every run reachable upward from\n * one. Those runs have a live descendant, whose own lineage the\n * `flows_run_parents_gc` trigger would drop with them. An edge whose parent\n * row is gone is not reachable from `live` at all, so it holds nothing back,\n * and since only terminal runs are ever deleted it never can.\n *\n * The walks carry run ids and nothing else, so `UNION` makes them terminate on\n * their own: a corrupt edge that closed a cycle adds a row the set already\n * holds, and the walk stops. That matters because these reads run inside the\n * pass's write transaction. They are SQLite-specific, like the `sqlite_master`\n * probe below, and rc.0 is SQLite-only.\n *\n * `parentEdges: false` drops the `flows_run_parents` half of `link`. A\n * database that carries the run rows but not the engine's edge table \u2014 the\n * control plane's, which migrates the run store and the journal and nothing\n * else \u2014 has no edges to walk, and reading the missing table would fail the\n * whole scan. The `parent_run_id` half still runs, so the continuation\n * lineage is guarded there too. The retention pass itself always walks both.\n *\n * @category constructors\n * @since 0.1.0\n */\nexport const lineagePrelude = (\n options: { readonly parentEdges: boolean }\n): string => `\n WITH RECURSIVE\n link(child_id, parent_id) AS (\n ${\n options.parentEdges\n ? `SELECT child_id, parent_id FROM flows_run_parents\n UNION\n `\n : ``\n}SELECT run_id, parent_run_id FROM flows_runs WHERE parent_run_id IS NOT NULL\n ),\n live(run_id) AS (\n SELECT run_id FROM flows_runs WHERE status NOT IN (${terminalList})\n ),\n under_live(run_id) AS (\n SELECT link.child_id FROM link JOIN live ON live.run_id = link.parent_id\n UNION\n SELECT link.child_id FROM link JOIN under_live ON under_live.run_id = link.parent_id\n ),\n over_live(run_id) AS (\n SELECT link.parent_id FROM link JOIN live ON live.run_id = link.child_id\n UNION\n SELECT link.parent_id FROM link JOIN over_live ON over_live.run_id = link.child_id\n )\n `\n\n/**\n * The bound one pass may delete, as both passes read it.\n *\n * Clamped, not interpolated: SQLite reads a negative `LIMIT` as no limit at\n * all, which would make a mistyped bound a full sweep, and rejects `NaN` or a\n * fraction as a datatype mismatch. Anything that is not a non-negative safe\n * integer is read as zero, so the pass deletes nothing.\n *\n * @category constructors\n * @since 1.0.0\n */\nexport const normalizeLimit = (limit: number | undefined): number => {\n const bound = limit ?? defaultLimit\n return Number.isSafeInteger(bound) && bound >= 0 ? bound : 0\n}\n\n/**\n * Which runs a pass may delete, and which it must keep, as CTEs.\n *\n * `eligible` is every terminal run older than the cutoff that no live run\n * stands above or below. `blocked` is every other run plus every ancestor of\n * one over `parent_run_id`: a continuation parent must outlive every child\n * excluded by age or live lineage, and this propagates that exclusion before\n * any bound is applied. `inclusive` says whether a run finished exactly at\n * the cutoff is old enough.\n */\nconst scanPrelude = (\n sql: SqlClient.SqlClient,\n options: {\n readonly cutoffMs: number\n readonly inclusive: boolean\n readonly parentEdges: boolean\n readonly pins?: ReadonlyArray<string> | undefined\n }\n) =>\n sql`\n ${sql.unsafe(lineagePrelude({ parentEdges: options.parentEdges }))},\n eligible(run_id) AS (\n SELECT run_id FROM flows_runs\n WHERE ${sql.in(\"status\", terminalStatuses)}\n AND ${\n options.inclusive\n ? sql`COALESCE(finished_at_ms, created_at_ms) <= ${options.cutoffMs}`\n : sql`COALESCE(finished_at_ms, created_at_ms) < ${options.cutoffMs}`\n }\n AND run_id NOT IN (SELECT run_id FROM under_live)\n AND run_id NOT IN (SELECT run_id FROM over_live)\n AND ${\n options.pins === undefined || options.pins.length === 0 ?\n sql.literal(\"1 = 1\")\n : sql`run_id NOT IN ${sql.in(options.pins)}`\n }\n ),\n blocked(run_id) AS (\n SELECT run_id FROM flows_runs WHERE run_id NOT IN (SELECT run_id FROM eligible)\n UNION\n SELECT runs.parent_run_id FROM flows_runs AS runs\n JOIN blocked ON blocked.run_id = runs.run_id\n WHERE runs.parent_run_id IS NOT NULL\n )\n `\n\n/**\n * The runs one pass deletes: at most `limit` of them, children before parents.\n *\n * The one candidate query, shared by {@link Service.retain} and\n * `Retention.collect`. A trampoline parent always finishes before its\n * successor, so an oldest-first window over a lineage longer than the bound\n * held only ancestors, each pinned by the round just outside the window, and\n * every later pass selected the same window and deleted nothing. Depth walks\n * from roots, visiting each continuation row once; corrupt cycles have no\n * root and cannot enter the deletion set. Excluded runs and their ancestors\n * are dropped before the bound, so pinned parents cannot consume it. The\n * result is presented oldest first.\n *\n * @category getters\n * @since 1.0.0\n */\nexport const candidatesOf = (\n sql: SqlClient.SqlClient,\n options: {\n readonly cutoffMs: number\n readonly inclusive: boolean\n readonly parentEdges: boolean\n readonly pins?: ReadonlyArray<string> | undefined\n readonly limit: number\n }\n): Effect.Effect<ReadonlyArray<Candidate>, SqlError> =>\n sql<{ readonly run_id: string; readonly parent_run_id: string | null }>`\n ${scanPrelude(sql, options)},\n depths(run_id, depth) AS (\n SELECT run_id, 0 FROM flows_runs WHERE parent_run_id IS NULL\n UNION ALL\n SELECT runs.run_id, depths.depth + 1 FROM flows_runs AS runs\n JOIN depths ON runs.parent_run_id = depths.run_id\n ),\n selected AS (\n SELECT runs.run_id, runs.parent_run_id,\n COALESCE(runs.finished_at_ms, runs.created_at_ms) AS finished_at_ms\n FROM flows_runs AS runs JOIN depths ON depths.run_id = runs.run_id\n WHERE runs.run_id NOT IN (SELECT run_id FROM blocked)\n ORDER BY depths.depth DESC, COALESCE(runs.finished_at_ms, runs.created_at_ms), runs.run_id\n LIMIT ${normalizeLimit(options.limit)}\n )\n SELECT run_id, parent_run_id FROM selected ORDER BY finished_at_ms, run_id\n `.pipe(\n Effect.map((rows): ReadonlyArray<Candidate> =>\n rows.map((row) => ({ runId: row.run_id, parentRunId: row.parent_run_id }))\n )\n )\n\n/** Classifies a read of the scan phase, where nothing has been deleted yet. */\nconst scanning = (what: string) => (cause: unknown): RetentionError =>\n new RetentionError({ code: \"scan_failed\", message: `${what} could not be read`, cause })\n\n/** Classifies a statement of the delete phase, which rolls the pass back. */\nconst deleting = (what: string) => (cause: unknown): RetentionError =>\n new RetentionError({ code: \"delete_failed\", message: `${what} could not be collected`, cause })\n\nconst chunksOf = (ids: ReadonlyArray<string>): ReadonlyArray<ReadonlyArray<string>> => {\n const chunks: Array<ReadonlyArray<string>> = []\n for (let offset = 0; offset < ids.length; offset += chunkSize) {\n chunks.push(ids.slice(offset, offset + chunkSize))\n }\n return chunks\n}\n\n/**\n * A run a pass proposes to delete, and the run it names as its parent.\n *\n * @category models\n * @since 0.1.0\n */\nexport interface Candidate {\n readonly runId: string\n readonly parentRunId: string | null\n}\n\n/** A run and the candidate it names as its parent. */\ninterface ChildEdge {\n readonly runId: string\n readonly parentRunId: string\n}\n\n/**\n * What one deletion pass removed, or would remove under `dryRun`.\n *\n * @category models\n * @since 0.1.0\n */\nexport interface DeletionReport {\n /** The candidates deleted, in the order they were given. */\n readonly runIds: ReadonlyArray<string>\n /** Candidates kept because a run outside the pass still names them. */\n readonly pinned: ReadonlyArray<string>\n /**\n * Rows counted per table, `flows_runs` included, zeroes omitted. Counted\n * whether or not the pass deletes, so a dry run reports the same numbers.\n */\n readonly deleted: Readonly<Record<string, number>>\n}\n\n/** Reads the installed SQLite table names once for inventory decisions. */\nconst installedTableNames = (sql: SqlClient.SqlClient): Effect.Effect<ReadonlySet<string>, RetentionError> =>\n Dialect.tables(sql).pipe(\n Effect.map((rows) => new Set(rows.map((row) => row.name))),\n Effect.mapError(scanning(\"the schema catalog\"))\n )\n\n/**\n * The {@link runScopedTables} entries this database actually has.\n *\n * `assumeLadder` keeps every entry the ladder installs whether the catalog\n * lists it or not. {@link Service.retain} passes `true`: this package composes\n * the run-store, journal, step-cache and engine migrations, so a ladder table\n * missing from the file it is pointed at is a broken schema, and a delete that\n * refuses says so. `Retention.collect` passes `false`, because it also runs\n * against the control plane's database, which migrates the run store and the\n * journal and nothing else; there a missing ladder table is a host that\n * composed fewer stores, and the pass still owes it a complete sweep of what\n * it has.\n *\n * The catalog read is SQLite-specific, like the lineage prelude above and the\n * `flows_run_parents_gc` trigger this package ships. rc.0 is SQLite-only.\n *\n * @category getters\n * @since 0.1.0\n */\nexport const installedTables = (\n sql: SqlClient.SqlClient,\n options: { readonly assumeLadder: boolean }\n): Effect.Effect<ReadonlyArray<RunScopedTable>, RetentionError> =>\n installedTableNames(sql).pipe(\n Effect.map((present) =>\n runScopedTables.filter((entry) => (options.assumeLadder && entry.ladder) || present.has(entry.table))\n )\n )\n\nconst countIn = (sql: SqlClient.SqlClient, table: string, column: string, ids: ReadonlyArray<string>) =>\n Effect.reduce(chunksOf(ids), () => 0, (total, chunk) =>\n sql<{ readonly total: number }>`\n SELECT COUNT(*) AS total FROM ${sql.unsafe(table)} WHERE ${sql.in(column, chunk)}\n `.pipe(\n Effect.map((rows) => rows.reduce((sum, row) => sum + Number(row.total), total)),\n Effect.mapError(deleting(table))\n ))\n\nconst deleteIn = (sql: SqlClient.SqlClient, table: string, column: string, ids: ReadonlyArray<string>) =>\n Effect.forEach(\n chunksOf(ids),\n (chunk) =>\n sql`DELETE FROM ${sql.unsafe(table)} WHERE ${sql.in(column, chunk)}`.pipe(\n Effect.mapError(deleting(table))\n ),\n { discard: true }\n )\n\nconst countTimeTravelReceipts = (sql: SqlClient.SqlClient, ids: ReadonlyArray<string>) =>\n Effect.reduce(chunksOf(ids), () => 0, (total, chunk) =>\n sql<{ readonly total: number }>`\n SELECT COUNT(*) AS total\n FROM flows_time_travel_receipts\n WHERE audit_id IN (\n SELECT id FROM flows_time_travel_audits WHERE ${sql.in(\"run_id\", chunk)}\n )\n `.pipe(\n Effect.map((rows) => rows.reduce((sum, row) => sum + Number(row.total), total)),\n Effect.mapError(deleting(\"flows_time_travel_receipts\"))\n ))\n\nconst deleteTimeTravelReceipts = (sql: SqlClient.SqlClient, ids: ReadonlyArray<string>) =>\n Effect.forEach(\n chunksOf(ids),\n (chunk) =>\n sql`\n DELETE FROM flows_time_travel_receipts\n WHERE audit_id IN (\n SELECT id FROM flows_time_travel_audits WHERE ${sql.in(\"run_id\", chunk)}\n )\n `.pipe(Effect.mapError(deleting(\"flows_time_travel_receipts\"))),\n { discard: true }\n )\n\n/** Every run whose parent is a candidate, candidates included. */\nconst childrenOf = (sql: SqlClient.SqlClient, candidateIds: ReadonlyArray<string>) =>\n Effect.reduce(\n chunksOf(candidateIds),\n (): Array<ChildEdge> => [],\n (accumulated, chunk) =>\n // `parent_run_id` is the match predicate, so every row this returns\n // has one.\n sql<{ readonly run_id: string; readonly parent_run_id: string }>`\n SELECT run_id, parent_run_id FROM flows_runs WHERE ${sql.in(\"parent_run_id\", chunk)}\n `.pipe(\n Effect.map((rows) => {\n for (const row of rows) {\n accumulated.push({ runId: row.run_id, parentRunId: row.parent_run_id })\n }\n return accumulated\n }),\n Effect.mapError(scanning(\"the run lineage\"))\n )\n )\n\n/**\n * Drops from `doomed` every candidate that must outlive this pass, and\n * every ancestor of one.\n *\n * A candidate whose child is not being deleted has to stay: `flows_runs`\n * carries a self-referential foreign key on `parent_run_id`, so deleting\n * it would orphan a row that still exists. Retention is upward-closed for\n * the same reason \u2014 once a candidate is retained, the candidate it points\n * at is still referenced and is retained too.\n */\nconst pinAncestors = (\n doomed: Set<string>,\n retained: Set<string>,\n parentOf: ReadonlyMap<string, string | null>,\n from: string\n): void => {\n let current: string | undefined = from\n while (current !== undefined && doomed.has(current)) {\n doomed.delete(current)\n retained.add(current)\n current = parentOf.get(current) ?? undefined\n }\n}\n\n/**\n * Groups the doomed runs so that no group holds a run and one of its\n * doomed descendants. Deleting a parent while its child row still exists\n * violates the foreign key, and SQLite evaluates it per row rather than at\n * commit, so the delete runs deepest generation first.\n */\nconst generations = (\n doomed: ReadonlySet<string>,\n parentOf: ReadonlyMap<string, string | null>\n): ReadonlyArray<ReadonlyArray<string>> => {\n const depths = new Map<string, number>()\n const depthOf = (runId: string): number => {\n const known = depths.get(runId)\n if (known !== undefined) return known\n const parent = parentOf.get(runId) ?? undefined\n const depth = parent !== undefined && doomed.has(parent) ? depthOf(parent) + 1 : 0\n depths.set(runId, depth)\n return depth\n }\n const byDepth = new Map<number, Array<string>>()\n for (const runId of doomed) {\n const depth = depthOf(runId)\n const bucket = byDepth.get(depth)\n if (bucket === undefined) byDepth.set(depth, [runId])\n else bucket.push(runId)\n }\n return Array.from(byDepth.keys())\n .sort((left, right) => right - left)\n .map((depth) => byDepth.get(depth)!)\n}\n\n/**\n * Deletes `candidates` and every row the inventory says names them.\n *\n * The one deletion in this package. Both passes call it, so neither can be\n * holding a shorter table list than the other, and neither can be deleting\n * run rows in an order the self-referential foreign key refuses: a handoff\n * parent always sorts before its successor, so a pass that deleted in age\n * order broke as soon as one lineage straddled a chunk boundary.\n *\n * The caller owns the transaction. Nothing here is atomic on its own, and a\n * pass that removed the dependents of every candidate and then refused on the\n * run rows would have destroyed history it could not put back.\n *\n * @category constructors\n * @since 0.1.0\n */\nexport const deleteRuns = (\n sql: SqlClient.SqlClient,\n candidates: ReadonlyArray<Candidate>,\n options: { readonly dryRun: boolean; readonly assumeLadder: boolean }\n): Effect.Effect<DeletionReport, RetentionError> =>\n Effect.gen(function*() {\n const installed = yield* installedTableNames(sql)\n const tables = runScopedTables.filter((entry) =>\n (options.assumeLadder && entry.ladder) || installed.has(entry.table)\n )\n const hasTimeTravelReceipts = installed.has(\"flows_time_travel_receipts\") &&\n installed.has(\"flows_time_travel_audits\")\n const candidateIds = candidates.map((candidate) => candidate.runId)\n const parentOf = new Map(candidates.map((candidate) => [candidate.runId, candidate.parentRunId]))\n const doomed = new Set(candidateIds)\n const pinned = new Set<string>()\n const candidateSet = new Set(candidateIds)\n // The foreign key, which no lineage filter covers: a child row that is\n // terminal and simply outside this pass \u2014 younger than the cutoff, or past\n // the bound \u2014 still points at its parent.\n for (const child of yield* childrenOf(sql, candidateIds)) {\n if (candidateSet.has(child.runId)) continue\n pinAncestors(doomed, pinned, parentOf, child.parentRunId)\n }\n // `candidates` arrives oldest first, so this reads in the order the pass\n // collected.\n const runIds = candidateIds.filter((runId) => doomed.has(runId))\n\n const companionIds = runIds.flatMap((runId) =>\n companionStreams.map((stream) => JournalEvent.companionRunId(stream, runId))\n )\n const idsFor = (entry: RunScopedTable): ReadonlyArray<string> =>\n entry.companions === true ? [...runIds, ...companionIds] : runIds\n\n const deleted: Record<string, number> = {}\n if (hasTimeTravelReceipts) {\n const total = yield* countTimeTravelReceipts(sql, runIds)\n if (total > 0) deleted[\"flows_time_travel_receipts\"] = total\n }\n for (const entry of tables) {\n const total = yield* countIn(sql, entry.table, entry.column, idsFor(entry))\n if (total > 0) deleted[entry.table] = total\n }\n if (runIds.length > 0) deleted[\"flows_runs\"] = runIds.length\n\n if (!options.dryRun) {\n // Receipts have no run column, so remove them through their audits\n // before the ordinary inventory reaches and deletes those audit rows.\n if (hasTimeTravelReceipts) yield* deleteTimeTravelReceipts(sql, runIds)\n for (const entry of tables) yield* deleteIn(sql, entry.table, entry.column, idsFor(entry))\n // Last, and deepest generation first. The `flows_run_parents_gc`\n // trigger drops each run's DAG edges as its row goes.\n for (const generation of generations(doomed, parentOf)) {\n yield* deleteIn(sql, \"flows_runs\", \"run_id\", generation)\n }\n }\n\n return { runIds, pinned: Array.from(pinned), deleted }\n })\n\n/**\n * Builds the retention operation over the composition's own durable tables.\n *\n * The scan reads `flows_runs` directly, the way `DurableEngineState` and\n * `ArtifactGc` do: this package composes the run-store, journal, and engine\n * migrations, so the schema is its own.\n *\n * @category constructors\n * @since 0.1.0\n */\nexport const make = (): Effect.Effect<Service, never, SqlClient.SqlClient | Journal.Journal> =>\n Effect.gen(function*() {\n const sql = yield* Effect.service(SqlClient.SqlClient)\n const journal = yield* Journal.Journal\n\n /**\n * Aged terminal runs held by live lineage or an excluded continuation\n * descendant. An upward exclusion takes precedence over a live ancestor.\n *\n * These runs are never candidates \u2014 `candidatesOf` excludes them \u2014 so this\n * read exists to say WHY a workspace with aged runs collected fewer than\n * it holds. It carries the pass's own bound so the report costs no more\n * than the pass does.\n */\n const retainedByLineage = (cutoffMs: number, limit: number) =>\n sql<{ readonly run_id: string; readonly live_descendant: number }>`\n ${scanPrelude(sql, { cutoffMs, inclusive: true, parentEdges: true })}\n SELECT\n run_id,\n (run_id IN (SELECT run_id FROM over_live) OR EXISTS (\n SELECT 1 FROM flows_runs AS child\n WHERE child.parent_run_id = flows_runs.run_id\n AND child.run_id IN (SELECT run_id FROM blocked)\n )) AS live_descendant\n FROM flows_runs\n WHERE ${sql.in(\"status\", terminalStatuses)}\n AND COALESCE(finished_at_ms, created_at_ms) <= ${cutoffMs}\n AND run_id IN (SELECT run_id FROM blocked)\n ORDER BY COALESCE(finished_at_ms, created_at_ms), run_id\n LIMIT ${limit}\n `.pipe(Effect.mapError(scanning(\"the run lineage\")))\n\n const pass = (options: RetainOptions, cutoffMs: number) =>\n Effect.gen(function*() {\n const limit = normalizeLimit(options.limit)\n const candidates = yield* candidatesOf(sql, { cutoffMs, inclusive: true, parentEdges: true, limit }).pipe(\n Effect.mapError(scanning(\"the run table\"))\n )\n const retained = new Set<string>()\n const retainedAbove = new Set<string>()\n // Why the workspace kept aged runs this pass. These never entered the\n // candidate set, so this only classifies them. It runs before the\n // deletes so the report describes the file the pass was handed.\n for (const held of yield* retainedByLineage(cutoffMs, limit)) {\n if (Number(held.live_descendant) > 0) retained.add(held.run_id)\n else retainedAbove.add(held.run_id)\n }\n // The whole deletion, shared with `Retention.collect` so the two\n // passes cannot hold different table lists or different orders. This\n // database is the engine's own ladder, so a table of it that the\n // catalog does not list is a broken schema and the delete says so.\n const removed = yield* deleteRuns(sql, candidates, {\n dryRun: options.dryRun === true,\n assumeLadder: true\n })\n\n return {\n cutoffMs,\n runIds: removed.runIds,\n retainedForLiveDescendants: Array.from(retained).sort(),\n retainedForLiveAncestors: Array.from(retainedAbove).sort(),\n runs: removed.runIds.length,\n attempts: removed.deleted[\"flows_attempts\"] ?? 0,\n clockDeadlines: removed.deleted[\"flows_clock_deadlines\"] ?? 0,\n deferredCompletions: removed.deleted[\"flows_deferred_completions\"] ?? 0,\n journalEntries: removed.deleted[\"flows_journal_events\"] ?? 0,\n journalCheckpoints: removed.deleted[\"flows_journal_checkpoints\"] ?? 0,\n journalIdentities: removed.deleted[\"flows_journal_dedup\"] ?? 0,\n archiveEntries: removed.deleted[\"flows_time_travel_archive\"] ?? 0,\n timeTravelReceipts: removed.deleted[\"flows_time_travel_receipts\"] ?? 0,\n dryRun: options.dryRun === true\n } satisfies RetainReport\n })\n\n const retain: Service[\"retain\"] = Effect.fn(\"Retention.retain\")((options: RetainOptions) =>\n Effect.gen(function*() {\n const nowMs = yield* Clock.currentTimeMillis.pipe(Effect.map(Math.floor))\n const cutoffMs = nowMs - Math.max(0, options.olderThanMs)\n // One commit: `transact` joins the run-row deletes, the dependent\n // deletes, and the journal truncation into a single write transaction,\n // so a crash can never leave a run without its history or history\n // without its run. A transaction that cannot commit fails as the\n // `JournalError` it is rather than being laundered into a retention\n // code that would claim to know why.\n return yield* journal.transact(pass(options, cutoffMs))\n })\n )\n\n return { retain }\n })\n\n/**\n * Provides run retention. Nothing schedules `retain()`; invoking it stays an\n * explicit caller decision.\n *\n * @category layers\n * @since 0.1.0\n */\nexport const layer: Layer.Layer<Retention, never, SqlClient.SqlClient | Journal.Journal> = Layer.effect(Retention)(\n make()\n)\n"],
|
|
4
|
+
"sourcesContent": ["/**\n * Retention: the one operation that deletes finished run state.\n *\n * Nothing in the composition removed a `flows_runs` row or anything that hangs\n * off one. Every run a workspace ever finished stayed in `.flows/engine.db`\n * with its attempts, its persisted deferred completions and clock deadlines,\n * its journal entries, and its checkpoints, and journal compaction is off by\n * default (`SqlJournal` `Options.compaction`), so the file only grew. This\n * module is the deletion side, and the whole of it: one operation, run\n * explicitly.\n *\n * Three properties make it safe to run against a live workspace.\n *\n * - **One transaction.** Every delete runs inside a single `journal.transact`,\n * so a crash leaves either a run with all of its rows or none of them \u2014\n * never a run row whose history is gone, or history whose run row is gone.\n * - **Terminal and aged only.** A run is a candidate only when its status is\n * `completed`, `failed`, or `cancelled` AND it finished before the cutoff.\n * A running, pending, or suspended run is never a candidate at any age.\n * - **Nothing a live run still needs.** A candidate is retained when a live\n * run stands on either side of it in the lineage, over BOTH relations that\n * make one run the parent of another: the `flows_run_parents` edge a spawned\n * child is recorded under, and the reserved `parent_run_id` column the\n * rounds of one trampoline lineage are chained through. Upward: an aged\n * terminal run with a live ancestor stays, because a parent reads a settled\n * child's result out of its run row (`agent/await`) and a parent parked on\n * an approval, a deferred, or a timer can be parked for longer than the\n * threshold before it ever asks. Downward: an aged terminal run whose\n * descendant is still live stays, because deleting it would leave that\n * descendant's `parent_run_id` pointing at nothing and would drop the live\n * descendant's lineage edges with the `flows_run_parents_gc` trigger.\n * - **Explicit.** Nothing schedules this. Automatic retention stays opt-in,\n * for the reason `ArtifactGc` states: deletion is the irreversible\n * direction, and a human approving a plan must be approving the deletions.\n *\n * Journal history is deleted outright rather than compacted to a checkpoint.\n * `Journal.checkpoint` and `Journal.compact` are owner-fenced \u2014 `SqlJournal`'s\n * `fenceGuard` requires a consensus lease held by the exact owner passed in \u2014\n * so neither can be called for a finished, ownerless run at all. Deleting the run's entries and checkpoints is strictly stronger than\n * compacting them to a checkpoint, and it happens in the same transaction as\n * the run row, which is the property a compaction could not have given.\n *\n * The operation is idempotent. With a positive integer limit and a fixed,\n * finite, acyclic run lineage, each successful non-dry pass deletes at least\n * one collectable run until none remain. Continuation children are selected\n * before parents; parents pinned by excluded children do not consume the\n * bound. Runs protected by age or live lineage remain until eligible.\n *\n * @since 0.1.0\n */\n\nimport * as Dialect from \"@smthrs/database/Dialect\"\nimport * as Journal from \"@smthrs/journal/Journal\"\nimport * as JournalEvent from \"@smthrs/journal/JournalEvent\"\nimport * as RunStore from \"@smthrs/run-store/RunStore\"\nimport * as Clock from \"effect/Clock\"\nimport * as Context from \"effect/Context\"\nimport * as Effect from \"effect/Effect\"\nimport * as Layer from \"effect/Layer\"\nimport * as Schema from \"effect/Schema\"\nimport * as SqlClient from \"effect/sql/SqlClient\"\nimport type { SqlError } from \"effect/sql/SqlError\"\n\n/**\n * Stable error codes returned by retention.\n *\n * @category models\n * @since 0.1.0\n */\nexport const RetentionErrorCode = Schema.Literals([\"scan_failed\", \"delete_failed\"])\n\n/**\n * Stable error codes returned by retention.\n *\n * @category models\n * @since 0.1.0\n */\nexport type RetentionErrorCode = typeof RetentionErrorCode.Type\n\n/**\n * A retention pass that could not complete.\n *\n * `scan_failed` means the candidate set could not be computed and nothing was\n * deleted. `delete_failed` means the candidate set held but a delete refused;\n * the transaction rolled back, so nothing was deleted then either, and\n * re-running converges once the cause is fixed.\n *\n * @category errors\n * @since 0.1.0\n */\nexport class RetentionError extends Schema.TaggedError<RetentionError>()(\n \"@smthrs/engine-store/RetentionError\",\n {\n code: RetentionErrorCode,\n message: Schema.String,\n cause: Schema.optional(Schema.Unknown)\n }\n) {}\n\n/**\n * Options for one retention pass.\n *\n * @category models\n * @since 0.1.0\n */\nexport interface RetainOptions {\n /**\n * How long a run must have been finished to be collected, in milliseconds.\n * A run's age is measured from `finished_at_ms`, or from `created_at_ms`\n * for a terminal row that never recorded one. Zero collects every terminal\n * run; a negative value is read as zero.\n */\n readonly olderThanMs: number\n /**\n * Largest number of runs one pass deletes. Defaults to\n * {@link defaultLimit}. Retention is idempotent, so a workspace with more\n * aged runs than the bound converges over repeated passes instead of\n * holding one long write transaction open. A negative value is read as\n * zero, so a pass under a mistyped bound deletes nothing.\n */\n readonly limit?: number | undefined\n /** Compute and report the pass without deleting anything. */\n readonly dryRun?: boolean | undefined\n}\n\n/**\n * What one retention pass did, or \u2014 under `dryRun` \u2014 would do.\n *\n * @category models\n * @since 0.1.0\n */\nexport interface RetainReport {\n /** Runs finished at or before this instant were candidates. */\n readonly cutoffMs: number\n /** The runs deleted, oldest first. */\n readonly runIds: ReadonlyArray<string>\n /**\n * Aged terminal runs left in place because a descendant of theirs is not\n * terminal, or is terminal and is not being deleted by this pass. They\n * become collectable once that descendant is. At most `limit` of them are\n * reported, the oldest first, so the report costs no more than the pass.\n */\n readonly retainedForLiveDescendants: ReadonlyArray<string>\n /**\n * Aged terminal runs left in place because an ancestor of theirs is not\n * terminal. That ancestor can still read the run's settled result through\n * `agent/await`, which answers out of the run row. They become collectable\n * once every ancestor is terminal \u2014 a terminal ancestor holds nothing back,\n * whatever its own age. A run retained for both reasons is reported under\n * {@link retainedForLiveDescendants}, and at most `limit` runs are reported.\n */\n readonly retainedForLiveAncestors: ReadonlyArray<string>\n readonly runs: number\n readonly attempts: number\n readonly clockDeadlines: number\n readonly deferredCompletions: number\n readonly journalEntries: number\n readonly journalCheckpoints: number\n /** Producer identities journal compaction kept after deleting event payloads. */\n readonly journalIdentities: number\n /** Time-travel archive rows; always zero where that table is not installed. */\n readonly archiveEntries: number\n /** Time-travel receipt rows reached through their audits. */\n readonly timeTravelReceipts: number\n readonly dryRun: boolean\n}\n\n/**\n * Explicit run retention.\n *\n * @category models\n * @since 0.1.0\n */\nexport interface Service {\n /**\n * Deletes up to `limit` aged terminal runs and their dependents atomically.\n * With a positive integer limit and a fixed, finite, acyclic lineage, each\n * successful non-dry pass makes progress until all collectable runs are gone,\n * including continuation chains longer than the bound. Age and live-lineage\n * protection still apply. Explicit only; nothing schedules this operation.\n */\n readonly retain: (\n options: RetainOptions\n ) => Effect.Effect<RetainReport, RetentionError | Journal.JournalError>\n}\n\n/**\n * Service tag for run retention.\n *\n * @category services\n * @since 0.1.0\n */\nexport class Retention extends Context.Service<Retention, Service>()(\"@smthrs/engine-store/Retention\") {}\n\n/**\n * Runs deleted by one pass when the caller names no bound. Large enough that\n * an ordinary workspace finishes in one pass, small enough that the write\n * transaction stays short on a workspace that has never been collected.\n *\n * @category constants\n * @since 0.1.0\n */\nexport const defaultLimit = 1000\n\n/**\n * Run ids per `IN (...)` list. SQLite's default host-parameter ceiling is 999.\n *\n * @category constants\n * @since 0.1.0\n */\nconst chunkSize = 500\n\n/**\n * One table a deleted run leaves rows in.\n *\n * @category models\n * @since 1.0.0\n */\nexport interface RunScopedTable {\n readonly table: string\n /** The column naming the run. */\n readonly column: string\n /**\n * Whether the migration ladder this package composes installs the table.\n *\n * A ladder table missing from an engine database is a broken schema, so\n * {@link installedTables} can be asked to keep it and let the delete refuse.\n * A table beyond the ladder belongs to a store the host may simply not have\n * composed, and is skipped when the catalog does not list it.\n */\n readonly ladder: boolean\n /**\n * Whether the table also holds the runs' journal companion streams\n * (`JournalEvent.companionRunId`), which name a run through a derived id\n * rather than the run id itself. {@link companionStreams} lists them.\n */\n readonly companions?: boolean | undefined\n}\n\n/**\n * The journal companion streams a run owns beside its own stream. A deleted\n * run's companion streams go with it.\n *\n * @category constants\n * @since 1.0.0\n */\nexport const companionStreams: ReadonlyArray<string> = [RunStore.companionStream]\n\n/**\n * Every table a deleted run leaves rows in, and the column naming the run.\n *\n * ONE inventory, because two passes read it: {@link Service.retain} over the\n * engine ladder, and `Retention.collect` over whatever a host's file holds. A\n * table listed by only one of them is a table the other leaks forever. Tables\n * are listed rather than discovered from foreign keys because the set spans\n * six packages that migrate into one database, and a silently missing entry is\n * exactly that leak.\n *\n * The run rows themselves are not here: they go last and in generation order,\n * which {@link deleteRuns} does after this list.\n * `flows_time_travel_receipts` is reached through its audit rather than listed\n * here because the upstream table has no run column: its `audit_id` points to\n * `flows_time_travel_audits.id`, whose row carries `run_id`.\n *\n * @category constants\n * @since 0.1.0\n */\nexport const runScopedTables: ReadonlyArray<RunScopedTable> = [\n { table: \"flows_deferred_completions\", column: \"execution_id\", ladder: true },\n { table: \"flows_clock_deadlines\", column: \"execution_id\", ladder: true },\n { table: \"flows_attempts\", column: \"run_id\", ladder: true },\n { table: \"flows_journal_events\", column: \"run_id\", ladder: true, companions: true },\n { table: \"flows_journal_checkpoints\", column: \"run_id\", ladder: true, companions: true },\n { table: \"flows_journal_dedup\", column: \"run_id\", ladder: true, companions: true },\n { table: \"flows_consensus_leases\", column: \"run_id\", ladder: true },\n { table: \"flows_step_cache_recorded\", column: \"recorded_run_id\", ladder: true },\n { table: \"flows_time_travel_archive\", column: \"run_id\", ladder: false },\n { table: \"flows_time_travel_snapshots\", column: \"run_id\", ladder: false },\n { table: \"flows_time_travel_audits\", column: \"run_id\", ladder: false },\n { table: \"flows_time_travel_edges\", column: \"child_run_id\", ladder: false },\n { table: \"control_run_messages\", column: \"run_id\", ladder: false },\n { table: \"control_runs\", column: \"run_id\", ladder: false }\n]\n\n/**\n * The statuses a run can never leave.\n *\n * @category constants\n * @since 1.0.0\n */\nexport const terminalStatuses: ReadonlyArray<string> = [\"completed\", \"failed\", \"cancelled\"]\n\n/** `terminalStatuses` as a SQL list, for the lineage prelude below. */\nconst terminalList = terminalStatuses.map((status) => `'${status}'`).join(\", \")\n\n/**\n * The prelude every scan opens with: which runs a live run stands above, and\n * which it stands below.\n *\n * Two relations make one run the parent of another, and a walk over either one\n * alone misses half the lineage. A SPAWNED child records its parent as a\n * `flows_run_parents` edge, one row per parent so a diamond records both, and\n * leaves `parent_run_id` NULL: the engine reserves that column for the rounds\n * of one trampoline lineage (`RunDriver.continueLineage`). The edge is the\n * relation every `agent/await` depends on. `link` is both relations as one\n * edge list.\n *\n * `under_live` is every run reachable downward from a run that is not\n * terminal. Those runs have a live ancestor, which can still read a settled\n * result out of their run row. `over_live` is every run reachable upward from\n * one. Those runs have a live descendant, whose own lineage the\n * `flows_run_parents_gc` trigger would drop with them. An edge whose parent\n * row is gone is not reachable from `live` at all, so it holds nothing back,\n * and since only terminal runs are ever deleted it never can.\n *\n * The walks carry run ids and nothing else, so `UNION` makes them terminate on\n * their own: a corrupt edge that closed a cycle adds a row the set already\n * holds, and the walk stops. That matters because these reads run inside the\n * pass's write transaction. They are SQLite-specific, like the `sqlite_master`\n * probe below, and rc.0 is SQLite-only.\n *\n * `parentEdges: false` drops the `flows_run_parents` half of `link`. A\n * database that carries the run rows but not the engine's edge table \u2014 the\n * control plane's, which migrates the run store and the journal and nothing\n * else \u2014 has no edges to walk, and reading the missing table would fail the\n * whole scan. The `parent_run_id` half still runs, so the continuation\n * lineage is guarded there too. The retention pass itself always walks both.\n *\n * @category constructors\n * @since 0.1.0\n */\nexport const lineagePrelude = (\n options: { readonly parentEdges: boolean }\n): string => `\n WITH RECURSIVE\n link(child_id, parent_id) AS (\n ${\n options.parentEdges\n ? `SELECT child_id, parent_id FROM flows_run_parents\n UNION\n `\n : ``\n}SELECT run_id, parent_run_id FROM flows_runs WHERE parent_run_id IS NOT NULL\n ),\n live(run_id) AS (\n SELECT run_id FROM flows_runs WHERE status NOT IN (${terminalList})\n ),\n under_live(run_id) AS (\n SELECT link.child_id FROM link JOIN live ON live.run_id = link.parent_id\n UNION\n SELECT link.child_id FROM link JOIN under_live ON under_live.run_id = link.parent_id\n ),\n over_live(run_id) AS (\n SELECT link.parent_id FROM link JOIN live ON live.run_id = link.child_id\n UNION\n SELECT link.parent_id FROM link JOIN over_live ON over_live.run_id = link.child_id\n )\n `\n\n/**\n * The bound one pass may delete, as both passes read it.\n *\n * Clamped, not interpolated: SQLite reads a negative `LIMIT` as no limit at\n * all, which would make a mistyped bound a full sweep, and rejects `NaN` or a\n * fraction as a datatype mismatch. Anything that is not a non-negative safe\n * integer is read as zero, so the pass deletes nothing.\n *\n * @category constructors\n * @since 1.0.0\n */\nexport const normalizeLimit = (limit: number | undefined): number => {\n const bound = limit ?? defaultLimit\n return Number.isSafeInteger(bound) && bound >= 0 ? bound : 0\n}\n\n/**\n * Which runs a pass may delete, and which it must keep, as CTEs.\n *\n * `eligible` is every terminal run older than the cutoff that no live run\n * stands above or below. `blocked` is every other run plus every ancestor of\n * one over `parent_run_id`: a continuation parent must outlive every child\n * excluded by age or live lineage, and this propagates that exclusion before\n * any bound is applied. `inclusive` says whether a run finished exactly at\n * the cutoff is old enough.\n */\nconst scanPrelude = (\n sql: SqlClient.SqlClient,\n options: {\n readonly cutoffMs: number\n readonly inclusive: boolean\n readonly parentEdges: boolean\n readonly pins?: ReadonlyArray<string> | undefined\n }\n) =>\n sql`\n ${sql.unsafe(lineagePrelude({ parentEdges: options.parentEdges }))},\n eligible(run_id) AS (\n SELECT run_id FROM flows_runs\n WHERE ${sql.in(\"status\", terminalStatuses)}\n AND ${\n options.inclusive\n ? sql`COALESCE(finished_at_ms, created_at_ms) <= ${options.cutoffMs}`\n : sql`COALESCE(finished_at_ms, created_at_ms) < ${options.cutoffMs}`\n }\n AND run_id NOT IN (SELECT run_id FROM under_live)\n AND run_id NOT IN (SELECT run_id FROM over_live)\n AND ${\n options.pins === undefined || options.pins.length === 0 ?\n sql.literal(\"1 = 1\")\n : sql`run_id NOT IN ${sql.in(options.pins)}`\n }\n ),\n blocked(run_id) AS (\n SELECT run_id FROM flows_runs WHERE run_id NOT IN (SELECT run_id FROM eligible)\n UNION\n SELECT runs.parent_run_id FROM flows_runs AS runs\n JOIN blocked ON blocked.run_id = runs.run_id\n WHERE runs.parent_run_id IS NOT NULL\n )\n `\n\n/**\n * The runs one pass deletes: at most `limit` of them, children before parents.\n *\n * The one candidate query, shared by {@link Service.retain} and\n * `Retention.collect`. A trampoline parent always finishes before its\n * successor, so an oldest-first window over a lineage longer than the bound\n * held only ancestors, each pinned by the round just outside the window, and\n * every later pass selected the same window and deleted nothing. Depth walks\n * from roots, visiting each continuation row once; corrupt cycles have no\n * root and cannot enter the deletion set. Excluded runs and their ancestors\n * are dropped before the bound, so pinned parents cannot consume it. The\n * result is presented oldest first.\n *\n * @category getters\n * @since 1.0.0\n */\nexport const candidatesOf = (\n sql: SqlClient.SqlClient,\n options: {\n readonly cutoffMs: number\n readonly inclusive: boolean\n readonly parentEdges: boolean\n readonly pins?: ReadonlyArray<string> | undefined\n readonly limit: number\n }\n): Effect.Effect<ReadonlyArray<Candidate>, SqlError> =>\n sql<{ readonly run_id: string; readonly parent_run_id: string | null }>`\n ${scanPrelude(sql, options)},\n depths(run_id, depth) AS (\n SELECT run_id, 0 FROM flows_runs WHERE parent_run_id IS NULL\n UNION ALL\n SELECT runs.run_id, depths.depth + 1 FROM flows_runs AS runs\n JOIN depths ON runs.parent_run_id = depths.run_id\n ),\n selected AS (\n SELECT runs.run_id, runs.parent_run_id,\n COALESCE(runs.finished_at_ms, runs.created_at_ms) AS finished_at_ms\n FROM flows_runs AS runs JOIN depths ON depths.run_id = runs.run_id\n WHERE runs.run_id NOT IN (SELECT run_id FROM blocked)\n ORDER BY depths.depth DESC, COALESCE(runs.finished_at_ms, runs.created_at_ms), runs.run_id\n LIMIT ${normalizeLimit(options.limit)}\n )\n SELECT run_id, parent_run_id FROM selected ORDER BY finished_at_ms, run_id\n `.pipe(\n Effect.map((rows): ReadonlyArray<Candidate> =>\n rows.map((row) => ({ runId: row.run_id, parentRunId: row.parent_run_id }))\n )\n )\n\n/** Classifies a read of the scan phase, where nothing has been deleted yet. */\nconst scanning = (what: string) => (cause: unknown): RetentionError =>\n new RetentionError({ code: \"scan_failed\", message: `${what} could not be read`, cause })\n\n/** Classifies a statement of the delete phase, which rolls the pass back. */\nconst deleting = (what: string) => (cause: unknown): RetentionError =>\n new RetentionError({ code: \"delete_failed\", message: `${what} could not be collected`, cause })\n\nconst chunksOf = (ids: ReadonlyArray<string>): ReadonlyArray<ReadonlyArray<string>> => {\n const chunks: Array<ReadonlyArray<string>> = []\n for (let offset = 0; offset < ids.length; offset += chunkSize) {\n chunks.push(ids.slice(offset, offset + chunkSize))\n }\n return chunks\n}\n\n/**\n * A run a pass proposes to delete, and the run it names as its parent.\n *\n * @category models\n * @since 0.1.0\n */\nexport interface Candidate {\n readonly runId: string\n readonly parentRunId: string | null\n}\n\n/** A run and the candidate it names as its parent. */\ninterface ChildEdge {\n readonly runId: string\n readonly parentRunId: string\n}\n\n/**\n * What one deletion pass removed, or would remove under `dryRun`.\n *\n * @category models\n * @since 0.1.0\n */\nexport interface DeletionReport {\n /** The candidates deleted, in the order they were given. */\n readonly runIds: ReadonlyArray<string>\n /** Candidates kept because a run outside the pass still names them. */\n readonly pinned: ReadonlyArray<string>\n /**\n * Rows counted per table, `flows_runs` included, zeroes omitted. Counted\n * whether or not the pass deletes, so a dry run reports the same numbers.\n */\n readonly deleted: Readonly<Record<string, number>>\n}\n\n/** Reads the installed SQLite table names once for inventory decisions. */\nconst installedTableNames = (sql: SqlClient.SqlClient): Effect.Effect<ReadonlySet<string>, RetentionError> =>\n Dialect.tables(sql).pipe(\n Effect.map((rows) => new Set(rows.map((row) => row.name))),\n Effect.mapError(scanning(\"the schema catalog\"))\n )\n\n/**\n * The {@link runScopedTables} entries this database actually has.\n *\n * `assumeLadder` keeps every entry the ladder installs whether the catalog\n * lists it or not. {@link Service.retain} passes `true`: this package composes\n * the run-store, journal, step-cache and engine migrations, so a ladder table\n * missing from the file it is pointed at is a broken schema, and a delete that\n * refuses says so. `Retention.collect` passes `false`, because it also runs\n * against the control plane's database, which migrates the run store and the\n * journal and nothing else; there a missing ladder table is a host that\n * composed fewer stores, and the pass still owes it a complete sweep of what\n * it has.\n *\n * The catalog read is SQLite-specific, like the lineage prelude above and the\n * `flows_run_parents_gc` trigger this package ships. rc.0 is SQLite-only.\n *\n * @category getters\n * @since 0.1.0\n */\nexport const installedTables = (\n sql: SqlClient.SqlClient,\n options: { readonly assumeLadder: boolean }\n): Effect.Effect<ReadonlyArray<RunScopedTable>, RetentionError> =>\n installedTableNames(sql).pipe(\n Effect.map((present) =>\n runScopedTables.filter((entry) => (options.assumeLadder && entry.ladder) || present.has(entry.table))\n )\n )\n\nconst countIn = (sql: SqlClient.SqlClient, table: string, column: string, ids: ReadonlyArray<string>) =>\n Effect.reduce(chunksOf(ids), () => 0, (total, chunk) =>\n sql<{ readonly total: number }>`\n SELECT COUNT(*) AS total FROM ${sql.unsafe(table)} WHERE ${sql.in(column, chunk)}\n `.pipe(\n Effect.map((rows) => rows.reduce((sum, row) => sum + Number(row.total), total)),\n Effect.mapError(deleting(table))\n ))\n\nconst deleteIn = (sql: SqlClient.SqlClient, table: string, column: string, ids: ReadonlyArray<string>) =>\n Effect.forEach(\n chunksOf(ids),\n (chunk) =>\n sql`DELETE FROM ${sql.unsafe(table)} WHERE ${sql.in(column, chunk)}`.pipe(\n Effect.mapError(deleting(table))\n ),\n { discard: true }\n )\n\nconst countTimeTravelReceipts = (sql: SqlClient.SqlClient, ids: ReadonlyArray<string>) =>\n Effect.reduce(chunksOf(ids), () => 0, (total, chunk) =>\n sql<{ readonly total: number }>`\n SELECT COUNT(*) AS total\n FROM flows_time_travel_receipts\n WHERE audit_id IN (\n SELECT id FROM flows_time_travel_audits WHERE ${sql.in(\"run_id\", chunk)}\n )\n `.pipe(\n Effect.map((rows) => rows.reduce((sum, row) => sum + Number(row.total), total)),\n Effect.mapError(deleting(\"flows_time_travel_receipts\"))\n ))\n\nconst deleteTimeTravelReceipts = (sql: SqlClient.SqlClient, ids: ReadonlyArray<string>) =>\n Effect.forEach(\n chunksOf(ids),\n (chunk) =>\n sql`\n DELETE FROM flows_time_travel_receipts\n WHERE audit_id IN (\n SELECT id FROM flows_time_travel_audits WHERE ${sql.in(\"run_id\", chunk)}\n )\n `.pipe(Effect.mapError(deleting(\"flows_time_travel_receipts\"))),\n { discard: true }\n )\n\n/** Every run whose parent is a candidate, candidates included. */\nconst childrenOf = (sql: SqlClient.SqlClient, candidateIds: ReadonlyArray<string>) =>\n Effect.reduce(\n chunksOf(candidateIds),\n (): Array<ChildEdge> => [],\n (accumulated, chunk) =>\n // `parent_run_id` is the match predicate, so every row this returns\n // has one.\n sql<{ readonly run_id: string; readonly parent_run_id: string }>`\n SELECT run_id, parent_run_id FROM flows_runs WHERE ${sql.in(\"parent_run_id\", chunk)}\n `.pipe(\n Effect.map((rows) => {\n for (const row of rows) {\n accumulated.push({ runId: row.run_id, parentRunId: row.parent_run_id })\n }\n return accumulated\n }),\n Effect.mapError(scanning(\"the run lineage\"))\n )\n )\n\n/**\n * Drops from `doomed` every candidate that must outlive this pass, and\n * every ancestor of one.\n *\n * A candidate whose child is not being deleted has to stay: `flows_runs`\n * carries a self-referential foreign key on `parent_run_id`, so deleting\n * it would orphan a row that still exists. Retention is upward-closed for\n * the same reason \u2014 once a candidate is retained, the candidate it points\n * at is still referenced and is retained too.\n */\nconst pinAncestors = (\n doomed: Set<string>,\n retained: Set<string>,\n parentOf: ReadonlyMap<string, string | null>,\n from: string\n): void => {\n let current: string | undefined = from\n while (current !== undefined && doomed.has(current)) {\n doomed.delete(current)\n retained.add(current)\n current = parentOf.get(current) ?? undefined\n }\n}\n\n/**\n * Groups the doomed runs so that no group holds a run and one of its\n * doomed descendants. Deleting a parent while its child row still exists\n * violates the foreign key, and SQLite evaluates it per row rather than at\n * commit, so the delete runs deepest generation first.\n */\nconst generations = (\n doomed: ReadonlySet<string>,\n parentOf: ReadonlyMap<string, string | null>\n): ReadonlyArray<ReadonlyArray<string>> => {\n const depths = new Map<string, number>()\n const depthOf = (runId: string): number => {\n const known = depths.get(runId)\n if (known !== undefined) return known\n const parent = parentOf.get(runId) ?? undefined\n const depth = parent !== undefined && doomed.has(parent) ? depthOf(parent) + 1 : 0\n depths.set(runId, depth)\n return depth\n }\n const byDepth = new Map<number, Array<string>>()\n for (const runId of doomed) {\n const depth = depthOf(runId)\n const bucket = byDepth.get(depth)\n if (bucket === undefined) byDepth.set(depth, [runId])\n else bucket.push(runId)\n }\n return Array.from(byDepth.keys())\n .sort((left, right) => right - left)\n .map((depth) => byDepth.get(depth)!)\n}\n\n/**\n * Deletes `candidates` and every row the inventory says names them.\n *\n * The one deletion in this package. Both passes call it, so neither can be\n * holding a shorter table list than the other, and neither can be deleting\n * run rows in an order the self-referential foreign key refuses: a handoff\n * parent always sorts before its successor, so a pass that deleted in age\n * order broke as soon as one lineage straddled a chunk boundary.\n *\n * The caller owns the transaction. Nothing here is atomic on its own, and a\n * pass that removed the dependents of every candidate and then refused on the\n * run rows would have destroyed history it could not put back.\n *\n * @category constructors\n * @since 0.1.0\n */\nexport const deleteRuns = (\n sql: SqlClient.SqlClient,\n candidates: ReadonlyArray<Candidate>,\n options: { readonly dryRun: boolean; readonly assumeLadder: boolean }\n): Effect.Effect<DeletionReport, RetentionError> =>\n Effect.gen(function*() {\n const installed = yield* installedTableNames(sql)\n const tables = runScopedTables.filter((entry) =>\n (options.assumeLadder && entry.ladder) || installed.has(entry.table)\n )\n const hasTimeTravelReceipts = installed.has(\"flows_time_travel_receipts\") &&\n installed.has(\"flows_time_travel_audits\")\n const candidateIds = candidates.map((candidate) => candidate.runId)\n const parentOf = new Map(candidates.map((candidate) => [candidate.runId, candidate.parentRunId]))\n const doomed = new Set(candidateIds)\n const pinned = new Set<string>()\n const candidateSet = new Set(candidateIds)\n // The foreign key, which no lineage filter covers: a child row that is\n // terminal and simply outside this pass \u2014 younger than the cutoff, or past\n // the bound \u2014 still points at its parent.\n for (const child of yield* childrenOf(sql, candidateIds)) {\n if (candidateSet.has(child.runId)) continue\n pinAncestors(doomed, pinned, parentOf, child.parentRunId)\n }\n // `candidates` arrives oldest first, so this reads in the order the pass\n // collected.\n const runIds = candidateIds.filter((runId) => doomed.has(runId))\n\n const companionIds = runIds.flatMap((runId) =>\n companionStreams.map((stream) => JournalEvent.companionRunId(stream, runId))\n )\n const idsFor = (entry: RunScopedTable): ReadonlyArray<string> =>\n entry.companions === true ? [...runIds, ...companionIds] : runIds\n\n const deleted: Record<string, number> = {}\n if (hasTimeTravelReceipts) {\n const total = yield* countTimeTravelReceipts(sql, runIds)\n if (total > 0) deleted[\"flows_time_travel_receipts\"] = total\n }\n for (const entry of tables) {\n const total = yield* countIn(sql, entry.table, entry.column, idsFor(entry))\n if (total > 0) deleted[entry.table] = total\n }\n if (runIds.length > 0) deleted[\"flows_runs\"] = runIds.length\n\n if (!options.dryRun) {\n // Receipts have no run column, so remove them through their audits\n // before the ordinary inventory reaches and deletes those audit rows.\n if (hasTimeTravelReceipts) yield* deleteTimeTravelReceipts(sql, runIds)\n for (const entry of tables) yield* deleteIn(sql, entry.table, entry.column, idsFor(entry))\n // Last, and deepest generation first. The `flows_run_parents_gc`\n // trigger drops each run's DAG edges as its row goes.\n for (const generation of generations(doomed, parentOf)) {\n yield* deleteIn(sql, \"flows_runs\", \"run_id\", generation)\n }\n }\n\n return { runIds, pinned: Array.from(pinned), deleted }\n })\n\n/**\n * Builds the retention operation over the composition's own durable tables.\n *\n * The scan reads `flows_runs` directly, the way `DurableEngineState` and\n * `ArtifactGc` do: this package composes the run-store, journal, and engine\n * migrations, so the schema is its own.\n *\n * @category constructors\n * @since 0.1.0\n */\nexport const make = (): Effect.Effect<Service, never, SqlClient.SqlClient | Journal.Journal> =>\n Effect.gen(function*() {\n const sql = yield* Effect.service(SqlClient.SqlClient)\n const journal = yield* Journal.Journal\n\n /**\n * Aged terminal runs held by live lineage or an excluded continuation\n * descendant. An upward exclusion takes precedence over a live ancestor.\n *\n * These runs are never candidates \u2014 `candidatesOf` excludes them \u2014 so this\n * read exists to say WHY a workspace with aged runs collected fewer than\n * it holds. It carries the pass's own bound so the report costs no more\n * than the pass does.\n */\n const retainedByLineage = (cutoffMs: number, limit: number) =>\n sql<{ readonly run_id: string; readonly live_descendant: number }>`\n ${scanPrelude(sql, { cutoffMs, inclusive: true, parentEdges: true })}\n SELECT\n run_id,\n (run_id IN (SELECT run_id FROM over_live) OR EXISTS (\n SELECT 1 FROM flows_runs AS child\n WHERE child.parent_run_id = flows_runs.run_id\n AND child.run_id IN (SELECT run_id FROM blocked)\n )) AS live_descendant\n FROM flows_runs\n WHERE ${sql.in(\"status\", terminalStatuses)}\n AND COALESCE(finished_at_ms, created_at_ms) <= ${cutoffMs}\n AND run_id IN (SELECT run_id FROM blocked)\n ORDER BY COALESCE(finished_at_ms, created_at_ms), run_id\n LIMIT ${limit}\n `.pipe(Effect.mapError(scanning(\"the run lineage\")))\n\n const pass = (options: RetainOptions, cutoffMs: number) =>\n Effect.gen(function*() {\n const limit = normalizeLimit(options.limit)\n const candidates = yield* candidatesOf(sql, { cutoffMs, inclusive: true, parentEdges: true, limit }).pipe(\n Effect.mapError(scanning(\"the run table\"))\n )\n const retained = new Set<string>()\n const retainedAbove = new Set<string>()\n // Why the workspace kept aged runs this pass. These never entered the\n // candidate set, so this only classifies them. It runs before the\n // deletes so the report describes the file the pass was handed.\n for (const held of yield* retainedByLineage(cutoffMs, limit)) {\n if (Number(held.live_descendant) > 0) retained.add(held.run_id)\n else retainedAbove.add(held.run_id)\n }\n // The whole deletion, shared with `Retention.collect` so the two\n // passes cannot hold different table lists or different orders. This\n // database is the engine's own ladder, so a table of it that the\n // catalog does not list is a broken schema and the delete says so.\n const removed = yield* deleteRuns(sql, candidates, {\n dryRun: options.dryRun === true,\n assumeLadder: true\n })\n\n return {\n cutoffMs,\n runIds: removed.runIds,\n retainedForLiveDescendants: Array.from(retained).sort(),\n retainedForLiveAncestors: Array.from(retainedAbove).sort(),\n runs: removed.runIds.length,\n attempts: removed.deleted[\"flows_attempts\"] ?? 0,\n clockDeadlines: removed.deleted[\"flows_clock_deadlines\"] ?? 0,\n deferredCompletions: removed.deleted[\"flows_deferred_completions\"] ?? 0,\n journalEntries: removed.deleted[\"flows_journal_events\"] ?? 0,\n journalCheckpoints: removed.deleted[\"flows_journal_checkpoints\"] ?? 0,\n journalIdentities: removed.deleted[\"flows_journal_dedup\"] ?? 0,\n archiveEntries: removed.deleted[\"flows_time_travel_archive\"] ?? 0,\n timeTravelReceipts: removed.deleted[\"flows_time_travel_receipts\"] ?? 0,\n dryRun: options.dryRun === true\n } satisfies RetainReport\n })\n\n const retain: Service[\"retain\"] = Effect.fn(\"Retention.retain\")((options: RetainOptions) =>\n Effect.gen(function*() {\n const nowMs = yield* Clock.currentTimeMillis.pipe(Effect.map(Math.floor))\n const cutoffMs = nowMs - Math.max(0, options.olderThanMs)\n // One commit: `transact` joins the run-row deletes, the dependent\n // deletes, and the journal truncation into a single write transaction,\n // so a crash can never leave a run without its history or history\n // without its run. A transaction that cannot commit fails as the\n // `JournalError` it is rather than being laundered into a retention\n // code that would claim to know why.\n return yield* journal.transact(pass(options, cutoffMs))\n })\n )\n\n return { retain }\n })\n\n/**\n * Provides run retention. Nothing schedules `retain()`; invoking it stays an\n * explicit caller decision.\n *\n * @category layers\n * @since 0.1.0\n */\nexport const layer: Layer.Layer<Retention, never, SqlClient.SqlClient | Journal.Journal> = Layer.effect(Retention)(\n make()\n)\n"],
|
|
5
5
|
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
import * as Context from "effect/Context";
|
|
26
26
|
import * as Effect from "effect/Effect";
|
|
27
27
|
import * as Layer from "effect/Layer";
|
|
28
|
-
import * as SqlClient from "effect/
|
|
28
|
+
import * as SqlClient from "effect/sql/SqlClient";
|
|
29
29
|
import * as RunListing from "./RunListing.ts";
|
|
30
30
|
import { RunCatalogError } from "./RunListingError.ts";
|
|
31
31
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RunCatalogOps.d.ts","sourceRoot":"","sources":["../../../src/internal/RunCatalogOps.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,KAAK,OAAO,MAAM,gBAAgB,CAAA;AACzC,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AACvC,OAAO,KAAK,KAAK,MAAM,cAAc,CAAA;AACrC,OAAO,KAAK,SAAS,MAAM
|
|
1
|
+
{"version":3,"file":"RunCatalogOps.d.ts","sourceRoot":"","sources":["../../../src/internal/RunCatalogOps.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,KAAK,OAAO,MAAM,gBAAgB,CAAA;AACzC,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AACvC,OAAO,KAAK,KAAK,MAAM,cAAc,CAAA;AACrC,OAAO,KAAK,SAAS,MAAM,sBAAsB,CAAA;AACjD,OAAO,KAAK,UAAU,MAAM,iBAAiB,CAAA;AAC7C,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AAEtD;;;;;GAKG;AACH,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAA;AAE1D;;;;;;;;;GASG;AACH,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AAEtD;;;;;GAKG;AACH,MAAM,WAAW,WAAW;IAC1B;;;;;OAKG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CACpC;AAED;;;;;GAKG;AACH,MAAM,WAAW,OAAO;IACtB,mEAAmE;IACnE,QAAQ,CAAC,QAAQ,EAAE,UAAU,CAAC,OAAO,CAAC,UAAU,CAAC,CAAA;IACjD;;;;;;OAMG;IACH,QAAQ,CAAC,UAAU,EAAE,CAAC,OAAO,CAAC,EAAE,WAAW,KAAK,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,MAAM,CAAC,EAAE,eAAe,CAAC,CAAA;CACtG;;AAED;;;;;GAKG;AACH,qBAAa,cAAe,SAAQ,mBAEnC;CAAG;AAEJ;;;;;;;GAOG;AACH,eAAO,MAAM,YAAY,QAAS,CAAA;AAElC;;;;;;;;;GASG;AACH,eAAO,MAAM,IAAI,QAAO,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,KAAK,EAAE,SAAS,CAAC,SAAS,CAgDrE,CAAA;AAEJ;;;;;GAKG;AACH,eAAO,MAAM,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,cAAc,EAAE,KAAK,EAAE,SAAS,CAAC,SAAS,CAAwC,CAAA"}
|
|
@@ -39,7 +39,7 @@ module.exports = __toCommonJS(RunCatalogOps_exports);
|
|
|
39
39
|
var Context = __toESM(require("effect/Context"), 1);
|
|
40
40
|
var Effect = __toESM(require("effect/Effect"), 1);
|
|
41
41
|
var Layer = __toESM(require("effect/Layer"), 1);
|
|
42
|
-
var SqlClient = __toESM(require("effect/
|
|
42
|
+
var SqlClient = __toESM(require("effect/sql/SqlClient"), 1);
|
|
43
43
|
var RunListing = __toESM(require("./RunListing.js"), 1);
|
|
44
44
|
var import_RunListingError = require("./RunListingError.js");
|
|
45
45
|
var import_RunListingError2 = require("./RunListingError.js");
|