/** * artifacts/gcsArtifacts — the claim-check store in a Cloud Storage bucket. * * The same five verbs, the same laws, a different vendor's client. Read * `objectStore.ts` first for the laws both object adapters obey; read * `s3Artifacts.ts` beside this one to see what is genuinely per-vendor rather * than per-implementation. Three things really are different here, and each * one changes what an operator pays: * * 1. **A listing carries the metadata.** `bucket.getFiles()` hands back File * objects with their metadata already populated — creation time, size AND * the custom entry holding the ticket. So `list()` is ONE call per page * and needs no per-row read, where the S3 adapter must HEAD each row it * returns. Same port, same promise, cheaper here; said out loud because * "the cloud adapters behave identically" is true of the CONTRACT and not * of the bill. * 2. **A bigger metadata budget.** Custom metadata is capped at 8 KiB * (keys and values together), against 2 KB on the other column. The * ticket is checked against it at put and refused by name, so the cap is * a stated limit and not a surprise from the service. * 3. **The client has no command objects.** Methods hang off a chain — * `storage.bucket(b).file(k).save(...)` — which is why this column's pin * test asserts a METHOD CHAIN rather than a set of command names. * * ── The object key ────────────────────────────────────────────────────────── * `[/]///`, scope-partitioned by * `scopePath.ts` — the same percent-encoding law as the directory adapter, so * a tenant of literally `'..'` is a NAME here too. A ref alone opens nothing: * a wrong scope computes a different object name, the service answers 404, and * the caller reads `null`. * * ── What a 404 is allowed to mean here ────────────────────────────────────── * A missing object and a missing bucket look the SAME on this column * (`code: 404`, reason `notFound`, differing only in prose this adapter will * not parse). So the split is made by the call instead: only a read of one * named object may read a 404 as "not there". A 404 from a save or a listing * is not an answer to anything the caller asked — it goes through the * sanitizer, because nothing downstream converts it and the client's own text * for it carries the object name. * * ── Retention, and the operator's bulk tool ───────────────────────────────── * `ttlMs` stamps `expiresAt` AT MINT (stated, never sprung); expiry is * enforced on READ and the expired object is deleted on the way past; budgets * evict oldest-first (an object store has no cheap read-recency). A put SCANS * the scope only when a byte/count budget is configured — with no budget there * is nothing to plan, and a put stays a single upload. * * Reclaiming what nobody reads again is **Object Lifecycle Management**, the * operator's bulk tool. This adapter does not create rules: a lifecycle rule * is a cost and compliance decision that belongs to your infrastructure. * Align it like this: * * ```jsonc * // Delete objects 7 days after creation. Keep the rule LONGER than the * // store's ttlMs, never shorter: `expiresAt` is the promise printed on the * // ticket, and a lifecycle rule that deletes first makes a live ticket * // resolve to null BEFORE the time it stated. Longer, and lifecycle is what * // it should be — the backstop for what the store's own sweep never * // revisited. * { "lifecycle": { "rule": [ * { "action": { "type": "Delete" }, * "condition": { "age": 7, "matchesPrefix": ["artifacts/"] } } * ]}} * ``` * * ── Lazy peer dependency ──────────────────────────────────────────────────── * `@google-cloud/storage` is an OPTIONAL peer dependency, required at * CONSTRUCTION (the sqliteSessions law): importing the barrel costs a browser * bundle nothing, and a missing install refuses where the config was written. * Pass `storage` to share the client your app already built. * * ── What that peer's TREE carries, said out loud ──────────────────────────── * Optional means it is your dependency and not this package's — nothing here * loads it unless you call `gcsArtifacts` — but you inherit its tree when you * do. Two independent audits (2026-08-13, 2026-08-14) reported the same five * MODERATE advisories, and they reproduce here: * * `@google-cloud/storage` → `retry-request` → `teeny-request` → `gaxios` → `uuid` * * rooted in `uuid` (GHSA-w5hq-g745-h8pq — a missing buffer bounds check in * v3/v5/v6 when `buf` is provided). It is not a defect in this adapter and * there is no line here that would fix it: the chain is entirely inside * Google's client. * * **Do not take `npm audit fix --force`.** Its resolution installs * `@google-cloud/storage@5.18.3` — a major DOWNGRADE to a client from a * different era of the API. Trading a bounds check in a path this adapter does * not exercise for a client several majors behind the service is a different * outage, not a security improvement, and this package will never pin you to * it. Pin the newest 7.x yourself and watch the upstream chain. * * ── Status ────────────────────────────────────────────────────────────────── * **Field-validated** (was "awaiting field use" through 9.28.0). The method * chain it calls is pinned against the really-installed package by * `test/adapters/google/google-surface-pin.test.ts`, and the behaviour is * proved offline against an emulation double that speaks the same chain — but * the promotion rests on live evidence, not on those: * * An independent trial ran THIS adapter, unchanged since 9.25.0, against a real * Cloud Storage bucket (`@google-cloud/storage@7.22.0`, uniform bucket-level * access, public-access prevention on) and all nine checks passed: JSON * put/head/get with a verified SHA-256, scope isolation refusing another * tenant / principal / conversation, two distinct cursor pages, a byte-exact * native `putStream`/`getStream`, a TTL that expired and lazy-deleted, a * `maxCountPerScope` eviction reporting `max-count`, an oversize label refused * BEFORE upload against the 8 KiB metadata budget, a missing bucket answering * with the documented ambiguous `null` on read and a sanitized 404 on write * that leaked neither key nor scope, and an idempotent repeated delete. * (FINDINGS "Cloud Storage `gcsArtifacts()` — field PASS".) * * What that does NOT promote: the same evidence says nothing about a bucket * with soft delete or object versioning enabled — the trial's bucket had both * off, on purpose, so the evidence would be disposable. */ /// import { type ArtifactRetention } from './retention.js'; import { type ArtifactStore } from './types.js'; /** One object's metadata, as this adapter reads it. */ export interface GcsFileMetadataLike { readonly size?: string | number; readonly timeCreated?: string; readonly contentType?: string; /** The custom entries — where the ticket rides. */ readonly metadata?: Record; } /** One object handle, as this adapter calls it. */ export interface GcsFileLike { /** Populated by a listing; a File built by `bucket.file()` may not have it. */ readonly name?: string; readonly metadata?: GcsFileMetadataLike; save(data: Uint8Array | string, options?: unknown): Promise; download(options?: unknown): Promise<[Uint8Array]>; getMetadata(options?: unknown): Promise<[GcsFileMetadataLike, unknown?]>; delete(options?: unknown): Promise; createReadStream(options?: unknown): NodeJS.ReadableStream; createWriteStream(options?: unknown): NodeJS.WritableStream; } /** One bucket handle, as this adapter calls it. */ export interface GcsBucketLike { file(name: string): GcsFileLike; getFiles(query?: unknown): Promise<[GcsFileLike[], unknown?, unknown?]>; } /** The client, as this adapter calls it. */ export interface GcsStorageLike { bucket(name: string): GcsBucketLike; } /** The module shape this adapter loads. */ export interface GcsSdkModule { readonly Storage?: new (config: Record) => GcsStorageLike; } /** Options for {@link gcsArtifacts}. */ export interface GcsArtifactsOptions { /** The bucket. It must already exist — this library never creates one. */ readonly bucket: string; /** Object-name prefix inside the bucket, so a bucket can be shared. */ readonly prefix?: string; /** Project id for the client this factory builds. Ignored when `storage` is * passed — that client's configuration is yours. */ readonly projectId?: string; /** Your own pre-built client; configuration and credentials stay yours. */ readonly storage?: GcsStorageLike; /** Retention dials. Budgets evict OLDEST-first (no cheap read-recency). */ readonly retention?: ArtifactRetention; /** @internal Test seam — the SDK module, injected. */ readonly _sdk?: GcsSdkModule; /** @internal Test seam — a client injected past the SDK entirely. */ readonly _storage?: GcsStorageLike; /** @internal Test seam — the clock. Defaults to `Date.now`. */ readonly _now?: () => number; } /** * An artifact store in a Cloud Storage bucket. * * @example * const store = gcsArtifacts({ bucket: 'my-agent-artifacts', prefix: 'artifacts' }); * const agent = Agent.create({ provider, artifacts: store }); */ export declare function gcsArtifacts(options: GcsArtifactsOptions): ArtifactStore; //# sourceMappingURL=gcsArtifacts.d.ts.map