@smthrs/run-store 0.0.0-stage → 1.0.0-rc.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (157) hide show
  1. package/CHANGELOG.md +112 -0
  2. package/LICENSE +21 -0
  3. package/README.md +195 -2
  4. package/dist/cjs/AttemptStore.d.ts +359 -0
  5. package/dist/cjs/AttemptStore.d.ts.map +1 -0
  6. package/dist/cjs/AttemptStore.js +731 -0
  7. package/dist/cjs/AttemptStore.js.map +7 -0
  8. package/dist/cjs/Heartbeat.d.ts +46 -0
  9. package/dist/cjs/Heartbeat.d.ts.map +1 -0
  10. package/dist/cjs/Heartbeat.js +28 -0
  11. package/dist/cjs/Heartbeat.js.map +7 -0
  12. package/dist/cjs/Migrations.d.ts +42 -0
  13. package/dist/cjs/Migrations.d.ts.map +1 -0
  14. package/dist/cjs/Migrations.js +55 -0
  15. package/dist/cjs/Migrations.js.map +7 -0
  16. package/dist/cjs/Ownership.d.ts +275 -0
  17. package/dist/cjs/Ownership.d.ts.map +1 -0
  18. package/dist/cjs/Ownership.js +129 -0
  19. package/dist/cjs/Ownership.js.map +7 -0
  20. package/dist/cjs/RunStore.d.ts +571 -0
  21. package/dist/cjs/RunStore.d.ts.map +1 -0
  22. package/dist/cjs/RunStore.js +1079 -0
  23. package/dist/cjs/RunStore.js.map +7 -0
  24. package/dist/cjs/RunStoreMetrics.d.ts +151 -0
  25. package/dist/cjs/RunStoreMetrics.d.ts.map +1 -0
  26. package/dist/cjs/RunStoreMetrics.js +91 -0
  27. package/dist/cjs/RunStoreMetrics.js.map +7 -0
  28. package/dist/cjs/index.d.ts +47 -0
  29. package/dist/cjs/index.d.ts.map +1 -0
  30. package/dist/cjs/index.js +43 -0
  31. package/dist/cjs/index.js.map +7 -0
  32. package/dist/cjs/internal/Boundary.d.ts +80 -0
  33. package/dist/cjs/internal/Boundary.d.ts.map +1 -0
  34. package/dist/cjs/internal/Boundary.js +92 -0
  35. package/dist/cjs/internal/Boundary.js.map +7 -0
  36. package/dist/cjs/internal/Companion.d.ts +44 -0
  37. package/dist/cjs/internal/Companion.d.ts.map +1 -0
  38. package/dist/cjs/internal/Companion.js +50 -0
  39. package/dist/cjs/internal/Companion.js.map +7 -0
  40. package/dist/cjs/internal/SpanOutcome.d.ts +37 -0
  41. package/dist/cjs/internal/SpanOutcome.d.ts.map +1 -0
  42. package/dist/cjs/internal/SpanOutcome.js +62 -0
  43. package/dist/cjs/internal/SpanOutcome.js.map +7 -0
  44. package/dist/cjs/migrations/0001_initial.d.ts +15 -0
  45. package/dist/cjs/migrations/0001_initial.d.ts.map +1 -0
  46. package/dist/cjs/migrations/0001_initial.js +110 -0
  47. package/dist/cjs/migrations/0001_initial.js.map +7 -0
  48. package/dist/cjs/migrations/0002_lineage.d.ts +27 -0
  49. package/dist/cjs/migrations/0002_lineage.d.ts.map +1 -0
  50. package/dist/cjs/migrations/0002_lineage.js +43 -0
  51. package/dist/cjs/migrations/0002_lineage.js.map +7 -0
  52. package/dist/cjs/migrations/0003_execution_revisions.d.ts +15 -0
  53. package/dist/cjs/migrations/0003_execution_revisions.d.ts.map +1 -0
  54. package/dist/cjs/migrations/0003_execution_revisions.js +77 -0
  55. package/dist/cjs/migrations/0003_execution_revisions.js.map +7 -0
  56. package/dist/cjs/migrations/0004_waiting_request.d.ts +32 -0
  57. package/dist/cjs/migrations/0004_waiting_request.d.ts.map +1 -0
  58. package/dist/cjs/migrations/0004_waiting_request.js +42 -0
  59. package/dist/cjs/migrations/0004_waiting_request.js.map +7 -0
  60. package/dist/cjs/package.json +1 -0
  61. package/dist/cjs/test/TestRunStore.d.ts +17 -0
  62. package/dist/cjs/test/TestRunStore.d.ts.map +1 -0
  63. package/dist/cjs/test/TestRunStore.js +42 -0
  64. package/dist/cjs/test/TestRunStore.js.map +7 -0
  65. package/dist/esm/AttemptStore.d.ts +359 -0
  66. package/dist/esm/AttemptStore.d.ts.map +1 -0
  67. package/dist/esm/AttemptStore.js +835 -0
  68. package/dist/esm/AttemptStore.js.map +1 -0
  69. package/dist/esm/Heartbeat.d.ts +46 -0
  70. package/dist/esm/Heartbeat.d.ts.map +1 -0
  71. package/dist/esm/Heartbeat.js +46 -0
  72. package/dist/esm/Heartbeat.js.map +1 -0
  73. package/dist/esm/Migrations.d.ts +42 -0
  74. package/dist/esm/Migrations.d.ts.map +1 -0
  75. package/dist/esm/Migrations.js +56 -0
  76. package/dist/esm/Migrations.js.map +1 -0
  77. package/dist/esm/Ownership.d.ts +275 -0
  78. package/dist/esm/Ownership.d.ts.map +1 -0
  79. package/dist/esm/Ownership.js +299 -0
  80. package/dist/esm/Ownership.js.map +1 -0
  81. package/dist/esm/RunStore.d.ts +571 -0
  82. package/dist/esm/RunStore.d.ts.map +1 -0
  83. package/dist/esm/RunStore.js +1287 -0
  84. package/dist/esm/RunStore.js.map +1 -0
  85. package/dist/esm/RunStoreMetrics.d.ts +151 -0
  86. package/dist/esm/RunStoreMetrics.d.ts.map +1 -0
  87. package/dist/esm/RunStoreMetrics.js +120 -0
  88. package/dist/esm/RunStoreMetrics.js.map +1 -0
  89. package/dist/esm/index.d.ts +47 -0
  90. package/dist/esm/index.d.ts.map +1 -0
  91. package/dist/esm/index.js +47 -0
  92. package/dist/esm/index.js.map +1 -0
  93. package/dist/esm/internal/Boundary.d.ts +80 -0
  94. package/dist/esm/internal/Boundary.d.ts.map +1 -0
  95. package/dist/esm/internal/Boundary.js +109 -0
  96. package/dist/esm/internal/Boundary.js.map +1 -0
  97. package/dist/esm/internal/Companion.d.ts +44 -0
  98. package/dist/esm/internal/Companion.d.ts.map +1 -0
  99. package/dist/esm/internal/Companion.js +53 -0
  100. package/dist/esm/internal/Companion.js.map +1 -0
  101. package/dist/esm/internal/SpanOutcome.d.ts +37 -0
  102. package/dist/esm/internal/SpanOutcome.d.ts.map +1 -0
  103. package/dist/esm/internal/SpanOutcome.js +48 -0
  104. package/dist/esm/internal/SpanOutcome.js.map +1 -0
  105. package/dist/esm/migrations/0001_initial.d.ts +15 -0
  106. package/dist/esm/migrations/0001_initial.d.ts.map +1 -0
  107. package/dist/esm/migrations/0001_initial.js +88 -0
  108. package/dist/esm/migrations/0001_initial.js.map +1 -0
  109. package/dist/esm/migrations/0002_lineage.d.ts +27 -0
  110. package/dist/esm/migrations/0002_lineage.d.ts.map +1 -0
  111. package/dist/esm/migrations/0002_lineage.js +33 -0
  112. package/dist/esm/migrations/0002_lineage.js.map +1 -0
  113. package/dist/esm/migrations/0003_execution_revisions.d.ts +15 -0
  114. package/dist/esm/migrations/0003_execution_revisions.d.ts.map +1 -0
  115. package/dist/esm/migrations/0003_execution_revisions.js +58 -0
  116. package/dist/esm/migrations/0003_execution_revisions.js.map +1 -0
  117. package/dist/esm/migrations/0004_waiting_request.d.ts +32 -0
  118. package/dist/esm/migrations/0004_waiting_request.d.ts.map +1 -0
  119. package/dist/esm/migrations/0004_waiting_request.js +37 -0
  120. package/dist/esm/migrations/0004_waiting_request.js.map +1 -0
  121. package/dist/esm/test/TestRunStore.d.ts +17 -0
  122. package/dist/esm/test/TestRunStore.d.ts.map +1 -0
  123. package/dist/esm/test/TestRunStore.js +19 -0
  124. package/dist/esm/test/TestRunStore.js.map +1 -0
  125. package/docs/README.md +142 -0
  126. package/docs/api.md +901 -0
  127. package/docs/concepts/durable-values.md +140 -0
  128. package/docs/concepts/execution-revisions.md +53 -0
  129. package/docs/concepts/fencing.md +146 -0
  130. package/docs/concepts/leases.md +154 -0
  131. package/docs/concepts/liveness-evidence.md +142 -0
  132. package/docs/guides/cancel-a-run.md +156 -0
  133. package/docs/guides/claim-and-finish-a-run.md +175 -0
  134. package/docs/guides/compose-the-stores.md +142 -0
  135. package/docs/guides/observe-outcomes.md +182 -0
  136. package/docs/guides/record-step-attempts.md +170 -0
  137. package/docs/guides/recover-a-stalled-run.md +173 -0
  138. package/docs/guides/testing.md +141 -0
  139. package/docs/installation.md +109 -0
  140. package/docs/quickstart.md +201 -0
  141. package/docs/troubleshooting.md +255 -0
  142. package/package.json +183 -3
  143. package/src/AttemptStore.ts +1195 -0
  144. package/src/Heartbeat.ts +47 -0
  145. package/src/Migrations.ts +59 -0
  146. package/src/Ownership.ts +407 -0
  147. package/src/RunStore.ts +2063 -0
  148. package/src/RunStoreMetrics.ts +165 -0
  149. package/src/index.ts +51 -0
  150. package/src/internal/Boundary.ts +130 -0
  151. package/src/internal/Companion.ts +64 -0
  152. package/src/internal/SpanOutcome.ts +71 -0
  153. package/src/migrations/0001_initial.ts +114 -0
  154. package/src/migrations/0002_lineage.ts +38 -0
  155. package/src/migrations/0003_execution_revisions.ts +69 -0
  156. package/src/migrations/0004_waiting_request.ts +39 -0
  157. package/src/test/TestRunStore.ts +22 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,112 @@
