import type { ImageViewer, ImageIndexEntry } from "./plugins/store-contracts.js"; /** Minimal slice of the image index the per-task resolver needs (a testable seam — the real `imageIndex`'s * `latestPublished` returns the full {@link ImageIndexEntry}, which is assignable to this Pick). */ export interface SandboxImageResolver { latestPublished(profile: string, viewer: ImageViewer): Promise | null>; } export type SandboxImageResolution = { ok: true; ref: string; capabilities?: import("./plugins/store-contracts.js").ImageCapabilities; } | { ok: false; status: number; message: string; }; /** * Resolve a requested sandbox image PROFILE to a pullable `repo@digest`, FAIL-CLOSED, re-admitting by `viewer`: * - `latestPublished(profile, viewer)` applies the SAME `visibilityClause` + `status='published'` filter the * select admission uses. Not visible / not published ⇒ `{ok:false, 404}` (never silent-allow). * - each `capabilitiesNeeded` entry must be a known boolean capability AND true on the resolved image, else fail. * Pure over the injected `index` ⇒ unit-testable with a fake resolver. * * NOTE (review LOW — error-code asymmetry, DELIBERATE): a not-visible / capability-miss here returns 404, whereas * the operator-facing `POST /v1/images/select` returns 409 + `missing`. That is intentional: task admission is an * UNTRUSTED-caller gate that must NOT distinguish "profile exists but you can't see it" from "doesn't exist" * (revealing that leaks the existence of other tenants' images), so it collapses both to 404. The select catalog * is the operator's own scoped view, where the richer 409 + `missing` detail is safe. */ export declare function resolveSandboxImageRef(opts: { profile: string; capabilitiesNeeded?: readonly string[]; viewer: ImageViewer; index: SandboxImageResolver; }): Promise; /** * The trusted-control-plane bridge from `resolveSpec` (resolves the per-task image with the caller's principal) to * the worker-global `ExecutionEnvFactory` (gets only core's minimal `{sessionId, taskId?}`). NOT a durable store * and NOT a cache: `resolveSpec` REPOPULATES it on EVERY call — initial AND resume, on whichever replica runs * resolveSpec immediately before the factory — so a cross-replica resume re-resolves LIVE (the resume re-admit * discipline: a stale persisted digest can never bypass admission), and there is nothing durable to go stale. * * KEYED BY sessionId — the ONLY identifier stable across the factory `ctx` on every path (adversarial-review * round-2): `/v1/runs` mints its OWN durable taskId that clobbers `spec.taskId`, so keying by taskId misses on the * primary path; `sessionId` survives the spec spread on every path. `get()` is NON-removing (the factory may be * invoked >once per logical task). Cleanup is by bounded eviction (oldest-first) — each entry is a tiny string; an * evicted task is long finished. * * ⚠️ KNOWN narrow edge (same-principal correctness, NOT a security breach): two CONCURRENT requests for the SAME * session (necessarily the same owner — session ownership is enforced) race `set()`; a request the run-claim later * rejects (409) can leave its image bound for the active run. Both images are the same principal's own admitted * images, so the worst case is the user's run using the user's OTHER selected image. The hardening * (register-after-claim, keyed by the durable run id) needs the registry threaded into the server claim path — * tracked as a follow-up. cascade/verify + a profile are rejected up front (they strip/replace the session). */ export declare class PerTaskImageRegistry { private readonly bounded; constructor(maxEntries?: number); /** Register the resolved `repo@digest` for a session. A re-set (resume / a later same-session task) overwrites; * on overflow the oldest insertion is evicted so an orphaned entry (a resolveSpec not followed by a factory * call, e.g. a rejected/pre-exec-failed request) stays bounded. */ set(sessionId: string, ref: string): void; /** Read the ref for a session (NON-removing — the factory may be invoked more than once per logical task). * `undefined` ⇒ no per-task image for this session ⇒ the factory uses its worker-global default image. */ get(sessionId: string | undefined): string | undefined; } //# sourceMappingURL=per-task-image.d.ts.map