@smthrs/artifacts 1.0.0-rc.7 → 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.
@@ -36,6 +36,7 @@
36
36
  */
37
37
  import * as Clock from "effect/Clock";
38
38
  import * as Deferred from "effect/Deferred";
39
+ import * as Duration from "effect/Duration";
39
40
  import * as Effect from "effect/Effect";
40
41
  import * as Option from "effect/Option";
41
42
  import * as PlatformError from "effect/PlatformError";
@@ -76,6 +77,15 @@ export const hold = (fs, lockPath, effect, failure, options = {}) => Effect.gen(
76
77
  const heartbeatEvery = options.heartbeatEvery ?? "10 seconds";
77
78
  const staleAfterMs = options.staleAfterMs ?? defaultStaleAfterMs;
78
79
  const retryEvery = options.retryEvery ?? "25 millis";
80
+ // A holder heartbeats its lock to stay fresh, so a stale bound at or below
81
+ // the heartbeat, or one that is not a positive finite number, would let a
82
+ // contender read a live holder's lock as abandoned and move it, or would
83
+ // never reclaim a dead holder's lock at all.
84
+ const heartbeatMs = Option.map(Duration.fromInput(heartbeatEvery), Duration.toMillis);
85
+ if (typeof staleAfterMs !== "number" || !Number.isFinite(staleAfterMs) || staleAfterMs <= 0 ||
86
+ Option.isNone(heartbeatMs) || !(heartbeatMs.value > 0 && heartbeatMs.value < staleAfterMs)) {
87
+ return yield* Effect.fail(failure(new RangeError(`${label} needs 0 < heartbeatEvery < staleAfterMs, with a finite staleAfterMs`)));
88
+ }
79
89
  const owner = yield* token;
80
90
  /**
81
91
  * Whether this call created the lock file, and therefore owes a release.
@@ -1 +1 @@
1
- {"version":3,"file":"FileLease.js","sourceRoot":"","sources":["../../src/FileLease.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * A cross-process lease on one lock file.\n *\n * The lock is a file created with `wx` (exclusive create) that holds a unique\n * owner token. The holder refreshes its mtime every `heartbeatEvery`; a lock\n * whose mtime is older than `staleAfterMs` belongs to a crashed or hard-killed\n * owner and is reclaimed. Reclaimers of one lock generation race for a `wx`\n * claim file first, so a stale generation is moved away at most once and a\n * fresh lock is never displaced. Acquisition is bounded by `acquireWithin`,\n * so a waiter fails with a typed error instead of waiting forever on a lock\n * nothing will release.\n *\n * | Setting | Default |\n * | ---------------- | ----------- |\n * | `heartbeatEvery` | 10 seconds |\n * | `staleAfterMs` | 60 000 ms |\n * | `acquireWithin` | 2 minutes |\n * | `retryEvery` | 25 millis |\n *\n * The fence is bounded, not absolute: a holder stalled past `staleAfterMs`\n * (a suspended laptop, a stopped debugger) is reaped while it still runs. Its\n * heartbeat then logs `<label> was reclaimed while its holder was still\n * running`, and the protected effect continues unfenced.\n *\n * A directory at the lock path, left by a release that used an exclusive\n * `makeDirectory` as its lock, is treated as a lock with no readable owner:\n * it is reclaimed once its mtime is past `staleAfterMs`, and waited on\n * before that.\n *\n * The host needs exclusive `wx` file creation, `readFileString`, `stat` with\n * an mtime, `utimes`, `rename`, and `remove`. The claim file and the tombstone\n * a reclaim renames the stale lock to are siblings of the lock:\n * `<lockPath>.reclaim-<token>` and `<lockPath>.stale-<token>`.\n *\n * @since 1.0.0-rc.1\n */\n\nimport * as Clock from \"effect/Clock\"\nimport * as Deferred from \"effect/Deferred\"\nimport type * as Duration from \"effect/Duration\"\nimport * as Effect from \"effect/Effect\"\nimport type * as FileSystem from \"effect/FileSystem\"\nimport * as Option from \"effect/Option\"\nimport * as PlatformError from \"effect/PlatformError\"\nimport * as Random from \"effect/Random\"\n\n/**\n * Tuning for one lease.\n *\n * @category models\n * @since 1.0.0-rc.1\n */\nexport interface Options {\n /** Names the lock in the reclaimed-holder warning. Defaults to `\"File lease\"`. */\n readonly label?: string | undefined\n /** Extra fields logged with the reclaimed-holder warning. */\n readonly annotations?: Readonly<Record<string, unknown>> | undefined\n /** How often the holder refreshes the lock's mtime. Defaults to 10 seconds. */\n readonly heartbeatEvery?: Duration.Input | undefined\n /** Age past which a lock or claim is stale. Defaults to 60 000 ms. */\n readonly staleAfterMs?: number | undefined\n /** How long acquisition may wait. Defaults to 2 minutes. */\n readonly acquireWithin?: Duration.Input | undefined\n /** How long a waiter sleeps between attempts. Defaults to 25 millis. */\n readonly retryEvery?: Duration.Input | undefined\n}\n\n/**\n * {@link Options} for {@link hold}, which leaves the deadline to its caller.\n *\n * @category models\n * @since 1.0.0-rc.1\n */\nexport interface HoldOptions extends Omit<Options, \"acquireWithin\"> {\n /** Runs once, the moment the lease is held and before `effect` starts. */\n readonly onAcquired?: Effect.Effect<void> | undefined\n /**\n * Runs before every create, heartbeat, rename, and remove of the lock, its\n * claim, or its tombstone. A failure refuses that step through `failure`,\n * so a caller can re-check that the lock directory was not replaced (for\n * example by a symlink) after it was created.\n */\n readonly guard?: Effect.Effect<void, unknown> | undefined\n}\n\n/**\n * The default acquisition deadline.\n *\n * @category constants\n * @since 1.0.0-rc.1\n */\nexport const defaultAcquireWithin: Duration.Input = \"2 minutes\"\n\n/**\n * The default stale bound, in milliseconds.\n *\n * @category constants\n * @since 1.0.0-rc.1\n */\nexport const defaultStaleAfterMs = 60_000\n\nconst isReason = (cause: unknown, tag: PlatformError.SystemErrorTag): boolean =>\n cause instanceof PlatformError.PlatformError && cause.reason._tag === tag\n\n/** The generation name of a lock with no readable owner: a directory. */\nconst directoryGeneration = \"legacy-directory\"\n\nconst token = Effect.gen(function*() {\n const first = yield* Random.nextIntBetween(0, Number.MAX_SAFE_INTEGER, { halfOpen: true })\n const second = yield* Random.nextIntBetween(0, Number.MAX_SAFE_INTEGER, { halfOpen: true })\n return `${first.toString(36)}-${second.toString(36)}-${yield* Clock.currentTimeMillis}`\n})\n\n/**\n * Runs `effect` while holding the lease at `lockPath`, with no acquisition\n * deadline. Use it when the caller already bounds a wider wait (an\n * in-process semaphore plus this lease) with its own deadline; otherwise use\n * {@link withLease}.\n *\n * @category combinators\n * @since 1.0.0-rc.1\n */\nexport const hold = <A, E, R, E2>(\n fs: FileSystem.FileSystem,\n lockPath: string,\n effect: Effect.Effect<A, E, R>,\n failure: (cause: unknown) => E2,\n options: HoldOptions = {}\n): Effect.Effect<A, E | E2, R> =>\n Effect.gen(function*() {\n const label = options.label ?? \"File lease\"\n const heartbeatEvery = options.heartbeatEvery ?? \"10 seconds\"\n const staleAfterMs = options.staleAfterMs ?? defaultStaleAfterMs\n const retryEvery = options.retryEvery ?? \"25 millis\"\n const owner = yield* token\n /**\n * Whether this call created the lock file, and therefore owes a release.\n * It is set in the same uninterruptible step that creates the file, so\n * interruption striking between the two cannot leak a lock nothing\n * releases — the only party that would ever reclaim it is a later\n * acquirer of the same path, which may never come.\n */\n let acquired = false\n\n const guard = options.guard === undefined ? Effect.void : options.guard.pipe(Effect.mapError(failure))\n const guarded = <B, F, S>(mutation: Effect.Effect<B, F, S>): Effect.Effect<B, F | E2, S> =>\n Effect.andThen(guard, mutation)\n\n const statIfPresent = (path: string): Effect.Effect<Option.Option<FileSystem.File.Info>, E2> =>\n fs.stat(path).pipe(\n Effect.map(Option.some),\n Effect.catch((cause): Effect.Effect<Option.Option<FileSystem.File.Info>, E2> =>\n isReason(cause, \"NotFound\") ? Effect.succeed(Option.none()) : Effect.fail(failure(cause))\n )\n )\n\n const refuse = (cause: unknown): Effect.Effect<never, E2> => Effect.fail(failure(cause))\n\n const readOwner = (\n path: string,\n onRefused: (cause: unknown) => Effect.Effect<Option.Option<string>, E2> = refuse\n ): Effect.Effect<Option.Option<string>, E2> =>\n fs.readFileString(path).pipe(\n Effect.map(Option.some),\n Effect.catch((cause) => isReason(cause, \"NotFound\") ? Effect.succeed(Option.none()) : onRefused(cause))\n )\n\n /**\n * Names the lock generation at `path`: its owner token, or\n * `directoryGeneration` for a directory, which has no readable owner.\n * A read refused for any other reason is propagated, not guessed at.\n */\n const observe = (path: string): Effect.Effect<Option.Option<string>, E2> =>\n readOwner(path, (cause) =>\n fs.stat(path).pipe(\n Effect.matchEffect({\n onFailure: () => refuse(cause),\n onSuccess: (info) =>\n info.type === \"Directory\" ? Effect.succeed(Option.some(directoryGeneration)) : refuse(cause)\n })\n ))\n\n const isStale = (path: string): Effect.Effect<boolean, E2> =>\n Effect.gen(function*() {\n const info = yield* statIfPresent(path)\n const modified = Option.isSome(info) ? Option.getOrUndefined(info.value.mtime) : undefined\n return modified !== undefined && (yield* Clock.currentTimeMillis) - modified.getTime() > staleAfterMs\n })\n\n const removeIfOwnedBy = (path: string, expected: string): Effect.Effect<void> =>\n fs.readFileString(path).pipe(\n Effect.flatMap((found) => found === expected ? guarded(fs.remove(path)) : Effect.void),\n Effect.ignore\n )\n\n const acquire = Effect.gen(function*() {\n while (true) {\n const created = yield* Effect.uninterruptible(\n guarded(fs.writeFileString(lockPath, owner, { flag: \"wx\", mode: 0o600 })).pipe(\n Effect.andThen(Effect.sync(() => {\n acquired = true\n return true\n })),\n Effect.catch((cause): Effect.Effect<boolean, E2> =>\n isReason(cause, \"AlreadyExists\") ? Effect.succeed(false) : Effect.fail(failure(cause))\n )\n )\n )\n if (created) return\n\n // The owner is read before the age, so a stale verdict always\n // belongs to the lock generation this read named.\n const observed = yield* observe(lockPath)\n if (Option.isNone(observed)) continue\n if (yield* isStale(lockPath)) {\n const reclaimed = yield* reclaim(observed.value)\n if (reclaimed) continue\n }\n yield* Effect.sleep(retryEvery)\n }\n })\n\n /**\n * Moves away the stale lock generation `observed` names, at most once.\n *\n * Measuring a lock and renaming it are two steps, so two contenders\n * that both measure the same generation as stale would otherwise both\n * rename, and the second would move away the fresh lock the first just\n * took. Reclaimers of one generation therefore race for a `wx` claim\n * named after its owner token first. Only the winner renames, and only\n * after re-reading that the path still holds that stale generation. A\n * loser, or a winner whose generation is already gone, moves nothing.\n * Owner tokens are unique per acquisition, so a claim never outlives\n * the generation it names in any way that matters.\n */\n const reclaim = (observed: string): Effect.Effect<boolean, E2> => {\n const claimPath = `${lockPath}.reclaim-${observed.replace(/[^0-9A-Za-z-]/g, \"_\").slice(0, 96)}`\n return Effect.acquireUseRelease(\n guarded(fs.writeFileString(claimPath, owner, { flag: \"wx\", mode: 0o600 })).pipe(\n Effect.as(true),\n Effect.catch((cause): Effect.Effect<boolean, E2> =>\n isReason(cause, \"AlreadyExists\") ? Effect.succeed(false) : Effect.fail(failure(cause))\n )\n ),\n (claimed) =>\n Effect.gen(function*() {\n if (!claimed) {\n // A claimant that died mid-reclaim leaves its claim behind.\n // It ages out on the same bound as a lock.\n const holder = yield* readOwner(claimPath)\n if (Option.isSome(holder) && (yield* isStale(claimPath))) {\n yield* removeIfOwnedBy(claimPath, holder.value)\n }\n return false\n }\n const current = yield* observe(lockPath)\n if (Option.isNone(current) || current.value !== observed || !(yield* isStale(lockPath))) return true\n const tombstone = `${lockPath}.stale-${owner}`\n const moved = yield* guarded(fs.rename(lockPath, tombstone)).pipe(\n Effect.as(true),\n Effect.catch((cause): Effect.Effect<boolean, E2> =>\n isReason(cause, \"NotFound\") ? Effect.succeed(false) : Effect.fail(failure(cause))\n )\n )\n if (!moved) return true\n // Only a stalled owner releasing between the re-read and the\n // rename, and a new owner acquiring in that gap, puts a live\n // lock here. Hand it back with an atomic create, which cannot\n // displace anyone who took the path since.\n const displaced = yield* observe(tombstone)\n if (\n Option.isSome(displaced) && displaced.value !== observed && displaced.value !== directoryGeneration\n ) {\n yield* guarded(fs.writeFileString(lockPath, displaced.value, { flag: \"wx\", mode: 0o600 })).pipe(\n Effect.ignore\n )\n }\n yield* guarded(fs.remove(tombstone, { recursive: true })).pipe(Effect.ignore)\n return true\n }),\n (claimed) => claimed ? removeIfOwnedBy(claimPath, owner) : Effect.void\n )\n }\n\n const release = fs.readFileString(lockPath).pipe(\n Effect.flatMap((found) => found === owner ? guarded(fs.remove(lockPath)) : Effect.void),\n // A concurrent stale-lock reaper can win release. `NotFound`\n // means no lock remains for this owner to release or leak.\n Effect.catch((cause): Effect.Effect<void, E2> =>\n isReason(cause, \"NotFound\") ? Effect.void : Effect.fail(failure(cause))\n )\n )\n\n // The finalizer is attached around acquisition itself, not just around\n // the protected effect, so a lock created moments before an interruption\n // is still released. A call that never created one owes nothing and must\n // not touch a file another owner holds.\n return yield* acquire.pipe(\n Effect.andThen(options.onAcquired ?? Effect.void),\n Effect.andThen(Effect.scoped(Effect.gen(function*() {\n yield* Effect.forkScoped(\n Effect.gen(function*() {\n while (true) {\n yield* Effect.sleep(heartbeatEvery)\n // Freshen only a lock this call still owns, the same comparison\n // the release below makes. A holder stalled past the stale\n // bound has already been reaped and replaced, and touching that\n // pathname anyway would hold a stranger's lock fresh forever: a\n // replacement that is then hard-killed could never be judged\n // stale while this zombie ran, so every later acquirer would\n // burn the full acquisition deadline and fail.\n const beat = yield* fs.readFileString(lockPath).pipe(\n Effect.map((found) => found === owner ? \"own\" as const : \"foreign\" as const),\n Effect.catch((cause) =>\n Effect.succeed(isReason(cause, \"NotFound\") ? \"gone\" as const : \"unreadable\" as const)\n )\n )\n if (beat === \"own\") {\n const timestamp = new Date(yield* Clock.currentTimeMillis)\n yield* Effect.ignore(guarded(fs.utimes(lockPath, timestamp, timestamp)))\n continue\n }\n // A read the host refused for any other reason is no evidence\n // either way, so this beat is skipped and the next one retries.\n // Ending the heartbeat on it would retire a lock this call\n // still holds: the file goes stale within the minute, another\n // process reaps it, and this holder keeps working unfenced.\n if (beat === \"unreadable\") continue\n // `gone` and `foreign` are the two states worth an operator\n // signal: this call's lock was reaped, and possibly already\n // replaced, while the effect it fences is still running, so\n // the rest of that effect proceeds unfenced against every other\n // process. The heartbeat is the only party that ever observes\n // it — the protected effect is not interrupted and the caller\n // is not failed — so retiring the fiber silently would leave no\n // trace at all.\n yield* Effect.logWarning(\n `${label} was reclaimed while its holder was still running`,\n { ...options.annotations, state: beat }\n )\n return\n }\n })\n )\n return yield* effect\n }))),\n Effect.onExit(() => acquired ? release : Effect.void)\n )\n })\n\n/**\n * Runs `effect` while holding the lease at `lockPath`.\n *\n * Acquisition fails with `failure(TimeoutError)` once `acquireWithin` passes\n * without the lease; every other host refusal reaches `failure` with the\n * refusing `PlatformError`. The deadline ends when the lease is held: the\n * protected effect and its release are never timed out.\n *\n * @category combinators\n * @since 1.0.0-rc.1\n */\nexport const withLease = <A, E, R, E2>(\n fs: FileSystem.FileSystem,\n lockPath: string,\n effect: Effect.Effect<A, E, R>,\n failure: (cause: unknown) => E2,\n options: Options = {}\n): Effect.Effect<A, E | E2, R> =>\n Effect.suspend(() => {\n const ready = Deferred.makeUnsafe<void>()\n const deadline = Deferred.await(ready).pipe(\n Effect.timeout(options.acquireWithin ?? defaultAcquireWithin),\n Effect.catchTag(\"TimeoutError\", (cause) => Effect.fail(failure(cause))),\n Effect.andThen(Effect.never)\n )\n return hold(fs, lockPath, effect, failure, {\n ...options,\n onAcquired: Deferred.succeed(ready, undefined).pipe(Effect.asVoid)\n }).pipe(Effect.raceFirst(deadline))\n })\n"]}
1
+ {"version":3,"file":"FileLease.js","sourceRoot":"","sources":["../../src/FileLease.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAEH,OAAO,KAAK,KAAK,MAAM,cAAc,CAAA;AACrC,OAAO,KAAK,QAAQ,MAAM,iBAAiB,CAAA;AAC3C,OAAO,KAAK,QAAQ,MAAM,iBAAiB,CAAA;AAC3C,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AAEvC,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AACvC,OAAO,KAAK,aAAa,MAAM,sBAAsB,CAAA;AACrD,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AA6CvC;;;;;GAKG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAmB,WAAW,CAAA;AAE/D;;;;;GAKG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,MAAM,CAAA;AAEzC,MAAM,QAAQ,GAAG,CAAC,KAAc,EAAE,GAAiC,EAAW,EAAE,CAC9E,KAAK,YAAY,aAAa,CAAC,aAAa,IAAI,KAAK,CAAC,MAAM,CAAC,IAAI,KAAK,GAAG,CAAA;AAE3E,yEAAyE;AACzE,MAAM,mBAAmB,GAAG,kBAAkB,CAAA;AAE9C,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;IAChC,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC,EAAE,MAAM,CAAC,gBAAgB,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAA;IAC1F,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC,EAAE,MAAM,CAAC,gBAAgB,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAA;IAC3F,OAAO,GAAG,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC,IAAI,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,IAAI,KAAK,CAAC,CAAC,KAAK,CAAC,iBAAiB,EAAE,CAAA;AACzF,CAAC,CAAC,CAAA;AAEF;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,IAAI,GAAG,CAClB,EAAyB,EACzB,QAAgB,EAChB,MAA8B,EAC9B,OAA+B,EAC/B,UAAuB,EAAE,EACI,EAAE,CAC/B,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;IAClB,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,YAAY,CAAA;IAC3C,MAAM,cAAc,GAAG,OAAO,CAAC,cAAc,IAAI,YAAY,CAAA;IAC7D,MAAM,YAAY,GAAG,OAAO,CAAC,YAAY,IAAI,mBAAmB,CAAA;IAChE,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,IAAI,WAAW,CAAA;IACpD,2EAA2E;IAC3E,0EAA0E;IAC1E,yEAAyE;IACzE,6CAA6C;IAC7C,MAAM,WAAW,GAAG,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,SAAS,CAAC,cAAc,CAAC,EAAE,QAAQ,CAAC,QAAQ,CAAC,CAAA;IACrF,IACE,OAAO,YAAY,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,YAAY,CAAC,IAAI,YAAY,IAAI,CAAC;QACvF,MAAM,CAAC,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,WAAW,CAAC,KAAK,GAAG,CAAC,IAAI,WAAW,CAAC,KAAK,GAAG,YAAY,CAAC,EAC1F,CAAC;QACD,OAAO,KAAK,CAAC,CAAC,MAAM,CAAC,IAAI,CACvB,OAAO,CAAC,IAAI,UAAU,CAAC,GAAG,KAAK,sEAAsE,CAAC,CAAC,CACxG,CAAA;IACH,CAAC;IACD,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,KAAK,CAAA;IAC1B;;;;;;OAMG;IACH,IAAI,QAAQ,GAAG,KAAK,CAAA;IAEpB,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAA;IACtG,MAAM,OAAO,GAAG,CAAU,QAAgC,EAA+B,EAAE,CACzF,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAA;IAEjC,MAAM,aAAa,GAAG,CAAC,IAAY,EAA0D,EAAE,CAC7F,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,CAChB,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,EACvB,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,EAA0D,EAAE,CAC7E,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAC1F,CACF,CAAA;IAEH,MAAM,MAAM,GAAG,CAAC,KAAc,EAA4B,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAA;IAExF,MAAM,SAAS,GAAG,CAChB,IAAY,EACZ,YAA0E,MAAM,EACtC,EAAE,CAC5C,EAAE,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC,IAAI,CAC1B,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,EACvB,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CACxG,CAAA;IAEH;;;;OAIG;IACH,MAAM,OAAO,GAAG,CAAC,IAAY,EAA4C,EAAE,CACzE,SAAS,CAAC,IAAI,EAAE,CAAC,KAAK,EAAE,EAAE,CACxB,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,CAChB,MAAM,CAAC,WAAW,CAAC;QACjB,SAAS,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC;QAC9B,SAAS,EAAE,CAAC,IAAI,EAAE,EAAE,CAClB,IAAI,CAAC,IAAI,KAAK,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;KAC/F,CAAC,CACH,CAAC,CAAA;IAEN,MAAM,OAAO,GAAG,CAAC,IAAY,EAA8B,EAAE,CAC3D,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;QAClB,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,aAAa,CAAC,IAAI,CAAC,CAAA;QACvC,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,cAAc,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;QAC1F,OAAO,QAAQ,KAAK,SAAS,IAAI,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,iBAAiB,CAAC,GAAG,QAAQ,CAAC,OAAO,EAAE,GAAG,YAAY,CAAA;IACvG,CAAC,CAAC,CAAA;IAEJ,MAAM,eAAe,GAAG,CAAC,IAAY,EAAE,QAAgB,EAAuB,EAAE,CAC9E,EAAE,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC,IAAI,CAC1B,MAAM,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,EACtF,MAAM,CAAC,MAAM,CACd,CAAA;IAEH,MAAM,OAAO,GAAG,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;QAClC,OAAO,IAAI,EAAE,CAAC;YACZ,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,eAAe,CAC3C,OAAO,CAAC,EAAE,CAAC,eAAe,CAAC,QAAQ,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,CAC5E,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE;gBAC9B,QAAQ,GAAG,IAAI,CAAA;gBACf,OAAO,IAAI,CAAA;YACb,CAAC,CAAC,CAAC,EACH,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,EAA8B,EAAE,CACjD,QAAQ,CAAC,KAAK,EAAE,eAAe,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CACvF,CACF,CACF,CAAA;YACD,IAAI,OAAO;gBAAE,OAAM;YAEnB,8DAA8D;YAC9D,kDAAkD;YAClD,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAA;YACzC,IAAI,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC;gBAAE,SAAQ;YACrC,IAAI,KAAK,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC;gBAC7B,MAAM,SAAS,GAAG,KAAK,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAA;gBAChD,IAAI,SAAS;oBAAE,SAAQ;YACzB,CAAC;YACD,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,CAAA;QACjC,CAAC;IACH,CAAC,CAAC,CAAA;IAEF;;;;;;;;;;;;OAYG;IACH,MAAM,OAAO,GAAG,CAAC,QAAgB,EAA8B,EAAE;QAC/D,MAAM,SAAS,GAAG,GAAG,QAAQ,YAAY,QAAQ,CAAC,OAAO,CAAC,gBAAgB,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAAA;QAC/F,OAAO,MAAM,CAAC,iBAAiB,CAC7B,OAAO,CAAC,EAAE,CAAC,eAAe,CAAC,SAAS,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,CAC7E,MAAM,CAAC,EAAE,CAAC,IAAI,CAAC,EACf,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,EAA8B,EAAE,CACjD,QAAQ,CAAC,KAAK,EAAE,eAAe,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CACvF,CACF,EACD,CAAC,OAAO,EAAE,EAAE,CACV,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;YAClB,IAAI,CAAC,OAAO,EAAE,CAAC;gBACb,4DAA4D;gBAC5D,2CAA2C;gBAC3C,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,SAAS,CAAC,SAAS,CAAC,CAAA;gBAC1C,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC;oBACzD,KAAK,CAAC,CAAC,eAAe,CAAC,SAAS,EAAE,MAAM,CAAC,KAAK,CAAC,CAAA;gBACjD,CAAC;gBACD,OAAO,KAAK,CAAA;YACd,CAAC;YACD,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAA;YACxC,IAAI,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,OAAO,CAAC,KAAK,KAAK,QAAQ,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;gBAAE,OAAO,IAAI,CAAA;YACpG,MAAM,SAAS,GAAG,GAAG,QAAQ,UAAU,KAAK,EAAE,CAAA;YAC9C,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,MAAM,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC,CAAC,IAAI,CAC/D,MAAM,CAAC,EAAE,CAAC,IAAI,CAAC,EACf,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,EAA8B,EAAE,CACjD,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAClF,CACF,CAAA;YACD,IAAI,CAAC,KAAK;gBAAE,OAAO,IAAI,CAAA;YACvB,6DAA6D;YAC7D,6DAA6D;YAC7D,8DAA8D;YAC9D,2CAA2C;YAC3C,MAAM,SAAS,GAAG,KAAK,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC,CAAA;YAC3C,IACE,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,SAAS,CAAC,KAAK,KAAK,QAAQ,IAAI,SAAS,CAAC,KAAK,KAAK,mBAAmB,EACnG,CAAC;gBACD,KAAK,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,eAAe,CAAC,QAAQ,EAAE,SAAS,CAAC,KAAK,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,CAC7F,MAAM,CAAC,MAAM,CACd,CAAA;YACH,CAAC;YACD,KAAK,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,MAAM,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;YAC7E,OAAO,IAAI,CAAA;QACb,CAAC,CAAC,EACJ,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CACvE,CAAA;IACH,CAAC,CAAA;IAED,MAAM,OAAO,GAAG,EAAE,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAC,IAAI,CAC9C,MAAM,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,KAAK,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC;IACvF,6DAA6D;IAC7D,2DAA2D;IAC3D,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,EAA2B,EAAE,CAC9C,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CACxE,CACF,CAAA;IAED,uEAAuE;IACvE,yEAAyE;IACzE,yEAAyE;IACzE,wCAAwC;IACxC,OAAO,KAAK,CAAC,CAAC,OAAO,CAAC,IAAI,CACxB,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,UAAU,IAAI,MAAM,CAAC,IAAI,CAAC,EACjD,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;QAC/C,KAAK,CAAC,CAAC,MAAM,CAAC,UAAU,CACtB,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;YAClB,OAAO,IAAI,EAAE,CAAC;gBACZ,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC,CAAA;gBACnC,gEAAgE;gBAChE,2DAA2D;gBAC3D,gEAAgE;gBAChE,gEAAgE;gBAChE,6DAA6D;gBAC7D,6DAA6D;gBAC7D,+CAA+C;gBAC/C,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,EAAE,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAC,IAAI,CAClD,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,KAAK,KAAK,CAAC,CAAC,CAAC,KAAc,CAAC,CAAC,CAAC,SAAkB,CAAC,EAC5E,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE,CACrB,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,MAAe,CAAC,CAAC,CAAC,YAAqB,CAAC,CACtF,CACF,CAAA;gBACD,IAAI,IAAI,KAAK,KAAK,EAAE,CAAC;oBACnB,MAAM,SAAS,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,iBAAiB,CAAC,CAAA;oBAC1D,KAAK,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,MAAM,CAAC,QAAQ,EAAE,SAAS,EAAE,SAAS,CAAC,CAAC,CAAC,CAAA;oBACxE,SAAQ;gBACV,CAAC;gBACD,8DAA8D;gBAC9D,gEAAgE;gBAChE,2DAA2D;gBAC3D,8DAA8D;gBAC9D,4DAA4D;gBAC5D,IAAI,IAAI,KAAK,YAAY;oBAAE,SAAQ;gBACnC,4DAA4D;gBAC5D,4DAA4D;gBAC5D,4DAA4D;gBAC5D,gEAAgE;gBAChE,8DAA8D;gBAC9D,8DAA8D;gBAC9D,gEAAgE;gBAChE,gBAAgB;gBAChB,KAAK,CAAC,CAAC,MAAM,CAAC,UAAU,CACtB,GAAG,KAAK,mDAAmD,EAC3D,EAAE,GAAG,OAAO,CAAC,WAAW,EAAE,KAAK,EAAE,IAAI,EAAE,CACxC,CAAA;gBACD,OAAM;YACR,CAAC;QACH,CAAC,CAAC,CACH,CAAA;QACD,OAAO,KAAK,CAAC,CAAC,MAAM,CAAA;IACtB,CAAC,CAAC,CAAC,CAAC,EACJ,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CACtD,CAAA;AACH,CAAC,CAAC,CAAA;AAEJ;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG,CACvB,EAAyB,EACzB,QAAgB,EAChB,MAA8B,EAC9B,OAA+B,EAC/B,UAAmB,EAAE,EACQ,EAAE,CAC/B,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE;IAClB,MAAM,KAAK,GAAG,QAAQ,CAAC,UAAU,EAAQ,CAAA;IACzC,MAAM,QAAQ,GAAG,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,IAAI,CACzC,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,aAAa,IAAI,oBAAoB,CAAC,EAC7D,MAAM,CAAC,QAAQ,CAAC,cAAc,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,EACvE,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAC7B,CAAA;IACD,OAAO,IAAI,CAAC,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE;QACzC,GAAG,OAAO;QACV,UAAU,EAAE,QAAQ,CAAC,OAAO,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC;KACnE,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAA;AACrC,CAAC,CAAC,CAAA","sourcesContent":["/**\n * A cross-process lease on one lock file.\n *\n * The lock is a file created with `wx` (exclusive create) that holds a unique\n * owner token. The holder refreshes its mtime every `heartbeatEvery`; a lock\n * whose mtime is older than `staleAfterMs` belongs to a crashed or hard-killed\n * owner and is reclaimed. Reclaimers of one lock generation race for a `wx`\n * claim file first, so a stale generation is moved away at most once and a\n * fresh lock is never displaced. Acquisition is bounded by `acquireWithin`,\n * so a waiter fails with a typed error instead of waiting forever on a lock\n * nothing will release.\n *\n * | Setting | Default |\n * | ---------------- | ----------- |\n * | `heartbeatEvery` | 10 seconds |\n * | `staleAfterMs` | 60 000 ms |\n * | `acquireWithin` | 2 minutes |\n * | `retryEvery` | 25 millis |\n *\n * The fence is bounded, not absolute: a holder stalled past `staleAfterMs`\n * (a suspended laptop, a stopped debugger) is reaped while it still runs. Its\n * heartbeat then logs `<label> was reclaimed while its holder was still\n * running`, and the protected effect continues unfenced.\n *\n * A directory at the lock path, left by a release that used an exclusive\n * `makeDirectory` as its lock, is treated as a lock with no readable owner:\n * it is reclaimed once its mtime is past `staleAfterMs`, and waited on\n * before that.\n *\n * The host needs exclusive `wx` file creation, `readFileString`, `stat` with\n * an mtime, `utimes`, `rename`, and `remove`. The claim file and the tombstone\n * a reclaim renames the stale lock to are siblings of the lock:\n * `<lockPath>.reclaim-<token>` and `<lockPath>.stale-<token>`.\n *\n * @since 1.0.0-rc.1\n */\n\nimport * as Clock from \"effect/Clock\"\nimport * as Deferred from \"effect/Deferred\"\nimport * as Duration from \"effect/Duration\"\nimport * as Effect from \"effect/Effect\"\nimport type * as FileSystem from \"effect/FileSystem\"\nimport * as Option from \"effect/Option\"\nimport * as PlatformError from \"effect/PlatformError\"\nimport * as Random from \"effect/Random\"\n\n/**\n * Tuning for one lease.\n *\n * @category models\n * @since 1.0.0-rc.1\n */\nexport interface Options {\n /** Names the lock in the reclaimed-holder warning. Defaults to `\"File lease\"`. */\n readonly label?: string | undefined\n /** Extra fields logged with the reclaimed-holder warning. */\n readonly annotations?: Readonly<Record<string, unknown>> | undefined\n /** How often the holder refreshes the lock's mtime. Defaults to 10 seconds. */\n readonly heartbeatEvery?: Duration.Input | undefined\n /**\n * Age past which a lock or claim is stale. Defaults to 60 000 ms. Must be a\n * finite positive number longer than `heartbeatEvery`; anything else fails\n * the lease through `failure` before the lock is touched.\n */\n readonly staleAfterMs?: number | undefined\n /** How long acquisition may wait. Defaults to 2 minutes. */\n readonly acquireWithin?: Duration.Input | undefined\n /** How long a waiter sleeps between attempts. Defaults to 25 millis. */\n readonly retryEvery?: Duration.Input | undefined\n}\n\n/**\n * {@link Options} for {@link hold}, which leaves the deadline to its caller.\n *\n * @category models\n * @since 1.0.0-rc.1\n */\nexport interface HoldOptions extends Omit<Options, \"acquireWithin\"> {\n /** Runs once, the moment the lease is held and before `effect` starts. */\n readonly onAcquired?: Effect.Effect<void> | undefined\n /**\n * Runs before every create, heartbeat, rename, and remove of the lock, its\n * claim, or its tombstone. A failure refuses that step through `failure`,\n * so a caller can re-check that the lock directory was not replaced (for\n * example by a symlink) after it was created.\n */\n readonly guard?: Effect.Effect<void, unknown> | undefined\n}\n\n/**\n * The default acquisition deadline.\n *\n * @category constants\n * @since 1.0.0-rc.1\n */\nexport const defaultAcquireWithin: Duration.Input = \"2 minutes\"\n\n/**\n * The default stale bound, in milliseconds.\n *\n * @category constants\n * @since 1.0.0-rc.1\n */\nexport const defaultStaleAfterMs = 60_000\n\nconst isReason = (cause: unknown, tag: PlatformError.SystemErrorTag): boolean =>\n cause instanceof PlatformError.PlatformError && cause.reason._tag === tag\n\n/** The generation name of a lock with no readable owner: a directory. */\nconst directoryGeneration = \"legacy-directory\"\n\nconst token = Effect.gen(function*() {\n const first = yield* Random.nextIntBetween(0, Number.MAX_SAFE_INTEGER, { halfOpen: true })\n const second = yield* Random.nextIntBetween(0, Number.MAX_SAFE_INTEGER, { halfOpen: true })\n return `${first.toString(36)}-${second.toString(36)}-${yield* Clock.currentTimeMillis}`\n})\n\n/**\n * Runs `effect` while holding the lease at `lockPath`, with no acquisition\n * deadline. Use it when the caller already bounds a wider wait (an\n * in-process semaphore plus this lease) with its own deadline; otherwise use\n * {@link withLease}.\n *\n * @category combinators\n * @since 1.0.0-rc.1\n */\nexport const hold = <A, E, R, E2>(\n fs: FileSystem.FileSystem,\n lockPath: string,\n effect: Effect.Effect<A, E, R>,\n failure: (cause: unknown) => E2,\n options: HoldOptions = {}\n): Effect.Effect<A, E | E2, R> =>\n Effect.gen(function*() {\n const label = options.label ?? \"File lease\"\n const heartbeatEvery = options.heartbeatEvery ?? \"10 seconds\"\n const staleAfterMs = options.staleAfterMs ?? defaultStaleAfterMs\n const retryEvery = options.retryEvery ?? \"25 millis\"\n // A holder heartbeats its lock to stay fresh, so a stale bound at or below\n // the heartbeat, or one that is not a positive finite number, would let a\n // contender read a live holder's lock as abandoned and move it, or would\n // never reclaim a dead holder's lock at all.\n const heartbeatMs = Option.map(Duration.fromInput(heartbeatEvery), Duration.toMillis)\n if (\n typeof staleAfterMs !== \"number\" || !Number.isFinite(staleAfterMs) || staleAfterMs <= 0 ||\n Option.isNone(heartbeatMs) || !(heartbeatMs.value > 0 && heartbeatMs.value < staleAfterMs)\n ) {\n return yield* Effect.fail(\n failure(new RangeError(`${label} needs 0 < heartbeatEvery < staleAfterMs, with a finite staleAfterMs`))\n )\n }\n const owner = yield* token\n /**\n * Whether this call created the lock file, and therefore owes a release.\n * It is set in the same uninterruptible step that creates the file, so\n * interruption striking between the two cannot leak a lock nothing\n * releases — the only party that would ever reclaim it is a later\n * acquirer of the same path, which may never come.\n */\n let acquired = false\n\n const guard = options.guard === undefined ? Effect.void : options.guard.pipe(Effect.mapError(failure))\n const guarded = <B, F, S>(mutation: Effect.Effect<B, F, S>): Effect.Effect<B, F | E2, S> =>\n Effect.andThen(guard, mutation)\n\n const statIfPresent = (path: string): Effect.Effect<Option.Option<FileSystem.File.Info>, E2> =>\n fs.stat(path).pipe(\n Effect.map(Option.some),\n Effect.catch((cause): Effect.Effect<Option.Option<FileSystem.File.Info>, E2> =>\n isReason(cause, \"NotFound\") ? Effect.succeed(Option.none()) : Effect.fail(failure(cause))\n )\n )\n\n const refuse = (cause: unknown): Effect.Effect<never, E2> => Effect.fail(failure(cause))\n\n const readOwner = (\n path: string,\n onRefused: (cause: unknown) => Effect.Effect<Option.Option<string>, E2> = refuse\n ): Effect.Effect<Option.Option<string>, E2> =>\n fs.readFileString(path).pipe(\n Effect.map(Option.some),\n Effect.catch((cause) => isReason(cause, \"NotFound\") ? Effect.succeed(Option.none()) : onRefused(cause))\n )\n\n /**\n * Names the lock generation at `path`: its owner token, or\n * `directoryGeneration` for a directory, which has no readable owner.\n * A read refused for any other reason is propagated, not guessed at.\n */\n const observe = (path: string): Effect.Effect<Option.Option<string>, E2> =>\n readOwner(path, (cause) =>\n fs.stat(path).pipe(\n Effect.matchEffect({\n onFailure: () => refuse(cause),\n onSuccess: (info) =>\n info.type === \"Directory\" ? Effect.succeed(Option.some(directoryGeneration)) : refuse(cause)\n })\n ))\n\n const isStale = (path: string): Effect.Effect<boolean, E2> =>\n Effect.gen(function*() {\n const info = yield* statIfPresent(path)\n const modified = Option.isSome(info) ? Option.getOrUndefined(info.value.mtime) : undefined\n return modified !== undefined && (yield* Clock.currentTimeMillis) - modified.getTime() > staleAfterMs\n })\n\n const removeIfOwnedBy = (path: string, expected: string): Effect.Effect<void> =>\n fs.readFileString(path).pipe(\n Effect.flatMap((found) => found === expected ? guarded(fs.remove(path)) : Effect.void),\n Effect.ignore\n )\n\n const acquire = Effect.gen(function*() {\n while (true) {\n const created = yield* Effect.uninterruptible(\n guarded(fs.writeFileString(lockPath, owner, { flag: \"wx\", mode: 0o600 })).pipe(\n Effect.andThen(Effect.sync(() => {\n acquired = true\n return true\n })),\n Effect.catch((cause): Effect.Effect<boolean, E2> =>\n isReason(cause, \"AlreadyExists\") ? Effect.succeed(false) : Effect.fail(failure(cause))\n )\n )\n )\n if (created) return\n\n // The owner is read before the age, so a stale verdict always\n // belongs to the lock generation this read named.\n const observed = yield* observe(lockPath)\n if (Option.isNone(observed)) continue\n if (yield* isStale(lockPath)) {\n const reclaimed = yield* reclaim(observed.value)\n if (reclaimed) continue\n }\n yield* Effect.sleep(retryEvery)\n }\n })\n\n /**\n * Moves away the stale lock generation `observed` names, at most once.\n *\n * Measuring a lock and renaming it are two steps, so two contenders\n * that both measure the same generation as stale would otherwise both\n * rename, and the second would move away the fresh lock the first just\n * took. Reclaimers of one generation therefore race for a `wx` claim\n * named after its owner token first. Only the winner renames, and only\n * after re-reading that the path still holds that stale generation. A\n * loser, or a winner whose generation is already gone, moves nothing.\n * Owner tokens are unique per acquisition, so a claim never outlives\n * the generation it names in any way that matters.\n */\n const reclaim = (observed: string): Effect.Effect<boolean, E2> => {\n const claimPath = `${lockPath}.reclaim-${observed.replace(/[^0-9A-Za-z-]/g, \"_\").slice(0, 96)}`\n return Effect.acquireUseRelease(\n guarded(fs.writeFileString(claimPath, owner, { flag: \"wx\", mode: 0o600 })).pipe(\n Effect.as(true),\n Effect.catch((cause): Effect.Effect<boolean, E2> =>\n isReason(cause, \"AlreadyExists\") ? Effect.succeed(false) : Effect.fail(failure(cause))\n )\n ),\n (claimed) =>\n Effect.gen(function*() {\n if (!claimed) {\n // A claimant that died mid-reclaim leaves its claim behind.\n // It ages out on the same bound as a lock.\n const holder = yield* readOwner(claimPath)\n if (Option.isSome(holder) && (yield* isStale(claimPath))) {\n yield* removeIfOwnedBy(claimPath, holder.value)\n }\n return false\n }\n const current = yield* observe(lockPath)\n if (Option.isNone(current) || current.value !== observed || !(yield* isStale(lockPath))) return true\n const tombstone = `${lockPath}.stale-${owner}`\n const moved = yield* guarded(fs.rename(lockPath, tombstone)).pipe(\n Effect.as(true),\n Effect.catch((cause): Effect.Effect<boolean, E2> =>\n isReason(cause, \"NotFound\") ? Effect.succeed(false) : Effect.fail(failure(cause))\n )\n )\n if (!moved) return true\n // Only a stalled owner releasing between the re-read and the\n // rename, and a new owner acquiring in that gap, puts a live\n // lock here. Hand it back with an atomic create, which cannot\n // displace anyone who took the path since.\n const displaced = yield* observe(tombstone)\n if (\n Option.isSome(displaced) && displaced.value !== observed && displaced.value !== directoryGeneration\n ) {\n yield* guarded(fs.writeFileString(lockPath, displaced.value, { flag: \"wx\", mode: 0o600 })).pipe(\n Effect.ignore\n )\n }\n yield* guarded(fs.remove(tombstone, { recursive: true })).pipe(Effect.ignore)\n return true\n }),\n (claimed) => claimed ? removeIfOwnedBy(claimPath, owner) : Effect.void\n )\n }\n\n const release = fs.readFileString(lockPath).pipe(\n Effect.flatMap((found) => found === owner ? guarded(fs.remove(lockPath)) : Effect.void),\n // A concurrent stale-lock reaper can win release. `NotFound`\n // means no lock remains for this owner to release or leak.\n Effect.catch((cause): Effect.Effect<void, E2> =>\n isReason(cause, \"NotFound\") ? Effect.void : Effect.fail(failure(cause))\n )\n )\n\n // The finalizer is attached around acquisition itself, not just around\n // the protected effect, so a lock created moments before an interruption\n // is still released. A call that never created one owes nothing and must\n // not touch a file another owner holds.\n return yield* acquire.pipe(\n Effect.andThen(options.onAcquired ?? Effect.void),\n Effect.andThen(Effect.scoped(Effect.gen(function*() {\n yield* Effect.forkScoped(\n Effect.gen(function*() {\n while (true) {\n yield* Effect.sleep(heartbeatEvery)\n // Freshen only a lock this call still owns, the same comparison\n // the release below makes. A holder stalled past the stale\n // bound has already been reaped and replaced, and touching that\n // pathname anyway would hold a stranger's lock fresh forever: a\n // replacement that is then hard-killed could never be judged\n // stale while this zombie ran, so every later acquirer would\n // burn the full acquisition deadline and fail.\n const beat = yield* fs.readFileString(lockPath).pipe(\n Effect.map((found) => found === owner ? \"own\" as const : \"foreign\" as const),\n Effect.catch((cause) =>\n Effect.succeed(isReason(cause, \"NotFound\") ? \"gone\" as const : \"unreadable\" as const)\n )\n )\n if (beat === \"own\") {\n const timestamp = new Date(yield* Clock.currentTimeMillis)\n yield* Effect.ignore(guarded(fs.utimes(lockPath, timestamp, timestamp)))\n continue\n }\n // A read the host refused for any other reason is no evidence\n // either way, so this beat is skipped and the next one retries.\n // Ending the heartbeat on it would retire a lock this call\n // still holds: the file goes stale within the minute, another\n // process reaps it, and this holder keeps working unfenced.\n if (beat === \"unreadable\") continue\n // `gone` and `foreign` are the two states worth an operator\n // signal: this call's lock was reaped, and possibly already\n // replaced, while the effect it fences is still running, so\n // the rest of that effect proceeds unfenced against every other\n // process. The heartbeat is the only party that ever observes\n // it — the protected effect is not interrupted and the caller\n // is not failed — so retiring the fiber silently would leave no\n // trace at all.\n yield* Effect.logWarning(\n `${label} was reclaimed while its holder was still running`,\n { ...options.annotations, state: beat }\n )\n return\n }\n })\n )\n return yield* effect\n }))),\n Effect.onExit(() => acquired ? release : Effect.void)\n )\n })\n\n/**\n * Runs `effect` while holding the lease at `lockPath`.\n *\n * Acquisition fails with `failure(TimeoutError)` once `acquireWithin` passes\n * without the lease; every other host refusal reaches `failure` with the\n * refusing `PlatformError`. The deadline ends when the lease is held: the\n * protected effect and its release are never timed out.\n *\n * @category combinators\n * @since 1.0.0-rc.1\n */\nexport const withLease = <A, E, R, E2>(\n fs: FileSystem.FileSystem,\n lockPath: string,\n effect: Effect.Effect<A, E, R>,\n failure: (cause: unknown) => E2,\n options: Options = {}\n): Effect.Effect<A, E | E2, R> =>\n Effect.suspend(() => {\n const ready = Deferred.makeUnsafe<void>()\n const deadline = Deferred.await(ready).pipe(\n Effect.timeout(options.acquireWithin ?? defaultAcquireWithin),\n Effect.catchTag(\"TimeoutError\", (cause) => Effect.fail(failure(cause))),\n Effect.andThen(Effect.never)\n )\n return hold(fs, lockPath, effect, failure, {\n ...options,\n onAcquired: Deferred.succeed(ready, undefined).pipe(Effect.asVoid)\n }).pipe(Effect.raceFirst(deadline))\n })\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"RemoteArtifacts.d.ts","sourceRoot":"","sources":["../../src/RemoteArtifacts.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqDG;AAEH,OAAO,KAAK,QAAQ,MAAM,iBAAiB,CAAA;AAC3C,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AAGvC,OAAO,KAAK,UAAU,MAAM,wBAAwB,CAAA;AAGpD,OAAO,KAAK,KAAK,MAAM,cAAc,CAAA;AAErC,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AAEvC,OAAO,KAAK,aAAa,MAAM,oBAAoB,CAAA;AAEnD;;;;;GAKG;AACH,eAAO,MAAM,cAAc,0DAA2D,CAAA;AAEtF;;;;;GAKG;AACH,MAAM,MAAM,cAAc,GAAG,OAAO,cAAc,CAAC,IAAI,CAAA;AAEvD;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,EAAE,aAAa,CAAC,cAAc,CAAiD,CAAA;AAE5G;;;;;;GAMG;AACH,MAAM,WAAW,OAAQ,SAAQ,aAAa,CAAC,OAAO;IACpD,QAAQ,CAAC,cAAc,EAAE,cAAc,CAAA;CACxC;AAED;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB,GAAI,OAAO,aAAa,CAAC,OAAO,KAAG,cAAc,GAAG,SAGhF,CAAA;AAED;;;;;;GAMG;AACH,MAAM,WAAW,OAAO;IACtB;;;;;;;;;;;;;;;;OAgBG;IACH,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB;;;;;;;;;;;;;;;;;OAiBG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,CAAA;IACrD;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,QAAQ,CAAC,KAAK,GAAG,SAAS,CAAA;IACrD;;;OAGG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,QAAQ,CAAC,KAAK,GAAG,SAAS,CAAA;IACnD;;;OAGG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,QAAQ,CAAC,KAAK,GAAG,SAAS,CAAA;IACpD;;;;;;;OAOG;IACH,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC9C;;;OAGG;IACH,QAAQ,CAAC,2BAA2B,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IACzD;;;;;;;;;;;;;;;OAeG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IACxC;;;;;;;;;;;;;;;OAeG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,cAAc,GAAG,SAAS,CAAA;CACrD;AA4FD;;;;;;GAMG;AACH,eAAO,MAAM,IAAI,GACf,SAAS,OAAO,KACf,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,aAAa,CAAC,kBAAkB,EAAE,UAAU,CAAC,UAAU,CA4c7E,CAAA;AAEJ;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,KAAK,GAChB,SAAS,OAAO,KACf,KAAK,CAAC,KAAK,CAAC,aAAa,CAAC,aAAa,EAAE,aAAa,CAAC,kBAAkB,EAAE,UAAU,CAAC,UAAU,CACzC,CAAA"}
1
+ {"version":3,"file":"RemoteArtifacts.d.ts","sourceRoot":"","sources":["../../src/RemoteArtifacts.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqDG;AAEH,OAAO,KAAK,QAAQ,MAAM,iBAAiB,CAAA;AAC3C,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AAGvC,OAAO,KAAK,UAAU,MAAM,wBAAwB,CAAA;AAGpD,OAAO,KAAK,KAAK,MAAM,cAAc,CAAA;AAErC,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AAEvC,OAAO,KAAK,aAAa,MAAM,oBAAoB,CAAA;AAEnD;;;;;GAKG;AACH,eAAO,MAAM,cAAc,0DAA2D,CAAA;AAEtF;;;;;GAKG;AACH,MAAM,MAAM,cAAc,GAAG,OAAO,cAAc,CAAC,IAAI,CAAA;AAEvD;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,EAAE,aAAa,CAAC,cAAc,CAAiD,CAAA;AAE5G;;;;;;GAMG;AACH,MAAM,WAAW,OAAQ,SAAQ,aAAa,CAAC,OAAO;IACpD,QAAQ,CAAC,cAAc,EAAE,cAAc,CAAA;CACxC;AAED;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB,GAAI,OAAO,aAAa,CAAC,OAAO,KAAG,cAAc,GAAG,SAGhF,CAAA;AAED;;;;;;GAMG;AACH,MAAM,WAAW,OAAO;IACtB;;;;;;;;;;;;;;;;OAgBG;IACH,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB;;;;;;;;;;;;;;;;;OAiBG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,CAAA;IACrD;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,QAAQ,CAAC,KAAK,GAAG,SAAS,CAAA;IACrD;;;OAGG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,QAAQ,CAAC,KAAK,GAAG,SAAS,CAAA;IACnD;;;OAGG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,QAAQ,CAAC,KAAK,GAAG,SAAS,CAAA;IACpD;;;;;;;OAOG;IACH,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC9C;;;OAGG;IACH,QAAQ,CAAC,2BAA2B,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IACzD;;;;;;;;;;;;;;;OAeG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IACxC;;;;;;;;;;;;;;;OAeG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,cAAc,GAAG,SAAS,CAAA;CACrD;AA4FD;;;;;;GAMG;AACH,eAAO,MAAM,IAAI,GACf,SAAS,OAAO,KACf,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,aAAa,CAAC,kBAAkB,EAAE,UAAU,CAAC,UAAU,CAid7E,CAAA;AAEJ;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,KAAK,GAChB,SAAS,OAAO,KACf,KAAK,CAAC,KAAK,CAAC,aAAa,CAAC,aAAa,EAAE,aAAa,CAAC,kBAAkB,EAAE,UAAU,CAAC,UAAU,CACzC,CAAA"}
@@ -217,7 +217,10 @@ export const make = (options) => Effect.gen(function* () {
217
217
  if (endpoint.protocol !== "https:" && !(endpoint.protocol === "http:" && loopback)) {
218
218
  return yield* Effect.fail(configurationFailure("remote artifact endpoint must use HTTPS"));
219
219
  }
220
- if (endpoint.username !== "" || endpoint.password !== "" || endpoint.search !== "" || endpoint.hash !== "") {
220
+ // An empty query or fragment ("https://h/p?") parses as "" yet survives
221
+ // serialization, so the delimiter itself is refused in the href.
222
+ if (endpoint.username !== "" || endpoint.password !== "" || endpoint.search !== "" || endpoint.hash !== "" ||
223
+ endpoint.href.includes("?") || endpoint.href.includes("#")) {
221
224
  return yield* Effect.fail(configurationFailure("remote artifact endpoint must not contain credentials, a query, or a fragment"));
222
225
  }
223
226
  endpoint.pathname = endpoint.pathname.replace(/\/+$/, "");
@@ -1 +1 @@
1
- {"version":3,"file":"RemoteArtifacts.js","sourceRoot":"","sources":["../../src/RemoteArtifacts.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The shared artifact tier: an {@link ArtifactStore.Service} spoken over HTTP.\n *\n * The wire protocol mirrors Bazel's dumb-HTTP remote cache, class\n * `HttpCacheClient` in `com.google.devtools.build.lib.remote.http`, which\n * documents it as: \"CAS (Content Addressable Storage) blobs are stored under\n * the path `/cas/base16-key`\", uploaded with `PUT`, downloaded with `GET`. We add\n * `HEAD /cas/{digest}` for a single existence probe and\n * `POST /cas/findMissing` for the batched one, because Bazel's HTTP client has\n * no `findMissingDigests` at all — it answers \"everything is missing\" and\n * re-uploads — and a batched probe is the whole reason\n * `MissingDigestsFinder` exists in the gRPC client.\n *\n * Transport is Effect's own `HttpClient` tag. There is no lower transport in\n * this repository, and there does not need to be one: the kernel already\n * decorates that tag with `net:get`/`net:post` capability checks, so a remote\n * artifact fetch is permission-checked like every other egress.\n *\n * ## Chunked uploads\n *\n * With `chunkBytes` set, a blob past that size travels as a sequence of\n * `PUT /cas/{digest}` requests carrying `Content-Range: bytes {a}-{b}/{total}`,\n * preceded by a `HEAD /cas/{digest}` existence probe and a\n * `Content-Range: bytes *\\/{total}` probe with an empty body. This is the\n * resumable-upload shape HTTP already has: the range probe asks what prefix\n * the tier holds and `308` means \"keep going\", with a `Range: bytes=0-{last}`\n * header on the probe or on a chunk answer moving the offset.\n *\n * Only a `308` continues the sequence, and the completing chunk's `2xx` is\n * confirmed with `HEAD` before `put` reports the digest as published. A `2xx`\n * anywhere else, to the empty probe or to a chunk the tier is still owed bytes\n * after, reads as a tier that ignored `Content-Range` and stored the body it\n * was handed, which is what plain WebDAV `PUT` does. That tier, the one that\n * answers `400`, `411`, or `416`, and the one whose `HEAD` will not confirm\n * the stored length all get the same treatment: one whole-blob `PUT`, which\n * overwrites the partial body the sequence left behind. So the blob always\n * lands whole, and the cost of turning the dial on against a server that\n * never learned about it is round trips, never a digest published over bytes\n * the server does not hold.\n *\n * This repository's own hosted and self-hosted cache services are such\n * servers: neither implements the resumable sequence, and both refuse a\n * ranged `PUT` with `400`, RFC 9110 section 14.5's answer from a resource\n * that does not support partial `PUT`. Against them `chunkBytes` costs the\n * probe round trip and the blob travels whole, and both cap one request body\n * at 16 MiB, so a blob past that cap is refused with `413` in either mode.\n *\n * The endpoint and its credentials arrive as **layer construction options**.\n * They are capabilities, never step inputs: they are not hashed into a step\n * key, journaled, or returned in a recorded result. Invalid endpoint errors are\n * sanitized before they cross the boundary.\n *\n * @since 1.0.0-rc.0\n */\n\nimport * as Duration from \"effect/Duration\"\nimport * as Effect from \"effect/Effect\"\nimport * as FetchHttpClient from \"effect/http/FetchHttpClient\"\nimport * as Headers from \"effect/http/Headers\"\nimport * as HttpClient from \"effect/http/HttpClient\"\nimport * as HttpClientRequest from \"effect/http/HttpClientRequest\"\nimport type * as HttpClientResponse from \"effect/http/HttpClientResponse\"\nimport * as Layer from \"effect/Layer\"\nimport * as Option from \"effect/Option\"\nimport * as Schema from \"effect/Schema\"\nimport * as Stream from \"effect/Stream\"\nimport * as ArtifactStore from \"./ArtifactStore.ts\"\n\n/**\n * How eagerly a composition materializes shared blobs into its local store.\n *\n * @category models\n * @since 1.0.0-rc.0\n */\nexport const DownloadPolicy = Schema.Literals([\"all\", \"toplevel\", \"minimal\"] as const)\n\n/**\n * How eagerly a composition materializes remote blobs.\n *\n * @category models\n * @since 1.0.0-rc.0\n */\nexport type DownloadPolicy = typeof DownloadPolicy.Type\n\n/**\n * Every download policy, in materialization order.\n *\n * @category models\n * @since 1.0.0-rc.0\n */\nexport const downloadPolicies: ReadonlyArray<DownloadPolicy> = Object.freeze([\"all\", \"toplevel\", \"minimal\"])\n\n/**\n * A remote artifact tier, which is an ordinary store that also states how\n * eagerly a composition reading through it should materialize blobs locally.\n *\n * @category models\n * @since 1.0.0-rc.0\n */\nexport interface Service extends ArtifactStore.Service {\n readonly downloadPolicy: DownloadPolicy\n}\n\n/**\n * Reads the download policy a store declares, or `undefined` for a store that\n * declares none — every local store, and any foreign implementation.\n *\n * @category getters\n * @since 1.0.0-rc.0\n */\nexport const downloadPolicyOf = (store: ArtifactStore.Service): DownloadPolicy | undefined => {\n const declared = (store as { readonly downloadPolicy?: unknown }).downloadPolicy\n return Schema.is(DownloadPolicy)(declared) ? declared : undefined\n}\n\n/**\n * How to reach the shared artifact tier.\n *\n * @category models\n * @since 1.0.0-rc.0\n * @slop\n */\nexport interface Options {\n /**\n * The cache root, e.g. `https://cache.example.com`. `/cas/{digest}` and\n * `/cas/findMissing` are resolved beneath it. A trailing slash is ignored.\n *\n * Four shapes are refused at construction as `invalid_configuration`, before\n * any request leaves and therefore before `headers` can reach a wire: a value\n * that is not a string, one no `URL` parser accepts, a scheme other than\n * `https:` (plain `http:` is accepted only for the literal loopback hosts\n * `localhost`, `127.0.0.1`, and `[::1]`), and an endpoint carrying\n * userinfo, a query, or a fragment. The\n * last rule keeps a credential out of a place nothing here would redact: an\n * endpoint is interpolated into every request path and into span attributes.\n *\n * The refusal message names only the violated rule and never echoes the\n * endpoint, so a rejected `https://user:secret@host` cannot leak its\n * credential into a log line or a durable error.\n */\n readonly endpoint: string\n /**\n * Headers sent with every request — an `Authorization` bearer token, a\n * tenant header, whatever the deployment's gateway wants. This is the\n * credential seam, and it is deliberately construction-time: a credential\n * that arrived as a step input would be hashed into a step key and persisted\n * everywhere the journal goes.\n *\n * The protocol wins every collision. These headers are applied to a request\n * first. Configured `content-type`, `content-length`, and `content-range`\n * are removed, then computed only where the protocol needs them. Whole-blob\n * uploads, including fallbacks, never carry `content-range`.\n * All configured header names are redacted in HTTP spans, in addition to\n * the host's existing redaction rules.\n *\n * A name that is not an HTTP token, or a value longer than 16 KiB or\n * carrying a control character (CR and LF included), is refused at\n * construction as `invalid_configuration`.\n */\n readonly headers?: Record<string, string> | undefined\n /**\n * The deadline on a single download, from the request leaving to the last\n * body byte arriving. A shared tier that stops answering must fail the read\n * rather than park it forever. `CombinedArtifacts.get` propagates a remote\n * `ArtifactStoreError`, including a download timeout. Only opportunistic\n * uploads from `CombinedArtifacts.put` ignore remote failures;\n * `ArtifactSync.hydrate` separately turns a failure into replay rejection.\n * Defaults to 60 seconds, Bazel's `--remote_timeout`\n * default for the same REST protocol (its `RemoteOptions`: \"For the REST\n * cache, this is both the connect and the read timeout\").\n */\n readonly downloadTimeout?: Duration.Input | undefined\n /**\n * Deadline for an upload, including every resume probe and chunk. Defaults\n * to 60 seconds.\n */\n readonly uploadTimeout?: Duration.Input | undefined\n /**\n * Deadline for an existence probe or one `findMissing` batch, including its\n * response body. Defaults to 60 seconds.\n */\n readonly requestTimeout?: Duration.Input | undefined\n /**\n * The largest download this client will buffer, in bytes. The body is read\n * incrementally and abandoned the moment it exceeds the bound — and refused\n * outright when `Content-Length` already declares the excess — so a\n * mis-serving or hostile cache cannot make this process buffer and hash an\n * arbitrarily large body before the digest check refuses it. Defaults to\n * 256 MiB.\n */\n readonly maxDownloadBytes?: number | undefined\n /**\n * Largest `findMissing` response body accepted. Defaults to the protocol's\n * 256 KiB request/response bound and may only lower that bound.\n */\n readonly maxFindMissingResponseBytes?: number | undefined\n /**\n * Above this many bytes an upload travels as a sequence of `Content-Range`\n * `PUT`s instead of one whole-blob body, and a transfer that died partway\n * resumes from the prefix the tier kept rather than starting over. See\n * {@link module:RemoteArtifacts | the chunked upload protocol} below.\n *\n * Absent by default: every upload is one whole-blob `PUT`, which is what\n * Bazel's dumb-HTTP client does and what every deployment already serving\n * this protocol expects. Set it when a proxy caps request bodies, or when\n * artifacts are large enough that losing a transfer is expensive.\n *\n * The repository's own hosted and self-hosted cache services answer the\n * ranged probe with `400`, so against them this dial degrades to one\n * whole-blob `PUT`, and their 16 MiB body cap refuses a larger blob with\n * `413` in either mode.\n */\n readonly chunkBytes?: number | undefined\n /**\n * How eagerly a composition reading through this tier materializes blobs\n * into its local store. This is Bazel's `--remote_download_{all,toplevel,\n * minimal}` dial (`RemoteOutputChecker`), and it lives here because the\n * shared tier is the thing being conserved.\n *\n * - `all` (the default) prefetches every referenced blob when a replay is\n * admitted, so the replay reads local bytes.\n * - `toplevel` prefetches nothing and materializes a blob into the local\n * store on the first read that actually needs it.\n * - `minimal` prefetches nothing and materializes nothing: a read is served\n * straight from the shared tier and the local store never grows.\n *\n * `CombinedArtifacts.get` honors the last two; `@smthrs/engine-store`'s\n * `ArtifactSync.hydrate` honors the first.\n */\n readonly downloadPolicy?: DownloadPolicy | undefined\n}\n\n/**\n * The default download deadline. 60 seconds is Bazel's `--remote_timeout`\n * default, governing the same dumb-HTTP cache protocol.\n */\nconst defaultDownloadTimeout = Duration.seconds(60)\nconst defaultUploadTimeout = Duration.seconds(60)\nconst defaultRequestTimeout = Duration.seconds(60)\n\n/**\n * The default bound on a downloaded blob: 256 MiB. Artifacts are spilled step\n * values, not media libraries; a body past this size is far more likely a\n * mis-serving cache than a legitimate blob, and the dial is per-store for a\n * deployment that knows better.\n */\nconst defaultMaxDownloadBytes = 256 * 1024 * 1024\nconst maxFindMissingBatchDigests = 1_000\nconst maxFindMissingBodyBytes = 256 * 1024\nconst encoder = new TextEncoder()\nconst decoder = new TextDecoder(\"utf-8\", { fatal: true })\n\n/**\n * The credential-free part of a transport failure: the HTTP client's reason\n * tag and the first operating-system error code (`ECONNREFUSED`, `ENOTFOUND`,\n * `CERT_HAS_EXPIRED`) in its cause chain. Never the request, the URL, the\n * headers, or free-form text, any of which may carry a credential.\n */\nconst transportReason = (cause: unknown): string | undefined => {\n const reason = (cause as { readonly reason?: { readonly _tag?: unknown } } | null)?.reason\n const tag = typeof reason?._tag === \"string\" ? reason._tag : undefined\n let code: string | undefined\n let current: unknown = reason ?? cause\n for (let depth = 0; depth < 8 && typeof current === \"object\" && current !== null; depth++) {\n const candidate = (current as { readonly code?: unknown }).code\n if (typeof candidate === \"string\" && /^[A-Z][A-Z0-9_]{1,63}$/.test(candidate)) {\n code = candidate\n break\n }\n current = (current as { readonly cause?: unknown }).cause\n }\n const parts = [tag, code].filter((part): part is string => part !== undefined)\n return parts.length === 0 ? undefined : parts.join(\" \")\n}\n\nconst transportFailure = (operation: string, cause?: unknown): ArtifactStore.ArtifactStoreError => {\n const reason = transportReason(cause)\n return new ArtifactStore.ArtifactStoreError({\n code: \"transport_failed\",\n message: `the remote artifact tier refused ${operation}${reason === undefined ? \"\" : ` (${reason})`}`\n })\n}\n\nconst unexpectedStatus = (operation: string, status: number): ArtifactStore.ArtifactStoreError =>\n new ArtifactStore.ArtifactStoreError({\n code: \"transport_failed\",\n message: `the remote artifact tier answered ${operation} with HTTP ${status}`\n })\n\nconst invalidOption = (name: string): ArtifactStore.ArtifactStoreError =>\n new ArtifactStore.ArtifactStoreError({\n code: \"invalid_configuration\",\n message: `invalid remote artifact option: ${name}`\n })\n\n/** The batched-probe response body. */\nconst FindMissingResponse = Schema.Struct({ missing: Schema.Array(Schema.String) })\n\n/** An RFC 9110 field-name token. */\nconst headerName = /^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/\n\n/** Whether a header value carries a C0 control character or DEL, CR and LF included. */\nconst hasControlText = (value: string): boolean => {\n for (let index = 0; index < value.length; index++) {\n const unit = value.charCodeAt(index)\n if (unit <= 0x1f || unit === 0x7f) return true\n }\n return false\n}\n\nconst configurationFailure = (message: string): ArtifactStore.ArtifactStoreError =>\n new ArtifactStore.ArtifactStoreError({ code: \"invalid_configuration\", message })\n\n/**\n * A 2xx is success, a 404 is a miss, and everything else is a transport\n * failure. Bazel's HTTP client takes the same three-way split; it is the only\n * classification a dumb-HTTP cache can support, because there is no richer\n * error envelope on the wire.\n */\nconst isOk = (response: HttpClientResponse.HttpClientResponse): boolean =>\n response.status >= 200 && response.status < 300\n\n/**\n * Builds a remote artifact store over Effect's `HttpClient`.\n *\n * @category constructors\n * @since 1.0.0-rc.0\n * @slop\n */\nexport const make = (\n options: Options\n): Effect.Effect<Service, ArtifactStore.ArtifactStoreError, HttpClient.HttpClient> =>\n Effect.gen(function*() {\n const configured = yield* Effect.try({\n try: () => {\n const headers = options.headers === undefined\n ? undefined\n : Object.freeze(Object.fromEntries(\n Object.entries(options.headers).map(([name, value]) => {\n // Refused here, once, rather than on every request as an opaque\n // transport failure: a CRLF in a value or a space in a name can\n // never become a valid header.\n if (typeof value !== \"string\" || value.length > 16 * 1024 || hasControlText(value)) {\n throw new TypeError(\"header value\")\n }\n if (!headerName.test(name)) throw new TypeError(\"header name\")\n return [name, value]\n })\n ))\n return {\n endpoint: options.endpoint,\n headers,\n downloadTimeout: options.downloadTimeout,\n uploadTimeout: options.uploadTimeout,\n requestTimeout: options.requestTimeout,\n maxDownloadBytes: options.maxDownloadBytes,\n maxFindMissingResponseBytes: options.maxFindMissingResponseBytes,\n chunkBytes: options.chunkBytes,\n downloadPolicy: options.downloadPolicy\n }\n },\n catch: () => invalidOption(\"options\")\n })\n if (typeof configured.endpoint !== \"string\") return yield* Effect.fail(invalidOption(\"endpoint\"))\n const endpoint = yield* Effect.try({\n try: () => new URL(configured.endpoint),\n catch: () => configurationFailure(\"invalid remote artifact endpoint\")\n })\n // A self-hosted cache on this machine is reachable over plain HTTP, and\n // loopback traffic never leaves the host. Only literal loopback names are\n // exempt: a `*.localhost` name reaches loopback only on a resolver that\n // follows RFC 6761, and one that forwards it to DNS would carry the\n // credential headers over plaintext to whatever host answers.\n const loopback = endpoint.hostname === \"localhost\" || endpoint.hostname === \"127.0.0.1\" ||\n endpoint.hostname === \"[::1]\"\n if (endpoint.protocol !== \"https:\" && !(endpoint.protocol === \"http:\" && loopback)) {\n return yield* Effect.fail(configurationFailure(\"remote artifact endpoint must use HTTPS\"))\n }\n if (endpoint.username !== \"\" || endpoint.password !== \"\" || endpoint.search !== \"\" || endpoint.hash !== \"\") {\n return yield* Effect.fail(\n configurationFailure(\"remote artifact endpoint must not contain credentials, a query, or a fragment\")\n )\n }\n endpoint.pathname = endpoint.pathname.replace(/\\/+$/, \"\")\n const base = endpoint.toString().replace(/\\/+$/, \"\")\n const headers = configured.headers\n const client = yield* HttpClient.HttpClient\n const credentialNames = Object.keys(headers ?? {}).map((name) => name.toLowerCase())\n /**\n * Protocol-owned headers are computed from each request's body and range,\n * never inherited from credentials, including on a whole-blob fallback.\n */\n const authorized = (request: HttpClientRequest.HttpClientRequest): HttpClientRequest.HttpClientRequest =>\n (headers === undefined ? request : HttpClientRequest.setHeaders(request, headers)).pipe(\n HttpClientRequest.removeHeader(\"content-type\"),\n HttpClientRequest.removeHeader(\"content-length\"),\n HttpClientRequest.removeHeader(\"content-range\")\n )\n // The address is a URL path segment, so it is refused before it is\n // interpolated and percent-encoded when it is: an address carrying a\n // separator or a `..` would otherwise point this client at a different\n // resource on the configured endpoint. `ArtifactStore.validateDigest` is\n // the same guard the filesystem tier applies to the same untrusted input —\n // a digest read back out of a durable row.\n const casUrl = (digest: ArtifactStore.Digest) => `${base}/cas/${digest}`\n const scopedClient = HttpClient.withScope(client)\n /**\n * Keep body processing inside the exchange; abort unused responses on every exit.\n *\n * Redirects are never followed. A fetch-based client follows them by\n * default and strips only `Authorization` on a cross-origin hop, so a\n * tier answering `3xx` would otherwise receive the credential headers at\n * another origin or over plain HTTP. Manual mode hands the `3xx` back,\n * where it is an unexpected status (or, for `308`, the resumable-upload\n * answer this protocol already speaks).\n */\n const send = <A, E, R>(\n operation: string,\n request: HttpClientRequest.HttpClientRequest,\n use: (response: HttpClientResponse.HttpClientResponse) => Effect.Effect<A, E, R>\n ): Effect.Effect<A, E | ArtifactStore.ArtifactStoreError, R> =>\n Effect.gen(function*() {\n const redactedNames = yield* Headers.CurrentRedactedNames\n const requestInit = yield* Effect.serviceOption(FetchHttpClient.RequestInit)\n return yield* scopedClient.execute(request).pipe(\n Effect.provideService(FetchHttpClient.RequestInit, {\n ...Option.getOrUndefined(requestInit),\n redirect: \"manual\"\n }),\n Effect.mapError((cause) => transportFailure(operation, cause)),\n Effect.flatMap(use),\n Effect.provideService(Headers.CurrentRedactedNames, [...redactedNames, ...credentialNames]),\n Effect.scoped\n )\n })\n const parseDeadline = (name: string, input: Duration.Input | undefined, fallback: Duration.Duration) =>\n Effect.gen(function*() {\n const parsed = Duration.fromInput(input ?? fallback)\n if (\n Option.isNone(parsed) ||\n !Number.isFinite(Duration.toMillis(parsed.value)) ||\n Duration.toMillis(parsed.value) <= 0\n ) {\n return yield* Effect.fail(invalidOption(name))\n }\n return parsed.value\n })\n const downloadDeadline = yield* parseDeadline(\n \"downloadTimeout\",\n configured.downloadTimeout,\n defaultDownloadTimeout\n )\n const uploadDeadline = yield* parseDeadline(\"uploadTimeout\", configured.uploadTimeout, defaultUploadTimeout)\n const requestDeadline = yield* parseDeadline(\"requestTimeout\", configured.requestTimeout, defaultRequestTimeout)\n const maxDownloadBytes = configured.maxDownloadBytes ?? defaultMaxDownloadBytes\n if (!Number.isSafeInteger(maxDownloadBytes) || maxDownloadBytes <= 0) {\n return yield* Effect.fail(invalidOption(\"maxDownloadBytes\"))\n }\n const maxFindMissingResponseBytes = configured.maxFindMissingResponseBytes ?? maxFindMissingBodyBytes\n if (\n !Number.isSafeInteger(maxFindMissingResponseBytes) ||\n maxFindMissingResponseBytes <= 0 ||\n maxFindMissingResponseBytes > maxFindMissingBodyBytes\n ) {\n return yield* Effect.fail(invalidOption(\"maxFindMissingResponseBytes\"))\n }\n const chunkBytes = configured.chunkBytes\n if (chunkBytes !== undefined && (!Number.isSafeInteger(chunkBytes) || chunkBytes <= 0)) {\n return yield* Effect.fail(invalidOption(\"chunkBytes\"))\n }\n const downloadPolicy = configured.downloadPolicy ?? \"all\"\n if (!Schema.is(DownloadPolicy)(downloadPolicy)) {\n return yield* Effect.fail(invalidOption(\"downloadPolicy\"))\n }\n const bodyTooLarge = (operation: string, received: number, bound: number): ArtifactStore.ArtifactStoreError =>\n new ArtifactStore.ArtifactStoreError({\n code: \"transport_failed\",\n message: `the remote artifact tier answered ${operation} with ${received} bytes, past the ${bound}-byte bound`\n })\n const timedOut = (operation: string): ArtifactStore.ArtifactStoreError =>\n new ArtifactStore.ArtifactStoreError({\n code: \"transport_failed\",\n message: `the remote artifact tier did not finish ${operation} within its configured deadline`\n })\n const within = <A, E, R>(\n operation: string,\n deadline: Duration.Duration,\n effect: Effect.Effect<A, E, R>\n ): Effect.Effect<A, E | ArtifactStore.ArtifactStoreError, R> =>\n effect.pipe(\n Effect.timeout(deadline),\n Effect.catchTag(\"TimeoutError\", () => Effect.fail(timedOut(operation)))\n )\n /**\n * Reads a download body incrementally, refusing it the moment it exceeds\n * the size bound; a `Content-Length` that already declares the excess is\n * refused before a single body byte is read. Streaming instead of\n * buffering the whole body means the guard fires at most one chunk past\n * the bound, never after an arbitrarily large response is in memory.\n */\n const readBounded = (\n response: HttpClientResponse.HttpClientResponse,\n operation: string,\n bound: number\n ) =>\n Effect.gen(function*() {\n const declared = response.headers[\"content-length\"]\n if (declared !== undefined) {\n const declaredBytes = Number(declared)\n if (!/^\\d+$/.test(declared) || !Number.isSafeInteger(declaredBytes)) {\n return yield* Effect.fail(transportFailure(`${operation} body`))\n }\n if (declaredBytes > bound) return yield* Effect.fail(bodyTooLarge(operation, declaredBytes, bound))\n }\n const chunks: Array<Uint8Array> = []\n let received = 0\n yield* Stream.runForEach(response.stream, (chunk: Uint8Array) =>\n Effect.suspend(() => {\n received += chunk.byteLength\n if (received > bound) return Effect.fail(bodyTooLarge(operation, received, bound))\n chunks.push(new Uint8Array(chunk))\n return Effect.void\n })).pipe(\n Effect.catchTag(\"HttpClientError\", (cause) =>\n // An absent body is zero bytes, not a transport refusal: the\n // digest check in `get` is the arbiter of whether empty content\n // is the requested artifact.\n cause.reason._tag === \"EmptyBodyError\"\n ? Effect.void\n : Effect.fail(transportFailure(`${operation} body`)))\n )\n const bytes = new Uint8Array(received)\n let offset = 0\n for (const chunk of chunks) {\n bytes.set(chunk, offset)\n offset += chunk.byteLength\n }\n return bytes\n })\n /** The network half of `get`: request, status split, bounded body read. */\n const download = (digest: ArtifactStore.Digest) =>\n send(\"a download\", authorized(HttpClientRequest.get(casUrl(digest))), (response) =>\n Effect.gen(function*() {\n if (response.status === 404) {\n return yield* Effect.fail(new ArtifactStore.ArtifactMissing({ code: \"artifact_missing\", digest }))\n }\n if (!isOk(response)) return yield* Effect.fail(unexpectedStatus(\"a download\", response.status))\n return yield* readBounded(response, \"a download\", maxDownloadBytes)\n }))\n\n /** One whole-blob `PUT`: the protocol every dumb-HTTP CAS already speaks. */\n const uploadWhole = (digest: ArtifactStore.Digest, bytes: Uint8Array) =>\n send(\n \"an upload\",\n authorized(HttpClientRequest.put(casUrl(digest))).pipe(\n HttpClientRequest.bodyUint8Array(bytes, \"application/octet-stream\")\n ),\n (response) => isOk(response) ? Effect.void : Effect.fail(unexpectedStatus(\"an upload\", response.status))\n )\n\n /**\n * The byte after the prefix a `Range: bytes=0-{last}` header reports, or\n * `undefined` when the answer names no prefix. Only a prefix starting at\n * zero is usable: a tier holding a middle span has nothing this client can\n * continue from, so it is treated as holding nothing.\n */\n const resumeOffset = (response: HttpClientResponse.HttpClientResponse): number | undefined => {\n const header = response.headers[\"range\"]\n if (header === undefined) return undefined\n const match = /^bytes=0-(\\d+)$/.exec(header.trim())\n if (match === null) return undefined\n const last = Number(match[1])\n // A prefix past the safe integer range does not survive `Number`: the\n // decimal digits are rounded, so the offset it names is a number this\n // client made up. It proves nothing about what the tier holds, which is\n // the same as naming no prefix at all.\n if (!Number.isSafeInteger(last)) return undefined\n return last + 1\n }\n\n /**\n * A tier that refuses ranged bodies, in the three ways HTTP has to say\n * so. `411` and `416` refuse the body's framing; `400` is RFC 9110\n * section 14.5's answer from a resource that \"does not support partial\n * PUT requests\", and it is what both of this repository's cache services\n * send. Falling back on it can mask no genuine `400`: the whole-blob\n * `PUT` that follows presents the same URL, digest, and credential, so a\n * real refusal comes straight back on that request.\n */\n const rejectsRanges = (status: number): boolean => status === 400 || status === 411 || status === 416\n\n /**\n * Whether the tier holds exactly `total` bytes under `digest`, read from\n * the `Content-Length` of a `HEAD` answer.\n *\n * This is the only claim about a chunked transfer this client will make on\n * its own behalf. A tier that answers `HEAD` without a length is not\n * refused — it simply proves nothing, so the caller sends the blob whole.\n */\n const holdsWhole = (digest: ArtifactStore.Digest, total: number) =>\n send(\"an existence probe\", authorized(HttpClientRequest.head(casUrl(digest))), (response) =>\n Effect.sync(() => {\n if (!isOk(response)) return false\n const declared = response.headers[\"content-length\"]\n return declared !== undefined && Number(declared) === total\n }))\n\n /**\n * Sends `bytes` as `Content-Range` chunks, resuming from whatever prefix\n * the tier reports. Answers `false` when the tier has not proved it stored\n * the whole blob, which is the caller's signal to send it whole.\n *\n * A `2xx` answer is never by itself proof of that. A tier that ignores\n * `Content-Range`, such as plain WebDAV `PUT`, which Bazel documents as a\n * supported dumb-HTTP cache, answers every request in this sequence `201`\n * while storing only the last body it was handed, which for the probe is\n * zero bytes. So only a `308` continues the sequence, and the completing\n * chunk's `2xx` is confirmed with `HEAD` before the caller is told the\n * digest is published. Everything else falls back, and the whole-blob\n * `PUT` overwrites whatever partial body the sequence left behind.\n */\n const uploadChunked = (digest: ArtifactStore.Digest, bytes: Uint8Array, chunk: number) =>\n Effect.gen(function*() {\n const total = bytes.byteLength\n const ranged = (range: string, body: Uint8Array) =>\n send(\n \"an upload\",\n authorized(HttpClientRequest.put(casUrl(digest))).pipe(\n HttpClientRequest.bodyUint8Array(body, \"application/octet-stream\"),\n HttpClientRequest.setHeader(\"content-range\", range)\n ),\n // Only status and headers are used after the response is released.\n Effect.succeed\n )\n // A blob the tier already holds whole costs one `HEAD` and no body.\n // Asking first is also what makes the probe's answer readable: a `2xx`\n // to it can no longer mean \"already there\", so the one reading left is\n // a tier that ignored the header and stored the empty body.\n if (yield* holdsWhole(digest, total)) return true\n // The probe carries no bytes: it exists to learn where to start, so\n // that an interrupted transfer costs one round trip rather than the\n // whole blob again.\n const probe = yield* ranged(`bytes */${total}`, new Uint8Array(0))\n if (isOk(probe) || rejectsRanges(probe.status)) return false\n if (probe.status !== 308 && probe.status !== 404) {\n return yield* Effect.fail(unexpectedStatus(\"an upload probe\", probe.status))\n }\n let offset = probe.status === 308 ? resumeOffset(probe) ?? 0 : 0\n while (offset < total) {\n const end = Math.min(offset + chunk, total)\n const answer = yield* ranged(`bytes ${offset}-${end - 1}/${total}`, bytes.subarray(offset, end))\n if (answer.status !== 308) {\n // The completing chunk may answer `2xx`; anything before it may\n // not, because a tier that is still owed bytes and reports success\n // is a tier that is not reading the header. Both are checked\n // against what the tier actually holds below.\n if (end === total && isOk(answer)) break\n if (isOk(answer) || rejectsRanges(answer.status)) return false\n return yield* Effect.fail(unexpectedStatus(\"an upload\", answer.status))\n }\n // A tier that reports a longer prefix than this chunk delivered has\n // more than we just sent, so the next chunk starts there. Never\n // backwards: an answer naming a shorter prefix would loop forever.\n offset = Math.max(end, resumeOffset(answer) ?? 0)\n }\n return yield* holdsWhole(digest, total)\n })\n\n const put: ArtifactStore.Service[\"put\"] = Effect.fn(\"RemoteArtifacts.put\")((bytes: Uint8Array) =>\n Effect.flatMap(ArtifactStore.snapshotBytes(bytes), (snapshot) =>\n Effect.gen(function*() {\n const digest = yield* ArtifactStore.measureBytes(snapshot)\n yield* Effect.annotateCurrentSpan({ digest })\n yield* within(\n \"an upload\",\n uploadDeadline,\n Effect.gen(function*() {\n if (chunkBytes === undefined || snapshot.byteLength <= chunkBytes) {\n yield* uploadWhole(digest, snapshot)\n return\n }\n const transferred = yield* uploadChunked(digest, snapshot, chunkBytes)\n if (!transferred) yield* uploadWhole(digest, snapshot)\n })\n )\n return digest\n }))\n )\n\n const get: ArtifactStore.Service[\"get\"] = Effect.fn(\"RemoteArtifacts.get\")((digest: string) =>\n Effect.gen(function*() {\n const validated = yield* ArtifactStore.validateDigest(digest)\n yield* Effect.annotateCurrentSpan({ digest: validated })\n // The deadline covers the whole exchange — request, headers, body —\n // because a tier that stalls mid-body is exactly as unanswering as\n // one that never accepts the connection.\n const bytes = yield* within(\"a download\", downloadDeadline, download(validated))\n // The shared tier is the least trusted store there is: it is written\n // by machines this one has never met. Verifying the address here means\n // a mis-serving or compromised cache can waste a round trip but can\n // never substitute content.\n const measured = yield* ArtifactStore.measureBytes(bytes)\n if (measured !== validated) {\n return yield* Effect.fail(\n new ArtifactStore.ArtifactCorruption({\n code: \"artifact_corruption\",\n recordedDigest: validated,\n measuredDigest: measured\n })\n )\n }\n return bytes\n })\n )\n\n const has: ArtifactStore.Service[\"has\"] = Effect.fn(\"RemoteArtifacts.has\")((digest: string) =>\n Effect.gen(function*() {\n const validated = yield* ArtifactStore.validateDigest(digest)\n yield* Effect.annotateCurrentSpan({ digest: validated })\n const response = yield* within(\n \"an existence probe\",\n requestDeadline,\n send(\"an existence probe\", authorized(HttpClientRequest.head(casUrl(validated))), Effect.succeed)\n )\n if (response.status === 404) return false\n if (!isOk(response)) return yield* Effect.fail(unexpectedStatus(\"an existence probe\", response.status))\n return true\n })\n )\n\n const findMissing: ArtifactStore.Service[\"findMissing\"] = Effect.fn(\"RemoteArtifacts.findMissing\")(\n (digests: Iterable<string>) =>\n Effect.gen(function*() {\n const requested = [...new Set(digests)]\n if (requested.length === 0) return []\n // The batch travels in a JSON body rather than a path, but an\n // unusable address is still an unusable address, and the local tier\n // refuses the same batch wholesale rather than probing part of it.\n const validated: Array<ArtifactStore.Digest> = []\n for (const digest of requested) validated.push(yield* ArtifactStore.validateDigest(digest))\n yield* Effect.annotateCurrentSpan({ count: validated.length })\n const missing = new Set<string>()\n for (let offset = 0; offset < validated.length; offset += maxFindMissingBatchDigests) {\n const batch = validated.slice(offset, offset + maxFindMissingBatchDigests)\n const requestBody = encoder.encode(JSON.stringify({ digests: batch }))\n /* v8 ignore next 3 -- strict 64-byte digests plus the 1,000-entry batch cap make this\n * at most 67,013 bytes; keep the protocol assertion beside serialization in case either invariant changes. */\n if (requestBody.byteLength > maxFindMissingBodyBytes) {\n return yield* Effect.fail(configurationFailure(\"remote artifact findMissing request exceeds 256 KiB\"))\n }\n const decoded = yield* within(\n \"a batched existence probe\",\n requestDeadline,\n send(\n \"a batched existence probe\",\n authorized(HttpClientRequest.post(`${base}/cas/findMissing`)).pipe(\n HttpClientRequest.bodyUint8Array(requestBody, \"application/json\")\n ),\n (response) =>\n Effect.gen(function*() {\n if (!isOk(response)) {\n return yield* Effect.fail(unexpectedStatus(\"a batched existence probe\", response.status))\n }\n const bodyBytes = yield* readBounded(\n response,\n \"a batched existence probe\",\n maxFindMissingResponseBytes\n )\n const body = yield* Effect.try({\n try: () => JSON.parse(decoder.decode(bodyBytes)) as unknown,\n catch: () => transportFailure(\"a batched existence probe body\")\n })\n return yield* Schema.decodeUnknownEffect(FindMissingResponse)(body).pipe(\n Effect.mapError(() => transportFailure(\"a batched existence probe body\"))\n )\n })\n )\n )\n const asked = new Set<string>(batch)\n for (const digest of decoded.missing) if (asked.has(digest)) missing.add(digest)\n }\n // \"The returned set is guaranteed to be a subset of `digests`\"\n // (`MissingDigestsFinder`). Bazel gets that from the server; we\n // enforce it on this side too, because a server that answered with\n // an unrequested digest would otherwise make the caller upload bytes\n // it never asked about.\n return validated.filter((digest) => missing.has(digest))\n })\n )\n\n return { put, get, has, findMissing, downloadPolicy }\n })\n\n/**\n * Provides a remote artifact store as the `ArtifactStore` tag.\n *\n * Composing this *alone* makes every artifact read and write a network round\n * trip. The intended production shape is\n * {@link CombinedArtifacts.layer | CombinedArtifacts}, with this as its remote\n * tier.\n *\n * @category layers\n * @since 1.0.0-rc.0\n * @slop\n */\nexport const layer = (\n options: Options\n): Layer.Layer<ArtifactStore.ArtifactStore, ArtifactStore.ArtifactStoreError, HttpClient.HttpClient> =>\n Layer.effect(ArtifactStore.ArtifactStore)(make(options))\n"]}
1
+ {"version":3,"file":"RemoteArtifacts.js","sourceRoot":"","sources":["../../src/RemoteArtifacts.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The shared artifact tier: an {@link ArtifactStore.Service} spoken over HTTP.\n *\n * The wire protocol mirrors Bazel's dumb-HTTP remote cache, class\n * `HttpCacheClient` in `com.google.devtools.build.lib.remote.http`, which\n * documents it as: \"CAS (Content Addressable Storage) blobs are stored under\n * the path `/cas/base16-key`\", uploaded with `PUT`, downloaded with `GET`. We add\n * `HEAD /cas/{digest}` for a single existence probe and\n * `POST /cas/findMissing` for the batched one, because Bazel's HTTP client has\n * no `findMissingDigests` at all — it answers \"everything is missing\" and\n * re-uploads — and a batched probe is the whole reason\n * `MissingDigestsFinder` exists in the gRPC client.\n *\n * Transport is Effect's own `HttpClient` tag. There is no lower transport in\n * this repository, and there does not need to be one: the kernel already\n * decorates that tag with `net:get`/`net:post` capability checks, so a remote\n * artifact fetch is permission-checked like every other egress.\n *\n * ## Chunked uploads\n *\n * With `chunkBytes` set, a blob past that size travels as a sequence of\n * `PUT /cas/{digest}` requests carrying `Content-Range: bytes {a}-{b}/{total}`,\n * preceded by a `HEAD /cas/{digest}` existence probe and a\n * `Content-Range: bytes *\\/{total}` probe with an empty body. This is the\n * resumable-upload shape HTTP already has: the range probe asks what prefix\n * the tier holds and `308` means \"keep going\", with a `Range: bytes=0-{last}`\n * header on the probe or on a chunk answer moving the offset.\n *\n * Only a `308` continues the sequence, and the completing chunk's `2xx` is\n * confirmed with `HEAD` before `put` reports the digest as published. A `2xx`\n * anywhere else, to the empty probe or to a chunk the tier is still owed bytes\n * after, reads as a tier that ignored `Content-Range` and stored the body it\n * was handed, which is what plain WebDAV `PUT` does. That tier, the one that\n * answers `400`, `411`, or `416`, and the one whose `HEAD` will not confirm\n * the stored length all get the same treatment: one whole-blob `PUT`, which\n * overwrites the partial body the sequence left behind. So the blob always\n * lands whole, and the cost of turning the dial on against a server that\n * never learned about it is round trips, never a digest published over bytes\n * the server does not hold.\n *\n * This repository's own hosted and self-hosted cache services are such\n * servers: neither implements the resumable sequence, and both refuse a\n * ranged `PUT` with `400`, RFC 9110 section 14.5's answer from a resource\n * that does not support partial `PUT`. Against them `chunkBytes` costs the\n * probe round trip and the blob travels whole, and both cap one request body\n * at 16 MiB, so a blob past that cap is refused with `413` in either mode.\n *\n * The endpoint and its credentials arrive as **layer construction options**.\n * They are capabilities, never step inputs: they are not hashed into a step\n * key, journaled, or returned in a recorded result. Invalid endpoint errors are\n * sanitized before they cross the boundary.\n *\n * @since 1.0.0-rc.0\n */\n\nimport * as Duration from \"effect/Duration\"\nimport * as Effect from \"effect/Effect\"\nimport * as FetchHttpClient from \"effect/http/FetchHttpClient\"\nimport * as Headers from \"effect/http/Headers\"\nimport * as HttpClient from \"effect/http/HttpClient\"\nimport * as HttpClientRequest from \"effect/http/HttpClientRequest\"\nimport type * as HttpClientResponse from \"effect/http/HttpClientResponse\"\nimport * as Layer from \"effect/Layer\"\nimport * as Option from \"effect/Option\"\nimport * as Schema from \"effect/Schema\"\nimport * as Stream from \"effect/Stream\"\nimport * as ArtifactStore from \"./ArtifactStore.ts\"\n\n/**\n * How eagerly a composition materializes shared blobs into its local store.\n *\n * @category models\n * @since 1.0.0-rc.0\n */\nexport const DownloadPolicy = Schema.Literals([\"all\", \"toplevel\", \"minimal\"] as const)\n\n/**\n * How eagerly a composition materializes remote blobs.\n *\n * @category models\n * @since 1.0.0-rc.0\n */\nexport type DownloadPolicy = typeof DownloadPolicy.Type\n\n/**\n * Every download policy, in materialization order.\n *\n * @category models\n * @since 1.0.0-rc.0\n */\nexport const downloadPolicies: ReadonlyArray<DownloadPolicy> = Object.freeze([\"all\", \"toplevel\", \"minimal\"])\n\n/**\n * A remote artifact tier, which is an ordinary store that also states how\n * eagerly a composition reading through it should materialize blobs locally.\n *\n * @category models\n * @since 1.0.0-rc.0\n */\nexport interface Service extends ArtifactStore.Service {\n readonly downloadPolicy: DownloadPolicy\n}\n\n/**\n * Reads the download policy a store declares, or `undefined` for a store that\n * declares none — every local store, and any foreign implementation.\n *\n * @category getters\n * @since 1.0.0-rc.0\n */\nexport const downloadPolicyOf = (store: ArtifactStore.Service): DownloadPolicy | undefined => {\n const declared = (store as { readonly downloadPolicy?: unknown }).downloadPolicy\n return Schema.is(DownloadPolicy)(declared) ? declared : undefined\n}\n\n/**\n * How to reach the shared artifact tier.\n *\n * @category models\n * @since 1.0.0-rc.0\n * @slop\n */\nexport interface Options {\n /**\n * The cache root, e.g. `https://cache.example.com`. `/cas/{digest}` and\n * `/cas/findMissing` are resolved beneath it. A trailing slash is ignored.\n *\n * Four shapes are refused at construction as `invalid_configuration`, before\n * any request leaves and therefore before `headers` can reach a wire: a value\n * that is not a string, one no `URL` parser accepts, a scheme other than\n * `https:` (plain `http:` is accepted only for the literal loopback hosts\n * `localhost`, `127.0.0.1`, and `[::1]`), and an endpoint carrying\n * userinfo, a query, or a fragment. The\n * last rule keeps a credential out of a place nothing here would redact: an\n * endpoint is interpolated into every request path and into span attributes.\n *\n * The refusal message names only the violated rule and never echoes the\n * endpoint, so a rejected `https://user:secret@host` cannot leak its\n * credential into a log line or a durable error.\n */\n readonly endpoint: string\n /**\n * Headers sent with every request — an `Authorization` bearer token, a\n * tenant header, whatever the deployment's gateway wants. This is the\n * credential seam, and it is deliberately construction-time: a credential\n * that arrived as a step input would be hashed into a step key and persisted\n * everywhere the journal goes.\n *\n * The protocol wins every collision. These headers are applied to a request\n * first. Configured `content-type`, `content-length`, and `content-range`\n * are removed, then computed only where the protocol needs them. Whole-blob\n * uploads, including fallbacks, never carry `content-range`.\n * All configured header names are redacted in HTTP spans, in addition to\n * the host's existing redaction rules.\n *\n * A name that is not an HTTP token, or a value longer than 16 KiB or\n * carrying a control character (CR and LF included), is refused at\n * construction as `invalid_configuration`.\n */\n readonly headers?: Record<string, string> | undefined\n /**\n * The deadline on a single download, from the request leaving to the last\n * body byte arriving. A shared tier that stops answering must fail the read\n * rather than park it forever. `CombinedArtifacts.get` propagates a remote\n * `ArtifactStoreError`, including a download timeout. Only opportunistic\n * uploads from `CombinedArtifacts.put` ignore remote failures;\n * `ArtifactSync.hydrate` separately turns a failure into replay rejection.\n * Defaults to 60 seconds, Bazel's `--remote_timeout`\n * default for the same REST protocol (its `RemoteOptions`: \"For the REST\n * cache, this is both the connect and the read timeout\").\n */\n readonly downloadTimeout?: Duration.Input | undefined\n /**\n * Deadline for an upload, including every resume probe and chunk. Defaults\n * to 60 seconds.\n */\n readonly uploadTimeout?: Duration.Input | undefined\n /**\n * Deadline for an existence probe or one `findMissing` batch, including its\n * response body. Defaults to 60 seconds.\n */\n readonly requestTimeout?: Duration.Input | undefined\n /**\n * The largest download this client will buffer, in bytes. The body is read\n * incrementally and abandoned the moment it exceeds the bound — and refused\n * outright when `Content-Length` already declares the excess — so a\n * mis-serving or hostile cache cannot make this process buffer and hash an\n * arbitrarily large body before the digest check refuses it. Defaults to\n * 256 MiB.\n */\n readonly maxDownloadBytes?: number | undefined\n /**\n * Largest `findMissing` response body accepted. Defaults to the protocol's\n * 256 KiB request/response bound and may only lower that bound.\n */\n readonly maxFindMissingResponseBytes?: number | undefined\n /**\n * Above this many bytes an upload travels as a sequence of `Content-Range`\n * `PUT`s instead of one whole-blob body, and a transfer that died partway\n * resumes from the prefix the tier kept rather than starting over. See\n * {@link module:RemoteArtifacts | the chunked upload protocol} below.\n *\n * Absent by default: every upload is one whole-blob `PUT`, which is what\n * Bazel's dumb-HTTP client does and what every deployment already serving\n * this protocol expects. Set it when a proxy caps request bodies, or when\n * artifacts are large enough that losing a transfer is expensive.\n *\n * The repository's own hosted and self-hosted cache services answer the\n * ranged probe with `400`, so against them this dial degrades to one\n * whole-blob `PUT`, and their 16 MiB body cap refuses a larger blob with\n * `413` in either mode.\n */\n readonly chunkBytes?: number | undefined\n /**\n * How eagerly a composition reading through this tier materializes blobs\n * into its local store. This is Bazel's `--remote_download_{all,toplevel,\n * minimal}` dial (`RemoteOutputChecker`), and it lives here because the\n * shared tier is the thing being conserved.\n *\n * - `all` (the default) prefetches every referenced blob when a replay is\n * admitted, so the replay reads local bytes.\n * - `toplevel` prefetches nothing and materializes a blob into the local\n * store on the first read that actually needs it.\n * - `minimal` prefetches nothing and materializes nothing: a read is served\n * straight from the shared tier and the local store never grows.\n *\n * `CombinedArtifacts.get` honors the last two; `@smthrs/engine-store`'s\n * `ArtifactSync.hydrate` honors the first.\n */\n readonly downloadPolicy?: DownloadPolicy | undefined\n}\n\n/**\n * The default download deadline. 60 seconds is Bazel's `--remote_timeout`\n * default, governing the same dumb-HTTP cache protocol.\n */\nconst defaultDownloadTimeout = Duration.seconds(60)\nconst defaultUploadTimeout = Duration.seconds(60)\nconst defaultRequestTimeout = Duration.seconds(60)\n\n/**\n * The default bound on a downloaded blob: 256 MiB. Artifacts are spilled step\n * values, not media libraries; a body past this size is far more likely a\n * mis-serving cache than a legitimate blob, and the dial is per-store for a\n * deployment that knows better.\n */\nconst defaultMaxDownloadBytes = 256 * 1024 * 1024\nconst maxFindMissingBatchDigests = 1_000\nconst maxFindMissingBodyBytes = 256 * 1024\nconst encoder = new TextEncoder()\nconst decoder = new TextDecoder(\"utf-8\", { fatal: true })\n\n/**\n * The credential-free part of a transport failure: the HTTP client's reason\n * tag and the first operating-system error code (`ECONNREFUSED`, `ENOTFOUND`,\n * `CERT_HAS_EXPIRED`) in its cause chain. Never the request, the URL, the\n * headers, or free-form text, any of which may carry a credential.\n */\nconst transportReason = (cause: unknown): string | undefined => {\n const reason = (cause as { readonly reason?: { readonly _tag?: unknown } } | null)?.reason\n const tag = typeof reason?._tag === \"string\" ? reason._tag : undefined\n let code: string | undefined\n let current: unknown = reason ?? cause\n for (let depth = 0; depth < 8 && typeof current === \"object\" && current !== null; depth++) {\n const candidate = (current as { readonly code?: unknown }).code\n if (typeof candidate === \"string\" && /^[A-Z][A-Z0-9_]{1,63}$/.test(candidate)) {\n code = candidate\n break\n }\n current = (current as { readonly cause?: unknown }).cause\n }\n const parts = [tag, code].filter((part): part is string => part !== undefined)\n return parts.length === 0 ? undefined : parts.join(\" \")\n}\n\nconst transportFailure = (operation: string, cause?: unknown): ArtifactStore.ArtifactStoreError => {\n const reason = transportReason(cause)\n return new ArtifactStore.ArtifactStoreError({\n code: \"transport_failed\",\n message: `the remote artifact tier refused ${operation}${reason === undefined ? \"\" : ` (${reason})`}`\n })\n}\n\nconst unexpectedStatus = (operation: string, status: number): ArtifactStore.ArtifactStoreError =>\n new ArtifactStore.ArtifactStoreError({\n code: \"transport_failed\",\n message: `the remote artifact tier answered ${operation} with HTTP ${status}`\n })\n\nconst invalidOption = (name: string): ArtifactStore.ArtifactStoreError =>\n new ArtifactStore.ArtifactStoreError({\n code: \"invalid_configuration\",\n message: `invalid remote artifact option: ${name}`\n })\n\n/** The batched-probe response body. */\nconst FindMissingResponse = Schema.Struct({ missing: Schema.Array(Schema.String) })\n\n/** An RFC 9110 field-name token. */\nconst headerName = /^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/\n\n/** Whether a header value carries a C0 control character or DEL, CR and LF included. */\nconst hasControlText = (value: string): boolean => {\n for (let index = 0; index < value.length; index++) {\n const unit = value.charCodeAt(index)\n if (unit <= 0x1f || unit === 0x7f) return true\n }\n return false\n}\n\nconst configurationFailure = (message: string): ArtifactStore.ArtifactStoreError =>\n new ArtifactStore.ArtifactStoreError({ code: \"invalid_configuration\", message })\n\n/**\n * A 2xx is success, a 404 is a miss, and everything else is a transport\n * failure. Bazel's HTTP client takes the same three-way split; it is the only\n * classification a dumb-HTTP cache can support, because there is no richer\n * error envelope on the wire.\n */\nconst isOk = (response: HttpClientResponse.HttpClientResponse): boolean =>\n response.status >= 200 && response.status < 300\n\n/**\n * Builds a remote artifact store over Effect's `HttpClient`.\n *\n * @category constructors\n * @since 1.0.0-rc.0\n * @slop\n */\nexport const make = (\n options: Options\n): Effect.Effect<Service, ArtifactStore.ArtifactStoreError, HttpClient.HttpClient> =>\n Effect.gen(function*() {\n const configured = yield* Effect.try({\n try: () => {\n const headers = options.headers === undefined\n ? undefined\n : Object.freeze(Object.fromEntries(\n Object.entries(options.headers).map(([name, value]) => {\n // Refused here, once, rather than on every request as an opaque\n // transport failure: a CRLF in a value or a space in a name can\n // never become a valid header.\n if (typeof value !== \"string\" || value.length > 16 * 1024 || hasControlText(value)) {\n throw new TypeError(\"header value\")\n }\n if (!headerName.test(name)) throw new TypeError(\"header name\")\n return [name, value]\n })\n ))\n return {\n endpoint: options.endpoint,\n headers,\n downloadTimeout: options.downloadTimeout,\n uploadTimeout: options.uploadTimeout,\n requestTimeout: options.requestTimeout,\n maxDownloadBytes: options.maxDownloadBytes,\n maxFindMissingResponseBytes: options.maxFindMissingResponseBytes,\n chunkBytes: options.chunkBytes,\n downloadPolicy: options.downloadPolicy\n }\n },\n catch: () => invalidOption(\"options\")\n })\n if (typeof configured.endpoint !== \"string\") return yield* Effect.fail(invalidOption(\"endpoint\"))\n const endpoint = yield* Effect.try({\n try: () => new URL(configured.endpoint),\n catch: () => configurationFailure(\"invalid remote artifact endpoint\")\n })\n // A self-hosted cache on this machine is reachable over plain HTTP, and\n // loopback traffic never leaves the host. Only literal loopback names are\n // exempt: a `*.localhost` name reaches loopback only on a resolver that\n // follows RFC 6761, and one that forwards it to DNS would carry the\n // credential headers over plaintext to whatever host answers.\n const loopback = endpoint.hostname === \"localhost\" || endpoint.hostname === \"127.0.0.1\" ||\n endpoint.hostname === \"[::1]\"\n if (endpoint.protocol !== \"https:\" && !(endpoint.protocol === \"http:\" && loopback)) {\n return yield* Effect.fail(configurationFailure(\"remote artifact endpoint must use HTTPS\"))\n }\n // An empty query or fragment (\"https://h/p?\") parses as \"\" yet survives\n // serialization, so the delimiter itself is refused in the href.\n if (\n endpoint.username !== \"\" || endpoint.password !== \"\" || endpoint.search !== \"\" || endpoint.hash !== \"\" ||\n endpoint.href.includes(\"?\") || endpoint.href.includes(\"#\")\n ) {\n return yield* Effect.fail(\n configurationFailure(\"remote artifact endpoint must not contain credentials, a query, or a fragment\")\n )\n }\n endpoint.pathname = endpoint.pathname.replace(/\\/+$/, \"\")\n const base = endpoint.toString().replace(/\\/+$/, \"\")\n const headers = configured.headers\n const client = yield* HttpClient.HttpClient\n const credentialNames = Object.keys(headers ?? {}).map((name) => name.toLowerCase())\n /**\n * Protocol-owned headers are computed from each request's body and range,\n * never inherited from credentials, including on a whole-blob fallback.\n */\n const authorized = (request: HttpClientRequest.HttpClientRequest): HttpClientRequest.HttpClientRequest =>\n (headers === undefined ? request : HttpClientRequest.setHeaders(request, headers)).pipe(\n HttpClientRequest.removeHeader(\"content-type\"),\n HttpClientRequest.removeHeader(\"content-length\"),\n HttpClientRequest.removeHeader(\"content-range\")\n )\n // The address is a URL path segment, so it is refused before it is\n // interpolated and percent-encoded when it is: an address carrying a\n // separator or a `..` would otherwise point this client at a different\n // resource on the configured endpoint. `ArtifactStore.validateDigest` is\n // the same guard the filesystem tier applies to the same untrusted input —\n // a digest read back out of a durable row.\n const casUrl = (digest: ArtifactStore.Digest) => `${base}/cas/${digest}`\n const scopedClient = HttpClient.withScope(client)\n /**\n * Keep body processing inside the exchange; abort unused responses on every exit.\n *\n * Redirects are never followed. A fetch-based client follows them by\n * default and strips only `Authorization` on a cross-origin hop, so a\n * tier answering `3xx` would otherwise receive the credential headers at\n * another origin or over plain HTTP. Manual mode hands the `3xx` back,\n * where it is an unexpected status (or, for `308`, the resumable-upload\n * answer this protocol already speaks).\n */\n const send = <A, E, R>(\n operation: string,\n request: HttpClientRequest.HttpClientRequest,\n use: (response: HttpClientResponse.HttpClientResponse) => Effect.Effect<A, E, R>\n ): Effect.Effect<A, E | ArtifactStore.ArtifactStoreError, R> =>\n Effect.gen(function*() {\n const redactedNames = yield* Headers.CurrentRedactedNames\n const requestInit = yield* Effect.serviceOption(FetchHttpClient.RequestInit)\n return yield* scopedClient.execute(request).pipe(\n Effect.provideService(FetchHttpClient.RequestInit, {\n ...Option.getOrUndefined(requestInit),\n redirect: \"manual\"\n }),\n Effect.mapError((cause) => transportFailure(operation, cause)),\n Effect.flatMap(use),\n Effect.provideService(Headers.CurrentRedactedNames, [...redactedNames, ...credentialNames]),\n Effect.scoped\n )\n })\n const parseDeadline = (name: string, input: Duration.Input | undefined, fallback: Duration.Duration) =>\n Effect.gen(function*() {\n const parsed = Duration.fromInput(input ?? fallback)\n if (\n Option.isNone(parsed) ||\n !Number.isFinite(Duration.toMillis(parsed.value)) ||\n Duration.toMillis(parsed.value) <= 0\n ) {\n return yield* Effect.fail(invalidOption(name))\n }\n return parsed.value\n })\n const downloadDeadline = yield* parseDeadline(\n \"downloadTimeout\",\n configured.downloadTimeout,\n defaultDownloadTimeout\n )\n const uploadDeadline = yield* parseDeadline(\"uploadTimeout\", configured.uploadTimeout, defaultUploadTimeout)\n const requestDeadline = yield* parseDeadline(\"requestTimeout\", configured.requestTimeout, defaultRequestTimeout)\n const maxDownloadBytes = configured.maxDownloadBytes ?? defaultMaxDownloadBytes\n if (!Number.isSafeInteger(maxDownloadBytes) || maxDownloadBytes <= 0) {\n return yield* Effect.fail(invalidOption(\"maxDownloadBytes\"))\n }\n const maxFindMissingResponseBytes = configured.maxFindMissingResponseBytes ?? maxFindMissingBodyBytes\n if (\n !Number.isSafeInteger(maxFindMissingResponseBytes) ||\n maxFindMissingResponseBytes <= 0 ||\n maxFindMissingResponseBytes > maxFindMissingBodyBytes\n ) {\n return yield* Effect.fail(invalidOption(\"maxFindMissingResponseBytes\"))\n }\n const chunkBytes = configured.chunkBytes\n if (chunkBytes !== undefined && (!Number.isSafeInteger(chunkBytes) || chunkBytes <= 0)) {\n return yield* Effect.fail(invalidOption(\"chunkBytes\"))\n }\n const downloadPolicy = configured.downloadPolicy ?? \"all\"\n if (!Schema.is(DownloadPolicy)(downloadPolicy)) {\n return yield* Effect.fail(invalidOption(\"downloadPolicy\"))\n }\n const bodyTooLarge = (operation: string, received: number, bound: number): ArtifactStore.ArtifactStoreError =>\n new ArtifactStore.ArtifactStoreError({\n code: \"transport_failed\",\n message: `the remote artifact tier answered ${operation} with ${received} bytes, past the ${bound}-byte bound`\n })\n const timedOut = (operation: string): ArtifactStore.ArtifactStoreError =>\n new ArtifactStore.ArtifactStoreError({\n code: \"transport_failed\",\n message: `the remote artifact tier did not finish ${operation} within its configured deadline`\n })\n const within = <A, E, R>(\n operation: string,\n deadline: Duration.Duration,\n effect: Effect.Effect<A, E, R>\n ): Effect.Effect<A, E | ArtifactStore.ArtifactStoreError, R> =>\n effect.pipe(\n Effect.timeout(deadline),\n Effect.catchTag(\"TimeoutError\", () => Effect.fail(timedOut(operation)))\n )\n /**\n * Reads a download body incrementally, refusing it the moment it exceeds\n * the size bound; a `Content-Length` that already declares the excess is\n * refused before a single body byte is read. Streaming instead of\n * buffering the whole body means the guard fires at most one chunk past\n * the bound, never after an arbitrarily large response is in memory.\n */\n const readBounded = (\n response: HttpClientResponse.HttpClientResponse,\n operation: string,\n bound: number\n ) =>\n Effect.gen(function*() {\n const declared = response.headers[\"content-length\"]\n if (declared !== undefined) {\n const declaredBytes = Number(declared)\n if (!/^\\d+$/.test(declared) || !Number.isSafeInteger(declaredBytes)) {\n return yield* Effect.fail(transportFailure(`${operation} body`))\n }\n if (declaredBytes > bound) return yield* Effect.fail(bodyTooLarge(operation, declaredBytes, bound))\n }\n const chunks: Array<Uint8Array> = []\n let received = 0\n yield* Stream.runForEach(response.stream, (chunk: Uint8Array) =>\n Effect.suspend(() => {\n received += chunk.byteLength\n if (received > bound) return Effect.fail(bodyTooLarge(operation, received, bound))\n chunks.push(new Uint8Array(chunk))\n return Effect.void\n })).pipe(\n Effect.catchTag(\"HttpClientError\", (cause) =>\n // An absent body is zero bytes, not a transport refusal: the\n // digest check in `get` is the arbiter of whether empty content\n // is the requested artifact.\n cause.reason._tag === \"EmptyBodyError\"\n ? Effect.void\n : Effect.fail(transportFailure(`${operation} body`)))\n )\n const bytes = new Uint8Array(received)\n let offset = 0\n for (const chunk of chunks) {\n bytes.set(chunk, offset)\n offset += chunk.byteLength\n }\n return bytes\n })\n /** The network half of `get`: request, status split, bounded body read. */\n const download = (digest: ArtifactStore.Digest) =>\n send(\"a download\", authorized(HttpClientRequest.get(casUrl(digest))), (response) =>\n Effect.gen(function*() {\n if (response.status === 404) {\n return yield* Effect.fail(new ArtifactStore.ArtifactMissing({ code: \"artifact_missing\", digest }))\n }\n if (!isOk(response)) return yield* Effect.fail(unexpectedStatus(\"a download\", response.status))\n return yield* readBounded(response, \"a download\", maxDownloadBytes)\n }))\n\n /** One whole-blob `PUT`: the protocol every dumb-HTTP CAS already speaks. */\n const uploadWhole = (digest: ArtifactStore.Digest, bytes: Uint8Array) =>\n send(\n \"an upload\",\n authorized(HttpClientRequest.put(casUrl(digest))).pipe(\n HttpClientRequest.bodyUint8Array(bytes, \"application/octet-stream\")\n ),\n (response) => isOk(response) ? Effect.void : Effect.fail(unexpectedStatus(\"an upload\", response.status))\n )\n\n /**\n * The byte after the prefix a `Range: bytes=0-{last}` header reports, or\n * `undefined` when the answer names no prefix. Only a prefix starting at\n * zero is usable: a tier holding a middle span has nothing this client can\n * continue from, so it is treated as holding nothing.\n */\n const resumeOffset = (response: HttpClientResponse.HttpClientResponse): number | undefined => {\n const header = response.headers[\"range\"]\n if (header === undefined) return undefined\n const match = /^bytes=0-(\\d+)$/.exec(header.trim())\n if (match === null) return undefined\n const last = Number(match[1])\n // A prefix past the safe integer range does not survive `Number`: the\n // decimal digits are rounded, so the offset it names is a number this\n // client made up. It proves nothing about what the tier holds, which is\n // the same as naming no prefix at all.\n if (!Number.isSafeInteger(last)) return undefined\n return last + 1\n }\n\n /**\n * A tier that refuses ranged bodies, in the three ways HTTP has to say\n * so. `411` and `416` refuse the body's framing; `400` is RFC 9110\n * section 14.5's answer from a resource that \"does not support partial\n * PUT requests\", and it is what both of this repository's cache services\n * send. Falling back on it can mask no genuine `400`: the whole-blob\n * `PUT` that follows presents the same URL, digest, and credential, so a\n * real refusal comes straight back on that request.\n */\n const rejectsRanges = (status: number): boolean => status === 400 || status === 411 || status === 416\n\n /**\n * Whether the tier holds exactly `total` bytes under `digest`, read from\n * the `Content-Length` of a `HEAD` answer.\n *\n * This is the only claim about a chunked transfer this client will make on\n * its own behalf. A tier that answers `HEAD` without a length is not\n * refused — it simply proves nothing, so the caller sends the blob whole.\n */\n const holdsWhole = (digest: ArtifactStore.Digest, total: number) =>\n send(\"an existence probe\", authorized(HttpClientRequest.head(casUrl(digest))), (response) =>\n Effect.sync(() => {\n if (!isOk(response)) return false\n const declared = response.headers[\"content-length\"]\n return declared !== undefined && Number(declared) === total\n }))\n\n /**\n * Sends `bytes` as `Content-Range` chunks, resuming from whatever prefix\n * the tier reports. Answers `false` when the tier has not proved it stored\n * the whole blob, which is the caller's signal to send it whole.\n *\n * A `2xx` answer is never by itself proof of that. A tier that ignores\n * `Content-Range`, such as plain WebDAV `PUT`, which Bazel documents as a\n * supported dumb-HTTP cache, answers every request in this sequence `201`\n * while storing only the last body it was handed, which for the probe is\n * zero bytes. So only a `308` continues the sequence, and the completing\n * chunk's `2xx` is confirmed with `HEAD` before the caller is told the\n * digest is published. Everything else falls back, and the whole-blob\n * `PUT` overwrites whatever partial body the sequence left behind.\n */\n const uploadChunked = (digest: ArtifactStore.Digest, bytes: Uint8Array, chunk: number) =>\n Effect.gen(function*() {\n const total = bytes.byteLength\n const ranged = (range: string, body: Uint8Array) =>\n send(\n \"an upload\",\n authorized(HttpClientRequest.put(casUrl(digest))).pipe(\n HttpClientRequest.bodyUint8Array(body, \"application/octet-stream\"),\n HttpClientRequest.setHeader(\"content-range\", range)\n ),\n // Only status and headers are used after the response is released.\n Effect.succeed\n )\n // A blob the tier already holds whole costs one `HEAD` and no body.\n // Asking first is also what makes the probe's answer readable: a `2xx`\n // to it can no longer mean \"already there\", so the one reading left is\n // a tier that ignored the header and stored the empty body.\n if (yield* holdsWhole(digest, total)) return true\n // The probe carries no bytes: it exists to learn where to start, so\n // that an interrupted transfer costs one round trip rather than the\n // whole blob again.\n const probe = yield* ranged(`bytes */${total}`, new Uint8Array(0))\n if (isOk(probe) || rejectsRanges(probe.status)) return false\n if (probe.status !== 308 && probe.status !== 404) {\n return yield* Effect.fail(unexpectedStatus(\"an upload probe\", probe.status))\n }\n let offset = probe.status === 308 ? resumeOffset(probe) ?? 0 : 0\n while (offset < total) {\n const end = Math.min(offset + chunk, total)\n const answer = yield* ranged(`bytes ${offset}-${end - 1}/${total}`, bytes.subarray(offset, end))\n if (answer.status !== 308) {\n // The completing chunk may answer `2xx`; anything before it may\n // not, because a tier that is still owed bytes and reports success\n // is a tier that is not reading the header. Both are checked\n // against what the tier actually holds below.\n if (end === total && isOk(answer)) break\n if (isOk(answer) || rejectsRanges(answer.status)) return false\n return yield* Effect.fail(unexpectedStatus(\"an upload\", answer.status))\n }\n // A tier that reports a longer prefix than this chunk delivered has\n // more than we just sent, so the next chunk starts there. Never\n // backwards: an answer naming a shorter prefix would loop forever.\n offset = Math.max(end, resumeOffset(answer) ?? 0)\n }\n return yield* holdsWhole(digest, total)\n })\n\n const put: ArtifactStore.Service[\"put\"] = Effect.fn(\"RemoteArtifacts.put\")((bytes: Uint8Array) =>\n Effect.flatMap(ArtifactStore.snapshotBytes(bytes), (snapshot) =>\n Effect.gen(function*() {\n const digest = yield* ArtifactStore.measureBytes(snapshot)\n yield* Effect.annotateCurrentSpan({ digest })\n yield* within(\n \"an upload\",\n uploadDeadline,\n Effect.gen(function*() {\n if (chunkBytes === undefined || snapshot.byteLength <= chunkBytes) {\n yield* uploadWhole(digest, snapshot)\n return\n }\n const transferred = yield* uploadChunked(digest, snapshot, chunkBytes)\n if (!transferred) yield* uploadWhole(digest, snapshot)\n })\n )\n return digest\n }))\n )\n\n const get: ArtifactStore.Service[\"get\"] = Effect.fn(\"RemoteArtifacts.get\")((digest: string) =>\n Effect.gen(function*() {\n const validated = yield* ArtifactStore.validateDigest(digest)\n yield* Effect.annotateCurrentSpan({ digest: validated })\n // The deadline covers the whole exchange — request, headers, body —\n // because a tier that stalls mid-body is exactly as unanswering as\n // one that never accepts the connection.\n const bytes = yield* within(\"a download\", downloadDeadline, download(validated))\n // The shared tier is the least trusted store there is: it is written\n // by machines this one has never met. Verifying the address here means\n // a mis-serving or compromised cache can waste a round trip but can\n // never substitute content.\n const measured = yield* ArtifactStore.measureBytes(bytes)\n if (measured !== validated) {\n return yield* Effect.fail(\n new ArtifactStore.ArtifactCorruption({\n code: \"artifact_corruption\",\n recordedDigest: validated,\n measuredDigest: measured\n })\n )\n }\n return bytes\n })\n )\n\n const has: ArtifactStore.Service[\"has\"] = Effect.fn(\"RemoteArtifacts.has\")((digest: string) =>\n Effect.gen(function*() {\n const validated = yield* ArtifactStore.validateDigest(digest)\n yield* Effect.annotateCurrentSpan({ digest: validated })\n const response = yield* within(\n \"an existence probe\",\n requestDeadline,\n send(\"an existence probe\", authorized(HttpClientRequest.head(casUrl(validated))), Effect.succeed)\n )\n if (response.status === 404) return false\n if (!isOk(response)) return yield* Effect.fail(unexpectedStatus(\"an existence probe\", response.status))\n return true\n })\n )\n\n const findMissing: ArtifactStore.Service[\"findMissing\"] = Effect.fn(\"RemoteArtifacts.findMissing\")(\n (digests: Iterable<string>) =>\n Effect.gen(function*() {\n const requested = [...new Set(digests)]\n if (requested.length === 0) return []\n // The batch travels in a JSON body rather than a path, but an\n // unusable address is still an unusable address, and the local tier\n // refuses the same batch wholesale rather than probing part of it.\n const validated: Array<ArtifactStore.Digest> = []\n for (const digest of requested) validated.push(yield* ArtifactStore.validateDigest(digest))\n yield* Effect.annotateCurrentSpan({ count: validated.length })\n const missing = new Set<string>()\n for (let offset = 0; offset < validated.length; offset += maxFindMissingBatchDigests) {\n const batch = validated.slice(offset, offset + maxFindMissingBatchDigests)\n const requestBody = encoder.encode(JSON.stringify({ digests: batch }))\n /* v8 ignore next 3 -- strict 64-byte digests plus the 1,000-entry batch cap make this\n * at most 67,013 bytes; keep the protocol assertion beside serialization in case either invariant changes. */\n if (requestBody.byteLength > maxFindMissingBodyBytes) {\n return yield* Effect.fail(configurationFailure(\"remote artifact findMissing request exceeds 256 KiB\"))\n }\n const decoded = yield* within(\n \"a batched existence probe\",\n requestDeadline,\n send(\n \"a batched existence probe\",\n authorized(HttpClientRequest.post(`${base}/cas/findMissing`)).pipe(\n HttpClientRequest.bodyUint8Array(requestBody, \"application/json\")\n ),\n (response) =>\n Effect.gen(function*() {\n if (!isOk(response)) {\n return yield* Effect.fail(unexpectedStatus(\"a batched existence probe\", response.status))\n }\n const bodyBytes = yield* readBounded(\n response,\n \"a batched existence probe\",\n maxFindMissingResponseBytes\n )\n const body = yield* Effect.try({\n try: () => JSON.parse(decoder.decode(bodyBytes)) as unknown,\n catch: () => transportFailure(\"a batched existence probe body\")\n })\n return yield* Schema.decodeUnknownEffect(FindMissingResponse)(body).pipe(\n Effect.mapError(() => transportFailure(\"a batched existence probe body\"))\n )\n })\n )\n )\n const asked = new Set<string>(batch)\n for (const digest of decoded.missing) if (asked.has(digest)) missing.add(digest)\n }\n // \"The returned set is guaranteed to be a subset of `digests`\"\n // (`MissingDigestsFinder`). Bazel gets that from the server; we\n // enforce it on this side too, because a server that answered with\n // an unrequested digest would otherwise make the caller upload bytes\n // it never asked about.\n return validated.filter((digest) => missing.has(digest))\n })\n )\n\n return { put, get, has, findMissing, downloadPolicy }\n })\n\n/**\n * Provides a remote artifact store as the `ArtifactStore` tag.\n *\n * Composing this *alone* makes every artifact read and write a network round\n * trip. The intended production shape is\n * {@link CombinedArtifacts.layer | CombinedArtifacts}, with this as its remote\n * tier.\n *\n * @category layers\n * @since 1.0.0-rc.0\n * @slop\n */\nexport const layer = (\n options: Options\n): Layer.Layer<ArtifactStore.ArtifactStore, ArtifactStore.ArtifactStoreError, HttpClient.HttpClient> =>\n Layer.effect(ArtifactStore.ArtifactStore)(make(options))\n"]}
package/docs/README.md CHANGED
@@ -41,7 +41,9 @@ Host completion and release finalizers can extend cancellation latency.
41
41
 
42
42
  ## Install
43
43
 
44
- Not on npm yet; see [Installation](https://github.com/smithersai/smithers/blob/main/packages/smithers/flows/flow/docs/installation.md#use-the-libraries).
44
+ ```bash
45
+ npm install @smthrs/artifacts@next effect
46
+ ```
45
47
 
46
48
  `@effect/platform-node` supplies the Node.js implementations of the services
47
49
  the store asks for. A browser or a test host provides different ones.
@@ -7,7 +7,11 @@ sidebar:
7
7
 
8
8
  ## Install the package
9
9
 
10
- Not on npm yet; see [Installation](https://github.com/smithersai/smithers/blob/main/packages/smithers/flows/flow/docs/installation.md#use-the-libraries).
10
+ ```bash
11
+ npm install @smthrs/artifacts@next effect
12
+ ```
13
+
14
+ The `next` tag carries the 1.0 release candidates.
11
15
 
12
16
  The package requires Node.js 26.4.0 or later and ships as both ESM and
13
17
  CommonJS with TypeScript declarations. It has exactly two runtime dependencies,
@@ -15,7 +15,8 @@ corrupted on purpose.
15
15
  - Node.js 26.4.0 or later.
16
16
  - An empty directory to work in. The store writes under `.flows/objects`
17
17
  relative to the process working directory.
18
- - The package and the Node host layers. Not on npm yet; see [Installation](https://github.com/smithersai/smithers/blob/main/packages/smithers/flows/flow/docs/installation.md#use-the-libraries).
18
+ - The package and the Node host layers:
19
+ `npm install @smthrs/artifacts@next @effect/platform-node effect`.
19
20
 
20
21
  ## Compose the store
21
22
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@smthrs/artifacts",
3
3
  "type": "module",
4
- "version": "1.0.0-rc.7",
4
+ "version": "1.0.0-rc.8",
5
5
  "license": "MIT",
6
6
  "smthrs": {
7
7
  "group": "engine"
@@ -145,7 +145,7 @@
145
145
  "tag": "next"
146
146
  },
147
147
  "dependencies": {
148
- "@smthrs/crypto": "1.0.0-rc.7"
148
+ "@smthrs/crypto": "1.0.0-rc.8"
149
149
  },
150
150
  "devDependencies": {
151
151
  "@effect/language-service": "0.87.2",
@@ -124,9 +124,10 @@ export const withLease = <A, E, R, E2>(
124
124
  acquire.pipe(Effect.andThen(
125
125
  Effect.scoped(Effect.gen(function*() {
126
126
  yield* Effect.forkScoped(
127
- Effect.forever(
128
- Effect.sleep(heartbeatEvery).pipe(
129
- Effect.andThen(gate(
127
+ Effect.gen(function*() {
128
+ while (true) {
129
+ yield* Effect.sleep(heartbeatEvery)
130
+ const reclaimed = yield* gate(
130
131
  fs,
131
132
  directory,
132
133
  fs.readFileString(marker).pipe(
@@ -139,27 +140,36 @@ export const withLease = <A, E, R, E2>(
139
140
  // lease stale and reaped it: deletion is no longer
140
141
  // fenced while the backup still copies. The sibling
141
142
  // `internal/ArtifactLocks.ts` heartbeat reports the same
142
- // loss the same way.
143
+ // loss the same way. A reaped marker is never handed
144
+ // back, so the loss is reported once and the heartbeat
145
+ // ends instead of repeating it every tick.
143
146
  if (Option.getOrUndefined(found) !== owner) {
144
- return Effect.logWarning(
145
- "Artifact backup lease was reclaimed while its backup was still running"
147
+ return Effect.as(
148
+ Effect.logWarning(
149
+ "Artifact backup lease was reclaimed while its backup was still running"
150
+ ),
151
+ true
146
152
  )
147
153
  }
148
154
  return Effect.flatMap(Clock.currentTimeMillis, (now) => {
149
155
  const timestamp = new Date(now)
150
- return fs.utimes(marker, timestamp, timestamp)
156
+ return Effect.as(fs.utimes(marker, timestamp, timestamp), false)
151
157
  })
152
158
  }),
153
159
  Effect.mapError(failure)
154
160
  ),
155
161
  failure
156
- )),
157
- // A failed heartbeat is retried on the next tick, but a
158
- // minute of them lets the marker go stale, so each one is
159
- // reported rather than dropped.
160
- Effect.catch((cause) => Effect.logWarning("Artifact backup lease heartbeat failed", cause))
161
- )
162
- )
162
+ ).pipe(
163
+ // A failed heartbeat is retried on the next tick, but a
164
+ // minute of them lets the marker go stale, so each one is
165
+ // reported rather than dropped.
166
+ Effect.catch((cause) =>
167
+ Effect.as(Effect.logWarning("Artifact backup lease heartbeat failed", cause), false)
168
+ )
169
+ )
170
+ if (reclaimed) return
171
+ }
172
+ })
163
173
  )
164
174
  return yield* effect
165
175
  }))