/** * Derivative status: the lifecycle of the question behind a derived scope, * as the party that READS the answer sees it. * * @remarks * A builder registers a question with {@link registerQuestion} and follows it * with {@link waitForQuestion}, both of which need a write session. The app * that only consumes the answer holds no write entry at all — a consent flow * grants it a bare read on the derived scope — so it cannot open one, and * `GET /v1/data/` answers 404 for all three of "computing right * now", "failed but retrying" and "failed for good". * * `GET /v1/derivatives/status?derivedScope=` is that reader's view. * Authorization is the data read's (a live grant covering the derived scope, * or the owner), nothing is served and nothing is charged, and the view is * deliberately narrow: lifecycle, a coarse {@link DerivativeErrorCode} and * the next retry. The question text, the source scopes, the question id, the * registrar and the server's raw error string stay owner-only. * * Requires `personal-server-ts` with the status route; an older Personal * Server answers 404 for the route itself. * * @category Protocol */ import { z } from "zod"; import { type QuestionStatus } from "./derivative-questions.js"; import { type ResolveWriteSignerOptions, type WriteSignerSource } from "./write-signer.js"; export { DERIVATIVE_ERROR_CODES, DerivativeErrorCodeSchema, type DerivativeErrorCode, } from "./derivative-questions.js"; /** The reader-facing status route. */ export declare const DERIVATIVE_STATUS_PATH = "/v1/derivatives/status"; /** How long {@link waitForDerivativeStatus} polls before giving up. */ export declare const DEFAULT_DERIVATIVE_STATUS_TIMEOUT_MS = 120000; /** * How long {@link waitForDerivativeStatus} waits between polls when the * server names no retry time of its own. */ export declare const DEFAULT_DERIVATIVE_STATUS_POLL_INTERVAL_MS = 2000; /** * The status of the derived scope, not of one registration: when several * questions write the same scope, the server reports the most optimistic * true state, because serving data is registration-agnostic. */ export declare const DerivativeStatusSchema: z.ZodObject<{ derivedScope: z.ZodString; status: z.ZodEnum<{ pending: "pending"; ready: "ready"; failed: "failed"; stale: "stale"; }>; lastComputedAt: z.ZodPipe>, z.ZodTransform>; derivedVersion: z.ZodPipe>, z.ZodTransform>; derivedCollectedAt: z.ZodPipe>, z.ZodTransform>; errorCode: z.ZodPipe>>, z.ZodTransform | null, "internal" | "inference_unavailable" | "source_missing" | "grant_invalid" | null | undefined>>; retryAfterSeconds: z.ZodPipe>, z.ZodTransform>; }, z.core.$strip>; /** @see {@link DerivativeStatusSchema} */ export type DerivativeStatus = z.infer; /** What {@link getDerivativeStatus} needs to sign and send one read. */ export interface GetDerivativeStatusParams extends ResolveWriteSignerOptions { /** Personal Server origin, e.g. `https://ps.example.com`. */ personalServerUrl: string; /** The derived scope whose question to observe. */ derivedScope: string; /** * A grant covering the derived scope, sent as the signed `grantId` claim. * Omit only when the signer is the Personal Server's owner, who is * authorized without one. */ grantId?: string; /** Reader key: a viem `LocalAccount`, `WalletClient`, or `{ signMessage }`. */ signer: WriteSignerSource; /** Web3Signed audience; defaults to `personalServerUrl`. */ audience?: string; /** `fetch` to use; defaults to `globalThis.fetch`. */ fetch?: typeof fetch; /** Extra request headers. */ headers?: HeadersInit; /** Aborts the request in flight. */ signal?: AbortSignal; } /** What {@link waitForDerivativeStatus} polls with. */ export interface WaitForDerivativeStatusParams extends GetDerivativeStatusParams { /** Give up after this long (default 120s). */ timeoutMs?: number; /** * Wait between polls when the server names no retry time (default 2s). * A `retryAfterSeconds` from the server replaces this outright, longer or * shorter: it is when the next compute actually happens. */ pollIntervalMs?: number; /** * Aborts the wait, and the request in flight with it: the signal is passed * to every poll, so an abort during a stalled request does not sit until * the transport gives up. */ signal?: AbortSignal; } /** * The request target for a status read: the query carries the derived scope, * the signed `uri` does not. * * @remarks * Like every Web3Signed read (data, lineage), the Personal Server verifies * the signature over the PATH; per-scope authorization is enforced live * against the caller's grant on each request, so the query needs no * signature to be safe. Only the write path signs path AND query, where a * parameter decides what is written. */ export declare function derivativeStatusTarget(derivedScope: string): string; /** * Is this a state the reader can act on? * * @remarks * `ready` means the derived scope has an answer to read. A `failed` status * is settled only when no retry is pending: with `retryAfterSeconds` set the * Personal Server will compute again on its own, so the answer may still * arrive. `pending` and `stale` are always in flight. */ export declare function isDerivativeStatusSettled(status: DerivativeStatus): boolean; /** * Read the lifecycle of the question behind a derived scope. * * @remarks * Sends `GET /v1/derivatives/status?derivedScope=` with a Web3Signed * `Authorization` header carrying `grantId`, the same authentication a data * read uses. Nothing is charged: the route authorizes, it does not serve * data, so a priced grant raises no 402 here. * * @returns The status of the derived scope. When several registrations write * it, the most optimistic true state answers — `ready`, then `stale`, then * `pending`, then `failed` — because a duplicate that never wrote anything * must not report away an answer the scope has. * @throws {DerivativeQuestionNotFoundError} 404: the caller may read the * scope but no question stands behind it (and, on an older Personal * Server, the route itself is unknown). * @throws {WriteForbiddenError} 403: the grant does not cover the derived * scope. The check runs before any store lookup, so a caller cannot probe * which scopes have questions. * @throws {DerivativeQuestionRejectedError} On any other non-2xx answer or * an unparseable body. * @throws {WriteTransportError} When `fetch` itself failed. * @throws {WriteRequestError} On a missing `derivedScope` or no `fetch`. * * @example * ```typescript * const status = await getDerivativeStatus({ * personalServerUrl: "https://ps.example.com", * derivedScope: "coach.weekly", * grantId, * signer, * }); * if (status.status === "ready") { * const record = await readPersonalServerData({ ... }); * } else if (status.retryAfterSeconds !== null) { * // Computing or retrying: come back then. * } * ``` */ export declare function getDerivativeStatus(params: GetDerivativeStatusParams): Promise; /** * Poll {@link getDerivativeStatus} until the derived scope has an answer or * has stopped trying to get one. * * @remarks * Returns as soon as {@link isDerivativeStatusSettled} holds: `ready`, or * `failed` with no retry pending. A failure the server will retry is not a * result, so the wait continues through it — on the server's own cadence, * because `retryAfterSeconds` is when the next compute actually happens and * polling faster only spends requests. A failed status is returned, not * thrown: the reader branches on `errorCode`. * * @returns The settled status. * @throws {DerivativeQuestionTimeoutError} When the budget ran out first. * The question keeps computing on the server; call again. * * @example * ```typescript * const status = await waitForDerivativeStatus({ * personalServerUrl, * derivedScope: "coach.weekly", * grantId, * signer, * timeoutMs: 60_000, * }); * if (status.status !== "ready") console.log(status.errorCode); * ``` */ export declare function waitForDerivativeStatus(params: WaitForDerivativeStatusParams): Promise; /** Statuses that mean a compute is in flight. @see {@link QuestionStatus} */ export type PendingDerivativeStatus = Extract;