import type { PublishAuditCaller, PublishAuditScope, PublishAuditScopeKind } from "./publish-audit.js"; /** * Consent + scope-token primitives for `scai content publish`. * * The safety model has three layers (see docs/parity-with-devex.md): * * 1. CLI default = `--what-if`. Real call requires `--allow-write`. * 2. Production-tier envs require a two-step typed scope token * (dry-run → token → real call with token). * 3. Library layer requires a structured `PublishConsent` argument; * an agent can't synthesize one without going through CLI prompt, * MCP host approval, or a CI gate. * * The scope token is the operator-visible artifact that bridges steps * 2 and 3. It encodes the resolved scope (envName + tenant + items + * languages + target), is bound to a TTL, and is **self-contained** — * no server-side cache needed for verification. Threat model: scope * tokens prevent fat-finger errors and give operators visibility into * what's about to publish; they don't (and can't) defend against a * malicious process running as the operator. That defense lives at * the MCP `allowWrite` gate and the operator's CI-secret hygiene. */ export declare const SCOPE_TOKEN_TTL_MS: number; export interface PublishConsent { confirmedBy: PublishAuditCaller; scope: PublishAuditScope; scopeHash: string; issuedAt: string; ttlMs: number; } /** * Stable hash over the resolved publish scope. Identical scopes * produce identical hashes regardless of input ordering, so a dry-run * and a subsequent real call with the same arguments resolve to the * same hash and the scope token verifies. */ export declare const computeScopeHash: (scope: PublishAuditScope) => string; interface ScopeTokenPayload { v: 1; /** Scope kind — see `PublishAuditScopeKind` in audit.ts. Existing * publish-item / publish-all tokens encode "item" / "full"; the new * unpublish / content version verbs add their own kinds so a token * minted for one verb can't be replayed against another. */ k: PublishAuditScopeKind; /** envName, kept short. */ e: string; /** scopeHash (32 hex chars). */ h: string; /** issuedAt epoch ms. */ t: number; /** ttl in ms. */ ttl: number; } /** * Mint a scope token for the resolved scope. Returns a short string * the operator copies from dry-run output to the real-call command. * * Format: `pub_`. The payload is self-describing * and self-verifying: TTL + scopeHash mean a token can only validate * against an identical scope within its lifetime window. */ export declare const mintScopeToken: (scope: PublishAuditScope, now?: number) => string; export type ScopeTokenVerificationFailure = { ok: false; reason: "malformed"; } | { ok: false; reason: "expired"; ageMs: number; } | { ok: false; reason: "env-mismatch"; expectedEnv: string; tokenEnv: string; } | { ok: false; reason: "kind-mismatch"; expectedKind: string; tokenKind: string; } | { ok: false; reason: "scope-mismatch"; expectedHash: string; tokenHash: string; }; export type ScopeTokenVerification = { ok: true; payload: ScopeTokenPayload; } | ScopeTokenVerificationFailure; /** * Verify a scope token against the operator's current request. The * token must: * - parse and have v=1 * - not be expired (issued within ttl of now) * - name the same env as the request * - name the same kind (item vs full) * - encode a scopeHash that matches the request's recomputed hash * * Any mismatch is surfaced with a specific `reason` so the CLI can * tell the operator exactly what changed between the dry-run and the * real call. */ export declare const verifyScopeToken: (token: string, scope: PublishAuditScope, now?: number) => ScopeTokenVerification; export {};