1
+ # @smthrs/run-store
2
+
3
+ ## [Unreleased]
4
+
5
+ ### Added
6
+
7
+ - Required `RunStore.Service` operations `lineage`, `latestRound`, and
8
+ `requestCancelLineage` resolve logical runs across trampoline rounds. Logical
9
+ cancellation is atomic and preserves terminal-round history and existing
10
+ request timestamps. Custom adapters must implement these operations; the
11
+ unavailable stub refuses them explicitly. No database migration is needed.
12
+
13
+ ### Fixed
14
+
15
+ - Fixed the CommonJS build of the migration set. `migrations/0001_initial`
16
+ now exports `initial` and `migrations/0002_lineage` exports `lineage` as
17
+ named bindings, and every importer reads them by name, because esbuild's
18
+ Node interop for a default import of a sibling module resolved to the whole
19
+ exports object instead of the Effect, so `require("@smthrs/run-store")`
20
+ produced a migration set whose entries had no `pipe` and the migrator failed
21
+ at load.
22
+
23
+ ### Changed
24
+
25
+ - `RunStore.claim`, `claimAndOwn`, `steal`, `heartbeat`, and `recoverClaim`
26
+ refuse a `nowMs` that runs ahead of the Effect `Clock` by more than
27
+ `heartbeatSkewAllowance` with `invalid_run`, so a runaway caller clock can
28
+ neither steal a run from a fresh owner nor pin a lease past the stale
29
+ cutoff. A reading behind the clock is still admitted, `requestCancel` keeps
30
+ its literal timestamp, and `claimedAtMs` fence tokens stay unbounded.
31
+
32
+ ## [1.0.0-rc.0] - 2026-09-01
33
+
34
+ ### Added
35
+
36
+ - Split out of `@smthrs/journal`: `RunStore`, `AttemptStore`, and `Ownership`
37
+ now live here, and the package owns the `flows_runs` and `flows_attempts`
38
+ migrations. No schema or behavioral change; see
39
+ `docs/pages/concepts/journal.md`.
40
+ - `Ownership` re-exports the `OwnerId` schema, which `@smthrs/journal` now
41
+ defines because it is the fence `emitDurable` accepts.
42
+ - Package-owned generated API documentation and an explicit Node test subpath.
43
+ - Added the `0002_lineage` migration and the `CreateOptions` trampoline pair
44
+ `lineageId` and `roundOrdinal`, read back on `RunRow`. Both absent means the
45
+ run is a lineage of one, read as round 0 of itself.
46
+ - Added `RequestCancelOutcome.Terminal`, so a request that lost to an ending
47
+ reports the status it lost to instead of claiming the row is gone.
48
+ - Added `ClaimAndOwnOutcome.EvidenceRequired` for a no-evidence claim against
49
+ another live owner, which previously asserted a `SnapshotChanged` comparison
50
+ that branch never performed.
51
+ - Added `StealOutcome.LivenessUnconfirmed` for evidence that does not match the
52
+ expected owner, host relation, or observation time before any comparison.
53
+ - Added the `RunStoreMetrics` module: the `flows_run_claims`,
54
+ `flows_run_transitions`, and `flows_run_heartbeats` counters plus one
55
+ attributed view per operation outcome, including `abandon_claim` and
56
+ `recover_claim`.
57
+ - Added `LivenessCheck`, `LivenessContext`, `leaseLiveness`,
58
+ `sameHostIncarnation`, and `sameHostPidProbe`, so a deployment supplies its
59
+ own liveness answer instead of the store guessing one.
60
+ - Added the `Heartbeat` leaf and its `@smthrs/run-store/Heartbeat` subpath, the
61
+ one place `heartbeatInterval`, `heartbeatStaleAfter`,
62
+ `heartbeatSkewAllowance`, and `heartbeatWriteTolerance` are related.
63
+ - Added `AttemptStore.Options` (`inProgressStates`, `maxCheckpointBytes`,
64
+ `putMode`) with `makeWith` and `layerWith`.
65
+ - Added `AttemptStoreError.method`, so a refusal names the operation that
66
+ produced it and its message reads `<code>: AttemptStore.<method>: <detail>`,
67
+ matching `RunStoreError`.
68
+
69
+ ### Changed
70
+
71
+ - Every caller timestamp is validated as a non-negative safe integer before it
72
+ reaches SQL, so `NaN`, a fraction, or a negative reading fails with a typed
73
+ `invalid_run` or `invalid_attempt` instead of a column-constraint error or a
74
+ success-shaped refusal. The two clock sources are unchanged and now written
75
+ down: evidence-bearing operations judge the caller's `nowMs` literally, while
76
+ `create`, `activate`, and `transitionOwned` stamp from the Effect `Clock`.
77
+ - Run and attempt state now crosses an inert-JSON boundary before every write
78
+ and after every read. It bounds shape, not size: accessors, `toJSON`, cycles,
79
+ malformed text, excess depth, and excess nodes are refused, while run state,
80
+ metadata, errors, and outcomes stay uncapped in bytes.
81
+ - Running snapshots require a complete owner and heartbeat pair; all other
82
+ statuses reject either field, and terminal attempts and runs cannot reopen.
83
+ - Same-host PID checks now treat only `ESRCH` as proof of death and fail closed
84
+ on invalid pids or unexpected host failures.
85
+ - `RunStore.heartbeat` writes `MAX(heartbeat_at_ms, now)`, so a pulse delayed
86
+ past a newer one cannot move the lease backwards; `AttemptStore.heartbeat`
87
+ does the same.
88
+ - `putMode: "upsert"` restates only a row that is still in progress. A finished
89
+ attempt is immutable, and a re-put of one resolves `ExistingSame` or
90
+ `Conflict` rather than reopening it.
91
+ - `put` compares an existing row structurally, so a re-put whose JSON carries
92
+ the same content in a different key order resolves `ExistingSame` instead of
93
+ `Conflict`.
94
+ - `transitionOwned` refuses `pending` as a target and validates its
95
+ `TransitionGuard` at runtime, so a malformed guard fails instead of silently
96
+ disabling the cancellation rule.
97
+ - `RunStoreErrorCode` no longer publishes `unknown`, a code no path in
98
+ `RunStore` produced.
99
+ - `./migrations/*` is closed in the export map: the migration modules are
100
+ implementation, reachable only through `Migrations.set`.
101
+
102
+ ### Fixed
103
+
104
+ - Fenced mutations now validate exact identifiers, monotonic timestamps, and
105
+ complete ownership tuples without retaining executable values in errors.
106
+ A failure cause carries the shape of `state_json`, never its text.
107
+ - Cancellation, claim recovery, and attempt update races now resolve to typed
108
+ contention or persistence outcomes instead of ambiguous missing rows.
109
+ - Identifiers carrying an unpaired UTF-16 surrogate are refused. SQLite maps
110
+ them to `U+FFFD`, so two distinct run ids collided on one durable key.
111
+ - `requestCancel` maps an undecodable persisted status to a typed
112
+ `decode_failed` failure instead of dying.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 William Cory and the Smithers Flows contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,196 @@
1
- # Temporary Holding Version
1
+ # @smthrs/run-store
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Release candidate scope, host requirements and compatibility review are defined in the [library support policy](https://github.com/smithersai/smithers/blob/main/RELEASE_SUPPORT.md).
4
+
5
+ This package declares `effect` as an exact
6
+ `4.0.0-rc.115` peer dependency. Keep the application on that version so
7
+ all Smithers packages share one Effect runtime.
8
+
9
+ **Documentation:** https://run-store.smithers.sh
10
+
11
+ Durable run state and fenced ownership for long-running jobs, as
12
+ [Effect](https://effect.website) services. `@smthrs/run-store` keeps one SQL row
13
+ per run holding what the run is doing, which process owns it, and the executable
14
+ state a restart re-enters. It also arbitrates who is allowed to touch that row,
15
+ so a process that comes back from the dead cannot overwrite the one that
16
+ replaced it.
17
+
18
+ Two services, `RunStore` and `AttemptStore`, plus the `Ownership` liveness
19
+ checks and heartbeat supervision. Arbitration itself — who holds the claim,
20
+ who holds the lease, whether a fence still stands — is delegated to
21
+ [`@smthrs/journal`](https://journal.smithers.sh)'s injectable `Consensus`
22
+ strategy, the same one the journal fences its durable channel through; the
23
+ run row mirrors the strategy's answer in the same transaction. They carry no
24
+ database of their own: both are written against the driver-neutral
25
+ [`@smthrs/database`](https://database.smithers.sh) contract, so the same code
26
+ runs over a local SQLite file, over a server, or over an in-memory database in a
27
+ test.
28
+
29
+ ## Install
30
+
31
+ Not on npm yet; see [Installation](https://github.com/smithersai/smithers/blob/main/packages/smithers/flows/flow/docs/installation.md#use-the-libraries).
32
+
33
+ `effect` is a peer dependency at exactly `4.0.0-rc.115`. Two copies of `effect` in
34
+ one program are two sets of service tags, so a store layer built against one
35
+ copy cannot be provided to a program holding the other.
36
+
37
+ The package requires Node.js 26.4.0 or later and ships as both ESM and
38
+ CommonJS with TypeScript declarations. Only the in-memory test layer binds a
39
+ Node built-in; the root entry point names no driver and bundles for the
40
+ browser.
41
+
42
+ ## Take a run, do the work, settle it
43
+
44
+ This program creates a run, claims ownership of it, and finishes it. The stores
45
+ are the production ones; `TestRunStore.layer` provides them over a migrated
46
+ in-memory database, so the file runs with no configuration.
47
+
48
+ ```ts
49
+ import { RunStore } from "@smthrs/run-store"
50
+ import type { OwnerId } from "@smthrs/run-store/Ownership"
51
+ import * as TestRunStore from "@smthrs/run-store/test/TestRunStore"
52
+ import * as Clock from "effect/Clock"
53
+ import * as Effect from "effect/Effect"
54
+
55
+ const owner: OwnerId = { hostId: "worker-1", pid: 4102, nonce: "9c31-af02" }
56
+
57
+ const program = Effect.gen(function*() {
58
+ const runs = yield* RunStore.RunStore
59
+
60
+ yield* runs.create("build-42", JSON.stringify({ step: "checkout" }))
61
+
62
+ const nowMs = yield* Clock.currentTimeMillis
63
+ const taken = yield* runs.claimAndOwn(
64
+ "build-42",
65
+ { status: "pending", owner: null, heartbeatAtMs: null },
66
+ owner,
67
+ nowMs
68
+ )
69
+ if (taken._tag !== "Activated") return `another process has it: ${taken._tag}`
70
+
71
+ // Do the work here, heartbeating while it runs.
72
+
73
+ const settled = yield* runs.transitionOwned(
74
+ "build-42",
75
+ owner,
76
+ "completed",
77
+ JSON.stringify({ step: "done" })
78
+ )
79
+ return `run ended as ${settled._tag}`
80
+ })
81
+
82
+ console.log(
83
+ await Effect.runPromise(
84
+ program.pipe(Effect.provide(TestRunStore.layer), Effect.scoped)
85
+ )
86
+ )
87
+ ```
88
+
89
+ ```text
90
+ run ended as Transitioned
91
+ ```
92
+
93
+ The second argument to `claimAndOwn` is the row as you last read it, restated as
94
+ the three fields a claim guards. The third argument is the new owner. The claim
95
+ is admitted only while the row still matches. If a peer took the run between
96
+ your read and your write, this caller loses the race and leaves the peer's
97
+ ownership unchanged.
98
+
99
+ ## What the stores guarantee
100
+
101
+ - **One row is the authority.** A restarted process reads the run row for its
102
+ status, owner, heartbeat, cancellation request, and the executable state to
103
+ re-enter, instead of replaying a log to work out where it was.
104
+ - **Every owned write carries a fence.** An owner identity is
105
+ `{ hostId, pid, nonce }`, and every owned mutation asks the `Consensus`
106
+ strategy whether that identity still holds the run inside the same
107
+ serialized write transaction as the mutation it guards. There is no window
108
+ between checking ownership and using it, so two processes cannot both win.
109
+ - **Competition is a value, not an error.** Losing a race returns
110
+ `AlreadyClaimed`, `HeartbeatFresh`, or `FenceLost` as an ordinary success
111
+ value you branch on. The error channel is reserved for real defects: invalid
112
+ input, a corrupt row, a constraint violation, a database failure.
113
+ - **A takeover needs evidence, not just a timeout.** An expired lease says the
114
+ owner stopped writing, not that it stopped working. `steal` and `recoverClaim`
115
+ additionally require `LivenessEvidence` about the recorded owner, bound to the
116
+ exact instant it is spent, and every unknown probe answer is read as life.
117
+ - **Step attempts inherit the run's fence.** `AttemptStore` records when each
118
+ execution of a step started, the checkpoints it wrote, and how it ended, and
119
+ refuses every write from a process that no longer owns the run.
120
+ - **Persisted values are copied inert.** Run state, checkpoints, errors,
121
+ outcomes, and metadata are walked into a frozen detached copy before
122
+ persistence can yield, so a getter, a cycle, or a `toJSON` method is refused
123
+ rather than persisted, and rows are validated again on the way out.
124
+
125
+ ## Public API
126
+
127
+ The root exports each module as a namespace, and each is also importable from
128
+ the matching `@smthrs/run-store/*` subpath. The
129
+ [API reference](https://run-store.smithers.sh/reference/api/) lists every export.
130
+
131
+ | Namespace | What it holds |
132
+ | ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
133
+ | `RunStore` | The run lifecycle: create, read, cancel, the six claim operations, heartbeat, and owned transitions, with `layer` over `SqlConsensus` and `layerWith` over an injected strategy. |
134
+ | `AttemptStore` | Fenced step attempts: start, heartbeat with a checkpoint, finish, patch, and read, with its policy options. |
135
+ | `Ownership` | `OwnerId` and `LivenessEvidence` from the journal, fail-closed pid probing, the lease checks, and the heartbeat supervision loop. |
136
+ | `RunStoreMetrics` | `flows_run_claims`, `flows_run_heartbeats`, and `flows_run_transitions`, attributed by operation and outcome. |
137
+ | `Migrations` | The `flows_runs` and `flows_attempts` migration set, and the runner and layer that install the journal's set ahead of it. |
138
+
139
+ The root and those subpaths are driver-neutral and bundle for the browser.
140
+ Two subpaths are not namespaces of the root:
141
+
142
+ | Import | Platform | What it holds |
143
+ | ------------------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
144
+ | `@smthrs/run-store/Heartbeat` | any | The lease durations `heartbeatInterval`, `heartbeatStaleAfter`, `heartbeatSkewAllowance`, and `heartbeatWriteTolerance`, defined by `@smthrs/journal`'s `Consensus` and also re-exported from `Ownership`. A consumer that needs only the durations imports this leaf and pulls in no store. |
145
+ | `@smthrs/run-store/test/TestRunStore` | Node | `layer`, providing migrated in-memory `RunStore` and `AttemptStore` services. |
146
+
147
+ Migration implementations and package internals are blocked in the export map:
148
+ the migration set is the contract, not the numbered files inside it.
149
+
150
+ ## Where the run store sits
151
+
152
+ `@smthrs/run-store` is one of the storage services inside
153
+ [`@smthrs/flows`](https://flows.smithers.sh), the package that carries the whole
154
+ Smithers durable flow engine in a single dependency. Flow authors never call it.
155
+
156
+ Reach for it directly when you are building the host rather than the flow: a
157
+ scheduler, a worker pool, or any service that hands durable work between
158
+ processes and needs ownership arbitration it can trust. Most hosts do not wire
159
+ it by hand even then.
160
+ [`@smthrs/engine-store`](https://engine-store.smithers.sh) already composes
161
+ these stores with the journal, the step cache, and the engine's own state into
162
+ one storage ladder.
163
+
164
+ The stores hold executable state. History lives in
165
+ [`@smthrs/journal`](https://journal.smithers.sh), and the two halves stay
166
+ consistent because both write through the same `@smthrs/database`
167
+ `DurableWriter`: `Journal.transact` commits a state projection and its durable
168
+ events in one transaction.
169
+
170
+ ## Documentation
171
+
172
+ Full documentation is at
173
+ [run-store.smithers.sh](https://run-store.smithers.sh):
174
+
175
+ - [Installation](https://run-store.smithers.sh/installation/)
176
+ - [Quickstart](https://run-store.smithers.sh/quickstart/): one run through its
177
+ whole lifecycle, including a step attempt.
178
+ - [Fencing and ownership](https://run-store.smithers.sh/concepts/fencing/): the
179
+ compare-and-swap every write goes through.
180
+ - [The heartbeat lease](https://run-store.smithers.sh/concepts/leases/): the
181
+ four durations, the two clocks that stamp one row, and what a wall-clock lease
182
+ cannot promise.
183
+ - [Liveness evidence](https://run-store.smithers.sh/concepts/liveness-evidence/):
184
+ what admits a takeover.
185
+ - [Durable values](https://run-store.smithers.sh/concepts/durable-values/): the
186
+ persistence boundary and its shape limits.
187
+ - [Execution revisions](https://run-store.smithers.sh/concepts/execution-revisions/):
188
+ database identity, monotonic changes, retained tombstones and cancellation acknowledgement.
189
+ - [API reference](https://run-store.smithers.sh/reference/api/): every public
190
+ export.
191
+ - [Troubleshooting](https://run-store.smithers.sh/troubleshooting/): each
192
+ refused outcome and typed error, and what to change.
193
+
194
+ ## License
195
+
196
+ MIT