import type { CollectExpiredInput, CollectExpiredResult } from './retention'; import type { TenantId } from './tenant'; /** `sha256:<64 lowercase hex>`. The only content-address form core accepts. */ export declare const CONTENT_HASH_PATTERN: RegExp; /** A validated content address. Branded so an unvalidated digest cannot stand in for one. */ export type ContentHash = string & { readonly __byokContentHash: unique symbol; }; /** * The single mint point for {@link ContentHash}. * * Fail-closed on uppercase hex: accepting both cases would make the same bytes * addressable under two keys, which breaks per-tenant deduplication and the * "count each hash once" billing rule (§12.7.6). * * @throws {ByokCoreError} code `content_hash_invalid`. */ export declare function contentHash(value: string): ContentHash; /** Non-throwing form of {@link contentHash}. */ export declare function isContentHash(value: unknown): value is ContentHash; /** * What a deployment-level key namespace may look like: slash-joined segments of * lowercase alphanumerics, `.`, `_`, and `-`, each starting with an * alphanumeric. No leading or trailing slash, and therefore no empty segment. * * Narrow on purpose, and narrower than S3 keys allow. A prefix is spliced into * every key this SDK ever writes, so the only prefixes worth accepting are the * ones that reach the object store spelled exactly as configured: anything * requiring percent-encoding would give the deployment's objects two names, and * anything with an empty segment would make `a//b` and `a/b` the same key at * rest but different strings in config. Case is folded out for the same reason * {@link contentHash} refuses uppercase hex — one address, one spelling. */ export declare const OBJECT_KEY_PREFIX_PATTERN: RegExp; /** A validated deployment key namespace. Branded, so only the mint point below can produce one. */ export type ObjectKeyPrefix = string & { readonly __byokObjectKeyPrefix: unique symbol; }; /** * The single mint point for {@link ObjectKeyPrefix}. * * There is no "empty means no prefix" spelling here: a composition that has no * prefix omits the option, and one that passes `''` has misconfigured something * (an unset environment variable is the usual way). Accepting it silently would * make the two indistinguishable at exactly the moment the difference decides * where a deployment's objects live. * * @throws {ByokCoreError} code `object_key_prefix_invalid`. */ export declare function objectKeyPrefix(value: string): ObjectKeyPrefix; /** * Tenant-scoped object key, e.g. * `tenants//objects/sha256/` (§12.7.4). * * `prefix` namespaces the whole layout — `/tenants/...` — so one bucket * can hold several deployments without either of them owning the bucket root. * Omitting it produces the unprefixed key verbatim, which is what makes the * option safe to add: every object already at rest was written without one. * * The prefix is an IMMUTABLE property of a deployment. It is spliced in here * and nowhere else, and nothing reads a key back under a second layout: change * a live deployment's prefix and its existing objects become unaddressable. * See `R2BlobStoreOptions.keyPrefix` in `@byok-sdk/cloud-dataplane` for the full * operational contract. */ export declare function tenantObjectKey(tenant: TenantId, hash: ContentHash, prefix?: ObjectKeyPrefix): string; /** * Object manifest lifecycle (§12.7.8). * * `pending` exists because Postgres and R2 have no shared transaction: the row * is written before the bytes land, and only `committed` rows may be referenced * by a truth record. `delete_pending` is the tombstone the GC worker drives, so * a failed R2 delete is retryable instead of leaving usage silently wrong. */ export declare const OBJECT_STATES: readonly ['pending', 'committed', 'delete_pending', 'deleted']; export type ObjectState = (typeof OBJECT_STATES)[number]; /** Legal manifest transitions. Anything else raises `object_state_invalid`. */ export declare const OBJECT_STATE_TRANSITIONS: Readonly>; export declare function isLegalObjectTransition(from: ObjectState, to: ObjectState): boolean; /** One row of `object_manifest` (§12.7.6). */ export interface ObjectManifestEntry { readonly tenantId: TenantId; readonly hash: ContentHash; /** Declared at reservation time, re-verified at commit against the store's `HEAD`. */ readonly byteSize: bigint; readonly contentType: string; readonly state: ObjectState; /** Number of live {@link ObjectReference} rows. `0` makes the object GC-eligible after grace. */ readonly refCount: number; readonly createdAt: string; readonly updatedAt: string; /** Set when the row entered `delete_pending`; the grace window is measured from here. */ readonly deletePendingAt?: string; } /** What references an object. `refKind`/`refId` are opaque to core. */ export interface ObjectReference { readonly tenantId: TenantId; readonly hash: ContentHash; readonly refKind: string; readonly refId: string; readonly createdAt: string; } export interface ObjectManifestInput { readonly hash: ContentHash; readonly byteSize: bigint; readonly contentType: string; } /** * Finalize input. `byteSize`/`contentType` are what the composition actually * observed on the object store, not what the client declared — the whole point * of the check is that the two can differ (§12.7.7 step 4). */ export interface ObjectCommitInput { readonly hash: ContentHash; readonly observedByteSize: bigint; readonly observedContentType: string; } export interface ObjectReferenceInput { readonly hash: ContentHash; readonly refKind: string; readonly refId: string; } export interface ObjectListQuery { readonly state?: ObjectState; /** * Only rows whose `deletePendingAt` is at or before this instant. * * Must be a **canonical ISO-8601 UTC instant** (`YYYY-MM-DDTHH:mm:ss.sssZ`); * anything else is rejected with `timestamp_not_canonical`, because the * in-memory composition compares it as a string and a SQL composition * compares it as a `timestamptz` — see `time.ts`. */ readonly deletePendingBefore?: string; readonly limit?: number; } /** * Object manifest port. Tenant-first, async, metadata only. * * Raises: `object_not_found`, `object_state_invalid`, * `storage_integrity_mismatch` (commit observed size/type disagreeing with the * declared manifest row), `timestamp_not_canonical` (a `list` query whose * `deletePendingBefore` is not a canonical ISO-8601 UTC instant). */ export interface ObjectStore { collectExpired(tenant: TenantId, input: ObjectCollectExpiredInput): Promise; /** Creates or returns the `pending` row for `hash`. Idempotent per (tenant, hash). */ putManifest(tenant: TenantId, input: ObjectManifestInput): Promise; /** `pending` → `committed` after verifying observed size/type. */ commit(tenant: TenantId, input: ObjectCommitInput): Promise; get(tenant: TenantId, hash: ContentHash): Promise; list(tenant: TenantId, query: ObjectListQuery): Promise; /** Idempotent per (tenant, hash, refKind, refId) — re-adding does not double-count. */ addReference(tenant: TenantId, input: ObjectReferenceInput): Promise; removeReference(tenant: TenantId, input: ObjectReferenceInput): Promise; /** Tombstone step 1: only legal at `refCount === 0`. */ markDeletePending(tenant: TenantId, hash: ContentHash): Promise; /** Tombstone step 3: the object store delete succeeded. */ markDeleted(tenant: TenantId, hash: ContentHash): Promise; } export interface ObjectCollectExpiredInput extends CollectExpiredInput { readonly orphanBefore: string; }