/** * Pack — the contract every domain Pack satisfies. * * A Pack is the unit of composability in the adjudicate platform: an * installable npm package that brings a domain (payments-pix, ordering, * billing) into an adopter's application. The Pack exposes: * * - the intent kinds it handles * - the PolicyBundle that adjudicates them * - the CapabilityPlanner that decides which tools the LLM may see * - the basis-code vocabulary the policy may emit (refusal taxonomy) * - optional handlers that execute side effects after kernel returns EXECUTE * * `PackV0` is the v0.x contract — implicit-and-observed during Phases 1–3. * After Phase 3's two more Packs validate the shape, `PackV1` extracts and * the `-experimental` semver tag drops on all qualifying Packs. * * Conformance pattern (preferred): * * ```ts * import type { PackV0 } from "@adjudicate/core"; * * export const myPack = { * id: "pack-foo", * version: "0.1.0-experimental", * contract: "v0", * intents: ["foo.create", "foo.cancel"], * policy: fooPolicyBundle, * planner: fooCapabilityPlanner, * basisCodes: ["foo.created", "foo.cancelled"], * } as const satisfies PackV0<"foo.create" | "foo.cancel">; * ``` * * The `satisfies` operator gives compile-time conformance without widening * the literal types — `myPack.intents` stays typed as the literal tuple. */ import type { PolicyBundle } from "./kernel/policy.js"; import type { CapabilityPlanner } from "./llm/planner.js"; import type { SideEffectClass } from "./side-effects.js"; import type { ExecutorContract } from "./pack-output-contract.js"; export interface PackV0 { /** * Stable identifier for this Pack. Conventionally matches the npm * package name's last segment (`@adjudicate/pack-payments-pix` → * `"pack-payments-pix"`). Referenced in audit records and the (future) * Phase 6 governance dashboard. */ readonly id: string; /** Pack semver. MUST match the `version` field in `package.json`. */ readonly version: string; /** * Pack contract version. Always `"v0"` for PackV0. * * When the contract evolves (Phase 3 surfaces a breaking change in the * shape), `PackV1` ships and Packs upgrade their `contract` field. Lets * adjudicate-side tooling detect contract-version mismatches at install * time and refuse to load incompatible Packs. */ readonly contract: "v0"; /** * Intent kinds this Pack handles. Must be a non-empty list of unique * strings. The PolicyBundle and (optional) handlers below are typed * against this kind union. */ readonly intents: ReadonlyArray; /** * PolicyBundle that adjudicates the Pack's intents — the core authority * for what's allowed when. Per the kernel's evaluation order: * `state → taint → auth → business` (ADR-104; the T8 reorder placed taint * ahead of auth so UNTRUSTED inputs short-circuit before any auth-guard * side effect runs). */ readonly policy: PolicyBundle; /** * CapabilityPlanner that decides which tools and intent kinds the LLM * sees per (state, context). Security-sensitive — adopters MUST unit-test * this at byte level. */ readonly planner: CapabilityPlanner; /** * Basis codes the Pack's policy may emit. Declares the Pack's refusal * taxonomy. Phase 6's AaC review verifies that every basis emitted at * runtime is in this list — drift indicates either a missed declaration * or unauthorized vocabulary. */ readonly basisCodes: ReadonlyArray; /** * Optional: side-effect handlers keyed by intent kind. Executed by the * adopter after `adjudicate()` returns EXECUTE. * * Phase 1 keeps handlers as plain functions. Phase 2's `@adjudicate/tools` * introduces `ToolDefinition` (versioned, schema-defined, signed) — * Packs migrate handler signatures onto that contract without changing * PackV0. */ readonly handlers?: Readonly>>; /** * Optional: DEFER signal vocabulary (T4). When declared, every DEFER * Decision emitted by the Pack's policy must carry a `signal` from * this list — `withBasisAudit` records `basis_code_drift` for unknown * signals. Cross-pack signal collision (issue #38) can be detected at * boot if two packs declare overlapping signals; the framework leaves * that detection to a future Phase-2 registry. * * Adopters publishing to a shared NATS topic typically prefix their * signals with the pack id (e.g., `"pack-payments-pix:payment.confirmed"`) * to avoid collisions. The lighthouse Pack's signal is documented as * `"payment.confirmed"` per ADR-002 of pack-payments-pix. */ readonly signals?: ReadonlyArray; /** * Optional: reconstitute the Pack's runtime `State` from a serializable * representation (typically `JSON.parse` output). Tools that source state * from JSON fixtures (`adjudicate simulate` scenario files, future * Console scenario builder, audit-replay payload restoration) call this * before passing state to the kernel. * * Required for Packs whose state contains shapes that don't survive * `JSON.stringify` round-tripping — `Map`, `Set`, `Date`, typed arrays. * Omitting it is correct for Packs whose state is already plain JSON * (records, arrays, primitives). * * Convention: be permissive on input. Treat absent/malformed fields as * empty containers, and treat already-rehydrated inputs (state passed * directly from production) as a pass-through. The policy's guards are * the authoritative validators of the rehydrated state. */ readonly rehydrateState?: (raw: unknown) => State; /** * Optional: declared side-effect class per intent kind. Pure registry * metadata — NOT on the hashed envelope and NOT pinned by ConfigSeal (which * seals only `{id,version,contract,intents,signals,basisCodes, * policyStructure,taintMinimums}`), exactly like `handlers?`/`signals?`. * * Consumed by the Layer-2 `createSideEffectTaintFloor` guard as its blanket * taint-floor map, and by tooling/analysis. Kinds absent from the map fall to * the guard's `defaultClass` (which fails CLOSED). Declaring this does not by * itself enforce anything — install the guard to act on it. */ readonly sideEffects?: Readonly>>; /** * Optional: per-kind executor output contract. Pure registry metadata — NOT * on the hashed envelope and NOT pinned by ConfigSeal, like `handlers?`. * * When supplied, the adapter validates the executor's return value AFTER * `invokeIntent` and emits an `executor_contract_violation` observation event * on a structural mismatch. EXECUTE is never flipped — the contract is an * observation layer over a side effect that already ran, not a guard. */ readonly executorContract?: Readonly>>; } /** * Side-effect handler for an intent kind. * * Receives the (possibly REWRITTEN) payload and current state; returns * whatever the side effect produces. The kernel never calls these directly * — they're invoked by the adopter's executor when adjudicate() returns * EXECUTE. * * Intentionally loose in v0; Phase 2's ToolDefinition tightens the * input/output contract via Zod schemas. */ export type PackHandler = (payload: Payload, state: State) => Promise; //# sourceMappingURL=pack.d.ts.map