/** * The validated-model-output envelope — the ONE discipline shared by every AI * cast projection target (GraphPatch proposals AND genui GeneratedUITree * proposals). * * THE LOAD-BEARING SECURITY PROPERTY (encoded in the type SHAPE, not just docs): * a model's raw output is NEVER directly actionable. The only way to obtain a * {@link ValidatedProposal} is through {@link mintValidated}, which this module * keeps PRIVATE to validator call-sites: the envelope carries a branded * {@link ApplyToken} that no caller can construct (the brand's witness is a * unique symbol this module never exports). Therefore "unvalidated model output * → mutation" is UNREPRESENTABLE — a host's `apply` step demands a * `ValidatedProposal`, and a `ValidatedProposal` can only exist downstream of a * passing validation. * * This is the framework PRIMITIVE half of "AI": LiteShip teaches graphs how to * speak to models; products decide whether model suggestions become action. The * envelope is the seam between the two — it is what a validator MINTS and what a * host's admission layer INSPECTS, but the framework never auto-applies it. * * The envelope is a pure VALUE: it content-addresses (the same fnv1a∘CanonicalCbor * kernel as every other cast) and carries a receipt subject id, so a validated * proposal is replayable and citable without re-running the model. * * @module */ import type { ContentAddress } from './brands.js'; import type { ReceiptSubject } from './receipt.js'; /** * The unforgeable witness for {@link ApplyToken}. A REAL, module-private symbol * (never exported) keyed into the token — it is what makes the apply token * un-constructable outside the validators in this module. Because the symbol * itself never escapes the module, no code elsewhere can produce an object with * this key, so no code elsewhere can fabricate a `ValidatedProposal`. (A real * value, not a `declare const` phantom — the key has to exist at runtime to brand * the token, while staying invisible to the rest of the program.) */ declare const ApplyTokenWitness: unique symbol; /** * A validation-minted, host-authorized apply token. Branded with a private * witness so it is impossible to construct except inside {@link mintValidated}. * Its value is the content address of the validated payload — so the token both * (a) proves validation happened and (b) binds to the EXACT payload validated * (a host cannot swap the payload after the token is minted without invalidating * the address match; see {@link assertTokenBinds}). */ export interface ApplyToken { readonly [ApplyTokenWitness]: true; /** Content address of the validated payload — the token is bound to THIS payload. */ readonly subject: ContentAddress; /** The projection target the proposal was validated against (diagnostic + routing). */ readonly target: ProposalTarget; } /** The closed set of projection targets a validated proposal can carry. */ export type ProposalTarget = 'graph-patch' | 'generated-ui'; /** * A model proposal that has PASSED validation — the only artifact a host's * admission layer is allowed to act on. The `token` is the load-bearing field: * it cannot be forged (its witness type is private), and it binds to `payload` * by content address. `subject` is the receipt subject id (== `token.subject`), * surfaced for citation/caching without reaching into the branded token. * * There is NO public constructor for this type. The framework exposes * `apply`-style host steps that CONSUME it, but never a path that produces one * from raw model output bypassing validation. */ export interface ValidatedProposal { readonly _tag: 'ValidatedProposal'; readonly _version: 1; readonly target: ProposalTarget; /** The validated payload (a GraphPatch, a GeneratedUINode, …). */ readonly payload: T; /** Content address of the payload — the proposal's stable identity / receipt subject. */ readonly subject: ContentAddress; /** The unforgeable, validation-minted apply authorization. */ readonly token: ApplyToken; } /** * Mint a {@link ValidatedProposal}. PRIVATE to validators (not re-exported from * the package index): this is the single mint site for the apply token, exactly * as `sealGraph`/`makeEntityId` are the single mint sites for their addresses. * Validators call this ONLY after their check passes, so the existence of a * `ValidatedProposal` is proof a passing validation ran. */ export declare function mintValidated(target: ProposalTarget, payload: T): ValidatedProposal; /** * Host-side guard: re-derive the payload's content address and assert it matches * the token's bound subject. A host's admission layer calls this immediately * before applying — it catches any attempt to swap the payload after minting * (the token binds to the bytes that were validated, not merely to "some * validation happened"). Returns the payload narrowed when bound; throws on * mismatch. * * This is defense-in-depth ON TOP of the unforgeable token: even a correctly * minted token cannot be paired with a different payload at apply time. * * It enforces, at runtime, the same three properties the type encodes: * 1. PROVENANCE — the token carries the module-private witness, so it was minted * by {@link mintValidated} (a runtime brand check that backs the type-level * guarantee against a structurally-shaped but un-minted impostor token). * 2. TARGET CONSISTENCY — `token.target === proposal.target` (no target/token * divergence routing a payload through the wrong validator's authority). * 3. PAYLOAD BINDING — the re-derived content address matches the token subject * (no post-validation payload swap). */ export declare function assertTokenBinds(proposal: ValidatedProposal): T; /** * The content address (== receipt subject id) of a validated proposal. Exposed * so a host can cite/cache a proposal by identity without touching the branded * token. */ export declare function proposalSubject(proposal: ValidatedProposal): ContentAddress; /** * RESOLVED (open question #1 — the generated-UI apply seam). The graph-patch * target has a host-authorized framework step (`AICast.applyValidatedPatch`): the * framework owns the re-addressing kernel, so it exposes (never invokes) apply. * The generated-UI target has NO such framework step — rendering belongs to the * host's renderer, and core stays renderer-FREE (the product boundary). So the * seam is an `unwrapValidated` ACCESSOR, not a framework-calls-renderer path: * the framework hands back the validated payload + asserts the token still binds; * the host then calls its OWN renderer with the returned tree. * * This is the SAME binding guard `AICast.applyValidatedPatch` runs before it * mutates — defense-in-depth against a post-validation payload swap — generalized * to ANY target. Concretely: `unwrapValidated` is `assertTokenBinds` named for * the host's intent (its return value is what you feed your renderer/applier), * so there is exactly one un-bypassable door for BOTH targets and the framework * never reaches into a renderer it does not own. */ export declare function unwrapValidated(proposal: ValidatedProposal): T; /** * RESOLVED (open question #7 — receipt integration). A full {@link * ReceiptEnvelope} is async (it hashes via `crypto.subtle`/SHA-256 inside an * `Effect`, exactly like `GraphPatch.receipt`). Folding that into the validators * would force `validateGraphPatchProposal`/`validateGeneratedUIProposal` async and * pull the whole cast-IN path into Effect — a scope balloon for no extra safety, * since the envelope's unforgeability already lives in the apply token, not a * receipt. * * Instead we wire the SMALL, real, SYNCHRONOUS integration that composes cleanly: * a `ValidatedProposal` already carries a content-address `subject` (the fnv1a∘ * CanonicalCbor identity — the same kernel `GraphPatch.receipt` subject-keys its * envelope on via `{ type: 'artifact', id }`). This derives the EXACT * {@link ReceiptSubject} a host would mint a receipt against, so a host can chain * the proposal into its receipt DAG WITHOUT re-running the model and without core * taking on the async hashing path. The full `ReceiptEnvelope` mint stays a * host-side step (the host owns timestamps/`previous`/chain authority — the * product boundary), seeded by this subject. That is the next step, not a stub: * the citable identity is real and pinned (see the content-address-subject law in * the capsule + unit tests). */ export declare function proposalReceiptSubject(proposal: ValidatedProposal): ReceiptSubject; export {}; //# sourceMappingURL=validated-output.d.ts.map