@smthrs/engine-store 1.0.0-rc.6 → 1.0.0-rc.8

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 (50) hide show
  1. package/README.md +13 -11
  2. package/dist/cjs/ArtifactSync.d.ts.map +1 -1
  3. package/dist/cjs/ArtifactSync.js +5 -0
  4. package/dist/cjs/ArtifactSync.js.map +2 -2
  5. package/dist/cjs/DisasterRecovery.d.ts +9 -26
  6. package/dist/cjs/DisasterRecovery.d.ts.map +1 -1
  7. package/dist/cjs/DisasterRecovery.js +27 -18
  8. package/dist/cjs/DisasterRecovery.js.map +2 -2
  9. package/dist/cjs/StepBoundary.d.ts +8 -6
  10. package/dist/cjs/StepBoundary.d.ts.map +1 -1
  11. package/dist/cjs/StepBoundary.js +10 -7
  12. package/dist/cjs/StepBoundary.js.map +2 -2
  13. package/dist/cjs/index.d.ts +12 -0
  14. package/dist/cjs/index.d.ts.map +1 -1
  15. package/dist/cjs/index.js.map +2 -2
  16. package/dist/cjs/internal/ActionPersistence.d.ts.map +1 -1
  17. package/dist/cjs/internal/ActionPersistence.js +27 -2
  18. package/dist/cjs/internal/ActionPersistence.js.map +3 -3
  19. package/dist/esm/ArtifactSync.d.ts.map +1 -1
  20. package/dist/esm/ArtifactSync.js +6 -1
  21. package/dist/esm/ArtifactSync.js.map +1 -1
  22. package/dist/esm/DisasterRecovery.d.ts +9 -26
  23. package/dist/esm/DisasterRecovery.d.ts.map +1 -1
  24. package/dist/esm/DisasterRecovery.js +46 -27
  25. package/dist/esm/DisasterRecovery.js.map +1 -1
  26. package/dist/esm/StepBoundary.d.ts +8 -6
  27. package/dist/esm/StepBoundary.d.ts.map +1 -1
  28. package/dist/esm/StepBoundary.js +17 -8
  29. package/dist/esm/StepBoundary.js.map +1 -1
  30. package/dist/esm/index.d.ts +12 -0
  31. package/dist/esm/index.d.ts.map +1 -1
  32. package/dist/esm/index.js +12 -0
  33. package/dist/esm/index.js.map +1 -1
  34. package/dist/esm/internal/ActionPersistence.d.ts.map +1 -1
  35. package/dist/esm/internal/ActionPersistence.js +59 -3
  36. package/dist/esm/internal/ActionPersistence.js.map +1 -1
  37. package/docs/README.md +3 -1
  38. package/docs/api.md +23 -17
  39. package/docs/concepts/attempts-and-replay.md +15 -2
  40. package/docs/guides/back-up-and-restore.md +9 -3
  41. package/docs/guides/reclaim-runs-from-a-dead-host.md +16 -0
  42. package/docs/installation.md +5 -1
  43. package/docs/quickstart.md +9 -1
  44. package/docs/troubleshooting.md +3 -0
  45. package/package.json +18 -18
  46. package/src/ArtifactSync.ts +7 -0
  47. package/src/DisasterRecovery.ts +90 -58
  48. package/src/StepBoundary.ts +29 -14
  49. package/src/index.ts +14 -0
  50. package/src/internal/ActionPersistence.ts +64 -3
