import type { Identity, DidDocument } from "./identity.js"; import { type Sphere } from "./did.js"; export interface Grantee { id: string; label?: string; pubkey?: string; } /** * Per-mandate cost ceilings for the `compute.invoke` scope. * * A mandate carrying `compute.invoke` MUST also carry `constraints.compute` * with at least one of `daily_cap_microcredits` or `total_cap_microcredits` * — see {@link validateComputeAuthorization}. This is the "in conscience, * voluntarily" property: a mandate that authorizes spending is required * to also bound it. Servers honouring the scope MUST enforce these caps * (atomic mandate-usage debit) on top of the wallet balance check. * * Fields are independent. Common combinations: * - daily_cap only: spending allowed across the mandate lifetime, but * no more than `daily_cap_microcredits` per UTC day. * - total_cap only: a single envelope of credits the agent may consume, * no further limits — useful for one-shot delegations. * - both: belt-and-suspenders. * * `max_credits_per_call` is a per-invocation safety net for runaway * single requests. `allowed_models` restricts which Bedrock model ids the * delegate may target — empty/undefined means no model restriction (the * server's own allowlist still applies). */ export interface ComputeConstraints { /** Hard cap on credits debited per UTC day under this mandate. */ daily_cap_microcredits?: number; /** Hard cap on credits debited over the whole mandate lifetime. */ total_cap_microcredits?: number; /** Hard cap on credits debited by any single invocation. */ max_credits_per_call?: number; /** Allowlist of Bedrock model ids the delegate may invoke. */ allowed_models?: string[]; } export interface MandateConstraints { domains?: string[]; rate_limit?: Record; require_counter_sign?: string[]; /** Required when `scopes` contains `compute.invoke`. */ compute?: ComputeConstraints; } /** * Narrows a mandate's `ethos.read/write.` scopes to a SUBSET of the * zone's sections (companion draft `bundle-v0.3-section-level-mandates.md`). * Absent ⇒ the whole zone (back-compat). A section matches iff its id is in * `ids` OR it carries a tag in `tags`. */ export interface SectionScope { ids?: string[]; tags?: string[]; } /** True iff `section` matches `scope` (§4.7.1′). Absent scope ⇒ whole zone. */ export declare function sectionMatchesScope(section: { id: string; tags?: string[]; }, scope?: SectionScope): boolean; /** * Mandate envelope versions understood by this library. * * `0.1.0` — pre-delegate-E2E format shipped in v0.1.x / v0.2.0. * `0.2.1` — adds forbidden-scope enforcement (mandate.issue, mandate.revoke, * identity.rotate-keys, identity.destroy) and the explicit * `ethos.read.{public,circle,self}` scope family. * `0.3.0` — introduces the `gamma.read` scope. Decouples gamma read access * from `ethos.write.*`: possession of a write scope no longer * implies visibility of the gamma log. A mandate carrying * `gamma.read` adds its grantee pubkey to `manifest.gamma.readers`; * future gamma entries seal their per-entry key to that pubkey. * See `spec/drafts/gamma-v0.3-per-entry-envelopes.md`. * `0.4.0` — introduces the `compute.invoke` scope (token-spending capability) * and the matching `constraints.compute` shape (daily / total / * per-call caps + model allowlist). The scope MUST NOT be granted * implicitly: it is its own family, never a side-effect of any * ethos / gamma scope. A mandate carrying `compute.invoke` * without at least one cap in `constraints.compute` is rejected * at mint AND at verify time. See `validateComputeAuthorization`. * `0.5.0` — introduces the ethos verb-scope grammar (draft * `bundle-v0.3-section-verb-scopes.md`): per-scope section selectors * (`ethos..#id=…|#prefix=…|#tag=…`) and the verbs * `edit` / `append` / `delete` alongside `read` / `write`. A single * mandate can now carry distinct read vs write perimeters. Purely * additive — a bare `ethos.read/write.` keeps its meaning and * every pre-0.5.0 envelope still verifies. See `./ethos-authz.ts`. * * New mandates are minted at the latest version; the verifier accepts all * past envelopes for backward compatibility. */ export declare const MANDATE_VERSION_CURRENT: "0.5.0"; export type MandateVersion = "0.1.0" | "0.2.1" | "0.3.0" | "0.4.0" | "0.5.0"; /** * The single scope that authorizes a delegate to spend the subject's * compute credits via the Aithos compute proxy. Designed as an opt-in, * stand-alone capability — never implied by ethos/gamma scopes, and * never granted without a matching `constraints.compute` budget. */ export declare const COMPUTE_INVOKE_SCOPE: "compute.invoke"; export interface Mandate { "aithos-mandate": MandateVersion; id: string; issuer: string; issued_by_key: string; grantee: Grantee; actor_sphere: Sphere; scopes: string[]; constraints?: MandateConstraints; /** Narrows read/write scopes to a subset of the zone's sections (§4.7′). */ section_scope?: SectionScope; not_before: string; not_after: string; issued_at: string; nonce: string; signature: { alg: "ed25519"; key: string; value: string; }; } /** * Kind of mandate this revocation targets. * * Default `"action"` corresponds to standard mandates (§4). `"sponsorship-mandate"` * targets sponsorship mandates from draft §13. Verifiers that pre-date the * field MUST treat its absence as `"action"` (back-compat). */ export type RevocationMandateKind = "action" | "sponsorship-mandate"; export interface Revocation { "aithos-revocation": "0.1.0"; mandate_id: string; /** Draft §13.9 — disambiguates which signed object the revocation targets. */ mandate_kind?: RevocationMandateKind; issuer: string; issued_by_key: string; revoked_at: string; reason: string; signature: { alg: "ed25519"; key: string; value: string; }; } export interface ActionArtifact { "aithos-action": "0.1.0"; id: string; mandate_id: string; issued_at: string; actor: { id: string; pubkey: string; }; action: { verb: string; target: Record; content_hash: string; summary: string; }; signature: { alg: "ed25519"; key: string; value: string; }; counter_signature: { alg: "ed25519"; key: string; value: string; } | null; } /** * Parse a duration string into seconds. Accepts `s`, `m`, `h`, `d`, * `w`. No composite forms ("1d12h") in v0.1.0. */ export declare function parseTtl(s: string): number; export interface CreateMandateArgs { issuer: Identity; actorSphere: Sphere; grantee: Grantee; scopes: string[]; ttlSeconds: number; constraints?: MandateConstraints; /** Narrow the read/write scopes to a subset of sections (§4.7′). */ sectionScope?: SectionScope; notBefore?: Date; } export declare function createMandate(args: CreateMandateArgs): Mandate; /** * Scopes a mandate may NEVER carry, regardless of signing sphere. * * These cover operations whose cryptographic root must remain with the * subject: issuing further mandates, revoking them, rotating identity keys, * and destroying the identity. Even the `self` sphere (which is otherwise a * superset) cannot delegate them — a delegate that could issue new mandates * would be indistinguishable from the subject. */ export declare const FORBIDDEN_SCOPES: ReadonlySet; /** Identify whether any of the given scopes is a write scope. */ export declare function hasWriteScope(scopes: string[]): boolean; /** * Whether `s` is a well-formed append-only data scope: `data..append`. * * `append` is a **lateral** capability, deliberately NOT part of the * `read ⊂ write ⊂ admin` hierarchy (mirrors `gamma.write`). It authorizes * `insert_record` ONLY — never read, update, or delete — and is the protocol * counterpart of the cryptographic "seal DEK to owner pubkey" deposit * (`@aithos/data-crypto` `encryptRecordForRecipient`). Because it grants no * read, a holder cannot decrypt anything in the collection, not even its own * deposit. Collection names MUST NOT contain ".". */ export declare function isDataAppendScope(s: string): boolean; /** Whether the scope set carries any `data..append` scope. */ export declare function hasDataAppendScope(scopes: string[]): boolean; /** * Identify whether the scope set grants gamma read access. * * In v0.3, gamma read is explicit — `ethos.write.*` no longer implies it. * A delegate with a write scope alone can append gamma entries (via per-entry * envelope seal to `manifest.gamma.readers`, no decryption needed) but cannot * read past history. */ export declare function hasGammaReadScope(scopes: string[]): boolean; /** * Identify whether the scope set grants compute (token-spending) authority. * * In v0.4, `compute.invoke` is its own opt-in capability: it is NEVER * implied by ethos / gamma scopes. A subject who issues a read-only * mandate can rest assured no token spend is authorized. The compute * proxy MUST refuse any compute invocation under a mandate that does * not carry this scope. */ export declare function hasComputeInvokeScope(scopes: string[]): boolean; /** * Enforce the compute-authorization invariant: a mandate carrying * `compute.invoke` MUST also carry `constraints.compute` with at least * one cap (`daily_cap_microcredits` or `total_cap_microcredits`). * * Rationale: the whole point of the v0.4 split is that token spending * is opt-in AND bounded. An unbounded compute mandate would be a bearer * token to drain the subject's wallet — exactly what we want to make * impossible at the protocol level, not just by server-side policy. * * Conversely, `constraints.compute` without the scope is a no-op * (probably a caller mistake) and is also rejected: the subject's * intent should be unambiguous. * * All numeric caps, when present, must be positive integers — zero * would silently disable spending without making it explicit, and * non-integers would mis-debit the wallet at the per-microcredit * granularity. * * Throws `Error` on violation; returns `void` on success. */ export declare function validateComputeAuthorization(scopes: string[], constraints?: MandateConstraints): void; export interface VerifyResult { ok: boolean; errors: string[]; } /** * Default clock-skew tolerance for mandate time-window checks (seconds). * * Real-world clocks drift. A client signs `not_before = now()` and the * server validates a few hundred milliseconds later — but if the server's * clock runs behind the client by even 100ms, a strict comparison * (`server_now < not_before`) rejects the mandate as "not yet valid". * * 30s matches common JWT practice (`leeway` / `clock_tolerance` in jose, * jsonwebtoken, oidc-client, etc.). It's wide enough to absorb routine * NTP jitter and Lambda cold-start clocks without weakening the * security model meaningfully — a 30s window doesn't help an attacker * who already has a valid mandate, and a stolen mandate is much more * dangerous than a 30s anticipated activation. */ export declare const MANDATE_CLOCK_SKEW_SECONDS_DEFAULT = 30; /** * Verify a mandate's structure, signature, and time window against a DID document. * Does NOT consult a revocation list — caller provides that separately. * * Time-window checks are bounded by `clockSkewSeconds` (default * {@link MANDATE_CLOCK_SKEW_SECONDS_DEFAULT}) on both ends: * - mandate is "not yet valid" only if `now < not_before - skew` * - mandate is "expired" only if `now >= not_after + skew` * Pass `clockSkewSeconds: 0` for the legacy strict behaviour. */ export declare function verifyMandate(mandate: Mandate, didDoc: DidDocument, now?: Date, options?: { clockSkewSeconds?: number; }): VerifyResult; export interface RevokeMandateArgs { issuer: Identity; mandate: Mandate; reason: string; revokedAt?: Date; /** * Draft §13.9 — kind of mandate this revocation targets. Defaults to * `"action"` for back-compat; pass `"sponsorship-mandate"` when revoking * a `SponsorshipMandate`. */ mandateKind?: RevocationMandateKind; } export declare function createRevocation(args: RevokeMandateArgs): Revocation; export declare function verifyRevocation(rev: Revocation, didDoc: DidDocument): VerifyResult; export interface SignActionArgs { mandate: Mandate; agentSeed: Uint8Array; agentId: string; verb: string; target: Record; contentBytes: Uint8Array; summary: string; issuedAt?: Date; } export declare function signActionArtifact(args: SignActionArgs): ActionArtifact; /** * Attach a counter-signature from the subject's mandated sphere key. Required * when the action's verb appears in mandate.constraints.require_counter_sign. */ export declare function counterSignAction(artifact: ActionArtifact, subject: Identity, mandate: Mandate): ActionArtifact; export declare function verifyActionArtifact(artifact: ActionArtifact, mandate: Mandate, didDoc: DidDocument): VerifyResult;