import { type SignedMandate } from "@aithos/protocol-client"; import type { AithosAuth } from "./auth.js"; import type { AithosSdkEndpoints } from "./endpoints.js"; /** Capability scope the SDK accepts. Server-side ultimately decides. * * Note: `compute.invoke` is intentionally NOT in this union. The token- * spending capability is opt-in via the dedicated {@link CreateMandateInput.compute} * namespace — see {@link MandatesNamespace.create}. Passing `compute.invoke` * directly in `scopes` is rejected at runtime; the compiler can't enforce * it (callers who up-cast to string[] would slip through), so the runtime * check is the real gate. */ /** An ethos verb (draft `bundle-v0.3-section-verb-scopes.md` §4.8.2′). */ export type EthosVerb = "read" | "edit" | "append" | "delete" | "write"; /** A writable ethos zone. */ export type EthosZone = "public" | "circle" | "self"; /** * An ethos scope: a whole-zone grant (`ethos.edit.self`) or one narrowed to a * subset of sections by a per-scope selector (`ethos.edit.self#id=X`, * `ethos.append.self#prefix=gmail:`, `ethos.read.circle#tag=bio`). Plus the * legacy whole-everything read `ethos.read.all`. §4.8′. */ export type EthosScope = `ethos.${EthosVerb}.${EthosZone}` | `ethos.${EthosVerb}.${EthosZone}#${string}` | "ethos.read.all"; export type Scope = EthosScope | DataScope; /** Action a data mandate may authorize on a collection. `write` implies * `read`; `admin` implies `write`. Mirrors the data sub-protocol grammar * `data..` (Aithos-protocol `spec/data/04-mandates.md` * §4.2) and the server-side check `requireScope` in data-backend. */ export type DataAction = "read" | "write" | "admin"; /** * A **lateral** data capability — deliberately OUTSIDE the * `read ⊂ write ⊂ admin` hierarchy (the same way `gamma.write` sits beside * the ethos scopes). Keeping it a separate type makes the security invariant * structural rather than conventional: `append` can never be reached by * widening a `write`/`admin` scope, so it cannot accidentally carry read. * * `append` authorizes `insert_record` ONLY (no read, update, or delete). The * depositor seals each record's DEK to the owner's public key * ({@link createAppendDataClient}) and holds no read capability — it cannot * decrypt anything in the collection, not even its own deposit. */ export type DataLateralAction = "append"; /** * A data-access scope: `data..`, or the cross-collection * wildcard `data.*.`. Examples: `data.contacts.read`, * `data.depots.write`, `data.*.read`. * * Note on `actor_sphere`: data mandates are minted under `actor_sphere: * "self"` (the owner's highest-authority sphere). The sphere is *not* the * access axis for data — the collection is. `actor_sphere` is informative * here; the cryptographic binding is the grantee's key + the CMK wrap, per * spec §4.4. A dedicated `#data` sphere key (independent rotation) MAY be * introduced later without changing this scope grammar. * * Collection names MUST NOT contain `.` (the server splits the scope on * `.` and reads the first three segments). */ export type DataScope = `data.${string}.${DataAction}` | `data.${string}.${DataLateralAction}`; /** * The opt-in scope that authorizes a delegate to spend the subject's * compute credits via the Aithos compute proxy. Mirror of * `COMPUTE_INVOKE_SCOPE` in `@aithos/protocol-core` v0.4.0. * * The SDK's `mandates.create()` injects this scope automatically when * the caller passes a `compute` namespace, and refuses to mint a * mandate where the caller put it directly into `scopes` — this is * what makes "compute is a separate, conscious decision" hold at the * API surface. */ export declare const COMPUTE_INVOKE_SCOPE: "compute.invoke"; /** * Which sphere of the owner signs the mandate. Bounds the upper-most * scope set the mandate can carry. */ export type ActorSphere = "public" | "circle" | "self"; /** * Compute-spending capability — opt-in only, never implied by ethos * scopes. * * When `compute` is set on {@link CreateMandateInput}, the SDK: * 1. Adds the `compute.invoke` scope to the minted mandate. * 2. Maps the caller's caps onto `constraints.compute` in the * protocol's snake_case shape (= what the verifier reads). * 3. Forbids the caller from passing `compute.invoke` in `scopes` * directly — that would let an app slip the scope past a * consent UI that only reviews `compute`. * * At least one of `dailyCapMicrocredits` or `totalCapMicrocredits` MUST * be set: an unbounded compute mandate is the kind of bearer-token * footgun this whole namespace exists to prevent. Validation happens * at the SDK boundary (here) AND at the protocol layer (the * server-side verifier rejects capless 0.4.0 mandates), so a bug in * either tier still fails closed. * * `maxCreditsPerCall` is a per-invocation safety net for runaway * single requests. `allowedModels`, when set, restricts which Bedrock * model ids the delegate may target (the proxy's own allowlist still * applies on top). */ export interface CreateMandateComputeInput { /** Hard cap on credits debited per UTC day under this mandate. */ readonly dailyCapMicrocredits?: number; /** Hard cap on credits debited over the whole mandate lifetime. */ readonly totalCapMicrocredits?: number; /** Hard cap on credits debited by any single invocation. */ readonly maxCreditsPerCall?: number; /** Allowlist of Bedrock model ids the delegate may invoke. */ readonly allowedModels?: readonly string[]; } export interface CreateMandateInput { /** Grantee URN — usually `urn:aithos:agent:` or similar. */ readonly granteeId: string; /** Optional human-readable label for the grantee. */ readonly granteeLabel?: string; /** * Sphere of the owner that issues the mandate. Defaults to the * highest-numbered sphere covered by `scopes` (most permissive * common ancestor): `"self"` if any scope ends in `.self`, else * `"circle"` if any ends in `.circle`, else `"public"`. */ readonly actorSphere?: ActorSphere; /** Capability set granted by the mandate. */ readonly scopes: readonly Scope[]; /** Lifetime in seconds. */ readonly ttlSeconds: number; /** * Opt-in compute (token-spending) capability — adds the * `compute.invoke` scope and a bounded `constraints.compute` budget * to the mandate. See {@link CreateMandateComputeInput}. * * NEVER add `compute.invoke` to `scopes` directly — the SDK rejects * that path so the caller has to pass through this typed namespace, * which is what a consent UI can review. */ readonly compute?: CreateMandateComputeInput; /** * When the mandate becomes valid. Optional — when omitted, the * underlying mint helper signs with `not_before = now - 30s` (see * `MANDATE_NOTBEFORE_OFFSET_SECONDS_DEFAULT` in * `@aithos/protocol-client`) so a server whose clock runs slightly * behind the client doesn't reject the freshly-minted mandate as * `not yet valid`. * * Pass an explicit `Date` only for advanced flows (delayed-activation * mandates, deterministic tests). */ readonly notBefore?: Date; } export interface MintedMandate { /** Unique mandate id (matches `mandate.id` inside the bundle). */ readonly mandateId: string; /** Subject DID — the owner who issued it. */ readonly subjectDid: string; /** Grantee URN. */ readonly granteeId: string; readonly scopes: readonly Scope[]; /** ISO-8601 (UTC) — `null` if the mandate has no `not_after`. */ readonly expiresAt: string | null; /** * The signed mandate object itself — pass it straight to * `sdk.ethos.me().reseal({ includeMandates: [minted.mandate] })` so the * fresh grant is sealed in deterministically, without waiting for the * server's `list_mandates` index to settle (it's eventually consistent; * a mint-then-immediately-reseal can otherwise miss the new mandate). * Same object as `bundle`'s `mandate` field — exposed so callers don't * have to re-parse the Blob. */ readonly mandate: SignedMandate; /** Shareable `.aithos-delegate.json` Blob. Hand this to the grantee. */ readonly bundle: Blob; /** Suggested filename for the bundle. */ readonly filename: string; } export interface OwnedMandate { readonly mandateId: string; readonly issuerDid: string; readonly actorDid: string; readonly scopes: readonly Scope[]; readonly notBefore: number | null; readonly notAfter: number | null; readonly createdAt: number; /** True iff this mandate has a published revocation (list_mandates reflects it). */ readonly revoked: boolean; } /** * Input for {@link MandatesNamespace.createBundle} — a CUMULATIVE grant: one * delegate key, N mandates (one per zone). Lets a single "full access" grant * cover public+circle+self without breaking the protocol's one-zone-per-mandate * write rule (each mandate still authors exactly its `actor_sphere`). */ export interface CreateBundleInput { readonly granteeId: string; readonly granteeLabel?: string; /** Zones to cover. Default: all three (`public`, `circle`, `self`). */ readonly zones?: readonly EthosZone[]; /** Mandate lifetime (seconds), applied to every mandate in the bundle. */ readonly ttlSeconds: number; /** Optional explicit activation; defaults to the mint clock-skew offset. */ readonly notBefore?: Date; } /** Result of {@link MandatesNamespace.createBundle}. */ export interface MintedBundle { /** Every signed mandate (one per requested zone), sharing one delegate key. */ readonly mandates: readonly SignedMandate[]; readonly granteeId: string; /** The single delegate keypair all mandates bind to. */ readonly agentKey: { readonly seedHex: string; readonly pubkeyMultibase: string; }; readonly zones: readonly EthosZone[]; /** Serialized `"aithos-mandate-pack": "2"` JSON (consumed by parseMandatePack). */ readonly pack: string; /** Same content as a downloadable Blob. */ readonly packBlob: Blob; readonly filename: string; /** Earliest `not_after` across the bundle (epoch ISO), or null. */ readonly expiresAt: string | null; } export interface MandatesNamespaceDeps { readonly auth: AithosAuth; readonly endpoints: AithosSdkEndpoints; readonly fetch: typeof fetch; } /** * Assemble a v2 (cumulative) mandate pack from N signed mandates sharing one * delegate key. Pure — no I/O — so it's unit-testable in isolation. The shape * matches the `parseMandatePack` v2 contract consumed by the agent host. */ /** * Active mandates this owner issued to a delegate. `actorDid` is the * grantee/delegate DID — the `granteeId` used at mint, also surfaced as * `OwnedMandate.actorDid` by {@link MandatesNamespace.list}. Pure: filters to * not-yet-revoked mandates, optionally dropping expired ones. Drives * {@link MandatesNamespace.revokeByGrantee} (cut a whole cumulative bundle). */ export declare function selectActiveMandatesForActor(mandates: readonly OwnedMandate[], actorDid: string, opts?: { readonly now?: number; readonly includeExpired?: boolean; }): OwnedMandate[]; export declare function buildBundlePackV2(mandates: readonly SignedMandate[], agentKey: { readonly seedHex: string; readonly pubkeyMultibase: string; }): { readonly pack: Record; readonly text: string; }; export declare class MandatesNamespace { #private; constructor(deps: MandatesNamespaceDeps); /** * Mint, sign, publish, and package a fresh delegate bundle. The * grantee's keypair is generated inside this call and never * persisted on the owner's machine — the seed flows out via the * returned Blob and only via that Blob. */ create(input: CreateMandateInput): Promise; /** * List mandates issued by the signed-in owner. Pages through * `aithos.list_mandates` until exhausted (or until 5 pages have * been crawled — an owner with more than 1000 active mandates is * out of scope today). */ list(): Promise; /** * Mint a CUMULATIVE grant: ONE delegate keypair + N mandates (one per zone), * so a single "full access" grant covers public+circle+self. Each mandate * still authors exactly its `actor_sphere` (protocol §4.8' unchanged); the * agent picks the matching mandate per zone. * * The first zone generates the shared keypair (via `mintDelegateBundle`); the * remaining zones reuse that same grantee key (via `signAndPublishMandate`, * which never regenerates a key). Like {@link create}, this does NOT reseal — * the caller seals the new grants in ONE pass: * * const b = await sdk.mandates.createBundle({ granteeId, ttlSeconds }); * await sdk.ethos.me().reseal({ includeMandates: [...b.mandates] }); * * Purely additive: leaves {@link create} / {@link revoke} untouched. */ createBundle(input: CreateBundleInput): Promise; /** * Publish a §4.2 revocation for `mandateId`. The mandate stops * authorizing future actions (artifacts dated before `revoked_at` * remain valid — revocation is not retroactive). * * Server-side: handled by `aithos.publish_revocation`. The envelope * is signed by the owner's `#public` sphere — the spec also accepts * `#root`, but `#public` is what the existing app does. * * Throws {@link AithosSDKError} on backend errors. Note that * server-side support is in-flight; this method may surface a * `mandates_-32601` (method not found) until the auth platform * lands the corresponding write handler. */ revoke(mandateId: string): Promise; /** * Revoke ALL mandates in ONE write — the revocation EPOCH. * * Publishes a root-signed did.json whose `aithos.mandates_void_before` is * set to `opts.epoch` (default: now): every mandate issued before that * instant is void. The server enforces it on both reads and writes (via * protocol-core's verifier), with no per-mandate enumeration — O(1) * however many mandates exist. Per-mandate revocation objects older than * the epoch become redundant (GC-safe, see the provider runbook). * * Scope note: this is the authorization kill-switch. Residual wraps still * sit on the manifest until `ethos.me().pruneWraps()` (metadata hygiene), * and the cryptographic cut for already-downloaded ciphertext remains * `ethos.me().reseal({ mode: "rotate" })`. */ /** * Revoke EVERY active mandate this owner issued to a delegate (`actorDid` = * the `granteeId` used at mint, or any `OwnedMandate.actorDid` from `list()`). * The one-shot cut for a cumulative bundle: all N zone-mandates share the same * delegate, so this revokes them together. Additive — composes the unchanged * `list()` + `revoke()`. * * @returns the revoked mandate ids (list order). Already-revoked / expired * mandates are skipped (pass `includeExpired` to revoke expired ones too). */ revokeByGrantee(actorDid: string, opts?: { readonly includeExpired?: boolean; }): Promise<{ readonly revoked: readonly string[]; }>; revokeAll(opts?: { readonly epoch?: Date; }): Promise<{ readonly mandatesVoidBefore: string; }>; } //# sourceMappingURL=mandates.d.ts.map