@@ -1 +1 @@
1
- {"version":3,"file":"ArtifactSync.d.ts","sourceRoot":"","sources":["../../src/ArtifactSync.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,OAAO,KAAK,aAAa,MAAM,iCAAiC,CAAA;AAChE,OAAO,KAAK,eAAe,MAAM,mCAAmC,CAAA;AACpE,OAAO,KAAK,OAAO,MAAM,gBAAgB,CAAA;AACzC,OAAO,KAAK,KAAK,MAAM,MAAM,eAAe,CAAA;AAC5C,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AACvC,OAAO,KAAK,KAAK,MAAM,cAAc,CAAA;AACrC,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;;;;;;;AAEvC;;;;;;;;;GASG;AACH,qBAAa,yBAA0B,SAAQ,8BAc9C;CAAG;AAEJ;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,MAAM,cAAc,GAAG,eAAe,CAAC,cAAc,CAAA;AAE3D;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,OAAO;IACtB;;;;OAIG;IACH,QAAQ,CAAC,OAAO,EAAE,CAChB,OAAO,EAAE,aAAa,CAAC,MAAM,CAAC,KAC3B,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,yBAAyB,EAAE,MAAM,CAAC,MAAM,CAAC,CAAA;IAClE;;;;;;;;OAQG;IACH,QAAQ,CAAC,OAAO,EAAE,CAChB,OAAO,EAAE,aAAa,CAAC,MAAM,CAAC,KAC3B,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,CAAA;CAClD;;AAED;;;;;GAKG;AACH,qBAAa,YAAa,SAAQ,iBAA6E;CAAG;AAElH;;;;;;;;;;GAUG;AACH,eAAO,MAAM,SAAS,QAAO,OAG3B,CAAA;AAEF;;;;;GAKG;AACH,eAAO,MAAM,UAAU,EAAE,KAAK,CAAC,KAAK,CAAC,YAAY,CAA4C,CAAA;AAE7F;;;;;GAKG;AACH,eAAO,MAAM,IAAI,GAAI,SAAS;IAC5B,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC,OAAO,CAAA;IACrC,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC,OAAO,CAAA;IACtC;;;OAGG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,cAAc,GAAG,SAAS,CAAA;CACrD,KAAG,OAmFH,CAAA;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,KAAK,GAAI,CAAC,EAAE,CAAC,EACxB,QAAQ,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC,CAAC,EAClD,UAAU;IAAE,QAAQ,CAAC,cAAc,CAAC,EAAE,cAAc,GAAG,SAAS,CAAA;CAAE,GAAG,SAAS,KAC7E,KAAK,CAAC,KAAK,CAAC,YAAY,EAAE,CAAC,EAAE,aAAa,CAAC,aAAa,GAAG,CAAC,CAW5D,CAAA"}
1
+ {"version":3,"file":"ArtifactSync.d.ts","sourceRoot":"","sources":["../../src/ArtifactSync.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,OAAO,KAAK,aAAa,MAAM,iCAAiC,CAAA;AAChE,OAAO,KAAK,eAAe,MAAM,mCAAmC,CAAA;AACpE,OAAO,KAAK,OAAO,MAAM,gBAAgB,CAAA;AACzC,OAAO,KAAK,KAAK,MAAM,MAAM,eAAe,CAAA;AAC5C,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AACvC,OAAO,KAAK,KAAK,MAAM,cAAc,CAAA;AACrC,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;;;;;;;AAEvC;;;;;;;;;GASG;AACH,qBAAa,yBAA0B,SAAQ,8BAc9C;CAAG;AAEJ;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,MAAM,cAAc,GAAG,eAAe,CAAC,cAAc,CAAA;AAE3D;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,OAAO;IACtB;;;;OAIG;IACH,QAAQ,CAAC,OAAO,EAAE,CAChB,OAAO,EAAE,aAAa,CAAC,MAAM,CAAC,KAC3B,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,yBAAyB,EAAE,MAAM,CAAC,MAAM,CAAC,CAAA;IAClE;;;;;;;;OAQG;IACH,QAAQ,CAAC,OAAO,EAAE,CAChB,OAAO,EAAE,aAAa,CAAC,MAAM,CAAC,KAC3B,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,CAAA;CAClD;;AAED;;;;;GAKG;AACH,qBAAa,YAAa,SAAQ,iBAA6E;CAAG;AAElH;;;;;;;;;;GAUG;AACH,eAAO,MAAM,SAAS,QAAO,OAG3B,CAAA;AAEF;;;;;GAKG;AACH,eAAO,MAAM,UAAU,EAAE,KAAK,CAAC,KAAK,CAAC,YAAY,CAA4C,CAAA;AAE7F;;;;;GAKG;AACH,eAAO,MAAM,IAAI,GAAI,SAAS;IAC5B,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC,OAAO,CAAA;IACrC,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC,OAAO,CAAA;IACtC;;;OAGG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,cAAc,GAAG,SAAS,CAAA;CACrD,KAAG,OA0FH,CAAA;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,KAAK,GAAI,CAAC,EAAE,CAAC,EACxB,QAAQ,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC,CAAC,EAClD,UAAU;IAAE,QAAQ,CAAC,cAAc,CAAC,EAAE,cAAc,GAAG,SAAS,CAAA;CAAE,GAAG,SAAS,KAC7E,KAAK,CAAC,KAAK,CAAC,YAAY,EAAE,CAAC,EAAE,aAAa,CAAC,aAAa,GAAG,CAAC,CAW5D,CAAA"}
@@ -146,7 +146,12 @@ export const make = (options) => {
146
146
  // read is digest-verified by `RemoteArtifacts`, and the local `put`
147
147
  // re-addresses the bytes, so a tier serving wrong content cannot get
148
148
  // them written into this workspace.
149
- const written = yield* remote.get(digest).pipe(Effect.flatMap((bytes) => local.put(bytes)), Effect.tapError(fallBack("the artifact could not be fetched", [digest])), Effect.option);
149
+ const written = yield* remote.get(digest).pipe(Effect.flatMap((bytes) => local.put(bytes)),
150
+ // A custom tier may skip that verification; the address `put`
151
+ // returned is the proof the requested blob is now local.
152
+ Effect.flatMap((address) => address === digest ?
153
+ Effect.void :
154
+ Effect.fail({ message: `the shared tier served the bytes of ${address}` })), Effect.tapError(fallBack("the artifact could not be fetched", [digest])), Effect.option);
150
155
  if (written._tag === "None")
151
156
  return false;
152
157
  }
@@ -1 +1 @@
1
- {"version":3,"file":"ArtifactSync.js","sourceRoot":"","sources":["../../src/ArtifactSync.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The two-tier artifact protocol the step cache rides on: publish before the\n * entry, fetch on demand after it.\n *\n * This is the seam that makes a shared cache safe. A cache entry references\n * its large outputs by digest, so an entry that becomes observable while one\n * of those artifacts is still missing hands the next machine a hit it cannot\n * materialize. Bazel states the constraint at\n * `reference/bazel/.../remote/UploadManifest.java:630-633` — \"action results\n * may fail to validate server-side if they are accessed before all blobs they\n * refer to are present\" — and enforces it by ordering:\n * `findMissingDigests` → upload the missing → upload the `ActionResult` LAST.\n * {@link Service.publish} is that ordering, and `ActionPersistence` calls it\n * before `CacheStore.put`.\n *\n * The read direction is the same protocol in reverse, and how eagerly it runs\n * is the {@link DownloadPolicy}, declared on the shared tier itself\n * (`RemoteArtifacts.Options.downloadPolicy`) and split across two seams.\n * `all` fetches every referenced artifact into this host's store while\n * admitting the replay. `toplevel` and `minimal` fetch nothing here: one\n * batched probe establishes that the shared tier can serve them and the\n * transfer is left to the first read through `CombinedArtifacts`, which then\n * writes the blob back under `toplevel` and does not under `minimal`. This is\n * Bazel's `RemoteOutputChecker` dial\n * (`--remote_download_{all,toplevel,minimal}`) split the way our two seams\n * split: what a replay prefetches is this module's decision, what a read\n * materializes is the combined store's.\n *\n * Governing design: `packages/smithers/flows/artifacts/docs/api.md`.\n *\n * @since 0.1.0\n */\n\nimport * as ArtifactStore from \"@smthrs/artifacts/ArtifactStore\"\nimport * as RemoteArtifacts from \"@smthrs/artifacts/RemoteArtifacts\"\nimport * as Context from \"effect/Context\"\nimport type * as Crypto from \"effect/Crypto\"\nimport * as Effect from \"effect/Effect\"\nimport * as Layer from \"effect/Layer\"\nimport * as Schema from \"effect/Schema\"\n\n/**\n * A referenced artifact could not be made durable in the shared tier, so the\n * cache entry that references it must not be published.\n *\n * Failing here is the point: silently publishing the entry anyway is exactly\n * the state the REAPI ordering constraint exists to prevent.\n *\n * @since 0.1.0\n * @category errors\n */\nexport class ArtifactPublicationFailed extends Schema.TaggedError<ArtifactPublicationFailed>()(\n \"@smthrs/engine-store/ArtifactPublicationFailed\",\n {\n code: Schema.Literal(\"artifact_publication_failed\"),\n digests: Schema.Array(Schema.String),\n message: Schema.String,\n cause: Schema.optional(\n Schema.Union([\n ArtifactStore.ArtifactStoreError,\n ArtifactStore.ArtifactMissing,\n ArtifactStore.ArtifactCorruption\n ])\n )\n }\n) {}\n\n/**\n * How eagerly a replay materializes the artifacts it references.\n *\n * `all` downloads every referenced artifact into this host's local store while\n * the replay is admitted, so every later read is local and a shared tier that\n * goes down afterwards costs nothing. `minimal` downloads none of them: it\n * establishes that each digest is resolvable — locally, or from the shared tier\n * — and lets the first actual read fetch the bytes.\n *\n * `minimal` is only sound when the store the replay reads through can reach the\n * shared tier, which means `CombinedArtifacts` with the same remote tier. Under\n * a purely local `ArtifactStore` an admitted `minimal` replay would later read\n * an artifact this host never fetched.\n *\n * @since 0.1.0\n * @category models\n */\nexport type DownloadPolicy = RemoteArtifacts.DownloadPolicy\n\n/**\n * The two directions of the artifact protocol: {@link Service.publish} before a\n * cache entry becomes observable, {@link Service.hydrate} when a replay needs\n * bytes this host does not have.\n *\n * The asymmetry in their error types is the design: publishing may fail,\n * because publishing an entry whose artifacts are missing is the unsafe state;\n * hydrating never fails, because a shared tier that is down should cost a run\n * a cache hit, not the run itself.\n *\n * @since 0.1.0\n * @category services\n */\nexport interface Service {\n /**\n * Makes every digest durable in the shared tier, in REAPI order:\n * `findMissing`, upload what is missing, then confirm nothing is missing\n * any more. Returns only once the shared tier can serve all of them.\n */\n readonly publish: (\n digests: ReadonlyArray<string>\n ) => Effect.Effect<void, ArtifactPublicationFailed, Crypto.Crypto>\n /**\n * Fetches the digests this host is missing from the shared tier and writes\n * them into the local store, reporting whether every digest is now locally\n * resolvable.\n *\n * Best-effort by construction: `false` means \"replay still cannot be\n * satisfied\", which the caller turns into a real execution. A shared tier\n * that is down must never fail a run that can simply do the work.\n */\n readonly hydrate: (\n digests: ReadonlyArray<string>\n ) => Effect.Effect<boolean, never, Crypto.Crypto>\n}\n\n/**\n * Service tag for the two-tier artifact protocol.\n *\n * @since 0.1.0\n * @category services\n */\nexport class ArtifactSync extends Context.Service<ArtifactSync, Service>()(\"@smthrs/engine-store/ArtifactSync\") {}\n\n/**\n * The single-tier implementation: there is no shared artifact store, so there\n * is nothing to publish and nothing to fetch.\n *\n * This is the default `ActionPersistence` falls back to when the tag is\n * absent, which is what keeps a purely local composition free of any\n * remote-cache machinery.\n *\n * @since 0.1.0\n * @category constructors\n */\nexport const makeLocal = (): Service => ({\n publish: Effect.fn(\"ArtifactSync.publish\")(() => Effect.void),\n hydrate: Effect.fn(\"ArtifactSync.hydrate\")((digests) => Effect.succeed(digests.length === 0))\n})\n\n/**\n * Provides the single-tier (no shared artifact store) protocol.\n *\n * @since 0.1.0\n * @category layers\n */\nexport const layerLocal: Layer.Layer<ArtifactSync> = Layer.succeed(ArtifactSync)(makeLocal())\n\n/**\n * Builds the two-tier protocol over a local and a shared artifact store.\n *\n * @since 0.1.0\n * @category constructors\n */\nexport const make = (options: {\n readonly local: ArtifactStore.Service\n readonly remote: ArtifactStore.Service\n /**\n * Defaults to the policy the shared tier declares, and to `all` for a tier\n * that declares none: every referenced artifact is fetched at hydrate.\n */\n readonly downloadPolicy?: DownloadPolicy | undefined\n}): Service => {\n const { local, remote } = options\n const downloadPolicy = options.downloadPolicy ?? RemoteArtifacts.downloadPolicyOf(remote) ?? \"all\"\n const publicationFailed = (\n digests: ReadonlyArray<string>,\n message: string,\n cause?: ArtifactStore.ArtifactStoreError | ArtifactStore.ArtifactMissing | ArtifactStore.ArtifactCorruption\n ) => new ArtifactPublicationFailed({ code: \"artifact_publication_failed\", digests, message, cause })\n return {\n publish: Effect.fn(\"ArtifactSync.publish\")(function*(digests) {\n if (digests.length === 0) return\n const missing = yield* remote.findMissing(digests).pipe(\n Effect.mapError((cause) => publicationFailed(digests, \"the shared tier refused a probe\", cause))\n )\n for (const digest of missing) {\n const bytes = yield* local.get(digest).pipe(\n Effect.mapError((cause) =>\n // The blob is referenced by evidence this host just produced, so\n // it should be in this host's own store. If it is not, the\n // evidence is not publishable — and saying so is far better than\n // publishing an entry nobody can replay.\n publicationFailed([digest], \"this host cannot read the artifact it recorded\", cause)\n )\n )\n yield* remote.put(bytes).pipe(\n Effect.mapError((cause) => publicationFailed([digest], \"the shared tier refused an upload\", cause))\n )\n }\n if (missing.length === 0) return\n // The confirming re-probe is not paranoia: a tier that accepted the\n // upload but did not durably store it is exactly the state that makes a\n // published entry unreplayable, and it is cheap to rule out — one\n // batched round trip over the set we just wrote.\n const stillMissing = yield* remote.findMissing(missing).pipe(\n Effect.mapError((cause) => publicationFailed(missing, \"the shared tier refused a probe\", cause))\n )\n if (stillMissing.length > 0) {\n return yield* Effect.fail(\n publicationFailed(stillMissing, \"the shared tier did not retain the uploaded artifacts\")\n )\n }\n }),\n hydrate: Effect.fn(\"ArtifactSync.hydrate\")(function*(digests) {\n // Every refusal below falls back to re-executing the step, which is\n // correct and silent. The warning is the operator's only signal that a\n // tier stopped serving replays.\n const fallBack = (message: string, digests: ReadonlyArray<string>) => (cause: { readonly message: string }) =>\n Effect.logWarning(`artifact hydration falls back to re-execution: ${message}: ${digests.join(\", \")}`).pipe(\n Effect.annotateLogs({ reason: cause.message })\n )\n const missing = yield* local.findMissing(digests).pipe(\n Effect.tapError(fallBack(\"the local tier refused a probe\", digests)),\n Effect.option\n )\n if (missing._tag === \"None\") return false\n if (downloadPolicy !== \"all\") {\n if (missing.value.length === 0) return true\n // One batched probe, no body transfer: the answer these policies owe\n // the caller is \"can this replay be satisfied\", and `findMissing`\n // answers exactly that. A tier that refuses the probe is\n // indistinguishable from one that holds nothing, so the replay is\n // refused either way.\n const elsewhere = yield* remote.findMissing(missing.value).pipe(\n Effect.tapError(fallBack(\"the shared tier refused a probe\", missing.value)),\n Effect.option\n )\n return elsewhere._tag === \"Some\" && elsewhere.value.length === 0\n }\n for (const digest of missing.value) {\n // Read-through, one artifact at a time and only on demand: the remote\n // read is digest-verified by `RemoteArtifacts`, and the local `put`\n // re-addresses the bytes, so a tier serving wrong content cannot get\n // them written into this workspace.\n const written = yield* remote.get(digest).pipe(\n Effect.flatMap((bytes) => local.put(bytes)),\n Effect.tapError(fallBack(\"the artifact could not be fetched\", [digest])),\n Effect.option\n )\n if (written._tag === \"None\") return false\n }\n return true\n })\n }\n}\n\n/**\n * Provides the two-tier protocol, taking the local store from the\n * `ArtifactStore` tag and the shared store from the supplied effect.\n *\n * The two tiers cannot both be layers: they inhabit the same tag, so composing\n * them would shadow one with the other.\n *\n * @since 0.1.0\n * @category layers\n */\nexport const layer = <E, R>(\n remote: Effect.Effect<ArtifactStore.Service, E, R>,\n options?: { readonly downloadPolicy?: DownloadPolicy | undefined } | undefined\n): Layer.Layer<ArtifactSync, E, ArtifactStore.ArtifactStore | R> =>\n Layer.effect(ArtifactSync)(\n Effect.gen(function*() {\n const local = yield* ArtifactStore.ArtifactStore\n const shared = yield* remote\n return make({\n local,\n remote: shared,\n ...(options?.downloadPolicy === undefined ? {} : { downloadPolicy: options.downloadPolicy })\n })\n })\n )\n"]}
1
+ {"version":3,"file":"ArtifactSync.js","sourceRoot":"","sources":["../../src/ArtifactSync.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The two-tier artifact protocol the step cache rides on: publish before the\n * entry, fetch on demand after it.\n *\n * This is the seam that makes a shared cache safe. A cache entry references\n * its large outputs by digest, so an entry that becomes observable while one\n * of those artifacts is still missing hands the next machine a hit it cannot\n * materialize. Bazel states the constraint at\n * `reference/bazel/.../remote/UploadManifest.java:630-633` — \"action results\n * may fail to validate server-side if they are accessed before all blobs they\n * refer to are present\" — and enforces it by ordering:\n * `findMissingDigests` → upload the missing → upload the `ActionResult` LAST.\n * {@link Service.publish} is that ordering, and `ActionPersistence` calls it\n * before `CacheStore.put`.\n *\n * The read direction is the same protocol in reverse, and how eagerly it runs\n * is the {@link DownloadPolicy}, declared on the shared tier itself\n * (`RemoteArtifacts.Options.downloadPolicy`) and split across two seams.\n * `all` fetches every referenced artifact into this host's store while\n * admitting the replay. `toplevel` and `minimal` fetch nothing here: one\n * batched probe establishes that the shared tier can serve them and the\n * transfer is left to the first read through `CombinedArtifacts`, which then\n * writes the blob back under `toplevel` and does not under `minimal`. This is\n * Bazel's `RemoteOutputChecker` dial\n * (`--remote_download_{all,toplevel,minimal}`) split the way our two seams\n * split: what a replay prefetches is this module's decision, what a read\n * materializes is the combined store's.\n *\n * Governing design: `packages/smithers/flows/artifacts/docs/api.md`.\n *\n * @since 0.1.0\n */\n\nimport * as ArtifactStore from \"@smthrs/artifacts/ArtifactStore\"\nimport * as RemoteArtifacts from \"@smthrs/artifacts/RemoteArtifacts\"\nimport * as Context from \"effect/Context\"\nimport type * as Crypto from \"effect/Crypto\"\nimport * as Effect from \"effect/Effect\"\nimport * as Layer from \"effect/Layer\"\nimport * as Schema from \"effect/Schema\"\n\n/**\n * A referenced artifact could not be made durable in the shared tier, so the\n * cache entry that references it must not be published.\n *\n * Failing here is the point: silently publishing the entry anyway is exactly\n * the state the REAPI ordering constraint exists to prevent.\n *\n * @since 0.1.0\n * @category errors\n */\nexport class ArtifactPublicationFailed extends Schema.TaggedError<ArtifactPublicationFailed>()(\n \"@smthrs/engine-store/ArtifactPublicationFailed\",\n {\n code: Schema.Literal(\"artifact_publication_failed\"),\n digests: Schema.Array(Schema.String),\n message: Schema.String,\n cause: Schema.optional(\n Schema.Union([\n ArtifactStore.ArtifactStoreError,\n ArtifactStore.ArtifactMissing,\n ArtifactStore.ArtifactCorruption\n ])\n )\n }\n) {}\n\n/**\n * How eagerly a replay materializes the artifacts it references.\n *\n * `all` downloads every referenced artifact into this host's local store while\n * the replay is admitted, so every later read is local and a shared tier that\n * goes down afterwards costs nothing. `minimal` downloads none of them: it\n * establishes that each digest is resolvable — locally, or from the shared tier\n * — and lets the first actual read fetch the bytes.\n *\n * `minimal` is only sound when the store the replay reads through can reach the\n * shared tier, which means `CombinedArtifacts` with the same remote tier. Under\n * a purely local `ArtifactStore` an admitted `minimal` replay would later read\n * an artifact this host never fetched.\n *\n * @since 0.1.0\n * @category models\n */\nexport type DownloadPolicy = RemoteArtifacts.DownloadPolicy\n\n/**\n * The two directions of the artifact protocol: {@link Service.publish} before a\n * cache entry becomes observable, {@link Service.hydrate} when a replay needs\n * bytes this host does not have.\n *\n * The asymmetry in their error types is the design: publishing may fail,\n * because publishing an entry whose artifacts are missing is the unsafe state;\n * hydrating never fails, because a shared tier that is down should cost a run\n * a cache hit, not the run itself.\n *\n * @since 0.1.0\n * @category services\n */\nexport interface Service {\n /**\n * Makes every digest durable in the shared tier, in REAPI order:\n * `findMissing`, upload what is missing, then confirm nothing is missing\n * any more. Returns only once the shared tier can serve all of them.\n */\n readonly publish: (\n digests: ReadonlyArray<string>\n ) => Effect.Effect<void, ArtifactPublicationFailed, Crypto.Crypto>\n /**\n * Fetches the digests this host is missing from the shared tier and writes\n * them into the local store, reporting whether every digest is now locally\n * resolvable.\n *\n * Best-effort by construction: `false` means \"replay still cannot be\n * satisfied\", which the caller turns into a real execution. A shared tier\n * that is down must never fail a run that can simply do the work.\n */\n readonly hydrate: (\n digests: ReadonlyArray<string>\n ) => Effect.Effect<boolean, never, Crypto.Crypto>\n}\n\n/**\n * Service tag for the two-tier artifact protocol.\n *\n * @since 0.1.0\n * @category services\n */\nexport class ArtifactSync extends Context.Service<ArtifactSync, Service>()(\"@smthrs/engine-store/ArtifactSync\") {}\n\n/**\n * The single-tier implementation: there is no shared artifact store, so there\n * is nothing to publish and nothing to fetch.\n *\n * This is the default `ActionPersistence` falls back to when the tag is\n * absent, which is what keeps a purely local composition free of any\n * remote-cache machinery.\n *\n * @since 0.1.0\n * @category constructors\n */\nexport const makeLocal = (): Service => ({\n publish: Effect.fn(\"ArtifactSync.publish\")(() => Effect.void),\n hydrate: Effect.fn(\"ArtifactSync.hydrate\")((digests) => Effect.succeed(digests.length === 0))\n})\n\n/**\n * Provides the single-tier (no shared artifact store) protocol.\n *\n * @since 0.1.0\n * @category layers\n */\nexport const layerLocal: Layer.Layer<ArtifactSync> = Layer.succeed(ArtifactSync)(makeLocal())\n\n/**\n * Builds the two-tier protocol over a local and a shared artifact store.\n *\n * @since 0.1.0\n * @category constructors\n */\nexport const make = (options: {\n readonly local: ArtifactStore.Service\n readonly remote: ArtifactStore.Service\n /**\n * Defaults to the policy the shared tier declares, and to `all` for a tier\n * that declares none: every referenced artifact is fetched at hydrate.\n */\n readonly downloadPolicy?: DownloadPolicy | undefined\n}): Service => {\n const { local, remote } = options\n const downloadPolicy = options.downloadPolicy ?? RemoteArtifacts.downloadPolicyOf(remote) ?? \"all\"\n const publicationFailed = (\n digests: ReadonlyArray<string>,\n message: string,\n cause?: ArtifactStore.ArtifactStoreError | ArtifactStore.ArtifactMissing | ArtifactStore.ArtifactCorruption\n ) => new ArtifactPublicationFailed({ code: \"artifact_publication_failed\", digests, message, cause })\n return {\n publish: Effect.fn(\"ArtifactSync.publish\")(function*(digests) {\n if (digests.length === 0) return\n const missing = yield* remote.findMissing(digests).pipe(\n Effect.mapError((cause) => publicationFailed(digests, \"the shared tier refused a probe\", cause))\n )\n for (const digest of missing) {\n const bytes = yield* local.get(digest).pipe(\n Effect.mapError((cause) =>\n // The blob is referenced by evidence this host just produced, so\n // it should be in this host's own store. If it is not, the\n // evidence is not publishable — and saying so is far better than\n // publishing an entry nobody can replay.\n publicationFailed([digest], \"this host cannot read the artifact it recorded\", cause)\n )\n )\n yield* remote.put(bytes).pipe(\n Effect.mapError((cause) => publicationFailed([digest], \"the shared tier refused an upload\", cause))\n )\n }\n if (missing.length === 0) return\n // The confirming re-probe is not paranoia: a tier that accepted the\n // upload but did not durably store it is exactly the state that makes a\n // published entry unreplayable, and it is cheap to rule out — one\n // batched round trip over the set we just wrote.\n const stillMissing = yield* remote.findMissing(missing).pipe(\n Effect.mapError((cause) => publicationFailed(missing, \"the shared tier refused a probe\", cause))\n )\n if (stillMissing.length > 0) {\n return yield* Effect.fail(\n publicationFailed(stillMissing, \"the shared tier did not retain the uploaded artifacts\")\n )\n }\n }),\n hydrate: Effect.fn(\"ArtifactSync.hydrate\")(function*(digests) {\n // Every refusal below falls back to re-executing the step, which is\n // correct and silent. The warning is the operator's only signal that a\n // tier stopped serving replays.\n const fallBack = (message: string, digests: ReadonlyArray<string>) => (cause: { readonly message: string }) =>\n Effect.logWarning(`artifact hydration falls back to re-execution: ${message}: ${digests.join(\", \")}`).pipe(\n Effect.annotateLogs({ reason: cause.message })\n )\n const missing = yield* local.findMissing(digests).pipe(\n Effect.tapError(fallBack(\"the local tier refused a probe\", digests)),\n Effect.option\n )\n if (missing._tag === \"None\") return false\n if (downloadPolicy !== \"all\") {\n if (missing.value.length === 0) return true\n // One batched probe, no body transfer: the answer these policies owe\n // the caller is \"can this replay be satisfied\", and `findMissing`\n // answers exactly that. A tier that refuses the probe is\n // indistinguishable from one that holds nothing, so the replay is\n // refused either way.\n const elsewhere = yield* remote.findMissing(missing.value).pipe(\n Effect.tapError(fallBack(\"the shared tier refused a probe\", missing.value)),\n Effect.option\n )\n return elsewhere._tag === \"Some\" && elsewhere.value.length === 0\n }\n for (const digest of missing.value) {\n // Read-through, one artifact at a time and only on demand: the remote\n // read is digest-verified by `RemoteArtifacts`, and the local `put`\n // re-addresses the bytes, so a tier serving wrong content cannot get\n // them written into this workspace.\n const written = yield* remote.get(digest).pipe(\n Effect.flatMap((bytes) => local.put(bytes)),\n // A custom tier may skip that verification; the address `put`\n // returned is the proof the requested blob is now local.\n Effect.flatMap((address) =>\n address === digest ?\n Effect.void :\n Effect.fail({ message: `the shared tier served the bytes of ${address}` })\n ),\n Effect.tapError(fallBack(\"the artifact could not be fetched\", [digest])),\n Effect.option\n )\n if (written._tag === \"None\") return false\n }\n return true\n })\n }\n}\n\n/**\n * Provides the two-tier protocol, taking the local store from the\n * `ArtifactStore` tag and the shared store from the supplied effect.\n *\n * The two tiers cannot both be layers: they inhabit the same tag, so composing\n * them would shadow one with the other.\n *\n * @since 0.1.0\n * @category layers\n */\nexport const layer = <E, R>(\n remote: Effect.Effect<ArtifactStore.Service, E, R>,\n options?: { readonly downloadPolicy?: DownloadPolicy | undefined } | undefined\n): Layer.Layer<ArtifactSync, E, ArtifactStore.ArtifactStore | R> =>\n Layer.effect(ArtifactSync)(\n Effect.gen(function*() {\n const local = yield* ArtifactStore.ArtifactStore\n const shared = yield* remote\n return make({\n local,\n remote: shared,\n ...(options?.downloadPolicy === undefined ? {} : { downloadPolicy: options.downloadPolicy })\n })\n })\n )\n"]}
@@ -341,32 +341,13 @@ export declare const verify: (backupDirectory: string, options?: FileSizeOptions
341
341
  * is a fresh store directory to compose `NodeDatabase.layer` and
342
342
  * `ArtifactStore.layerFileSystem` over. The database is restored with the
343
343
  * pre-backup owner fences still in place — run {@link fence} against it
344
- * before any engine adopts it.
344
+ * before any engine adopts it. A failed copy removes everything it wrote, so
345
+ * the target is left empty and reusable for a retry.
345
346
  *
346
347
  * @since 0.1.0
347
348
  * @category operations
348
349
  */
349
- export declare const restore: (options: RestoreOptions) => Effect.Effect<{
350
- databaseFile: string;
351
- objectsDirectory: string;
352
- manifest: {
353
- readonly formatVersion: 1;
354
- readonly createdAtMs: number;
355
- readonly database: {
356
- readonly file: "store.sqlite3";
357
- readonly sha256: string & import("effect/Brand").Brand<"@smthrs/crypto/Sha256/Digest">;
358
- readonly sizeBytes: number;
359
- readonly migrations: readonly {
360
- readonly migrationId: number;
361
- readonly name: string;
362
- }[];
363
- };
364
- readonly artifacts: readonly {
365
- readonly digest: string & import("effect/Brand").Brand<"@smthrs/crypto/Sha256/Digest">;
366
- readonly sizeBytes: number;
367
- }[];
368
- };
369
- }, DisasterRecoveryError, FileSystem.FileSystem | Crypto.Crypto>;
350
+ export declare const restore: (options: RestoreOptions) => Effect.Effect<RestoredStore, DisasterRecoveryError, FileSystem.FileSystem | Crypto.Crypto>;
370
351
  /**
371
352
  * Bumps the restored store's fencing epoch so pre-backup owners are fenced
372
353
  * out.
@@ -418,10 +399,12 @@ export declare const fence: (manifest: {
418
399
  /**
419
400
  * Restores a backup and immediately fences the restored database.
420
401
  *
421
- * This is the one-shot operator API for disaster recovery. The database
422
- * driver itself still arrives through a caller-supplied Layer so this module
423
- * remains host-neutral and browser-bundleable; the bundled Node script passes
424
- * `NodeDatabase.layer` for the restored file.
402
+ * This is the one-shot operator API for disaster recovery. When fencing
403
+ * refuses, the restored files are removed, so no engine can adopt a store that
404
+ * still carries pre-backup owners. The database driver itself still arrives
405
+ * through a caller-supplied Layer so this module remains host-neutral and
406
+ * browser-bundleable; the bundled Node script passes `NodeDatabase.layer` for
407
+ * the restored file.
425
408
  *
426
409
  * @since 0.1.0
427
410
  * @category operations
@@ -1 +1 @@
1
- {"version":3,"file":"DisasterRecovery.d.ts","sourceRoot":"","sources":["../../src/DisasterRecovery.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AAIH,OAAO,EAAE,aAAa,EAAE,MAAM,gCAAgC,CAAA;AAE9D,OAAO,KAAK,KAAK,MAAM,MAAM,eAAe,CAAA;AAC5C,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AACvC,OAAO,KAAK,UAAU,MAAM,mBAAmB,CAAA;AAC/C,OAAO,KAAK,KAAK,KAAK,MAAM,cAAc,CAAA;AAC1C,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AACvC,OAAO,KAAK,SAAS,MAAM,sBAAsB,CAAA;AAGjD;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB,kBAAkB,CAAA;AAE/C;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB,kBAAkB,CAAA;AAE/C;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,YAAY,CAAA;AAE7C;;;;;;GAMG;AACH,eAAO,MAAM,sBAAsB,kBAAkB,CAAA;AAErD;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,QAAoB,CAAA;AAExD;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB;;;EAG3B,CAAA;AAEF;;;;;GAKG;AACH,MAAM,MAAM,gBAAgB,GAAG,OAAO,gBAAgB,CAAC,IAAI,CAAA;AAE3D;;;;;;GAMG;AACH,eAAO,MAAM,aAAa;;;EAGxB,CAAA;AAEF;;;;;GAKG;AACH,MAAM,MAAM,aAAa,GAAG,OAAO,aAAa,CAAC,IAAI,CAAA;AAErD;;;;;;GAMG;AACH,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;EAUzB,CAAA;AAEF;;;;;GAKG;AACH,MAAM,MAAM,cAAc,GAAG,OAAO,cAAc,CAAC,IAAI,CAAA;AAKvD;;;;;;GAMG;AACH,eAAO,MAAM,yBAAyB,iMAWpC,CAAA;AAEF;;;;;GAKG;AACH,MAAM,MAAM,yBAAyB,GAAG,OAAO,yBAAyB,CAAC,IAAI,CAAA;;;;;;;AAE7E;;;;;;;GAOG;AACH,qBAAa,qBAAsB,SAAQ,0BAQ1C;CAAG;AAEJ;;;;;GAKG;AACH,MAAM,WAAW,eAAe;IAC9B;;;;OAIG;IACH,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAC/C;AAED;;;;;GAKG;AACH,MAAM,WAAW,aAAa,CAAC,CAAC,GAAG,KAAK,EAAE,CAAC,GAAG,KAAK,CAAE,SAAQ,eAAe;IAC1E,iGAAiG;IACjG,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC9C;;;;OAIG;IACH,QAAQ,CAAC,qBAAqB,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,KAAK,CAAC,KAAK,CAAC,SAAS,CAAC,SAAS,EAAE,CAAC,EAAE,CAAC,CAAC,CAAA;CACjG;AAED;;;;;GAKG;AACH,MAAM,WAAW,cAAe,SAAQ,eAAe;IACrD,qDAAqD;IACrD,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAA;IAChC,+GAA+G;IAC/G,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAA;CACjC;AAED;;;;;;GAMG;AACH,MAAM,WAAW,sBAAsB,CAAC,CAAC,GAAG,KAAK,EAAE,CAAC,GAAG,KAAK,CAAE,SAAQ,cAAc;IAClF;;;;OAIG;IACH,QAAQ,CAAC,aAAa,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,KAAK,CAAC,KAAK,CAAC,aAAa,GAAG,SAAS,CAAC,SAAS,EAAE,CAAC,EAAE,CAAC,CAAC,CAAA;CACzG;AAED;;;;;;GAMG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAA;IAC7B,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAA;IACjC,QAAQ,CAAC,QAAQ,EAAE,cAAc,CAAA;CAClC;AAED;;;;;;GAMG;AACH,MAAM,WAAW,mBAAoB,SAAQ,aAAa;IACxD,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAA;CAC7B;AAED;;;;;GAKG;AACH,MAAM,WAAW,YAAY;IAC3B,qDAAqD;IACrD,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAA;IAC9B,6FAA6F;IAC7F,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAA;CAC/B;AAoND;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,MAAM,GAAI,CAAC,GAAG,KAAK,EAAE,CAAC,GAAG,KAAK,EACzC,SAAS,aAAa,CAAC,CAAC,EAAE,CAAC,CAAC,KAC3B,MAAM,CAAC,MAAM,CACd,cAAc,EACd,qBAAqB,GAAG,CAAC,EACzB,SAAS,CAAC,SAAS,GAAG,UAAU,CAAC,UAAU,GAAG,MAAM,CAAC,MAAM,GAAG,CAAC,CA4HZ,CAAA;AAErD;;;;;;;GAOG;AACH,eAAO,MAAM,MAAM;;;;;;;;;;;;;;;;gEA0BjB,CAAA;AAEF;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,OAAO;;;;;;;;;;;;;;;;;;;;gEAiDlB,CAAA;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,eAAO,MAAM,KAAK;;;;;;;;;;;;;;;;;;;8DAmDhB,CAAA;AAEF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,eAAe,GAAI,CAAC,GAAG,KAAK,EAAE,CAAC,GAAG,KAAK,EAClD,SAAS,sBAAsB,CAAC,CAAC,EAAE,CAAC,CAAC,KACpC,MAAM,CAAC,MAAM,CAAC,mBAAmB,EAAE,qBAAqB,GAAG,CAAC,EAAE,UAAU,CAAC,UAAU,GAAG,MAAM,CAAC,MAAM,GAAG,CAAC,CAOtG,CAAA"}
1
+ {"version":3,"file":"DisasterRecovery.d.ts","sourceRoot":"","sources":["../../src/DisasterRecovery.ts"],"names":[],"mappings":"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"}
@@ -209,6 +209,12 @@ const ensureEmptyDirectory = (fs, method, directory) => Effect.gen(function* ()
209
209
  return yield* Effect.fail(error(method, "not_empty", `${directory} already holds ${entries.length} entries`, directory));
210
210
  }
211
211
  });
212
+ /**
213
+ * Removes every entry of a directory this module filled after admitting it
214
+ * empty, leaving the directory reusable for a retry. Cleanup is best effort;
215
+ * the caller reports the original failure.
216
+ */
217
+ const discardEntries = (fs, directory) => fs.readDirectory(directory).pipe(Effect.flatMap((entries) => Effect.forEach(entries, (entry) => fs.remove(`${directory}/${entry}`, { recursive: true, force: true }), { discard: true })), Effect.ignore);
212
218
  /**
213
219
  * A relative blob path in the store's fanout layout: a two-hex directory
214
220
  * holding a 64-hex digest that begins with it. Anything else under the
@@ -366,9 +372,8 @@ export const backup = (options) => Effect.gen(function* () {
366
372
  // The directory was empty when the capture began, so everything in it is
367
373
  // this capture's: the snapshot, any SQLite sidecar the inspection
368
374
  // connection left, and the copied blobs. A failed capture removes them and
369
- // leaves the directory reusable for a retry. Cleanup is best effort; the
370
- // original failure is the one reported.
371
- const discardPartial = fs.readDirectory(options.directory).pipe(Effect.flatMap((entries) => Effect.forEach(entries, (entry) => fs.remove(`${options.directory}/${entry}`, { recursive: true, force: true }), { discard: true })), Effect.ignore);
375
+ // leaves the directory reusable for a retry.
376
+ const discardPartial = discardEntries(fs, options.directory);
372
377
  return yield* (options.objectsDirectory === undefined
373
378
  ? capture
374
379
  : ArtifactBackupLease.withLease(fs, options.objectsDirectory, capture, ioFailure("backup", `coordinating artifact backup in ${options.objectsDirectory}`))).pipe(Effect.onError(() => discardPartial));
@@ -394,6 +399,27 @@ export const verify = Effect.fn("DisasterRecovery.verify")(function* (backupDire
394
399
  }
395
400
  return manifest;
396
401
  });
402
+ /** Copies a verified backup into an admitted empty target directory. */
403
+ const writeRestoredStore = (fs, options, manifest, maxFileSizeBytes) => Effect.gen(function* () {
404
+ const databaseBytes = yield* checkedFile(fs, "restore", `${options.backupDirectory}/${manifest.database.file}`, manifest.database.sha256, maxFileSizeBytes);
405
+ const databaseFile = `${options.targetDirectory}/${databaseFileName}`;
406
+ yield* fs.writeFile(databaseFile, databaseBytes).pipe(Effect.mapError(ioFailure("restore", `writing ${databaseFile}`)));
407
+ const objectsDirectory = `${options.targetDirectory}/${objectsDirectoryName}`;
408
+ for (const entry of manifest.artifacts) {
409
+ const bytes = yield* checkedFile(fs, "restore", blobPath(`${options.backupDirectory}/${objectsDirectoryName}`, entry.digest), entry.digest, maxFileSizeBytes);
410
+ yield* fs.makeDirectory(`${objectsDirectory}/${entry.digest.slice(0, 2)}`, { recursive: true }).pipe(Effect.mapError(ioFailure("restore", `creating the objects directory for ${entry.digest}`)));
411
+ yield* fs.writeFile(blobPath(objectsDirectory, entry.digest), bytes).pipe(Effect.mapError(ioFailure("restore", `writing the blob for ${entry.digest}`)));
412
+ }
413
+ const restoredAtMs = yield* Clock.currentTimeMillis.pipe(Effect.map(Math.floor));
414
+ const marker = {
415
+ formatVersion: 1,
416
+ restoredAtMs,
417
+ backupCreatedAtMs: manifest.createdAtMs,
418
+ databaseSha256: manifest.database.sha256
419
+ };
420
+ yield* fs.writeFileString(`${options.targetDirectory}/${restoredMarkerFileName}`, `${JSON.stringify(marker, null, 2)}\n`).pipe(Effect.mapError(ioFailure("restore", `writing ${restoredMarkerFileName}`)));
421
+ return { databaseFile, objectsDirectory, manifest };
422
+ });
397
423
  /**
398
424
  * Restores a verified backup into an empty target directory.
399
425
  *
@@ -403,7 +429,8 @@ export const verify = Effect.fn("DisasterRecovery.verify")(function* (backupDire
403
429
  * is a fresh store directory to compose `NodeDatabase.layer` and
404
430
  * `ArtifactStore.layerFileSystem` over. The database is restored with the
405
431
  * pre-backup owner fences still in place — run {@link fence} against it
406
- * before any engine adopts it.
432
+ * before any engine adopts it. A failed copy removes everything it wrote, so
433
+ * the target is left empty and reusable for a retry.
407
434
  *
408
435
  * @since 0.1.0
409
436
  * @category operations
@@ -416,24 +443,10 @@ export const restore = Effect.fn("DisasterRecovery.restore")(function* (options)
416
443
  const fs = yield* FileSystem.FileSystem;
417
444
  const manifest = yield* readManifest(fs, "restore", options.backupDirectory, maxFileSizeBytes);
418
445
  yield* ensureEmptyDirectory(fs, "restore", options.targetDirectory);
419
- const databaseBytes = yield* checkedFile(fs, "restore", `${options.backupDirectory}/${manifest.database.file}`, manifest.database.sha256, maxFileSizeBytes);
420
- const databaseFile = `${options.targetDirectory}/${databaseFileName}`;
421
- yield* fs.writeFile(databaseFile, databaseBytes).pipe(Effect.mapError(ioFailure("restore", `writing ${databaseFile}`)));
422
- const objectsDirectory = `${options.targetDirectory}/${objectsDirectoryName}`;
423
- for (const entry of manifest.artifacts) {
424
- const bytes = yield* checkedFile(fs, "restore", blobPath(`${options.backupDirectory}/${objectsDirectoryName}`, entry.digest), entry.digest, maxFileSizeBytes);
425
- yield* fs.makeDirectory(`${objectsDirectory}/${entry.digest.slice(0, 2)}`, { recursive: true }).pipe(Effect.mapError(ioFailure("restore", `creating the objects directory for ${entry.digest}`)));
426
- yield* fs.writeFile(blobPath(objectsDirectory, entry.digest), bytes).pipe(Effect.mapError(ioFailure("restore", `writing the blob for ${entry.digest}`)));
427
- }
428
- const restoredAtMs = yield* Clock.currentTimeMillis.pipe(Effect.map(Math.floor));
429
- const marker = {
430
- formatVersion: 1,
431
- restoredAtMs,
432
- backupCreatedAtMs: manifest.createdAtMs,
433
- databaseSha256: manifest.database.sha256
434
- };
435
- yield* fs.writeFileString(`${options.targetDirectory}/${restoredMarkerFileName}`, `${JSON.stringify(marker, null, 2)}\n`).pipe(Effect.mapError(ioFailure("restore", `writing ${restoredMarkerFileName}`)));
436
- return { databaseFile, objectsDirectory, manifest };
446
+ // The target was empty when the copy began, so everything in it is this
447
+ // restore's. A refused blob or a failed write removes the partial store, so
448
+ // nothing store-shaped is left to compose over and a retry can reuse it.
449
+ return yield* Effect.onError(writeRestoredStore(fs, options, manifest, maxFileSizeBytes), () => discardEntries(fs, options.targetDirectory));
437
450
  });
438
451
  /**
439
452
  * Bumps the restored store's fencing epoch so pre-backup owners are fenced
@@ -507,17 +520,23 @@ export const fence = Effect.fn("DisasterRecovery.fence")(function* (manifest) {
507
520
  /**
508
521
  * Restores a backup and immediately fences the restored database.
509
522
  *
510
- * This is the one-shot operator API for disaster recovery. The database
511
- * driver itself still arrives through a caller-supplied Layer so this module
512
- * remains host-neutral and browser-bundleable; the bundled Node script passes
513
- * `NodeDatabase.layer` for the restored file.
523
+ * This is the one-shot operator API for disaster recovery. When fencing
524
+ * refuses, the restored files are removed, so no engine can adopt a store that
525
+ * still carries pre-backup owners. The database driver itself still arrives
526
+ * through a caller-supplied Layer so this module remains host-neutral and
527
+ * browser-bundleable; the bundled Node script passes `NodeDatabase.layer` for
528
+ * the restored file.
514
529
  *
515
530
  * @since 0.1.0
516
531
  * @category operations
517
532
  */
518
533
  export const restoreAndFence = (options) => Effect.gen(function* () {
534
+ const fs = yield* FileSystem.FileSystem;
519
535
  const restored = yield* restore(options);
520
- const summary = yield* fence(restored.manifest).pipe(Effect.provide(options.databaseLayer(restored.databaseFile)));
536
+ // A refused fence leaves a database that still carries pre-backup owner
537
+ // fences beside a `restored.json` marker. Remove the whole restore so no
538
+ // engine can adopt it and the operator can retry into the same target.
539
+ const summary = yield* fence(restored.manifest).pipe(Effect.provide(options.databaseLayer(restored.databaseFile)), Effect.onError(() => discardEntries(fs, options.targetDirectory)));
521
540
  return { ...restored, fence: summary };
522
541
  });
523
542
  //# sourceMappingURL=DisasterRecovery.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"DisasterRecovery.js","sourceRoot":"","sources":["../../src/DisasterRecovery.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Hot backup, verified restore, and restore-time fencing for the durable\n * flows store.\n *\n * A backup captures the whole SQLite database — journal, run store, step\n * cache, engine state, and the out-of-ladder objects created by\n * `internal/EngineStateSchema.ts` — with `VACUUM INTO`, which reads one\n * consistent snapshot under WAL while writers stay live. The content-addressed\n * artifact blobs are copied beside it, digest-verified, after the database\n * snapshot: results reference artifacts only after publication\n * (`packages/smithers/flows/step-cache/docs/api.md`), so a blob walk taken after the\n * database snapshot is a superset of every digest the snapshot references.\n * A manifest written last records the digests a restore must verify, so a\n * partial backup is detectable by its missing manifest.\n *\n * Restore never lets a pre-backup owner resurrect. The fencing token is the\n * `OwnerId` triple mirrored on `flows_runs` and arbitrated by the journal's\n * `flows_consensus_leases` — Temporal's shard `rangeID` equality check reduced\n * to one SQL predicate (`reference/temporal`\n * `service/history/shard/context_impl.go`, `renewRangeLocked`). Temporal\n * fences a restored shard by bumping the persisted epoch above anything a\n * surviving owner could hold; the equivalent bump for an identity fence is\n * {@link fence}: clear every persisted claim, owner, and consensus lease in\n * one serialized write transaction, so every pre-backup `OwnerId` fails its\n * equality compare-and-swap (`FenceLost`, journal `fence_lost`) and the\n * restored runs are claimable immediately instead of after the heartbeat\n * staleness cutoff.\n *\n * Host access arrives through Effect's `FileSystem` and `SqlClient` tags and\n * hashing through the injected `Crypto` service, so the module carries no\n * platform binding. The SQL dialect is SQLite — a non-SQLite backend fails\n * with the `sql` code rather than pretending to snapshot. Every file is\n * stat-checked before it is buffered for hashing or copying. The configurable\n * ceiling defaults to {@link defaultMaxFileSizeBytes}; an oversized file fails\n * with the typed `io` code instead of reaching the host's allocation limit.\n *\n * @since 0.1.0\n */\n\nimport * as ArtifactBackupLease from \"@smthrs/artifacts/ArtifactBackupLease\"\nimport { Sha256 } from \"@smthrs/crypto\"\nimport { DurableWriter } from \"@smthrs/database/DurableWriter\"\nimport * as Clock from \"effect/Clock\"\nimport type * as Crypto from \"effect/Crypto\"\nimport * as Effect from \"effect/Effect\"\nimport * as FileSystem from \"effect/FileSystem\"\nimport type * as Layer from \"effect/Layer\"\nimport * as Schema from \"effect/Schema\"\nimport * as SqlClient from \"effect/sql/SqlClient\"\nimport * as ArtifactRoots from \"./internal/ArtifactRoots.ts\"\n\n/**\n * The database file name inside a backup directory and a restored store\n * directory.\n *\n * @since 0.1.0\n * @category constants\n */\nexport const databaseFileName = \"store.sqlite3\"\n\n/**\n * The manifest file name inside a backup directory. It is written last, so\n * its presence marks a complete backup.\n *\n * @since 0.1.0\n * @category constants\n */\nexport const manifestFileName = \"manifest.json\"\n\n/**\n * The artifact blob directory name inside a backup directory and a restored\n * store directory, laid out with the store's own two-hex-prefix fanout.\n *\n * @since 0.1.0\n * @category constants\n */\nexport const objectsDirectoryName = \"objects\"\n\n/**\n * The marker file a restore writes into the target directory, recording when\n * the restore happened and which backup it came from.\n *\n * @since 0.1.0\n * @category constants\n */\nexport const restoredMarkerFileName = \"restored.json\"\n\n/**\n * Default ceiling for one backup file read into memory: 512 MiB.\n *\n * @since 1.0.0\n * @category constants\n */\nexport const defaultMaxFileSizeBytes = 512 * 1024 * 1024\n\n/**\n * One applied row of the shared `flows_migrations` table, recorded at backup\n * time and re-verified at fence time as the schema version of the snapshot.\n *\n * @since 0.1.0\n * @category schemas\n */\nexport const AppliedMigration = Schema.Struct({\n migrationId: Schema.Number,\n name: Schema.String\n})\n\n/**\n * One applied row of the shared `flows_migrations` table.\n *\n * @since 0.1.0\n * @category models\n */\nexport type AppliedMigration = typeof AppliedMigration.Type\n\n/**\n * One captured artifact blob. `sizeBytes` is informational for operators; the\n * digest is the integrity check.\n *\n * @since 0.1.0\n * @category schemas\n */\nexport const ArtifactEntry = Schema.Struct({\n digest: Sha256.Digest,\n sizeBytes: Schema.Number\n})\n\n/**\n * One captured artifact blob.\n *\n * @since 0.1.0\n * @category models\n */\nexport type ArtifactEntry = typeof ArtifactEntry.Type\n\n/**\n * The verification contract of a backup: the database snapshot's digest and\n * schema version, and the digest of every captured artifact blob.\n *\n * @since 0.1.0\n * @category schemas\n */\nexport const BackupManifest = Schema.Struct({\n formatVersion: Schema.Literal(1),\n createdAtMs: Schema.Number,\n database: Schema.Struct({\n file: Schema.Literal(databaseFileName),\n sha256: Sha256.Digest,\n sizeBytes: Schema.Number,\n migrations: Schema.Array(AppliedMigration)\n }),\n artifacts: Schema.Array(ArtifactEntry)\n})\n\n/**\n * The verification contract of a backup.\n *\n * @since 0.1.0\n * @category models\n */\nexport type BackupManifest = typeof BackupManifest.Type\n\n/** JSON text carrying a decoded backup manifest. */\nconst ManifestFromJsonString = Schema.fromJsonString(BackupManifest)\n\n/**\n * Stable failure codes surfaced by backup, verify, restore, and fence.\n * `invalid_options` reports a public option that cannot be admitted safely.\n *\n * @since 0.1.0\n * @category errors\n */\nexport const DisasterRecoveryErrorCode = Schema.Literals([\n \"invalid_options\",\n \"not_empty\",\n \"invalid_manifest\",\n \"missing_file\",\n \"digest_mismatch\",\n \"artifact_corruption\",\n \"snapshot_incomplete\",\n \"schema_mismatch\",\n \"io\",\n \"sql\"\n])\n\n/**\n * A stable disaster-recovery failure code.\n *\n * @since 0.1.0\n * @category errors\n */\nexport type DisasterRecoveryErrorCode = typeof DisasterRecoveryErrorCode.Type\n\n/**\n * A normalized backup or restore failure. The identity string equals this\n * module's package path, per the house rule that a new identity is the\n * defining module path.\n *\n * @since 0.1.0\n * @category errors\n */\nexport class DisasterRecoveryError extends Schema.TaggedError<DisasterRecoveryError>()(\n \"@smthrs/engine-store/DisasterRecoveryError\",\n {\n code: DisasterRecoveryErrorCode,\n method: Schema.String,\n message: Schema.String,\n cause: Schema.Unknown\n }\n) {}\n\n/**\n * Shared buffering limit for backup, verification, and restore operations.\n *\n * @since 1.0.0\n * @category models\n */\nexport interface FileSizeOptions {\n /**\n * Largest file, in bytes, that backup, verification, or restore may buffer.\n * The value must be a finite non-negative safe integer. Defaults to\n * {@link defaultMaxFileSizeBytes}.\n */\n readonly maxFileSizeBytes?: number | undefined\n}\n\n/**\n * Where a backup is written and which artifact store it captures.\n *\n * @since 0.1.0\n * @category models\n */\nexport interface BackupOptions<R = never, E = never> extends FileSizeOptions {\n /** The directory the backup is written into. Created when absent; must be empty when present. */\n readonly directory: string\n /**\n * The live store's content-addressed objects directory (the\n * `ArtifactStore.layerFileSystem` directory). When set, the whole capture\n * holds the cross-process artifact backup lease, so a concurrent\n * `ArtifactGc.gc` sweep cannot delete a blob between the database snapshot\n * and its copy. Absent means the composition has no filesystem artifact\n * tier: the backup carries no blobs and takes no lease, because there is\n * no objects directory to fence, and a frozen snapshot that references any\n * artifact digest fails with `snapshot_incomplete` instead of reporting\n * success. A filesystem-backed composition must therefore pass the same\n * directory it gives `ArtifactStore.layerFileSystem`.\n */\n readonly objectsDirectory?: string | undefined\n /**\n * Opens the frozen `VACUUM INTO` file so backup can enumerate artifact\n * roots and migrations from that exact snapshot, independently of the live\n * connection. The host should open this file without applying migrations.\n */\n readonly snapshotDatabaseLayer: (databaseFile: string) => Layer.Layer<SqlClient.SqlClient, E, R>\n}\n\n/**\n * Which backup to restore and where the restored store lands.\n *\n * @since 0.1.0\n * @category models\n */\nexport interface RestoreOptions extends FileSizeOptions {\n /** The directory a previous {@link backup} wrote. */\n readonly backupDirectory: string\n /** The fresh directory the restored store is written into. Created when absent; must be empty when present. */\n readonly targetDirectory: string\n}\n\n/**\n * Which backup to restore and how to open the freshly restored SQLite file\n * for restore-time fencing.\n *\n * @since 0.1.0\n * @category models\n */\nexport interface RestoreAndFenceOptions<R = never, E = never> extends RestoreOptions {\n /**\n * Builds the database and durable-writer layer for the newly restored file.\n * This keeps database opening as the caller's host composition rather than\n * importing a Node-only driver into this browser-safe module.\n */\n readonly databaseLayer: (databaseFile: string) => Layer.Layer<DurableWriter | SqlClient.SqlClient, E, R>\n}\n\n/**\n * The restored store's on-disk locations, ready to compose database and\n * artifact layers over.\n *\n * @since 0.1.0\n * @category models\n */\nexport interface RestoredStore {\n readonly databaseFile: string\n readonly objectsDirectory: string\n readonly manifest: BackupManifest\n}\n\n/**\n * A restored store after restore-time fencing has invalidated pre-backup\n * owners.\n *\n * @since 0.1.0\n * @category models\n */\nexport interface FencedRestoredStore extends RestoredStore {\n readonly fence: FenceSummary\n}\n\n/**\n * What {@link fence} invalidated on the restored store.\n *\n * @since 0.1.0\n * @category models\n */\nexport interface FenceSummary {\n /** Runs whose pending claim columns were cleared. */\n readonly clearedClaims: number\n /** Runs that were `running` at backup time, now suspended with their owner fence cleared. */\n readonly suspendedRuns: number\n}\n\nconst error = (\n method: string,\n code: DisasterRecoveryErrorCode,\n message: string,\n cause: unknown\n): DisasterRecoveryError =>\n new DisasterRecoveryError({\n code,\n method,\n message: `${code}: DisasterRecovery.${method}: ${message}`,\n cause\n })\n\n/**\n * One mapper per failure family, shared across every call site so the\n * vocabulary stays uniform.\n */\nconst ioFailure = (method: string, subject: string) => (cause: unknown): DisasterRecoveryError =>\n error(method, \"io\", `the host filesystem refused ${subject}`, cause)\n\nconst sqlFailure = (method: string, subject: string) => (cause: unknown): DisasterRecoveryError =>\n error(method, \"sql\", `the database refused ${subject}`, cause)\n\n/** Hashes bytes with the injected crypto service; a broken host is a defect. */\nconst measure = (bytes: Uint8Array): Effect.Effect<typeof Sha256.Digest.Type, never, Crypto.Crypto> =>\n Schema.decodeUnknownEffect(Sha256)(bytes).pipe(Effect.orDie)\n\n/** Resolves and validates the shared whole-file buffering ceiling. */\nconst fileSizeLimit = (\n method: string,\n options: FileSizeOptions\n): number | DisasterRecoveryError => {\n const limit = options.maxFileSizeBytes\n if (limit === undefined) return defaultMaxFileSizeBytes\n if (Number.isSafeInteger(limit) && limit >= 0) return limit\n return error(\n method,\n \"invalid_options\",\n `maxFileSizeBytes must be a finite non-negative safe integer; received ${String(limit)}`,\n limit\n )\n}\n\n/**\n * Refuses an oversized file before a whole-file read allocates its buffer.\n * Every caller admits `limit` through `fileSizeLimit`, so `BigInt` cannot\n * reject it.\n */\nconst checkFileSize = (\n fs: FileSystem.FileSystem,\n method: string,\n path: string,\n limit: number\n): Effect.Effect<void, DisasterRecoveryError> =>\n Effect.gen(function*() {\n const info = yield* fs.stat(path).pipe(Effect.mapError(ioFailure(method, `stating ${path}`)))\n if (info.size <= BigInt(limit)) return\n const message = `${path} is ${info.size} bytes, which exceeds the ${limit}-byte file limit`\n return yield* Effect.fail(error(method, \"io\", message, new RangeError(message)))\n })\n\n/**\n * Admits a target directory that does not exist yet — created recursively —\n * or exists empty. Anything else is refused rather than merged over.\n */\nconst ensureEmptyDirectory = (\n fs: FileSystem.FileSystem,\n method: string,\n directory: string\n): Effect.Effect<void, DisasterRecoveryError> =>\n Effect.gen(function*() {\n const present = yield* fs.exists(directory).pipe(Effect.mapError(ioFailure(method, `probing ${directory}`)))\n if (!present) {\n return yield* fs.makeDirectory(directory, { recursive: true }).pipe(\n Effect.mapError(ioFailure(method, `creating ${directory}`))\n )\n }\n const entries = yield* fs.readDirectory(directory).pipe(\n Effect.mapError(ioFailure(method, `listing ${directory}`))\n )\n if (entries.length > 0) {\n return yield* Effect.fail(\n error(method, \"not_empty\", `${directory} already holds ${entries.length} entries`, directory)\n )\n }\n })\n\n/**\n * A relative blob path in the store's fanout layout: a two-hex directory\n * holding a 64-hex digest that begins with it. Anything else under the\n * objects directory — a scratch `.tmp-*` file, the fanout directory itself —\n * is not part of the store's address space.\n */\nconst blobEntry = (entry: string): string | undefined => {\n const match = entry.match(/^([0-9a-f]{2})[/\\\\]([0-9a-f]{64})$/)\n if (match === null || !match[2]!.startsWith(match[1]!)) return undefined\n return match[2]\n}\n\nconst blobPath = (directory: string, digest: string): string => `${directory}/${digest.slice(0, 2)}/${digest}`\n\n/** The applied rows of the shared migrations table, in ladder order. */\nconst appliedMigrations = (\n sql: SqlClient.SqlClient,\n method: string\n): Effect.Effect<Array<AppliedMigration>, DisasterRecoveryError> =>\n sql<{ readonly migration_id: number; readonly name: string }>`\n SELECT migration_id, name FROM flows_migrations ORDER BY migration_id ASC\n `.withoutTransform.pipe(\n Effect.mapError(sqlFailure(method, \"reading flows_migrations\")),\n Effect.map((rows) => rows.map((row) => ({ migrationId: Number(row.migration_id), name: row.name })))\n )\n\ninterface SnapshotState {\n readonly migrations: Array<AppliedMigration>\n readonly artifactRoots: ReadonlySet<string>\n}\n\n/** Reads every artifact root from the frozen database, never the live one. */\nconst inspectSnapshot = (\n sql: SqlClient.SqlClient\n): Effect.Effect<SnapshotState, DisasterRecoveryError> =>\n Effect.gen(function*() {\n const migrations = yield* appliedMigrations(sql, \"backup\")\n const cacheRows = yield* sql<{ readonly meta_json: string }>`\n SELECT meta_json FROM flows_step_cache\n `.withoutTransform.pipe(\n Effect.mapError(sqlFailure(\"backup\", \"reading artifact roots from snapshot step cache\"))\n )\n const attemptRows = yield* sql<{\n readonly checkpoint_json: string | null\n readonly meta_json: string\n }>`\n SELECT checkpoint_json, meta_json FROM flows_attempts\n `.withoutTransform.pipe(\n Effect.mapError(sqlFailure(\"backup\", \"reading artifact roots from snapshot attempts\"))\n )\n const artifactRoots = new Set<string>()\n const decodeFailure = (cause: ArtifactRoots.ArtifactRootDecodeError): DisasterRecoveryError =>\n error(\"backup\", \"snapshot_incomplete\", cause.message, cause.cause)\n for (const row of cacheRows) {\n for (\n const digest of yield* ArtifactRoots.rootDigests(\"flows_step_cache\", row.meta_json).pipe(\n Effect.mapError(decodeFailure)\n )\n ) artifactRoots.add(digest)\n }\n for (const row of attemptRows) {\n for (\n const digest of yield* ArtifactRoots.rootDigests(\"flows_attempts\", row.meta_json).pipe(\n Effect.mapError(decodeFailure)\n )\n ) artifactRoots.add(digest)\n for (\n const digest of yield* ArtifactRoots.checkpointDigests(row.checkpoint_json).pipe(\n Effect.mapError(decodeFailure)\n )\n ) artifactRoots.add(digest)\n }\n return { migrations, artifactRoots }\n })\n\n/** Reads and decodes the manifest a backup directory must carry. */\nconst readManifest = (\n fs: FileSystem.FileSystem,\n method: string,\n backupDirectory: string,\n maxFileSizeBytes: number\n): Effect.Effect<BackupManifest, DisasterRecoveryError, Crypto.Crypto> =>\n Effect.gen(function*() {\n const path = `${backupDirectory}/${manifestFileName}`\n const present = yield* fs.exists(path).pipe(Effect.mapError(ioFailure(method, `probing ${path}`)))\n if (!present) {\n return yield* Effect.fail(\n error(method, \"missing_file\", `${path} does not exist — the backup is incomplete or not a backup`, path)\n )\n }\n yield* checkFileSize(fs, method, path, maxFileSizeBytes)\n const text = yield* fs.readFileString(path).pipe(Effect.mapError(ioFailure(method, `reading ${path}`)))\n return yield* Schema.decodeUnknownEffect(ManifestFromJsonString)(text).pipe(\n Effect.mapError((cause) =>\n error(method, \"invalid_manifest\", `${path} does not decode as a backup manifest`, cause)\n )\n )\n })\n\n/** Reads a backup file and refuses bytes that no longer hash to the manifest. */\nconst checkedFile = (\n fs: FileSystem.FileSystem,\n method: string,\n path: string,\n expected: string,\n maxFileSizeBytes: number\n): Effect.Effect<Uint8Array, DisasterRecoveryError, Crypto.Crypto> =>\n Effect.gen(function*() {\n const present = yield* fs.exists(path).pipe(Effect.mapError(ioFailure(method, `probing ${path}`)))\n if (!present) {\n return yield* Effect.fail(error(method, \"missing_file\", `${path} does not exist`, path))\n }\n yield* checkFileSize(fs, method, path, maxFileSizeBytes)\n const bytes = yield* fs.readFile(path).pipe(Effect.mapError(ioFailure(method, `reading ${path}`)))\n const measured = yield* measure(bytes)\n if (measured !== expected) {\n return yield* Effect.fail(\n error(method, \"digest_mismatch\", `${path} hashes to ${measured}, the manifest records ${expected}`, path)\n )\n }\n return bytes\n })\n\n/**\n * Captures a hot backup of the live store into an empty directory.\n *\n * The database is snapshotted first with `VACUUM INTO` — a read transaction\n * under WAL, so live writers are never blocked and the copy is one consistent\n * point in time. The artifact walk runs after it, so every digest the\n * snapshot references is captured (publication precedes reference). Blobs are\n * digest-verified as they are copied; a blob whose bytes no longer hash to\n * its address fails the backup loudly rather than capturing corruption. The\n * manifest is encoded and admitted under the same file-size ceiling before it\n * is written last, so a successful backup remains readable with its own\n * options.\n *\n * With an `objectsDirectory`, the whole capture holds the artifact backup\n * lease, so concurrent sweep deletion cannot remove a referenced blob\n * mid-copy. Without one, no lease is taken; the frozen-root verification\n * still fails the backup loudly when the snapshot references any blob, so an\n * unfenced capture can never report success while missing artifact bytes.\n *\n * @since 0.1.0\n * @category operations\n */\nexport const backup = <R = never, E = never>(\n options: BackupOptions<R, E>\n): Effect.Effect<\n BackupManifest,\n DisasterRecoveryError | E,\n SqlClient.SqlClient | FileSystem.FileSystem | Crypto.Crypto | R\n> =>\n Effect.gen(function*() {\n const limit = fileSizeLimit(\"backup\", options)\n if (typeof limit !== \"number\") return yield* Effect.fail(limit)\n const maxFileSizeBytes = limit\n const sql = yield* Effect.service(SqlClient.SqlClient)\n const fs = yield* FileSystem.FileSystem\n const createdAtMs = yield* Clock.currentTimeMillis.pipe(Effect.map(Math.floor))\n yield* ensureEmptyDirectory(fs, \"backup\", options.directory)\n\n const capture = Effect.gen(function*() {\n const databasePath = `${options.directory}/${databaseFileName}`\n yield* sql`VACUUM INTO ${databasePath}`.pipe(\n Effect.mapError(sqlFailure(\"backup\", `snapshotting the database into ${databasePath}`))\n )\n const snapshot = yield* Effect.flatMap(\n Effect.service(SqlClient.SqlClient),\n inspectSnapshot\n ).pipe(Effect.provide(options.snapshotDatabaseLayer(databasePath)))\n // Opening the frozen file may normalize SQLite journal metadata, so its\n // manifest digest is measured only after the inspection connection closes.\n yield* checkFileSize(fs, \"backup\", databasePath, maxFileSizeBytes)\n const databaseBytes = yield* fs.readFile(databasePath).pipe(\n Effect.mapError(ioFailure(\"backup\", `reading ${databasePath} back`))\n )\n const sha256 = yield* measure(databaseBytes)\n\n const artifacts: Array<ArtifactEntry> = []\n const captured = new Set<string>()\n if (options.objectsDirectory !== undefined) {\n const objectsDirectory = options.objectsDirectory\n const entries = yield* fs.readDirectory(objectsDirectory, { recursive: true }).pipe(\n Effect.mapError(ioFailure(\"backup\", `listing ${objectsDirectory}`))\n )\n for (const entry of [...entries].sort()) {\n const digest = blobEntry(entry)\n if (digest === undefined) continue\n const source = blobPath(objectsDirectory, digest)\n yield* checkFileSize(fs, \"backup\", source, maxFileSizeBytes)\n const bytes = yield* fs.readFile(source).pipe(\n Effect.mapError(ioFailure(\"backup\", `reading ${source}`))\n )\n const measured = yield* measure(bytes)\n if (measured !== digest) {\n return yield* Effect.fail(\n error(\n \"backup\",\n \"artifact_corruption\",\n `${source} hashes to ${measured}; repair the store before backing it up`,\n source\n )\n )\n }\n const target = blobPath(`${options.directory}/${objectsDirectoryName}`, digest)\n yield* fs.makeDirectory(`${options.directory}/${objectsDirectoryName}/${digest.slice(0, 2)}`, {\n recursive: true\n }).pipe(Effect.mapError(ioFailure(\"backup\", `creating the objects directory for ${digest}`)))\n yield* fs.writeFile(target, bytes).pipe(Effect.mapError(ioFailure(\"backup\", `writing ${target}`)))\n artifacts.push({ digest: measured, sizeBytes: bytes.length })\n captured.add(measured)\n }\n }\n\n const missing = [...snapshot.artifactRoots].filter((digest) => !captured.has(digest)).sort()\n if (missing.length > 0) {\n return yield* Effect.fail(\n error(\n \"backup\",\n \"snapshot_incomplete\",\n `the frozen database references ${missing.length} artifact blobs absent from the backup`,\n { missing }\n )\n )\n }\n\n const manifest: BackupManifest = {\n formatVersion: 1,\n createdAtMs,\n database: {\n file: databaseFileName,\n sha256,\n sizeBytes: databaseBytes.length,\n migrations: snapshot.migrations\n },\n artifacts\n }\n const manifestPath = `${options.directory}/${manifestFileName}`\n const manifestBytes = new TextEncoder().encode(`${JSON.stringify(manifest, null, 2)}\\n`)\n if (manifestBytes.byteLength > maxFileSizeBytes) {\n const message =\n `${manifestPath} is ${manifestBytes.byteLength} bytes, which exceeds the ${maxFileSizeBytes}-byte file limit`\n return yield* Effect.fail(error(\"backup\", \"io\", message, new RangeError(message)))\n }\n yield* fs.writeFile(manifestPath, manifestBytes).pipe(\n Effect.mapError(ioFailure(\"backup\", `writing ${manifestFileName}`))\n )\n return manifest\n })\n\n // The directory was empty when the capture began, so everything in it is\n // this capture's: the snapshot, any SQLite sidecar the inspection\n // connection left, and the copied blobs. A failed capture removes them and\n // leaves the directory reusable for a retry. Cleanup is best effort; the\n // original failure is the one reported.\n const discardPartial = fs.readDirectory(options.directory).pipe(\n Effect.flatMap((entries) =>\n Effect.forEach(\n entries,\n (entry) => fs.remove(`${options.directory}/${entry}`, { recursive: true, force: true }),\n { discard: true }\n )\n ),\n Effect.ignore\n )\n\n return yield* (options.objectsDirectory === undefined\n ? capture\n : ArtifactBackupLease.withLease(\n fs,\n options.objectsDirectory,\n capture,\n ioFailure(\"backup\", `coordinating artifact backup in ${options.objectsDirectory}`)\n )).pipe(Effect.onError(() => discardPartial))\n }).pipe(Effect.withSpan(\"DisasterRecovery.backup\"))\n\n/**\n * Verifies a backup directory against its manifest without restoring it:\n * the manifest decodes, the database snapshot hashes to its recorded digest,\n * and every listed artifact blob is present and hashes to its address.\n *\n * @since 0.1.0\n * @category operations\n */\nexport const verify = Effect.fn(\"DisasterRecovery.verify\")(function*(\n backupDirectory: string,\n options: FileSizeOptions = {}\n) {\n const limit = fileSizeLimit(\"verify\", options)\n if (typeof limit !== \"number\") return yield* Effect.fail(limit)\n const maxFileSizeBytes = limit\n const fs = yield* FileSystem.FileSystem\n const manifest = yield* readManifest(fs, \"verify\", backupDirectory, maxFileSizeBytes)\n yield* checkedFile(\n fs,\n \"verify\",\n `${backupDirectory}/${manifest.database.file}`,\n manifest.database.sha256,\n maxFileSizeBytes\n )\n for (const entry of manifest.artifacts) {\n yield* checkedFile(\n fs,\n \"verify\",\n blobPath(`${backupDirectory}/${objectsDirectoryName}`, entry.digest),\n entry.digest,\n maxFileSizeBytes\n )\n }\n return manifest\n})\n\n/**\n * Restores a verified backup into an empty target directory.\n *\n * Every byte is re-verified against the manifest as it is copied, so a backup\n * that rotted in storage is refused rather than restored. The target holds\n * the database file, the objects directory, and a `restored.json` marker; it\n * is a fresh store directory to compose `NodeDatabase.layer` and\n * `ArtifactStore.layerFileSystem` over. The database is restored with the\n * pre-backup owner fences still in place — run {@link fence} against it\n * before any engine adopts it.\n *\n * @since 0.1.0\n * @category operations\n */\nexport const restore = Effect.fn(\"DisasterRecovery.restore\")(function*(options: RestoreOptions) {\n const limit = fileSizeLimit(\"restore\", options)\n if (typeof limit !== \"number\") return yield* Effect.fail(limit)\n const maxFileSizeBytes = limit\n const fs = yield* FileSystem.FileSystem\n const manifest = yield* readManifest(fs, \"restore\", options.backupDirectory, maxFileSizeBytes)\n yield* ensureEmptyDirectory(fs, \"restore\", options.targetDirectory)\n\n const databaseBytes = yield* checkedFile(\n fs,\n \"restore\",\n `${options.backupDirectory}/${manifest.database.file}`,\n manifest.database.sha256,\n maxFileSizeBytes\n )\n const databaseFile = `${options.targetDirectory}/${databaseFileName}`\n yield* fs.writeFile(databaseFile, databaseBytes).pipe(\n Effect.mapError(ioFailure(\"restore\", `writing ${databaseFile}`))\n )\n\n const objectsDirectory = `${options.targetDirectory}/${objectsDirectoryName}`\n for (const entry of manifest.artifacts) {\n const bytes = yield* checkedFile(\n fs,\n \"restore\",\n blobPath(`${options.backupDirectory}/${objectsDirectoryName}`, entry.digest),\n entry.digest,\n maxFileSizeBytes\n )\n yield* fs.makeDirectory(`${objectsDirectory}/${entry.digest.slice(0, 2)}`, { recursive: true }).pipe(\n Effect.mapError(ioFailure(\"restore\", `creating the objects directory for ${entry.digest}`))\n )\n yield* fs.writeFile(blobPath(objectsDirectory, entry.digest), bytes).pipe(\n Effect.mapError(ioFailure(\"restore\", `writing the blob for ${entry.digest}`))\n )\n }\n\n const restoredAtMs = yield* Clock.currentTimeMillis.pipe(Effect.map(Math.floor))\n const marker = {\n formatVersion: 1,\n restoredAtMs,\n backupCreatedAtMs: manifest.createdAtMs,\n databaseSha256: manifest.database.sha256\n }\n yield* fs.writeFileString(\n `${options.targetDirectory}/${restoredMarkerFileName}`,\n `${JSON.stringify(marker, null, 2)}\\n`\n ).pipe(Effect.mapError(ioFailure(\"restore\", `writing ${restoredMarkerFileName}`)))\n return { databaseFile, objectsDirectory, manifest } satisfies RestoredStore\n})\n\n/**\n * Bumps the restored store's fencing epoch so pre-backup owners are fenced\n * out.\n *\n * The store's fence is `OwnerId` equality, so the epoch bump is fence\n * invalidation: in one serialized write transaction every pending claim is\n * cleared, every run that was `running` at backup time is suspended with its\n * owner and heartbeat cleared, and every consensus lease is deleted. A\n * surviving pre-backup owner then fails every fenced operation against the\n * restored store — `heartbeat` and `transitionOwned` report `FenceLost`,\n * fenced journal appends fail `fence_lost` — exactly as a stale Temporal owner\n * fails its `rangeID` compare-and-swap. The suspended runs are claimable\n * immediately, without waiting out the heartbeat staleness cutoff and without\n * liveness evidence.\n *\n * Every migration recorded in the manifest must remain applied with the same\n * ID and namespaced name. Forward additions within any installed block are\n * compatible, including engine-store `3006` below an existing plan `4003`;\n * the current database layer applies them on open. A missing or renamed\n * historical entry fails `schema_mismatch` before ownership is cleared.\n *\n * In-flight attempt rows are deliberately untouched: attempt writes are\n * fenced on the run row's ownership, and the resuming engine adopts or\n * retries them under its own fresh owner.\n *\n * @since 0.1.0\n * @category operations\n */\nexport const fence = Effect.fn(\"DisasterRecovery.fence\")(function*(manifest: BackupManifest) {\n const sql = yield* Effect.service(SqlClient.SqlClient)\n const writer = yield* DurableWriter\n const applied = yield* appliedMigrations(sql, \"fence\")\n const expected = manifest.database.migrations\n const key = (migration: AppliedMigration): string => `${migration.migrationId}:${migration.name}`\n // IDs identify migrations within their reserved blocks. Compare historical\n // identities independently so additions in lower blocks cannot shift them.\n const appliedById = new Map(applied.map((migration) => [migration.migrationId, migration.name]))\n if (expected.some((migration) => appliedById.get(migration.migrationId) !== migration.name)) {\n return yield* Effect.fail(\n error(\n \"fence\",\n \"schema_mismatch\",\n `the restored database applied [${applied.map(key).join(\", \")}] but the manifest records [${\n expected.map(key).join(\", \")\n }]`,\n { applied, expected }\n )\n )\n }\n return yield* writer.write(\n Effect.gen(function*() {\n const claims = yield* sql<{ readonly run_id: string }>`\n UPDATE flows_runs\n SET\n claim_host_id = NULL,\n claim_pid = NULL,\n claim_nonce = NULL,\n claimed_at_ms = NULL\n WHERE claim_host_id IS NOT NULL\n RETURNING run_id\n `.withoutTransform\n const running = yield* sql<{ readonly run_id: string }>`\n UPDATE flows_runs\n SET\n status = 'suspended',\n owner_host_id = NULL,\n owner_pid = NULL,\n owner_nonce = NULL,\n heartbeat_at_ms = NULL,\n waiting_reason = 'released',\n waiting_wake_at_ms = NULL,\n waiting_token = NULL\n WHERE status = 'running'\n RETURNING run_id\n `.withoutTransform\n yield* sql`DELETE FROM flows_consensus_leases`.withoutTransform\n return { clearedClaims: claims.length, suspendedRuns: running.length } satisfies FenceSummary\n })\n ).pipe(Effect.mapError(sqlFailure(\"fence\", \"invalidating the persisted fences\")))\n})\n\n/**\n * Restores a backup and immediately fences the restored database.\n *\n * This is the one-shot operator API for disaster recovery. The database\n * driver itself still arrives through a caller-supplied Layer so this module\n * remains host-neutral and browser-bundleable; the bundled Node script passes\n * `NodeDatabase.layer` for the restored file.\n *\n * @since 0.1.0\n * @category operations\n */\nexport const restoreAndFence = <R = never, E = never>(\n options: RestoreAndFenceOptions<R, E>\n): Effect.Effect<FencedRestoredStore, DisasterRecoveryError | E, FileSystem.FileSystem | Crypto.Crypto | R> =>\n Effect.gen(function*() {\n const restored = yield* restore(options)\n const summary = yield* fence(restored.manifest).pipe(\n Effect.provide(options.databaseLayer(restored.databaseFile))\n )\n return { ...restored, fence: summary } satisfies FencedRestoredStore\n })\n"]}
1
+ {"version":3,"file":"DisasterRecovery.js","sourceRoot":"","sources":["../../src/DisasterRecovery.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Hot backup, verified restore, and restore-time fencing for the durable\n * flows store.\n *\n * A backup captures the whole SQLite database — journal, run store, step\n * cache, engine state, and the out-of-ladder objects created by\n * `internal/EngineStateSchema.ts` — with `VACUUM INTO`, which reads one\n * consistent snapshot under WAL while writers stay live. The content-addressed\n * artifact blobs are copied beside it, digest-verified, after the database\n * snapshot: results reference artifacts only after publication\n * (`packages/smithers/flows/step-cache/docs/api.md`), so a blob walk taken after the\n * database snapshot is a superset of every digest the snapshot references.\n * A manifest written last records the digests a restore must verify, so a\n * partial backup is detectable by its missing manifest.\n *\n * Restore never lets a pre-backup owner resurrect. The fencing token is the\n * `OwnerId` triple mirrored on `flows_runs` and arbitrated by the journal's\n * `flows_consensus_leases` — Temporal's shard `rangeID` equality check reduced\n * to one SQL predicate (`reference/temporal`\n * `service/history/shard/context_impl.go`, `renewRangeLocked`). Temporal\n * fences a restored shard by bumping the persisted epoch above anything a\n * surviving owner could hold; the equivalent bump for an identity fence is\n * {@link fence}: clear every persisted claim, owner, and consensus lease in\n * one serialized write transaction, so every pre-backup `OwnerId` fails its\n * equality compare-and-swap (`FenceLost`, journal `fence_lost`) and the\n * restored runs are claimable immediately instead of after the heartbeat\n * staleness cutoff.\n *\n * Host access arrives through Effect's `FileSystem` and `SqlClient` tags and\n * hashing through the injected `Crypto` service, so the module carries no\n * platform binding. The SQL dialect is SQLite — a non-SQLite backend fails\n * with the `sql` code rather than pretending to snapshot. Every file is\n * stat-checked before it is buffered for hashing or copying. The configurable\n * ceiling defaults to {@link defaultMaxFileSizeBytes}; an oversized file fails\n * with the typed `io` code instead of reaching the host's allocation limit.\n *\n * @since 0.1.0\n */\n\nimport * as ArtifactBackupLease from \"@smthrs/artifacts/ArtifactBackupLease\"\nimport { Sha256 } from \"@smthrs/crypto\"\nimport { DurableWriter } from \"@smthrs/database/DurableWriter\"\nimport * as Clock from \"effect/Clock\"\nimport type * as Crypto from \"effect/Crypto\"\nimport * as Effect from \"effect/Effect\"\nimport * as FileSystem from \"effect/FileSystem\"\nimport type * as Layer from \"effect/Layer\"\nimport * as Schema from \"effect/Schema\"\nimport * as SqlClient from \"effect/sql/SqlClient\"\nimport * as ArtifactRoots from \"./internal/ArtifactRoots.ts\"\n\n/**\n * The database file name inside a backup directory and a restored store\n * directory.\n *\n * @since 0.1.0\n * @category constants\n */\nexport const databaseFileName = \"store.sqlite3\"\n\n/**\n * The manifest file name inside a backup directory. It is written last, so\n * its presence marks a complete backup.\n *\n * @since 0.1.0\n * @category constants\n */\nexport const manifestFileName = \"manifest.json\"\n\n/**\n * The artifact blob directory name inside a backup directory and a restored\n * store directory, laid out with the store's own two-hex-prefix fanout.\n *\n * @since 0.1.0\n * @category constants\n */\nexport const objectsDirectoryName = \"objects\"\n\n/**\n * The marker file a restore writes into the target directory, recording when\n * the restore happened and which backup it came from.\n *\n * @since 0.1.0\n * @category constants\n */\nexport const restoredMarkerFileName = \"restored.json\"\n\n/**\n * Default ceiling for one backup file read into memory: 512 MiB.\n *\n * @since 1.0.0\n * @category constants\n */\nexport const defaultMaxFileSizeBytes = 512 * 1024 * 1024\n\n/**\n * One applied row of the shared `flows_migrations` table, recorded at backup\n * time and re-verified at fence time as the schema version of the snapshot.\n *\n * @since 0.1.0\n * @category schemas\n */\nexport const AppliedMigration = Schema.Struct({\n migrationId: Schema.Number,\n name: Schema.String\n})\n\n/**\n * One applied row of the shared `flows_migrations` table.\n *\n * @since 0.1.0\n * @category models\n */\nexport type AppliedMigration = typeof AppliedMigration.Type\n\n/**\n * One captured artifact blob. `sizeBytes` is informational for operators; the\n * digest is the integrity check.\n *\n * @since 0.1.0\n * @category schemas\n */\nexport const ArtifactEntry = Schema.Struct({\n digest: Sha256.Digest,\n sizeBytes: Schema.Number\n})\n\n/**\n * One captured artifact blob.\n *\n * @since 0.1.0\n * @category models\n */\nexport type ArtifactEntry = typeof ArtifactEntry.Type\n\n/**\n * The verification contract of a backup: the database snapshot's digest and\n * schema version, and the digest of every captured artifact blob.\n *\n * @since 0.1.0\n * @category schemas\n */\nexport const BackupManifest = Schema.Struct({\n formatVersion: Schema.Literal(1),\n createdAtMs: Schema.Number,\n database: Schema.Struct({\n file: Schema.Literal(databaseFileName),\n sha256: Sha256.Digest,\n sizeBytes: Schema.Number,\n migrations: Schema.Array(AppliedMigration)\n }),\n artifacts: Schema.Array(ArtifactEntry)\n})\n\n/**\n * The verification contract of a backup.\n *\n * @since 0.1.0\n * @category models\n */\nexport type BackupManifest = typeof BackupManifest.Type\n\n/** JSON text carrying a decoded backup manifest. */\nconst ManifestFromJsonString = Schema.fromJsonString(BackupManifest)\n\n/**\n * Stable failure codes surfaced by backup, verify, restore, and fence.\n * `invalid_options` reports a public option that cannot be admitted safely.\n *\n * @since 0.1.0\n * @category errors\n */\nexport const DisasterRecoveryErrorCode = Schema.Literals([\n \"invalid_options\",\n \"not_empty\",\n \"invalid_manifest\",\n \"missing_file\",\n \"digest_mismatch\",\n \"artifact_corruption\",\n \"snapshot_incomplete\",\n \"schema_mismatch\",\n \"io\",\n \"sql\"\n])\n\n/**\n * A stable disaster-recovery failure code.\n *\n * @since 0.1.0\n * @category errors\n */\nexport type DisasterRecoveryErrorCode = typeof DisasterRecoveryErrorCode.Type\n\n/**\n * A normalized backup or restore failure. The identity string equals this\n * module's package path, per the house rule that a new identity is the\n * defining module path.\n *\n * @since 0.1.0\n * @category errors\n */\nexport class DisasterRecoveryError extends Schema.TaggedError<DisasterRecoveryError>()(\n \"@smthrs/engine-store/DisasterRecoveryError\",\n {\n code: DisasterRecoveryErrorCode,\n method: Schema.String,\n message: Schema.String,\n cause: Schema.Unknown\n }\n) {}\n\n/**\n * Shared buffering limit for backup, verification, and restore operations.\n *\n * @since 1.0.0\n * @category models\n */\nexport interface FileSizeOptions {\n /**\n * Largest file, in bytes, that backup, verification, or restore may buffer.\n * The value must be a finite non-negative safe integer. Defaults to\n * {@link defaultMaxFileSizeBytes}.\n */\n readonly maxFileSizeBytes?: number | undefined\n}\n\n/**\n * Where a backup is written and which artifact store it captures.\n *\n * @since 0.1.0\n * @category models\n */\nexport interface BackupOptions<R = never, E = never> extends FileSizeOptions {\n /** The directory the backup is written into. Created when absent; must be empty when present. */\n readonly directory: string\n /**\n * The live store's content-addressed objects directory (the\n * `ArtifactStore.layerFileSystem` directory). When set, the whole capture\n * holds the cross-process artifact backup lease, so a concurrent\n * `ArtifactGc.gc` sweep cannot delete a blob between the database snapshot\n * and its copy. Absent means the composition has no filesystem artifact\n * tier: the backup carries no blobs and takes no lease, because there is\n * no objects directory to fence, and a frozen snapshot that references any\n * artifact digest fails with `snapshot_incomplete` instead of reporting\n * success. A filesystem-backed composition must therefore pass the same\n * directory it gives `ArtifactStore.layerFileSystem`.\n */\n readonly objectsDirectory?: string | undefined\n /**\n * Opens the frozen `VACUUM INTO` file so backup can enumerate artifact\n * roots and migrations from that exact snapshot, independently of the live\n * connection. The host should open this file without applying migrations.\n */\n readonly snapshotDatabaseLayer: (databaseFile: string) => Layer.Layer<SqlClient.SqlClient, E, R>\n}\n\n/**\n * Which backup to restore and where the restored store lands.\n *\n * @since 0.1.0\n * @category models\n */\nexport interface RestoreOptions extends FileSizeOptions {\n /** The directory a previous {@link backup} wrote. */\n readonly backupDirectory: string\n /** The fresh directory the restored store is written into. Created when absent; must be empty when present. */\n readonly targetDirectory: string\n}\n\n/**\n * Which backup to restore and how to open the freshly restored SQLite file\n * for restore-time fencing.\n *\n * @since 0.1.0\n * @category models\n */\nexport interface RestoreAndFenceOptions<R = never, E = never> extends RestoreOptions {\n /**\n * Builds the database and durable-writer layer for the newly restored file.\n * This keeps database opening as the caller's host composition rather than\n * importing a Node-only driver into this browser-safe module.\n */\n readonly databaseLayer: (databaseFile: string) => Layer.Layer<DurableWriter | SqlClient.SqlClient, E, R>\n}\n\n/**\n * The restored store's on-disk locations, ready to compose database and\n * artifact layers over.\n *\n * @since 0.1.0\n * @category models\n */\nexport interface RestoredStore {\n readonly databaseFile: string\n readonly objectsDirectory: string\n readonly manifest: BackupManifest\n}\n\n/**\n * A restored store after restore-time fencing has invalidated pre-backup\n * owners.\n *\n * @since 0.1.0\n * @category models\n */\nexport interface FencedRestoredStore extends RestoredStore {\n readonly fence: FenceSummary\n}\n\n/**\n * What {@link fence} invalidated on the restored store.\n *\n * @since 0.1.0\n * @category models\n */\nexport interface FenceSummary {\n /** Runs whose pending claim columns were cleared. */\n readonly clearedClaims: number\n /** Runs that were `running` at backup time, now suspended with their owner fence cleared. */\n readonly suspendedRuns: number\n}\n\nconst error = (\n method: string,\n code: DisasterRecoveryErrorCode,\n message: string,\n cause: unknown\n): DisasterRecoveryError =>\n new DisasterRecoveryError({\n code,\n method,\n message: `${code}: DisasterRecovery.${method}: ${message}`,\n cause\n })\n\n/**\n * One mapper per failure family, shared across every call site so the\n * vocabulary stays uniform.\n */\nconst ioFailure = (method: string, subject: string) => (cause: unknown): DisasterRecoveryError =>\n error(method, \"io\", `the host filesystem refused ${subject}`, cause)\n\nconst sqlFailure = (method: string, subject: string) => (cause: unknown): DisasterRecoveryError =>\n error(method, \"sql\", `the database refused ${subject}`, cause)\n\n/** Hashes bytes with the injected crypto service; a broken host is a defect. */\nconst measure = (bytes: Uint8Array): Effect.Effect<typeof Sha256.Digest.Type, never, Crypto.Crypto> =>\n Schema.decodeUnknownEffect(Sha256)(bytes).pipe(Effect.orDie)\n\n/** Resolves and validates the shared whole-file buffering ceiling. */\nconst fileSizeLimit = (\n method: string,\n options: FileSizeOptions\n): number | DisasterRecoveryError => {\n const limit = options.maxFileSizeBytes\n if (limit === undefined) return defaultMaxFileSizeBytes\n if (Number.isSafeInteger(limit) && limit >= 0) return limit\n return error(\n method,\n \"invalid_options\",\n `maxFileSizeBytes must be a finite non-negative safe integer; received ${String(limit)}`,\n limit\n )\n}\n\n/**\n * Refuses an oversized file before a whole-file read allocates its buffer.\n * Every caller admits `limit` through `fileSizeLimit`, so `BigInt` cannot\n * reject it.\n */\nconst checkFileSize = (\n fs: FileSystem.FileSystem,\n method: string,\n path: string,\n limit: number\n): Effect.Effect<void, DisasterRecoveryError> =>\n Effect.gen(function*() {\n const info = yield* fs.stat(path).pipe(Effect.mapError(ioFailure(method, `stating ${path}`)))\n if (info.size <= BigInt(limit)) return\n const message = `${path} is ${info.size} bytes, which exceeds the ${limit}-byte file limit`\n return yield* Effect.fail(error(method, \"io\", message, new RangeError(message)))\n })\n\n/**\n * Admits a target directory that does not exist yet — created recursively —\n * or exists empty. Anything else is refused rather than merged over.\n */\nconst ensureEmptyDirectory = (\n fs: FileSystem.FileSystem,\n method: string,\n directory: string\n): Effect.Effect<void, DisasterRecoveryError> =>\n Effect.gen(function*() {\n const present = yield* fs.exists(directory).pipe(Effect.mapError(ioFailure(method, `probing ${directory}`)))\n if (!present) {\n return yield* fs.makeDirectory(directory, { recursive: true }).pipe(\n Effect.mapError(ioFailure(method, `creating ${directory}`))\n )\n }\n const entries = yield* fs.readDirectory(directory).pipe(\n Effect.mapError(ioFailure(method, `listing ${directory}`))\n )\n if (entries.length > 0) {\n return yield* Effect.fail(\n error(method, \"not_empty\", `${directory} already holds ${entries.length} entries`, directory)\n )\n }\n })\n\n/**\n * Removes every entry of a directory this module filled after admitting it\n * empty, leaving the directory reusable for a retry. Cleanup is best effort;\n * the caller reports the original failure.\n */\nconst discardEntries = (fs: FileSystem.FileSystem, directory: string): Effect.Effect<void> =>\n fs.readDirectory(directory).pipe(\n Effect.flatMap((entries) =>\n Effect.forEach(\n entries,\n (entry) => fs.remove(`${directory}/${entry}`, { recursive: true, force: true }),\n { discard: true }\n )\n ),\n Effect.ignore\n )\n\n/**\n * A relative blob path in the store's fanout layout: a two-hex directory\n * holding a 64-hex digest that begins with it. Anything else under the\n * objects directory — a scratch `.tmp-*` file, the fanout directory itself —\n * is not part of the store's address space.\n */\nconst blobEntry = (entry: string): string | undefined => {\n const match = entry.match(/^([0-9a-f]{2})[/\\\\]([0-9a-f]{64})$/)\n if (match === null || !match[2]!.startsWith(match[1]!)) return undefined\n return match[2]\n}\n\nconst blobPath = (directory: string, digest: string): string => `${directory}/${digest.slice(0, 2)}/${digest}`\n\n/** The applied rows of the shared migrations table, in ladder order. */\nconst appliedMigrations = (\n sql: SqlClient.SqlClient,\n method: string\n): Effect.Effect<Array<AppliedMigration>, DisasterRecoveryError> =>\n sql<{ readonly migration_id: number; readonly name: string }>`\n SELECT migration_id, name FROM flows_migrations ORDER BY migration_id ASC\n `.withoutTransform.pipe(\n Effect.mapError(sqlFailure(method, \"reading flows_migrations\")),\n Effect.map((rows) => rows.map((row) => ({ migrationId: Number(row.migration_id), name: row.name })))\n )\n\ninterface SnapshotState {\n readonly migrations: Array<AppliedMigration>\n readonly artifactRoots: ReadonlySet<string>\n}\n\n/** Reads every artifact root from the frozen database, never the live one. */\nconst inspectSnapshot = (\n sql: SqlClient.SqlClient\n): Effect.Effect<SnapshotState, DisasterRecoveryError> =>\n Effect.gen(function*() {\n const migrations = yield* appliedMigrations(sql, \"backup\")\n const cacheRows = yield* sql<{ readonly meta_json: string }>`\n SELECT meta_json FROM flows_step_cache\n `.withoutTransform.pipe(\n Effect.mapError(sqlFailure(\"backup\", \"reading artifact roots from snapshot step cache\"))\n )\n const attemptRows = yield* sql<{\n readonly checkpoint_json: string | null\n readonly meta_json: string\n }>`\n SELECT checkpoint_json, meta_json FROM flows_attempts\n `.withoutTransform.pipe(\n Effect.mapError(sqlFailure(\"backup\", \"reading artifact roots from snapshot attempts\"))\n )\n const artifactRoots = new Set<string>()\n const decodeFailure = (cause: ArtifactRoots.ArtifactRootDecodeError): DisasterRecoveryError =>\n error(\"backup\", \"snapshot_incomplete\", cause.message, cause.cause)\n for (const row of cacheRows) {\n for (\n const digest of yield* ArtifactRoots.rootDigests(\"flows_step_cache\", row.meta_json).pipe(\n Effect.mapError(decodeFailure)\n )\n ) artifactRoots.add(digest)\n }\n for (const row of attemptRows) {\n for (\n const digest of yield* ArtifactRoots.rootDigests(\"flows_attempts\", row.meta_json).pipe(\n Effect.mapError(decodeFailure)\n )\n ) artifactRoots.add(digest)\n for (\n const digest of yield* ArtifactRoots.checkpointDigests(row.checkpoint_json).pipe(\n Effect.mapError(decodeFailure)\n )\n ) artifactRoots.add(digest)\n }\n return { migrations, artifactRoots }\n })\n\n/** Reads and decodes the manifest a backup directory must carry. */\nconst readManifest = (\n fs: FileSystem.FileSystem,\n method: string,\n backupDirectory: string,\n maxFileSizeBytes: number\n): Effect.Effect<BackupManifest, DisasterRecoveryError, Crypto.Crypto> =>\n Effect.gen(function*() {\n const path = `${backupDirectory}/${manifestFileName}`\n const present = yield* fs.exists(path).pipe(Effect.mapError(ioFailure(method, `probing ${path}`)))\n if (!present) {\n return yield* Effect.fail(\n error(method, \"missing_file\", `${path} does not exist — the backup is incomplete or not a backup`, path)\n )\n }\n yield* checkFileSize(fs, method, path, maxFileSizeBytes)\n const text = yield* fs.readFileString(path).pipe(Effect.mapError(ioFailure(method, `reading ${path}`)))\n return yield* Schema.decodeUnknownEffect(ManifestFromJsonString)(text).pipe(\n Effect.mapError((cause) =>\n error(method, \"invalid_manifest\", `${path} does not decode as a backup manifest`, cause)\n )\n )\n })\n\n/** Reads a backup file and refuses bytes that no longer hash to the manifest. */\nconst checkedFile = (\n fs: FileSystem.FileSystem,\n method: string,\n path: string,\n expected: string,\n maxFileSizeBytes: number\n): Effect.Effect<Uint8Array, DisasterRecoveryError, Crypto.Crypto> =>\n Effect.gen(function*() {\n const present = yield* fs.exists(path).pipe(Effect.mapError(ioFailure(method, `probing ${path}`)))\n if (!present) {\n return yield* Effect.fail(error(method, \"missing_file\", `${path} does not exist`, path))\n }\n yield* checkFileSize(fs, method, path, maxFileSizeBytes)\n const bytes = yield* fs.readFile(path).pipe(Effect.mapError(ioFailure(method, `reading ${path}`)))\n const measured = yield* measure(bytes)\n if (measured !== expected) {\n return yield* Effect.fail(\n error(method, \"digest_mismatch\", `${path} hashes to ${measured}, the manifest records ${expected}`, path)\n )\n }\n return bytes\n })\n\n/**\n * Captures a hot backup of the live store into an empty directory.\n *\n * The database is snapshotted first with `VACUUM INTO` — a read transaction\n * under WAL, so live writers are never blocked and the copy is one consistent\n * point in time. The artifact walk runs after it, so every digest the\n * snapshot references is captured (publication precedes reference). Blobs are\n * digest-verified as they are copied; a blob whose bytes no longer hash to\n * its address fails the backup loudly rather than capturing corruption. The\n * manifest is encoded and admitted under the same file-size ceiling before it\n * is written last, so a successful backup remains readable with its own\n * options.\n *\n * With an `objectsDirectory`, the whole capture holds the artifact backup\n * lease, so concurrent sweep deletion cannot remove a referenced blob\n * mid-copy. Without one, no lease is taken; the frozen-root verification\n * still fails the backup loudly when the snapshot references any blob, so an\n * unfenced capture can never report success while missing artifact bytes.\n *\n * @since 0.1.0\n * @category operations\n */\nexport const backup = <R = never, E = never>(\n options: BackupOptions<R, E>\n): Effect.Effect<\n BackupManifest,\n DisasterRecoveryError | E,\n SqlClient.SqlClient | FileSystem.FileSystem | Crypto.Crypto | R\n> =>\n Effect.gen(function*() {\n const limit = fileSizeLimit(\"backup\", options)\n if (typeof limit !== \"number\") return yield* Effect.fail(limit)\n const maxFileSizeBytes = limit\n const sql = yield* Effect.service(SqlClient.SqlClient)\n const fs = yield* FileSystem.FileSystem\n const createdAtMs = yield* Clock.currentTimeMillis.pipe(Effect.map(Math.floor))\n yield* ensureEmptyDirectory(fs, \"backup\", options.directory)\n\n const capture = Effect.gen(function*() {\n const databasePath = `${options.directory}/${databaseFileName}`\n yield* sql`VACUUM INTO ${databasePath}`.pipe(\n Effect.mapError(sqlFailure(\"backup\", `snapshotting the database into ${databasePath}`))\n )\n const snapshot = yield* Effect.flatMap(\n Effect.service(SqlClient.SqlClient),\n inspectSnapshot\n ).pipe(Effect.provide(options.snapshotDatabaseLayer(databasePath)))\n // Opening the frozen file may normalize SQLite journal metadata, so its\n // manifest digest is measured only after the inspection connection closes.\n yield* checkFileSize(fs, \"backup\", databasePath, maxFileSizeBytes)\n const databaseBytes = yield* fs.readFile(databasePath).pipe(\n Effect.mapError(ioFailure(\"backup\", `reading ${databasePath} back`))\n )\n const sha256 = yield* measure(databaseBytes)\n\n const artifacts: Array<ArtifactEntry> = []\n const captured = new Set<string>()\n if (options.objectsDirectory !== undefined) {\n const objectsDirectory = options.objectsDirectory\n const entries = yield* fs.readDirectory(objectsDirectory, { recursive: true }).pipe(\n Effect.mapError(ioFailure(\"backup\", `listing ${objectsDirectory}`))\n )\n for (const entry of [...entries].sort()) {\n const digest = blobEntry(entry)\n if (digest === undefined) continue\n const source = blobPath(objectsDirectory, digest)\n yield* checkFileSize(fs, \"backup\", source, maxFileSizeBytes)\n const bytes = yield* fs.readFile(source).pipe(\n Effect.mapError(ioFailure(\"backup\", `reading ${source}`))\n )\n const measured = yield* measure(bytes)\n if (measured !== digest) {\n return yield* Effect.fail(\n error(\n \"backup\",\n \"artifact_corruption\",\n `${source} hashes to ${measured}; repair the store before backing it up`,\n source\n )\n )\n }\n const target = blobPath(`${options.directory}/${objectsDirectoryName}`, digest)\n yield* fs.makeDirectory(`${options.directory}/${objectsDirectoryName}/${digest.slice(0, 2)}`, {\n recursive: true\n }).pipe(Effect.mapError(ioFailure(\"backup\", `creating the objects directory for ${digest}`)))\n yield* fs.writeFile(target, bytes).pipe(Effect.mapError(ioFailure(\"backup\", `writing ${target}`)))\n artifacts.push({ digest: measured, sizeBytes: bytes.length })\n captured.add(measured)\n }\n }\n\n const missing = [...snapshot.artifactRoots].filter((digest) => !captured.has(digest)).sort()\n if (missing.length > 0) {\n return yield* Effect.fail(\n error(\n \"backup\",\n \"snapshot_incomplete\",\n `the frozen database references ${missing.length} artifact blobs absent from the backup`,\n { missing }\n )\n )\n }\n\n const manifest: BackupManifest = {\n formatVersion: 1,\n createdAtMs,\n database: {\n file: databaseFileName,\n sha256,\n sizeBytes: databaseBytes.length,\n migrations: snapshot.migrations\n },\n artifacts\n }\n const manifestPath = `${options.directory}/${manifestFileName}`\n const manifestBytes = new TextEncoder().encode(`${JSON.stringify(manifest, null, 2)}\\n`)\n if (manifestBytes.byteLength > maxFileSizeBytes) {\n const message =\n `${manifestPath} is ${manifestBytes.byteLength} bytes, which exceeds the ${maxFileSizeBytes}-byte file limit`\n return yield* Effect.fail(error(\"backup\", \"io\", message, new RangeError(message)))\n }\n yield* fs.writeFile(manifestPath, manifestBytes).pipe(\n Effect.mapError(ioFailure(\"backup\", `writing ${manifestFileName}`))\n )\n return manifest\n })\n\n // The directory was empty when the capture began, so everything in it is\n // this capture's: the snapshot, any SQLite sidecar the inspection\n // connection left, and the copied blobs. A failed capture removes them and\n // leaves the directory reusable for a retry.\n const discardPartial = discardEntries(fs, options.directory)\n\n return yield* (options.objectsDirectory === undefined\n ? capture\n : ArtifactBackupLease.withLease(\n fs,\n options.objectsDirectory,\n capture,\n ioFailure(\"backup\", `coordinating artifact backup in ${options.objectsDirectory}`)\n )).pipe(Effect.onError(() => discardPartial))\n }).pipe(Effect.withSpan(\"DisasterRecovery.backup\"))\n\n/**\n * Verifies a backup directory against its manifest without restoring it:\n * the manifest decodes, the database snapshot hashes to its recorded digest,\n * and every listed artifact blob is present and hashes to its address.\n *\n * @since 0.1.0\n * @category operations\n */\nexport const verify = Effect.fn(\"DisasterRecovery.verify\")(function*(\n backupDirectory: string,\n options: FileSizeOptions = {}\n) {\n const limit = fileSizeLimit(\"verify\", options)\n if (typeof limit !== \"number\") return yield* Effect.fail(limit)\n const maxFileSizeBytes = limit\n const fs = yield* FileSystem.FileSystem\n const manifest = yield* readManifest(fs, \"verify\", backupDirectory, maxFileSizeBytes)\n yield* checkedFile(\n fs,\n \"verify\",\n `${backupDirectory}/${manifest.database.file}`,\n manifest.database.sha256,\n maxFileSizeBytes\n )\n for (const entry of manifest.artifacts) {\n yield* checkedFile(\n fs,\n \"verify\",\n blobPath(`${backupDirectory}/${objectsDirectoryName}`, entry.digest),\n entry.digest,\n maxFileSizeBytes\n )\n }\n return manifest\n})\n\n/** Copies a verified backup into an admitted empty target directory. */\nconst writeRestoredStore = (\n fs: FileSystem.FileSystem,\n options: RestoreOptions,\n manifest: BackupManifest,\n maxFileSizeBytes: number\n): Effect.Effect<RestoredStore, DisasterRecoveryError, Crypto.Crypto> =>\n Effect.gen(function*() {\n const databaseBytes = yield* checkedFile(\n fs,\n \"restore\",\n `${options.backupDirectory}/${manifest.database.file}`,\n manifest.database.sha256,\n maxFileSizeBytes\n )\n const databaseFile = `${options.targetDirectory}/${databaseFileName}`\n yield* fs.writeFile(databaseFile, databaseBytes).pipe(\n Effect.mapError(ioFailure(\"restore\", `writing ${databaseFile}`))\n )\n\n const objectsDirectory = `${options.targetDirectory}/${objectsDirectoryName}`\n for (const entry of manifest.artifacts) {\n const bytes = yield* checkedFile(\n fs,\n \"restore\",\n blobPath(`${options.backupDirectory}/${objectsDirectoryName}`, entry.digest),\n entry.digest,\n maxFileSizeBytes\n )\n yield* fs.makeDirectory(`${objectsDirectory}/${entry.digest.slice(0, 2)}`, { recursive: true }).pipe(\n Effect.mapError(ioFailure(\"restore\", `creating the objects directory for ${entry.digest}`))\n )\n yield* fs.writeFile(blobPath(objectsDirectory, entry.digest), bytes).pipe(\n Effect.mapError(ioFailure(\"restore\", `writing the blob for ${entry.digest}`))\n )\n }\n\n const restoredAtMs = yield* Clock.currentTimeMillis.pipe(Effect.map(Math.floor))\n const marker = {\n formatVersion: 1,\n restoredAtMs,\n backupCreatedAtMs: manifest.createdAtMs,\n databaseSha256: manifest.database.sha256\n }\n yield* fs.writeFileString(\n `${options.targetDirectory}/${restoredMarkerFileName}`,\n `${JSON.stringify(marker, null, 2)}\\n`\n ).pipe(Effect.mapError(ioFailure(\"restore\", `writing ${restoredMarkerFileName}`)))\n return { databaseFile, objectsDirectory, manifest } satisfies RestoredStore\n })\n\n/**\n * Restores a verified backup into an empty target directory.\n *\n * Every byte is re-verified against the manifest as it is copied, so a backup\n * that rotted in storage is refused rather than restored. The target holds\n * the database file, the objects directory, and a `restored.json` marker; it\n * is a fresh store directory to compose `NodeDatabase.layer` and\n * `ArtifactStore.layerFileSystem` over. The database is restored with the\n * pre-backup owner fences still in place — run {@link fence} against it\n * before any engine adopts it. A failed copy removes everything it wrote, so\n * the target is left empty and reusable for a retry.\n *\n * @since 0.1.0\n * @category operations\n */\nexport const restore = Effect.fn(\"DisasterRecovery.restore\")(function*(options: RestoreOptions) {\n const limit = fileSizeLimit(\"restore\", options)\n if (typeof limit !== \"number\") return yield* Effect.fail(limit)\n const maxFileSizeBytes = limit\n const fs = yield* FileSystem.FileSystem\n const manifest = yield* readManifest(fs, \"restore\", options.backupDirectory, maxFileSizeBytes)\n yield* ensureEmptyDirectory(fs, \"restore\", options.targetDirectory)\n\n // The target was empty when the copy began, so everything in it is this\n // restore's. A refused blob or a failed write removes the partial store, so\n // nothing store-shaped is left to compose over and a retry can reuse it.\n return yield* Effect.onError(\n writeRestoredStore(fs, options, manifest, maxFileSizeBytes),\n () => discardEntries(fs, options.targetDirectory)\n )\n})\n\n/**\n * Bumps the restored store's fencing epoch so pre-backup owners are fenced\n * out.\n *\n * The store's fence is `OwnerId` equality, so the epoch bump is fence\n * invalidation: in one serialized write transaction every pending claim is\n * cleared, every run that was `running` at backup time is suspended with its\n * owner and heartbeat cleared, and every consensus lease is deleted. A\n * surviving pre-backup owner then fails every fenced operation against the\n * restored store — `heartbeat` and `transitionOwned` report `FenceLost`,\n * fenced journal appends fail `fence_lost` — exactly as a stale Temporal owner\n * fails its `rangeID` compare-and-swap. The suspended runs are claimable\n * immediately, without waiting out the heartbeat staleness cutoff and without\n * liveness evidence.\n *\n * Every migration recorded in the manifest must remain applied with the same\n * ID and namespaced name. Forward additions within any installed block are\n * compatible, including engine-store `3006` below an existing plan `4003`;\n * the current database layer applies them on open. A missing or renamed\n * historical entry fails `schema_mismatch` before ownership is cleared.\n *\n * In-flight attempt rows are deliberately untouched: attempt writes are\n * fenced on the run row's ownership, and the resuming engine adopts or\n * retries them under its own fresh owner.\n *\n * @since 0.1.0\n * @category operations\n */\nexport const fence = Effect.fn(\"DisasterRecovery.fence\")(function*(manifest: BackupManifest) {\n const sql = yield* Effect.service(SqlClient.SqlClient)\n const writer = yield* DurableWriter\n const applied = yield* appliedMigrations(sql, \"fence\")\n const expected = manifest.database.migrations\n const key = (migration: AppliedMigration): string => `${migration.migrationId}:${migration.name}`\n // IDs identify migrations within their reserved blocks. Compare historical\n // identities independently so additions in lower blocks cannot shift them.\n const appliedById = new Map(applied.map((migration) => [migration.migrationId, migration.name]))\n if (expected.some((migration) => appliedById.get(migration.migrationId) !== migration.name)) {\n return yield* Effect.fail(\n error(\n \"fence\",\n \"schema_mismatch\",\n `the restored database applied [${applied.map(key).join(\", \")}] but the manifest records [${\n expected.map(key).join(\", \")\n }]`,\n { applied, expected }\n )\n )\n }\n return yield* writer.write(\n Effect.gen(function*() {\n const claims = yield* sql<{ readonly run_id: string }>`\n UPDATE flows_runs\n SET\n claim_host_id = NULL,\n claim_pid = NULL,\n claim_nonce = NULL,\n claimed_at_ms = NULL\n WHERE claim_host_id IS NOT NULL\n RETURNING run_id\n `.withoutTransform\n const running = yield* sql<{ readonly run_id: string }>`\n UPDATE flows_runs\n SET\n status = 'suspended',\n owner_host_id = NULL,\n owner_pid = NULL,\n owner_nonce = NULL,\n heartbeat_at_ms = NULL,\n waiting_reason = 'released',\n waiting_wake_at_ms = NULL,\n waiting_token = NULL\n WHERE status = 'running'\n RETURNING run_id\n `.withoutTransform\n yield* sql`DELETE FROM flows_consensus_leases`.withoutTransform\n return { clearedClaims: claims.length, suspendedRuns: running.length } satisfies FenceSummary\n })\n ).pipe(Effect.mapError(sqlFailure(\"fence\", \"invalidating the persisted fences\")))\n})\n\n/**\n * Restores a backup and immediately fences the restored database.\n *\n * This is the one-shot operator API for disaster recovery. When fencing\n * refuses, the restored files are removed, so no engine can adopt a store that\n * still carries pre-backup owners. The database driver itself still arrives\n * through a caller-supplied Layer so this module remains host-neutral and\n * browser-bundleable; the bundled Node script passes `NodeDatabase.layer` for\n * the restored file.\n *\n * @since 0.1.0\n * @category operations\n */\nexport const restoreAndFence = <R = never, E = never>(\n options: RestoreAndFenceOptions<R, E>\n): Effect.Effect<FencedRestoredStore, DisasterRecoveryError | E, FileSystem.FileSystem | Crypto.Crypto | R> =>\n Effect.gen(function*() {\n const fs = yield* FileSystem.FileSystem\n const restored = yield* restore(options)\n // A refused fence leaves a database that still carries pre-backup owner\n // fences beside a `restored.json` marker. Remove the whole restore so no\n // engine can adopt it and the operator can retry into the same target.\n const summary = yield* fence(restored.manifest).pipe(\n Effect.provide(options.databaseLayer(restored.databaseFile)),\n Effect.onError(() => discardEntries(fs, options.targetDirectory))\n )\n return { ...restored, fence: summary } satisfies FencedRestoredStore\n })\n"]}
@@ -385,12 +385,14 @@ export interface FileSystemOptions {
385
385
  */
386
386
  readonly maxTotalInlineBytes?: number | undefined;
387
387
  /**
388
- * The workspace root cache replay confines its writes, removals, and prune
389
- * walk to. A filesystem already confined to a root (the kernel-guarded
390
- * layer's service, or a `confined` view) keeps its own root and ignores
391
- * this. Otherwise it defaults to the process working directory, the root a
392
- * path-based host resolves `prepare` and `settle` against. {@link layer}
393
- * passes the kernel `Workspace` root when one is in context.
388
+ * The workspace root every declared path resolves against: `prepare` and
389
+ * `settle` measure under it, and cache replay confines its writes, removals,
390
+ * and prune walk to it. A filesystem already confined to a root (the
391
+ * kernel-guarded layer's service, or a `confined` view) keeps its own root
392
+ * and ignores this. Otherwise it defaults to the process working directory,
393
+ * the root a path-based host resolves a relative path against. {@link layer}
394
+ * passes the kernel `Workspace` root when one is in context, which is the
395
+ * root a step's own FileSystem resolves against (issue #3911).
394
396
  */
395
397
  readonly root?: string | undefined;
396
398
  }