@smthrs/artifacts 1.0.0-rc.5 → 1.0.0-rc.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,11 +2,11 @@
2
2
 
3
3
  Release candidate scope, host requirements and compatibility review are defined in the [library support policy](https://github.com/smithersai/smithers/blob/main/RELEASE_SUPPORT.md).
4
4
 
5
- This package declares `effect` as an exact
6
- `4.0.0-rc.115` peer dependency. Keep the application on that version so
7
- all Smithers packages share one Effect runtime.
5
+ This package declares `effect` as a `^4.0.4` peer dependency. Install one
6
+ `effect` release in that range so all Smithers packages share one Effect
7
+ runtime.
8
8
 
9
- **Documentation:** https://artifacts.smithers.sh
9
+ **Documentation:** https://github.com/smithersai/smithers/tree/main/packages/smithers/flows/artifacts/docs
10
10
 
11
11
  The content-addressed artifact store: bytes addressed by their own SHA-256
12
12
  digest. You hand it a `Uint8Array`, it hands back a 64 character address, and
@@ -92,7 +92,7 @@ modification time fence, and `ArtifactBackupLease` keeps a sweep from deleting
92
92
  a blob a running backup is still copying.
93
93
 
94
94
  Every export, option, and error code is in the
95
- [API reference](https://artifacts.smithers.sh/reference/api/).
95
+ [API reference](https://github.com/smithersai/smithers/blob/main/packages/smithers/flows/artifacts/docs/api.md).
96
96
 
97
97
  ## What it guarantees
98
98
 
@@ -133,7 +133,7 @@ caller-owned buffer, and every successful `get` returns a new byte array.
133
133
  `ArtifactMissing` is an ordinary miss, and `ArtifactCorruption` is an integrity
134
134
  failure. `digest_failed`, `unavailable`, and `transport_failed` describe host
135
135
  or transport failures whose retryability depends on the host and operation.
136
- [Troubleshooting](https://artifacts.smithers.sh/troubleshooting/) lists each
136
+ [Troubleshooting](https://github.com/smithersai/smithers/blob/main/packages/smithers/flows/artifacts/docs/troubleshooting.md) lists each
137
137
  one with what to change.
138
138
 
139
139
  ## Prior art
@@ -149,14 +149,14 @@ the fsync before the rename are
149
149
  and the wire protocol, CAS blobs under `/cas/base16-key`, is
150
150
  [`HttpCacheClient`](https://github.com/bazelbuild/bazel/blob/master/src/main/java/com/google/devtools/build/lib/remote/http/HttpCacheClient.java).
151
151
  Where this package deviates, and why, is in
152
- [content addressing](https://artifacts.smithers.sh/concepts/content-addressing/).
152
+ [content addressing](https://github.com/smithersai/smithers/blob/main/packages/smithers/flows/artifacts/docs/concepts/content-addressing.md).
153
153
 
154
154
  ## Where it fits
155
155
 
156
156
  This package is one piece of the Smithers durable flow engine, whose whole
157
- surface is re-exported by [`@smthrs/flows`](https://flows.smithers.sh). Inside
157
+ surface is re-exported by [`@smthrs/flows`](https://github.com/smithersai/smithers/tree/main/packages/smithers/flows/docs). Inside
158
158
  that engine the artifact store is the byte half of the result cache:
159
- [`@smthrs/step-cache`](https://step-cache.smithers.sh) records what a step
159
+ [`@smthrs/step-cache`](https://github.com/smithersai/smithers/tree/main/packages/smithers/flows/step-cache/docs) records what a step
160
160
  returned, and any part of that result too large to sit inline is spilled here
161
161
  and referenced by digest.
162
162
 
@@ -167,7 +167,7 @@ Reclaiming published artifacts is an explicit operation, never a side effect of
167
167
  a store call. The `.tmp-*` sweep in `layerFileSystem` reclaims crash orphans
168
168
  only. `ArtifactSweep` is the deletion surface, and the mark phase that decides
169
169
  what is live belongs to
170
- [`@smthrs/engine-store`](https://engine-store.smithers.sh).
170
+ [`@smthrs/engine-store`](https://github.com/smithersai/smithers/tree/main/packages/smithers/flows/engine-store/docs).
171
171
 
172
172
  ## License
173
173
 
@@ -6,7 +6,7 @@
6
6
  * recorded result, while large result bytes live here under their digest. The
7
7
  * two tiers remain separate because artifacts must be published before a cache
8
8
  * record may reference them. See the package README and
9
- * {@link https://smithers.sh/docs/concepts/content-addressing | step-key documentation}.
9
+ * {@link https://github.com/smithersai/smithers/blob/main/packages/smithers/flows/plan/docs/concepts/step-keys.md | step-key documentation}.
10
10
  *
11
11
  * This module defines the contract: the address schema, the service interface
12
12
  * and tag, and one layer per local implementation. The errors, byte helpers,
@@ -60,7 +60,7 @@ export type Digest = typeof Sha256.Digest.Type;
60
60
  * Content-addressed blob storage.
61
61
  *
62
62
  * The contract's ergonomics follow Effect's own `KeyValueStore`
63
- * (`effect/unstable/persistence/KeyValueStore`): a small set of total
63
+ * (`effect/persistence/KeyValueStore`): a small set of total
64
64
  * operations over one address space, with a single typed error family, so a
65
65
  * memory, filesystem, or network implementation is the same shape.
66
66
  * `findMissing` is Bazel's `MissingDigestsFinder` — one batched round trip
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/ArtifactStore.ts"],
4
- "sourcesContent": ["/**\n * The content-addressed artifact store: bytes addressed by their own SHA-256\n * digest.\n *\n * It is deliberately *not* the step cache: the step cache maps a step key to a\n * recorded result, while large result bytes live here under their digest. The\n * two tiers remain separate because artifacts must be published before a cache\n * record may reference them. See the package README and\n * {@link https://smithers.sh/docs/concepts/content-addressing | step-key documentation}.\n *\n * This module defines the contract: the address schema, the service interface\n * and tag, and one layer per local implementation. The errors, byte helpers,\n * and implementations are defined in the modules named for them and re-exported\n * here unchanged, so this subpath and the root `ArtifactStore` namespace keep\n * their whole surface.\n *\n * @since 1.0.0-rc.0\n */\n\nimport { Sha256 } from \"@smthrs/crypto\"\nimport * as Context from \"effect/Context\"\nimport type * as Crypto from \"effect/Crypto\"\nimport * as Effect from \"effect/Effect\"\nimport * as FileSystem from \"effect/FileSystem\"\nimport * as Layer from \"effect/Layer\"\nimport * as Path from \"effect/Path\"\nimport type { ArtifactCorruption, ArtifactMissing, ArtifactStoreError } from \"./ArtifactStoreError.ts\"\nimport { type FileSystemOptions, makeFileSystem } from \"./FileSystemArtifactStore.ts\"\nimport { makeMemory } from \"./MemoryArtifactStore.ts\"\nimport { makeNoop } from \"./NoopArtifactStore.ts\"\n\nexport {\n ArtifactCorruption,\n ArtifactMissing,\n ArtifactStoreError,\n ArtifactStoreErrorCode\n} from \"./ArtifactStoreError.ts\"\nexport { defaultDirectory, type FileSystemOptions, makeFileSystem } from \"./FileSystemArtifactStore.ts\"\nexport { measureBytes } from \"./measureBytes.ts\"\nexport { makeMemory } from \"./MemoryArtifactStore.ts\"\nexport { makeNoop } from \"./NoopArtifactStore.ts\"\nexport { snapshotBytes } from \"./snapshotBytes.ts\"\nexport { validateDigest } from \"./validateDigest.ts\"\n\n/**\n * Schema for a content address: exactly 64 lowercase hexadecimal SHA-256\n * characters, branded by `@smthrs/crypto`. Re-exported so a consumer never has\n * to reach past this package for the address type it stores under.\n *\n * @category schemas\n * @since 1.0.0-rc.0\n * @slop\n */\nexport const Digest = Sha256.Digest\n\n/**\n * A content address produced by this store.\n *\n * Read operations accept a plain `string` rather than this brand on purpose: a\n * digest read back out of a durable row is untrusted input, so the store\n * validates it (see {@link ArtifactStoreError}'s `invalid_digest` code) instead\n * of asking every caller to re-brand a persisted column. `put` returns the\n * brand, because it measured the bytes itself.\n *\n * @category models\n * @since 1.0.0-rc.0\n * @slop\n */\nexport type Digest = typeof Sha256.Digest.Type\n\n/**\n * Content-addressed blob storage.\n *\n * The contract's ergonomics follow Effect's own `KeyValueStore`\n * (`effect/unstable/persistence/KeyValueStore`): a small set of total\n * operations over one address space, with a single typed error family, so a\n * memory, filesystem, or network implementation is the same shape.\n * `findMissing` is Bazel's `MissingDigestsFinder` \u2014 one batched round trip\n * whose result is guaranteed to be a subset of its input \u2014 because a\n * per-digest existence probe over a network tier is the wrong shape entirely.\n *\n * @category models\n * @since 1.0.0-rc.0\n * @slop\n */\nexport interface Service {\n /**\n * Stores `bytes` under their own SHA-256 digest and returns that address.\n * Storing the same bytes twice is idempotent.\n */\n readonly put: (bytes: Uint8Array) => Effect.Effect<Digest, ArtifactStoreError, Crypto.Crypto>\n /**\n * Reads the bytes stored at `digest`, verifying that they still hash to it.\n */\n readonly get: (\n digest: string\n ) => Effect.Effect<Uint8Array, ArtifactMissing | ArtifactCorruption | ArtifactStoreError, Crypto.Crypto>\n /** Whether this tier holds an artifact at `digest`. */\n readonly has: (digest: string) => Effect.Effect<boolean, ArtifactStoreError>\n /**\n * Which of `digests` this tier does not hold. The returned array is\n * guaranteed to be a subset of the input and free of duplicates.\n */\n readonly findMissing: (\n digests: Iterable<string>\n ) => Effect.Effect<Array<string>, ArtifactStoreError>\n}\n\n/**\n * Service tag for the content-addressed artifact store.\n *\n * The identity string equals this module's package path, per the house rule\n * that an identity is the defining module path.\n *\n * @category services\n * @since 1.0.0-rc.0\n * @slop\n */\nexport class ArtifactStore extends Context.Service<ArtifactStore, Service>()(\"@smthrs/artifacts/ArtifactStore\") {}\n\n/**\n * Provides the filesystem-backed artifact store.\n *\n * @category layers\n * @since 1.0.0-rc.0\n * @slop\n */\nexport const layerFileSystem = (\n options: FileSystemOptions = {}\n): Layer.Layer<ArtifactStore, never, FileSystem.FileSystem | Path.Path> =>\n Layer.effect(ArtifactStore)(Effect.gen(function*() {\n return makeFileSystem(yield* FileSystem.FileSystem, yield* Path.Path, options)\n }))\n\n/**\n * Provides an in-memory artifact store.\n *\n * @category layers\n * @since 1.0.0-rc.0\n * @slop\n */\nexport const layerMemory: Layer.Layer<ArtifactStore> = Layer.effect(ArtifactStore)(Effect.sync(makeMemory))\n\n/**\n * Provides a no-op artifact store.\n *\n * @category layers\n * @since 1.0.0-rc.0\n * @slop\n */\nexport const layerNoop = (overrides: Partial<Service> = {}): Layer.Layer<ArtifactStore> =>\n Layer.succeed(ArtifactStore)(makeNoop(overrides))\n"],
4
+ "sourcesContent": ["/**\n * The content-addressed artifact store: bytes addressed by their own SHA-256\n * digest.\n *\n * It is deliberately *not* the step cache: the step cache maps a step key to a\n * recorded result, while large result bytes live here under their digest. The\n * two tiers remain separate because artifacts must be published before a cache\n * record may reference them. See the package README and\n * {@link https://github.com/smithersai/smithers/blob/main/packages/smithers/flows/plan/docs/concepts/step-keys.md | step-key documentation}.\n *\n * This module defines the contract: the address schema, the service interface\n * and tag, and one layer per local implementation. The errors, byte helpers,\n * and implementations are defined in the modules named for them and re-exported\n * here unchanged, so this subpath and the root `ArtifactStore` namespace keep\n * their whole surface.\n *\n * @since 1.0.0-rc.0\n */\n\nimport { Sha256 } from \"@smthrs/crypto\"\nimport * as Context from \"effect/Context\"\nimport type * as Crypto from \"effect/Crypto\"\nimport * as Effect from \"effect/Effect\"\nimport * as FileSystem from \"effect/FileSystem\"\nimport * as Layer from \"effect/Layer\"\nimport * as Path from \"effect/Path\"\nimport type { ArtifactCorruption, ArtifactMissing, ArtifactStoreError } from \"./ArtifactStoreError.ts\"\nimport { type FileSystemOptions, makeFileSystem } from \"./FileSystemArtifactStore.ts\"\nimport { makeMemory } from \"./MemoryArtifactStore.ts\"\nimport { makeNoop } from \"./NoopArtifactStore.ts\"\n\nexport {\n ArtifactCorruption,\n ArtifactMissing,\n ArtifactStoreError,\n ArtifactStoreErrorCode\n} from \"./ArtifactStoreError.ts\"\nexport { defaultDirectory, type FileSystemOptions, makeFileSystem } from \"./FileSystemArtifactStore.ts\"\nexport { measureBytes } from \"./measureBytes.ts\"\nexport { makeMemory } from \"./MemoryArtifactStore.ts\"\nexport { makeNoop } from \"./NoopArtifactStore.ts\"\nexport { snapshotBytes } from \"./snapshotBytes.ts\"\nexport { validateDigest } from \"./validateDigest.ts\"\n\n/**\n * Schema for a content address: exactly 64 lowercase hexadecimal SHA-256\n * characters, branded by `@smthrs/crypto`. Re-exported so a consumer never has\n * to reach past this package for the address type it stores under.\n *\n * @category schemas\n * @since 1.0.0-rc.0\n * @slop\n */\nexport const Digest = Sha256.Digest\n\n/**\n * A content address produced by this store.\n *\n * Read operations accept a plain `string` rather than this brand on purpose: a\n * digest read back out of a durable row is untrusted input, so the store\n * validates it (see {@link ArtifactStoreError}'s `invalid_digest` code) instead\n * of asking every caller to re-brand a persisted column. `put` returns the\n * brand, because it measured the bytes itself.\n *\n * @category models\n * @since 1.0.0-rc.0\n * @slop\n */\nexport type Digest = typeof Sha256.Digest.Type\n\n/**\n * Content-addressed blob storage.\n *\n * The contract's ergonomics follow Effect's own `KeyValueStore`\n * (`effect/persistence/KeyValueStore`): a small set of total\n * operations over one address space, with a single typed error family, so a\n * memory, filesystem, or network implementation is the same shape.\n * `findMissing` is Bazel's `MissingDigestsFinder` \u2014 one batched round trip\n * whose result is guaranteed to be a subset of its input \u2014 because a\n * per-digest existence probe over a network tier is the wrong shape entirely.\n *\n * @category models\n * @since 1.0.0-rc.0\n * @slop\n */\nexport interface Service {\n /**\n * Stores `bytes` under their own SHA-256 digest and returns that address.\n * Storing the same bytes twice is idempotent.\n */\n readonly put: (bytes: Uint8Array) => Effect.Effect<Digest, ArtifactStoreError, Crypto.Crypto>\n /**\n * Reads the bytes stored at `digest`, verifying that they still hash to it.\n */\n readonly get: (\n digest: string\n ) => Effect.Effect<Uint8Array, ArtifactMissing | ArtifactCorruption | ArtifactStoreError, Crypto.Crypto>\n /** Whether this tier holds an artifact at `digest`. */\n readonly has: (digest: string) => Effect.Effect<boolean, ArtifactStoreError>\n /**\n * Which of `digests` this tier does not hold. The returned array is\n * guaranteed to be a subset of the input and free of duplicates.\n */\n readonly findMissing: (\n digests: Iterable<string>\n ) => Effect.Effect<Array<string>, ArtifactStoreError>\n}\n\n/**\n * Service tag for the content-addressed artifact store.\n *\n * The identity string equals this module's package path, per the house rule\n * that an identity is the defining module path.\n *\n * @category services\n * @since 1.0.0-rc.0\n * @slop\n */\nexport class ArtifactStore extends Context.Service<ArtifactStore, Service>()(\"@smthrs/artifacts/ArtifactStore\") {}\n\n/**\n * Provides the filesystem-backed artifact store.\n *\n * @category layers\n * @since 1.0.0-rc.0\n * @slop\n */\nexport const layerFileSystem = (\n options: FileSystemOptions = {}\n): Layer.Layer<ArtifactStore, never, FileSystem.FileSystem | Path.Path> =>\n Layer.effect(ArtifactStore)(Effect.gen(function*() {\n return makeFileSystem(yield* FileSystem.FileSystem, yield* Path.Path, options)\n }))\n\n/**\n * Provides an in-memory artifact store.\n *\n * @category layers\n * @since 1.0.0-rc.0\n * @slop\n */\nexport const layerMemory: Layer.Layer<ArtifactStore> = Layer.effect(ArtifactStore)(Effect.sync(makeMemory))\n\n/**\n * Provides a no-op artifact store.\n *\n * @category layers\n * @since 1.0.0-rc.0\n * @slop\n */\nexport const layerNoop = (overrides: Partial<Service> = {}): Layer.Layer<ArtifactStore> =>\n Layer.succeed(ArtifactStore)(makeNoop(overrides))\n"],
5
5
  "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAmBA,oBAAuB;AACvB,cAAyB;AAEzB,aAAwB;AACxB,iBAA4B;AAC5B,YAAuB;AACvB,WAAsB;AAEtB,qCAAuD;AACvD,iCAA2B;AAC3B,+BAAyB;AAEzB,gCAKO;AACP,IAAAA,kCAAyE;AACzE,0BAA6B;AAC7B,IAAAC,8BAA2B;AAC3B,IAAAC,4BAAyB;AACzB,2BAA8B;AAC9B,4BAA+B;AAWxB,MAAM,SAAS,qBAAO;AAiEtB,MAAM,sBAAsB,QAAQ,QAAgC,EAAE,iCAAiC,EAAE;AAAC;AAS1G,MAAM,kBAAkB,CAC7B,UAA6B,CAAC,MAE9B,MAAM,OAAO,aAAa,EAAE,OAAO,IAAI,aAAY;AACjD,aAAO,+CAAe,OAAO,WAAW,YAAY,OAAO,KAAK,MAAM,OAAO;AAC/E,CAAC,CAAC;AASG,MAAM,cAA0C,MAAM,OAAO,aAAa,EAAE,OAAO,KAAK,qCAAU,CAAC;AASnG,MAAM,YAAY,CAAC,YAA8B,CAAC,MACvD,MAAM,QAAQ,aAAa,MAAE,mCAAS,SAAS,CAAC;",
6
6
  "names": ["import_FileSystemArtifactStore", "import_MemoryArtifactStore", "import_NoopArtifactStore"]
7
7
  }
@@ -54,9 +54,9 @@
54
54
  */
55
55
  import * as Duration from "effect/Duration";
56
56
  import * as Effect from "effect/Effect";
57
+ import * as HttpClient from "effect/http/HttpClient";
57
58
  import * as Layer from "effect/Layer";
58
59
  import * as Schema from "effect/Schema";
59
- import * as HttpClient from "effect/unstable/http/HttpClient";
60
60
  import * as ArtifactStore from "./ArtifactStore.ts";
61
61
  /**
62
62
  * How eagerly a composition materializes shared blobs into its local store.
@@ -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;AACvC,OAAO,KAAK,KAAK,MAAM,cAAc,CAAA;AAErC,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AAIvC,OAAO,KAAK,UAAU,MAAM,iCAAiC,CAAA;AAG7D,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,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"}
@@ -37,14 +37,14 @@ __export(RemoteArtifacts_exports, {
37
37
  module.exports = __toCommonJS(RemoteArtifacts_exports);
38
38
  var Duration = __toESM(require("effect/Duration"), 1);
39
39
  var Effect = __toESM(require("effect/Effect"), 1);
40
+ var FetchHttpClient = __toESM(require("effect/http/FetchHttpClient"), 1);
41
+ var Headers = __toESM(require("effect/http/Headers"), 1);
42
+ var HttpClient = __toESM(require("effect/http/HttpClient"), 1);
43
+ var HttpClientRequest = __toESM(require("effect/http/HttpClientRequest"), 1);
40
44
  var Layer = __toESM(require("effect/Layer"), 1);
41
45
  var Option = __toESM(require("effect/Option"), 1);
42
46
  var Schema = __toESM(require("effect/Schema"), 1);
43
47
  var Stream = __toESM(require("effect/Stream"), 1);
44
- var FetchHttpClient = __toESM(require("effect/unstable/http/FetchHttpClient"), 1);
45
- var Headers = __toESM(require("effect/unstable/http/Headers"), 1);
46
- var HttpClient = __toESM(require("effect/unstable/http/HttpClient"), 1);
47
- var HttpClientRequest = __toESM(require("effect/unstable/http/HttpClientRequest"), 1);
48
48
  var ArtifactStore = __toESM(require("./ArtifactStore.js"), 1);
49
49
  const DownloadPolicy = Schema.Literals(["all", "toplevel", "minimal"]);
50
50
  const downloadPolicies = Object.freeze(["all", "toplevel", "minimal"]);
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/RemoteArtifacts.ts"],
4
- "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 \u2014 it answers \"everything is missing\" and\n * re-uploads \u2014 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 Layer from \"effect/Layer\"\nimport * as Option from \"effect/Option\"\nimport * as Schema from \"effect/Schema\"\nimport * as Stream from \"effect/Stream\"\nimport * as FetchHttpClient from \"effect/unstable/http/FetchHttpClient\"\nimport * as Headers from \"effect/unstable/http/Headers\"\nimport * as HttpClient from \"effect/unstable/http/HttpClient\"\nimport * as HttpClientRequest from \"effect/unstable/http/HttpClientRequest\"\nimport type * as HttpClientResponse from \"effect/unstable/http/HttpClientResponse\"\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 \u2014 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 \u2014 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 \u2014 and refused\n * outright when `Content-Length` already declares the excess \u2014 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 \u2014\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 \u2014 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 \u2014 request, headers, body \u2014\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"],
5
- "mappings": "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",
4
+ "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 \u2014 it answers \"everything is missing\" and\n * re-uploads \u2014 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 \u2014 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 \u2014 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 \u2014 and refused\n * outright when `Content-Length` already declares the excess \u2014 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 \u2014\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 \u2014 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 \u2014 request, headers, body \u2014\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"],
5
+ "mappings": "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",
6
6
  "names": ["headers"]
7
7
  }
@@ -6,7 +6,7 @@
6
6
  * recorded result, while large result bytes live here under their digest. The
7
7
  * two tiers remain separate because artifacts must be published before a cache
8
8
  * record may reference them. See the package README and
9
- * {@link https://smithers.sh/docs/concepts/content-addressing | step-key documentation}.
9
+ * {@link https://github.com/smithersai/smithers/blob/main/packages/smithers/flows/plan/docs/concepts/step-keys.md | step-key documentation}.
10
10
  *
11
11
  * This module defines the contract: the address schema, the service interface
12
12
  * and tag, and one layer per local implementation. The errors, byte helpers,
@@ -60,7 +60,7 @@ export type Digest = typeof Sha256.Digest.Type;
60
60
  * Content-addressed blob storage.
61
61
  *
62
62
  * The contract's ergonomics follow Effect's own `KeyValueStore`
63
- * (`effect/unstable/persistence/KeyValueStore`): a small set of total
63
+ * (`effect/persistence/KeyValueStore`): a small set of total
64
64
  * operations over one address space, with a single typed error family, so a
65
65
  * memory, filesystem, or network implementation is the same shape.
66
66
  * `findMissing` is Bazel's `MissingDigestsFinder` — one batched round trip
@@ -6,7 +6,7 @@
6
6
  * recorded result, while large result bytes live here under their digest. The
7
7
  * two tiers remain separate because artifacts must be published before a cache
8
8
  * record may reference them. See the package README and
9
- * {@link https://smithers.sh/docs/concepts/content-addressing | step-key documentation}.
9
+ * {@link https://github.com/smithersai/smithers/blob/main/packages/smithers/flows/plan/docs/concepts/step-keys.md | step-key documentation}.
10
10
  *
11
11
  * This module defines the contract: the address schema, the service interface
12
12
  * and tag, and one layer per local implementation. The errors, byte helpers,
@@ -1 +1 @@
1
- {"version":3,"file":"ArtifactStore.js","sourceRoot":"","sources":["../../src/ArtifactStore.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAA;AACvC,OAAO,KAAK,OAAO,MAAM,gBAAgB,CAAA;AAEzC,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AACvC,OAAO,KAAK,UAAU,MAAM,mBAAmB,CAAA;AAC/C,OAAO,KAAK,KAAK,MAAM,cAAc,CAAA;AACrC,OAAO,KAAK,IAAI,MAAM,aAAa,CAAA;AAEnC,OAAO,EAA0B,cAAc,EAAE,MAAM,8BAA8B,CAAA;AACrF,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAA;AACrD,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAA;AAEjD,OAAO,EACL,kBAAkB,EAClB,eAAe,EACf,kBAAkB,EAClB,sBAAsB,EACvB,MAAM,yBAAyB,CAAA;AAChC,OAAO,EAAE,gBAAgB,EAA0B,cAAc,EAAE,MAAM,8BAA8B,CAAA;AACvG,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAChD,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAA;AACrD,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAA;AACjD,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA;AAEpD;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAA;AAuDnC;;;;;;;;;GASG;AACH,MAAM,OAAO,aAAc,SAAQ,OAAO,CAAC,OAAO,EAA0B,CAAC,iCAAiC,CAAC;CAAG;AAElH;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,CAC7B,UAA6B,EAAE,EACuC,EAAE,CACxE,KAAK,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;IAC9C,OAAO,cAAc,CAAC,KAAK,CAAC,CAAC,UAAU,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC,CAAA;AAChF,CAAC,CAAC,CAAC,CAAA;AAEL;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,WAAW,GAA+B,KAAK,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAA;AAE3G;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,YAA8B,EAAE,EAA8B,EAAE,CACxF,KAAK,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAA","sourcesContent":["/**\n * The content-addressed artifact store: bytes addressed by their own SHA-256\n * digest.\n *\n * It is deliberately *not* the step cache: the step cache maps a step key to a\n * recorded result, while large result bytes live here under their digest. The\n * two tiers remain separate because artifacts must be published before a cache\n * record may reference them. See the package README and\n * {@link https://smithers.sh/docs/concepts/content-addressing | step-key documentation}.\n *\n * This module defines the contract: the address schema, the service interface\n * and tag, and one layer per local implementation. The errors, byte helpers,\n * and implementations are defined in the modules named for them and re-exported\n * here unchanged, so this subpath and the root `ArtifactStore` namespace keep\n * their whole surface.\n *\n * @since 1.0.0-rc.0\n */\n\nimport { Sha256 } from \"@smthrs/crypto\"\nimport * as Context from \"effect/Context\"\nimport type * as Crypto from \"effect/Crypto\"\nimport * as Effect from \"effect/Effect\"\nimport * as FileSystem from \"effect/FileSystem\"\nimport * as Layer from \"effect/Layer\"\nimport * as Path from \"effect/Path\"\nimport type { ArtifactCorruption, ArtifactMissing, ArtifactStoreError } from \"./ArtifactStoreError.ts\"\nimport { type FileSystemOptions, makeFileSystem } from \"./FileSystemArtifactStore.ts\"\nimport { makeMemory } from \"./MemoryArtifactStore.ts\"\nimport { makeNoop } from \"./NoopArtifactStore.ts\"\n\nexport {\n ArtifactCorruption,\n ArtifactMissing,\n ArtifactStoreError,\n ArtifactStoreErrorCode\n} from \"./ArtifactStoreError.ts\"\nexport { defaultDirectory, type FileSystemOptions, makeFileSystem } from \"./FileSystemArtifactStore.ts\"\nexport { measureBytes } from \"./measureBytes.ts\"\nexport { makeMemory } from \"./MemoryArtifactStore.ts\"\nexport { makeNoop } from \"./NoopArtifactStore.ts\"\nexport { snapshotBytes } from \"./snapshotBytes.ts\"\nexport { validateDigest } from \"./validateDigest.ts\"\n\n/**\n * Schema for a content address: exactly 64 lowercase hexadecimal SHA-256\n * characters, branded by `@smthrs/crypto`. Re-exported so a consumer never has\n * to reach past this package for the address type it stores under.\n *\n * @category schemas\n * @since 1.0.0-rc.0\n * @slop\n */\nexport const Digest = Sha256.Digest\n\n/**\n * A content address produced by this store.\n *\n * Read operations accept a plain `string` rather than this brand on purpose: a\n * digest read back out of a durable row is untrusted input, so the store\n * validates it (see {@link ArtifactStoreError}'s `invalid_digest` code) instead\n * of asking every caller to re-brand a persisted column. `put` returns the\n * brand, because it measured the bytes itself.\n *\n * @category models\n * @since 1.0.0-rc.0\n * @slop\n */\nexport type Digest = typeof Sha256.Digest.Type\n\n/**\n * Content-addressed blob storage.\n *\n * The contract's ergonomics follow Effect's own `KeyValueStore`\n * (`effect/unstable/persistence/KeyValueStore`): a small set of total\n * operations over one address space, with a single typed error family, so a\n * memory, filesystem, or network implementation is the same shape.\n * `findMissing` is Bazel's `MissingDigestsFinder` — one batched round trip\n * whose result is guaranteed to be a subset of its input — because a\n * per-digest existence probe over a network tier is the wrong shape entirely.\n *\n * @category models\n * @since 1.0.0-rc.0\n * @slop\n */\nexport interface Service {\n /**\n * Stores `bytes` under their own SHA-256 digest and returns that address.\n * Storing the same bytes twice is idempotent.\n */\n readonly put: (bytes: Uint8Array) => Effect.Effect<Digest, ArtifactStoreError, Crypto.Crypto>\n /**\n * Reads the bytes stored at `digest`, verifying that they still hash to it.\n */\n readonly get: (\n digest: string\n ) => Effect.Effect<Uint8Array, ArtifactMissing | ArtifactCorruption | ArtifactStoreError, Crypto.Crypto>\n /** Whether this tier holds an artifact at `digest`. */\n readonly has: (digest: string) => Effect.Effect<boolean, ArtifactStoreError>\n /**\n * Which of `digests` this tier does not hold. The returned array is\n * guaranteed to be a subset of the input and free of duplicates.\n */\n readonly findMissing: (\n digests: Iterable<string>\n ) => Effect.Effect<Array<string>, ArtifactStoreError>\n}\n\n/**\n * Service tag for the content-addressed artifact store.\n *\n * The identity string equals this module's package path, per the house rule\n * that an identity is the defining module path.\n *\n * @category services\n * @since 1.0.0-rc.0\n * @slop\n */\nexport class ArtifactStore extends Context.Service<ArtifactStore, Service>()(\"@smthrs/artifacts/ArtifactStore\") {}\n\n/**\n * Provides the filesystem-backed artifact store.\n *\n * @category layers\n * @since 1.0.0-rc.0\n * @slop\n */\nexport const layerFileSystem = (\n options: FileSystemOptions = {}\n): Layer.Layer<ArtifactStore, never, FileSystem.FileSystem | Path.Path> =>\n Layer.effect(ArtifactStore)(Effect.gen(function*() {\n return makeFileSystem(yield* FileSystem.FileSystem, yield* Path.Path, options)\n }))\n\n/**\n * Provides an in-memory artifact store.\n *\n * @category layers\n * @since 1.0.0-rc.0\n * @slop\n */\nexport const layerMemory: Layer.Layer<ArtifactStore> = Layer.effect(ArtifactStore)(Effect.sync(makeMemory))\n\n/**\n * Provides a no-op artifact store.\n *\n * @category layers\n * @since 1.0.0-rc.0\n * @slop\n */\nexport const layerNoop = (overrides: Partial<Service> = {}): Layer.Layer<ArtifactStore> =>\n Layer.succeed(ArtifactStore)(makeNoop(overrides))\n"]}
1
+ {"version":3,"file":"ArtifactStore.js","sourceRoot":"","sources":["../../src/ArtifactStore.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAA;AACvC,OAAO,KAAK,OAAO,MAAM,gBAAgB,CAAA;AAEzC,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AACvC,OAAO,KAAK,UAAU,MAAM,mBAAmB,CAAA;AAC/C,OAAO,KAAK,KAAK,MAAM,cAAc,CAAA;AACrC,OAAO,KAAK,IAAI,MAAM,aAAa,CAAA;AAEnC,OAAO,EAA0B,cAAc,EAAE,MAAM,8BAA8B,CAAA;AACrF,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAA;AACrD,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAA;AAEjD,OAAO,EACL,kBAAkB,EAClB,eAAe,EACf,kBAAkB,EAClB,sBAAsB,EACvB,MAAM,yBAAyB,CAAA;AAChC,OAAO,EAAE,gBAAgB,EAA0B,cAAc,EAAE,MAAM,8BAA8B,CAAA;AACvG,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAChD,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAA;AACrD,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAA;AACjD,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA;AAEpD;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAA;AAuDnC;;;;;;;;;GASG;AACH,MAAM,OAAO,aAAc,SAAQ,OAAO,CAAC,OAAO,EAA0B,CAAC,iCAAiC,CAAC;CAAG;AAElH;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,CAC7B,UAA6B,EAAE,EACuC,EAAE,CACxE,KAAK,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;IAC9C,OAAO,cAAc,CAAC,KAAK,CAAC,CAAC,UAAU,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC,CAAA;AAChF,CAAC,CAAC,CAAC,CAAA;AAEL;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,WAAW,GAA+B,KAAK,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAA;AAE3G;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,YAA8B,EAAE,EAA8B,EAAE,CACxF,KAAK,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAA","sourcesContent":["/**\n * The content-addressed artifact store: bytes addressed by their own SHA-256\n * digest.\n *\n * It is deliberately *not* the step cache: the step cache maps a step key to a\n * recorded result, while large result bytes live here under their digest. The\n * two tiers remain separate because artifacts must be published before a cache\n * record may reference them. See the package README and\n * {@link https://github.com/smithersai/smithers/blob/main/packages/smithers/flows/plan/docs/concepts/step-keys.md | step-key documentation}.\n *\n * This module defines the contract: the address schema, the service interface\n * and tag, and one layer per local implementation. The errors, byte helpers,\n * and implementations are defined in the modules named for them and re-exported\n * here unchanged, so this subpath and the root `ArtifactStore` namespace keep\n * their whole surface.\n *\n * @since 1.0.0-rc.0\n */\n\nimport { Sha256 } from \"@smthrs/crypto\"\nimport * as Context from \"effect/Context\"\nimport type * as Crypto from \"effect/Crypto\"\nimport * as Effect from \"effect/Effect\"\nimport * as FileSystem from \"effect/FileSystem\"\nimport * as Layer from \"effect/Layer\"\nimport * as Path from \"effect/Path\"\nimport type { ArtifactCorruption, ArtifactMissing, ArtifactStoreError } from \"./ArtifactStoreError.ts\"\nimport { type FileSystemOptions, makeFileSystem } from \"./FileSystemArtifactStore.ts\"\nimport { makeMemory } from \"./MemoryArtifactStore.ts\"\nimport { makeNoop } from \"./NoopArtifactStore.ts\"\n\nexport {\n ArtifactCorruption,\n ArtifactMissing,\n ArtifactStoreError,\n ArtifactStoreErrorCode\n} from \"./ArtifactStoreError.ts\"\nexport { defaultDirectory, type FileSystemOptions, makeFileSystem } from \"./FileSystemArtifactStore.ts\"\nexport { measureBytes } from \"./measureBytes.ts\"\nexport { makeMemory } from \"./MemoryArtifactStore.ts\"\nexport { makeNoop } from \"./NoopArtifactStore.ts\"\nexport { snapshotBytes } from \"./snapshotBytes.ts\"\nexport { validateDigest } from \"./validateDigest.ts\"\n\n/**\n * Schema for a content address: exactly 64 lowercase hexadecimal SHA-256\n * characters, branded by `@smthrs/crypto`. Re-exported so a consumer never has\n * to reach past this package for the address type it stores under.\n *\n * @category schemas\n * @since 1.0.0-rc.0\n * @slop\n */\nexport const Digest = Sha256.Digest\n\n/**\n * A content address produced by this store.\n *\n * Read operations accept a plain `string` rather than this brand on purpose: a\n * digest read back out of a durable row is untrusted input, so the store\n * validates it (see {@link ArtifactStoreError}'s `invalid_digest` code) instead\n * of asking every caller to re-brand a persisted column. `put` returns the\n * brand, because it measured the bytes itself.\n *\n * @category models\n * @since 1.0.0-rc.0\n * @slop\n */\nexport type Digest = typeof Sha256.Digest.Type\n\n/**\n * Content-addressed blob storage.\n *\n * The contract's ergonomics follow Effect's own `KeyValueStore`\n * (`effect/persistence/KeyValueStore`): a small set of total\n * operations over one address space, with a single typed error family, so a\n * memory, filesystem, or network implementation is the same shape.\n * `findMissing` is Bazel's `MissingDigestsFinder` — one batched round trip\n * whose result is guaranteed to be a subset of its input — because a\n * per-digest existence probe over a network tier is the wrong shape entirely.\n *\n * @category models\n * @since 1.0.0-rc.0\n * @slop\n */\nexport interface Service {\n /**\n * Stores `bytes` under their own SHA-256 digest and returns that address.\n * Storing the same bytes twice is idempotent.\n */\n readonly put: (bytes: Uint8Array) => Effect.Effect<Digest, ArtifactStoreError, Crypto.Crypto>\n /**\n * Reads the bytes stored at `digest`, verifying that they still hash to it.\n */\n readonly get: (\n digest: string\n ) => Effect.Effect<Uint8Array, ArtifactMissing | ArtifactCorruption | ArtifactStoreError, Crypto.Crypto>\n /** Whether this tier holds an artifact at `digest`. */\n readonly has: (digest: string) => Effect.Effect<boolean, ArtifactStoreError>\n /**\n * Which of `digests` this tier does not hold. The returned array is\n * guaranteed to be a subset of the input and free of duplicates.\n */\n readonly findMissing: (\n digests: Iterable<string>\n ) => Effect.Effect<Array<string>, ArtifactStoreError>\n}\n\n/**\n * Service tag for the content-addressed artifact store.\n *\n * The identity string equals this module's package path, per the house rule\n * that an identity is the defining module path.\n *\n * @category services\n * @since 1.0.0-rc.0\n * @slop\n */\nexport class ArtifactStore extends Context.Service<ArtifactStore, Service>()(\"@smthrs/artifacts/ArtifactStore\") {}\n\n/**\n * Provides the filesystem-backed artifact store.\n *\n * @category layers\n * @since 1.0.0-rc.0\n * @slop\n */\nexport const layerFileSystem = (\n options: FileSystemOptions = {}\n): Layer.Layer<ArtifactStore, never, FileSystem.FileSystem | Path.Path> =>\n Layer.effect(ArtifactStore)(Effect.gen(function*() {\n return makeFileSystem(yield* FileSystem.FileSystem, yield* Path.Path, options)\n }))\n\n/**\n * Provides an in-memory artifact store.\n *\n * @category layers\n * @since 1.0.0-rc.0\n * @slop\n */\nexport const layerMemory: Layer.Layer<ArtifactStore> = Layer.effect(ArtifactStore)(Effect.sync(makeMemory))\n\n/**\n * Provides a no-op artifact store.\n *\n * @category layers\n * @since 1.0.0-rc.0\n * @slop\n */\nexport const layerNoop = (overrides: Partial<Service> = {}): Layer.Layer<ArtifactStore> =>\n Layer.succeed(ArtifactStore)(makeNoop(overrides))\n"]}
@@ -54,9 +54,9 @@
54
54
  */
55
55
  import * as Duration from "effect/Duration";
56
56
  import * as Effect from "effect/Effect";
57
+ import * as HttpClient from "effect/http/HttpClient";
57
58
  import * as Layer from "effect/Layer";
58
59
  import * as Schema from "effect/Schema";
59
- import * as HttpClient from "effect/unstable/http/HttpClient";
60
60
  import * as ArtifactStore from "./ArtifactStore.ts";
61
61
  /**
62
62
  * How eagerly a composition materializes shared blobs into its local store.
@@ -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;AACvC,OAAO,KAAK,KAAK,MAAM,cAAc,CAAA;AAErC,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AAIvC,OAAO,KAAK,UAAU,MAAM,iCAAiC,CAAA;AAG7D,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,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"}
@@ -54,14 +54,14 @@
54
54
  */
55
55
  import * as Duration from "effect/Duration";
56
56
  import * as Effect from "effect/Effect";
57
+ import * as FetchHttpClient from "effect/http/FetchHttpClient";
58
+ import * as Headers from "effect/http/Headers";
59
+ import * as HttpClient from "effect/http/HttpClient";
60
+ import * as HttpClientRequest from "effect/http/HttpClientRequest";
57
61
  import * as Layer from "effect/Layer";
58
62
  import * as Option from "effect/Option";
59
63
  import * as Schema from "effect/Schema";
60
64
  import * as Stream from "effect/Stream";
61
- import * as FetchHttpClient from "effect/unstable/http/FetchHttpClient";
62
- import * as Headers from "effect/unstable/http/Headers";
63
- import * as HttpClient from "effect/unstable/http/HttpClient";
64
- import * as HttpClientRequest from "effect/unstable/http/HttpClientRequest";
65
65
  import * as ArtifactStore from "./ArtifactStore.js";
66
66
  /**
67
67
  * How eagerly a composition materializes shared blobs into its local store.
@@ -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 Layer from \"effect/Layer\"\nimport * as Option from \"effect/Option\"\nimport * as Schema from \"effect/Schema\"\nimport * as Stream from \"effect/Stream\"\nimport * as FetchHttpClient from \"effect/unstable/http/FetchHttpClient\"\nimport * as Headers from \"effect/unstable/http/Headers\"\nimport * as HttpClient from \"effect/unstable/http/HttpClient\"\nimport * as HttpClientRequest from \"effect/unstable/http/HttpClientRequest\"\nimport type * as HttpClientResponse from \"effect/unstable/http/HttpClientResponse\"\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 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"]}
package/docs/api.md CHANGED
@@ -195,6 +195,13 @@ resumable-upload `308` fails as an unexpected status, so credential headers
195
195
  never reach another origin. The complete server-side contract is in
196
196
  [Serve the artifact protocol](./guides/serve-the-artifact-protocol.md).
197
197
 
198
+ Effect's HTTP client opens one span per request, named for the request method.
199
+ It records no request or response header, so a trace carries no credential. A
200
+ host that sets `HttpClient.TracerHeaderFilter` to record headers gets every
201
+ name configured in `headers` as `<redacted>`. A `4xx` or `5xx` answer ends the
202
+ span as failed with the status in `error.type`, so a `HEAD` for an absent
203
+ artifact shows a failed span although `has` returned `false`.
204
+
198
205
  ### Options
199
206
 
200
207
  | Field | Default | Meaning |
@@ -148,7 +148,7 @@ cache misses that re-publish. There is no compatibility shim.
148
148
  ## Prior art
149
149
 
150
150
  The contract's ergonomics follow Effect's own `KeyValueStore`
151
- (`effect/unstable/persistence/KeyValueStore`): one small set of total
151
+ (`effect/persistence/KeyValueStore`): one small set of total
152
152
  operations over one address space, so a memory, filesystem, or network
153
153
  implementation is the same shape. `findMissing` is Bazel's
154
154
  `MissingDigestsFinder`, one batched round trip whose result is guaranteed to be
@@ -114,8 +114,8 @@ that authority onto its port, so the request, body, status code, and
114
114
  connection are all real and only TLS termination is missing:
115
115
 
116
116
  ```ts
117
+ import * as FetchHttpClient from "effect/http/FetchHttpClient"
117
118
  import * as Layer from "effect/Layer"
118
- import * as FetchHttpClient from "effect/unstable/http/FetchHttpClient"
119
119
 
120
120
  /** The real fetch transport, with a declared HTTPS authority mapped to `port`. */
121
121
  const transport = (port: number) =>
@@ -20,12 +20,12 @@ Nothing in this package opens a file or a socket by itself. Every host effect
20
20
  goes through an Effect service, which is what lets the same code run in Node,
21
21
  in Bun, in a browser tab, and inside a sandbox:
22
22
 
23
- | Service | Needed by | Node layer |
24
- | -------------------------------------------------------------- | ------------------------------------------------- | ----------------------- |
25
- | `Crypto.Crypto` from `effect/Crypto` | `put` and `get` on every store | `NodeCrypto.layer` |
26
- | `FileSystem.FileSystem` from `effect/FileSystem` | the filesystem store, the sweep, the backup lease | `NodeFileSystem.layer` |
27
- | `Path.Path` from `effect/Path` | the filesystem store | `NodePath.layer` |
28
- | `HttpClient.HttpClient` from `effect/unstable/http/HttpClient` | `RemoteArtifacts` | `FetchHttpClient.layer` |
23
+ | Service | Needed by | Node layer |
24
+ | ----------------------------------------------------- | ------------------------------------------------- | ----------------------- |
25
+ | `Crypto.Crypto` from `effect/Crypto` | `put` and `get` on every store | `NodeCrypto.layer` |
26
+ | `FileSystem.FileSystem` from `effect/FileSystem` | the filesystem store, the sweep, the backup lease | `NodeFileSystem.layer` |
27
+ | `Path.Path` from `effect/Path` | the filesystem store | `NodePath.layer` |
28
+ | `HttpClient.HttpClient` from `effect/http/HttpClient` | `RemoteArtifacts` | `FetchHttpClient.layer` |
29
29
 
30
30
  `has` and `findMissing` never hash anything, so they do not require `Crypto`.
31
31
  The memory and no-op stores require no `FileSystem` or `Path` at all, which is what makes
@@ -34,15 +34,15 @@ them usable in a browser and in a test with no temporary directory.
34
34
  The Node implementations live in `@effect/platform-node`:
35
35
 
36
36
  ```bash
37
- pnpm add @effect/platform-node@4.0.0-rc.115 @effect/platform-node-shared@4.0.0-rc.115
37
+ pnpm add @effect/platform-node @effect/platform-node-shared
38
38
  ```
39
39
 
40
40
  ```ts
41
41
  import * as NodeCrypto from "@effect/platform-node/NodeCrypto"
42
42
  import * as NodeFileSystem from "@effect/platform-node/NodeFileSystem"
43
43
  import * as NodePath from "@effect/platform-node/NodePath"
44
+ import * as FetchHttpClient from "effect/http/FetchHttpClient"
44
45
  import * as Layer from "effect/Layer"
45
- import * as FetchHttpClient from "effect/unstable/http/FetchHttpClient"
46
46
 
47
47
  /** Everything every tier in this package can ask for. */
48
48
  export const host = Layer.mergeAll(NodeCrypto.layer, NodeFileSystem.layer, NodePath.layer, FetchHttpClient.layer)
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.5",
4
+ "version": "1.0.0-rc.7",
5
5
  "license": "MIT",
6
6
  "smthrs": {
7
7
  "group": "engine"
@@ -145,17 +145,17 @@
145
145
  "tag": "next"
146
146
  },
147
147
  "dependencies": {
148
- "@smthrs/crypto": "1.0.0-rc.5"
148
+ "@smthrs/crypto": "1.0.0-rc.7"
149
149
  },
150
150
  "devDependencies": {
151
151
  "@effect/language-service": "0.87.2",
152
- "@effect/platform-node": "4.0.0-rc.115",
153
- "@effect/vitest": "4.0.0-rc.115",
152
+ "@effect/platform-node": "4.0.4",
153
+ "@effect/vitest": "4.0.4",
154
154
  "@eslint/js": "9.39.5",
155
155
  "@types/node": "26.4.1",
156
156
  "@vitest/coverage-v8": "5.0.0",
157
157
  "dprint": "0.57.1",
158
- "effect": "4.0.0-rc.115",
158
+ "effect": "4.0.4",
159
159
  "esbuild": "0.28.2",
160
160
  "eslint": "9.39.5",
161
161
  "eslint-import-resolver-typescript": "4.4.5",
@@ -168,7 +168,7 @@
168
168
  "vitest": "5.0.0"
169
169
  },
170
170
  "peerDependencies": {
171
- "effect": "4.0.0-rc.115"
171
+ "effect": "^4.0.4"
172
172
  },
173
173
  "scripts": {
174
174
  "lint": "eslint src --max-warnings=0 && dprint check",
@@ -6,7 +6,7 @@
6
6
  * recorded result, while large result bytes live here under their digest. The
7
7
  * two tiers remain separate because artifacts must be published before a cache
8
8
  * record may reference them. See the package README and
9
- * {@link https://smithers.sh/docs/concepts/content-addressing | step-key documentation}.
9
+ * {@link https://github.com/smithersai/smithers/blob/main/packages/smithers/flows/plan/docs/concepts/step-keys.md | step-key documentation}.
10
10
  *
11
11
  * This module defines the contract: the address schema, the service interface
12
12
  * and tag, and one layer per local implementation. The errors, byte helpers,
@@ -72,7 +72,7 @@ export type Digest = typeof Sha256.Digest.Type
72
72
  * Content-addressed blob storage.
73
73
  *
74
74
  * The contract's ergonomics follow Effect's own `KeyValueStore`
75
- * (`effect/unstable/persistence/KeyValueStore`): a small set of total
75
+ * (`effect/persistence/KeyValueStore`): a small set of total
76
76
  * operations over one address space, with a single typed error family, so a
77
77
  * memory, filesystem, or network implementation is the same shape.
78
78
  * `findMissing` is Bazel's `MissingDigestsFinder` — one batched round trip
@@ -55,15 +55,15 @@
55
55
 
56
56
  import * as Duration from "effect/Duration"
57
57
  import * as Effect from "effect/Effect"
58
+ import * as FetchHttpClient from "effect/http/FetchHttpClient"
59
+ import * as Headers from "effect/http/Headers"
60
+ import * as HttpClient from "effect/http/HttpClient"
61
+ import * as HttpClientRequest from "effect/http/HttpClientRequest"
62
+ import type * as HttpClientResponse from "effect/http/HttpClientResponse"
58
63
  import * as Layer from "effect/Layer"
59
64
  import * as Option from "effect/Option"
60
65
  import * as Schema from "effect/Schema"
61
66
  import * as Stream from "effect/Stream"
62
- import * as FetchHttpClient from "effect/unstable/http/FetchHttpClient"
63
- import * as Headers from "effect/unstable/http/Headers"
64
- import * as HttpClient from "effect/unstable/http/HttpClient"
65
- import * as HttpClientRequest from "effect/unstable/http/HttpClientRequest"
66
- import type * as HttpClientResponse from "effect/unstable/http/HttpClientResponse"
67
67
  import * as ArtifactStore from "./ArtifactStore.ts"
68
68
 
69
69
  /**