/** * Job queue protocol types for encrypted Personal Server execution. * * Builder -> GW -> Agent -> Sandbox * Builder -> GW: submit an enclave-encrypted request. * GW -> Agent: claim work with a fenced lease and sealed owner identity. * Agent -> Sandbox: decrypt, wake the owner sandbox, and execute privately. * Sandbox -> Agent: encrypt the result to the builder's public key. * Agent -> GW: store the sealed result and complete with its object key, hash, and size. * GW -> Builder: return inline or polled status with an object-storage handle. * Builder: decrypt and verify the result's job, scope, and version bindings. * Full flow: personal-server-ts `docs/260903-jobs-contract.md`, section 1. * * @category Protocol */ import type { Address, Hex } from "viem"; import type { SealedEnvelope } from "./identity.js"; export declare const JOB_PROTOCOL_VERSION = 1; export declare const JOB_OPERATIONS: readonly ["raw_read", "inference"]; export type JobOperation = (typeof JOB_OPERATIONS)[number]; export declare const JOB_STATES: readonly ["queued", "claimed", "running", "completed", "failed", "expired", "cancelled"]; export type JobState = (typeof JOB_STATES)[number]; /** Payment lifecycle recorded for a queued job. */ /** * What actually happened to the money for one read. * * `none` is the pre-fee literal every job carried before job fees and stays in * the union for those rows. `free` is a chain whose `data_access` fee is * disabled or zero — priced honestly at nothing, not a placeholder. `unbilled` * is a read the Gateway priced but did not charge, either during the fee * rollout or on a path with no payer. */ export type PaymentState = "none" | "free" | "unbilled" | "reserved" | "settling" | "settled" | "released"; /** * The builder's signed authorization for the price of one read. * * The same EIP-712 `GenericPayment` a legacy Personal Server read signs into * its `X-PAYMENT` header, over `opType: "job_access"` and * `opId: keccak256(jobId)`. The Gateway stores it verbatim on the payment row. */ export interface JobPaymentAuthorization { signature: Hex; /** uint256 decimal; must equal the Gateway's quote for this chain. */ amount: string; asset: Address; /** uint256 decimal, unique per (payer, kind). */ paymentNonce: string; } /** The price of one delivered read, as `GET /v1/jobs/quote` reports it. */ export interface JobQuote { chainId: number; /** uint256 decimal; "0" when this chain charges nothing. */ price: string; asset: Address; /** False when the fee is disabled or zero, so no payment is needed. */ payable: boolean; /** Whether the Gateway refuses an unpaid submission today. */ enforced: boolean; } /** RecordDataAccess receipt fields plus the enclave signature over them. */ export interface JobAccessRecord { dataPointId: Hex; /** uint256 decimal — the data-point version actually served. */ version: string; accessor: Address; /** bytes32 per-event replay nonce; the contract pins it. */ recordId: Hex; signature: Hex; } export declare const DEFAULT_LEASE_SECONDS = 30; export declare const MAX_LEASE_SECONDS = 300; export declare const MAX_ATTEMPTS = 3; export declare const MAX_WAIT_SECONDS = 25; export declare const CLAIM_POLL_FLOOR_MS = 1000; export declare const DEFAULT_JOB_DEADLINE_SECONDS = 600; export declare const MAX_JOB_DEADLINE_SECONDS = 3600; /** Inner plaintext of the request box (ECIES to the enclave publicKey). */ export interface JobRequest { v: 1; jobId: string; owner: Address; builder: Address; builderPublicKey: Hex; grantId: Hex; scope: string; operation: JobOperation; pinnedVersion: string | null; deadline: string; } /** Plaintext of the request box; the Gateway never sees it. */ export interface JobRequestEnvelope { request: JobRequest; /** * `Web3Signed .` by the builder: `aud` = Gateway origin, * `uri` = `/v1/jobs/execute`, * `bodyHash` = `sha256(canonicalJobRequestBytes(request))`. */ auth: string; } /** Outer body of POST /v1/jobs (signed Web3Signed by the builder). */ export interface JobSubmission { owner: Address; grantId: Hex; scope: string; operation: JobOperation; idempotencyKey: string; /** Client UUID, echoed in `JobRequest.jobId`. */ jobId: string; deadline?: string; /** Base64 ECIES from `sealJobRequest`. */ requestCiphertext: string; /** * The most the builder accepts for this read, uint256 decimal. The Gateway * refuses a quote above it rather than charging a price nobody agreed to. * * @remarks * This submission is authenticated by a Web3Signed body hash, not by an * EIP-712 struct, so these three fields are covered by the builder's existing * request signature. No typed-data version changes. */ maxPrice?: string; priceAsset?: Address; payment?: JobPaymentAuthorization; } /** Where a completed job's sealed result lives. Bytes never transit the Gateway. */ export interface ResultHandle { /** Object key in vana-storage, `jobresults/{chainId}/{jobId}`. */ objectKey: string; /** Absolute URL the builder GETs. The Gateway builds it from its storage origin. */ url: string; /** Byte length of the sealed object. */ size: number; /** sha256 of the sealed bytes, 0x-prefixed. */ hash: Hex; /** Logical expiry. After this the Gateway stops serving the handle. */ expiresAt: string; } /** Response from `GET /v1/jobs/:id`. */ export interface JobStatus { jobId: string; state: JobState; operation: JobOperation; owner: Address; grantId: Hex; scope: string; pinnedVersion: string | null; attempt: number; price: string; /** * Null exactly when the read is free; nothing owed is owed to nobody. * Optional so a consumer's existing `JobStatus` fixtures keep compiling; the * Gateway always sends it. */ priceAsset?: Address | null; payer: "builder"; paymentState: PaymentState; createdAt: string; claimedAt: string | null; completedAt: string | null; failureReason: string | null; /** Present only when `state === "completed"`. */ result?: ResultHandle; } /** Request body for `POST /v1/jobs/claim`. */ export interface ClaimRequest { leaseSeconds?: number; capacity?: number; } /** Claimed job and owner identity returned by `POST /v1/jobs/claim`. */ export interface ClaimResponse { job: { jobId: string; owner: Address; builder: Address; grantId: Hex; scope: string; operation: JobOperation; pinnedVersion: string | null; requestCiphertext: string; attempt: number; deadlineAt: string | null; claimExpiresAt: string; fencingToken: number; }; identity: { userPsId: Hex; epoch: number; enclaveAddress: Address; enclavePublicKey: Hex; sealedEnvelope: SealedEnvelope; }; } /** Request body for `POST /v1/jobs/:id/heartbeat`. */ export interface HeartbeatRequest { leaseSeconds?: number; fencingToken: number; } /** Request body for `POST /v1/jobs/:id/complete`. */ export interface CompleteRequest { fencingToken: number; resultHash: Hex; resultSize: number; /** `jobresults/{chainId}/{jobId}`. */ resultObjectKey: string; /** * Server-signed delivery receipt for the read this job served, minted by the * node agent with the owner's enclave wallet. Binds the reserved payment to * an on-chain `recordDataAccess`, exactly as the legacy paid read does. * Required whenever the job reserved a fee. */ accessRecord?: JobAccessRecord; } /** Request body for `POST /v1/jobs/:id/fail`. */ export interface FailRequest { fencingToken: number; reason: string; } /** Successful response from a fenced job write endpoint. */ export interface FencedResponse { success: true; jobId: string; state: JobState; claimExpiresAt: string | null; } /** Inner plaintext of the result box (ECIES to builderPublicKey). */ export interface JobResult { v: 1; jobId: string; scope: string; version: string | null; contentType: string; /** Raw result bytes; callers decode text explicitly when appropriate. */ body: Uint8Array; } /** Admission lifecycle of a registered TEE node. */ export type TeeNodeState = "pending" | "admitted" | "draining" | "removed"; /** Request body for `POST /v1/tee-nodes`. */ export interface TeeNodeRegistration { nodeId: string; appId: Hex; composeHash: Hex; publicUrl: string; capacity: number; secret: string; } /** Request body for `POST /v1/tee-nodes/:id/heartbeat`. */ export interface TeeNodeHeartbeat { composeHash: Hex; instanceId: string; activeSandboxes: number; capacity: number; } /** Public registration and capacity state for a TEE node. */ export interface TeeNode { nodeId: string; appId: Hex; composeHash: Hex; publicUrl: string; state: TeeNodeState; capacity: number; activeSandboxes: number; lastHeartbeatAt: string | null; }