/** * artifacts/objectStore — the half every REMOTE OBJECT adapter shares. * * Two adapters put artifacts in somebody else's bucket. They speak different * SDKs and dispatch different operations, and that is exactly where a vendor * belongs — but the laws they follow must not fork, so the laws live here: * * 1. **The payload is the object body, and it is the CANONICAL BYTES** — * the same bytes `meta.bytes` counts and a digest covers. Not a JSON * envelope wrapping base64: a stored report should be downloadable with * the vendor's own console and be the report. That choice is also what * makes `getStream` possible at all — a body you have to parse is a body * you cannot stream. * 2. **The meta rides as ONE object-metadata entry** ({@link ARTIFACT_META_KEY}), * an ASCII-safe JSON envelope. One key, one truth: a "helpful" second * copy of `kind` in a sibling field is a second thing that can disagree. * ASCII because user metadata travels as an HTTP header on at least one * of these services — a label with an em-dash in it must not be * mangled at the transport — and JSON-with-escapes rather than base64 * because an operator reading the console should be able to SEE what the * ticket says. * 3. **The metadata budget is checked at put and REFUSED by name.** Both * services cap user metadata (the caps differ; each adapter states its * own). A refusal that names the field to shorten beats a vendor's * "MetadataTooLarge" at document 400, and beats silent truncation * always. * 4. **Missing means null; unreadable means an error.** A 404 is the * port's deliberate "no data" ambiguity — but only when the call ASKED * ABOUT ONE OBJECT. A write and a listing do not ask that question, so a * 404 from one of them is the service saying something else entirely (the * bucket does not exist, the endpoint is wrong); nobody downstream turns * it into `null`, so letting it past the sanitizer hands the caller the * SDK's own text — which contains the object key. That is what * {@link NotFoundMeaning} makes each call site declare, and why the * default is the safe one. Anything else — denied, throttled, a network * failure — is NOT "no data" either, and answering `null` for it would * report an empty scope over objects that exist. Each adapter maps its * own SDK's missing-object shape; everything else raises through * {@link objectSdkFailure}. * 5. **Listings order by the SERVICE's own creation time**, which comes back * free on a listing, and page by offset over that sorted list. The port * promises newest-first ACROSS pages; a bucket's key order is * lexicographic, so a cursor that was the service's own continuation * token would page in the wrong order and call it paging. * * Nothing in this file imports an SDK, names a vendor, or knows a wire * format. It is the part of an object adapter that can be unit-tested with * nothing but strings. */ import type { PayloadShape } from './payload.js'; import { type ArtifactListOptions, type ArtifactListResult, type ArtifactMeta } from './types.js'; /** The single object-metadata key that carries the ticket. */ export declare const ARTIFACT_META_KEY = "af-artifact"; /** * Encode a ticket for the object's metadata, refusing one that will not fit. * * @param meta the minted ticket. * @param shape how the payload must be rebuilt from the body's bytes. * @param budget the service's user-metadata cap, in bytes. * @param adapter the factory name, for the refusal. * @throws InvalidArtifactError when the encoded ticket exceeds the budget — * naming the fields that are big enough to be the reason. */ export declare function encodeArtifactMetaValue(meta: ArtifactMeta, shape: PayloadShape, budget: number, adapter: string): string; /** * Read a ticket back off an object's metadata. * * `undefined` for anything this runtime must not interpret: no entry (not * ours — a foreign object in a shared bucket is left alone), unparseable, a * newer envelope version, or a ref that is not a minted ref. Refusing to * interpret is the point: a half-read ticket would be a confident description * of the wrong thing. */ export declare function decodeArtifactMetaValue(value: unknown): { readonly meta: ArtifactMeta; readonly shape: PayloadShape; } | undefined; /** Case-insensitive lookup in a metadata bag. At least one of these services * lower-cases user-metadata keys in transit, so the key written and the key * read back are not guaranteed to be the same string. */ export declare function readMetadataEntry(bag: Record | undefined, key: string): unknown; /** Has the calendar passed this ticket? The one expiry predicate both object * adapters use, so "expired" cannot mean two things. */ export declare function isExpiredMeta(meta: ArtifactMeta, now: number): boolean; /** * Re-raise a failed SDK call WITHOUT its text (the `sdkFailure` law, applied * to object storage). * * A cloud SDK reports transport and validation failures by echoing request * detail into the message: the bucket, the KEY — which here contains the * tenant, the principal and the conversation id — and sometimes a signed URL * or a header the request carried. This library then hands that string to the * LLM as a tool result, puts it on the commit log, and ships it to every sink * attached. One echo and the scope tuple is in the conversation. * * So the text does not come through. What does is the part that is both safe * and actionable: which operation failed, the exception's NAME, and the HTTP * status. The original is deliberately NOT attached as `cause` — a cause * travels into every serializer that walks own properties, which would undo * all of this in one `JSON.stringify`. */ export declare function objectSdkFailure(adapter: string, operation: string, err: unknown): Error; /** * What a "not found" from ONE call is allowed to mean. * * The distinction is not academic: it decides whether an SDK error object * leaves this library intact. * * • `'missing-object'` — the call named one object and asked whether it is * there. Not-found is the port's deliberate "no data", so the SDK's own * error travels back to the CALL SITE, which immediately converts it to * `null`/`undefined`. It never leaves the adapter, so its text never reaches * an event, a log, or a model. * * • `'not-an-answer'` — the call asked no such question (it wrote, or it * listed). A not-found here means something the caller did not ask about is * wrong — most often that the bucket itself is not there — and NOTHING * downstream is waiting to convert it. It goes through * {@link objectSdkFailure} like every other failure. * * This is the DEFAULT for exactly that reason: a call site that says nothing * gets the sanitizer. Leaking has to be typed out on purpose, next to the * `catch` that proves the error is caught. */ export type NotFoundMeaning = 'missing-object' | 'not-an-answer'; /** * The one gate every object adapter runs its SDK failures through. * * Returns what to throw — the sanitized error for anything the caller is not * standing by to interpret, the original ONLY for a genuine missing object at * a call site that declared it asks about one, and our own refusals verbatim * (an {@link InvalidArtifactError} is this library's sentence, already written * for a human, and re-wrapping it would replace a teaching refusal with a * transport complaint). * * Built once per adapter so the vendor's not-found detection is bound in one * place: two call sites in the same adapter cannot end up disagreeing about * what "missing" looks like. * * @param adapter the factory name, for the message. * @param isMissingObject the adapter's own reading of its SDK's * "no such object" — the one genuinely per-vendor part. */ export declare function objectFailurePolicy(adapter: string, isMissingObject: (err: unknown) => boolean): (operation: string, err: unknown, meaning: NotFoundMeaning) => unknown; /** One row of a scope listing, before the tickets are paged. */ export interface ObjectListingRow { readonly meta: ArtifactMeta; /** The service's own creation time for the object (ms). The sort key — * see law 5 in the module header. */ readonly serviceCreatedAt: number; } /** * The port's paging law over a scope's rows: newest first, offset cursor. * * Sorted by the SERVICE's creation time (falling back to the ticket's own * `createdAt` when a listing did not carry one), ties broken by ref so two * objects written in the same millisecond still page deterministically. */ export declare function pageObjectListing(rows: readonly ObjectListingRow[], options: ArtifactListOptions | undefined): ArtifactListResult; /** The offset a cursor names. Total: a cursor this store did not mint reads * as "start at the beginning" rather than as an error — a caller cannot * learn anything about another scope by guessing one. */ export declare function decodeOffsetCursor(cursor: string | undefined): number; /** Validate a bucket name option at construction — the same shape of refusal * every adapter here gives for a missing required option. */ export declare function assertBucketOption(adapter: string, option: string, value: unknown): string;