@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.
- package/CHANGELOG.md +112 -0
- package/LICENSE +21 -0
- package/README.md +195 -2
- package/dist/cjs/AttemptStore.d.ts +359 -0
- package/dist/cjs/AttemptStore.d.ts.map +1 -0
- package/dist/cjs/AttemptStore.js +731 -0
- package/dist/cjs/AttemptStore.js.map +7 -0
- package/dist/cjs/Heartbeat.d.ts +46 -0
- package/dist/cjs/Heartbeat.d.ts.map +1 -0
- package/dist/cjs/Heartbeat.js +28 -0
- package/dist/cjs/Heartbeat.js.map +7 -0
- package/dist/cjs/Migrations.d.ts +42 -0
- package/dist/cjs/Migrations.d.ts.map +1 -0
- package/dist/cjs/Migrations.js +55 -0
- package/dist/cjs/Migrations.js.map +7 -0
- package/dist/cjs/Ownership.d.ts +275 -0
- package/dist/cjs/Ownership.d.ts.map +1 -0
- package/dist/cjs/Ownership.js +129 -0
- package/dist/cjs/Ownership.js.map +7 -0
- package/dist/cjs/RunStore.d.ts +571 -0
- package/dist/cjs/RunStore.d.ts.map +1 -0
- package/dist/cjs/RunStore.js +1079 -0
- package/dist/cjs/RunStore.js.map +7 -0
- package/dist/cjs/RunStoreMetrics.d.ts +151 -0
- package/dist/cjs/RunStoreMetrics.d.ts.map +1 -0
- package/dist/cjs/RunStoreMetrics.js +91 -0
- package/dist/cjs/RunStoreMetrics.js.map +7 -0
- package/dist/cjs/index.d.ts +47 -0
- package/dist/cjs/index.d.ts.map +1 -0
- package/dist/cjs/index.js +43 -0
- package/dist/cjs/index.js.map +7 -0
- package/dist/cjs/internal/Boundary.d.ts +80 -0
- package/dist/cjs/internal/Boundary.d.ts.map +1 -0
- package/dist/cjs/internal/Boundary.js +92 -0
- package/dist/cjs/internal/Boundary.js.map +7 -0
- package/dist/cjs/internal/Companion.d.ts +44 -0
- package/dist/cjs/internal/Companion.d.ts.map +1 -0
- package/dist/cjs/internal/Companion.js +50 -0
- package/dist/cjs/internal/Companion.js.map +7 -0
- package/dist/cjs/internal/SpanOutcome.d.ts +37 -0
- package/dist/cjs/internal/SpanOutcome.d.ts.map +1 -0
- package/dist/cjs/internal/SpanOutcome.js +62 -0
- package/dist/cjs/internal/SpanOutcome.js.map +7 -0
- package/dist/cjs/migrations/0001_initial.d.ts +15 -0
- package/dist/cjs/migrations/0001_initial.d.ts.map +1 -0
- package/dist/cjs/migrations/0001_initial.js +110 -0
- package/dist/cjs/migrations/0001_initial.js.map +7 -0
- package/dist/cjs/migrations/0002_lineage.d.ts +27 -0
- package/dist/cjs/migrations/0002_lineage.d.ts.map +1 -0
- package/dist/cjs/migrations/0002_lineage.js +43 -0
- package/dist/cjs/migrations/0002_lineage.js.map +7 -0
- package/dist/cjs/migrations/0003_execution_revisions.d.ts +15 -0
- package/dist/cjs/migrations/0003_execution_revisions.d.ts.map +1 -0
- package/dist/cjs/migrations/0003_execution_revisions.js +77 -0
- package/dist/cjs/migrations/0003_execution_revisions.js.map +7 -0
- package/dist/cjs/migrations/0004_waiting_request.d.ts +32 -0
- package/dist/cjs/migrations/0004_waiting_request.d.ts.map +1 -0
- package/dist/cjs/migrations/0004_waiting_request.js +42 -0
- package/dist/cjs/migrations/0004_waiting_request.js.map +7 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/test/TestRunStore.d.ts +17 -0
- package/dist/cjs/test/TestRunStore.d.ts.map +1 -0
- package/dist/cjs/test/TestRunStore.js +42 -0
- package/dist/cjs/test/TestRunStore.js.map +7 -0
- package/dist/esm/AttemptStore.d.ts +359 -0
- package/dist/esm/AttemptStore.d.ts.map +1 -0
- package/dist/esm/AttemptStore.js +835 -0
- package/dist/esm/AttemptStore.js.map +1 -0
- package/dist/esm/Heartbeat.d.ts +46 -0
- package/dist/esm/Heartbeat.d.ts.map +1 -0
- package/dist/esm/Heartbeat.js +46 -0
- package/dist/esm/Heartbeat.js.map +1 -0
- package/dist/esm/Migrations.d.ts +42 -0
- package/dist/esm/Migrations.d.ts.map +1 -0
- package/dist/esm/Migrations.js +56 -0
- package/dist/esm/Migrations.js.map +1 -0
- package/dist/esm/Ownership.d.ts +275 -0
- package/dist/esm/Ownership.d.ts.map +1 -0
- package/dist/esm/Ownership.js +299 -0
- package/dist/esm/Ownership.js.map +1 -0
- package/dist/esm/RunStore.d.ts +571 -0
- package/dist/esm/RunStore.d.ts.map +1 -0
- package/dist/esm/RunStore.js +1287 -0
- package/dist/esm/RunStore.js.map +1 -0
- package/dist/esm/RunStoreMetrics.d.ts +151 -0
- package/dist/esm/RunStoreMetrics.d.ts.map +1 -0
- package/dist/esm/RunStoreMetrics.js +120 -0
- package/dist/esm/RunStoreMetrics.js.map +1 -0
- package/dist/esm/index.d.ts +47 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +47 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/internal/Boundary.d.ts +80 -0
- package/dist/esm/internal/Boundary.d.ts.map +1 -0
- package/dist/esm/internal/Boundary.js +109 -0
- package/dist/esm/internal/Boundary.js.map +1 -0
- package/dist/esm/internal/Companion.d.ts +44 -0
- package/dist/esm/internal/Companion.d.ts.map +1 -0
- package/dist/esm/internal/Companion.js +53 -0
- package/dist/esm/internal/Companion.js.map +1 -0
- package/dist/esm/internal/SpanOutcome.d.ts +37 -0
- package/dist/esm/internal/SpanOutcome.d.ts.map +1 -0
- package/dist/esm/internal/SpanOutcome.js +48 -0
- package/dist/esm/internal/SpanOutcome.js.map +1 -0
- package/dist/esm/migrations/0001_initial.d.ts +15 -0
- package/dist/esm/migrations/0001_initial.d.ts.map +1 -0
- package/dist/esm/migrations/0001_initial.js +88 -0
- package/dist/esm/migrations/0001_initial.js.map +1 -0
- package/dist/esm/migrations/0002_lineage.d.ts +27 -0
- package/dist/esm/migrations/0002_lineage.d.ts.map +1 -0
- package/dist/esm/migrations/0002_lineage.js +33 -0
- package/dist/esm/migrations/0002_lineage.js.map +1 -0
- package/dist/esm/migrations/0003_execution_revisions.d.ts +15 -0
- package/dist/esm/migrations/0003_execution_revisions.d.ts.map +1 -0
- package/dist/esm/migrations/0003_execution_revisions.js +58 -0
- package/dist/esm/migrations/0003_execution_revisions.js.map +1 -0
- package/dist/esm/migrations/0004_waiting_request.d.ts +32 -0
- package/dist/esm/migrations/0004_waiting_request.d.ts.map +1 -0
- package/dist/esm/migrations/0004_waiting_request.js +37 -0
- package/dist/esm/migrations/0004_waiting_request.js.map +1 -0
- package/dist/esm/test/TestRunStore.d.ts +17 -0
- package/dist/esm/test/TestRunStore.d.ts.map +1 -0
- package/dist/esm/test/TestRunStore.js +19 -0
- package/dist/esm/test/TestRunStore.js.map +1 -0
- package/docs/README.md +142 -0
- package/docs/api.md +901 -0
- package/docs/concepts/durable-values.md +140 -0
- package/docs/concepts/execution-revisions.md +53 -0
- package/docs/concepts/fencing.md +146 -0
- package/docs/concepts/leases.md +154 -0
- package/docs/concepts/liveness-evidence.md +142 -0
- package/docs/guides/cancel-a-run.md +156 -0
- package/docs/guides/claim-and-finish-a-run.md +175 -0
- package/docs/guides/compose-the-stores.md +142 -0
- package/docs/guides/observe-outcomes.md +182 -0
- package/docs/guides/record-step-attempts.md +170 -0
- package/docs/guides/recover-a-stalled-run.md +173 -0
- package/docs/guides/testing.md +141 -0
- package/docs/installation.md +109 -0
- package/docs/quickstart.md +201 -0
- package/docs/troubleshooting.md +255 -0
- package/package.json +183 -3
- package/src/AttemptStore.ts +1195 -0
- package/src/Heartbeat.ts +47 -0
- package/src/Migrations.ts +59 -0
- package/src/Ownership.ts +407 -0
- package/src/RunStore.ts +2063 -0
- package/src/RunStoreMetrics.ts +165 -0
- package/src/index.ts +51 -0
- package/src/internal/Boundary.ts +130 -0
- package/src/internal/Companion.ts +64 -0
- package/src/internal/SpanOutcome.ts +71 -0
- package/src/migrations/0001_initial.ts +114 -0
- package/src/migrations/0002_lineage.ts +38 -0
- package/src/migrations/0003_execution_revisions.ts +69 -0
- package/src/migrations/0004_waiting_request.ts +39 -0
- package/src/test/TestRunStore.ts +22 -0
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/AttemptStore.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Durable storage for individual step attempts.\n *\n * Attempt metadata is deliberately opaque to this module. Its shape belongs to\n * the step executor, and is persisted unchanged across attempt state changes.\n *\n * The running-state and owner fences mean stale attempts and repeated terminal\n * transitions never overwrite the winning row.\n *\n * @since 0.1.0\n */\n\nimport * as Dialect from \"@smthrs/database/Dialect\"\nimport { DatabaseError, DurableWriter, fromSqlError } from \"@smthrs/database/DurableWriter\"\nimport type { OwnerId } from \"@smthrs/journal/OwnerId\"\nimport * as Context from \"effect/Context\"\nimport * as Effect from \"effect/Effect\"\nimport * as Layer from \"effect/Layer\"\nimport * as Option from \"effect/Option\"\nimport * as Schema from \"effect/Schema\"\nimport * as SqlClient from \"effect/unstable/sql/SqlClient\"\nimport * as SqlError from \"effect/unstable/sql/SqlError\"\nimport * as Boundary from \"./internal/Boundary.ts\"\nimport { observeExit, observeOutcome } from \"./internal/SpanOutcome.ts\"\n\nconst NonNegativeSafeInt = Schema.Int.check(\n Schema.isGreaterThanOrEqualTo(0),\n Schema.isLessThanOrEqualTo(Number.MAX_SAFE_INTEGER)\n)\n\nconst durableIdentifier = Schema.NonEmptyString.check(\n Schema.isMaxLength(Boundary.maximumIdentifierLength),\n Schema.makeFilter((value: string) => Boundary.isDurableText(value), { title: \"durableIdentifier\" })\n)\n\n/**\n * Absolute ceiling for {@link Options.maxCheckpointBytes}.\n *\n * This bounds only the configured checkpoint policy; metadata, errors, and\n * outcomes have no byte ceiling.\n *\n * @category constants\n * @since 1.0.0-rc.0\n */\nexport const maximumCheckpointBytes = 16 * 1024 * 1024\n\n/**\n * Maximum nesting admitted for durable attempt JSON.\n *\n * @category constants\n * @since 1.0.0-rc.0\n */\nexport const maximumJsonDepth = 128\n\n/**\n * Maximum values and members admitted for durable attempt JSON.\n *\n * @category constants\n * @since 1.0.0-rc.0\n */\nexport const maximumJsonNodes = 100_000\n\nconst jsonLimits = (maxBytes?: number | undefined): Boundary.JsonLimits => ({\n ...(maxBytes === undefined ? {} : { maxBytes, maxStringBytes: maxBytes }),\n maxDepth: maximumJsonDepth,\n maxMembers: maximumJsonNodes,\n maxNodes: maximumJsonNodes\n})\n\n/**\n * Strict JSON value accepted as executable attempt state.\n *\n * The store still takes an inert snapshot at effect start; this schema is the\n * public declaration contract shared by `Attempt`, `FinishAttempt`, and\n * `AttemptPatch`.\n *\n * @category schemas\n * @since 1.0.0-rc.0\n */\nexport const JsonValue = Schema.declare<Boundary.Json>(\n (value): value is Boundary.Json => Boundary.admitJson(value, jsonLimits()).ok,\n { identifier: \"@smthrs/run-store/JsonValue\" }\n)\n\n/**\n * Strict JSON value accepted as executable attempt state.\n *\n * @category models\n * @since 1.0.0-rc.0\n */\nexport type JsonValue = typeof JsonValue.Type\n\n/**\n * Stable error codes returned by attempt persistence operations.\n *\n * @category models\n * @since 0.1.0\n */\nexport const AttemptStoreErrorCode = Schema.Literals([\n \"invalid_attempt\",\n \"constraint\",\n \"decode_failed\",\n \"persistence_failed\",\n \"unknown\"\n])\n\n/**\n * Stable error codes returned by attempt persistence operations.\n *\n * @category models\n * @since 0.1.0\n */\nexport type AttemptStoreErrorCode = typeof AttemptStoreErrorCode.Type\n\n/**\n * Error raised by attempt persistence operations.\n *\n * The identity string equals the defining module path, which is how every\n * tagged identity in this package is named.\n *\n * @category errors\n * @since 0.1.0\n */\nexport class AttemptStoreError extends Schema.TaggedError<AttemptStoreError>()(\n \"@smthrs/run-store/AttemptStoreError\",\n {\n code: AttemptStoreErrorCode,\n method: Schema.String,\n message: Schema.String,\n cause: Schema.optional(Schema.Unknown)\n }\n) {}\n\n/**\n * Identifies one execution of a content-addressed step within a run.\n *\n * @category models\n * @since 0.1.0\n */\nexport const AttemptId = Schema.Struct({\n runId: durableIdentifier,\n stepKeyDigest: durableIdentifier,\n attempt: NonNegativeSafeInt\n})\n\n/**\n * Identifies one execution of a content-addressed step within a run.\n *\n * @category models\n * @since 0.1.0\n */\nexport type AttemptId = typeof AttemptId.Type\n\n/**\n * A durable attempt row.\n *\n * @category models\n * @since 0.1.0\n */\nexport const Attempt = Schema.Struct({\n ...AttemptId.fields,\n state: durableIdentifier,\n startedAtMs: NonNegativeSafeInt,\n finishedAtMs: Schema.optionalKey(NonNegativeSafeInt),\n heartbeatAtMs: Schema.optionalKey(NonNegativeSafeInt),\n checkpoint: Schema.optionalKey(JsonValue),\n error: Schema.optionalKey(JsonValue),\n outcome: Schema.optionalKey(JsonValue),\n meta: JsonValue\n})\n\n/**\n * A durable attempt row.\n *\n * @category models\n * @since 0.1.0\n */\nexport type Attempt = typeof Attempt.Type\n\n/**\n * Input used to finish an existing attempt. `error`, `outcome`, and `meta`\n * follow the same rule as {@link AttemptPatch}: an omitted field is left as\n * recorded, so a terminal transition never erases a value written mid-flight\n * by `put` or `patch`. Supplying one replaces it atomically with the terminal\n * state, which lets an executor durably record what it discovered while\n * handling a failure.\n *\n * @category models\n * @since 0.1.0\n */\nexport const FinishAttempt = Schema.Struct({\n ...AttemptId.fields,\n state: durableIdentifier,\n finishedAtMs: NonNegativeSafeInt,\n error: Schema.optionalKey(JsonValue),\n outcome: Schema.optionalKey(JsonValue),\n meta: Schema.optionalKey(JsonValue)\n})\n\n/**\n * Input used to finish an existing attempt.\n *\n * @category models\n * @since 0.1.0\n */\nexport type FinishAttempt = typeof FinishAttempt.Type\n\n/**\n * Result of starting an owner-fenced attempt.\n *\n * @category models\n * @since 0.1.0\n */\nexport type PutResult =\n | { readonly _tag: \"Inserted\" }\n | { readonly _tag: \"Upserted\" }\n | { readonly _tag: \"ExistingSame\" }\n | { readonly _tag: \"Conflict\" }\n | { readonly _tag: \"FenceLost\" }\n | { readonly _tag: \"RunNotFound\" }\n\n/**\n * Fields a patch may rewrite.\n *\n * A patch never touches `state`, `started_at_ms`, or `finished_at_ms`: those\n * are the lifecycle, and only `put`/`heartbeat`/`finish` move them. An\n * omitted field is left as recorded.\n *\n * @category models\n * @since 0.1.0\n */\nexport const AttemptPatch = Schema.Struct({\n checkpoint: Schema.optionalKey(JsonValue),\n error: Schema.optionalKey(JsonValue),\n outcome: Schema.optionalKey(JsonValue),\n meta: Schema.optionalKey(JsonValue)\n})\n\n/**\n * Fields a patch may rewrite.\n *\n * @category models\n * @since 0.1.0\n */\nexport type AttemptPatch = typeof AttemptPatch.Type\n\n/**\n * Result of an owner-fenced attempt patch.\n *\n * @category models\n * @since 0.1.0\n */\nexport type PatchResult =\n | { readonly _tag: \"Patched\" }\n | { readonly _tag: \"NotFound\" }\n | { readonly _tag: \"FenceLost\" }\n\n/**\n * Store-wide policy.\n *\n * Defaults treat only `running` attempts as in progress, cap checkpoints at\n * 1 MiB, and make attempt insertion first-writer-wins.\n *\n * @category models\n * @since 0.1.0\n */\nexport interface Options {\n /**\n * States the store treats as \"attempt still in progress\". `heartbeat` and\n * `finish` fence on membership, and `finish` refuses them as targets.\n * Defaults to `[\"running\"]`.\n */\n readonly inProgressStates?: ReadonlyArray<string> | undefined\n /**\n * Largest encoded checkpoint accepted, in bytes. Defaults to 1 MiB and may\n * not exceed {@link maximumCheckpointBytes}.\n */\n readonly maxCheckpointBytes?: number | undefined\n /**\n * `\"insert\"` (the default) is first-writer-wins: a re-put with different\n * content reports `Conflict`. `\"upsert\"` overwrites it and reports\n * `Upserted` only while the existing row is still in progress. A terminal\n * row remains immutable. Both modes keep the run-ownership fence.\n */\n readonly putMode?: \"insert\" | \"upsert\" | undefined\n}\n\n/**\n * Result of a fenced attempt heartbeat.\n *\n * @category models\n * @since 0.1.0\n */\nexport type HeartbeatResult =\n | { readonly _tag: \"Updated\" }\n | { readonly _tag: \"FenceLost\" }\n | { readonly _tag: \"NotFound\" }\n | { readonly _tag: \"StateChanged\" }\n\n/**\n * Result of a fenced terminal attempt transition.\n *\n * @category models\n * @since 0.1.0\n */\nexport type FinishResult =\n | { readonly _tag: \"Finished\" }\n | { readonly _tag: \"FenceLost\" }\n | { readonly _tag: \"NotFound\" }\n | { readonly _tag: \"StateChanged\" }\n\n/**\n * Attempt persistence operations.\n *\n * @category models\n * @since 0.1.0\n */\nexport interface Service {\n readonly put: (attempt: Attempt, owner: OwnerId) => Effect.Effect<PutResult, AttemptStoreError>\n readonly get: (id: AttemptId) => Effect.Effect<Option.Option<Attempt>, AttemptStoreError>\n readonly heartbeat: (\n runId: string,\n stepKeyDigest: string,\n attempt: number,\n owner: OwnerId,\n nowMs: number,\n checkpoint?: JsonValue\n ) => Effect.Effect<HeartbeatResult, AttemptStoreError>\n readonly finish: (attempt: FinishAttempt, owner: OwnerId) => Effect.Effect<FinishResult, AttemptStoreError>\n /**\n * Rewrites opaque fields without competing for the attempt lifecycle: a\n * patch never moves `state`, so executors record response text, worktree\n * pointers, or cache flags on running *and* terminal rows. It is fenced on\n * run ownership like every other write \u2014 `outcome` is replayed verbatim as\n * the attempt's result, so a delayed patch from an owner that lost the run\n * (or from any writer after the run reached a terminal status and its\n * owner columns were cleared) reports `FenceLost` instead of rewriting the\n * winning row. Prior art: Temporal conditions every persistence write on\n * the shard `rangeID` (`reference/temporal/service/history/shard/`); there\n * is no unfenced write surface.\n */\n readonly patch: (\n id: AttemptId,\n patch: AttemptPatch,\n owner: OwnerId\n ) => Effect.Effect<PatchResult, AttemptStoreError>\n}\n\n/**\n * Service tag for durable step attempts.\n *\n * The identity string equals the defining module path, which is how every\n * service in this package is named.\n *\n * @category services\n * @since 0.1.0\n */\nexport class AttemptStore extends Context.Service<AttemptStore, Service>()(\"@smthrs/run-store/AttemptStore\") {}\n\nconst AttemptRow = Schema.Struct({\n run_id: durableIdentifier,\n step_key_digest: durableIdentifier,\n attempt: NonNegativeSafeInt,\n state: durableIdentifier,\n started_at_ms: NonNegativeSafeInt,\n finished_at_ms: Schema.NullOr(NonNegativeSafeInt),\n heartbeat_at_ms: Schema.NullOr(NonNegativeSafeInt),\n checkpoint_json: Schema.NullOr(Schema.String),\n error_json: Schema.NullOr(Schema.String),\n outcome_json: Schema.NullOr(Schema.String),\n meta_json: Schema.String\n})\n\ntype AttemptRow = typeof AttemptRow.Type\n\ninterface RunFenceRow {\n readonly status: string\n readonly owner_host_id: string | null\n readonly owner_pid: number | null\n readonly owner_nonce: string | null\n}\n\ntype AttemptStoreMethod = \"makeWith\" | keyof Service\n\nconst attemptStoreError = (\n method: AttemptStoreMethod,\n code: AttemptStoreErrorCode,\n detail: string,\n cause?: unknown\n): AttemptStoreError =>\n new AttemptStoreError({\n code,\n method,\n message: `${code}: AttemptStore.${method}: ${detail}`,\n cause\n })\n\nconst error = (method: AttemptStoreMethod, code: AttemptStoreErrorCode, detail: string): AttemptStoreError =>\n attemptStoreError(method, code, detail)\n\nconst encode = (\n method: AttemptStoreMethod,\n value: unknown,\n field: string,\n maxBytes?: number | undefined\n): Effect.Effect<string, AttemptStoreError> =>\n Effect.suspend(() => {\n const admitted = Boundary.admitJson(value, jsonLimits(maxBytes))\n return admitted.ok\n ? Effect.succeed(JSON.stringify(admitted.value))\n : Effect.fail(error(method, \"invalid_attempt\", `${field} ${admitted.complaint}`))\n })\n\nconst encodeOptionalWith =\n (method: AttemptStoreMethod) =>\n (value: unknown | undefined, field: string): Effect.Effect<string | null, AttemptStoreError> =>\n value === undefined ? Effect.succeed(null) : encode(method, value, field)\n\nconst defaultMaxCheckpointBytes = 1024 * 1024\n\nconst defaultInProgressStates: ReadonlyArray<string> = [\"running\"]\n\nconst encodeCheckpointWith =\n (method: AttemptStoreMethod, maxBytes: number) =>\n (value: unknown | undefined): Effect.Effect<string | null, AttemptStoreError> =>\n value === undefined ? Effect.succeed(null) : encode(method, value, \"checkpoint\", maxBytes)\n\nconst decode = (\n method: AttemptStoreMethod,\n value: string | null,\n field: string,\n maxBytes?: number | undefined\n): Effect.Effect<JsonValue | undefined, AttemptStoreError> =>\n Effect.suspend(() => {\n if (value === null) return Effect.succeed(undefined)\n const admitted = Boundary.admitJsonText(value, jsonLimits(maxBytes))\n return admitted.ok\n ? Effect.succeed(admitted.json)\n : Effect.fail(error(method, \"decode_failed\", `${field} ${admitted.complaint}`))\n })\n\nconst decodeRequired = (\n method: AttemptStoreMethod,\n value: string,\n field: string,\n maxBytes?: number | undefined\n): Effect.Effect<JsonValue, AttemptStoreError> =>\n // A non-null SQL column cannot take `decode`'s missing-value branch.\n decode(method, value, field, maxBytes) as Effect.Effect<JsonValue, AttemptStoreError>\n\nconst validateId = (method: AttemptStoreMethod, id: AttemptId): Effect.Effect<void, AttemptStoreError> =>\n Boundary.isDurableText(id.runId) &&\n Boundary.isDurableText(id.stepKeyDigest) &&\n Number.isSafeInteger(id.attempt) &&\n id.attempt >= 0\n ? Effect.void\n : Effect.fail(\n error(method, \"invalid_attempt\", \"attempt identity violates the durable identifier contract\")\n )\n\nconst inspectRecord = (\n input: unknown,\n required: ReadonlyArray<string>,\n optional: ReadonlyArray<string>,\n field: string\n): Readonly<Record<string, unknown>> => {\n if (typeof input !== \"object\" || input === null) throw new TypeError(field)\n const prototype = Object.getPrototypeOf(input)\n if (prototype !== Object.prototype && prototype !== null) throw new TypeError(field)\n const allowed = new Set([...required, ...optional])\n const output = Object.create(null) as Record<string, unknown>\n for (const name of required) {\n const descriptor = Object.getOwnPropertyDescriptor(input, name)\n if (descriptor === undefined || !(\"value\" in descriptor) || !descriptor.enumerable) throw new TypeError(field)\n output[name] = descriptor.value\n }\n for (const name of optional) {\n const descriptor = Object.getOwnPropertyDescriptor(input, name)\n if (descriptor === undefined) continue\n if (!(\"value\" in descriptor) || !descriptor.enumerable) throw new TypeError(field)\n if (descriptor.value !== undefined) output[name] = descriptor.value\n }\n for (const key of Reflect.ownKeys(input)) {\n if (typeof key !== \"string\" || !allowed.has(key)) {\n if (Object.getOwnPropertyDescriptor(input, key)?.enumerable) throw new TypeError(field)\n }\n }\n return output\n}\n\n/**\n * Captures the fencing owner once, as a frozen inert record. Every span, SQL\n * predicate, and fence comparison reads this copy, so an accessor, a proxy, or\n * a mutation during an await cannot put two different owners into one write.\n */\nconst snapshotOwner = (method: AttemptStoreMethod, input: OwnerId): Effect.Effect<OwnerId, AttemptStoreError> =>\n Effect.suspend(() => {\n try {\n const values = inspectRecord(input, [\"hostId\", \"pid\", \"nonce\"], [], \"owner\")\n const owner = Object.freeze({ hostId: values.hostId, pid: values.pid, nonce: values.nonce })\n if (\n Boundary.isDurableText(owner.hostId) && Boundary.isDurableText(owner.nonce) &&\n Number.isSafeInteger(owner.pid) && (owner.pid as number) >= 0\n ) {\n return Effect.succeed(owner as OwnerId)\n }\n } catch {\n // Falls through to the one refusal below.\n }\n return Effect.fail(error(method, \"invalid_attempt\", \"owner must be an inert, valid owner identity\"))\n })\n\nconst snapshotJson = (\n method: AttemptStoreMethod,\n value: unknown,\n field: string,\n maxBytes?: number | undefined\n): { readonly ok: true; readonly value: JsonValue } | { readonly ok: false; readonly error: AttemptStoreError } => {\n const admitted = Boundary.admitJson(value, jsonLimits(maxBytes))\n return admitted.ok\n ? { ok: true, value: admitted.value }\n : { ok: false, error: error(method, \"invalid_attempt\", `${field} ${admitted.complaint}`) }\n}\n\n/**\n * Structural admission for an attempt identity. `Attempt` and `FinishAttempt`\n * extend `AttemptId`, so a stored attempt must pass wherever an id is typed:\n * extra own data fields are ignored, while a non-plain prototype, any\n * enumerable accessor, or a missing or inherited required field is refused.\n */\nconst inspectIdRecord = (input: unknown, field: string): Readonly<Record<string, unknown>> => {\n if (typeof input !== \"object\" || input === null) throw new TypeError(field)\n const prototype = Object.getPrototypeOf(input)\n if (prototype !== Object.prototype && prototype !== null) throw new TypeError(field)\n for (const key of Reflect.ownKeys(input)) {\n const descriptor = Object.getOwnPropertyDescriptor(input, key)\n if (descriptor !== undefined && descriptor.enumerable && !(\"value\" in descriptor)) throw new TypeError(field)\n }\n const output = Object.create(null) as Record<string, unknown>\n for (const name of [\"runId\", \"stepKeyDigest\", \"attempt\"]) {\n const descriptor = Object.getOwnPropertyDescriptor(input, name)\n if (descriptor === undefined || !(\"value\" in descriptor) || !descriptor.enumerable) throw new TypeError(field)\n output[name] = descriptor.value\n }\n return output\n}\n\nconst snapshotId = (method: AttemptStoreMethod, input: AttemptId): Effect.Effect<AttemptId, AttemptStoreError> =>\n Effect.suspend(() => {\n try {\n const values = inspectIdRecord(input, \"attempt id\")\n const id = Object.freeze({\n runId: values.runId,\n stepKeyDigest: values.stepKeyDigest,\n attempt: values.attempt\n }) as AttemptId\n return Effect.as(validateId(method, id), id)\n } catch {\n return Effect.fail(error(method, \"invalid_attempt\", \"attempt id must be an inert data record\"))\n }\n })\n\nconst snapshotAttempt = (method: AttemptStoreMethod, input: Attempt): Effect.Effect<Attempt, AttemptStoreError> =>\n Effect.suspend(() => {\n try {\n const values = inspectRecord(\n input,\n [\"runId\", \"stepKeyDigest\", \"attempt\", \"state\", \"startedAtMs\", \"meta\"],\n [\"finishedAtMs\", \"heartbeatAtMs\", \"checkpoint\", \"error\", \"outcome\"],\n \"attempt\"\n )\n const checkpoint = values.checkpoint === undefined\n ? undefined\n : snapshotJson(method, values.checkpoint, \"checkpoint\", maximumCheckpointBytes)\n const attemptError = values.error === undefined ? undefined : snapshotJson(method, values.error, \"error\")\n const outcome = values.outcome === undefined ? undefined : snapshotJson(method, values.outcome, \"outcome\")\n const meta = snapshotJson(method, values.meta, \"meta\")\n if (checkpoint !== undefined && !checkpoint.ok) return Effect.fail(checkpoint.error)\n if (attemptError !== undefined && !attemptError.ok) return Effect.fail(attemptError.error)\n if (outcome !== undefined && !outcome.ok) return Effect.fail(outcome.error)\n if (!meta.ok) return Effect.fail(meta.error)\n const candidate = Object.freeze({\n runId: values.runId,\n stepKeyDigest: values.stepKeyDigest,\n attempt: values.attempt,\n state: values.state,\n startedAtMs: values.startedAtMs,\n ...(values.finishedAtMs === undefined ? {} : { finishedAtMs: values.finishedAtMs }),\n ...(values.heartbeatAtMs === undefined ? {} : { heartbeatAtMs: values.heartbeatAtMs }),\n ...(checkpoint === undefined ? {} : { checkpoint: checkpoint.value }),\n ...(attemptError === undefined ? {} : { error: attemptError.value }),\n ...(outcome === undefined ? {} : { outcome: outcome.value }),\n meta: meta.value\n })\n return Schema.decodeUnknownEffect(Attempt)(candidate).pipe(\n Effect.mapError(() => error(method, \"invalid_attempt\", \"attempt violates the persistence contract\"))\n )\n } catch {\n return Effect.fail(error(method, \"invalid_attempt\", \"attempt must be an inert data record\"))\n }\n })\n\nconst snapshotFinish = (\n method: AttemptStoreMethod,\n input: FinishAttempt\n): Effect.Effect<FinishAttempt, AttemptStoreError> =>\n Effect.suspend(() => {\n try {\n const values = inspectRecord(\n input,\n [\"runId\", \"stepKeyDigest\", \"attempt\", \"state\", \"finishedAtMs\"],\n [\"error\", \"outcome\", \"meta\"],\n \"finished attempt\"\n )\n const attemptError = values.error === undefined ? undefined : snapshotJson(method, values.error, \"error\")\n const outcome = values.outcome === undefined ? undefined : snapshotJson(method, values.outcome, \"outcome\")\n const meta = values.meta === undefined ? undefined : snapshotJson(method, values.meta, \"meta\")\n if (attemptError !== undefined && !attemptError.ok) return Effect.fail(attemptError.error)\n if (outcome !== undefined && !outcome.ok) return Effect.fail(outcome.error)\n if (meta !== undefined && !meta.ok) return Effect.fail(meta.error)\n return Schema.decodeUnknownEffect(FinishAttempt)(Object.freeze({\n runId: values.runId,\n stepKeyDigest: values.stepKeyDigest,\n attempt: values.attempt,\n state: values.state,\n finishedAtMs: values.finishedAtMs,\n ...(attemptError === undefined ? {} : { error: attemptError.value }),\n ...(outcome === undefined ? {} : { outcome: outcome.value }),\n ...(meta === undefined ? {} : { meta: meta.value })\n })).pipe(Effect.mapError(() => error(method, \"invalid_attempt\", \"finished attempt violates the contract\")))\n } catch {\n return Effect.fail(error(method, \"invalid_attempt\", \"finished attempt must be an inert data record\"))\n }\n })\n\nconst snapshotPatch = (\n method: AttemptStoreMethod,\n input: AttemptPatch\n): Effect.Effect<AttemptPatch, AttemptStoreError> =>\n Effect.suspend(() => {\n try {\n const values = inspectRecord(input, [], [\"checkpoint\", \"error\", \"outcome\", \"meta\"], \"attempt patch\")\n const checkpoint = values.checkpoint === undefined\n ? undefined\n : snapshotJson(method, values.checkpoint, \"checkpoint\", maximumCheckpointBytes)\n const attemptError = values.error === undefined ? undefined : snapshotJson(method, values.error, \"error\")\n const outcome = values.outcome === undefined ? undefined : snapshotJson(method, values.outcome, \"outcome\")\n const meta = values.meta === undefined ? undefined : snapshotJson(method, values.meta, \"meta\")\n if (checkpoint !== undefined && !checkpoint.ok) return Effect.fail(checkpoint.error)\n if (attemptError !== undefined && !attemptError.ok) return Effect.fail(attemptError.error)\n if (outcome !== undefined && !outcome.ok) return Effect.fail(outcome.error)\n if (meta !== undefined && !meta.ok) return Effect.fail(meta.error)\n return Effect.succeed(Object.freeze({\n ...(checkpoint === undefined ? {} : { checkpoint: checkpoint.value }),\n ...(attemptError === undefined ? {} : { error: attemptError.value }),\n ...(outcome === undefined ? {} : { outcome: outcome.value }),\n ...(meta === undefined ? {} : { meta: meta.value })\n }))\n } catch {\n return Effect.fail(error(method, \"invalid_attempt\", \"attempt patch must be an inert data record\"))\n }\n })\n\ninterface ResolvedOptions {\n readonly inProgressStates: ReadonlyArray<string>\n readonly maxCheckpointBytes: number\n readonly putMode: \"insert\" | \"upsert\"\n}\n\nconst snapshotOptions = (\n method: AttemptStoreMethod,\n input: Options\n): Effect.Effect<ResolvedOptions, AttemptStoreError> =>\n Effect.suspend(() => {\n try {\n const values = inspectRecord(\n input,\n [],\n [\"inProgressStates\", \"maxCheckpointBytes\", \"putMode\"],\n \"attempt store options\"\n )\n const rawStates = values.inProgressStates ?? defaultInProgressStates\n const admitted = Boundary.admitJson(rawStates, {\n ...jsonLimits(64 * 1024),\n maxDepth: 2,\n maxMembers: 256,\n maxNodes: 257\n })\n if (!admitted.ok || !Array.isArray(admitted.value)) {\n return Effect.fail(error(method, \"invalid_attempt\", \"inProgressStates must be an inert string array\"))\n }\n const states = admitted.value\n if (\n states.length === 0 ||\n states.some((state) => !Boundary.isDurableText(state)) ||\n new Set(states).size !== states.length\n ) {\n return Effect.fail(\n error(method, \"invalid_attempt\", \"inProgressStates must contain unique durable state names\")\n )\n }\n const maxCheckpointBytes = values.maxCheckpointBytes ?? defaultMaxCheckpointBytes\n if (\n typeof maxCheckpointBytes !== \"number\" || !Number.isSafeInteger(maxCheckpointBytes) ||\n maxCheckpointBytes <= 0 ||\n maxCheckpointBytes > maximumCheckpointBytes\n ) {\n return Effect.fail(\n error(method, \"invalid_attempt\", `maxCheckpointBytes must be between 1 and ${maximumCheckpointBytes}`)\n )\n }\n const putMode = values.putMode ?? \"insert\"\n if (putMode !== \"insert\" && putMode !== \"upsert\") {\n return Effect.fail(error(method, \"invalid_attempt\", \"putMode must be insert or upsert\"))\n }\n return Effect.succeed(Object.freeze({\n inProgressStates: Object.freeze([...states]) as ReadonlyArray<string>,\n maxCheckpointBytes,\n putMode\n }))\n } catch {\n return Effect.fail(error(method, \"invalid_attempt\", \"attempt store options must be an inert data record\"))\n }\n })\n\nconst ownsRunningRun = (row: RunFenceRow, owner: OwnerId): boolean =>\n row.status === \"running\" &&\n row.owner_host_id === owner.hostId &&\n row.owner_pid === owner.pid &&\n row.owner_nonce === owner.nonce\n\nconst sameAttempt = (stored: Attempt, expected: Attempt): boolean =>\n stored.state === expected.state &&\n stored.startedAtMs === expected.startedAtMs &&\n stored.finishedAtMs === expected.finishedAtMs &&\n stored.heartbeatAtMs === expected.heartbeatAtMs &&\n Boundary.sameJson(stored.checkpoint, expected.checkpoint) &&\n Boundary.sameJson(stored.error, expected.error) &&\n Boundary.sameJson(stored.outcome, expected.outcome) &&\n Boundary.sameJson(stored.meta, expected.meta)\n\n/**\n * Curried so each operation names itself once at construction and every\n * failure travels through the one mapper, rather than through a per-call-site\n * closure that only that operation's failing test could ever reach.\n */\nconst mapPersistenceError = (method: AttemptStoreMethod) => (cause: unknown): AttemptStoreError => {\n if (Schema.is(AttemptStoreError)(cause)) {\n return cause\n }\n const sqlError = SqlError.isSqlError(cause)\n const databaseError = Schema.is(DatabaseError)(cause)\n const constraint = sqlError\n ? cause.reason instanceof SqlError.ConstraintError || cause.reason instanceof SqlError.UniqueViolation\n : databaseError && cause.code === \"constraint\"\n const code = constraint ? \"constraint\" : \"persistence_failed\"\n // Causes reach logs and telemetry. Driver failures may retain bound attempt\n // payloads, so publish only a stable, payload-free descriptor.\n return attemptStoreError(\n method,\n code,\n \"attempt persistence failed\",\n {\n category: code,\n reason: sqlError ? cause.reason._tag : databaseError ? cause.code : \"unknown\",\n // The outer writer must still recognize a nested savepoint's conflict.\n ...(sqlError || databaseError\n ? { cause: new DatabaseError({ code: sqlError ? fromSqlError(cause).code : cause.code }) }\n : {})\n }\n )\n}\n\nconst decodeRow = (method: AttemptStoreMethod, input: unknown): Effect.Effect<Attempt, AttemptStoreError> =>\n Schema.decodeUnknownEffect(AttemptRow)(input).pipe(\n Effect.mapError(() => error(method, \"decode_failed\", \"could not decode flows_attempts row\")),\n Effect.flatMap((row) =>\n Effect.all({\n checkpoint: decode(method, row.checkpoint_json, \"checkpoint_json\", maximumCheckpointBytes),\n error: decode(method, row.error_json, \"error_json\"),\n outcome: decode(method, row.outcome_json, \"outcome_json\"),\n meta: decodeRequired(method, row.meta_json, \"meta_json\")\n }).pipe(\n Effect.map(({ checkpoint, error: attemptError, outcome, meta }) =>\n Object.freeze({\n runId: row.run_id,\n stepKeyDigest: row.step_key_digest,\n attempt: row.attempt,\n state: row.state,\n startedAtMs: row.started_at_ms,\n ...(row.finished_at_ms === null ? {} : { finishedAtMs: row.finished_at_ms }),\n ...(row.heartbeat_at_ms === null ? {} : { heartbeatAtMs: row.heartbeat_at_ms }),\n ...(checkpoint === undefined ? {} : { checkpoint }),\n ...(attemptError === undefined ? {} : { error: attemptError }),\n ...(outcome === undefined ? {} : { outcome }),\n meta\n })\n )\n )\n )\n )\n\n/**\n * Builds the SQL-backed attempt store under an explicit policy.\n *\n * @category constructors\n * @since 0.1.0\n */\nexport const makeWith = (\n options: Options = {}\n): Effect.Effect<Service, AttemptStoreError, DurableWriter | SqlClient.SqlClient> =>\n Effect.gen(function*() {\n const sql = yield* Effect.service(SqlClient.SqlClient)\n const writer = yield* DurableWriter\n\n const configured = yield* snapshotOptions(\"makeWith\", options)\n const inProgressStates = configured.inProgressStates\n const maxCheckpointBytes = configured.maxCheckpointBytes\n const upsert = configured.putMode === \"upsert\"\n const putEncodeOptional = encodeOptionalWith(\"put\")\n const putEncodeCheckpoint = encodeCheckpointWith(\"put\", maxCheckpointBytes)\n const heartbeatEncodeCheckpoint = encodeCheckpointWith(\"heartbeat\", maxCheckpointBytes)\n const finishEncodeOptional = encodeOptionalWith(\"finish\")\n const patchEncodeOptional = encodeOptionalWith(\"patch\")\n const patchEncodeCheckpoint = encodeCheckpointWith(\"patch\", maxCheckpointBytes)\n const inProgress = sql.in(\"state\", [...inProgressStates])\n\n const put: Service[\"put\"] = Effect.fn(\"AttemptStore.put\")((input, ownerInput) =>\n Effect.gen(function*() {\n const owner = yield* snapshotOwner(\"put\", ownerInput)\n const attempt = yield* snapshotAttempt(\"put\", input)\n yield* Effect.annotateCurrentSpan({\n runId: attempt.runId,\n stepKeyDigest: attempt.stepKeyDigest,\n attempt: attempt.attempt,\n ownerHostId: owner.hostId\n })\n const checkpoint = yield* putEncodeCheckpoint(attempt.checkpoint)\n const attemptError = yield* putEncodeOptional(attempt.error, \"error\")\n const outcome = yield* putEncodeOptional(attempt.outcome, \"outcome\")\n const meta = yield* encode(\"put\", attempt.meta, \"meta\")\n return yield* writer.write(\n Effect.gen(function*() {\n const inserted = yield* sql<{ readonly attempt: number }>`\n INSERT INTO flows_attempts (\n run_id, step_key_digest, attempt, state, started_at_ms, finished_at_ms,\n heartbeat_at_ms, checkpoint_json, error_json, outcome_json, meta_json\n )\n SELECT\n ${attempt.runId}, ${attempt.stepKeyDigest}, ${attempt.attempt}, ${attempt.state},\n ${attempt.startedAtMs}, ${attempt.finishedAtMs ?? null}, ${attempt.heartbeatAtMs ?? null},\n ${checkpoint}, ${attemptError}, ${outcome}, ${meta}\n WHERE EXISTS (\n SELECT 1 FROM flows_runs\n WHERE run_id = ${attempt.runId}\n AND status = 'running'\n AND owner_host_id = ${owner.hostId}\n AND owner_pid = ${owner.pid}\n AND owner_nonce = ${owner.nonce}\n )\n ON CONFLICT (run_id, step_key_digest, attempt) DO NOTHING\n RETURNING attempt\n `\n if (inserted.length > 0) {\n return { _tag: \"Inserted\" } as const\n }\n\n if (upsert) {\n const replaced = yield* sql<{ readonly attempt: number }>`\n UPDATE flows_attempts\n SET\n state = ${attempt.state},\n started_at_ms = ${attempt.startedAtMs},\n finished_at_ms = ${attempt.finishedAtMs ?? null},\n heartbeat_at_ms = ${attempt.heartbeatAtMs ?? null},\n checkpoint_json = ${checkpoint},\n error_json = ${attemptError},\n outcome_json = ${outcome},\n meta_json = ${meta}\n WHERE run_id = ${attempt.runId}\n AND step_key_digest = ${attempt.stepKeyDigest}\n AND attempt = ${attempt.attempt}\n AND ${inProgress}\n AND EXISTS (\n SELECT 1 FROM flows_runs\n WHERE run_id = ${attempt.runId}\n AND status = 'running'\n AND owner_host_id = ${owner.hostId}\n AND owner_pid = ${owner.pid}\n AND owner_nonce = ${owner.nonce}\n )\n RETURNING attempt\n `\n if (replaced.length > 0) {\n return { _tag: \"Upserted\" } as const\n }\n }\n\n const runRows = yield* sql<RunFenceRow>`\n SELECT status, owner_host_id, owner_pid, owner_nonce\n FROM flows_runs WHERE run_id = ${attempt.runId}\n `\n if (runRows.length === 0) {\n return { _tag: \"RunNotFound\" } as const\n }\n if (!ownsRunningRun(runRows[0]!, owner)) {\n return { _tag: \"FenceLost\" } as const\n }\n\n const rows = yield* sql<AttemptRow>`\n SELECT run_id, step_key_digest, attempt, state, started_at_ms, finished_at_ms,\n heartbeat_at_ms, checkpoint_json, error_json, outcome_json, meta_json\n FROM flows_attempts\n WHERE run_id = ${attempt.runId}\n AND step_key_digest = ${attempt.stepKeyDigest}\n AND attempt = ${attempt.attempt}\n `\n /* v8 ignore next -- the owned run and conflicting row are read in the same serialized write transaction */\n if (rows.length === 0) {\n return yield* Effect.fail(error(\"put\", \"unknown\", \"attempt disappeared during put\"))\n }\n const stored = yield* decodeRow(\"put\", rows[0]!)\n return sameAttempt(stored, attempt)\n ? { _tag: \"ExistingSame\" } as const\n : { _tag: \"Conflict\" } as const\n })\n ).pipe(Effect.mapError(mapPersistenceError(\"put\")))\n }).pipe(observeOutcome<PutResult>())\n )\n\n const get: Service[\"get\"] = Effect.fn(\"AttemptStore.get\")((input) =>\n Effect.gen(function*() {\n const id = yield* snapshotId(\"get\", input)\n yield* Effect.annotateCurrentSpan({\n runId: id.runId,\n stepKeyDigest: id.stepKeyDigest,\n attempt: id.attempt\n })\n const rows = yield* sql<Record<string, unknown>>`\n SELECT run_id, step_key_digest, attempt, state, started_at_ms, finished_at_ms,\n heartbeat_at_ms, checkpoint_json, error_json, outcome_json, meta_json\n FROM flows_attempts\n WHERE run_id = ${id.runId} AND step_key_digest = ${id.stepKeyDigest} AND attempt = ${id.attempt}\n `.pipe(Effect.mapError(mapPersistenceError(\"get\")))\n return rows.length === 0 ? Option.none() : yield* Effect.map(decodeRow(\"get\", rows[0]!), Option.some)\n }).pipe(observeExit)\n )\n\n const heartbeat: Service[\"heartbeat\"] = Effect.fn(\"AttemptStore.heartbeat\")((\n runId,\n stepKeyDigest,\n attempt,\n ownerInput,\n nowMs,\n checkpointValue\n ) =>\n Effect.gen(function*() {\n const owner = yield* snapshotOwner(\"heartbeat\", ownerInput)\n yield* validateId(\"heartbeat\", { runId, stepKeyDigest, attempt })\n if (!Number.isSafeInteger(nowMs) || nowMs < 0) {\n return yield* Effect.fail(\n error(\"heartbeat\", \"invalid_attempt\", \"nowMs must be a non-negative safe integer\")\n )\n }\n yield* Effect.annotateCurrentSpan({ runId, stepKeyDigest, attempt, ownerHostId: owner.hostId })\n const checkpoint = yield* heartbeatEncodeCheckpoint(checkpointValue)\n return yield* writer.write(\n Effect.gen(function*() {\n const updated = yield* sql<{ readonly attempt: number }>`\n UPDATE flows_attempts\n SET\n heartbeat_at_ms = ${Dialect.greatest(sql)}(COALESCE(heartbeat_at_ms, ${nowMs}), ${nowMs}),\n checkpoint_json = COALESCE(${checkpoint}, checkpoint_json)\n WHERE run_id = ${runId}\n AND step_key_digest = ${stepKeyDigest}\n AND attempt = ${attempt}\n AND ${inProgress}\n AND EXISTS (\n SELECT 1 FROM flows_runs\n WHERE run_id = ${runId}\n AND status = 'running'\n AND owner_host_id = ${owner.hostId}\n AND owner_pid = ${owner.pid}\n AND owner_nonce = ${owner.nonce}\n )\n RETURNING attempt\n `\n if (updated.length > 0) {\n return { _tag: \"Updated\" } as const\n }\n const found = yield* sql<Pick<AttemptRow, \"state\">>`\n SELECT state FROM flows_attempts\n WHERE run_id = ${runId} AND step_key_digest = ${stepKeyDigest} AND attempt = ${attempt}\n `\n if (found.length === 0) {\n return { _tag: \"NotFound\" } as const\n }\n const runRows = yield* sql<RunFenceRow>`\n SELECT status, owner_host_id, owner_pid, owner_nonce\n FROM flows_runs WHERE run_id = ${runId}\n `\n if (runRows.length === 0 || !ownsRunningRun(runRows[0]!, owner)) {\n return { _tag: \"FenceLost\" } as const\n }\n return { _tag: \"StateChanged\" } as const\n })\n ).pipe(Effect.mapError(mapPersistenceError(\"heartbeat\")))\n }).pipe(observeOutcome<HeartbeatResult>())\n )\n\n const finish: Service[\"finish\"] = Effect.fn(\"AttemptStore.finish\")((input, ownerInput) =>\n Effect.gen(function*() {\n const owner = yield* snapshotOwner(\"finish\", ownerInput)\n const attempt = yield* snapshotFinish(\"finish\", input)\n yield* Effect.annotateCurrentSpan({\n runId: attempt.runId,\n stepKeyDigest: attempt.stepKeyDigest,\n attempt: attempt.attempt,\n ownerHostId: owner.hostId\n })\n if (\n inProgressStates.includes(attempt.state) ||\n !Boundary.isDurableText(attempt.state)\n ) {\n return yield* Effect.fail(\n error(\"finish\", \"invalid_attempt\", \"finish requires a terminal state and valid timestamp\")\n )\n }\n const attemptError = yield* finishEncodeOptional(attempt.error, \"error\")\n const outcome = yield* finishEncodeOptional(attempt.outcome, \"outcome\")\n const meta = yield* finishEncodeOptional(attempt.meta, \"meta\")\n return yield* writer.write(\n Effect.gen(function*() {\n const updated = yield* sql<{ readonly attempt: number }>`\n UPDATE flows_attempts\n SET\n state = ${attempt.state},\n finished_at_ms = ${attempt.finishedAtMs},\n error_json = COALESCE(${attemptError}, error_json),\n outcome_json = COALESCE(${outcome}, outcome_json),\n meta_json = COALESCE(${meta}, meta_json)\n WHERE run_id = ${attempt.runId}\n AND step_key_digest = ${attempt.stepKeyDigest}\n AND attempt = ${attempt.attempt}\n AND ${inProgress}\n AND EXISTS (\n SELECT 1 FROM flows_runs\n WHERE run_id = ${attempt.runId}\n AND status = 'running'\n AND owner_host_id = ${owner.hostId}\n AND owner_pid = ${owner.pid}\n AND owner_nonce = ${owner.nonce}\n )\n RETURNING attempt\n `\n if (updated.length > 0) {\n return { _tag: \"Finished\" } as const\n }\n const found = yield* sql<Pick<AttemptRow, \"state\">>`\n SELECT state FROM flows_attempts\n WHERE run_id = ${attempt.runId}\n AND step_key_digest = ${attempt.stepKeyDigest}\n AND attempt = ${attempt.attempt}\n `\n if (found.length === 0) {\n return { _tag: \"NotFound\" } as const\n }\n const runRows = yield* sql<RunFenceRow>`\n SELECT status, owner_host_id, owner_pid, owner_nonce\n FROM flows_runs WHERE run_id = ${attempt.runId}\n `\n if (runRows.length === 0 || !ownsRunningRun(runRows[0]!, owner)) {\n return { _tag: \"FenceLost\" } as const\n }\n return { _tag: \"StateChanged\" } as const\n })\n ).pipe(Effect.mapError(mapPersistenceError(\"finish\")))\n }).pipe(observeOutcome<FinishResult>())\n )\n\n const patch: Service[\"patch\"] = Effect.fn(\"AttemptStore.patch\")((idInput, patchInput, ownerInput) =>\n Effect.gen(function*() {\n const owner = yield* snapshotOwner(\"patch\", ownerInput)\n const id = yield* snapshotId(\"patch\", idInput)\n const fields = yield* snapshotPatch(\"patch\", patchInput)\n yield* Effect.annotateCurrentSpan({\n runId: id.runId,\n stepKeyDigest: id.stepKeyDigest,\n attempt: id.attempt,\n ownerHostId: owner.hostId\n })\n const checkpoint = yield* patchEncodeCheckpoint(fields.checkpoint)\n const attemptError = yield* patchEncodeOptional(fields.error, \"error\")\n const outcome = yield* patchEncodeOptional(fields.outcome, \"outcome\")\n const meta = yield* patchEncodeOptional(fields.meta, \"meta\")\n return yield* writer.write(\n Effect.gen(function*() {\n // Unlike `heartbeat`/`finish` there is no state predicate: a patch\n // may touch a terminal row (evidence quarantine does), so the run\n // fence is the only gate. After a terminal run transition the\n // owner columns are cleared and every patch is refused.\n const updated = yield* sql<{ readonly attempt: number }>`\n UPDATE flows_attempts\n SET\n checkpoint_json = COALESCE(${checkpoint}, checkpoint_json),\n error_json = COALESCE(${attemptError}, error_json),\n outcome_json = COALESCE(${outcome}, outcome_json),\n meta_json = COALESCE(${meta}, meta_json)\n WHERE run_id = ${id.runId}\n AND step_key_digest = ${id.stepKeyDigest}\n AND attempt = ${id.attempt}\n AND EXISTS (\n SELECT 1 FROM flows_runs\n WHERE run_id = ${id.runId}\n AND status = 'running'\n AND owner_host_id = ${owner.hostId}\n AND owner_pid = ${owner.pid}\n AND owner_nonce = ${owner.nonce}\n )\n RETURNING attempt\n `\n if (updated.length > 0) {\n return { _tag: \"Patched\" } as const\n }\n const found = yield* sql<Pick<AttemptRow, \"state\">>`\n SELECT state FROM flows_attempts\n WHERE run_id = ${id.runId} AND step_key_digest = ${id.stepKeyDigest} AND attempt = ${id.attempt}\n `\n return found.length === 0\n ? { _tag: \"NotFound\" } as const\n : { _tag: \"FenceLost\" } as const\n })\n ).pipe(Effect.mapError(mapPersistenceError(\"patch\")))\n }).pipe(observeOutcome<PatchResult>())\n )\n\n return { put, get, heartbeat, finish, patch }\n })\n\n/**\n * Builds the SQL-backed attempt store with default policy.\n *\n * @category constructors\n * @since 0.1.0\n */\nexport const make: Effect.Effect<Service, never, DurableWriter | SqlClient.SqlClient> = Effect.orDie(makeWith())\n\n/**\n * Creates an attempt store from an implementation.\n *\n * @category constructors\n * @since 0.1.0\n */\nexport const makeNoop = (overrides: Partial<Service> = {}): Service => {\n const unavailable = (method: Exclude<AttemptStoreMethod, \"makeWith\">) =>\n Effect.fail(attemptStoreError(method, \"unknown\", \"the store is unavailable in this environment\"))\n return {\n put: Effect.fn(\"AttemptStore.put\")(() => unavailable(\"put\")),\n get: Effect.fn(\"AttemptStore.get\")(() => unavailable(\"get\")),\n heartbeat: Effect.fn(\"AttemptStore.heartbeat\")(() => unavailable(\"heartbeat\")),\n finish: Effect.fn(\"AttemptStore.finish\")(() => unavailable(\"finish\")),\n patch: Effect.fn(\"AttemptStore.patch\")(() => unavailable(\"patch\")),\n ...overrides\n }\n}\n\n/**\n * Provides a no-op attempt store.\n *\n * @category layers\n * @since 0.1.0\n */\nexport const layerNoop = (overrides: Partial<Service> = {}): Layer.Layer<AttemptStore> =>\n Layer.succeed(AttemptStore)(makeNoop(overrides))\n\n/**\n * Provides the SQL-backed attempt store.\n *\n * @category layers\n * @since 0.1.0\n */\nexport const layer: Layer.Layer<AttemptStore, never, DurableWriter | SqlClient.SqlClient> = Layer.effect(AttemptStore)(\n make\n)\n\n/**\n * Provides the SQL-backed attempt store under an explicit policy.\n *\n * @category layers\n * @since 0.1.0\n */\nexport const layerWith = (\n options: Options\n): Layer.Layer<AttemptStore, AttemptStoreError, DurableWriter | SqlClient.SqlClient> =>\n Layer.effect(AttemptStore)(makeWith(options))\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The heartbeat lease constants, re-exported from the journal's consensus
|
|
3
|
+
* module.
|
|
4
|
+
*
|
|
5
|
+
* A leaf module on purpose. `RunStore` needs the staleness cutoff for its
|
|
6
|
+
* claim predicates and `Ownership` needs all four for the supervision loop,
|
|
7
|
+
* but `Ownership` imports `RunStore`, so neither could own the constants
|
|
8
|
+
* without the other restating them. The definitions themselves live in
|
|
9
|
+
* `@smthrs/journal`'s `Consensus`: every consensus strategy judges R5
|
|
10
|
+
* staleness against the same cutoff this store uses, and one definition is
|
|
11
|
+
* what keeps that structural.
|
|
12
|
+
*
|
|
13
|
+
* @since 0.1.0
|
|
14
|
+
*/
|
|
15
|
+
export {
|
|
16
|
+
/**
|
|
17
|
+
* How often the supervision loop pulses to renew the owner's lease.
|
|
18
|
+
*
|
|
19
|
+
* @since 0.1.0
|
|
20
|
+
* @category constants
|
|
21
|
+
*/
|
|
22
|
+
heartbeatInterval,
|
|
23
|
+
/**
|
|
24
|
+
* How far the owner's wall clock may run behind a peer's before the lease
|
|
25
|
+
* reasoning stops holding.
|
|
26
|
+
*
|
|
27
|
+
* @since 0.1.0
|
|
28
|
+
* @category constants
|
|
29
|
+
*/
|
|
30
|
+
heartbeatSkewAllowance,
|
|
31
|
+
/**
|
|
32
|
+
* How old a persisted heartbeat must be before a peer may steal the run.
|
|
33
|
+
*
|
|
34
|
+
* @since 0.1.0
|
|
35
|
+
* @category constants
|
|
36
|
+
*/
|
|
37
|
+
heartbeatStaleAfter,
|
|
38
|
+
/**
|
|
39
|
+
* How long the owner may keep working through failing or stalled heartbeat
|
|
40
|
+
* writes.
|
|
41
|
+
*
|
|
42
|
+
* @since 0.1.0
|
|
43
|
+
* @category constants
|
|
44
|
+
*/
|
|
45
|
+
heartbeatWriteTolerance } from "@smthrs/journal/Consensus";
|
|
46
|
+
//# sourceMappingURL=Heartbeat.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Heartbeat.d.ts","sourceRoot":"","sources":["../../src/Heartbeat.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO;AACL;;;;;GAKG;AACH,iBAAiB;AACjB;;;;;;GAMG;AACH,sBAAsB;AACtB;;;;;GAKG;AACH,mBAAmB;AACnB;;;;;;GAMG;AACH,uBAAuB,EACxB,MAAM,2BAA2B,CAAA"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var Heartbeat_exports = {};
|
|
20
|
+
__export(Heartbeat_exports, {
|
|
21
|
+
heartbeatInterval: () => import_Consensus.heartbeatInterval,
|
|
22
|
+
heartbeatSkewAllowance: () => import_Consensus.heartbeatSkewAllowance,
|
|
23
|
+
heartbeatStaleAfter: () => import_Consensus.heartbeatStaleAfter,
|
|
24
|
+
heartbeatWriteTolerance: () => import_Consensus.heartbeatWriteTolerance
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(Heartbeat_exports);
|
|
27
|
+
var import_Consensus = require("@smthrs/journal/Consensus");
|
|
28
|
+
//# sourceMappingURL=Heartbeat.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/Heartbeat.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * The heartbeat lease constants, re-exported from the journal's consensus\n * module.\n *\n * A leaf module on purpose. `RunStore` needs the staleness cutoff for its\n * claim predicates and `Ownership` needs all four for the supervision loop,\n * but `Ownership` imports `RunStore`, so neither could own the constants\n * without the other restating them. The definitions themselves live in\n * `@smthrs/journal`'s `Consensus`: every consensus strategy judges R5\n * staleness against the same cutoff this store uses, and one definition is\n * what keeps that structural.\n *\n * @since 0.1.0\n */\n\nexport {\n /**\n * How often the supervision loop pulses to renew the owner's lease.\n *\n * @since 0.1.0\n * @category constants\n */\n heartbeatInterval,\n /**\n * How far the owner's wall clock may run behind a peer's before the lease\n * reasoning stops holding.\n *\n * @since 0.1.0\n * @category constants\n */\n heartbeatSkewAllowance,\n /**\n * How old a persisted heartbeat must be before a peer may steal the run.\n *\n * @since 0.1.0\n * @category constants\n */\n heartbeatStaleAfter,\n /**\n * How long the owner may keep working through failing or stalled heartbeat\n * writes.\n *\n * @since 0.1.0\n * @category constants\n */\n heartbeatWriteTolerance\n} from \"@smthrs/journal/Consensus\"\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAeA,uBA+BO;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Run and attempt schema migrations.
|
|
3
|
+
*
|
|
4
|
+
* This package owns `flows_runs` and `flows_attempts`. It reserves migration
|
|
5
|
+
* id block `1000` so its ids can never collide with the journal's or the step
|
|
6
|
+
* cache's — see `@smthrs/database`'s `Migrations` for how the blocks compose.
|
|
7
|
+
*
|
|
8
|
+
* The SQL `RunStore` arbitrates ownership through `@smthrs/journal`'s
|
|
9
|
+
* `SqlConsensus`, whose lease table the journal's migration set creates, so
|
|
10
|
+
* {@link run} and {@link layer} install the journal's set ahead of this one.
|
|
11
|
+
* {@link set} stays scoped to the run-store tables for compositions that list
|
|
12
|
+
* every set themselves, such as `@smthrs/engine-store/Migrations`.
|
|
13
|
+
*
|
|
14
|
+
* @since 0.1.0
|
|
15
|
+
*/
|
|
16
|
+
import * as DatabaseMigrations from "@smthrs/database/Migrations";
|
|
17
|
+
import * as Layer from "effect/Layer";
|
|
18
|
+
/**
|
|
19
|
+
* The run store's namespaced migration set, for composition with the other
|
|
20
|
+
* storage packages.
|
|
21
|
+
*
|
|
22
|
+
* @category migrations
|
|
23
|
+
* @since 0.1.0
|
|
24
|
+
*/
|
|
25
|
+
export declare const set: DatabaseMigrations.MigrationSet;
|
|
26
|
+
/**
|
|
27
|
+
* Creates the run and attempt schema, and the journal schema whose consensus
|
|
28
|
+
* lease table the SQL `RunStore` fences through.
|
|
29
|
+
*
|
|
30
|
+
* @category migrations
|
|
31
|
+
* @since 0.1.0
|
|
32
|
+
*/
|
|
33
|
+
export declare const run: import("effect/Effect").Effect<readonly (readonly [id: number, name: string])[], import("effect/unstable/sql/SqlError").SqlError | import("effect/unstable/sql/Migrator").MigrationError, import("effect/unstable/sql/SqlClient").SqlClient>;
|
|
34
|
+
/**
|
|
35
|
+
* Layer that runs run-store migrations before exposing the database to the run
|
|
36
|
+
* and attempt services.
|
|
37
|
+
*
|
|
38
|
+
* @category layers
|
|
39
|
+
* @since 0.1.0
|
|
40
|
+
*/
|
|
41
|
+
export declare const layer: Layer.Layer<never, import("effect/unstable/sql/SqlError").SqlError | import("effect/unstable/sql/Migrator").MigrationError, import("effect/unstable/sql/SqlClient").SqlClient>;
|
|
42
|
+
//# sourceMappingURL=Migrations.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Migrations.d.ts","sourceRoot":"","sources":["../../src/Migrations.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,kBAAkB,MAAM,6BAA6B,CAAA;AAEjE,OAAO,KAAK,KAAK,MAAM,cAAc,CAAA;AAMrC;;;;;;GAMG;AACH,eAAO,MAAM,GAAG,EAAE,kBAAkB,CAAC,YASpC,CAAA;AAED;;;;;;GAMG;AACH,eAAO,MAAM,GAAG,8OAAuD,CAAA;AAEvE;;;;;;GAMG;AACH,eAAO,MAAM,KAAK,gLAA2B,CAAA"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
var Migrations_exports = {};
|
|
30
|
+
__export(Migrations_exports, {
|
|
31
|
+
layer: () => layer,
|
|
32
|
+
run: () => run,
|
|
33
|
+
set: () => set
|
|
34
|
+
});
|
|
35
|
+
module.exports = __toCommonJS(Migrations_exports);
|
|
36
|
+
var DatabaseMigrations = __toESM(require("@smthrs/database/Migrations"), 1);
|
|
37
|
+
var JournalMigrations = __toESM(require("@smthrs/journal/Migrations"), 1);
|
|
38
|
+
var Layer = __toESM(require("effect/Layer"), 1);
|
|
39
|
+
var import_initial = require("./migrations/0001_initial.js");
|
|
40
|
+
var import_lineage = require("./migrations/0002_lineage.js");
|
|
41
|
+
var import_execution_revisions = require("./migrations/0003_execution_revisions.js");
|
|
42
|
+
var import_waiting_request = require("./migrations/0004_waiting_request.js");
|
|
43
|
+
const set = {
|
|
44
|
+
namespace: "run-store",
|
|
45
|
+
idOffset: DatabaseMigrations.idBlock,
|
|
46
|
+
migrations: {
|
|
47
|
+
"0001_initial": import_initial.initial,
|
|
48
|
+
"0002_lineage": import_lineage.lineage,
|
|
49
|
+
"0003_execution_revisions": import_execution_revisions.executionRevisions,
|
|
50
|
+
"0004_waiting_request": import_waiting_request.waitingRequest
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
const run = DatabaseMigrations.run([JournalMigrations.set, set]);
|
|
54
|
+
const layer = Layer.effectDiscard(run);
|
|
55
|
+
//# sourceMappingURL=Migrations.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/Migrations.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Run and attempt schema migrations.\n *\n * This package owns `flows_runs` and `flows_attempts`. It reserves migration\n * id block `1000` so its ids can never collide with the journal's or the step\n * cache's \u2014 see `@smthrs/database`'s `Migrations` for how the blocks compose.\n *\n * The SQL `RunStore` arbitrates ownership through `@smthrs/journal`'s\n * `SqlConsensus`, whose lease table the journal's migration set creates, so\n * {@link run} and {@link layer} install the journal's set ahead of this one.\n * {@link set} stays scoped to the run-store tables for compositions that list\n * every set themselves, such as `@smthrs/engine-store/Migrations`.\n *\n * @since 0.1.0\n */\n\nimport * as DatabaseMigrations from \"@smthrs/database/Migrations\"\nimport * as JournalMigrations from \"@smthrs/journal/Migrations\"\nimport * as Layer from \"effect/Layer\"\nimport { initial } from \"./migrations/0001_initial.ts\"\nimport { lineage } from \"./migrations/0002_lineage.ts\"\nimport { executionRevisions } from \"./migrations/0003_execution_revisions.ts\"\nimport { waitingRequest } from \"./migrations/0004_waiting_request.ts\"\n\n/**\n * The run store's namespaced migration set, for composition with the other\n * storage packages.\n *\n * @category migrations\n * @since 0.1.0\n */\nexport const set: DatabaseMigrations.MigrationSet = {\n namespace: \"run-store\",\n idOffset: DatabaseMigrations.idBlock,\n migrations: {\n \"0001_initial\": initial,\n \"0002_lineage\": lineage,\n \"0003_execution_revisions\": executionRevisions,\n \"0004_waiting_request\": waitingRequest\n }\n}\n\n/**\n * Creates the run and attempt schema, and the journal schema whose consensus\n * lease table the SQL `RunStore` fences through.\n *\n * @category migrations\n * @since 0.1.0\n */\nexport const run = DatabaseMigrations.run([JournalMigrations.set, set])\n\n/**\n * Layer that runs run-store migrations before exposing the database to the run\n * and attempt services.\n *\n * @category layers\n * @since 0.1.0\n */\nexport const layer = Layer.effectDiscard(run)\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAgBA,yBAAoC;AACpC,wBAAmC;AACnC,YAAuB;AACvB,qBAAwB;AACxB,qBAAwB;AACxB,iCAAmC;AACnC,6BAA+B;AASxB,MAAM,MAAuC;AAAA,EAClD,WAAW;AAAA,EACX,UAAU,mBAAmB;AAAA,EAC7B,YAAY;AAAA,IACV,gBAAgB;AAAA,IAChB,gBAAgB;AAAA,IAChB,4BAA4B;AAAA,IAC5B,wBAAwB;AAAA,EAC1B;AACF;AASO,MAAM,MAAM,mBAAmB,IAAI,CAAC,kBAAkB,KAAK,GAAG,CAAC;AAS/D,MAAM,QAAQ,MAAM,cAAc,GAAG;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Run ownership arbitration: liveness evidence, probes, and heartbeat
|
|
3
|
+
* supervision.
|
|
4
|
+
*
|
|
5
|
+
* The identity being arbitrated — {@link OwnerId} — is defined by
|
|
6
|
+
* `@smthrs/journal`, because it is the fencing token the journal accepts on
|
|
7
|
+
* durable appends. It is re-exported here so ownership callers keep reading it
|
|
8
|
+
* as one vocabulary.
|
|
9
|
+
*
|
|
10
|
+
* @since 0.1.0
|
|
11
|
+
*/
|
|
12
|
+
import { LivenessEvidence } from "@smthrs/journal/Consensus";
|
|
13
|
+
import { OwnerId } from "@smthrs/journal/OwnerId";
|
|
14
|
+
import { Duration, Effect } from "effect";
|
|
15
|
+
import { RunStore } from "./RunStore.ts";
|
|
16
|
+
export {
|
|
17
|
+
/**
|
|
18
|
+
* A process identity scoped to a host and a unique ownership nonce, defined
|
|
19
|
+
* by `@smthrs/journal` as the fence on durable appends.
|
|
20
|
+
*
|
|
21
|
+
* @since 0.1.0
|
|
22
|
+
* @category models
|
|
23
|
+
*/
|
|
24
|
+
OwnerId };
|
|
25
|
+
export {
|
|
26
|
+
/**
|
|
27
|
+
* Evidence that the owner in an exact run snapshot is no longer live.
|
|
28
|
+
*
|
|
29
|
+
* Defined by `@smthrs/journal`'s `Consensus`, because R5 — steal requires
|
|
30
|
+
* staleness plus liveness evidence — is a consensus rule every strategy
|
|
31
|
+
* validates. Two of the three kinds are collected outside the store:
|
|
32
|
+
* `same-host-pid-dead` is a local process probe, and
|
|
33
|
+
* `cross-host-unreachable-stale` is a reachability judgement the deployment
|
|
34
|
+
* makes. `lease-expired` is different — it asserts only that the persisted
|
|
35
|
+
* heartbeat is older than the staleness cutoff, which is the one claim the
|
|
36
|
+
* strategy can check for itself, and `steal` checks it: the takeover
|
|
37
|
+
* refuses any lease whose heartbeat is still inside the window. It is
|
|
38
|
+
* therefore accepted from a claimant on any host, while the other two stay
|
|
39
|
+
* bound to the host relation that makes them meaningful. The observation
|
|
40
|
+
* must carry the exact instant supplied to the consuming operation:
|
|
41
|
+
* `checkedAtMs` must equal that operation's `nowMs`. A caller that probes
|
|
42
|
+
* at T and calls at T+1 is refused and must build fresh evidence.
|
|
43
|
+
*
|
|
44
|
+
* @since 0.1.0
|
|
45
|
+
* @category models
|
|
46
|
+
*/
|
|
47
|
+
LivenessEvidence };
|
|
48
|
+
/**
|
|
49
|
+
* Evidence-factory signature a composition uses for ownership arbitration.
|
|
50
|
+
*
|
|
51
|
+
* A `LivenessProbe` returns `LivenessEvidence` or `undefined` for `steal`,
|
|
52
|
+
* `claimAndOwn`, and `recoverClaim` to verify. {@link LivenessCheck} is the
|
|
53
|
+
* separate alive-or-not question the engine asks before it decides to steal.
|
|
54
|
+
* `RunStore` validates supplied evidence and never probes a process or network
|
|
55
|
+
* itself.
|
|
56
|
+
*
|
|
57
|
+
* @since 0.1.0
|
|
58
|
+
* @category models
|
|
59
|
+
*/
|
|
60
|
+
export type LivenessProbe<E = never, R = never> = (expectedOwner: OwnerId, claimant: OwnerId, checkedAtMs: number) => Effect.Effect<LivenessEvidence | undefined, E, R>;
|
|
61
|
+
/**
|
|
62
|
+
* What a liveness check knows about the run it is asked about, beyond the
|
|
63
|
+
* owner recorded on it.
|
|
64
|
+
*
|
|
65
|
+
* The lease is here because it is the only liveness signal every deployment
|
|
66
|
+
* has: `heartbeatAtMs` is the last heartbeat the owner persisted, `nowMs` is
|
|
67
|
+
* the reading the arbitration is made against, and `claimant` is the identity
|
|
68
|
+
* that would take the run over. A check that wants to probe a pid compares
|
|
69
|
+
* hosts with {@link sameHostIncarnation} first; a check that has nothing but
|
|
70
|
+
* the lease uses {@link leaseLiveness}.
|
|
71
|
+
*
|
|
72
|
+
* @since 0.1.0
|
|
73
|
+
* @category models
|
|
74
|
+
*/
|
|
75
|
+
export interface LivenessContext {
|
|
76
|
+
readonly claimant: OwnerId;
|
|
77
|
+
readonly heartbeatAtMs: number | null;
|
|
78
|
+
readonly nowMs: number;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* The question ownership arbitration asks before it steals a run: is the
|
|
82
|
+
* recorded owner still working?
|
|
83
|
+
*
|
|
84
|
+
* Answering `true` refuses the takeover. The engine consults this only for a
|
|
85
|
+
* run whose lease has already expired, so a check that has no better evidence
|
|
86
|
+
* than the lease answers `false` there — which is exactly what
|
|
87
|
+
* {@link leaseLiveness} does.
|
|
88
|
+
*
|
|
89
|
+
* @since 0.1.0
|
|
90
|
+
* @category models
|
|
91
|
+
*/
|
|
92
|
+
export type LivenessCheck = (expectedOwner: OwnerId, context: LivenessContext) => Effect.Effect<boolean>;
|
|
93
|
+
/**
|
|
94
|
+
* Whether two owner identities are incarnations on the same host.
|
|
95
|
+
*
|
|
96
|
+
* The predicate a probe applies before it inspects a pid: `owner.pid` names a
|
|
97
|
+
* process in the claimant's own process namespace only when the hosts match,
|
|
98
|
+
* so a cross-host probe that read it would be answering about an unrelated
|
|
99
|
+
* process. Exposed because the check that needs it — a
|
|
100
|
+
* `process.kill(pid, 0)` probe — belongs to a platform package rather than
|
|
101
|
+
* here.
|
|
102
|
+
*
|
|
103
|
+
* @since 0.1.0
|
|
104
|
+
* @category ownership
|
|
105
|
+
*/
|
|
106
|
+
export declare const sameHostIncarnation: (expectedOwner: OwnerId, claimant: OwnerId) => boolean;
|
|
107
|
+
/**
|
|
108
|
+
* The default liveness check: the lease, and nothing else.
|
|
109
|
+
*
|
|
110
|
+
* An owner is treated as alive for as long as its persisted heartbeat is
|
|
111
|
+
* younger than `staleAfter`, and as gone once it is not. That is the weakest
|
|
112
|
+
* honest answer, and it is the one every host can give: a fresh process with
|
|
113
|
+
* no application code at all can reclaim a hard-killed owner's runs once the
|
|
114
|
+
* lease it stopped renewing has expired. A deployment that can say more — a
|
|
115
|
+
* pid probe on the owner's host, an orchestrator that reports pod liveness —
|
|
116
|
+
* supplies its own {@link LivenessCheck} and refuses the takeover for longer.
|
|
117
|
+
*
|
|
118
|
+
* An owner with no recorded heartbeat holds no lease and is reported gone; the
|
|
119
|
+
* steal it enables is still gated by the store's own snapshot compare-and-swap.
|
|
120
|
+
*
|
|
121
|
+
* @since 0.1.0
|
|
122
|
+
* @category ownership
|
|
123
|
+
*/
|
|
124
|
+
export declare const leaseLiveness: (staleAfter?: Duration.Input) => LivenessCheck;
|
|
125
|
+
/**
|
|
126
|
+
* The Node hosts' liveness check: does the owner's process still exist?
|
|
127
|
+
*
|
|
128
|
+
* {@link leaseLiveness} is the honest floor — every host can read a persisted
|
|
129
|
+
* heartbeat — but it is only a timeout, so two engine processes over one
|
|
130
|
+
* database steal each other's running rows `heartbeatStaleAfter` after any
|
|
131
|
+
* heartbeat stall: a stop-the-world pause, a swapped-out process, a disk that
|
|
132
|
+
* blocked longer than the window. This answers the question the lease is
|
|
133
|
+
* standing in for, by asking the operating system whether the recorded pid is
|
|
134
|
+
* still there.
|
|
135
|
+
*
|
|
136
|
+
* `process.kill(pid, 0)` sends no signal; it performs only the delivery
|
|
137
|
+
* checks. Three answers matter:
|
|
138
|
+
*
|
|
139
|
+
* - It returns: the process exists and is signalable. The owner is alive.
|
|
140
|
+
* - It throws `ESRCH`: no such process exists. This is the sole death answer,
|
|
141
|
+
* so the owner is gone.
|
|
142
|
+
* - It throws anything else, including `EPERM`, `EINVAL`, or an unrecognized
|
|
143
|
+
* value: the owner is treated as alive because an unknown answer is not
|
|
144
|
+
* death.
|
|
145
|
+
*
|
|
146
|
+
* This `ESRCH`-only death rule deliberately aligns with
|
|
147
|
+
* `@smthrs/platform-node`'s `HostLiveness`.
|
|
148
|
+
*
|
|
149
|
+
* A pid is only meaningful inside one process namespace, so a recorded owner
|
|
150
|
+
* on another host is never probed: the answer is `false` and the arbitration
|
|
151
|
+
* falls back to the evidence that does cross hosts — the expired lease, which
|
|
152
|
+
* `RunStore.steal` verifies for itself. The engine consults this check only
|
|
153
|
+
* for a run whose lease has ALREADY expired, so answering `false` here does
|
|
154
|
+
* not weaken anything; it declines to add evidence.
|
|
155
|
+
*
|
|
156
|
+
* Node hosts only. It is not part of the browser promise: this entry point
|
|
157
|
+
* bundles for the browser because it never imports a `node:` built-in, and a
|
|
158
|
+
* browser composition has no process table to ask, so it keeps
|
|
159
|
+
* {@link leaseLiveness}.
|
|
160
|
+
*
|
|
161
|
+
* `@smthrs/platform-node`'s `HostLiveness.isAlive` asks the same question of
|
|
162
|
+
* the same process table and differs in one deliberate place: it answers
|
|
163
|
+
* `true` for an owner on another host, which refuses the steal outright, while
|
|
164
|
+
* this check answers `false` and lets the expired lease decide. The difference
|
|
165
|
+
* matters after a host dies for good — under the refusing answer its runs are
|
|
166
|
+
* never reclaimed by anyone, because no other machine can ever produce
|
|
167
|
+
* evidence about its pids.
|
|
168
|
+
*
|
|
169
|
+
* Two limits are inherent to asking a pid, and they bound what reclaim can
|
|
170
|
+
* promise. Both are shared with `HostLiveness.isAlive`.
|
|
171
|
+
*
|
|
172
|
+
* - An owner recorded with the CLAIMANT'S OWN pid is always alive. A previous
|
|
173
|
+
* incarnation of this process, or a second engine composed inside it, differs
|
|
174
|
+
* from the claimant only by `nonce`, and the process it names is this one.
|
|
175
|
+
* Such a row is never stolen while the process lives, so an embedded host
|
|
176
|
+
* that re-creates its engine in place should keep {@link leaseLiveness},
|
|
177
|
+
* whose timeout does expire. Reading same-pid-different-nonce as death is
|
|
178
|
+
* not the alternative: it would let two engines in one process — the exact
|
|
179
|
+
* shape this check exists to arbitrate — steal each other's live runs.
|
|
180
|
+
* - A pid the operating system has REUSED reports the unrelated process that
|
|
181
|
+
* now holds it. The dead owner's row stays refused for as long as that
|
|
182
|
+
* process lives, which delays reclaim rather than breaking it: the row is
|
|
183
|
+
* still `running` under an expired lease, and the next probe after the pid
|
|
184
|
+
* is free reclaims it.
|
|
185
|
+
*
|
|
186
|
+
* @since 0.1.0
|
|
187
|
+
* @category ownership
|
|
188
|
+
*/
|
|
189
|
+
export declare const sameHostPidProbe: LivenessCheck;
|
|
190
|
+
export {
|
|
191
|
+
/**
|
|
192
|
+
* How often the supervision loop pulses to renew the owner's lease.
|
|
193
|
+
*
|
|
194
|
+
* @since 0.1.0
|
|
195
|
+
* @category constants
|
|
196
|
+
*/
|
|
197
|
+
heartbeatInterval,
|
|
198
|
+
/**
|
|
199
|
+
* How far the owner's wall clock may run behind a peer's before the lease
|
|
200
|
+
* reasoning stops holding.
|
|
201
|
+
*
|
|
202
|
+
* @since 0.1.0
|
|
203
|
+
* @category constants
|
|
204
|
+
*/
|
|
205
|
+
heartbeatSkewAllowance,
|
|
206
|
+
/**
|
|
207
|
+
* How old a persisted heartbeat must be before a peer may steal the run.
|
|
208
|
+
*
|
|
209
|
+
* @since 0.1.0
|
|
210
|
+
* @category constants
|
|
211
|
+
*/
|
|
212
|
+
heartbeatStaleAfter,
|
|
213
|
+
/**
|
|
214
|
+
* How long the owner may keep working through failing or stalled heartbeat writes.
|
|
215
|
+
*
|
|
216
|
+
* @since 0.1.0
|
|
217
|
+
* @category constants
|
|
218
|
+
*/
|
|
219
|
+
heartbeatWriteTolerance } from "./Heartbeat.ts";
|
|
220
|
+
/**
|
|
221
|
+
* Hooks a {@link heartbeatLoop} caller may supply.
|
|
222
|
+
*
|
|
223
|
+
* @since 1.0.0
|
|
224
|
+
* @category models
|
|
225
|
+
*/
|
|
226
|
+
export interface HeartbeatLoopOptions {
|
|
227
|
+
/** Called after the same unclaimed owner reconfirms an expired heartbeat budget. */
|
|
228
|
+
readonly onReconfirm?: ((unconfirmedMs: number) => Effect.Effect<void>) | undefined;
|
|
229
|
+
/** Called with the unconfirmed milliseconds when the lease lapses, before the loop interrupts itself. */
|
|
230
|
+
readonly onLapse?: ((unconfirmedMs: number) => Effect.Effect<void>) | undefined;
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Runs heartbeats until the persisted ownership fence is lost, then interrupts
|
|
234
|
+
* itself. Race this effect with owned work so structured concurrency
|
|
235
|
+
* interrupts the work when ownership disappears.
|
|
236
|
+
*
|
|
237
|
+
* Each pulse drives the injected `Consensus` strategy's `heartbeat` through
|
|
238
|
+
* `RunStore.heartbeat`, which renews the strategy's lease and mirrors the
|
|
239
|
+
* recorded stamp onto the run row in the same transaction. Heartbeats are
|
|
240
|
+
* lease evidence, never journal events.
|
|
241
|
+
*
|
|
242
|
+
* Pulses are delayed by `heartbeatInterval` and read the Effect `Clock`, so the
|
|
243
|
+
* loop is fully driveable with `TestClock`.
|
|
244
|
+
*
|
|
245
|
+
* A lost fence — any outcome other than `Updated` — is durable evidence and
|
|
246
|
+
* interrupts immediately. A failed heartbeat *write* is not: the persisted
|
|
247
|
+
* heartbeat is still there and no other process may steal the run until it is
|
|
248
|
+
* `heartbeatStaleAfter` old, so transient write errors are tolerated for
|
|
249
|
+
* `heartbeatWriteTolerance` — deliberately shorter than the steal cutoff by a
|
|
250
|
+
* pulse plus `heartbeatSkewAllowance`. The reserved pulse bounds a final
|
|
251
|
+
* reconfirmation; a pending claim or changed owner fails that check. Past that allowance
|
|
252
|
+
* the fence still protects durable writes but non-durable side effects may
|
|
253
|
+
* overlap; see {@link heartbeatWriteTolerance}.
|
|
254
|
+
*
|
|
255
|
+
* An independent deadline races the pulse loop. At expiry it reconfirms the
|
|
256
|
+
* same owner with no pending claim, bounded by one heartbeat interval. A
|
|
257
|
+
* renewed lease continues supervision; loss, error, or timeout interrupts
|
|
258
|
+
* even when an ordinary heartbeat write never returns. Each successful
|
|
259
|
+
* pulse re-arms the deadline from the timestamp sent to the store, not from
|
|
260
|
+
* its completion time. The deadline re-reads the clock after waking, so delayed
|
|
261
|
+
* writes cannot hide expiry behind a stale clock reading.
|
|
262
|
+
*
|
|
263
|
+
* `options.onReconfirm` records a successful reconfirmation after a gap. Its
|
|
264
|
+
* deadline is the remaining renewed lease budget, measured from the timestamp
|
|
265
|
+
* sent to the store. A blocked or failed receipt lapses the lease instead of
|
|
266
|
+
* stranding supervision; receipt completion never grants another lease budget.
|
|
267
|
+
* `options.onLapse` runs once, before the self-interrupt, when reconfirmation
|
|
268
|
+
* fails: it receives how long the lease went unconfirmed, so the caller can
|
|
269
|
+
* record why its work stopped. A lost fence does not call it.
|
|
270
|
+
*
|
|
271
|
+
* @since 0.1.0
|
|
272
|
+
* @category supervision
|
|
273
|
+
*/
|
|
274
|
+
export declare const heartbeatLoop: (runId: string, owner: OwnerId, options?: HeartbeatLoopOptions) => Effect.Effect<never, never, RunStore>;
|
|
275
|
+
//# sourceMappingURL=Ownership.d.ts.map
|