# Kovo — Technical Specification

**Version:** 0.2 (Draft)
**Status:** Normative specification for v1, with staged roadmap through v3
**Audience:** Framework implementers and AI app-builder integrators

---

## 1. Vision

Kovo is a web-platform-native framework for building multi-page applications that **never show stale or inconsistent UI** and are **interactive at first paint with minimal JS and CSS** — achieved by making the whole system legible at every layer and statically verifiable end-to-end.

It composes ideas from prior systems (Qwik, htmx/LiveView, RTK Query, Replicache; full prior-art table in the README) around one organizing constraint: _every artifact the system produces (compiled output, HTML, wire traffic, dependency graphs) must be readable by a human in devtools and checkable by a machine without executing a browser._

### 1.1 Primary goals

Kovo exists to deliver three outcomes, in priority order, all produced by one technique — machine-auditable generation (§1.3). Every other property of the framework — legibility, static verifiability, the auditable wire — is a _means_ to these ends.

1. **Secure by construction.** Whole vulnerability classes — cross-site scripting, SQL injection, broken access control and IDOR, confidential-data exposure, mass assignment, SSRF, request forgery, lost-update races — are not runtime hazards to test for but build/check errors that never ship, or fail-closed runtime floors where static proof is impossible. Kovo makes the insecure pattern _inexpressible_ wherever the same static analysis that proves data freshness can prove it, and forces the residue into declared, audited, suppressible-in-source decisions visible to `kovo explain`. The distinctive claim is not "secure" — every framework says that — but **secure by the same machine-auditable construction that eliminates stale UI**: one substrate, checked without a browser.
2. **Eliminate stale-UI bugs at compile time.** Inconsistent UI states — a badge that disagrees with the cart, a list that didn't reflect its own mutation, two views of one fact drifting apart — are not runtime races to debug but build/check errors that never ship. Kovo makes the staleness it can statically model (§1.2) a `tsc`/check failure, and forces the residue it cannot prove into declared, suppressible-in-source decisions.
3. **Make loading instant.** First paint is interactive, and the bytes to get there are minimal: little-to-no JavaScript on the critical path (global delegation + `import()` on first interaction, not hydration), compiler-scoped CSS with no runtime style engine, and named incremental wire deltas in prod. Performance is a budget the compiler enforces, not a guideline.

### 1.2 Thesis statement

> An application's complete behavior — every handler wiring, navigation target, form field, mutation contract, data dependency, and optimistic prediction — should be provable by TypeScript static checking plus static graph queries, and auditable by reading the page source and the Network panel.

For v1 data freshness, that proof covers staleness caused by this client's own
statically analyzable, modeled writes. Kovo turns those stale-UI paths into build
or check errors, and turns freshness gaps it cannot statically prove — raw-SQL
seams, database-engine side effects, the wall clock — into declared, checked,
suppressible-in-source decisions. Cross-session liveness is an explicit
out-of-guarantee boundary for v1 and belongs to the opt-in live tier (§9.3), not
to the core mutation proof.

### 1.3 Design driver: machine-auditable generation

Kovo is built to be the most machine-auditable compilation target a code-generation agent can emit: generated apps fail TypeScript static checking if wiring is wrong, and intent is verifiable against printed dependency graphs without headless browsers. Where a design choice trades author convenience for machine-checkability, machine-checkability wins. The corollary holds for every reader, not just agents: debugging always proceeds _down_ into plainer code, never _up_ into compiler internals. Machine-auditable generation is the chief _technique_ by which the three primary goals (§1.1) are reached: the same static analysis that lets an agent's output be checked without a browser is what makes whole vulnerability classes inexpressible (the Prime Principle, §2), turns stale-UI paths into build errors, and lets the compiler hold the byte budget.

### 1.4 Explicit non-goals

- **Figma-class shared-workspace apps.** Long-lived client sessions over one mutable heap (collaborative canvases, video editors, DAWs) are outside the sweet spot. Kovo islands can host rich widgets, but the framework will not grow a client router or global client store to serve this segment.
- **Offline-first.** Server truth is unconditionally authoritative; Kovo does not ship a sync engine.
- **App-authored persistent navigation state** in v1. Enhanced navigation may preserve unchanged compiler-stamped layout DOM when JS is present (§8), but the canonical behavior is still real URLs and server-rendered documents.
- **Browser support parity for enhancements.** Speculation Rules and invoker commands are Chromium-led; Kovo degrades gracefully (real navigations, real forms) but does not polyfill them.
- **A sanctioned JSON/REST public API in v1.** Typed public APIs need their own token-auth and schema-reuse story. Until that exists, ad-hoc JSON APIs live only behind declared `endpoint()` entries (§9.1), where their auth and CSRF posture stay visible to audits; `respond.json()` is not a route outcome.

---

## 2. The Constitution (Design Tests)

The framework's overriding commitment is the **Prime Principle**, which precedes and is served by every test below:

> **Security is by construction.** A feature crossing a trust boundary — data coming _in_, data going _out_, _who_ may act, _how much_ — makes the unsafe state inexpressible at compile time wherever static analysis can prove it (over AST symbol-identity provenance, never a branded type or runtime taint, both unsound here; §6.6), falls back to a fail-closed runtime floor where it cannot, and routes every exception through an audited escape hatch surfaced in `kovo explain`. **Default-deny over default-allow; advanced TypeScript types make the safe path explicit wherever they naturally encode the contract, but are defense-in-depth, not the mechanism; runtime floors are labeled as floors, never sold as proofs.** This turns XSS, SQL injection, IDOR/broken access control, confidential-data exposure, mass assignment, SSRF, and lost-update races into build errors or fail-closed floors — checkable without a browser, by the same machine-auditable generation (§1.3) that eliminates stale UI. It is the first primary goal (§1.1) and the lead gate here precisely because it is the highest-stakes property _and_ is delivered by the legibility, declare-once, and static-auditability the tests below enforce.

Every feature proposal is then evaluated against five design tests. A feature failing the Prime Principle or any test is redesigned or rejected. These are normative. The objectives are the three primary goals (§1.1); the tests are how those goals — security included — are kept honest under pressure.

| #   | Test                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    | Consequence                                                                                                                                                                                                                                                                        |
| --- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 1   | **Legibility is load-bearing.** Names appear in HTML attributes and wire traffic, so they structurally cannot be mangled.                                                                                                                                                                                                                                                                                                                                                                                                                               | Minifiers cannot rename handler exports; debugging never requires decompiling the framework.                                                                                                                                                                                       |
| 2   | **No global knowledge at local sites.** Any API requiring the author to enumerate distant call sites from memory is a bug factory and is rejected.                                                                                                                                                                                                                                                                                                                                                                                                      | Killed manual fragment targets, manual per-island optimism, query-side mutation registration, call-site mass-assignment allowlists, and per-handler authorization — security facts (`secret`/`owner`/`governed`/`access`) are declared once and derived everywhere.                |
| 3   | **Sugar must lower to authorable IR.** Every compiler feature emits valid Kovo source. Compiling the output is a no-op (CI-enforced fixpoint).                                                                                                                                                                                                                                                                                                                                                                                                          | Output is auditable in devtools and mechanically checked; app authors still write TSX.                                                                                                                                                                                             |
| 4   | **The wire is the documentation.** Named POSTs and schema-shaped JSON in every environment; full self-describing HTML fragments in dev; size-optimized but still named/schema-shaped deltas against a version-validated base in prod (§9.1.1). The wire documents what the server **chose to send**, not all it knows: a `secret`-classified field is ineligible to reach the client wire or a client module, so legibility and confidentiality coexist by construction — the dual of output-safety (§5.2 rule 10, integrity), now for confidentiality. | A dev frame is a complete document auditable from the Network panel; a prod frame shows _what changed_, reconstructable via `kovo explain`. Names are never mangled in either mode (#1). A typed `secret` boundary keeps the readable wire from becoming an over-exposure channel. |
| 5   | **Server truth always wins.** No client cache to invalidate; reconciliation is "morph the authority in."                                                                                                                                                                                                                                                                                                                                                                                                                                                | Optimistic predictions are disposable; there is no consistency protocol.                                                                                                                                                                                                           |

---

## 3. Architecture Overview

```
                        AUTHORING                    COMPILED IR                  RUNTIME
┌──────────────────┐   ┌──────────────────────────┐   ┌──────────────────────────────────┐
│  cart.tsx        │   │ cart.server.js           │   │ Self-describing HTML             │
│  (JSX, inline    │──▶│   render fns, queries    │──▶│  • plain elements, kovo-c stamps   │
│   closures,      │   │ cart.client.js           │   │  • on:click="cart.js#Cart$remove"│
│   single file)   │   │   named handler exports, │   │  • <script kovo-query="cart"> JSON │
│                  │   │   derives, transforms    │   │  • kovo-deps="cart" stamps         │
└──────────────────┘   └──────────────────────────┘   └──────────────────────────────────┘
        │                        │                                  │
        │ fixpoint:              │ 1:1 file mapping,                │ budgeted bootstrap: global event
        │ compile(IR) ≡ IR       │ source-derived names             │ delegation + import() on
        ▼                        ▼                                  ▼ first interaction
┌─────────────────────────────────────────────────────────────────────────────────────────┐
│ MPA SPINE: real URLs + server documents + optional enhanced navigation over the full-doc │
│ oracle + Speculation Rules + cross-document View Transitions + bfcache. No client router.│
├─────────────────────────────────────────────────────────────────────────────────────────┤
│ DATA PLANE: queries (typed reads) ← invalidation graph → mutations (typed writes)        │
│ derived from domain layer / Drizzle AST. Optimistic transforms may be hand-written        │
│ or compiler-derived.                                                                     │
├─────────────────────────────────────────────────────────────────────────────────────────┤
│ WIRE: one fragment/query-JSON vocabulary, transport-agnostic:                            │
│ document load · enhanced fetch (mutations) · SSE live queries                            │
└─────────────────────────────────────────────────────────────────────────────────────────┘
```

### 3.1 Rejected from prior art

Client-owned routers and SPA navigation state; hydration; hash-named heuristic chunks; load-bearing semantic optimizer; single global state blob; **runtime signal graphs in the core client — proprietary or TC39** (the client dependency graph is compile-time-known, so the compiler emits a per-query update plan instead; Signals interop is outside the core client); opaque closure capture (`useLexicalScope`); client-side cache with invalidation lifecycle; manual invalidation calls as the primary mechanism; **shadow DOM** (tree-scoped IDREFs, form participation, and ARIA all break at the boundary — fatal to L0 platform behaviors and the no-JS form contract; style scoping comes from the compiler instead, `plans/open-design-areas.md`); **custom-element registration** (resumability comes from delegation + `import()`, never from `customElements.define`; component identity is the `kovo-c` stamp, dashed tags survive as inert sugar, and native hosts like `<tr kovo-c="cart-row">` avoid the table-nesting problem); **load-bearing import maps** (the compiler and server emit full module URLs with cache-busting they control; import maps remain an optional deployment strategy); **portals and runtime context APIs** (composition is lexical at render time and the DOM tree is the runtime context, §4.5 — framework code never reparents islands, so `closest('[kovo-c]')` resolution stays sound; native top-layer promotion (`<dialog>`, popover) does not reparent, which is exactly why no portal is needed). Enhanced navigation (§8) is not a client router: it starts from a real `<a href>`, fetches the canonical server document, and falls back to the browser's full GET on uncertainty.

---

### 3.2 Authority and module split

`SPEC.md` remains Kovo's entry point and highest-level normative authority. The detailed contracts in `spec/*.md` are incorporated by reference and are normative with the same force as text that appears in this file. When a sub-spec and this root disagree, treat that as a specification bug: follow the more specific sub-spec for its owned domain, preserve the Prime Principle in §2, and fix the conflict in the same change that exposes it.

The split is editorial, not semantic. It reduces the root to the material readers need first: vision, constitution, architecture, an index of normative modules, and short local invariants. No framework behavior changes merely because text moved from the root to a numbered module.

`rules/` remains the standing rule layer for agents, releases, conformance, docs, and workflow discipline. `docs/` and `site/content/` remain explanatory unless this spec explicitly delegates a narrow artifact to them. Active ledgers in `plans/` sequence work; they do not override the normative contracts here or in `spec/*.md`.

### 3.3 Normative module index

| Old section(s)    | Normative module                                         | Owns                                                                                                                                                                                  |
| ----------------- | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| §4, §13.1, §13.2  | [spec/04-component-model.md](spec/04-component-model.md) | Component authoring, rendered output, handlers, loader obligations, composition, primitive merging, update plans, dynamic rendering bounds, StyleX/theme tokens, `kovo-key` identity. |
| §5                | [spec/05-compiler.md](spec/05-compiler.md)               | Compiler pipeline, hard rules, representation/render/app-build identities, prod render-equivalence, and `kovo explain`.                                                               |
| §6                | [spec/06-type-system.md](spec/06-type-system.md)         | Generated registries, package component prefixes, typed surfaces, mutation typing, routes/links, sessions, and soundness boundaries.                                                  |
| §7, §8            | [spec/07-navigation.md](spec/07-navigation.md)           | Interaction ladder, MPA spine, enhanced navigation, bfcache posture, speculation rules, view transitions, and streaming/defer behavior.                                               |
| §9                | [spec/09-wire-protocol.md](spec/09-wire-protocol.md)     | Mutation round trips, prod deltas, error envelopes, live/liveness, typed reads, request shell, HMR, durable tasks, and scheduling.                                                    |
| §10               | [spec/10-data-plane.md](spec/10-data-plane.md)           | Schema/domain annotations, queries, access decisions, SQL safety, mutations/writes, optimism, derivation algebra, and exhaustiveness.                                                 |
| §11, except §11.3 | [spec/11-verification.md](spec/11-verification.md)       | Touch-set extraction, runtime verification, and the browser-free verification surface.                                                                                                |
| §11.3             | [spec/11-diagnostics.md](spec/11-diagnostics.md)         | Normative KV### diagnostic registry and generated-reference comparison target.                                                                                                        |
| §12               | [spec/12-testing.md](spec/12-testing.md)                 | Framework testing API and proof-surface test model.                                                                                                                                   |
| §14               | [spec/14-deploy-skew.md](spec/14-deploy-skew.md)         | Deploy skew recovery, app-build-token mismatch handling, and prior-version retention floor.                                                                                           |

SPEC §6.6's finite-transfer claim is accompanied by the
[reviewed analyzable-fragment hand argument](spec/06-analyzable-fragment-hand-argument.md),
which states its compositionality, adequacy, and non-claims without presenting prose as a
mechanized proof.

### 3.4 Compatibility map for section citations

Existing comments, diagnostics, tests, and docs may still cite `SPEC §N.M`. Those citations remain understandable by preserving the old top-level numbers in this root and by keeping the detailed numbered headings inside the linked modules. For example, `SPEC §4.8` means the update-plan contract in `spec/04-component-model.md`; `SPEC §10.3` means the mutations-and-writes contract in `spec/10-data-plane.md`; `SPEC §11.3` means the diagnostic registry in `spec/11-diagnostics.md`. New citations may use either the old number (`SPEC §10.3`) or the file-qualified form (`spec/10-data-plane.md §10.3`) when the file qualification prevents ambiguity.

Detailed moved-subsection map:

| Citation                                                            | Detailed owner                                           |
| ------------------------------------------------------------------- | -------------------------------------------------------- |
| §4.1 Anatomy of a component                                         | [spec/04-component-model.md](spec/04-component-model.md) |
| §4.2 Rendered output                                                | [spec/04-component-model.md](spec/04-component-model.md) |
| §4.3 Handlers and closures                                          | [spec/04-component-model.md](spec/04-component-model.md) |
| §4.4 The loader                                                     | [spec/04-component-model.md](spec/04-component-model.md) |
| §4.5 Composition: children, slots, layouts                          | [spec/04-component-model.md](spec/04-component-model.md) |
| §4.6 Primitive composition and attribute merging                    | [spec/04-component-model.md](spec/04-component-model.md) |
| §4.7 Execution triggers                                             | [spec/04-component-model.md](spec/04-component-model.md) |
| §4.8 The update plan: bindings, derives, stamps                     | [spec/04-component-model.md](spec/04-component-model.md) |
| §4.9 Update coverage                                                | [spec/04-component-model.md](spec/04-component-model.md) |
| §4.10 Registry-bounded dynamic rendering                            | [spec/04-component-model.md](spec/04-component-model.md) |
| §5.1 Pipeline                                                       | [spec/05-compiler.md](spec/05-compiler.md)               |
| §5.2 Hard rules                                                     | [spec/05-compiler.md](spec/05-compiler.md)               |
| §5.2.1 Client representation, render-plan, and app-build identities | [spec/05-compiler.md](spec/05-compiler.md)               |
| §5.2.2 Prod render-equivalence gate                                 | [spec/05-compiler.md](spec/05-compiler.md)               |
| §5.2.3 Build artifact provenance                                    | [spec/05-compiler.md](spec/05-compiler.md)               |
| §5.3 `kovo explain`                                                 | [spec/05-compiler.md](spec/05-compiler.md)               |
| §6.1 The registries                                                 | [spec/06-type-system.md](spec/06-type-system.md)         |
| §6.1.1 Package component prefixes                                   | [spec/06-type-system.md](spec/06-type-system.md)         |
| §6.2 Typed surfaces                                                 | [spec/06-type-system.md](spec/06-type-system.md)         |
| §6.3 Mutation typing contract                                       | [spec/06-type-system.md](spec/06-type-system.md)         |
| §6.4 Routes and links                                               | [spec/06-type-system.md](spec/06-type-system.md)         |
| §6.5 Session schema                                                 | [spec/06-type-system.md](spec/06-type-system.md)         |
| §6.6 Soundness boundary                                             | [spec/06-type-system.md](spec/06-type-system.md)         |
| §7 Interaction ladder                                               | [spec/07-navigation.md](spec/07-navigation.md)           |
| §8 MPA spine and navigation                                         | [spec/07-navigation.md](spec/07-navigation.md)           |
| §9.1 Enhanced mutation round-trip                                   | [spec/09-wire-protocol.md](spec/09-wire-protocol.md)     |
| §9.1.1 Prod delta encoding                                          | [spec/09-wire-protocol.md](spec/09-wire-protocol.md)     |
| §9.2 Errors                                                         | [spec/09-wire-protocol.md](spec/09-wire-protocol.md)     |
| §9.3 Liveness and live                                              | [spec/09-wire-protocol.md](spec/09-wire-protocol.md)     |
| §9.4 Typed reads: the query endpoint                                | [spec/09-wire-protocol.md](spec/09-wire-protocol.md)     |
| §9.5 Request shell                                                  | [spec/09-wire-protocol.md](spec/09-wire-protocol.md)     |
| §9.5.1 Dev HMR                                                      | [spec/09-wire-protocol.md](spec/09-wire-protocol.md)     |
| §9.6 Durable tasks and scheduling                                   | [spec/09-wire-protocol.md](spec/09-wire-protocol.md)     |
| §10.1 Schema as domain registry                                     | [spec/10-data-plane.md](spec/10-data-plane.md)           |
| §10.2 Queries                                                       | [spec/10-data-plane.md](spec/10-data-plane.md)           |
| §10.3 Mutations and writes                                          | [spec/10-data-plane.md](spec/10-data-plane.md)           |
| §10.4 Optimistic updates                                            | [spec/10-data-plane.md](spec/10-data-plane.md)           |
| §10.5 Derivation algebra                                            | [spec/10-data-plane.md](spec/10-data-plane.md)           |
| §10.6 Exhaustiveness                                                | [spec/10-data-plane.md](spec/10-data-plane.md)           |
| §11.1 Touch-set extraction                                          | [spec/11-verification.md](spec/11-verification.md)       |
| §11.2 Runtime verification                                          | [spec/11-verification.md](spec/11-verification.md)       |
| §11.3 Diagnostic codes                                              | [spec/11-diagnostics.md](spec/11-diagnostics.md)         |
| §11.4 Verification surface                                          | [spec/11-verification.md](spec/11-verification.md)       |
| §12 Testing API                                                     | [spec/12-testing.md](spec/12-testing.md)                 |
| §13.1 StyleX and theme tokens                                       | [spec/04-component-model.md](spec/04-component-model.md) |
| §13.2 `kovo-key` runtime identity                                   | [spec/04-component-model.md](spec/04-component-model.md) |
| §14 Deploy skew and version recovery                                | [spec/14-deploy-skew.md](spec/14-deploy-skew.md)         |

### 3.5 Diagnostic and generated-reference ownership

The full diagnostic table lives in [spec/11-diagnostics.md](spec/11-diagnostics.md). The framework-owned `diagnosticDefinitions` registry remains the implementation source used by compiler, CLI, MCP, and generated docs. Generated references must compare framework KV### mentions and registry entries against the normative table; they may not invent codes, severities, or local blocking policy outside that source.

### 3.6 Maintenance rules for spec edits

Use this root for contracts that shape the whole framework: the Prime Principle, the architecture model, module ownership, cross-reference maps, and deployment-wide authority. Put domain-specific normative detail in the owning `spec/*.md` file, and link back here only when the root needs to name the invariant at a high level.

When changing a detailed contract, update the smallest owning module first, then update this root only if the module index, compatibility map, or root summary would otherwise mislead a reader. When adding a new KV### code, update the shared `diagnosticDefinitions` registry and [spec/11-diagnostics.md](spec/11-diagnostics.md) together so generated reference checks compare one implementation registry with one normative table.

When adding explanatory examples, tutorials, or decision history, prefer `docs/`, `site/content/`, or `plans/` unless the example is itself a normative rule. A docs link from this file is a pointer to supporting material, not authority transfer, unless the text explicitly says the linked artifact owns a narrowly scoped contract.

When changing `rules/`, keep the relationship one-way: rules may enforce process, conformance, release, accessibility, docs, workflow, or agent discipline around the spec, but they do not silently override Kovo behavior. If a process rule needs a behavior change, make that behavior change in this root or the owning sub-spec.

When moving text between modules, preserve the old numbered heading until its inbound references have been deliberately migrated. A renumbering that makes existing `SPEC §...` citations ambiguous is a behavior-affecting documentation change and must land with the matching compatibility-map edit.

When in doubt, keep the root terse and make the linked module carry the full proof.

## 4. Component Model & Authoring

Normative module: [spec/04-component-model.md](spec/04-component-model.md).

Components are authored as TSX/JSX source with `component()` definitions. Authors do not repeat compiler-derivable registry strings, stamps, bindings, or lowered IR by hand. The compiler derives component identities from exported bindings and module paths, emits readable runtime stamps, and rejects app-authored lowered output that would make the proof model dishonest.

The local invariant is that rendered output is both browser-valid HTML and machine-checkable IR: handlers load through named event references, data dependencies are visible through query stamps, IDREF and content-model constraints are statically checked, and server-refreshable fragments have enough identity to reconcile without clobbering state-bearing children. StyleX/theme-token extraction and `kovo-key` runtime identity are part of this component contract, not a separate styling compatibility layer.

Subsection map: §4.1 anatomy, §4.2 rendered output, §4.3 handlers and closures, §4.4 loader, §4.5 composition/slots/layouts, §4.6 primitive merging, §4.7 triggers, §4.8 update plan/bindings/derives/stamps, §4.9 update coverage, §4.10 registry-bounded dynamic rendering, §13.1 StyleX/theme tokens, §13.2 `kovo-key`.

## 5. Compiler

Normative module: [spec/05-compiler.md](spec/05-compiler.md).

The compiler lowers authored TSX into readable server/client modules, generated registries, CSS assets, query/update metadata, and verification artifacts. Its hard rules keep generated output authorable, deterministic, source-derived, and security-preserving. The compiler must prove its own fixpoint and prod render-equivalence instead of relying on opaque optimizer behavior.

The local invariant is that every emitted artifact remains auditable: generated names survive minification, app-authored TSX remains the source of truth, and three derived build-coherence values use only two external carriers. Client-module URLs carry exact-representation digests; document/request/response `Kovo-Build` carriers use the app build token; the internal render-plan fingerprint moves with shape-changing grammar edits and is folded into that token rather than stamped separately. `kovo explain` exposes the graph facts needed for review without executing a browser.

## 6. Type System

Normative module: [spec/06-type-system.md](spec/06-type-system.md).

Kovo uses TypeScript to make safe wiring the normal authoring path: generated registries type components, routes, queries, mutations, sessions, forms, and public surfaces. These types prevent accidental misspellings and unsafe call shapes, but the security proof still belongs to AST/provenance analysis and fail-closed runtime floors.

The local invariant is the honesty boundary: branded or conditional types are author-time guardrails, not trust proofs. The framework must still verify access, ownership, SQL provenance, output sinks, CSRF posture, session shape, package-prefix uniqueness, and deploy-skew retention through runtime validation, compiler provenance, or generated registries.

## 7. The Interaction Ladder

Normative module: [spec/07-navigation.md](spec/07-navigation.md).

The interaction ladder orders behavior from platform-native L0 through lazy islands and enhanced server round trips. Kovo starts from real HTML and real browser semantics, then adds JavaScript only where the compiler can name, load, and verify the enhancement.

The local invariant is progressive capability: an interaction must preserve the no-JS or low-JS path unless its contract explicitly requires a higher rung, and any eager or enhanced behavior must remain visible to static checks and the wire.

## 8. MPA Spine & Navigation

Normative module: [spec/07-navigation.md](spec/07-navigation.md).

Navigation is an MPA spine, not a client router. Canonical URLs, server-rendered documents, browser history, forms, bfcache, and full GET fallback remain authoritative. Enhanced navigation may fetch and morph the canonical server document when the proof model allows it, but uncertainty falls back to the browser.

The local invariant is that navigation state is URL/server-owned. Speculation Rules, View Transitions, streaming navigation, and `<Defer>` improve the experience without creating app-authored persistent navigation state or a global client store.

## 9. Wire Protocol

Normative module: [spec/09-wire-protocol.md](spec/09-wire-protocol.md).

The wire is the documentation: mutation posts, fragment responses, query reads, live events, errors, HMR frames, endpoint audits, durable-task scheduling, and prod deltas use named, schema-shaped traffic. Dev can ship full self-describing frames; prod may compress them only when app build tokens and reconstruction rules keep the delta auditable.

The local invariant is that server truth always wins and every trust boundary stays explicit. Client optimism is disposable, `/_q/` reads are typed and token-tagged, errors preserve per-region/per-field identity where applicable, and endpoint/webhook surfaces are auditable through declared auth, CSRF, cache, and response-body posture.

## 10. Data Plane

Normative module: [spec/10-data-plane.md](spec/10-data-plane.md).

The data plane connects schema facts, query read sets, mutation touch sets, access decisions, SQL safety, optimistic transforms, and exhaustiveness checks. Drizzle-backed schema and AST provenance are the blessed path because they let the compiler derive domains, row keys, ownership, secret classification, and read/write edges.

The local invariant is default-deny plus freshness proof. Queries, mutations, endpoints, and routes need explicit access posture; owner/secret/governed facts flow from schema to wire eligibility and invalidation; and Kovo only claims "the engine is the sole authorization/confidentiality door" when the runtime is least-privilege and a closure audit proves every role-reachable object is `FORCE`-RLS+policy, proven `security_invoker`, or explicitly allowlisted. In-process PGlite is a single-tenant dev/test database whose bootstrap identity is superuser; production MUST refuse it before serving and requires an external Postgres URL whose runtime login passes the least-privilege boot invariant. Security-relevant boundary crossings are further constrained by C9: sinks must be reconstructed carriers, runtime boxes, or framework-owned doors, with a named sink inventory and hostile-value proof for each class. Owner-scoped/governed rows retain provenance at persistent non-engine sinks, and vector/RAG copies cross only through the request-scoped `derived()` namespace door. Raw or opaque SQL must declare the facts the analyzer cannot prove and is then runtime-verified.

Capability URLs use replay domain `v3`, the first domain backed by the durable per-surface reclamation watermark. Pre-watermark `v2` tokens are invalid and MUST be rejected before replay-store access because deleted one-time truth from an older runtime cannot be reconstructed safely after database-clock rollback.

## 11. Static Analysis & Verification

Normative modules: [spec/11-verification.md](spec/11-verification.md) and [spec/11-diagnostics.md](spec/11-diagnostics.md).

Kovo's verification surface combines static touch/read extraction, runtime instrumentation, graph inspection, generated diagnostics, and browser-free contract tests. Static analysis over-approximates, runtime instrumentation under-approximates executed paths, and the invariant is that observed behavior remains within static or explicitly declared facts.

For managed Postgres/PGlite writes, dangerous observed-vs-declared escapes are also engine-bounded: owner-table writes are constrained by RLS/WITH CHECK, and unclassified/reference tables are default-denied by writer grants. That engine-door claim is honest only while the runtime itself is non-superuser/`NOBYPASSRLS` and the closure audit has proved every app-role-reachable object safe; build-time lints are defense-in-depth. The declared-write wrapper still carries the coverage/invalidation contract for benign over-declaration among writable tables, and the C9 sink inventory records the hostile-value proof surface for DB, wire, file, webhook, task, log, and outbound-egress sinks; §10.3 and §11.2 own the detailed layer split.

The diagnostic registry is split out because it is lookup material with independent generated-reference checks. Its authority remains normative: every framework KV### code, severity, and fix posture must agree with the shared `diagnosticDefinitions` registry and the generated diagnostics reference.

## 12. Testing API

Normative module: [spec/12-testing.md](spec/12-testing.md).

The testing API mirrors the proof surface. Mutations execute as functions with touch checking, pages render to inspectable HTML without a browser, typed error unions stay visible, optimistic transforms are pure enough for property tests, and HTTP integration tests exercise the wire against realistic database semantics.

The local invariant is that application wiring should be testable through generated contracts and HTTP/HTML assertions. Browser tests still matter for framework-owned morph survival and platform behavior, but apps should not need broad browser suites to compensate for unverifiable wiring.

## 13. Related Rules and Roadmaps

`SPEC.md` is the normative source of framework behavior. The following files
carry standing conformance rules, release gates, implementation roadmaps, and
explanatory examples:

- Accessibility conformance: `rules/accessibility-conformance.md`
- Data-layer policy: `rules/data-layer-policy.md`
- v1 acceptance gates: `rules/v1-acceptance.md`
- Open design areas: `plans/open-design-areas.md`
- Data-layer roadmap: `plans/data-layer-roadmap.md`
- Risk register: `docs/risk-register.md`
- Worked add-to-cart example: `docs/worked-example-add-to-cart.md`
- Integration testing and browser-free test API examples: `docs/integration-testing.md`
- Layout authoring examples: `site/content/guides/layouts.md`
- Component authoring and copy-in UI examples: `site/content/guides/components.md`
- Optimistic derivation examples and expanded grammar: `site/content/guides/optimistic.md`
- StyleX, stylesheet, and theme-token guidance: `site/content/guides/styling.md`

StyleX/theme-token contracts and the `kovo-key` runtime-identity contract moved to [spec/04-component-model.md](spec/04-component-model.md) as §13.1 and §13.2 because they are component/runtime identity rules.

Rules and roadmaps do not weaken the spec. If a plan conflicts with this root or a linked normative module, follow the spec and update the plan or ask before coding through the conflict.

## 14. Deploy Skew & Version Recovery

Normative module: [spec/14-deploy-skew.md](spec/14-deploy-skew.md).

Kovo treats long-lived documents, stale prerenders, and redeploy skew as recoverable version mismatches, not silent stale patches. An app build token mismatch prevents delta/query merge, triggers full-value refetch when possible, and escalates to full page reload when document and server tokens cannot be reconciled.

The local invariant is a deployment floor: every supported deployment must retain prior immutable client modules and token-scoped `/_q/` reads for at least 24 hours of wall-clock retention across redeploys, or surface KV417 instead of shipping a broken artifact.

---

<!-- Source: spec/04-component-model.md -->

# Component Model & Authoring (SPEC §4, §13.1-§13.2)

This file is incorporated by reference from [../SPEC.md](../SPEC.md) and is normative for Kovo framework behavior.
The root spec remains the entry point and cross-reference index; this module owns the detailed contract below.
Owns component identity, authored TSX, rendered IR, handler loading, composition, primitive merging, update coverage, registry-bounded dynamic rendering, StyleX/theme-token extraction, and keyed runtime identity.

## 4. Component Model & Authoring

### 4.1 Anatomy of a component

```tsx
// cart-badge.tsx — what you write
import { component } from '@kovojs/core';
import { cartQuery } from './cart.queries.js';

export const CartBadge = component({
  queries: { cart: cartQuery }, // typed data dependencies
  state: () => ({ bouncing: false }), // LOCAL state: UI-only facts, JsonValue-constrained

  render: ({ cart }, state) => (
    <button
      commandfor="cart-drawer"
      command="show-modal" // L0: platform behavior, zero JS
      class={state.bouncing ? 'bounce' : ''}
    >
      🛒 <span>{cart.count}</span> {/* compiler derives data-bind="cart.count" (§4.8) */}
    </button>
  ),
});
```

`component()` accepts the definition object only. The author never supplies a component name string:
the compiler derives the DOM wire leaf from the exported binding (`CartBadge` -> `cart-badge`) and the
registry/type key from the module path plus that leaf (`components/cart-badge/cart-badge`). This is the
component-name application of the §4.8 rule that TSX does not require strings the compiler can derive.
Implementation sequencing for the 2026-06-16 migration is tracked in `plans/name-derivation.md`.

**Address strings vs registry identities (normative).** A first positional string argument is reserved
for externally meaningful addresses or protocol paths: route paths, endpoint mounts, capability URL
mounts, and webhook receiver paths. Framework registry identities are source-derived whenever the
compiler can prove an exported binding plus module path: components, webhooks, mutations, queries,
domains, and tags follow this rule. The derived identity is the stable name printed by explain output
and carried by generated registries and internal wire references (`/_m/*`, `/_q/*`, `kovo-deps`,
`<kovo-query>`, replay scopes, touch graphs). App-authored TSX and server modules do not write
registry-name strings merely to repeat facts the compiler can derive; emitted IR may retain residual
strings only when those strings are validated against the generated graph (§4.8, §6.1, §11.3).
Every mutation registry identity is a non-empty string of at most 1,024 JavaScript code units. The
compiler/runtime assignment boundary MUST enforce that ceiling before consuming an unkeyed
declaration or composing replay scope, so a source-derived name cannot overflow durable identity
storage.
Explicit strings remain appropriate for conceptual groupings that intentionally span declarations and
are not one declaration's identity, such as a shared mutation queue (§10.4).

Query-backed components are ordinary live refresh candidates: a component with `queries` gets
`kovo-deps` and a derived fragment target when the compiler can prove the root is addressable and
the subtree can be reconstructed from declared query data plus serializable stamped props. App
authors do not write `fragmentTarget: true` or `kovo-fragment-target="..."`; those are derived IR
facts. The component-level escape hatch is force-off only: `disableServerRefresh: true` opts a
query-backed component out of server fragment refresh while preserving query bindings and local
client updates. There is no force-on mode.

**Call-site props are inferred from `render`.** The first `render` parameter is the single runtime
input bag: the server calls `render({ ...callSiteProps, ...queryResults }, state, slots)`, so query
result keys are server-owned and override any same-named call-site value. TypeScript therefore derives
the public component call-site props from the annotated first `render` parameter with the component's
declared `queries` keys removed. The second parameter is component-local state, and the third
parameter carries render-time slots (`children`, named slots, form state, and request-only framework
slots). A render function whose first parameter is unannotated or `any` exports no ordinary call-site
props; authors must annotate the render input to make props public. `props` metadata and
`query.args((props) => ...)` declarations are consistency checks against that derived call-site prop
shape, not a second source of truth. Component JSX/call expressions still accept framework-owned
attributes such as `key`, `kovo-key`, `style`, and `styles`.

**Rules enforced by the type system:**

- `state` must satisfy `JsonValue` (no `Date`, `Map`, functions, class instances) — serializability is a compile error, not a runtime surprise.
- A query-backed component that is inferred as server-refreshable has render inputs ⊆ (declared queries ∪ stamped props); otherwise the compiler emits a diagnostic explaining why the target cannot be reconstructed, while §4.8 plan-covered positions may still update from query JSON.
- Repeated or prop-keyed inferred targets need stable instance identity from authored `key` or serializable keyed component props; duplicate or ambiguous target identities are compile errors.
- Query data is **shared and server-owned**; local state is **private and client-owned**. A lint (`KV301`) rejects server facts in local state.
- Declared `clocks` are part of the component definition contract. They describe named `now.*`
  inputs for time-dependent render positions and derives; undeclared clock reads remain KV312/KV315.

**Framework component-library contract (normative).** `@kovojs/ui` exposes components through
task-specific subpaths such as `@kovojs/ui/card`; it has no root barrel. The same manifest owns each
component's package import, copy-in command, parts, slots, IDs, state inputs, enhancement tier,
roles, keyboard behavior, and accessibility note, and generated registries, catalogs, reference
material, examples, and copied source MUST agree with that manifest. Card's anatomy is exactly
`Card`, `CardHeader`, `CardTitle`, `CardDescription`, `CardContent`, and `CardFooter`. Generated
`@kovojs/icons/<glyph>` functions return the canonical `ComponentRenderResult` from
`@kovojs/core`; an icon package MUST NOT define a weaker parallel render-result type.

The public result is the small opaque `Component<Props>` callable contract. Its call/JSX props are
the derived call-site props plus framework-owned JSX attributes; it has no public `.definition`,
query-key metadata, render-slot plumbing, or conditional helper-family fields. The framework keeps
the definition in module-private identity state and compiler/server internals resolve it through a
private ABI. Public `AnyFunction`, `IsAny`, `Checked*`, `ComponentCall*`, internal prop/query
metadata, and `ComponentRenderSlots` are not app API. TypeScript opacity is ergonomics; compiler
provenance and exact runtime registry membership own definition acceptance.

Mutation slots are inferred from mutation handles named in the component definition or used by a
typed mutation form in its render. The third render argument exposes only the resulting
handle-indexed `forms` failures, field errors, children, and named slots; authors do not declare a
parallel slot-map type or augment a registry. A mutation code or payload-field rename therefore
turns its exact component use red without exposing framework slot-support types.

### 4.2 Rendered output (the IR's runtime form)

```html
<cart-badge kovo-deps="cart" kovo-fragment-target="cart-badge">
  <button commandfor="cart-drawer" command="show-modal">
    🛒 <span data-bind="cart.count">2</span>
  </button>
</cart-badge>

<!-- Query data ships ONCE per page, as shared client data -->
<script type="application/json" kovo-query="cart">
  { "count": 2, "items": [{ "productId": "p1", "qty": 2, "unitPrice": 1499 }] }
</script>
```

Components render to **light DOM** as plain, never-registered elements — no shadow roots, no `customElements.define`, no upgrade step (rationale in §3.1). The load-bearing DOM identity is the derived `kovo-c` leaf; the compiler omits it when the host tag already spells the derived leaf (`<cart-badge>` — dashed tags are inert sugar) and emits it on native hosts (`<tr kovo-c="cart-row">`, so table content-model nesting works). Query-backed server-refreshable roots also carry a derived `kovo-fragment-target`; for singleton components this is the DOM leaf (`cart-badge`), while repeated instances append stable authored identity (`product-form:p2`). Registry and type identities are separately namespaced by module path (§6.1), so global uniqueness never lengthens the ordinary DOM leaf. If two distinct registry keys would put the same DOM leaf on one page, the composition pass derives a stable disambiguated `kovo-c` value from the registry key and reports it through component explain output; fragment target identities use the same disambiguated leaf before any instance suffix. StyleX-authored component styles compile to globally collision-free atomic classes and dedupe into declared stylesheet assets; raw co-located CSS remains an escape hatch scoped to the derived host leaf (`@scope`, donut-scoped out of nested islands) (§13.1). With no shadow boundary, IDREF wiring (`commandfor`, `for`, `aria-*`), native form participation, and find-in-page work document-wide — the L0 layer and no-JS form fallback depend on it. The compiler validates JSX nesting against the HTML content model (**KV225**): markup the parser would re-parent (`<div>` in `<p>`, `<tr>` outside a table) makes served HTML and parsed DOM disagree, silently breaking morph identity and fragment targets — a compile error, not a runtime surprise.

**Declarative Shadow DOM is disabled.** A `<template>` carrying `shadowrootmode`,
`shadowrootdelegatesfocus`, `shadowrootclonable`, or `shadowrootserializable` (ASCII casing
insensitive) is **KV236**, including direct/static/dynamic attributes, binding or derive targets,
static spreads, and opaque spreads. There is no app-authored suppression. The server renderer, live
binding paths, inline loader, and response-fragment sanitizer remove those controls and their
control-targeting stamps before output or detached-tree adoption, while preserving ordinary inert
template content. This runtime floor covers uncompiled JSX and hostile response bytes; it does not
make declarative shadow roots an alternate component API. For the same finite-output reason, Kovo
does not render or adopt unsandboxable `object`/`embed` or obsolete `frame`/`frameset` primitives;
use a reviewed sandboxed `iframe` or ordinary navigation/download link instead (§4.8, §5.2 rule 10).

Everything is inspectable in the Elements panel: dependencies (`kovo-deps`), data (the JSON), behavior (`on:*` attributes), pending mutations (`kovo-pending`, §10.3).

### 4.3 Handlers and closures

You author inline closures; the compiler lowers them (§5). The lowered form is the contract:

```tsx
// Authoring (sugar)
<button
  onClick={() => {
    state.pendingRemovalId = String(item.id);
  }}
>
  ×
</button>
```

```js
// cart.client.js — GENERATED compiler IR, never an app-authoring surface
import { securityHandler } from '@kovojs/browser/generated';

/** captures: item.id → element params */
export const Cart$removeItem = securityHandler(
  [{ door: 'compiler-state', kind: 'browser.state.write', target: 'state.pendingRemovalId' }],
  (event, ctx) => {
    ctx.state.pendingRemovalId = String(ctx.params.itemId);
  },
);
```

```html
<button on:click="/c/cart.client.js#Cart$removeItem" data-p-item-id="i_42">×</button>
<!-- full URL + #export: no import-map indirection; cache-busting via query
     strings/ETags the server controls. '#cart'-style aliases exist only at
     the authoring/type level (§6.1); import maps are an optional deployment
     strategy, never load-bearing. -->
```

**Capture channels (exhaustive):** component/query state (via `ctx`), element params (`data-p-*`, typed — attribute values arrive as strings, so non-string params declare coercion once, schema-style, exactly like form fields §6.3), module scope (shared, not captured). Anything else is compile error `KV201`, whose message shows what the closure _would have_ compiled to and the three fixes.

**Finite browser effects (normative).** Every serialized handler is classified before emission into
the closed `kovo-security-operation-ir/v1` vocabulary. Authored inline handlers currently admit
static component-state reads/writes and exact timer schedule/cancel operations; compiler-generated
primitive handler references retain their separately reviewed event/DOM behavior. The generated
`securityHandler(operations, fn)` wrapper carries the exact compiler-derived list into the immutable
client module. It is a generated ABI and audit witness, not a public authoring helper and not an
opcode interpreter. App source cannot construct, widen, or suppress the list.

A native event and every object reached from it remain capability-bearing. Property or method names
such as `target`, `value`, `preventDefault`, or `focus` are not authority proofs: a synthetic event
can own-shadow them and invoke attacker-selected getters or functions. Authored raw event/DOM member
reads and calls are therefore **KV449** until lowering targets a framework-pinned operation rather
than dispatching through the raw receiver. The same rule closes raw browser globals, computed
terminal operations, and authority that escapes through an alias, destructuring, mutable container,
constructor, or unreviewed call. Exact framework import identity alone does not summarize argument
positions, callback-bearing containers, or return authority, so authored inline framework-helper
calls are also **KV449** until that export has a generated exact summary. Direct primitive handler
references remain the supported path. Named exceptional doors and the server vocabulary are defined
in §6.6, while §5.2 owns lowering and fixpoint behavior.

**Closed handler language (normative).** A browser handler is synchronous and its public function
type returns `void`. A concise body or explicit return may evaluate only closed JSON/scalar data or
an exact finite effect; the loader discards that value. Authority-bearing, executable, or thenable
outcomes are **KV449**. `async`, generators, `await`, `yield`, exception control
(`try`/`catch`/`finally`/`throw`), constructors, `instanceof`, and every tagged template are outside
the language. The loader never performs `PromiseResolve`, reads a returned `then`, or awaits a
handler return. Asynchronous work must use an explicit finite operation such as a timer whose
callback is an exact reviewed zero-parameter callable; source-text timer callbacks and callback
parameters/defaults/destructuring are forbidden. Timer cancellation accepts exactly one
compiler-proven primitive handle or the exact result of a finite timer schedule. Promise factories
and other asynchronous global work are outside the synchronous language. A handler may contain at
most 256 distinct finite operations, matching the generated runtime manifest bound. A timer callback cannot
read, write, or capture handler state, including through an immutable alias: synchronous dispatch
has already committed and retired that state snapshot before the callback runs. Delayed state work
requires a future framework-owned scheduled operation that re-enters the state-host queue and
performs a fresh snapshot/validate/commit transaction.

State may be read and written only through static member targets. Computed or destructured state
targets, binding or assignment default initializers, spreads, `for...of`, `this`, authority-bearing
computed keys or scalar coercions, opaque call/callback results, accessors, constructors/classes, and
implicit object protocols are outside the language. `Object.assign` cannot hide a state mutation
from the compiler-owned state-write IR. A state write expression is built from JSON literals and
dense literal containers, immutable aliases of those values, static state reads, ordinary scalar
operators, and the compiler's exact scalar intrinsics. The callable state-method vocabulary is
closed to `pop`, `push`, `reverse`, `shift`, `unshift`, `endsWith`, `includes`, `indexOf`,
`lastIndexOf`, `replace`, `replaceAll`, `startsWith`, `toLowerCase`, `toUpperCase`, `trim`, `trimEnd`,
and `trimStart`; replacement callbacks are not accepted. The only callback transform on local data
is `map` over an exact dense array literal or its immutable alias, with an exact reviewed callback.
Adding a method requires a SPEC change and adversarial proof of its callbacks, coercions, return
provenance, and mutation semantics; a familiar JavaScript method name is not evidence.

State is also a runtime boundary. Before module import and the first state snapshot, the loader
serializes dispatches per state host so overlapping events cannot read the same stale base. Before
the first handler and after every handler in a chain, it replaces `ctx.state` with a fresh recursive
own-data `JsonValue` snapshot. Arrays must be
dense and use the intrinsic array prototype; records must use `Object.prototype` or `null`; only
enumerable string-keyed data properties are copied; enumerable accessors, cycles, non-finite
numbers, non-JSON values, exotic prototypes, and over-budget graphs fail with **KV449**. The current
bounds are 64 levels, 10,000 values, and 1,000,000 string/key code units. Non-enumerable and symbol
data is outside JSON and is discarded. This runtime check is defense-in-depth after the compiler's
closed verdict, not a substitute for it.

A delegated dispatch is one fail-closed state transaction. After the final handler, the loader first
snapshots and serializes the candidate state. It then prepares the complete state-binding update:
every direct target and derive reference is snapshotted, every derive module is imported, every
owned export and owned `run` function is strictly validated, every derive runs, and the results are
materialized as a closed list of typed text/attribute/property sink operations. Preparation performs
no framework DOM writes. Only after preparation succeeds does the loader apply that list, commit the
serialized `kovo-state`, and drain post-commit callbacks, in that order. A missing or invalid derive
export/`run` is a closed failure, never an implicit `undefined` binding value.

If handler import/export resolution, handler invocation, the post-handler state snapshot or
serialization, or binding preparation throws, the loader aborts the remaining transaction, performs
no binding write, leaves serialized state unchanged, and discards every post-commit callback
collected by that dispatch. If an arbitrary platform DOM setter throws while applying an already
prepared list, an earlier presentation write in that list can remain applied: the web platform does
not provide a general rollback transaction for setters. Kovo still leaves `kovo-state` unchanged and
does not drain post-commit work because that commit happens only after every prepared write succeeds.
Modular and all generated-inline runtimes use the same rule. A later queued dispatch starts from the
last successfully committed state snapshot.

### 4.4 The loader

A gzip-capped inline bootstrap is the only JavaScript Kovo ever puts in a document. Its enforced
ceiling lives in `inlineKovoLoaderGzipByteBudget` (currently 10,500 gzip bytes).
The bootstrap captures first interactions, queues or falls back safely while the
runtime loads, promotes deferred styles, and imports the versioned Kovo deferred
runtime module from the framework-owned `/c/` module registry. The deferred
runtime module is not part of the first-paint byte budget and has no SPEC gzip
cap; it is versioned, cacheable, and loaded by `import()` after the bootstrap's
first-interaction or post-paint trigger.

**Emission is conditional (normative).** The bootstrap is emitted only into a document that carries
client surface — anything below the deferred runtime's responsibilities: an island or delegated
handler or execution trigger, an update-plan binding, an enhanced form, `<kovo-query>` truth, a
deferred region, a `deferFull` stylesheet the bootstrap alone promotes (§13.1), an app-declared
module preload or bootstrap script, or a session-dependent/fingerprinted document whose bfcache
restore the runtime must force through the server (§8, §9.3). A document with none of those has
nothing for the deferred runtime to do, so it ships **zero** JavaScript: no inline bootstrap, no
`import()`, and correspondingly no inline-script hash in its `Content-Security-Policy` — a strictly
tighter policy, never a looser one. The test is a deliberate over-approximation read only in the
fail-safe direction: any framework-emitted client marker keeps the bootstrap, and only the total
absence of every marker drops it. A fully server-rendered document therefore also has no enhanced
navigation; it navigates natively, which is the same behavior it already has before the runtime
settles.

Ordering inside `<head>` is fixed by authority class, not by hint kind. Non-executable CSS delivery
— inline critical `<style>` and the `<link rel="stylesheet">` (or its deferred `rel="preload"` +
`<noscript>` form) — is emitted **before** the bootstrap so the browser can discover the stylesheet
within the first congestion window. Everything that can execute — app-declared module preloads, an
authored bootstrap script, structured-document head scripts — is emitted **after** it, so the
bootstrap still installs its controls before any app-authored script runs (§6.6, §8).

Deferred runtime responsibilities:

- **Event delegation** (capture phase) for all `on:*` events — including chained refs (§4.6) and the execution triggers `on:visible` (one shared IntersectionObserver) / `on:idle` / `on:load` (§4.7).
- **Ref resolution:** parse `url#export`, `import()` the URL, invoke synchronously with
  `(event, ctx)`, discard the return value without thenable inspection, and snapshot `ctx.state`
  before invoking the next ref. Module loading and the later update plan may be asynchronous; the
  handler call frame is not.
- **Per-island `AbortSignal`** (`ctx.signal`), aborted when the morph layer removes the island (§4.7); no mount/unmount callbacks.
- **Enhanced form interception** (§9) and **query-data hydration** from `kovo-query` scripts.
- **Update plan** (bindings → derives → stamps, §4.8) on query/state change, by walking the self-describing attributes.
- **Refetch on focus/visibility** over the typed read endpoint (§9.3, §9.4).
- **Morph application:** the morph layer accounts for islands it patches in and aborts the signals of those it removes — nothing is registered.

### 4.5 Composition: children, slots, layouts

Composition is **render-time function composition** — there is no client re-render, so projection happens exactly once, on the server. Three rules:

**1. Children are a render-time value.** JSX children lower to an opaque `Html`-typed argument; named slots are just named `Html`-typed props. The lowered IR is a plain function call — fixpoint-trivial:

```tsx
export const Card = component({
  render: (_, state, { children, footer }) => (
    <div class="card">
      {children}
      <div class="card-footer">{footer}</div>
    </div>
  ),
});

// call site — lowers to Card.render(…, { children, footer })
<Card footer={<Totals />}>…</Card>;
```

**2. Compound components coordinate through lexical scope and the DOM — there is no context API.** At render time, sub-parts are functions sharing scope (a `Dialog.Root` generates ids and passes them down as ordinary arguments; KV221 validates the IDREF wiring). Ids are **unique by construction**: generated ids are keyed to the render site, and a static `id` in a component the compiler cannot prove renders at most once per page is **KV224** (derive it from `kovo-key`/props instead) — KV221 proves an id _exists_; KV224 keeps that proof meaningful by forbidding duplicates, including under list stamping and fragment patch-in. At runtime, the tree is the context: a sub-part's handler resolves its island via `closest('[kovo-c]')`, which `ctx` already does. This is sound because **framework code never reparents islands** (normative; dev mode asserts it). Native top-layer promotion (`<dialog>`, popover) does not reparent — exactly why Kovo needs no portal.

**3. Refreshable-target children must remain server-renderable.** An inferred server-refreshable
query component's subtree must be reconstructible from (declared queries ∪ stamped props) — and
call-site children are part of that subtree. They are therefore **lowered to component references**:
the compiler hoists JSX children into a named component (`Parent$slot_children`) when their free
variables fit the stamped-prop channels (the same lowering discipline as handlers, §4.3), records
the reference + props in the target's stamps, and re-renders the full subtree on fragment patch.
Children that cannot be hoisted (unserializable captures) are compile error **KV230**, whose message
shows the hoisted component that _would have_ been generated and the fixes.
Because the full subtree re-renders on every fragment patch from (declared queries ∪ stamped props)
and island-local `state` rides neither channel nor any morph-preserved serialization (§9.1, §4.9),
an island declaring local `state` (or carrying `kovo-state`) inside another component's inferred
server-refreshable fragment target is compile error **KV420**. The fixes are: lift the child's state
into a declared query, mark the child `isomorphic: true` (§4.8), set `disableServerRefresh: true` on
the enclosing component, move the stateful island outside the refreshable target, or declare genuinely
document-lifetime-immutable state as `renderOnce`. If the component has `disableServerRefresh: true`,
the hoist requirement applies only to positions that still need a server fragment; ordinary §4.8 query
bindings remain valid. Fragment responses must fully describe the DOM they produce; prod may encode
that refresh as a version-validated delta (§9.1.1), but there are no morph-preserved slot holes.

**Layouts are first-class route chrome.** v1 has explicit `layout()` declarations, not a file-system
route-tree convention. A layout is still render-time function composition over `children`, but
authors attach it to routes instead of wrapping every page by hand. Layouts may be nested with an
explicit `parent`, may declare `queries`, `guard`, and per-segment `boundaries`, and are shown by
`kovo explain page <path> --layouts`. They are page chrome, not document assembly; documents are
owned by the request shell (§9.5). Runtime persistence is not part of v1: every navigation still
renders a full document, so later enhanced-navigation layers must preserve the same authored layout
declarations. Authoring examples live in `site/content/guides/layouts.md`.

Routes may declare **parallel layout regions** at the route boundary with `regions`, for sibling
chrome that a layout positions beside the main page without app-authored runtime stamps:

```tsx
const DocsLayout = layout({
  render: (_queries, _state, { regions }) => (
    <DocsShell page={regions.page} sidebar={regions.sidebar} />
  ),
});

route('/guides/:slug', {
  layout: DocsLayout,
  regions: {
    page: ({ params }) => <GuidePage slug={params.slug} />,
    sidebar: ({ params }) => <DocsSidebar activeSlug={params.slug} />,
  },
});
```

`regions.page` is the route leaf region when present; additional names are scoped to the declaring
route/layout contract. The request shell renders every region from the same route params, search,
guard-refined request, and JSX context as the page, then passes the rendered map to
`layout().render` as `slots.regions`. The compiler owns the stable segment ids and dependency
metadata for those regions. JSX marker components or app-authored `kovo-nav-*` attributes are not
part of this API.

Route pages that return JSX are **compiler-processed Kovo source**, not opaque runtime JSX. The
compiler lowers the route page into authorable server IR, records the component calls and
serializable props, runs the declared component queries for the initial document, and emits the
live-target registry used by enhanced mutation responses (§9.1). Dynamic route composition that
cannot be scanned receives a diagnostic rather than falling back to app-authored fragment routing.
Every navigation is a full document, so there is no persistent-layout state to manage;
cross-document View Transitions carry the visual continuity. A route-tree convention may arrive
later as sugar lowering to exactly these calls (Constitution #3).

**Payload posture:** projected children ship in the initial HTML — all tab panels, dialog bodies, accordion contents. There is no client-side lazy mount; `<kovo-defer>` (§8) is the escape hatch for expensive subtrees. This is the MPA posture by design. Component authoring examples live in `site/content/guides/components.md`.

Because projected children ship once and never receive a client mount, an `isomorphic: true` island (§4.8) that composes children or named slots must, on self-render, leave those projected regions in place and re-render only its own positions (§4.8); a render whose own positions cannot be separated from the projected regions is **KV316**.

### 4.6 Primitive composition & attribute merging

Headless primitives decorate author-owned elements through three spellings of one mechanism: the primitive computes a plain, serializable attribute record (ARIA, `data-state`, `on:*` refs, ids) at render time, and it **merges into the author's element before emission**. The wire shows only the result — a merged element is indistinguishable from one written by hand (Constitution #3, #4) — and merging is deterministic (stable ordering), so the fixpoint and byte-stable IR hold.

**Attrs-function children (the normative IR):**

```tsx
<Tooltip.Trigger>
  {(attrs) => (
    <a {...attrs} href="/pricing" class="nav-link">
      Pricing
    </a>
  )}
</Tooltip.Trigger>
```

`attrs` is typed; this is the render-prop pattern minus its runtime cost, because there is no re-render. An `Html`-returning function whose `attrs` parameter goes unused is a lint.

**`asChild` (sugar lowering to the attrs-function form):** requires a single, statically-known element child; the compiler merges and emits. Dynamic or multiple children → teaching error pointing at the attrs-function form to write instead.

**Behavior attributes (trigger-shaped cases):** annotate instead of wrap — `<a href="/pricing" kovo-tooltip="pricing-tip">` — the invoker-commands idiom (`commandfor`/`command`) extended upward from L0; the package prefix comes from §6.1.1 and the IDREF is validated by KV221. This is also the only spelling that works on markup Kovo didn't render (CMS content, markdown).

**Rejected:** a polymorphic `as` prop — it composes only with intrinsic tags, and polymorphic typing is the heaviest TS pattern known (type-perf risk; see `docs/risk-register.md`) for the weakest payoff.

**Merge rules (normative).** Merging happens once, at render; conflicts resolve per attribute class:

| Attribute class                                                                                                                                                                                                              | Rule                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `class`                                                                                                                                                                                                                      | Concatenate (primitive first, author last), dedupe, stable order                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| `style`                                                                                                                                                                                                                      | Concatenate; author declarations last (later wins per property)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| `on:<event>`                                                                                                                                                                                                                 | **Chain**: space-separated refs, author's first, then primitive's; after each module resolves, the loader invokes its handler synchronously left-to-right, discards its return without thenable inspection, and replaces `ctx.state` with a fresh bounded own-data JSON snapshot before the next ref. `defaultPrevented` does **not** stop the chain (platform semantics) — primitive handlers contractually no-op when `event.defaultPrevented` (linted in the primitive package, not the loader)                                                                                                                                                                  |
| `id`                                                                                                                                                                                                                         | Author wins; the primitive rewires its IDREF references to the surviving id (KV221 validates the result)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| IDREF attrs (`commandfor`, `popovertarget`, `for`, `aria-controls`, …)                                                                                                                                                       | Both set → **error KV231** (double-wired relationships are ambiguity, not composition)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| Descriptive `aria-*` (`aria-label`, `aria-labelledby`, `aria-describedby`, `aria-roledescription`), `role`                                                                                                                   | Author wins, **lint KV232** (the escape hatch stays open; the override stays visible). These are not runtime-driven, so author authority cannot freeze a live value.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| State `aria-*` the primitive updates at runtime (`aria-expanded`, `aria-selected`, `aria-checked`, `aria-pressed`, `aria-current`, `aria-disabled` when state-driven, and any `aria-*` the primitive lists as state-bearing) | **Primitive wins, lint KV232** — same hazard as `data-state`: the primitive's runtime derive owns the attribute, so a static author override would be clobbered on first state change. The primitive's runtime updater keeps writing this attribute after the merge regardless of the static winner; the author's static value is used only as the initial server-rendered value when the primitive is silent at render. Authoring a static state `aria-*` whose value contradicts the primitive's render-time state is **error KV317** (a frozen-vs-clobbered ambiguity the author cannot have meant), distinct from the visible-escape-hatch override lint KV232. |
| `data-state` & primitive-owned `data-*` state attrs                                                                                                                                                                          | Primitive wins, **lint KV232** (runtime-updated values; a static override would be clobbered on first state change)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| `data-p-*` (handler params)                                                                                                                                                                                                  | Same param from both → **error KV231**                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| Binding attrs (`data-bind`, `data-bind:*`)                                                                                                                                                                                   | Same target slot → **error KV233**; distinct targets compose                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| `disabled`, `aria-disabled`, `required`, `readonly`                                                                                                                                                                          | Logical OR                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| Other scalars (`type`, `href`, `tabindex`, `value`, `view-transition-name`, …)                                                                                                                                               | Author wins; the primitive value is a default (used only when the author is silent)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| `kovo-deps`                                                                                                                                                                                                                  | Union                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| `kovo-c`, `kovo-state`                                                                                                                                                                                                       | Both present → **error KV231** (one element = one island)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |

### 4.7 Execution triggers

"Execute nothing until interaction" is a proxy for the real invariant: **execute nothing the page didn't declare, and make every trigger legible in markup.** Interaction is the default trigger; three declared alternatives extend the same `on:*` → delegate → `import()` → named-export model:

```html
<sales-chart on:visible="/c/chart.client.js#SalesChart$mount" kovo-deps="sales"></sales-chart>
<search-index on:idle="/c/search.client.js#Search$warm"></search-index>
<stock-ticker on:load="/c/ticker.client.js#Ticker$start"></stock-ticker>
<!-- lint-gated -->
```

- **`on:visible`** — one shared IntersectionObserver; fires once on first intersection. Charts, maps, carousels, lazy embeds.
- **`on:idle`** — `requestIdleCallback`; warm-up work.
- **`on:load`** — fires at parse. The escape hatch: it reintroduces eager JS, so lint **KV211** requires a justification comment, and `grep 'on:load'` is the app's eager-JS budget.

The set is closed — `on:media` is CSS's job; timers belong inside handlers. Islands patched in by morph are observed like everything else (the morph layer already accounts for islands it patches in, §4.4).

**Lifecycle is one primitive:** `ctx.signal`, an `AbortSignal` aborted when the morph layer removes the island (or the document tears down). Long-running handlers (autoplay loops, map instances, observers) register cleanup on it; there are no mount/unmount callbacks.

### 4.8 The update plan: bindings, derives, stamps

**The DOM is the plan.** There is no separate compiled-plan artifact: binding attributes are self-describing, the loader executes them by walking the tree under `kovo-deps` islands, and compile-time knowledge is used for _typing_ only. When a query value — or island-local state; same machinery, two data sources — changes, the loader runs, in order:

Each component-local query alias MUST occur once and MUST identify exactly one canonical runtime
query; one canonical runtime query MUST NOT be bound under multiple aliases in the same component.
Until generated alias-to-instance ownership metadata exists, either duplicate object-key aliases or
distinct aliases that resolve to the same source-derived runtime query are compile error **KV240**:
otherwise one store identity could drive multiple update-plan owners without a deterministic mapping
from returned query data to the DOM writes it owns.

**1. Bindings — path writes.** `data-bind="cart.count"` sets text content; `data-bind:<attr>` sets attributes (`data-bind:value`, `data-bind:hidden`). **Contextual encoding is mandatory and by default.** Every interpolated value MUST be contextually encoded for the sink it lands in before it reaches the DOM, and the server renderer and the loader MUST use byte-identical encoding (the §5.2 #3 render-equivalence gate binds them — divergent encoding is **KV222** drift). A `data-bind` text write is a `textContent`/escaped-text write, never an HTML parse: it never opens an element or comment. A `data-bind:<attr>` write is an attribute-value write with attribute-value escaping; it never opens a new attribute or tag. No binding ever inserts attacker-influenced markup as raw HTML, and no plain binding reaches an unsafe sink — those sinks are enumerated under **KV236** below and require the trusted-HTML escape hatch. Grammar: dot paths plus optional segments (`deal.contact?.name`) — no expressions, no indexing (arrays are stamps' job). Paths type-check against the query's inferred shape (§6.2), and the check is **null-aware**: a path traversing a nullable or optional segment — the routine shape of leftJoin projections — must mark the traversal `?.`, or it is compile error **KV227**; rendering `undefined` is unrepresentable, not a runtime surprise. `?.` has defined empty semantics shared by the server renderer and the loader (the two must not disagree — the KV222 drift rule applied to nullability): a text binding renders the empty string; an attribute binding removes the attribute. Sugar lowers `{deal.contact?.name}` to exactly this form; item-relative stamp paths (`.contact?.name`) and `data-bind-list` paths follow the same rule. When empty-on-null is the wrong rendering, the KV227 fix menu is the usual ladder: extract a named derive that handles `null` explicitly, or make the projection non-null in the query itself (`coalesce`), keeping the binding total.

**Unsafe output contexts and the trusted-HTML escape hatch (KV236).** A _safe_ binding context is one whose contextual encoding (above) provably neutralizes attacker-influenced bytes: HTML text content and ordinary attribute values. The following are **unsafe output contexts**, and a plain `data-bind`/`data-bind:<attr>`/derive value flowing into one is a compile error **KV236**:

- raw-HTML insertion (any binding that would parse its value as markup rather than set escaped text);
- URL-scheme attributes — `href`, `src`, `action`, `formaction`, `xlink:href`, `ping`, `poster`, and CSS `url(...)` — whose value's scheme is not on the allowlist `http`, `https`, `mailto`, `tel`, `ftp`, relative/path-only, or a fragment; in particular `javascript:` and `data:` are denied;
- event-handler attributes (`on*`);
- the `style` attribute and `<style>` element text;
- `srcdoc`;
- `<script>` element text and `<script type="application/json">` island bodies (§9.1 governs the byte-level encoding for the latter).
- element-context execution, request, and isolation controls form one finite denominator of exactly
  67 element/attribute tuples: `script` × (`src`, `href`, `xlink:href`, `type`, `nomodule`,
  `integrity`, `crossorigin`, `referrerpolicy`, `charset`, `nonce`, `language`, `attributionsrc`);
  `style` × (`type`, `media`, `nonce`); `link` × (`href`, `rel`, `type`, `media`, `disabled`,
  `integrity`, `crossorigin`, `referrerpolicy`, `as`, `nonce`); `iframe` × (`src`, `sandbox`,
  `allow`, `allowfullscreen`, `allowpaymentrequest`, `browsingtopics`, `credentialless`,
  `sharedstoragewritable`, `csp`, `referrerpolicy`, `name`); `annotation-xml[encoding]`;
  `geolocation` × (`autolocate`, `watch`, `accuracymode`); `a` × (`target`, `rel`,
  `referrerpolicy`, `ping`, `attributionsrc`, `attributiondestination`, `attributionsourceid`,
  `attributionsourcenonce`); `area` × (`target`, `rel`, `referrerpolicy`, `ping`,
  `attributionsrc`); `form` × (`target`, `rel`); `button[formtarget]`; `input[formtarget]`; `img` ×
  (`referrerpolicy`, `crossorigin`, `attributionsrc`, `sharedstoragewritable`);
  `audio[crossorigin]`; `video[crossorigin]`; SVG `image[crossorigin]` and
  `feImage[crossorigin]`; and `meta` × (`name`, `http-equiv`). Every listed control is static-only or
  disabled by the stronger value rules below: a direct dynamic value, dynamic removal, or opaque
  spread is KV236.
  The parser-request controls are included as tuples, not treated as independent strings: changing
  a script/link/style `type` or `media`, subresource `integrity` or credential mode, destination,
  or referrer policy after parsing/request selection cannot retroactively secure that action.
  Script scheduling hints (`async`, `defer`, `fetchpriority`) are deliberately outside this
  denominator because they schedule an already-reviewed resource rather than select its authority
  or isolation posture.
  The URL halves (`script[src|href|xlink:href]`, `link[href]`, and `iframe[src]`) may instead hold an
  exact `trustedUrl(value, { reason: auditedReason })`; `trustedUrl` suppresses no other tuple. Live bindings and
  keyed fragment morphs preserve the reviewed current value (including absence) rather than apply
  or remove a blocked control; newly adopted fragment nodes pass the same static-value floor before
  adoption. Compiler, server JSX, modular binding/fragment code, and the generated inline loader
  consume the same closed classifier, and the conformance corpus asserts every tuple non-vacuously.
- the finite browser-control denominator has these stronger value rules. `referrerpolicy` is limited
  to `no-referrer`, `same-origin`, `strict-origin`, or `strict-origin-when-cross-origin`, so an
  element cannot weaken Kovo's `strict-origin-when-cross-origin` response-header floor. Anchor and
  area `target` accepts only `_blank`, `_self`, `_parent`, or `_top` (never an opener-bearing named
  browsing context), and their `rel` token list must not contain `opener`. Anchor/area `ping` is
  disabled outright because its reporting headers can disclose the source URL. `meta[name=referrer]`
  is disabled because it can override the response-header posture. `meta[http-equiv=refresh]` is
  disabled because it can navigate before framework controls install. Script/link `nonce` is
  framework-owned and disabled in authored output under Kovo's hash-locked CSP, and obsolete
  `script[language]` is disabled. Attribution registration, browsing-topics disclosure,
  shared-storage writes, and legacy payment-request delegation controls are disabled pending named
  reviewed capability doors. `geolocation[autolocate|watch|accuracymode]` is likewise disabled:
  supported Kovo responses send `Permissions-Policy: geolocation=()`, and a future opt-in requires
  a named permission-policy capability rather than an incidental element attribute. HTML and SVG
  `crossorigin` credential modes and `style[type|media]` activation remain available only as
  statically reviewed values. These bans have no trusted-value suppression.
  An `iframe[src]` additionally requires a present, statically reviewed `sandbox` attribute; a
  trusted URL never suppresses that relational boundary. `sandbox` is an ASCII-whitespace token
  set with the exact admitted tokens `allow-forms`, `allow-modals`, `allow-orientation-lock`,
  `allow-pointer-lock`, `allow-presentation`, `allow-same-origin`, and `allow-scripts`. Unknown or
  newly introduced tokens fail closed. `allow-scripts` and `allow-same-origin` may each appear but
  MUST NOT be combined, because a same-origin scripted child can remove its own sandbox.
  Navigation/popup/storage/download escapes — including every `allow-top-navigation*` token,
  `allow-popups`, `allow-popups-to-escape-sandbox`, `allow-storage-access-by-user-activation`, and
  `allow-downloads` — are not admitted. The compiler rejects missing or unsafe sandbox posture;
  server JSX and modular/generated live or fragment floors remove the active `src` (and any unsafe
  `sandbox`) before adoption or update.
- document-wide navigation elements are outside the app-authored output surface: `<base>` is
  disabled because even a safe-scheme value retargets every later relative URL, and
  `<meta http-equiv="refresh">` is disabled because `http-equiv` plus `content` is one executable
  navigation sink whose attributes may be committed in either order. The compiler reports KV236
  for `<base>` and for a statically or directly dynamically refresh-capable `<meta>`; opaque spread
  values are reconstructed and classified by the element-aware runtime pair sink. Server rendering
  and live binding updates remove attempts that reach that floor. Ordinary metadata such as
  `<meta name="description" content={...}>` and statically non-refresh `http-equiv` values remain
  valid. There is no trusted-value suppression for document-wide navigation; use Kovo router or
  response navigation outcomes and framework deployment-base configuration instead.
- generic SVG SMIL execution primitives — `<animate>`, `<animateColor>`, `<animateMotion>`, `<animateTransform>`, `<discard>`, and `<set>` — are disabled outright in framework-generated or framework-managed DOM. SMIL's `attributeName` target and its `values`/`from`/`to`/`by` transfers are one temporal sink: the target may be an ancestor or an `href="#id"` sibling, and live bindings may commit target and transfer values in either order. The compiler reports **KV236** for these intrinsic elements even when the apparent target is benign; server JSX omits them, and fragment/live-update runtimes inert them before adoption or update. There is no plain-JSX trusted-value suppression for this ban; reviewed raw `trustedHtml(...)` remains the explicit whole-markup authority described below.
- unsandboxable active embeds — `<object>` and `<embed>` — are disabled outright in framework-generated or framework-managed DOM. Either element can load same-origin HTML with the embedding document's authority, but neither provides the isolation contract of `iframe[sandbox]`; CSP `object-src 'none'` remains defense in depth rather than the proof. The compiler reports **KV236**, server JSX omits the element, and fragment/live-update runtimes remove its attributes and fallback descendants before adoption or update. Use a sandboxed iframe or an ordinary download/navigation link instead; there is no plain-JSX trusted-value suppression for this ban.

Every `data-bind:<attr>` write into a URL-scheme attribute MUST scheme-allowlist its resolved value at both render and loader update time; a value resolving to a denied scheme is dropped to the attribute's empty semantics (the attribute is removed, per the `?.` rule above), never written verbatim. A binding into an unsafe context with no escape hatch is **KV236** with the usual teaching menu: change the projection, extract a derive that returns a safe value, or — for genuinely author-trusted markup/URLs — opt in via the trusted-HTML escape hatch.

The escape hatch is a typed, named, public Kovo API
(`trustedHtml(value, { reason, source? })` / `trustedUrl(value, { reason, source? })`, importable only
from a documented public entrypoint per §5.2 #8) that brands its argument as author-vouched. The
structured metadata argument is required. `reason` is a non-empty, trimmed audit justification;
`source`, when present, is a non-empty, trimmed provenance label. Both are bounded and reject
control/format characters, accessors, arrays, and unknown fields. String shorthand, missing
metadata, and blank or dynamic reasons do not discharge compiler provenance. A binding may reach an
unsafe context only when its lowered value is a `trustedHtml`/`trustedUrl` brand; the brand is the
only thing that suppresses KV236, it is visible in source and in `kovo explain component`, and it is
never derivable by the compiler (so the author always writes it explicitly — the inverse of the
"TSX never requires a string the compiler can derive" rule, applied to trust). A trusted value
carries no escaping obligation onto the framework; producing it from unvalidated query data is the
documented hazard the brand makes auditable.

For a reactive URL binding, the compiler MUST preserve that source provenance without emitting a
runtime call to the author-facing constructor. It lowers the exact `trustedUrl(value, { reason:
auditedReason, source? })` argument as the derive value and emits a compiler-owned, generated-only
sink marker plus a closed query-plan fact. The modular browser runtime and inline loader may mint
the runtime brand only from that plan fact immediately before the reviewed URL sink. App-authored
copies of the marker are KV235, and an unmarked query value remains subject to the ordinary
URL-scheme floor.

**Live-property bindings (`data-bind-prop:<prop>`) — the property-authoritative addendum.** A handful of attributes are _property-authoritative_: once the live DOM property is dirtied by user interaction (or script), the browser stops reflecting the attribute onto the property, so an attribute-only `data-bind:<attr>` write silently fails to update the observed state — `FormData` reads `input.checked`, not the `checked` attribute; `.indeterminate`/`.scrollTop`/`.scrollLeft` are not HTML attributes at all. For a **closed, security-reviewed allowlist** — `checked`, `indeterminate`, `value` (form controls), `scrollTop`, `scrollLeft`, `selected`, `open` — the compiler additionally emits a companion `data-bind-prop:<prop>` stamp alongside the SSR attribute and `data-bind:<attr>`, and the loader applies it by **assigning the live element property** (`el[prop] = coerce(prop, value)`: boolean for `checked`/`indeterminate`/`selected`/`open`, number for `scrollTop`/`scrollLeft`, string for `value`) on hydration and after every derive/morph re-render — the property write runs _after_ the attribute patch. The SSR attribute is unchanged, so first paint and no-JS stay correct and render-equivalence (§5.2 #3) treats `data-bind-prop:*` as a non-attribute output (byte-identical visible HTML; the property write is the extra output). This is not an author surface: a component still writes `checked={…}`/`scrollTop={…}` and the compiler derives both stamps from the one fact. **The allowlist is the security boundary** — `data-bind-prop:*` is never emitted or applied for any other property, and the unsafe sinks (`innerHTML`/`outerHTML`/`srcdoc`/`on*`) stay forbidden (KV236); the runtime ignores a non-allowlisted property defensively.

**2. Named derives — the expression layer.** A derive is a named, exported, pure function with declared inputs — exactly parallel to handlers:

```ts
// cart.client.ts — public authoring API
import { derive } from '@kovojs/browser';
import { cart } from './cart.server.js';

export const Cart$isEmpty = derive([derive.query(cart)], (cartValue) => cartValue.count === 0);

export const Cart$summary = derive(
  {
    cart: derive.query(cart),
    local: derive.state<{ expanded: boolean }>(),
    now: derive.clock<Date>(),
  },
  ({ cart, local, now }) => `${cart.count}:${local.expanded}:${now.toISOString()}`,
);
```

```html
<button data-bind:disabled="/c/cart.client.js#Cart$isEmpty">Checkout</button>
```

Each public input is an opaque, fieldless handle minted by the same installed
`@kovojs/browser` instance. `derive.query(queryHandle)` preserves the query's result type and lets
the compiler carry its source-derived runtime name without an app-authored string;
`derive.state<Value>()` and `derive.clock<Value>()` name the framework-owned state and clock
channels while preserving the callback value type. Tuple inputs preserve tuple positions. Object
inputs preserve author-selected callback property names while retaining the underlying runtime
input names. Casts, structural copies, accessors, foreign-package handles, and raw string inputs
fail closed.

The compiler-emitted `@kovojs/browser/generated` ABI retains raw tuple and object input names so
lowered output is inspectable and fixpoint-validatable. App-authored use of that generated subpath
or hand-authored raw derive IR is KV235 under §5.2; raw strings are not accepted by the public
authoring API.

Declared inputs tell the loader which query changes re-run it — no dependency tracking — and the
module loads lazily on the first relevant change, preserving resumability. Inline JSX expressions
in bound positions lower to named derives (the KV210 naming nudge applies). Minification cannot
rename them (Constitution #1); `kovo explain component` lists every derive with its inputs.

**3. Template stamps — keyed list reconciliation.**

```html
<ul data-bind-list="cart.items" kovo-key="productId">
  <template kovo-stamp>
    <li><span data-bind=".qty"></span> × <span data-bind=".name"></span></li>
  </template>
  <li kovo-key="p1"><span data-bind=".qty">2</span> × <span data-bind=".name">Mug</span></li>
</ul>
```

On change, the loader keys existing `[kovo-key]` children against the new array: clone the template for inserts, remove exits, reorder by key, then run item-relative bindings (`.qty`, typed against the array element type). **`key={...}` is the authored TSX identity; `kovo-key` is the lowered runtime identity contract** — written once and shared verbatim by stamps, morph, inferred fragment target suffixes, submitted-form identity, and optimistic reordering (§13.2). App source that hand-authors `kovo-key` where it can write `key` instead is hand-authored lowered IR under **KV235**; emitted IR keeps `kovo-key` so fixpoint validation can recompile it.

**Stamps are derived, never required in TSX.** `{cart.count}` and `data-bind="cart.count"` are one fact; the author writes the typed expression, the compiler emits the stamp. Classification: an expression that is an element's sole text child stamps that element; an expression in mixed content gets a synthesized `<span data-bind>` (reported in `kovo explain component` — wrap it yourself if the extra element matters); an expression in attribute position lowers to a named derive (above). Hand-written stamps remain valid compiler input so the fixpoint gate can recompile emitted IR (Constitution #3), but app-authored TSX must not carry derivable stamps: redundant stamps are lint **KV223**, and a stamp that disagrees with the expression it wraps is an error (**KV222**). The same rule covers `kovo-fragment-target`, `kovo-deps`, and `kovo-key`: app TSX writes typed queries and `key`; emitted IR carries residual strings and validates them. A component module in app source that hand-authors the lowered string/template IR instead of TSX is **KV235**. The general rule, normative framework-wide: **a residual string may be _validated_ in emitted IR, but TSX never requires a string the compiler can derive from a typed expression.**

**The ceiling is explicit, and the escape hatch is defined.** Anything beyond paths, derives, and keyed lists flips to a server fragment — or to an **isomorphic island**: `isomorphic: true` on a component also emits its render function into the client module; on query/state change the island re-renders itself and self-morphs. It is the _same_ render function the server uses (partials cannot drift), and it is lint-gated (**KV318**: justification comment required) — this is the sanctioned SPA-creep escape, bounded by KV318 and the §4.9 update-coverage proof.

The "partials cannot drift" guarantee holds only when the client self-render binds the **same arguments** the server bound. Projected children and named slots are `Html`-typed arguments supplied at the server render site and ship once in the initial HTML (§4.5); a client self-render has no slot/children arguments. To keep the self-render sound for a children- or slot-accepting isomorphic island, the self-morph **must preserve the projected-children DOM regions in place and re-render only the island's own positions** — the loader marks each projected-children/slot region (`kovo-slot="children"`, `kovo-slot="<name>"`) at server render, scopes the self-render's morph to the island's own attributes/text/structure, and treats the marked regions as morph-stable holes whose subtrees the self-render does not touch. The island's render therefore reads its slot arguments as the existing region contents, not as fresh `Html`. A children- or slot-accepting component whose render cannot be partitioned this way — where the island's own positions interleave with projected content such that the slot regions are not contiguous, statically locatable holes — cannot be made isomorphic without drift and is compile error **KV316**, whose message shows the interleaving position and the fix menu (lift the dynamic part above or below the slot, make the children a stamped-prop-hoistable inferred fragment target per §4.5/KV230, or drop `isomorphic: true` and use a server fragment).

### 4.9 Update coverage (exhaustiveness)

§10.6 proves every invalidated query has an optimistic story; this is the same theorem one hop further down the dataflow: **every query- or island-local-state-dependent position in rendered output must have a declared update status**, or the page renders data it will never refresh — the silent-staleness bug §10.6 exists to kill, recurring on the client side of the wire. The framework rejected runtime dependency tracking (§3.1), and the thing removed was also the thing that guaranteed coverage in SPA frameworks; a static plan needs a static completeness proof.

During lowering, the compiler classifies every render-output position that reads query data or island-local state:

| Status       | Meaning                                                                                                                                                                                                                                                                                                                                 | Latency                           |
| ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------- |
| `plan`       | lowered to a binding, derive, or stamp (§4.8)                                                                                                                                                                                                                                                                                           | instant; participates in optimism |
| `isomorphic` | island self-renders on change (§4.8, KV318); a children/slot-accepting island self-morphs in place over preserved projected-children regions, and a non-partitionable render is **KV316** (§4.8)                                                                                                                                        | instant; costs the render module  |
| `fragment`   | inside an inferred server-refreshable query target — mutation success may re-render it after invalidation ∩ live targets (§9.1); **not a state remedy** — the morph carries no island-local `kovo-state` serialization (§9.1), so a nested island declaring local `state` inside the target is **KV420** (§4.5), not a covered position | 1 RTT — **no optimistic update**  |
| `renderOnce` | declared immutable for the document's lifetime (suppression recorded in source)                                                                                                                                                                                                                                                         | never                             |

A position fitting none of these is **KV311**. The teaching error shows the classification, why the position exceeds the plan grammar, and the fix menu — extract a derive, lower to a CSS/attribute toggle, make the query-backed component reconstructible as an inferred target, remove `disableServerRefresh: true` if it is suppressing a valid target, use `isomorphic: true`, or declare `renderOnce`:

```
kovo check coverage
query cart:
  cart-badge   span text          plan: binding ✓
  cart-badge   button class       plan: derive (CartBadge$button_class) ✓
  cart-badge   conditional <dot>  UNHANDLED ⚠ KV311
     → derive + [hidden] toggle, inferred fragment target, isomorphic, or renderOnce
  mini-cart    (subtree)          fragment ✓ — no optimistic update (inferred target)
```

Like KV310, the check runs at two altitudes off one derived set: in the compiler during lowering (editor-visible) and as `kovo check coverage` (CI/agents). Together with §10.6 and the touch graph, a mutation's full dataflow is exhaustiveness-checked edge by edge: write → invalidated queries (§11.1) → optimistic prediction (KV310) → every dependent DOM position (KV311) → fragment reconcile (§9.1). No edge from this client's own statically analyzable modeled writes may be silently uncovered; raw-SQL seams, DB-engine fan-outs, wall-clock freshness, and cross-session liveness must be declared through their checked escape hatches or treated as outside the v1 automatic freshness guarantee.

### 4.10 Registry-bounded dynamic rendering

Some content is authored by an LLM or stored in a database as **rich text that embeds components**
— well-formed XML tags drawn from a fixed vocabulary (`<kovo-chart title="Q3">…</kovo-chart>`).
The tree's _shape_ is unknown until runtime, but the _set_ of renderable components is fixed ahead of
time. This is the one place the static composition model of §4.5 does not reach — the call graph
cannot be scanned because it is data — so v1 provides a bounded runtime primitive rather than an
app-authored dynamic dispatch (which remains KV230/§4.5).

`renderTree(registry, nodes)` renders such a tree **server-side and once** (the §4.5 posture: no
client re-render of the dynamic shape). It is framework code, not lowered app TSX, so the static
ban does not apply; the bound is the registry:

- **The registry is the pre-approval boundary.** `renderRegistry({...})` is a closed map of tag →
  `{ component, props }`. A tag with no entry can never render a component, so the approved set is
  structural, not conventional. Registered components must be server-renderable; an `isomorphic`
  component (§4.8) defeats the lazy posture (it ships its render module) and is not a valid entry.
- **Parsing is the trust boundary.** The untrusted string is parsed into a plain-data AST and is
  **never reconstituted into HTML** — there is no markup sink for an injection to reach.
  `parseComponentXml` is pure and side-effect-free, so validation may run at write time and the AST
  stored, leaving render-time on already-trusted data.
- **Output is safe by construction (§4.8).** The walker HTML-escapes every text node itself (the
  bare JSX runtime inserts a child verbatim — escaping dynamic text is otherwise the compiler's job),
  passes only schema-declared props to a component (attributes outside the `s.object` schema are
  dropped, never spread through), and never produces `trustedHtml`/`trustedUrl`. Attribute and URL
  emission still pass through the §4.8 attribute-escape, URL-scheme allowlist, and `on*`/`srcdoc`
  refusal. The XSS review therefore reduces to one invariant: no registered component binds untrusted
  data into a `trustedHtml`/`trustedUrl` sink.
- **Attributes validate against the component's own schema (§6.3).** The same `s.object({...})` that
  types the component validates the LLM-supplied attributes — one source of truth. Invalid attributes
  are dropped (and defaulted where the schema declares a default); a tag whose required attributes
  cannot be satisfied, or an unknown tag, renders its children with the wrapper dropped rather than
  failing the whole document.

Lazy loading needs no new mechanism: because the tree renders to light-DOM HTML on the server, an
unregistered-or-unused component ships no client JS, and a rendered component's handlers still load
on first interaction through the §4.4 loader.

---

### 13.1 StyleX and Theme Tokens

Kovo component styles are authored as TSX/JSX source with `@kovojs/style`
objects. The compiler may extract static `style.create(...)`, `style.defineVars(...)`,
`style.keyframes(...)`, and compiler-known imported token
references into ordinary CSS assets, but it may not turn lowered style IR into a
second app-authoring surface (§5.2). Extracted rules are global atomic CSS with
stable provenance, not shadow-DOM scoped rules; components remain light DOM so
form participation, IDREFs, and accessibility relationships cross component
boundaries. Static keyframes resolve to deterministic animation names and are emitted once.

`style.create(...)` returns module-provenance `StyleHandle` capabilities, not public rule records.
The public runtime representation is fieldless and opaque: `style.attrs(...)` accepts only handles
minted by the same installed `@kovojs/style` instance, falsy entries, or nested arrays of those
values. Type assertions, object spreads, marker-shaped records, raw representation tuples, and
handles from a second package instance MUST fail the runtime provenance check. Compiler-owned rule,
style-key, and source-attribution metadata is available only through internal build capabilities;
it MUST NOT appear in app-public declarations. Rendered HTML may still carry `data-style-src`
provenance because that is inspectable output, not an app-authoring input.

Theme tokens are document CSS custom properties. Components may reference typed public tokens from
`@kovojs/style`, but the runtime value is still resolved by the document. No core runtime theme
store, hydration graph, or shadow boundary is introduced for theme selection. The one app-facing
theme constructor is `defineTheme({ seed, ...options })`; variable-override classes and base-theme
derivation are not public APIs. Expanded StyleX, stylesheet, and theme-token guidance lives in
`site/content/guides/styling.md`.

### 13.2 `kovo-key` runtime-identity contract (normative)

`kovo-key` is the single lowered runtime identity for a keyed row (§4.8): the same string is written once and shared verbatim by stamps, morph, inferred fragment-target instance suffixes, submitted-form identity, and optimistic reordering. This subsection pins the order-of-operations every consumer MUST follow so identity stays stable across reconciliation, delta merge, and optimism.

1. **Identity, not position.** A row's identity is its `kovo-key` value, never its array index or DOM order. A key value is stable for the lifetime of the row it names and unique within its `data-bind-list` (uniqueness is a render-site invariant, asserted in dev). The authored TSX identity is `key={...}`; the compiler lowers it to `kovo-key` (§4.8). The `kovo-key` field MUST be one of the projected query-shape fields that feed the render-plan fingerprint (§5.2.1).
2. **Keyed reconciliation order-of-operations.** On any array change the loader reconciles existing `[kovo-key]` children against the new keyed set in a fixed order: (a) **match** existing children to new rows by key; (b) **remove** children whose key is absent from the new set (or named in a delta removed-key list, §9.1.1); (c) **insert** rows whose key is new by cloning the row template; (d) **reorder** matched children to the new key order, moving existing nodes rather than recreating them; (e) **bind** item-relative paths (`.qty`, …) on every surviving and inserted child. Steps run in this order so a moved row preserves its node identity (and its UA state — focus, selection, in-flight transition) instead of being destroyed and recreated.
3. **Morph identity.** A `<kovo-fragment>`/delta morph matches incoming keyed rows to live DOM by `kovo-key`, applying the same match/remove/insert/reorder/bind order as (2). The morph MUST NOT key by position; a row whose key is unchanged is morphed in place (same node, same island signal) even if its order moved.
4. **Submitted-form identity.** A keyed mutation form lowers its row key to `kovo-key` on the form element (§6.3), and the post-commit fragment target and any failure re-render (§9.2) resolve back to that same key — so the response patches the originating row, not a positional neighbor, even if the list reordered between submit and response.
5. **Wire-stable identity.** A server-authored identity MUST survive UTF-8 serialization, HTML input preprocessing, and (when it is a successful-control routing/name/value) native form encoding as the same string. Kovo fails closed with KV236 before emitting NUL, carriage returns, or lone UTF-16 surrogates in DOM identities; control names and identity-bearing single-line/hidden values additionally reject line feeds because every native form encoding normalizes line endings to CRLF. A hidden `<input>` MUST NOT use the ASCII-case-insensitive name `_charset_`: HTML reserves that cross-attribute tuple and replaces its submitted value with the selected encoding label, so Kovo rejects it at compiler, runtime-render, and CSRF-configuration boundaries; an ordinary non-hidden `_charset_` field remains valid. An `<option>` without explicit `value` must already equal the browser's stripped/collapsed fallback value (use an explicit stable `value` when its visible label needs formatting whitespace). Ordinary server-prefilled `<textarea>` content is not identity: it permits CR/LF and follows native multiline form normalization, while still rejecting NUL and lone surrogates that HTML/UTF-8 would replace. Valid Unicode scalar values, including surrogate pairs, remain valid. The identity restrictions apply to native form association/submission routing fields and Kovo identity/target stamps (`key`/`kovo-key`, fragment targets, form keys, query/live targets); ordinary business content and non-authority display text are unchanged. Compiler-known violations are build errors and runtime-dynamic violations abort rendering rather than silently normalizing two source-distinct records into one browser identity.
6. **Optimistic reordering.** An optimistic transform (§10.4) that inserts, removes, or moves a keyed row predicts the new key order; the predicted rows reconcile by key under (2), so the optimistic prediction and the arriving server truth (§10.3) align on identity and a moved row is not double-rendered. Rebase (§10.4) re-applies pending transforms over the keyed identity, never over array indices, so a reorder that lands between prediction and truth does not misattribute a later transform to the wrong row.

The two prod-delta soundness claims that cite this contract — keyed-collection merge-by-identity and removed-key deletion (§9.1.1) — hold because every consumer above keys off this one stable identity.

---

---

<!-- Source: spec/05-compiler.md -->

# Compiler (SPEC §5)

This file is incorporated by reference from [../SPEC.md](../SPEC.md) and is normative for Kovo framework behavior.
The root spec remains the entry point and cross-reference index; this module owns the detailed contract below.

## 5. Compiler

### 5.1 Pipeline

```
cart.tsx ──parse──▶ analyze ──lower──▶ cart.server.js + cart.client.js ──(prod only)──▶ minify*
                       │
                       ├─▶ generated/registries/*.d.ts   (module aliases, fragment targets, query keys, domains,
                       │                                  routes, element ids, invalidation sets)
                       ├─▶ generated/touch-graph.ts      (§11.3 — reproducible/checkable on demand)
                       └─▶ generated/optimistic/*.ts     (§10.4; emitted output; authored transforms override)
```

\* Minification may never rename exported handler symbols or anything appearing in HTML attributes (Constitution #1 — enforced because those names are load-bearing at runtime); this holds in prod too, where payloads are delta-encoded (§9.1.1) but names stay verbatim. The prod build gives each final client representation one immutable content-addressed URL and stamps the separate **app build token** defined in §5.2.1 into documents and data/fragment responses, so §9.1.1 base-version validation can fail loud on deploy skew instead of patching stale DOM silently.

### 5.2 Hard rules (normative)

1. **Source-derived names and content-addressed modules.** Extracted handlers are named `Component$fnName`, or `Component$element_event` when anonymous (lint `KV210` nudges naming). Framework client modules use only the immutable path grammar `/c/__v/<representation-digest>/<module>` from §5.2.1. Author version strings, `?v=` cache busters, truncated hashes, ETag-selected identity, and render-plan fingerprints are not module identity.
2. **1:1 file mapping.** `x.tsx` → exactly `x.server.js` + `x.client.js`. No heuristic chunking. A prod-only merge pass for tiny modules is opt-in (`kovo.config: mergeClientModules`), defaulting off.
3. **Fixpoint invariant.** `compile(compile(src)) === compile(src)`; the IR is valid input. CI test ships in the starter template. Paired with a **semantic gate**: `render(src) ≡ render(compile(src))` — authored and lowered components must produce byte-identical HTML over the test corpus (a browser-free differential suite), so the fixpoint proves behavior preservation, not merely syntactic idempotence.
4. **Platform-behavior emission.** Where the compiler proves a handler equivalent to a declarative platform feature (dialog open/close → invoker commands; popovers; `<details>`; pure-CSS state via `:has()`), it emits the attribute and drops the handler. `kovo explain` reports each substitution.
5. **Teaching errors.** Every diagnostic shows the lowering: what would have been generated, why it can't be, and the fix menu.
6. **Registry and app-membership atomicity.** Registry `.d.ts` emission and the app-contract
   membership check (§6.2.1) are part of every compile; `kovo dev`, `kovo check`, and `kovo build`
   derive both from one immutable project snapshot before type-checking or authored evaluation.
   A stale registry, a compiled app-scoped declaration omitted from `assemble`, or membership from
   another module generation is unrepresentable, not just unlikely. Emitted registries contain
   source-derived identities; they are not an ambient runtime registration mechanism.
7. **TSX-only authoring.** TSX is the sole app-authoring surface. The lowered IR is an output format: valid Kovo source for fixpoint/render-equivalence gates and readable artifacts, but not something app code hand-authors or vendors. Hand-authored lowered IR in app source is **KV235** with a teaching message that shows the TSX equivalent. There is no suppression pragma or ejection workflow in v1; a front-end gap is fixed in the compiler or recorded as a SPEC conflict.
8. **Public imports in app source.** App-authored source may import Kovo packages only through documented public entrypoints. Imports from framework-maintenance subpaths (`@kovojs/*/internal`, `kovo/internal`) and compiler-emitted ABI subpaths (`@kovojs/*/generated`) are invalid in app source and must produce a teaching diagnostic. Compiler-emitted modules may import generated ABI subpaths such as `@kovojs/browser/generated`; those imports are compiler-owned artifacts, not app-authored API. Generated app artifacts are reproducible outputs, not app dependencies: app-authored modules MUST NOT import app-local generated modules such as `src/generated/*`, and app-local generated artifacts MUST NOT be checked in. App-facing tests and scripts use authored entry points plus public `kovo emit`/`kovo explain`/`kovo check` flows; direct generated reads are reserved for compiler/build internals and on-demand verification artifacts that are created during the command.
9. **Source proof before deployment proof.** Standalone `kovo check` MUST regenerate the nearest
   TypeScript project's `tsc --noEmit` result and the compiler/security graph from current authored
   source, fail rather than accept an absent graph, emit the stable, inspectable `kovo-check/v1`
   surface, and stop before deployment-preset, artifact, least-privilege, and §14 retention proof.
   `kovo build` MUST rerun that same source verifier, then enforce those deployment obligations
   (including KV417) before writing deploy artifacts. Build reuses the verifier as a deployment gate,
   not a separate policy, and check never invents retention merely to become green.
10. **Post-parse decisions use typed facts, not source strings.** After parsing, the compiler's post-parse phases (`lower/**`, `validate/**`, `analyze/**`, `emit/**`, and `graph.ts`) MUST decide from typed model facts and spans, never from raw source snippets, regexes, `getText()`/`getFullText()`, or ad hoc string slicing; the scanner/parser is the sole boundary that reads source text into typed facts. Permitted source-text uses elsewhere are narrow: diagnostic source-frame rendering, span-based source-patch application by known offsets, generated-artifact body carry and `renderSource()` emission, generated-artifact verification, IR-header provenance checks (`source.startsWith(compilerIrHeader)`), binding-path grammar parsing on typed `.path` fields, URL/route parsing of an extracted literal `attribute.value`, import-specifier boundary validation for the public/generated/internal Kovo subpath rule above, and name-formatting of model-derived identifiers. A mechanical kovo-check guard enforces this.
11. **Output safety is contextual and default-on.** The server renderer and the client update plan MUST contextually encode every interpolated query/state value for its sink — escaped text for text content, attribute-value escaping for attributes, the §9.1 script-data encoding for JSON islands — and MUST encode identically (bound by render-equivalence, rule #3). Pair-dependent HTML sinks MUST classify the browser-effective tuple from the same pinned attribute snapshot and renderer order: attribute names use HTML ASCII-case-insensitive matching, omitted values do not participate, and the first emitted duplicate owns the browser decision. In particular, `<meta>` refresh `content` is an executable navigation sink whenever the first rendered `http-equiv` attribute has the ASCII-case-insensitive value `refresh`; a later differently-cased duplicate cannot replace that decision. Plain bindings may reach only safe contexts; the unsafe output contexts and the URL-scheme allowlist are defined in §4.8 and gated by **KV236**. The only suppression is the typed trusted-HTML escape hatch (§4.8); there is no raw-string ejection. A sink renderer or any other app-authored presentation layer that consumes streamed/model output is bound by the same obligation (§9.1).
12. **Security-critical effects lower to a finite compiler-owned IR.** The scanner derives every
    supported browser-handler and structured-server effect as one exact
    `kovo-security-operation-ir/v1` operation before emission (§4.3, §6.6). The same closed union
    contains two compiler-control records: `server.handler.root` proves that each supported
    query/mutation/endpoint/webhook/task root was enrolled, and `server.helper.call` records an exact
    same-file authority transfer discharged by the bounded bottom-up summaries in §6.6.
    Generated client
    modules carry their browser subset through the compiler-only `@kovojs/browser/generated`
    `securityHandler` ABI; generated server modules carry the corresponding immutable manifest for
    component-graph and explain consumers. Neither manifest is caller-supplied enforcement or a
    runtime sandbox: the pre-evaluation compiler gate owns the supported-subset decision, and the
    C9 sink inventory owns each real runtime door and the capability-closure owner for those two
    control records. Unknown terminal calls, raw capability/DOM
    escapes, ambiguous receiver joins, and unreviewed authority transfer fail with **KV449** before
    output. The generated wrapper and manifest are valid only as provenance-marked compiler IR for
    the rule #3 fixpoint/render-equivalence gates; rule #7/#8 still forbid app-authored lowered IR or
    generated-ABI imports. An app-scoped root additionally carries the exact proved `defineKovo`
    receiver and owning `assemble` identities from §6.2.1. Direct receiver calls and ordinary
    immutable local import/re-export aliases are supported; a destructured factory, wrapper result,
    computed member, cast/structural copy, mutable or ambiguous receiver, duplicate package
    identity, missing assembly membership, or second assembly is closed before output. Once
    enrolled, `app.query`/`app.mutation`/`app.endpoint`/`app.task` emit the same
    `server.handler.root` family and callback facts as their primitive counterparts. A missing,
    spread/computed, imported, aliased, reassigned, or otherwise unresolved callback root is KV449;
    it cannot disappear by producing no manifest row.
13. **Authored-source provenance stays compiler-owned.** Compiler/build graph facts for component,
    query, mutation, route/page, endpoint, agent, tool, and durable-task declarations carry
    `SourceAnchor { file, start, end }`, where offsets are zero-based UTF-16 code-unit positions,
    `end` is exclusive, and `file` names the exact analyzed source input. Compiler-generated form
    transport and atomic-style facts additionally carry `generatedFrom` pointing to the authored
    form or style declaration that owns them. Agent-to-tool bindings, tool-to-mutation bindings,
    and task-to-query/mutation/task composition edges carry their own exact authored identifier
    anchor rather than borrowing a declaration anchor. These anchors come from the pinned parser
    snapshot or an explicit lowering offset map; a post-parse consumer MUST NOT rediscover them
    with symbol or text matching. An invalid, conflicting, summary-inconsistent, or unresolvable
    compiler anchor fails closed instead of falling back to a heuristic source match. Source
    provenance belongs to graph, diagnostic, and development artifacts; it does not enlarge an
    executable route or browser/server helper ABI.

#### 5.2.1 Client representation, render-plan, and app-build identities (normative)

Kovo derives exactly three distinct build-coherence values but exposes exactly two external carriers. The immutable client-module URL carries the representation digest; `Kovo-Build` and its response/meta equivalents carry the app build token. The render-plan fingerprint is an internal input folded into the app build token and is never stamped separately. These values prevent cache aliases and mixed-deploy merges; they are not signatures, do not authenticate a producer, and are never an app security principal.

1. **Client representation digest.** Every immutable client-module URL contains one full 64-character lowercase hexadecimal SHA-256 digest. Its domain-separated, UTF-8 byte-length-framed preimage is exactly: the domain `kovo-client-module-representation/v1`, the fixed media type `text/javascript; charset=utf-8`, and the exact final well-formed UTF-8 JavaScript bytes after all compiler/browser import rewriting. The canonical URL is `/c/__v/<representation-digest>/<module>` with no query string. A fragment may select an export at a reference site, but is not part of the stored representation identity. The URL contains no render-plan fingerprint, author version, custom content type, truncated digest, or second content hash. Identical final representations keep the same digest when only render/query grammar changes. A resolver MUST re-verify the returned 200 body and fixed metadata against the requested digest before serving it; mismatch fails closed.
2. **Render-plan fingerprint.** The compiler derives a separate full 64-character lowercase hexadecimal SHA-256 fingerprint over canonical byte-length-framed facts that include, at minimum: (a) the **projected shape of every query** — field set, nesting, nullability, and element type, including each `kovo-key` field per keyed collection (§4.8); (b) the **update-plan grammar version** — the binding/derive/stamp lowering vocabulary and §9.1.1 delta deep-merge semantics; and (c) the core-owned **wire-input grammar schema** defining canonical target, query-dependency, and live-target identities (§9.1). A change to any projected query shape, keyed-collection identity field, update-plan grammar, or wire-input grammar schema MUST change this fingerprint even when every client-module byte remains unchanged. This is one centrally derived compatibility fingerprint, not a module URL component, direct wire token, or authenticator.
3. **App build token.** The framework derives one full 64-character lowercase hexadecimal SHA-256 token directly from byte-length-framed values: the domain `kovo-app-build-token/v1`, the render-plan fingerprint, and the ascending sorted set of exact active immutable client-module hrefs. There is no nested module-graph digest, delimiter-only encoding, truncation, author version, custom content type, or caller-supplied token. The active set MAY contain simultaneous representations of one logical module path and MUST exclude resolver history retained only for skew recovery. A module-less app hashes the empty active set and still has a non-empty token.
4. **Ownership and finalization.** An injected `VersionedClientModuleStore` supplies only four storage operations: `retain` stores one immutable representation without changing the active deployment; `readActiveSnapshot` returns the durable exact `{ modules, renderPlanFingerprint }` snapshot; `replaceActiveSnapshot` atomically commits that complete snapshot; and `resolve` reads retained representation history. The framework closes it behind a `VersionedClientModuleRegistry` facade and derives every representation href and app build token itself. Store-supplied hrefs, `buildToken`, or fingerprint setters have no authority. A store replacement that is not atomic or whose immediate readback differs from the requested exact snapshot fails closed. Production registration/finalization stages every compiler module together with framework-mandatory and stable/manual modules, performs one complete snapshot replacement, and then seals it: after finalization the manifest and token are frozen, request handling performs no hashing or storage writes, and later `put` attempts fail with KV417. Development/HMR replaces the render fingerprint and complete active module set as one atomic snapshot and publishes the new token only after all entries validate, retain, commit, and read back successfully; retained history is not silently promoted into that snapshot. The framework-mandatory loader participates in the active href set and in §14 retention requirements even when it is the deployment's only client module.
5. **Two external carriers.** The app build token — not either component value — is carried by every full page render (document meta, §9.5), every `<kovo-query>`/`<kovo-fragment>` delta or full response (§9.1.1), and every `/_q/<key>` read response (§9.4). Client-module URLs carry only their representation digest. The render-plan fingerprint has no third stamp, header, URL component, or manifest carrier.
6. **Comparison.** The client applies a delta only when the response app build token equals the token the held base was produced against (§9.1.1); on mismatch it discards and refetches full (§9.4). Every enhanced typed-read, mutation, and HMR request carries the immutable document token as `Kovo-Build`; once dispatched to one app build, a missing or unequal value fails before target/query decoding or handler work. A `/_q/` or target-bearing response whose token differs from the receiving document's token is a §14 build-skew event. A retained serving layer MAY select the matching immutable app and decoder by exact token, but may never heuristically dual-decode one request. All three identities are opaque to app code; only equality is defined.

#### 5.2.2 Prod render-equivalence gate (normative)

The prod build is sound only if delta encoding reconstructs the dev full render. The gate, over the differential corpus (§5.2 rule 3): for every query and every change record, `apply_delta(base, render_prod(Δ)) ≡ render_dev(full)`, where `apply_delta` is the §9.1.1 deep-merge plus update plan and `base` is the prior full value. The gate MUST also assert the three-value separation and monotonicity plus the two-carrier rule: a projected-shape or update-grammar change moves the §5.2.1 render-plan fingerprint and app build token, while an unchanged final client representation keeps its representation digest and href; the internal fingerprint never becomes a separate external stamp. A prod build whose delta path or these identity properties fail is **KV416**.

#### 5.2.3 Build artifact provenance (normative)

Every successful `kovo build` MUST add a top-level `provenance` object to the emitted
`dist/.kovo/graph.json`. The object has schema `kovo.artifact.provenance/v1` and contains exactly the
path-independent inputs later certificates and advisories use to identify the framework posture:

- `graphSchemaVersion` is the compiler-owned graph grammar identifier (`kovo.graph/v2`).
  Any incompatible meaning or shape change in the graph moves this value.
- `frameworkPackages` is the unique, ascending sequence of `{name, version}` pairs for resolved
  `@kovojs/*` packages. Resolution starts from the executing `@kovojs/cli` package and the nearest app
  `package.json` inside the lockfile root, follows declared Kovo dependencies recursively under Node's
  actual resolution contexts, and retains simultaneous versions of one package as separate pairs.
  App `dependencies`, `devDependencies`, optional dependencies, and peers seed the walk; resolved Kovo
  packages contribute their production dependencies, optional dependencies, and peers. Missing
  optional packages are absent; a missing required declared package fails the build.
- `pnpmLock.contentHash` is `sha256:<lowercase-hex>` over the exact bytes of the nearest ancestor
  `pnpm-lock.yaml`, with no newline, path, or text normalization. A production build with no such
  lockfile fails before app or config evaluation.
- `securityGuarantees` records schema `kovo.security.guarantees/v1` plus `canonicalHash`. The hash is
  SHA-256 over UTF-8 canonical JSON of the fenced guarantee register in `SECURITY.md`: arrays retain
  order; object keys sort by JavaScript/Unicode UTF-16 code-unit order at every depth; strings,
  numbers, booleans, and null use ordinary JSON encoding; and the serialization contains no
  whitespace. The executing CLI's package manifest embeds this identity, and the security-guarantee
  gate MUST reject a digest that does not match the normative register before that CLI ships.

The stamp contains no absolute paths, filesystem identities, wall-clock time, random values, or
output-directory names. Capturing it before authored config/app evaluation and sorting every set-like
field makes two no-op builds byte-identical; changing any listed input MUST change the emitted graph
bytes. The stamp identifies the build inputs. It is not a signature and does not by itself prove that
the artifact is safe or that the package contents match their version labels.

#### 5.2.4 Proof-graph completion and transactional promotion (normative)

A deploy graph is admissible only when its top-level `proof` record is the exact
`kovo.graph.proof/v2` shape. The record binds the complete analyzed source set, config subset,
executing compiler version, app build token, canonical declared app identity (or `null` when the
app omitted one), and selected posture profile. Its `completion` value is `complete`; a missing
record, an unknown field, an invalid digest, an identity that does not recompute from the graph's
own build-owned inputs, or any other completion value is not a partial success. Human CLI
inspection of this graph requires `--artifact <path>` so a nearby
`dist/.kovo/graph.json` cannot silently become source authority. Source-backed `kovo check` and
`kovo explain` instead derive current facts or consume an explicitly selected non-deployment review
graph; they never infer a deploy claim from directory layout.

`kovo build` writes every neutral and preset artifact beneath one unique sibling staging directory.
Only after source proof, preset inspection, artifact emission, and the complete proof stamp succeed
may it promote that directory to the requested output. Replacement preserves the previous complete
output until the staged output is ready and restores it if promotion fails. Any failure before
promotion leaves the last known-good output byte-for-byte unchanged. Validate-only builds remove
their staging directory and never touch the requested output.

Failed-build evidence is not deploy output. When the operator explicitly enables debug evidence,
Kovo may write one bounded, redacted `kovo.build-debug/v1` record beneath
`.kovo/debug/<build-id>/` at the invocation root. That record contains no environment values,
authored source, stack, absolute path, or partially emitted artifact, and `dist/.kovo` is never used
as a failed-build cache.

### 5.3 `kovo explain`

The compiler's decision tree, on demand. `explain` has one discriminated grammar: the token after
`explain` is always a view (`component`, `mutation`, `query`, `page`, `context`, `task`,
`access`, `agent`, `authorization`, `auth-lifecycle`, `capabilities`, `cookies`, `document`,
`endpoints`, `grants`, `model-boundaries`, `revealed`, `sources-sinks`, `tasks`, `trust`,
`unguarded`, `unscoped`, or the deployment-review view `attest`). Programmatic command callers use
the corresponding exhaustive discriminated request union; graph explanation callers use its
non-attestation `{ view, ... }` subset. Flag-shaped view selectors are not a second grammar. All
output remains stable, diffable text and agents consume the same artifact humans read:

```bash
kovo explain component cart        # lowerings: extracted handlers, derives, capture channels, platform substitutions, attribute merges, triggers
kovo explain mutation cart/add     # writes → domains → invalidated queries → consumers; guard chain
kovo explain mutation cart/add --optimistic   # transform coverage per query; derivation traces + punts (§10.5)
kovo explain query cart            # read set, consumers, every mutation that invalidates it
kovo explain page /products/:id    # emitted modulepreloads, per-route prefetch config, param/search schemas, query payloads
kovo explain capabilities          # held capabilities plus untrusted roots, reviewed doors, exact package verdicts, and closed provenance paths
kovo mcp                           # the same compile/check/explain results over the finite stdio protocol in §11.5
```

The capability-closure rows are the stable rendering of the pre-evaluation proof from §6.6, not a
runtime sandbox trace. Root, door, package-summary, and closed rows are sorted independently of
source traversal order; a closed row retains the exact root-to-terminal path also emitted by KV448.

`kovo mcp` is only a machine-readable command surface for these framework decisions. It MUST use
the dependency-free, finite stdio protocol in §11.5; it is not a general MCP server or an extension
point for application code.

---

---

<!-- Source: spec/06-analyzable-fragment-hand-argument.md -->

# SPEC §6.6 analyzable-fragment hand argument

Status: reviewed non-mechanized hand argument.

This is not a mechanized proof. It is the repository's explicit, reviewable argument for why the
finite transfer rules in SPEC §6.6 compose and what they are adequate to claim. The generated
prohibition table and compiler witnesses make its boundary falsifiable; they do not turn this prose
into a proof assistant result or a proof of the implementation.

## Claim and classification vocabulary

The claim is intentionally narrow: if a semantic root receives a `proved` verdict, every
authority-bearing value on that root's recorded traces was introduced by a recognized root
parameter or reviewed finite operation, preserved only by the transfer rules named in SPEC §6.6,
and consumed only by an operation in the finite server operation vocabulary. Any transfer that the
relation cannot represent must produce KV449 before output.

The ledger classifications apply to the general prohibition, not to the difficulty of its minimal
witness:

- `FUNDAMENTAL` means sound and complete acceptance for the general JavaScript family would require
  information unavailable to this finite source-local decision procedure or would amount to a
  sound and complete decision over general mutable JavaScript behavior.
- `DELIBERATE` means a narrower exact subset could be implemented, but SPEC §6.6 intentionally
  excludes it to keep invocation identity, authority ownership, and review obligations explicit.
- `BUDGETED` means the transfer is in the finite language but evaluation stops at a deterministic
  resource ceiling. No prohibition row is currently `BUDGETED`; the four resource-contract rows are
  the budgeted boundary.

The `FUNDAMENTAL` label does not say every instance is impossible to analyze. For example, the
minimal `mutating-authority-alias` fixture is easy to reject. It says accepting the whole general
family while remaining both sound and complete is outside the claimed decision procedure.

## Compositionality hand argument

Take one semantic root and order its same-file helper summaries callee-first. The argument is by
structural induction over the finite transfer tree after unsupported constructs and exhausted
budgets have been replaced by closed leaves.

At a leaf, root parameters have the exact authority values assigned by the root contract. A finite
operation has a reviewed door and terminal kind. Its result is plain data, except the explicit
principal-scope acquisition whose returned scope is itself a named provenance value. A direct
unsupported use is therefore a closed leaf with one of the eight closed reasons rather than an
unrecorded authority transition.

For an internal expression step, an exact immutable alias preserves its lattice value. Static
destructuring applies the reviewed member transition to each named property. Those rules neither
invent authority nor erase it. `opaque-container`, `mutating-authority-alias`, and
`mutable-ambiguous-join` close precisely where that local substitution argument would stop being
valid.

For a helper step, an exact immutable same-file callable and exact positional arguments determine
the callee's complete authority-input vector. The context-sensitive summary is keyed by that vector,
computed before its caller, and merged back with the original root and ordered transfer prefix.
Assuming the callee summary satisfies the claim, substitution of its parameter provenances for the
caller's argument provenances preserves the claim. A repeated active key closes as `helper-cycle`;
the resource ceilings close before an unfinished summary can be treated as proved.
`unsummarized-nested-callable`, `arguments-rest-spread-recovery`, `call-apply-bind`, and
`foreign-callable` close the cases where callable identity or positional substitution is not exact.

The query no-managed-write posture is an invariant carried in the root state and copied through
every helper summary. It is not inferred again from a helper's name. Thus a proved callee cannot
silently relax its caller's posture.

By the induction hypothesis, every proved child is authority-preserving and every non-compositional
child is closed. The parent can therefore be proved only when all relevant children and summaries
are proved. This establishes the stated compositionality claim for the finite relation, subject to
the limits below.

## Adequacy hand argument

The relation is adequate for Kovo's claimed purpose only inside that finite language. Within it,
authority enters through enumerated root bindings or an explicit operation result, exact aliases and
member projections retain provenance, helper summaries retain the complete transfer prefix, and
terminal operations retain their reviewed kind and door. Those facts are sufficient for downstream
consumers to reconstruct the root-to-transfer-to-sink decision they own without treating an emitted
graph as runtime authority.

The prohibition ledger is complete with respect to the exact unsupported sentence in SPEC §6.6:
`returning-authority`, `throwing-authority`, `opaque-container`, `mutating-authority-alias`,
`mutable-ambiguous-join`, `unsummarized-nested-callable`,
`arguments-rest-spread-recovery`, `call-apply-bind`, and `foreign-callable` each have exactly one
classification, one of the eight closed reasons, and an app-authored source fixture. The focused
compiler test does not grep those fixtures; it compiles every one and requires both an emitted KV449
diagnostic with the named `verdict=closed:<reason>` and the corresponding closed semantic trace.

The four budgets are adequate as deterministic termination guards, not as semantic evidence. The
checked measurement compiles every tracked starter/example source file that currently declares a
shipping server root: 11 source files and 29 emitted semantic roots. None currently reaches
`budget-call-depth`, `budget-node-count`, `budget-operation-count`, or `budget-summary-count`. The
test recomputes that result from the real files and fails if either the root census or binding set
drifts.

## Limits and non-claims

- This hand argument is not a formal operational semantics, a machine-checked soundness theorem, or
  a proof that the TypeScript implementation faithfully realizes every stated transfer.
- The fixtures prove current compiler verdicts for representative authored programs. One witness
  per prohibition does not prove closure over every JavaScript spelling of that family.
- The argument is not a completeness claim. Deliberate and fundamental closures can reject programs
  whose behavior a stronger whole-program analyzer could prove safe.
- Foreign module behavior, proxies, getters, reflective calls, ambient mutation, and runtime code
  outside the compiler-owned sink doors remain outside the adequacy claim.
- The real-root measurement is a regression observation over the named repository corpus. Zero
  binding roots does not predict downstream application shape and does not justify widening or
  removing a budget.
- Emitted graphs and diagnostics are audit evidence only. Runtime capability ownership and C9 sink
  enforcement remain independent obligations.

Any future widening must update the normative transfer sentence, generated ledger, compiler
witnesses, and this argument together. A new accepted construct cannot be justified merely by
removing its KV449 diagnostic.

---

<!-- Source: spec/06-type-system.md -->

# Type System (SPEC §6)

This file is incorporated by reference from [../SPEC.md](../SPEC.md) and is normative for Kovo framework behavior.
The root spec remains the entry point and cross-reference index; this module owns the detailed contract below.

## 6. Type System

One pattern, applied everywhere: **declare facts once → derive every surface → validate residual strings against generated registries.** The only codegen is trivial registry `.d.ts` files; all wiring checks are TypeScript static checks over code that runs as written. Residual strings live in emitted IR and are derived from TSX authoring facts (§4.8); every load-bearing attribute the IR carries (`on:*`, `data-bind*`, `kovo-deps`, `kovo-c`, `kovo-key`, `kovo-fragment-target`, `href`, IDREFs) has a named validator in §11.3, so "all residual strings are validated" is a checkable claim, not an aspiration.

### 6.1 The registries (generated)

```ts
// generated/registries.d.ts (excerpt)
interface HandlerModules {
  '#cart': typeof import('../components/cart/cart.client.js'); /* … */
}
// '#cart' is a compile-time alias only — emission resolves it to a full URL (§4.3)
interface FragmentTargets {
  'components/cart-badge/cart-badge': CartBadgeProps; /* … */
}
interface ComponentRegistry {
  'components/cart-badge/cart-badge': typeof import('../components/cart-badge.js').CartBadge; /* … */
}
interface QueryRegistry {
  cart: typeof cartQuery;
  product: typeof productQuery;
}
interface MutationRegistry {
  'cart/add': typeof addToCart;
}
interface RouteRegistry {
  '/products/:id': typeof productRoute; /* … */
}
interface InvalidationSets {
  'cart/add': 'cart' | 'product'; // compiler-owned IR from the touch graph (§11.1);
  // app.mutation query-handle bindings are checked against this set (§10.6)
}
// also: DomainKey (schema domains), PageIds (per-page element ids, §6.4/KV221),
// ComponentPackagePrefixes + ComponentPackageRegistry (§6.1.1)
```

`FragmentTargets` is generated from inferred server-refreshable query components, not from an
author-written `fragmentTarget` option. Singleton targets use the component registry key as the type
identity and the derived DOM leaf as the ordinary wire target; repeated targets add their typed
instance identity at the wire edge (`cart-row:p1`) while the registry records the serializable prop
shape required to reconstruct any instance. `disableServerRefresh: true` suppresses target generation
for that component and appears in explain output.

Component registry keys are derived as `<module path relative to the package src root>/<dom leaf>`, with
`tests/integration/fixtures/` used as the fixture root in the integration suite. The DOM leaf remains
the exported binding's kebab-case form; the generated registry key is for TypeScript, fragment targets,
graph facts, and uniqueness diagnostics only.

The same source-derived registry rule applies to app-authored webhooks, mutations, queries, domains,
and tags: their module-relative exported binding identity is the generated graph key unless the
primitive declares an external address string instead (§4.1). Routes and endpoints keep explicit path
strings because those strings are the public HTTP addresses.

App-scoped handles are the authoring identity and generated registry keys are compiler/runtime IR.
An application does not augment `QueryRegistry`, `MutationRegistry`, or `InvalidationSets` by hand
and does not read a query key to wire optimism. Registry generation resolves each proved handle's
exported binding and assembly membership in one snapshot; an orphan or duplicate handle prevents
emission rather than producing a partial registry.

### 6.1.1 Package component prefixes

Component packages declare their HTML namespace once in their package manifest:

```json
{
  "name": "@acme/primitives",
  "kovo": {
    "prefix": "acme-"
  }
}
```

The field is required for any dependency that exports Kovo component primitives intended to define a
package-owned public HTML vocabulary. A package prefix is lowercase ASCII, dash-terminated, and
becomes part of that package vocabulary: package behavior attributes use the effective prefix
(`acme-menu="account-menu"`), `kovo explain component <name>` uses it for provenance, and packages
should encode it in their exported component binding names (`AcmeCartBadge` -> `acme-cart-badge`)
because component DOM leaves are always derived from bindings (§4.1). App-local components may remain
bare-named; vendored source such as `@kovojs/ui` installed by `kovo add` is app source, not a
component package, so its names are the app's names.

Prefix uniqueness is app-wide. During registry generation the compiler collects every imported component package, applies app aliases, and requires that no two packages have the same effective prefix. The alias escape hatch is app-side and explicit:

```ts
// kovo.config.ts
export default {
  packagePrefixes: {
    '@acme/primitives': 'acme-primitives-',
  },
};
```

Aliases affect only the consuming app's effective package behavior/provenance prefix; they do not
rewrite component binding-derived DOM leaves, the package manifest, or the package's documentation.
They are for package-vocabulary collision repair, not style preferences, because changing prefixes
changes the HTML behavior-attribute vocabulary an app serves.

The `kovo-` prefix family is reserved for first-party packages. Only packages whose manifest `name` is in the `@kovojs/*` scope may declare or be aliased to a prefix beginning with `kovo-`; `@kovojs/ui` declares `kovo-ui-`. This is a reservation check inside the same general prefix-registration rule, not a separate first-party naming mechanism.

Package behavior attributes ride the effective package prefix: `kovo-tooltip="pricing-tip"`, `acme-menu="account-menu"`, and so on. The `kovo-*` attribute namespace is reserved for framework-owned attributes and future loader/compiler growth. Package behavior attributes are compiler-known attributes supplied by the owning package; when a behavior value is an IDREF, it participates in the same page/component id registry as `commandfor`, `popovertarget`, `for`, and `aria-*` and is validated by KV221.

A duplicate prefix, invalid prefix, missing prefix on an imported component package, or non-`@kovojs/*` attempt to use `kovo-*` is **KV234**. The teaching error names both packages when there is a collision, shows the effective prefix that would have been emitted into package behavior attributes and component explain provenance, and prints the alias fix:

```text
ERROR KV234 package component prefix conflict.
  prefix: acme-
  packages:
    @acme/primitives (package.json kovo.prefix)
    @other/acme-widgets (package.json kovo.prefix)
  emitted names would collide: acme-tooltip="..."
  fix: add an app alias, for example packagePrefixes["@other/acme-widgets"] = "other-acme-"
```

### 6.2 Typed surfaces (summary table)

| Surface               | Source of truth                                             | What TypeScript proves                                                                                                                                                                                                                                                       |
| --------------------- | ----------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Handler refs          | client module exports                                       | `cart.remove` exists; params required & typed; typo = error                                                                                                                                                                                                                  |
| Form fields           | mutation input schema                                       | names ∈ schema; types match; **completeness** (missing required field = error); coercion declared once (KV242)                                                                                                                                                               |
| Fragment targets      | component registry                                          | target exists; patched with the right component's props                                                                                                                                                                                                                      |
| Query data / bindings | Drizzle select shape (`$infer`) + `JsonValue` boundary      | `data-bind` paths exist; column rename propagates to every template; nullable traversal requires `?.` or a derive (KV227, §4.8); query values are serializable client wire payloads, so `Date`, `Map`, functions, class instances, and other non-JSON values are type errors |
| Invalidations         | domain layer / touch graph                                  | invalidated keys exist; optimistic exhaustiveness in `tsc` via emitted invalidation sets (§10.6)                                                                                                                                                                             |
| Errors                | declared error codes                                        | `onError` receives exhaustive discriminated union                                                                                                                                                                                                                            |
| Guards                | guard combinators                                           | `req.session.user` non-null under `authed`; guards receive the validated args/instance key (§10.3) so ownership is expressible; static audit of unguarded mutations, routes, and queries, and IDOR audit (KV414) over `owner:` tables                                        |
| State                 | `JsonValue` constraint                                      | serializability by construction                                                                                                                                                                                                                                              |
| Routes / links        | `route()` declarations (§6.4)                               | `href`/`<Link>`/`redirect()` target exists; path params required & typed; search params typed; route rename propagates to every link                                                                                                                                         |
| GET forms / URL state | route `search` schema                                       | field names ∈ search schema; coercion declared once; the §7 URL channel is typed                                                                                                                                                                                             |
| IDREFs (L0 wiring)    | compiler id registry                                        | `commandfor`/`popovertarget`/`for`/`aria-*` reference an id that exists in scope (KV221)                                                                                                                                                                                     |
| Sessions              | declared session schema (§6.5)                              | `req.session` fully typed; instance keys (§10.2) and guard refinements rest on typed fields                                                                                                                                                                                  |
| Derives               | declared inputs (§4.8)                                      | derive inputs exist in `QueryRegistry`; input types match query shapes; bound attribute targets type-checked                                                                                                                                                                 |
| Stamp lists           | query result element type                                   | `data-bind-list` paths are arrays; item-relative paths exist on the element type; `kovo-key` names a real field (§4.8)                                                                                                                                                       |
| Slots / children      | hoisted component refs (§4.5)                               | fragment-target children lower to component references with serializable props (KV230)                                                                                                                                                                                       |
| Component props       | first `render` parameter (§4.1)                             | call sites may pass exactly the annotated render-input props after query result keys are removed; unannotated/`any` render input means no ordinary props; `props` metadata must match this derived shape                                                                     |
| Query args            | first `render` parameter + query `args` schema (§4.1/§10.2) | components bind args from their own derived call-site props; mappers cannot invent props outside the render annotation; coercion declared once; instance keys typed end-to-end (store, wire, optimism)                                                                       |
| Update coverage       | render-output classification (§4.9)                         | every query/state-dependent DOM position has a status — `plan` / `isomorphic` / `fragment` / `renderOnce`; none is KV311                                                                                                                                                     |
| Opaque projections    | declared output schema (§10.2)                              | `sql<T>`/raw projections carry `s.*` output schemas + a `reads:` table set (KV410); `reads:` checked against exemption, folded into the read set; result shape runtime-verified (§11.2)                                                                                      |
| SQL statement safety  | managed DB-handle contract (§10.2/§10.3)                    | executable SQL text reaches framework-managed DB handles only as typed builders, parameterized SQL values, or audited `trustedSql(...)`; scalar request data binds as parameters, while identifiers/keywords come from schema facts or typed allowlists (KV422)              |
| Output safety         | binding sink + value brand (§4.8)                           | every binding/derive into an unsafe output context (raw HTML, URL-scheme attr, `on*`, `style`, `srcdoc`, script/JSON) is `trustedHtml`/`trustedUrl`-branded or it is KV236                                                                                                   |

#### 6.2.1 App-scoped declaration contract

An app declares its runtime context exactly once with `defineKovo({...})`. The returned
`KovoContract` is a declaration owner, not a mutable application aggregate. Its receiver methods
`route`, `layout`, `query`, `mutation`, `endpoint`, and `task` are the ordinary authoring factories;
each returns a named, declaration-emit-stable opaque handle interface. Request, validated session,
managed read-only or transactional DB posture, declared environment projection, query input/result,
mutation error/payload, route params/search, task input, and endpoint request/result types flow from
that one contract. Authors MUST NOT need to name `AppRequest`, `Reader`, `QueryLoadContext`,
`MutationContext`, `ComponentRenderSlots`, registry augmentations, or explicit app generics for an
ordinary declaration.

Focused capability subpaths may construct a standalone advanced `EndpointDeclaration`, including
`webhook()` and `createStorageDownloadEndpoint()`. Such a declaration joins an app only through the
explicit `app.endpoint(declaration)` bridge, which returns an opaque endpoint handle owned by that
contract. Adoption is deterministic and exact-identity: it may happen once, creates no ambient
registry, preserves the capability declaration's runtime/provenance facts, and remains subject to
the same single-assembly completeness checks. Passing the raw standalone declaration directly to
`assemble`, adopting it into two contracts, adopting it twice, or assembling a structural copy MUST
fail the private runtime ownership check.

Capability-bounded `agent()` and `tool()` declarations remain on
`@kovojs/server/agent`; their constructors, model callback, mutation capability, and session runner
MUST NOT move to the ordinary server root. One exact `AgentDefinition` joins an app through
`app.agent(declaration)`, which returns only a named opaque bridge. Its `session(rawRequest)` method
is unavailable until `app.assemble()` has validated and closed the app, then inherits that app's
session provider, managed DB provider, validated env projection, error handler, and trusted
client-IP policy without asking the author to repeat their types or callbacks. The bridge may expose
an explicit response-owned `Set-Cookie` sink because an agent invocation has no ambient HTTP
response; it accepts no alternate provider, DB, env, principal, client-IP, or egress authority.
Agent adoption is exact-identity and one-owner like advanced endpoint adoption: a copy, cast,
duplicate adoption, second app, or duplicate package instance fails the private runtime check.
Adopted agents are not HTTP declaration-inventory members and create no ambient registry; the
compiler still derives their finite model/tool operation witnesses from the original task-subpath
declarations under the §6.6 agent mediation contract.

`defineKovo` snapshots provider descriptors and callbacks but MUST NOT invoke a DB, session/auth,
environment, CSRF, replay, client-module, or other live provider. Provider evaluation and
environment parsing begin only when `app.assemble({...})` closes the graph (or when an explicitly
documented provider's existing contract requires a later request-time call). Importing the contract
or any declaration module is therefore provider-inert. A provider throw during assembly fails that
assembly before a `KovoApp` is returned; it cannot leave a partially registered application.

Every declaration handle is registered by exact object identity in module-private state owned by
the `KovoContract` that minted it. A module-private `unique symbol` may make accidental structural
construction a TypeScript error, but the runtime proof is exact private-map membership. A handle
from another contract, a copied/spread object, a structurally similar value, or a handle from a
duplicate `@kovojs/server` package instance MUST fail assembly with an actionable diagnostic that
names the declaration kind and the two resolved package identities when available. Public
structural brand fields and `Symbol.for()` are forbidden. The compiler additionally detects
duplicate Kovo package instances before authored evaluation; the runtime check is the fail-closed
floor for direct/custom hosts.

`app.assemble({...})` is the sole public application assembly operation and succeeds at most once
for one contract identity. Its declaration arrays are dense, finite, snapshotted in authored order,
and contain only handles minted by that exact contract. The result is an opaque minimal `KovoApp`
token; normalized options, providers, route/query/mutation registries, runtime authorities, DB
carriers, and generated registries remain framework-private. Public `CreateAppOptions` and a
structural `createApp()` aggregate are not app API. A custom adapter accepts the same opaque token
through a focused adapter entrypoint and cannot inspect or reconstruct its private state.

Assembly membership is compiler-checked, not merely a hand-maintained runtime convention. For each
proved `defineKovo` receiver, every compiled declaration handle reachable from that receiver MUST
appear exactly once in the single proved `assemble` call. A missing handle, duplicate membership,
dynamic/spread declaration list, second assembly, unresolved handle, or cross-contract handle is a
build diagnostic before output. The diagnostic for a missing handle includes one deterministic
source edit that appends the exported handle to the matching kind array; applying the edit twice is
a no-op. Runtime assembly repeats the membership and duplicate checks for uncompiled/custom hosts.
There is no ambient registration, import-order discovery, process-global pending registry, or
fallback from the closed application inventory.

Development HMR constructs a fresh contract and closed graph for the new module generation, then
atomically swaps it only after compile and assembly succeed. It discards the old generation's
private membership and provider references after in-flight requests release them; late declarations
cannot join either generation. A failed update keeps the prior closed graph. HMR never invokes
`assemble` twice on one contract or accumulates process-global registrations.

The app-scoped access algebra reuses the executable self-naming guards of §10.2. The contract exposes
`app.authenticated`, plus parameterized `app.role(...)`, `app.rateLimit(...)`,
`app.owns(keyOf, ownsRow)`, and `app.all(...)`; these return the same guard values runtime dispatch
executes and preserve their request refinements. They are not type markers or audit-only labels.
`access: [guard, ...]`, `publicAccess(reason)`, and verified machine access remain the mutually
exclusive §10.2 decisions, and missing or mixed decisions remain KV436. Binding a guard to a
contract narrows its request/session type but does not change the authorization proof boundary:
compiler census plus the exact runtime guard chain remain authoritative.

Factory results expose only purpose-specific operations. In particular, a query handle exposes its
inferred input/result types, component binding, and the §10.4 `optimistic` constructor; it does not
expose a writable registry key. A mutation handle exposes its inferred input/result/error union for
forms and tests without exposing private runtime callbacks. A component that binds a mutation
handle receives its form failure and field-error slots from that handle (§6.3), so a parallel
author-maintained slot registry is not part of the public contract. Public handle types keep
conditional machinery behind named interfaces and errors must anchor on the offending definition
property rather than expand private witness types.

The ordinary route/query handles come only from the app-scoped factories. The Core root does not
offer parallel `routeRef`/`queryRef` constructors, declaration option records, writable registry
keys, or a human registry-augmentation path: none of those can prove app ownership or rename-safe
assembly identity. Typed navigation consumes the compiler-generated facts from the app-owned route
declarations.

The compiler recognizes an app-scoped declaration only when the call receiver is proven by exact
TypeScript symbol identity to originate from one direct `defineKovo` result under the receiver
provenance rules in §5.2. A same-named local, cast, wrapper, destructured method, computed property,
mutable/ambiguous alias, duplicate package identity, or structurally copied receiver does not mint
factory provenance. Once proven, the factory lowers to the same finite declaration and
`server.handler.root` facts as the corresponding primitive; the facade never replaces the
AST/provenance gate or a runtime sink check.

Client-handler publication has a deliberately narrower value grammar than the general JSON wire.
`publishToClient(value, { reason })` accepts exactly `string | number | boolean | null`. It rejects
every object, array, symbol, bigint, undefined, and function at runtime using only primitive
classification, without reflecting over or coercing caller-owned values; its TypeScript signature
exposes the same finite union as an author-time guardrail. In client-handler source, the compiler
accepts only a unique, pristine same-file `const` initialized directly from that literal grammar and
snapshots the literal into the generated module. An imported value, re-export, alias to an import,
mutable binding, duplicate/shadowed binding, array, or object is refused even when wrapped, because
evaluating its source module or carrier could itself execute authority. Only the finite reviewed
client-handler import registry grants executable authority (§5.2, §6.6).

### 6.3 Mutation typing contract

Where the mutation value is importable — server-rendered templates always can — `mutation={addToCart}`
is the preferred form authoring spelling: inference comes straight off the value, no registry hop.
The compiler emits the concrete `action="/_m/<key>"`, mutation key metadata, input coercion metadata,
CSRF field, idempotency token, and submitted-form target. The string-keyed `form('<key>')` helper
survives for sites that cannot import the value, but author TSX should not hard-code mutation URLs.
An end-to-end add-to-cart walkthrough lives in `docs/worked-example-add-to-cart.md`.

The typed `mutation={definition}` path is the **sole complete public mutation-form bundle**: the
framework emits the mutation-audience CSRF field and canonical `Kovo-Idem` field together, from one
proven definition, before authored controls. An exact compiler-recognized
`{...mutationFormAttributes(definition)}` JSX spread is an equivalent typed spelling and receives
the same generated field bundle. Standalone CSRF token/field construction is not a mutation-form
authoring API because it cannot establish the idempotency half of the protocol. TypeScript prevents
the ordinary partial call shape, while compiler provenance and the runtime request lifecycle remain
the enforcement boundaries.

Enhanced form failures use the same render function as the no-JS full-page path. Expected failures
are typed mutation results: schema validation maps to `<FieldError name="...">`, declared
application codes map to `<FormError code="...">`, and both helpers are compiler-bound to the
enclosing enhanced mutation form. The third render argument still carries typed form state as the
escape hatch for custom UI, with each bound mutation exposing
`forms.<mutation>.failure: null | { code; payload; fieldErrors? }`. The failure value is scoped to
the submitted form instance for that render and is cleared by the next successful render of that
instance. `ctx.submit(mutation, { input, onError })` receives the same exhaustive typed-error union.
Under the app contract, `<mutation handle>.form` and component mutation binding infer this surface
directly from the handle; `ComponentRenderSlots` and a separately authored form-state map are not
public prerequisites.

Repeated forms must provide stable identity through authored `key` or serializable keyed component
props; the compiler lowers it to `kovo-key` and derives the submitted-form fragment target. Hidden
inputs are submitted data, not identity. An enhanced form in a repeatable position with no stable key
is a teaching diagnostic because the server cannot know which live form to re-render.

### 6.4 Routes & links (typed navigation)

Navigation is the inter-page wiring of an MPA, and it is typed with the same declare-once pattern — a TanStack-Router-style type layer with none of its runtime, because the server owns navigation (§8). Routes are declared values whose path strings are captured as literal types:

```ts
// products.routes.ts
export const productRoute = route('/products/:id', {
  params: s.object({ id: s.string() }), // coercion declared once, like FormData (§6.3)
  guard: authed, // same combinators as mutations (§10.3); pages join the unguarded audit
  search: s.object({ max: s.number().optional() }), // the §7 URL channel, typed
  prefetch: 'conservative', // Speculation Rules config lives here (§8)
  meta: ({ params }, queries) => ({
    /* … */
  }), // §13.5 head/meta, typed, fed by queries
  page: async ({ params, search }, req) => {
    /* rendered page */
  },
});
```

Path params are extracted from the literal by template-literal types (`PathParams<'/products/:id'> = 'id'`), so links demand exactly the right params — missing or extra is a compile error, and the params argument exists only when the route has params:

```tsx
// Authoring (sugar)
<Link to="/products/:id" params={{ id: item.productId }} search={{ max: 500 }}>
  View
</Link>;

// GET forms — the §7 coordination channel — validate against the route's search schema
const f = form.get('/products');
<f.Form>
  <f.input name="max" type="number" />
</f.Form>;
// ✗ compile error: field name not in search schema — same machinery as mutation forms (§6.3)
```

```html
<!-- Lowered IR / wire: a plain anchor. No client router, no link runtime —
     Constitution #1 (legible), #3 (a string href is valid Kovo source), #4. -->
<a href="/products/p1?max=500">View</a>
```

`Link` is JSX-only: `<Link to={productRoute} params={...}>...</Link>` renders an anchor and has no
imperative overload or descriptor result. `href(productRoute, { params, search })` is the sole
imperative URL-string constructor. GET-form helpers infer their public record directly from the
route's search schema and expose only the form and typed control builders; their conditional helper
families are private implementation types.

`redirect(productRoute, { params })` types the POST-redirect-GET path (§9.1) the same way. Residual literal `href`s in hand-authored IR are validated against the route table at compile time (KV220); full-origin URLs and an `external` marker opt out. The propagation property of §6.2 holds for navigation too: renaming a route path turns every `<Link>`, GET form, and `redirect()` in the app red under `kovo check`.

Two more route-level affordances close the request shell: **guards** — `guard:` on a `route()` runs the same combinator chain as mutations (§10.3) before `page`, refines `req.session` identically, and enrolls the page in the `kovo explain unguarded` audit; and **`notFound()`** — returning `notFound()` from `page` renders the app's 404 page with the correct status, so status codes stay part of the typed surface rather than ad-hoc response construction. `redirect()` and `notFound()` are the sanctioned non-200 page outcomes in v1.

Routes may also return two sanctioned non-HTML 200/304 outcomes: `respond.file(body, { contentType, filename?, etag?, headers? })` and `respond.stream(body, { contentType, filename?, etag?, disposition?, headers? })`. These are still ordinary `route()`s: params/search schemas, guards, typed links, KV220 validation, the unguarded audit, and the `owner:`-powered `unscoped` audit all apply before the body is served. `Content-Type` is required, `Content-Disposition` is declared (`respond.file()` defaults to attachment; `respond.stream()` defaults to attachment unless `inline` is requested), and a matching `If-None-Match` answers 304 without rendering HTML. Upload filename metadata and every final live/generated `Content-Disposition` filename sink MUST neutralize Unicode directional-formatting controls U+061C, U+200E/U+200F, U+202A–U+202E, and U+2066–U+2069 before constructing either `filename` or RFC 8187 `filename*`; browser-visible filenames cannot retain display-direction authority from a remote uploader. Range/resumable downloads are out of scope for v1; large exports that exceed a request/response window belong to a later background-jobs design.

`respond.stream()` and raw `endpoint()` responses are the escape hatch for app-owned streaming protocols. They do not participate in enhanced mutation application, query truth, mutation failure rendering, CSRF/replay semantics, or final fragment reconciliation unless the app builds that protocol itself.

### 6.5 Session schema

Sessions are a declared `s.object` schema, not an `any` bag: `req.session` is fully typed everywhere it appears. This is core, not a nicety — query instance keys (§10.2) and guard refinements (`req.session.user` non-null under `authed`, §6.2) are load-bearing on session fields, so an untyped session would be a hole directly under the proof surface.

Session provenance is an application capability, not a framework-owned identity system. The app declares a `sessionProvider` in the server request shell; Kovo runs it once before route, query, or mutation guards and exposes the returned value as `req.session`. `session(schema).provider(provider)` MUST snapshot the exact schema and runtime-validate every non-null provider result through it before the value reaches guards or handlers. This applies to synchronous and asynchronous providers and to both plain values and `{ value, setCookies }` envelopes; envelope cookies keep their independently snapshotted forwarding semantics. The validated session is an owned framework value, so undeclared properties, inherited/accessor fields, Proxies, and later provider-object mutation cannot create session authority. TypeScript assignability remains an author-time guardrail, not the proof. A provider returning `null` or `undefined` means "anonymous"; guard combinators must treat that as unauthenticated rather than as a malformed request.

Route and query guard failures have fixed outcomes so auth remains part of the typed surface. `authed` failures run the app's `onUnauthenticated` handler, whose default is a 303 redirect to the configured login route with the original URL available as `next`. `next` is framework-validated: it MUST be a same-origin, single-leading-slash absolute path (no `//`, no scheme, no host) that resolves against the route table (§6.4); a value failing that check is stripped to a safe default. The framework re-validates `next` both where it is captured and again wherever it hands `next` to the post-login redirect, so app-authored login code cannot consume an open-redirect target. Authenticated-but-unauthorized failures render the app's 403 shell with status 403. Mutation guard failures distinguish **authentication** failure from **authorization/validation** failure. An _unauthenticated_ mutation guard failure (an `authed` guard failing because `req.session` is null/anonymous, §6.5 — e.g. a session that expired between page render and submit) is a distinct outcome from a validation or app-`fail()` error (§9.2): the enhanced path returns **HTTP 401** with a `Kovo-Reauth` directive carrying the login route and a same-origin `next` (the original document URL), which the loader follows to re-authenticate exactly as a page route would for the same expired session; the no-JS path returns a **303** redirect to the configured login route with `next`, mirroring the route/query `onUnauthenticated` contract. An _authenticated-but-unauthorized_ mutation guard failure (a `role()`/ownership refinement failing on a valid session) keeps the §9.2 typed-error path — **HTTP 403** with `forms.<mutation>.failure` carrying an `unauthorized` code — and introduces no redirect body. Only the unauthenticated case crosses into the auth-redirect vocabulary; this prevents a routine session-expiry on submit from surfacing as a generic validation-style error with no path to re-auth.

### 6.6 Soundness boundary (normative)

The §1.2 proof claims are claims about TypeScript programs that stay inside the sound subset. The starter therefore ships — and the docs state as a precondition — `strict` everything plus lint bans on `any`, non-null assertions, and `as` casts in app code. Three boundaries are runtime-validated regardless, by design: the **wire** (every mutation input passes its `s.*` schema — types-without-validators, raw-tRPC style, was rejected); **deploy skew** (a long-lived document POSTing yesterday's form shape is answered by schema validation and the 422 path, §9.2 — never undefined behavior); and **CSRF** — `kovo-csrf` (§9.1) is a synchronizer token stamped into every emitted form and verified before schema parsing, replay lookup, and the guard chain on every mutation POST. When `req.session` is present the token is bound to it; when it is null/anonymous (§6.5) the token is bound instead to a **framework-owned signed-cookie secret** that exists independent of `sessionProvider`, so pre-auth forms (login, signup, password reset) are CSRF-protected even with no session to bind to — anonymous-CSRF is mandatory, not optional. `CsrfOptions.sessionId` MUST return a stable opaque 1..1,024-character rotation id for a framework-resolved session and `undefined` only for a genuinely anonymous request; non-string, missing, empty, oversized, anonymous-with-id, and unresolved-session results fail closed. The rotation id has no reserved textual spellings: exact length framing and a separate kind frame distinguish even `anonymous`-shaped session text from an anonymous cookie. The signing payload domain-separates that session/anonymous kind and, for a framework lifecycle request, both the rotation id and the independently pinned authorization principal, so a shared or namespace-shaped app id cannot cross-bind two principals. On a successful authenticating submit the framework rotates the anonymous token's binding to the new principal; apps should rotate their own session identity on auth (Kovo does not own session identity, §6.5). CSRF is default-on for server-rendered mutation endpoints; an explicit `csrf: false` is the only per-mutation opt-out and is reserved for non-browser or externally authenticated endpoints. A `csrf: false` mutation MUST NOT use browser authority: it is compile error **KV418** for such a mutation to read `req.session`, `Cookie`, `Authorization`, or `Proxy-Authorization`; escape an unproven request carrier; run a session/cookie-derived guard (e.g. `authed`, `role()`, `owns()`); or call a browser-state response sink (`setCookie`, `forwardSetCookie`, or `setSessionRevocationClearSiteData`). Skipping CSRF while riding the victim's ambient credential is forgeable, and minting an attacker session or clearing victim storage is login/logout CSRF even when the handler never reads ambient state. The exemption is sound only by construction: a `csrf: false` mutation is served with no ambient session/browser credential headers and cannot emit `Set-Cookie` or `Clear-Site-Data`. Machine callers use an explicit non-ambient custom signature header; browser credential flows keep the anonymous synchronizer token. Raw endpoints may separately declare executable verifier auth. Truly non-browser writes belong in `endpoint()`/`webhook()`. Every mutation's CSRF posture (`checked` or `exempt:<justification>`) is listed in `kovo explain endpoints` (§11.4) alongside endpoints and webhooks. The `Kovo-Idem` replay token (§9.1) is a per-submit, high-entropy value minted fresh by the client on each logical submit and refreshed in the enhanced success response (§10.3) — a freshly stamped hidden field, never a form-instance constant — so re-editing and re-submitting a form is a new mutation rather than a silent replay of the first response. Deploy skew also covers handler modules, normatively: emitted module URLs are immutable and versioned, and the serving layer retains prior versions — an old document's `on:*` refs keep resolving after a deploy; first interaction on a still-open tab never 404s. Generated ABI subpaths (for example `@kovojs/browser/generated`) may change when the compiler and runtime ship together because app source regenerates those imports, but already-emitted immutable modules remain governed by the same versioned-module retention rule: old generated modules must keep resolving to the runtime symbols they were emitted against for the supported deploy-skew window.

When replay storage is configured, a `csrf: false` mutation MUST declare
`machineReplayPrincipal(request)`. Kovo invokes it exactly once per request, only after
parse/coerce and the guard/access decision succeed, against the pinned post-guard request. It MUST
return a primitive non-empty string of at most 1,024 JavaScript code units. Missing declarations,
throws/rejections, wrappers, and malformed values collapse to the sanitized 422 idempotency
conflict before replay-store or handler authority. A CSRF-protected mutation MUST reject this
machine-only declaration. The returned public caller/tenant id is canonically length-framed under a
versioned domain and committed by applying the boot-pinned SHA-256 control to the exact UTF-16LE
encoding of every JavaScript code unit before it enters replay scope. The encoding MUST preserve
lone surrogates rather than replacing them with U+FFFD; the raw value MUST NOT enter replay keys,
store metadata, errors, or diagnostics. Enhanced and no-JavaScript delivery share this scope. A
retry that changes response vocabulary conflicts under the existing claim and MUST NOT execute the
handler again.

**Persistent principal revocation epochs (normative).** A `PrincipalEpochStore` is the authoritative
identity-lifecycle capability for one persistent, monotone `{ epoch, changedAtMs, status }` row per
proven principal, independent of any session. `initialize(principal)` atomically creates active
epoch 1 or returns the existing row without changing it; it never reactivates a tombstone.
`advance(principal, reason)` and `tombstone(principal, reason)` accept only the finite reason unions
exported by Kovo and MUST increase both epoch and change time. Tombstones are permanent. Better
Auth bindings supplied this store initialize the row from the provider's sanitized authenticated
user id before the session reaches app code. Other identity providers MUST initialize at account
creation or authenticated resolution and call `advancePrincipalEpoch`/`tombstonePrincipalEpoch`
for password, role, tenant, administrative, provider-revocation, and deletion events, including
out-of-band changes.

Every persistent credential derived from a principal MUST carry the current epoch at its mint door
and compare it with an authoritative current row at every release door. Kovo's closed census owns
the capability-URL mint and verify doors plus mutation replay-receipt reservation, response
release, handler admission, in-transaction completion, and settlement doors. The
`exactly-once-continuation` callback is explicitly inapplicable: it is
closed before its adapter frame returns and never becomes a durable credential. Missing,
malformed, unavailable, timed-out, contradictory, stale, or tombstoned state fails closed. The
default has no positive application cache; each lookup has a 1,000 ms ceiling, so there is zero
successful-lookup staleness beyond the one authoritative read/action race. Expiry remains a second
floor and is never freshness evidence. Production accepts only the module-private durable store
provenance exposed by `createPostgresAppRuntimeDb().principalEpochStore`; structural fields or a
global symbol cannot forge that provenance. §10.3 defines replay scoping and the explicit mutation
transition declaration.

Anonymous-CSRF cookie names are logical unprefixed names; Kovo alone applies the effective
`__Host-`/`__Secure-` prefix. Across the app-wide and every mutation-local CSRF configuration, one
logical anonymous-cookie name MUST have exactly one Path, Max-Age, SameSite, and Secure posture.
App construction rejects conflicting or prefixed aliases because Cookie request headers omit those
attributes: multiple same-name secrets would otherwise collapse to a browser last-wins value or
arrive as indistinguishable duplicate-name pairs and make another emitted form unverifiable.
Standalone `mintCsrfToken`/`mintCsrfField` calls made during one framework-managed response lifecycle
MUST likewise reuse one anonymous binding and one identical `Set-Cookie` value per logical cookie
posture. A conflicting same-name posture or authored browser-prefix alias fails before a second token
can be emitted, so a raw response containing multiple forms cannot silently invalidate an earlier
form. While that lifecycle is active, token-generation calls through cloned, reconstructed, or other
derived `Request` values resolve browser/session authority from the canonical lifecycle request and
share its binding/posture state and response-header commit boundary. An exact framework-retained
request can identify that lifecycle after async context is lost. That exact retained context takes
precedence over any ambient outer lifecycle: nested dispatches cannot cross-bind canonical
authority, personalization witnesses, pending cookies, or seal state. Every
`createRequestHandler()` invocation is a distinct response boundary: it clears an ambient caller
frame before pre-dispatch callbacks run and, when passed the caller's exact retained `Request`,
reconstructs a detached native ingress carrier before the nested dispatcher can finalize anything.
An arbitrary detached derivative cannot identify a lifecycle. A first-anonymous mint therefore
requires an active lifecycle or an exact retained lifecycle receipt. The lifecycle privately records
the exact standalone `Set-Cookie`; finalization atomically seals and snapshots that record before
delivering it through the route/document sink or an endpoint response authorized to emit browser
state. An exact authored duplicate is emitted once;
a non-identical plain/`__Host-`/`__Secure-` alias under the same logical name fails closed. Direct
`runEndpoint()` and direct internal `renderRoutePageResponse()` have no managed cookie sink and
reject a first-anonymous mint, while a truly late
post-seal mint cannot enter the snapshot. Detached session-bound generation and generation from an
already-present anonymous cookie can remain valid only from the exact supplied credential carrier;
they do not inherit canonical response authority from a settled ambient frame. An authored raw
stream therefore must retain and use its exact handler `Request` when it needs the response receipt;
a reconstructed or otherwise derived request after owner settlement cannot recover that receipt.
CSRF validation and replay resolution always use the exact supplied ingress request and never
inherit response-generation authority.

Every deployable `AsyncLocalStorage` authority cell is governed by the versioned
`kovo.async-context-confinement/v1` census and one framework-owned confinement contract. Cells stay
separate and least-authority; the contract does not merge request, response, provenance, egress,
build, credential, or module-load values into one ambient bag. A cell is an opaque exact identity
whose storage remains module-private. Each store is bound to an exact framework lifecycle witness
and generation. A read exposes a value only when the cell, current lifecycle, and open-generation
identities all match; missing, inherited-foreign, forged, or stale state yields no authority and
never falls back to another ambient store. Entering a cell from a detached descendant of a closed
lifecycle fails. `createRequestHandler()` establishes a fresh authority-empty lifecycle before any
pre-dispatch callback; nested request cells share that exact lifecycle, while build evaluation,
generated-module loading, and cloud-credential access deliberately open isolated roots. The owner
closes its lifecycle on synchronous return, throw, asynchronous fulfillment, or rejection, so work
not awaited by the owner cannot retain or reacquire its cells. Exact retained response-lifecycle
receipts remain the separately specified identity bridge above and never turn an unrelated ambient
cell into authority. The sole framework-owned post-settlement re-entry is deferred-region JSX
rendering: `Defer` captures the same exact JSX context object that registered the region and runs its
success, rejection, and timeout rendering in a fresh isolated lifecycle containing only that cell.
Each captured re-entry capability is one-shot, and success, rejection, and timeout select one winner
before opening a fallback lifecycle. Timeout explicitly revokes an unfinished success lifecycle
before rendering the error fallback, so a never-settling or late-rejecting authored promise cannot
retain or re-mint even JSX authority. The exact retained request inside that context may resolve its
already-sealed response receipt;
sibling request, provenance, egress, credential, build, and module-load cells remain absent. Critical
and non-collected regions stay in their current owner lifecycle, authored raw streams receive no
ambient re-entry, and the census gate rejects any additional consumer of this finite bridge. The
`@kovojs/test` SQL observation carrier is a censused, non-deployable observer rather than app
authority; it retains its independent exact-scope revocation so observation can span the request
boundary without weakening runtime isolation.

The public `mintCsrfToken` and `mintCsrfField` helpers serve only a verified raw endpoint protocol
with an explicit custom audience. They reject mutation targeting. The lower-level
`csrfToken` and `csrfField` helpers are internal/test-only; exposing either at the package root would
make an incomplete handwritten mutation form look supported while omitting canonical `Kovo-Idem`.
The closed mint/deliver/validate/rotate/replay surface and its proof anchors are recorded in
`security/csrf-mint-delivery.json`; adding a response or bootstrap surface requires adding a closed
matrix row before release.

Every independently resolved authorization principal entering a CSRF or replay identity, and every
source-derived mutation identity, MUST likewise be a non-empty string of at most 1,024 JavaScript
code units. An inbound anonymous-CSRF cookie secret is accepted only when it is 32..1,024 base64url
characters; the framework mints a 43-character secret. A present malformed or oversized credential
fails closed and is never replaced by an anonymous fallback within that request.

`s.string()` rejects raw C0 control characters (`U+0000` through `U+001F`), `U+007F` DEL, and the JavaScript line-terminator code points (`U+000A`, `U+000D`, `U+2028`, `U+2029`) by default before any format, pattern, or unsafe-regex refinement runs. This is defense-in-depth for every request-derived string sink: an embedded NUL, CR/LF, tab, or other control character cannot survive validation by relying on a loose or parity-sensitive author regex. Authors who are intentionally accepting textarea-style content must opt in with `s.string().multiline()`, which admits line terminators while still rejecting the other raw C0 controls and DEL. Authors who intentionally accept arbitrary raw controls must opt in with `s.string().allowControlChars()`. These opt-ins alter only the base string hygiene gate; all existing chained format/pattern/optional/default behavior still applies normally.

**Security soundness (normative).** The Prime Principle (§2) rests on the same sound-subset discipline, bounded by six rules. (1) **The compiler performs no TypeScript type inference of its own** — security classification is carried by AST symbol-identity provenance, sink classification, and fail-closed runtime checks; a branded type (`Secret<T>`, a `public()` brand, and the like) is `tsc`-time ergonomics and defense-in-depth, never the enforcement. (2) **Runtime taint is unsound** — JS string operations and template literals produce fresh primitives with no surviving metadata, so request-derived provenance for confidentiality, write-eligibility, and input shape is proven _statically_ at the AST (where the path is still code), never by runtime value-tracking; runtime contributes only _sink validation_ (checking a final value's grammar, shape, or resolved IP, which survives transforms). (3) **By-construction and defense-in-depth are distinguished and labeled.** Where static analysis can prove the unsafe state inexpressible, the guarantee is by-construction (output-safety §5.2 rule 10, the confidentiality boundary, default-deny authorization, write-provenance). Where it cannot — outbound egress, a read-only-handle runtime proxy, Content-Security-Policy / Trusted Types, log redaction — the control is a fail-closed runtime floor: sound at its sink but bypassable by privileged same-process code, and it MUST be documented as defense-in-depth rather than a proof. (4) **Advanced TypeScript types are preferred when they narrow author mistakes without becoming the trust boundary.** Validated branded constructors are appropriate for strong signing material; module-private `unique symbol` brands are appropriate for framework-owned sentinels; branded escaped/trusted/rendered HTML values are appropriate for UI composition; exact header-bag and discriminated-union types are appropriate for preserving multi-value headers and explicit posture choices. Public structural brands, casts, and type-only assertions MUST NOT be accepted as security evidence unless a runtime constructor, AST/provenance gate, or fail-closed sink also enforces the invariant. (5) **Boundary decisions over caller-owned carriers must classify-and-pin or reconstruct.** Once a runtime boundary classifies, normalizes, or validates a caller-owned value, the sink MUST consume either an immutable framework-owned pinned carrier for that exact classified value or a reconstructed fixed output; the sink MUST NOT re-read mutable caller bytes after classification and still claim the earlier decision. Browser sinks MUST classify platform behavior that depends on a tuple of attributes from the same pinned element snapshot, not validate each string in isolation; hidden `_charset_` substitution is the canonical HTML example (§13.2). Spec §10.3 C15 names the concrete sink obligations. (6) **Authority-bearing controls have a framework-owned bootstrap trust root.** Every supported Kovo compiler, dev, build, export, generated-server, worker, and test runner MUST evaluate the framework security bootstrap before any authored app module, Vite/plugin module, generated module, or other caller-controlled dependency in that realm. The bootstrap eagerly captures the ambient bindings, prototypes, framework controls, and reviewed dependency objects used by later security decisions; ordinary late replacement of those captured controls can therefore affect only unused public bindings. This is not a claim that deliberately hostile same-realm code cannot instrument a dependency, discover a module-private object that was not reachable at bootstrap, and mutate that object later; that is privileged application compromise under the trusted application-code boundary below. A security claim that must remain independent of app code MUST run in a fresh process or genuinely isolated realm that never evaluates the app graph; in-process parser/control reconciliation is defense-in-depth only. Function source text, names/arity, native-looking descriptors, and finite positive/negative probe corpora are health diagnostics only and MUST NOT be accepted as provenance for a control captured after caller code ran. A host preload (`NODE_OPTIONS`, embedding code, loader hook, VM setup, or equivalent) that executes before the supported Kovo entry is privileged same-process host compromise and outside the app-level framework claim; a platform that cannot guarantee bootstrap order MUST move authority computation into a genuinely pristine isolate with a fail-closed typed RPC boundary. Tests for import-order mutation MUST enter through the same bootstrap-first runner and poison controls only after that boundary, including the first entropy/hash/command use rather than relying on second-use detection.

**Capability-bounded agent mediation and honesty (normative).** Kovo does not claim prompt-injection
immunity: an application that lets a model read adversarial text can still receive an
authorized-but-undesired decision. In the supported subset, a model-selected application action can
reach an effect only by naming a framework-owned `tool()` declaration. Each tool names one exact
compiler-visible mutation binding; the compiler derives its effect closure from the same finite L2
operation IR used for HTTP roots and installs that closure as generated runtime evidence. An opaque
model callback, dynamic tool collection, unresolved tool-to-mutation link, or model invocation with
raw authority is a KV448/KV449 build error, not a warning. The model receives frozen inert tool
descriptors and the exact framework `ctx.fetch` egress door, never an executable mutation, request,
database handle, or ambient principal.

A selected tool executes the exact mutation's input parser, access decision and guard chain,
managed-database SQL write policy, RLS principal pinning, and transaction path. The invocation
principal is an immutable framework session-provider snapshot established before the model runs;
structural request fields and caller-supplied service-principal overrides cannot establish agent
authority. Internal agent calls do not impersonate a browser form and therefore do not replay the
browser CSRF protocol, but they MUST NOT bypass the mutation's authorization or data-plane policy.

An agent session carries the closed integrity order
`untrusted < retrieved < validated < principal`. Every admitted content value is an exact immutable
`agentContent()` carrier with an explicit integrity, and every tool result is unconditionally
classified as `untrusted` or `retrieved`; no prose/content classifier participates. The session
updates by the lattice meet only, rejects concurrent turns, and filters the next offered tool set by
the compiler-derived minimum integrity of each tool's effect closure. Thus, once injected or
retrieved content is admitted below `principal`, it cannot raise the session's integrity or regain a
removed high-authority tool for the rest of that session. `kovo explain agent` MUST print the same
model effects, per-tool mutation/effect/minimum/result-integrity facts, and retained closure at every
integrity level that runtime enforcement consumes. This proves only that no model-selected action
exceeds the invoking principal and that admitted lower-integrity content cannot raise authority. It
does not prove that an authorized action was intended, that an app classified direct input honestly,
or that malicious prompts and retrieved content are harmless.

**Cryptographic authority and lifecycle (normative).** Raw secret-crypto acquisition is a capability,
not an authority-free implementation detail. The capability-closure vocabulary distinguishes
`crypto-acquisition` from `digest`. An exact named import of a reviewed non-keyed SHA-256 digest
primitive may classify as `digest`; a namespace/default crypto import, WebCrypto/global crypto,
entropy, keyed hashing, password hashing, signing, encryption, or an ambiguous import MUST classify
as `crypto-acquisition`. Either capability reached from an untrusted root closes with KV448 unless
the exact framework export is a reviewed door. Kovo's repository gate separately records every
remaining production direct acquisition by exact path, class, and operation. That high-authority
path set MUST be a non-increasing ratchet: adding or widening a row requires an explicit reviewed
architecture change, while deleting or narrowing one requires no compatibility mode.

`kovo.certificate/v1` MUST carry the exact same nine-member raw-capability domain:
`crypto-acquisition`, `database-driver`, `digest`, `dynamic-loader`, `filesystem`, `network`,
`process`, `vm`, and `worker`. Its search-side analyzer and disjoint checker MUST preserve the
binding-sensitive distinction between an exact reviewed digest import and broader crypto acquisition;
neither kind may be downgraded to an opaque external import or omitted from post-fixpoint closure.

The authority posture for that certificate belongs to a distinct, canonical
`kovo.certificate-policy/v1` reviewer policy. The policy MUST own the exact sorted
`{path, sha512}` set of packed `@kovojs/*/dist/*.mjs` modules, the complete installed manifest for
every package in scope, and the complete roots, doors, and opaque-assumption rows. The certificate
MUST contain only the corresponding sorted artifact paths, the fixed capability domain, capability
summaries, import edges, exact copies of the policy's roots/doors/opaque rows, and `policySha512`
computed over the exact canonical policy bytes. A checker MUST require exact equality for every
copied posture row and artifact path before checking coverage, stability, or closure. The policy is
an independently obtained review decision: a copy emitted beside an application build is an audit
convenience only and MUST NOT become a trust root. Fetching policy, certificate, and artifacts from
one mutable location does not establish independent review. A detached signature over a certificate
authenticates only those certificate bytes and MUST NOT substitute for obtaining and reviewing the
policy bytes named by `policySha512`.

The standalone directory checker MUST derive the actual `@kovojs/*` package census from the supplied
packed tree and require it to equal the policy package census. It MUST compare each installed
`package.json` as a complete JSON object with the reviewer-owned manifest, reject `publishConfig` and
`browser` remapping, and reject every non-empty automatic package-manager lifecycle hook named
`dependencies`, `install`, `postinstall`, `preinstall`, `prepack`, `postpack`, `prepare`,
`preprepare`, `postprepare`, `prepublish`, or `prepublishOnly`. Every non-types conditional export,
fallback, `main`, `module`, or `bin` arm MUST collapse to one canonical listed runtime target.
Package `imports` aliases MUST be exact non-wildcard `#name` keys whose non-types arms collapse to
one canonical relative target; any alias used by packed runtime code MUST resolve to a listed packed
module. Source-only aliases are inert only when the complete packed-tree census proves the source
files are absent.

The packed-tree census MUST admit only regular, non-symlink files: the installed manifest, canonical
`.mjs` runtime modules below `dist/`, reviewed declaration/source-map companions below `dist/`, and
the package README. Root or `dist/` JavaScript with any other suffix, extensionless executable
files, JSON runtime payloads, native addons, WASM, special files, unexpected documentation, and
unreviewed siblings fail coverage. Certificate and policy input files and every manifest/runtime
module MUST be read through a no-follow descriptor after path and descriptor identity agree; the
checker MUST read through a fixed maximum-plus-one buffer rather than an EOF-growing convenience
read, compare size and file identity before and after that same-descriptor read, bind each read to the
initial file/directory census, and repeat the complete census after verification. An added, removed,
replaced, grown, or otherwise mutated file or directory at any of those boundaries fails closed.

The v1 checker budgets are part of the denial-of-service boundary: policy bytes are at most 1 MiB,
CLI certificate bytes at most 2 MiB, one runtime module at most 4 MiB, all runtime modules together
at most 32 MiB, the policy may name at most 32 packages, the packed tree at most 4,096 files and
depth 16, and certificate plus policy JSON together at most 262,144 nodes and depth 64. After parsing
each complete runtime module, reference extraction may consume at most 32,768 reference units for
that module and 131,072 across the packed tree. Each import, re-export, or dynamic-import occurrence
costs one unit, plus one unit for each raw imported or re-exported binding it names; an export-all or
dynamic-import occurrence also costs one wildcard-target unit. Deduplication MUST NOT reduce the
charged units. Artifact analysis may retain at most 1,024 findings. Exceeding a
reference or artifact-analysis finding limit MUST discard every partial graph, summary, and finding
and return exactly one fixed fail-closed budget finding; a syntax error beyond the extraction limit
MUST still be observed because complete parsing precedes extraction. The checker MUST snapshot
caller-owned JSON and policy bytes once before validation and MUST NOT re-read them after making a
decision.

The published `@kovojs/verify` package is the runtime-independent front door to this checker. Its
human-public root MUST retain the certificate family as one coherent 11-declaration surface:
`KOVO_CERTIFICATE_CAPABILITY_DOMAIN`, `KovoCertificateCapabilityKind`,
`KovoCertificateRootKind`, `KovoCertificateV1`, `KovoCertificatePolicyV1`,
`KovoCertificateFinding`, `KovoCertificateArtifactSource`,
`KovoCertificateVerificationResult`, `verifyCertificate`, `verifyCertificateDirectory`, and
`formatCertificateVerification`. The packed package MUST bundle its reviewed parser bytes and MUST
have no production dependency on a Kovo compiler, analyzer, server, or runtime package.

The package bin is `kovo-verify`. `-h`, `--help`, and `--version` MUST write to stdout and exit 0.
The verification grammar is one certificate path plus the required `--policy <path>` and
`--artifacts <root>` flag groups and optional `--format <human|json>`; those groups may appear in
any order. A completed verification with no findings exits 0. A completed verification with one or
more certificate findings exits 1. Invalid/ambiguous usage, duplicate or unknown options, file I/O,
text decoding, and JSON parsing failures are indeterminate rather than certificate findings and
MUST write to stderr and exit 2. Human reports remain `kovo-verify/v1`. JSON reports MUST carry
schema `kovo.verify-report/v1`, status, `ok`, the same stats, and the exact same ordered
`{obligation, code, message}` findings as the human report. A JSON-mode indeterminate error uses
`kovo.verify-command-error/v1`; it MUST NOT be shaped like a completed verification report.

Every non-dry release from the authorized `main` commit MUST publish separate GitHub artifact
attestations for the exact committed reviewer-policy and certificate files. The attestation job MUST
perform no dependency installation or repository script execution, and dry runs MUST receive no
attestation or OIDC authority. Consumers MUST verify those attestations against the intended release
workflow and commit, or obtain the exact policy bytes through another independently authenticated
channel; the committed SHA-512 joins evidence but does not create that channel.

Certificate doors remain coarse module-plus-capability approvals. Their `site` field is a reviewer
label, not a source-location proof. The checker re-derives lexical import edges and the modeled raw
capability vocabulary, but it does not prove the behavior of host globals, `eval`, `new Function`,
computed runtime loading, or every native/WASM execution route beyond explicit rejection and the
reviewed opaque ledger. Those residual limitations remain honesty obligations under §4.6 and the
trusted application-code boundary below.

The server runtime has one primitive-owning crypto authority. It captures its Node crypto and byte
controls during the bootstrap-before-app boundary and runs known-answer checks for RFC 5869
HKDF-SHA256, HMAC-SHA256, fixed-width equality, and AES-256-GCM before serving. It MUST NOT expose a
generic signer, sealer, primitive table, or derived key. Each consumer receives an exact
framework-witnessed frozen handle containing only the operations for one registered purpose. The
environment-neutral webhook verifier uses the corresponding core-realm WebCrypto authority because
core cannot import the Node server runtime; that authority is verify-only, boot-captured, and keeps
provider signing material out of public verifier metadata. Types and private brands are ergonomics;
the runtime witness, closed registry, and acquisition gates are the enforcement.

The runtime-posture Ed25519 trust anchor also verifies detached privileged-write reviews from
§10.3 and detached Metric E escape-root reviews from §11. An escape-review signature is
domain-separated by the exact
`kovo.escape-obligation-review/v1` subject and binds one scanner-owned call-span identity, one
structured obligation, and one reviewed artifact subject. This reuses the runtime-attestation
fingerprint; a second review root is forbidden. A Metric E signature is separately domain-separated
by `kovo.escape-census-review/v1` and binds the exact reviewed artifact, closed door, counted root,
and complete canonical producer-site set. Build emits only unsigned subjects, and its import graph
plus the app-facing/internal execution surface expose verification but no signing handle.
Signing authority belongs to the out-of-band review/deployment process and is absent from the build
environment and coding-agent capability set. A valid signature is evidence that the pinned key
holder approved those exact bytes. It is not evidence that the asserted guard or policy exists,
that the cited evidence is sufficient, or that a human reviewer was independent; those are retained
operational obligations.

The closed registry is `kovo-crypto-purpose-registry/v1`. Every framework derivation is
HKDF-SHA256 over its root with public salt `kovo-crypto-authority-v1`. Its fixed-width info is the
SHA-256 commitment of the injective, length-framed tuple
`(registry-version, purpose, audience, algorithm)`; no bounded audience bytes are truncated to meet
provider-specific HKDF info limits. A row fixes the literal purpose, algorithm, allowed operation
set, root source, and bounded audience grammar. An absent,
dynamic, malformed, algorithm-mismatched, or operation-mismatched row fails before derivation.
HMAC-SHA256 is the v1 symmetric signature/PRF algorithm; SHA-256 is the v1 non-keyed digest;
AES-256-GCM with a fresh 96-bit IV and 128-bit tag is the v1 confidential-at-rest algorithm. A
provider-owned webhook HMAC is verified with the provider's raw protocol key through a verify-only
handle and is not HKDF-derived, because changing that key would break the external wire protocol.

Framework key rings are opaque configuration carriers, not generic signing objects. Exactly one
entry is `active`; `previous` entries require a finite `acceptUntil` epoch-millisecond deadline;
`revoked` entries carry no usable secret. New signatures and seals use only the active key.
Verification and opening may use the active key or a previous key strictly before its deadline;
unknown, expired, and revoked ids fail closed. On expiry/revocation the authority overwrites its own
retained Buffer copy on a best-effort basis. This is memory hygiene, not a JavaScript zeroization
guarantee: caller strings/buffers, VM and native-library copies, allocator snapshots, crash dumps,
and keys already copied into a crypto implementation can remain.

The confidential-at-rest envelope is unconditionally
`kovo-aes256gcm-v2.<key-id>.<iv-base64url>.<tag-base64url>.<ciphertext-base64url>`; v1 has no
compatibility fallback. The key id is chosen by the active ring, never by the caller. The authority
derives the key for registered purpose `confidential-at-rest` and the bounded declared string
audience, then authenticates the exact envelope version, key id, purpose, audience, and caller AAD
as one length-framed AES-GCM AAD value. Opening performs a bounded canonical parse, selects only an
eligible ring key by the authenticated id, and authenticates before returning plaintext. Tampered
version, id, IV, tag, ciphertext, audience, or AAD; an unknown/revoked/expired key; and a raw-key
call shape all fail closed.

**Trusted application-code boundary (normative).** Kovo does not sandbox app-authored server modules or third-party packages that execute in the server realm. The public-import and provenance rules in §5.2 prevent unsupported or accidental authority use inside the supported authoring subset; they are not a claim that deliberately hostile same-realm code cannot recover ambient JavaScript authority through `Function`, dynamic loading, reflection, native addons, or equivalent language/host facilities. Such code is privileged application compromise, not a remote-input framework boundary. Deployments that execute mutually untrusted plugins or generated server code MUST place that code in a separate process or genuinely isolated realm and expose only a fail-closed typed RPC capability surface. Finite syntax deny-lists and intrinsic pinning may remain defense-in-depth, but MUST NOT be described or tested as a sandbox proof.

**Capability-closed untrusted roots (normative, supported-subset static gate).** Before evaluating
authored app modules, `kovo build` MUST scan the immutable app-source snapshot and census every
proved `defineKovo()` contract and its single `assemble()` closure (including lifecycle callbacks),
route, layout,
query, mutation, endpoint or low-level request adapter, webhook, durable or scheduled task,
serialized browser handler, and supported agent/tool callback as an untrusted-data root. For each
root, Kovo computes a transitive module/callback graph across eager imports, re-exports, local aliases
and wrappers, literal `import()`/`require()` edges, conditional local targets, and callbacks or
callback-bearing containers transferred through a local wrapper. A non-literal loader, unresolved
local target, or reachable raw filesystem, network, process, worker, VM/dynamic-loader, or
database-driver capability fails the pre-evaluation build gate with **KV448** and a root-to-terminal
provenance path. Reviewed
framework APIs are the only nodes that may terminate such a path as a capability door; app or
package metadata cannot mint a framework door.

The lexical-provenance scan behind this gate is resource-bounded and fail-closed. Its analysis
budgets — abstract work, per-value candidates, loop reanalysis, invocation cycles, and the recorded
unmodeled-effect-site history — bound analyzer time and memory only; they are not classification
inputs, and a completed analysis yields the same verdicts at any budget. Exceeding any budget MUST
mark the module's provenance analysis exhausted, and an exhausted module MUST close every framework
root discovered in it with KV448 rather than report the partial analysis as exact. The effect-site
history budget is derived, not constant: it scales with the module's own syntax-node count, clamped
to a floor of 128 and a ceiling of 4,096 recorded sites, so a legitimate module does not become
unbuildable purely by growing, while no module can obtain more than the ceiling by inflating its
own syntax. The remaining budgets are flat and MUST NOT scale with module size; inflating a module
to raise the effect-site budget therefore spends the flat abstract-work budget and still fails
closed. Partial effect-site history MUST never be consumed as if complete: any truncation or
refused recording is valid only in the same analysis that marks the module exhausted.

A custom Node adapter is privileged host wiring, not request-handler code. Its entry module MUST
import `@kovojs/server/runtime-bootstrap` as its exact literal first side-effect import and MUST pass
one directly imported handler from a separate local module to `toNodeHandler()`. Capability closure
starts at that handler module, while the adapter entry retains only the host listener boundary.
Inlining `createRequestHandler(app)`, importing the handler before the bootstrap, or importing the
bootstrap from the handler graph is unsupported and fails closed with KV448. Generated runners own
the equivalent compiler-created separation and bootstrap order.

**TASK B layered routing (normative).** The pre-evaluation request/process check MUST consume the
capability-closure result, dependency manifest, finite-operation diagnostics, and normalized
semantic graphs derived from the same immutable source snapshot; running those analyzers beside an
unbound legacy pass is not sufficient. The internal `kovo-task-b-closure/v2` carrier repeats the
exact source census, capability root rows, and `kovo-app-dependency-capabilities/v1` manifest. It
also carries one immutable `kovo-task-b-finite-verdict/v1` snapshot taken at the compiler-result
boundary. That snapshot MUST retain every KV449, KV450, and KV452 diagnostic with its exact site,
start, length, message, and severity, and MUST bind the complete diagnostic census plus normalized
semantic-source carrier with a canonical SHA-256 digest. TASK B accepts only an empty diagnostic
census, an `accepted` status consistent with that census, and a digest recomputed over the exact
carrier it consumes. Omitting or substituting any diagnostic or semantic trace after the snapshot
fails KV424. This transport-integrity proof does not replace the independent compiler soundness
oracles and does not claim resistance to coherent same-realm forgery outside the trusted
application-code boundary above. TASK B
MUST reconstruct each enrolled `createApp`, endpoint, layout, mutation, query, route, task, and
webhook invocation from its independent parser view and require exactly one capability root at the
same module and call site plus the same root kind in that module's dependency-manifest entries. A
missing, duplicate, byte-mismatched, or differently rooted row fails KV424 with a stable
`root -> transfers -> sink -> closed verdict` trace; it never falls back to a syntax-only allow.

For endpoint, mutation, query, task, and webhook effect handlers, TASK B MUST additionally require
one exact `kovo-security-semantic-graph/v3` root whose factory call span, callable span, callback,
root identity, all-path verdict, helper summaries, and terminal inventory match the authored root.
A missing graph, closed trace, closed helper summary, unknown transfer, or terminal mismatch is
KV424 even when the residual analyzer would otherwise find no named sink. KV448 remains the primary
diagnostic for raw/module/package authority and KV449 remains the primary diagnostic for a finite-IR
operation that cannot lower; the KV424 correspondence check prevents either result from being
silently omitted between compiler phases. KV450 and KV452 are likewise authoritative closed
verdicts for scoped-key and derived-dataset provenance and MUST remain in the same finite verdict.
Existing request/process predicates may remain as a
conservative residual and independent C13 oracle until their exact root-and-terminal
correspondence is proved, but they MUST NOT discharge a missing L1/L2/L3 proof or mint an allow
verdict. The specialized Drizzle KV406/owner-predicate proof remains a separate data-plane
correspondence responsibility under §10.3 and §11.1.

Reachable package code requires a least-authority verdict for the exact installed package name,
version, security-relevant manifest fingerprint, requested subpath, imported export, and complete
conditional-export arm set. Every manifest-public Kovo runtime export and every public subpath's
`<module>` initializer MUST appear exactly once in the compiler-owned, versioned framework export
posture ledger with an explicit raw-authority disposition, root kind or `none`, security role,
implementation binding, manifest-target/condition fingerprint, and threat-matrix posture. A
posture that can produce an authority-free or framework-door verdict MUST bind the exact installed
implementation digest. A package whose complete public runtime surface is explicitly
`request-closed` MAY instead use the `unconditional-request-closure` binding: after the exact
installed package name, requested specifier/export status, reviewed version, and security-relevant
manifest fingerprint match, the compiler rejects that package without consulting implementation
identity. Such a binding is invalid if any public initializer or export is missing or has a
disposition other than `request-closed`; widening the package therefore restores the
exact-implementation requirement rather than inheriting a digest-free allow path. A new,
missing, duplicate, stale, or unclassified first-party export fails closed; absence from a shorter
door list is never an authority-free verdict. Compiler-emitted private ABI edges may bypass public
subpath membership only through one compiler-owned exact table that classifies the initializer and
every admitted member separately; that table is consulted only after the installed first-party
manifest fingerprint and implementation digest match. A vocabulary match alone cannot mint an
authority-free verdict. Explicitly reviewed framework companions use the same compiler-owned,
version-pinned verdict model. Other packages use the committed
`kovo.capabilities.json` `kovo-package-capability-summaries/v1` ledger, whose entries are versioned
independently and may classify exports only as pure or raw. A side-effect-only import is the reserved
`<module>` entry. Every package import, including a named, default, or namespace import, evaluates
that initializer and MUST consume one exact `<module>` verdict in addition to every requested export;
an export wildcard cannot stand in for the initializer. An absent, stale, duplicate,
contradictory, malformed, export-incomplete, condition-incomplete, or unresolved verdict fails
closed with KV448. `kovo explain capabilities` prints the root census, reviewed doors, exact
package-summary versions/fingerprints, and every closed fact with the same provenance used by the
diagnostic. This is a conservative proof about accidental authority in Kovo's supported static
authoring subset; consistent with the trusted application-code boundary above, it is not a
same-realm JavaScript sandbox or a claim about deliberately hostile dependencies.

`@kovojs/compiler` is a deliberate zero-public-surface instance of unconditional request closure.
It has no app-facing public runtime subpath, so an authored or request-reachable import of its exact
package name or any subpath is rejected before installed resolution, version, manifest fingerprint,
or implementation digest is consulted. The analyzer executable that makes this decision is a
trusted release/install subject: its bytes are authenticated externally by verified release-tarball
provenance, package-manager integrity, a certificate, or equivalent host provenance. The analyzer
does not and cannot self-authenticate by comparing its running bytes with a digest embedded in
those same bytes.
Accordingly, this app-level proof does not detect arbitrary post-install mutation of the analyzer;
such mutation is release/install or privileged-host compromise, outside the application-level
claim. If the compiler ever gains an app-public runtime subpath, the zero-public closure is invalid
and the posture gate fails until that surface receives an explicit non-circular classification.

For every authored or compiler-derived package edge, including an initializer in a malformed or
currently rootless module, the compiler MUST also derive one
`kovo-app-dependency-capabilities/v1` loader manifest row containing the exact installed identity,
specifier and conditional-export arms, retained export dispositions/capabilities, exact importer
and sites, and the reachable root kinds. A loader-census-only row uses an explicit empty
`rootKinds` array; that row does not invent a request root or explain fact, but its package
initializer still cannot execute by omission. Every supported production Vite path that loads or
bundles an approved app source MUST re-resolve that exact package identity before admitting its bare
import and reject an absent/duplicate or malformed row, identity drift, a closed package row, or a
retained `raw` or `request-closed` export. Pre-evaluation SSR MUST force complete dependency
traversal and parse every admitted third-party module before execution; every bare child edge,
including a Node builtin, and every non-literal module edge fails closed before Vite can externalize
it. A relative child edge from a reviewed package MUST retain that package's exact nearest owning
package root; physical containment does not admit a nested `package.json` or `node_modules` package
identity, including one reached through a symlink or package-main redirect. Application aliases MUST
NOT match a reviewed package child edge, and reviewed dependencies require Kovo's fixed Vite
extension-resolution order; same-root retargeting invalidates the reviewed summary just as an escape
does. Every direct reviewed export and resolved relative child MUST have one exact case-sensitive
JavaScript/TypeScript module suffix — `.cjs`, `.cts`, `.js`, `.jsx`, `.mjs`, `.mts`, `.ts`, or
`.tsx` — in both its lexical resolver identity and canonical realpath. Extensionless modules, JSON,
CSS, SVG/HTML, WASM/native modules, and image/font/media resources remain closed until a separate
pinned semantic and provenance lane admits them; Vite cannot reinterpret reviewed bytes as an
asset, stylesheet, executable document, or worker payload. Query/fragment variants fail closed at
the pre-evaluation module-edge census. Direct `Worker`/`SharedWorker` construction, dynamic-code or
timer-handler recovery, and every retained `new URL(..., import.meta.url)` executable-asset carrier
from a reviewed package MUST fail closed in the complete build-client artifact before that artifact
can be published or executed. Vite MAY transform or stage bytes during an ultimately rejected build;
tree-shaken source that is absent from the retained artifact creates no executable secondary graph.
This retained-artifact rule does not weaken the earlier pre-evaluation package/module-edge census. An
external module edge from a loaded HTML entry MUST resolve to the immutable approved-source
snapshot or one exact framework-owned Vite bootstrap virtual; inline HTML module proxies remain
outside the supported source graph. Before Vite resolution, Kovo MUST parse raw HTML with one
exact-pinned standards-compatible parser in both the build tool's scripting-disabled state and the
browser's ordinary scripting-enabled state. Every script source must be an exact HTML-namespace
module URL in the immutable approved-source snapshot; inline scripts other than explicit JSON data
blocks, foreign-namespace scripts, raw `on*` event attributes, JavaScript URL attributes, SVG SMIL
execution primitives, and any `iframe`/`frame`/`frameset`/`object`/`embed` carrier fail closed with
KV448. Raw element controls consume §4.8's finite static-value policy, including target-keyword,
no-opener, and `meta[http-equiv=refresh]` automatic-navigation rules. Raw `<base href>` and
`<base target>` also fail closed because they can retarget emitted modules or later navigation at
browser consumption. Public-asset shadows, `vite-ignore`, browser-only module-type spellings, and
post-resolution aliases cannot weaken the same approved-file binding. An exact
framework host-tool external is permitted only when no
app dependency manifest row overlaps that package or subpath. Artifact checks distinguish
bundle-owned chunk filenames from true unresolved externals without weakening the earlier source
and module-graph checks: only a relative runtime specifier normalized from its importing chunk may
bind a bundle-owned file, while Rollup file-name metadata is checked separately and cannot bless a
bare runtime specifier. Retained non-literal module edges fail closed. Relative artifact specifiers
with percent encoding, query/fragment syntax, backslashes, ASCII whitespace/control bytes, or empty
path segments fail closed before ownership comparison because browser/Node URL resolution can map
those spellings to a different file. Only `OutputChunk` names — never `OutputAsset` names — satisfy
executable bundle ownership. The same manifest is emitted in `graph.json`; an explicit empty manifest
means the compiler proved that the app graph has no dependency edge. This loader check turns the
pre-evaluation census into a fail-closed runtime/build bound for supported production artifacts, but
it is defense-in-depth under rule (3): it does not sandbox deliberately hostile same-realm package
code or prevent privileged host loaders from bypassing Kovo.

Supported browser event handlers MUST be authored as TSX/JSX event attributes and lowered through
the compiler-owned finite browser operation vocabulary. App-authored imperative registration — an
`on*` property write, `addEventListener`, or an equivalent opaque protocol/call transfer — is outside
the supported subset and MUST fail closed with KV424 before authored modules are evaluated. That
verdict is rooted in the registration's reachable authority, not in a deny-list of names found in
the callback body; adding or renaming a dangerous browser API therefore cannot reopen the raw
registration path. Compiler-owned JSX handlers remain governed by KV449 and the finite operation IR.

**Compiler-derived browser response posture (normative).** Supported build and dev runners MUST
derive one `kovo-browser-posture/v1` manifest from the immutable project source snapshot and
register its reconstructed generated carrier before authored app evaluation. The compiler census
uses the final effective intrinsic element/attribute tuple after static spread and primitive
composition lowering. It records canonical absolute HTTP(S) origins, CSP directive, file, and
source span for `script[src]`, sandboxed `iframe[src]`, `img[src|srcset]`, SVG
`image[href|xlink:href]` and `feImage[href|xlink:href]`, `audio[src]`, `video[src|poster]`,
`source[src|srcset]`, `track[src]`, `input[type=image][src]`, and fetch-bearing `link[href]` relations
(`stylesheet`, `modulepreload`, icons, and typed preloads). Relative/path-only and fragment URLs
remain same-origin and add no origin. A computed URL at one of those positions is KV236 unless it
is the exact framework `trustedUrl(value, { reason: auditedReason, source? })` call with a non-empty
static reason. That
escape is recorded as opaque audit evidence; it does not invent an origin or establish isolation.
A computed `link[rel]`, opaque spread that could introduce an asset position, unclassifiable
external URL, raw browser fetch/worker authority, frame, or popup likewise prevents a positive
isolation verdict. Local spelling, structural copies, missing reasons, and self-consistent manifest
fields cannot mint compiler provenance.

Framework-rendered page hints are re-witnessed at the document sink. An absolute HTTP(S)
stylesheet, modulepreload, or bootstrap-script hint prevents the optional isolation posture because
the compiler cannot establish the remote response's CORP/CORS behavior; build-generated
same-origin hint paths remain eligible.

Document CSP assembly consumes that registered manifest. Census origins are admitted only to their
derived fetch directive. An authored `CspAllowlist` string MUST be a canonical origin already in
the same directive's census; a non-static origin requires the structured `{ origin, rationale }`
escape with a non-empty audited rationale. An unused unmatched string is a build/check error, not a
silent widening. The manifest and authored config are snapshotted from stable own data before use,
and generated registry reconstruction rejects unknown kinds, malformed spans, noncanonical origins,
accessors, sparse arrays, and schema drift.

`Permissions-Policy` has exactly one response assembler with one exhaustive decision for every
`BrowserSecurityOperationKind`; adding an operation without a decision fails the build. Both normal
and reporting header bytes come from that assembler. The default document posture remains
`Cross-Origin-Opener-Policy: same-origin-allow-popups` and makes no cross-origin-isolation claim.
`document.csp.crossOriginIsolation: true` is accepted only with the exact generated manifest and no
external or opaque resource, dynamic/opaque browser call, frame, popup, or authored origin whose
CORP/CORS behavior is unproved. The isolated response is exact: COOP `same-origin`, COEP
`require-corp`, and document CORP `same-origin`, plus the derived CSP and Permissions Policy. A
route response cannot replace or weaken those selected headers. Missing or contradictory evidence
fails before a deployable artifact or document response; Kovo never silently weakens isolation to
preserve OAuth, popup, embed, or third-party-resource compatibility.

**Finite operation closure (normative, supported-subset static gate).** Capability closure answers
which code and reviewed doors are reachable; the finite security-operation IR answers which
security-relevant effects a supported handler can perform. Its browser vocabulary is closed in
§4.3. Its structured-server vocabulary is exactly: principal-scope acquisition; managed database
read/write; justified trusted SQL; framework egress; justified trusted HTML; cookie/header/outcome,
raw-response, and redirect response effects; storage read/write; task composition; plus the
typed `server.data.declassify` effect; plus the compiler-control records `server.handler.root` and
`server.helper.call`. The root record enrolls each
supported query, mutation, endpoint, webhook, and task body even when it has no terminal effects.
The helper-call record names an exact immutable same-file callable that received authority and
carries the source-derived handler root on the edge. The normalized interpreter below MUST
discharge that edge before the build can treat the root as closed. The inventory in
`securityOperationKinds` is the canonical union. C9 assigns terminal effects to one real boundary
owner and the two control records to capability closure. Adding a kind without exactly one owner, or
an inventory row that names an unknown/duplicate kind, fails `check:c9-sink-inventory`.

Classification follows symbol identity and monotone receiver provenance, not variable spelling.
Endpoint/query/task context is the declared context parameter; a mutation's request and context are
the second and third parameters respectively. Context, principal scope, database, headers, storage,
`Response`, and their destructured method aliases retain authority through direct immutable aliases.
An ambiguous/mutable join, computed terminal method, raw database client member, authority-bearing
container or constructor, or return of authority is unsupported and MUST fail closed with
**KV449**. Authority may pass to an exact immutable same-file function only by emitting a
`server.helper.call` edge with its local identity. Imported, foreign, computed, aliased, reassigned,
or unresolved helpers remain KV449. The finite edge enrollment itself does not guess about the
helper body; the normalized interpreter MUST produce an explicit bottom-up summary before Kovo can
claim cross-helper effect closure. A helper may always consume plain data returned by a reviewed
operation, or the capability-closed module graph may terminate at an exact reviewed framework door.
Namespace and named imports of the three exceptional operations preserve exact
framework identity: `trustedSql` and `trustedHtml` require a static justification, and raw
`Response` use is admitted only where the declared endpoint posture supplies the compiler-owned
justification. App spelling, a same-named local, a cast, or a generated manifest cannot mint a door.

**Typed declassification door (normative).** A secret or untrusted value may be unboxed only with an
exact nominal `DeclassifyPolicy` constructed by the door-specific static constructor exported from
`@kovojs/core/security`: `forRevealSecret({ purpose, ownerScope })`,
`forSecretValue({ purpose, ownerScope })`, `forTrustedReveal({ ownerScope })`,
`forRevealUntrusted({ ownerScope })`, or `forUntrustedValue({ ownerScope })`. There is no generic
public constructor that accepts a caller-selected door. The policy vocabulary is closed.
`ownerScope` is one of `application`, `current-principal`, `current-tenant`, or `framework`.
`forTrustedReveal` fixes purpose to `public-projection`; the two untrusted-value constructors fix it
to `request-validation`; and the two secret-value constructors accept only `credential-use` or
`server-computation`. Free-form strings, structural object literals, copied fields, casts,
subclasses, surplus fields, an unknown tuple, or a policy created for another door MUST NOT
authorize release. TypeScript's nominal shape is an author-time guardrail; the module-private
constructor token, exact runtime registry membership, closed option validation, and exact-door
check own the runtime floor.

A policy exposes no public `door`, `purpose`, or `ownerScope` data fields; those facts live only in
the module-private record used for admission and audit. The bounded destructive reveal-audit drain
is framework-internal. Public callers observe only the exact door-specific constructor and the
reveal result inferred from the reveal operation.

The finite compiler IR admits `trustedReveal` as `server.data.declassify` only for exact direct
named imports of `trustedReveal` and `DeclassifyPolicy` from `@kovojs/core/security` and the inline
exact spelling `DeclassifyPolicy.forTrustedReveal({ ownerScope: <closed literal> })`. The released
expression and every finite enclosing enabling condition MUST both have integrity strictly above
request input.
If either is request-derived, foreign executable, unresolved, or otherwise unknown, the operation
MUST fail closed with KV449; an attacker-chosen condition may not select release of an otherwise
constant secret. This is a robustness judgment over the existing normalized provenance relation,
not a claim to interpret general JavaScript. Independently, declassification is an L1 capability:
`DeclassifyPolicy`, `revealSecret`, `revealUntrusted`, and `trustedReveal` are request-closed public
exports. A module reachable from any untrusted-data root, including through a transitive helper or
re-export, MUST fail capability closure with KV448 if it imports the constructor or a reveal door.
Closing policy construction also closes `.reveal(policy)` use without prohibiting creation of a
poison box. A module with no such root does not gain a trusted root merely by importing the door.

This layer deliberately does not claim general JavaScript interpretation or same-realm isolation.
The emitted operation lists are immutable, inspectable audit evidence consumed by component graphs
and `kovo explain`; they are not an opcode sandbox and do not replace the actual C9 sink checks.
For an app-scoped declaration, the factory root includes the receiver proof from §6.2.1. The
compiler follows ordinary immutable local import/re-export aliases back to the exact
`defineKovo()` result and binds the declaration to that contract's one `assemble()` closure before
authored evaluation. A destructured factory, computed property, wrapper result, structural copy,
cast, mutable or ambiguous alias, duplicate package identity, or declaration omitted from assembly
is an unresolved root. The facade cannot make a same-named callback or structurally similar object
trusted; successful receiver proof only enrolls the existing finite root analysis below.

Every supported factory root MUST resolve from an inline definition object to either an inline
function or one exact immutable same-file function. Definition spreads/computed root keys, missing
roots, imported/aliased/reassigned roots, and dynamic definition carriers are KV449. This includes
`query({ load })`: query roots appear in the emitted manifest even when the loader is effect-free,
and a directly reached managed DB write from a query remains KV449. Value-flow beyond the closed
alias/receiver and explicit local-call-edge rules above belongs only to the normalized abstract
interpreter defined next; the edge preserves that obligation rather than guessing its downstream
verdict.

**Normalized helper provenance (normative, narrow abstract interpreter).** The compiler MUST
discharge every `server.helper.call` over `kovo-security-semantic-graph/v3`, a normalized graph whose
nodes are enrolled handler roots, exact same-file callables, finite operations, and explicit closed
verdicts. This is not a JavaScript evaluator, SSA optimizer, or type-inference engine. Its complete
value lattice is: plain local data; request/context authority; managed database, structured-header,
storage, response-constructor, response-outcome, principal-scope, and exact module-constant
`derived()` dataset authority; one exact `operation:<securityOperationKind>` terminal;
non-authority `governed-data` carried by managed database and derived-dataset reads; and absorbing
unknown authority. `governed-data` survives reviewed calls, static member projection, containers,
aliases, destructuring, binary/conditional joins, and same-file helper arguments so a persistent
non-engine sink cannot erase owner/governed provenance merely by reshaping a value. The scanner is
the only raw-syntax boundary; validation, emission, graph, and explain consumers decide from these
typed facts (SPEC §5.2 rule 10).

Version 3 is unconditional; consumers MUST reject versions 1 and 2 rather than enter a compatibility
posture. Every semantic root carries an exact binding to its full root identity, factory family,
callback name, factory-call `[start,end)` span, and callback-callable `[start,end)` span in the same
UTF-16 source snapshot. Every proved terminal additionally carries the SHA-256 hash of the exact
UTF-16LE source slice selected by its JavaScript code-unit `[start,end)` span. Every helper transfer
carries its exact invocation and ordered argument
spans, callable name and declaration span, complete ordered root-to-helper transfer prefix, authority-input vector,
terminal-operation inventory, and verdict. A consumer may admit a helper summary only when an exact
invocation fact has the same callable identity and span, authority-input vector, terminal inventory,
and verdict; the invocation span and root binding must also match the authored call and root being
classified. The terminal inventory MUST equal the unique finite sink kinds reached by proved traces
under that complete transfer prefix. A downstream consumer MUST independently reconstruct the exact
root, argument authority, and every terminal-operation family on which its own admission decision
depends; self-consistent carrier fields are not authentication. A consumer-specific gate MAY project
the complete graph onto only the terminal families it owns, but unverified families confer no
authority in that gate. A missing or contradictory relevant fact, a root/trace identity mismatch, an
omitted or extra relevant terminal kind, a closed trace, or a closed sibling summary/invocation for
the same callable span closes that consumer proof. Source bytes, callable identity, and all-path
closure remain mandatory; these carrier checks do not turn semantic facts into app-authored
authority.

Transfer semantics are finite. An exact immutable alias preserves its lattice value. Static object
destructuring applies the reviewed member transition one property at a time. Results of finite
operations are plain data, except the explicit principal-scope acquisition that returns a scoped
context and managed database/derived-dataset reads that return `governed-data`. Passing authority or
governed data to an exact immutable same-file helper maps each positional argument to
that helper's parameter binding and computes a context-sensitive summary keyed by the complete
authority-input vector. Summaries are computed callee-first and merged back into the caller; nested
helper operations retain the source root and ordered transfer path. Returning or throwing
authority, placing it in an opaque container, mutating an authority alias/member, using a mutable or
ambiguous join, capturing it in an unsummarized nested callable, recovering it through `arguments`
or a rest/spread mapping, invoking an operation through `call`/`apply`/`bind`, or using an imported,
computed, aliased, reassigned, unresolved, or otherwise foreign callable is unsupported and MUST
remain KV449. A query root's no-managed-write posture propagates unchanged through every summary.

<!-- BEGIN GENERATED ANALYZABLE FRAGMENT -->

#### Closed analyzable-fragment prohibitions (generated)

This table is generated from [`security/analyzable-fragment.json`](../security/analyzable-fragment.json). The classification describes the general prohibition; each fixture is a minimal compiler-verdict witness, not an impossibility proof.

| Prohibition                                                              | Classification | KV449 closed reason         | Witness                                                                                                 |
| ------------------------------------------------------------------------ | -------------- | --------------------------- | ------------------------------------------------------------------------------------------------------- |
| Returning authority                                                      | `DELIBERATE`   | `unsupported-authority-use` | [fixture](../packages/compiler/src/fixtures/analyzable-fragment/returning-authority.tsx.txt)            |
| Throwing authority                                                       | `DELIBERATE`   | `unsupported-authority-use` | [fixture](../packages/compiler/src/fixtures/analyzable-fragment/throwing-authority.tsx.txt)             |
| Opaque authority container                                               | `FUNDAMENTAL`  | `opaque-transfer`           | [fixture](../packages/compiler/src/fixtures/analyzable-fragment/opaque-container.tsx.txt)               |
| Mutating an authority alias or member                                    | `FUNDAMENTAL`  | `unsupported-authority-use` | [fixture](../packages/compiler/src/fixtures/analyzable-fragment/mutating-authority-alias.tsx.txt)       |
| Mutable or ambiguous join                                                | `FUNDAMENTAL`  | `opaque-transfer`           | [fixture](../packages/compiler/src/fixtures/analyzable-fragment/mutable-ambiguous-join.tsx.txt)         |
| Unsummarized nested callable                                             | `DELIBERATE`   | `opaque-transfer`           | [fixture](../packages/compiler/src/fixtures/analyzable-fragment/unsummarized-nested-callable.tsx.txt)   |
| `arguments`, rest, or spread recovery                                    | `DELIBERATE`   | `opaque-transfer`           | [fixture](../packages/compiler/src/fixtures/analyzable-fragment/arguments-rest-spread-recovery.tsx.txt) |
| `call`, `apply`, or `bind` invocation                                    | `DELIBERATE`   | `opaque-transfer`           | [fixture](../packages/compiler/src/fixtures/analyzable-fragment/call-apply-bind.tsx.txt)                |
| Imported, computed, aliased, reassigned, unresolved, or foreign callable | `FUNDAMENTAL`  | `opaque-transfer`           | [fixture](../packages/compiler/src/fixtures/analyzable-fragment/foreign-callable.tsx.txt)               |

The ledger also records the current real-root budget-binding measurement. Its [reviewed hand argument](06-analyzable-fragment-hand-argument.md) states the compositionality claim, adequacy claim, and limits.

<!-- END GENERATED ANALYZABLE FRAGMENT -->

The resource contract is deterministic and has no app-authored widening knob: at most 16 helper
edges on one path, 50,000 interpreted AST nodes, 4,096 finite operations, and 256 helper summaries
per root. A repeated active summary key is a recursion cycle, not a fixpoint guess. The only closed
reasons are `helper-cycle`, `opaque-transfer`, `unknown-operation`,
`unsupported-authority-use`, and the four named `budget-*` reasons. A cycle, unsupported construct,
or exhausted call-depth/node/operation/summary budget MUST produce KV449 before output, with
`root`, ordered `transfers`, `sink`, and `verdict=closed:<reason>` in the diagnostic. Successful
generated server manifests and `kovo explain` expose the same root-to-transfer-to-sink trace and
bottom-up summaries. These artifacts are audit evidence; they neither grant runtime authority nor
replace the C9 sink owner.

**Authorization-gates-DATA scope (normative honesty boundary).** The normalized substrate may
contribute to OPP-28 only when the data analyzer has an exact private principal symbol, an exact
owner-column identity, and an equality-equivalent predicate (`eq` or singleton membership) whose
accepted guard principal is the same symbol. That structurally proven subset may be reported as
owner-scoped. Arbitrary JavaScript guard correctness, semantic equivalence between general
predicates, multi-principal policy composition, database policy correctness, and whether an opaque
helper actually enforces the intended business rule are not proved by this interpreter. They remain
an explicit database-engine/runtime-policy and audit responsibility; unknown correspondence stays
`scope: unknown` and MUST NOT be promoted by naming, types, or a permissive helper summary.

**Analyzer-summary proof boundary (normative).** `kovoAnalyzerSummary` is a candidate marker, not
an app-authored provenance assertion. A private-scope marker contributes to any invalidation,
owner-scope, accepted-guard, write, or diagnostic verdict only when the analyzer independently
resolves a bare helper identifier to exactly one declaration in the same source file. The only
accepted declaration forms are a direct function declaration or a `const` binding initialized
directly by an arrow or function expression. Object-literal properties and methods, class members,
property-access targets, imports, alias bindings presented as the marker target, destructured
bindings, `let`/`var` bindings, reassigned bindings, and otherwise opaque or multiply declared
callables remain unknown. No alias or container may stand between the marker and the proven
declaration.

The direct helper MUST have one non-default, non-rest identifier parameter and no generator body.
Its body MUST be either an expression-bodied arrow or a block containing exactly one return, and
that expression MUST be a literal property chain rooted in the parameter. The first private-scope
segment MUST be `guard`, `session`, or `tenant` (for example, `parameter.guard.userId` or
`parameter.request.session.id`), and the declared kind and path MUST exactly equal that segment and
the literal suffix after it. A provenance-bearing invocation MUST call that exact helper identifier
or one direct same-file immutable `const alias = provenHelper` identifier and pass the exact
framework request/context parameter (`req`, `request`, `ctx`, or `context`), proven by its
callback/receiver position rather than its spelling, as the sole argument. That one-hop alias may
preserve an already-proven identity but cannot be the marker target or widen the proof. Property or
element access, destructured/container aliases, alias chains, and imported, opaque, or mutable
aliases remain unknown. Multi-statement/general bodies, computed returns, mismatched principals,
unresolved symbols, and calls with client input or opaque/container arguments also remain unknown.
No `server` summary kind exists: general server provenance and KV438 cannot be discharged by an app
declaration. These restrictions apply uniformly to every consumer of session provenance; a looser
invalidation or explain path MUST NOT become a security side door.

**Build-preset capability boundary (normative).** `KovoPreset` is an opaque, framework-owned
selection token, not a public structural deployment descriptor. `node()`, `vercel()`, and
`cloudflare()` mint exact frozen tokens registered by identity in a framework-private `WeakMap`;
`kovo build` resolves only those exact objects through the matching internal module instance.
Copying or spreading a token, reconstructing its symbol-shaped type, or supplying an object with
`name`/`emit`/`inspect`/`capabilities` fields MUST fail closed. Emission callbacks, inspection
callbacks, and capability records remain internal build authority and MUST NOT be reflectively
reachable from the public token. The module-private `unique symbol` type is only author-time
ergonomics; exact runtime registry membership and config preflight own enforcement.

**Classifier-intrinsic lockdown (normative).** Rule 6 unconditionally pins the finite global bindings and direct namespace members that the request classifier recognizes, and guards their language/Web intrinsic prototypes before caller-controlled evaluation; the classifier corpus gate MUST keep that runtime inventory exact. A custom runner MUST import `@kovojs/server/runtime-bootstrap` as its literal first import, while generated runners establish the same order themselves. The public dispatch refusal detects an omitted bootstrap, but cannot authenticate earlier evaluation in the same mutable realm: importing authored/package code first and bootstrapping later is unsupported privileged-host misuse, not a repair path. This unconditional intrinsic lockdown does **not** freeze or claim provenance for egress/transport instrumentation prototypes such as undici, `node:http`, `net.Socket`, Datadog, OTel, or nock hooks. The separate outbound-egress prototype-freezing option below therefore remains off by default.

**Operator-environment trust root (normative).** Bootstrap MUST pin operator environment names and values before authored evaluation, and later security lookup MUST preserve the host's name semantics. In particular, Windows names are case-insensitive: the pinned authority MUST resolve every case spelling equivalently and fail closed if an injected source contains case-fold-colliding names, while app env-schema snapshots retain the operator's original key spellings.

**Application config-secret door (normative).** `defineKovo({ env: s.object(...) })` is the sole
public operator-environment projection. The runtime MUST admit only a genuine framework `s.object`
schema, parse the bootstrap-pinned source once, retain only declared own fields, freeze that parsed
record, and expose it only through the precisely inferred read-only declaration/request context.
The opaque public `KovoApp` token has no structural `env` field. The raw operator snapshot and
undeclared keys remain framework-internal. A declared env schema failure refuses boot in every
mode: development may warn for a weak framework signing secret, but it cannot return a typed
`app.env` whose value never validated. `kovo build` is not runtime boot and MUST NOT require or
receive the production values for this declared projection. While it evaluates the app only to
derive the closed build graph, every declared field MUST instead be a framework-owned,
non-coercible unavailable sentinel: the schema shape is still provenance-checked, but no operator
value is read, parsed, rendered, serialized, cloned, or copied into an artifact. Observing or
exporting a sentinel MUST fail closed. The emitted server then evaluates the app outside that build
posture and MUST parse the real bootstrap-pinned source before it can serve a request.
`s.secret(schema)` MUST return a runtime `SecretValue`, not a
type-only `Secret<T>` cast, so interpolation, template/string coercion, JSON and wire encoding,
structured cloning, SSR output, and artifact capture encounter the existing fail-closed
confidentiality doors. The box's module-private runtime registration owns this invariant; its type
is author-time ergonomics only. A dependency credential should be revealed exactly once inside its
boot-time credential factory through `revealSecret(value,
DeclassifyPolicy.forRevealSecret({ purpose: 'credential-use', ownerScope: 'application' }))`; the
static call site
remains an audit-grade row in the existing
`kovo explain revealed` fact graph (and therefore also in its folded `capabilities` view), while
the bounded runtime reveal collector is observational evidence, not a complete process-lifetime
proof. The audit collector MUST recognize direct `@kovojs/core` named imports, bind each reveal to
its exact policy tuple, and accept those literal policy fields in any order. A call with dynamic or
otherwise unrecordable policy MUST emit error-severity KV426 instead of disappearing from the audit. When
the typed query analyzer and runtime audit analyzer observe the same reveal, their facts may be
deduplicated only by the exact call span/AST identity; a shared `file:line` label is insufficient.
These audit rules do not relax the request security classifier's stricter exact matcher. This
pattern does not claim arbitrary JavaScript string
comparison is constant-time; use `SecretValue.equals` only for fixed token/verifier comparisons
whose operands fit that contract.

**Authentication request-origin binding (normative).** A framework-owned Better Auth binding MUST
normalize and pin the configured `baseURL` origin when the binding is constructed. Before parsing a
request cookie, delegating to a Better Auth handler, consuming a credential, revoking a session, or
reading or writing auth storage, it MUST require the request's exact normalized scheme, hostname,
and effective port to equal that pinned origin. A mismatch fails before cookie parsing, rate-limit
storage, credential verification, session lookup/mint/revocation, or response-cookie forwarding; it
does not fall back to the request `Host`, an untrusted forwarded header, or a less-secure cookie
name. URL paths do not relax this comparison. Case, Unicode hostname spelling, IPv6 spelling, and
default ports are compared only after URL normalization, while non-default ports remain exact.
Kovo's fixed SQLite/Postgres binding constructor MUST validate the base URL, construct the complete
host-only cookie posture, and privately register the exact Better Auth object with that canonical
origin before creating session, credential, or mount operations. Those private consumers admit only
an exact registry member; structural compatibility, a dependency `$context`, and an arbitrary
safe-looking cookie configuration are not construction proof. Caller-created `betterAuth()` objects
are unsupported and MUST fail before auth handler/API, cookie parsing/minting, or database access.
The ordinary HTTPS `__Secure-` default is insufficient: a sibling subdomain can plant that name with
`Domain` and browser duplicate-cookie ordering can place the attacker value first. On HTTPS, fixed
bindings therefore construct `__Host-` cookies with `Secure`, `HttpOnly`, `Path=/`, and no `Domain`,
and keep cookie-cache state inside the same fixed posture or disabled. Plaintext is admitted only for
an exact loopback origin (`localhost`, IPv4 `127/8`, or `[::1]`) in non-production local development;
those bare development cookies have no sibling-domain security guarantee. Production and every
non-loopback origin require HTTPS unconditionally.

**Better Auth credential-consumer non-egress door (normative).** Every supported fixed-binding
consumer of Better Auth signing material, submitted or stored credentials, request/session
cookies, password hash/verify values, session records, and dependency results MUST be enumerated
in one complete package-private contract census and invoked through the same runtime gate. Each
contract binds a stable consumer id to the M2 secret paths it may receive and the only result shape
that may reach its reviewed next sink. The gate MUST admit only an exact runtime-registered
consumer token, validate the result, seal it, and permit exactly one consume by that same token;
structural forgeries, unknown consumers, cross-consumer swaps, replayed results, invalid result
shapes, and dependency-thrown secret-bearing errors fail closed. Only an exact dependency
400/401/403 verdict from a credential operation may become Kovo's opaque invalid-credential
outcome, and that verdict is itself one-shot. A module-private `unique symbol` or validated secret
brand is author-time ergonomics only: the runtime registry, complete path/consumer and source-use
censuses, and hostile-value/sink tests own enforcement. The static census proves coverage but is
not itself runtime authority (SPEC §10.3 C9-C10).

Every captured external Better Auth credential source—including handler/API callables, constructors,
password functions, rate-limit construction, and cookie extraction—MUST be invoked inside that
runtime gate after exact consumer/source validation. Passing an owner-supplied callback through a
generic gate while the callback itself retains raw dependency call authority is not the sole-door
construction; generic callbacks are limited to package-owned transforms such as sanitized session
reconstruction. The package source-use census MUST resolve aliases, destructuring, literal computed
access, `.call`/`.apply`, and local imports/re-exports by symbol/value flow; printed callee spelling
or a fixed method-name regex is not coverage evidence.

The fixed credential rate limiter MUST domain-separate each admitted raw Better Auth identity by
HMACing the canonical `ScopedKey` frame under the finite `better-auth-rate-limit` system posture,
not a bespoke delimiter prefix. Its bounded 16-bit bucket is itself a runtime-witnessed key under
that same posture, and the SQLite/Postgres consumer MUST authenticate the witness, exact posture,
and four-lowercase-hex app-key before persisting the complete canonical frame. Raw IP/path input,
bare strings, structural forgeries, public/principal keys, and other system postures fail before the
database statement. HMAC collisions deliberately share the same bucket and aggregate attempts, so
the fixed 65,536-bucket bound fails closed rather than granting additional credential guesses.

**Better Auth redirect mount response boundary (normative).** The opaque Better Auth mount is a
redirect-protocol adapter, not a public proxy for the dependency router. After the exact request
origin check, Kovo MUST admit only status `301`, `302`, `303`, `307`, or `308` with exactly one
nonempty `Location` that resolves to the pinned origin over HTTP(S). Missing, duplicate/ambiguous,
protocol-relative, credential-bearing, non-HTTP(S), or off-origin locations fail inside the opaque
boundary. Kovo MUST canonicalize an admitted location to its same-origin path, query, and fragment,
then reconstruct an empty response containing only that `Location` and reviewed `Set-Cookie`
values plus Kovo's own `Cache-Control: no-store` floor. It MUST NOT forward the dependency response
body, status text, content headers, or arbitrary headers. In particular, dependency routes such as
`get-session` and error pages MUST fail closed rather than exposing session JSON, bearer material,
or HTML through the mount (SPEC §6.6/§9.1).

**Better Auth lifecycle ownership and non-claims (normative).** The fixed SQLite/Postgres bindings
own exactly four Kovo-owned identity transitions: the CSRF-protected `signIn` mutation, the
CSRF-protected `signOut` mutation, development-only seed `signUp` (which provisions a credential
with `autoSignIn: false`), and the feature-conditional CSRF-protected `requestPasswordReset`
mutation. The fourth transition exists only when the binding receives a constructor-minted,
purpose-closed password-reset mail door, an explicit public/pre-auth access decision, and one
canonical same-origin reset path. For an accepted provider request, the mutation MUST expose one
generic accepted result for account-present and account-absent worlds, MUST discard the provider
response and cookies, and MUST invoke the registered sender exactly once with only
`{ to, resetUrl }` in either world. Rate-limit or provider failure MUST stop before mail dispatch.
The absent world uses a same-shape decoy URL minted before provider work. The real token MUST reach
only that mail sender inside the validated same-origin URL; the standalone token, provider user
record, request, and other dependency values MUST NOT cross the door. Mail-provider delivery and
its attacker-visible behavior are deployment egress outside Kovo's HTTP-equivalence claim. No
other direct Better Auth lifecycle API is exposed. The opaque provider mount accepts only `GET`, so
provider lifecycle operations requiring an unsafe
method are structurally unreachable through that mount. This is not a claim that every dependency
lifecycle route is unreachable: the redirect/callback mount can reach dependency-defined `GET`
handlers that change identity state, including provider or token callback flows. That reachable GET
callback lifecycle is delegated and unsupported by Kovo guarantees; only the origin, non-egress,
redirect-response, and cookie-posture boundaries above apply. Session expiry, rolling update,
freshness, cookie-cache posture, and sign-in rotation behavior are inherited from the exact-pinned
provider and MUST remain characterized by the provider-pin conformance test. Reset-token minting,
expiry, single use, and reset completion remain exact-pinned Better Auth protocol behavior rather
than a Kovo guarantee. `kovo explain auth-lifecycle` MUST print the inherited values, the four
owned transitions (including the feature condition), the structurally unreachable unsafe-method
class, and the reachable delegated non-claim.

**Outbound egress: the positive framework capability (normative).** Untrusted-data-reachable framework code MUST have one supported positive HTTP network door: the exact framework-owned `ctx.fetch` supplied to durable/scheduled tasks, verified webhooks, and any supported agent-tool callback. A runner or app MUST NOT replace that function. Raw `fetch`, `node:http`, `node:https`, `net`, datagram, proxy-agent, database-driver, worker, process, native-socket, or dynamically loaded network authority remains unavailable from that graph unless a separately reviewed framework door explicitly owns it. `egress.allowDestinations` MUST be a dense list of exact HTTP(S) origins. Boot MUST reject an empty, malformed, credential-bearing, path/query/fragment-bearing, non-HTTP(S), or non-string entry instead of warning and widening or silently narrowing posture. Boot canonicalizes scheme, URL-normalized hostname (including Unicode, legacy IPv4, IPv6, case, and a DNS trailing dot), and effective port into one origin identity. The initial request and every redirect or pooled-request origin MUST match that canonical set **before DNS, proxy selection, pool reuse, or dial**. Every admitted hostname request/hop MUST resolve all candidate addresses and classify all of them; any closed answer closes the whole request. Every new TCP dial MUST classify the exact resolver result that Node may select and pin that immutable result into the dial, so DNS rotation is admitted only when the origin remains declared and every new answer remains safe. A declared private origin additionally needs the ambient `allowInternal` posture below. A framework-created database socket is a separate, module-private exact-endpoint capability: it may follow DNS rotation for its registered Postgres host/port without opening that endpoint to unrelated sockets. Arbitrary application proxy/dispatcher configuration is unsupported and MUST fail boot or be stripped from `ctx.fetch`; an operator-controlled transparent proxy remains deployment authority outside this application-level origin proof and does not turn the private-network floor into a sandbox. Future agent-tool APIs MUST supply this same contextual door before they are supported. Same-process deliberately malicious code or intrinsic poisoning is outside this construction proof, as stated by the capability-closure boundary above.

**Outbound egress: the private-network deny floor (normative, runtime defense-in-depth — NOT a proof).** The threat is the **SSRF network position**: a reflected or forged inbound request coaxes the server into making an _outbound_ request to an address it must never reach — cloud instance-metadata (`169.254.169.254` and the AWS ECS/EKS variants `169.254.170.2`/`169.254.170.23`, the AWS IMDSv6 `fd00:ec2::254`, Azure's IMDS plus its `IDENTITY_ENDPOINT` loopback, GCP's `metadata.google.internal`), localhost sidecars, or internal-only services on RFC1918 / link-local / unique-local / CGNAT ranges. The payoff is managed-identity credential theft off the metadata endpoint, or an internal-service pivot. Kovo installs the floor when `app.assemble()` closes the `defineKovo` contract and accepts explicit operator config through `defineKovo({ egress: { allowInternal: ['otel:4318', '10.0.5.2:6379'] } })` with the following normative behavior. **All public/external egress is UNRESTRICTED at this ambient process floor** — the positive framework capability above is the separate control that closes public destinations reached through `ctx.fetch`. **Private / loopback / link-local / unique-local / CGNAT / IANA-special destinations are DENIED by default in production and whenever an explicit `egress` object is supplied**, reachable only when the exact `host:port` is in the operator's narrow `allowInternal` allowlist (broad CIDR entries are flagged and warned). In development, an omitted `egress` option still installs both enforcement layers and still denies cloud metadata, but permits non-metadata private-network destinations so localhost DB/Redis/OTel/Ollama sidecars do not brick ordinary local boot; pass `egress: { allowInternal: [] }` in development to exercise production empty-allowlist semantics. A blocked connection throws a typed 502-class `EgressBlockedError` naming the destination and the remediation. **The cloud instance-metadata endpoint is DENIED by default and is NEVER reachable via `allowInternal`** — it is reachable only inside a module-private `metadataAllowed` `AsyncLocalStorage` frame entered ONLY by the per-cloud credential factories `awsCredential()` / `gcpCredential()` / `azureCredential()`, which wrap the cloud SDK's credential provider so a token _refresh_ re-enters the frame. There is deliberately no generic `withMetadataAccess` helper. A reflected SSRF never calls a factory, so it never enters the frame, so metadata stays denied at the very same IP — provenance-as-current-frame, unforgeable by SSRF (it survives the `await`/timer boundaries that destroy stack frames) yet still runtime-DiD, not a proof. **DNS64/NAT64 topology is explicit operator authority.** Kovo always decodes RFC 6052's well-known `64:ff9b::/96` carrier. A deployment using any Network-Specific Prefix MUST list every active translator prefix in `egress.nat64Prefixes`; automatic RFC 7050 discovery or A/AAAA correlation is not accepted as the policy root. Only `/32`, `/40`, `/48`, `/56`, `/64`, and `/96` are valid. Boot MUST reject malformed CIDRs, set host bits, a non-zero `/96` u octet, duplicate/overlapping configured prefixes, and any configured prefix that overlaps the implicit well-known decoder. The framework snapshots and canonicalizes the resulting prefix set as process-global posture. At the sink, a matching configured prefix is decoded using RFC 6052 Table 1 before the context-free IPv6 registry verdict: the u octet MUST be zero for layouts shorter than `/96`, suffix bits are ignored, and the embedded IPv4 destination is classified normally. This explicit topology may expose public IPv4 through RFC 8215's local-use `64:ff9b:1::/48`, but embedded metadata remains metadata and can never be reopened by `allowInternal`. The decision rule runs **per request and per redirect hop, at BOTH enforcement layers**: resolve the host → normalize (IPv4-mapped `::ffff:`, decimal/octal/hex, well-known NAT64, and configured Network-Specific Pref64) → pin the exact validated resolver result from which Node may select a dial address → public IP allow; metadata IP allow iff the `metadataAllowed` frame is active; other non-public IP allow iff the development omitted-config posture permits it or `host:port ∈ allowInternal`; anything not confidently classified as public fails **closed**. Enforcement is **dual-layer because a single layer fails open**: (a) a custom undici dispatcher at the per-request `dispatch()` level — pooled-socket reuse skips the per-connection hook, so a connect-only check would pass the _second_ request to an origin; and (b) the `node:http`/`node:https` + `net.Socket.prototype.connect` layer — AWS IMDS via `@smithy` uses raw `node:http` and bypasses undici entirely — which also injects a pinning `lookup` so a TOCTOU DNS-rebind cannot swap a public answer for a private one between check and dial. Bootstrap installs both layers at the assembly chokepoint and runs a **loud startup self-probe** that warns unmissably when the floor is not installed; production refuses boot when the floor is missing, partial, tampered, or disabled without an audited non-empty opt-out justification. Because monkeypatches do not cross `Worker`/`child_process` boundaries, every worker/child bootstrap that serves requests MUST re-install (the self-probe is the safety net). Prototype-freezing is **opt-in / off by default** (it breaks Datadog/OTel/nock). This control is **labeled everywhere as a fail-closed runtime defense-in-depth floor, never a by-construction proof**. Residual fail-open holes (enumerated, by design): same-process app code can re-patch `net.Socket.prototype.connect` or call `setGlobalDispatcher` after the floor; `Worker`/`child_process`/native-socket paths the JS layer never sees; arbitrary raw per-call dispatchers/proxy agents outside the supported capability graph; and provider-shape drift in a future undici/node internal. The floor is **redundant on Lambda/PaaS/Workload-Identity-Federation** where IMDSv2 / hop-limits already close the metadata path; it earns its keep on long-lived managed-identity VMs and against the internal-service pivot.

**Scoped keys for non-database state (normative, fail-closed runtime authority).** Every
app-addressable key that reaches a non-database stateful sink MUST be a runtime-opaque `ScopedKey`,
not a string or structural brand. Its canonical physical identity is the exact length-prefixed frame
`(kovo-scoped-key-v1, posture, authority, app-key)`. Length framing is over JavaScript code units and
MUST distinguish delimiter, slash, NUL, and ill-formed-surrogate placements without concatenation
ambiguity. `posture` is exactly one of `principal`, `public`, or `system`: principal authority comes
only from the framework-installed request session snapshot (`scopedKey(request, key)`) or an audited
task `actAs(id).stateKey(key)` scope; public authority comes only from the named
`publicScopedKey(key)` capability; system authority comes only from a finite framework-owned posture
registered in the C9 census. App-authored principal ids and free-form system reason strings are not
key authority. Authority components and every public/principal app-key are non-empty strings of at
most 1,024 code units. The finite `mutation-replay` system posture alone may carry the already-
bounded canonical `(scope, idem)` subframe as its app-key beyond 1,024 code units; its complete outer
`ScopedKey` frame MUST remain at most 4,096 code units. No app-facing constructor or other system
posture inherits that composite-key exception.

The public TypeScript type is ergonomics, not the proof. A module-private runtime witness owns the
frame and exact posture facts; storage, signed-URL, stored-file-response, durable-task queue,
mutation-replay, bounded rate-limit, and derived-dataset doors MUST authenticate that witness before
reading any fields or deriving a namespace. Bare strings,
casts, object literals, copied properties, proxies, malformed/non-canonical persisted frames, and
unregistered system postures fail **KV450**. A validated key remains opaque to app code; framework
internals may restore a persisted frame only through the same canonical parser and finite-posture
check. Storage object results may expose the normalized app-key string as descriptive metadata, but
that string carries no authority back into a sink.

The compiler MUST also fail **KV450** at every statically visible storage key, signed-URL `key`,
stored-file-response key, and durable-schedule coalescing `key` unless every finite branch derives
the value through the exact `scopedKey`, `publicScopedKey`, task `stateKey`, or task
`systemStateKey` constructor. Casts, structural lookalikes, runtime-selected options, computed
properties, and option spreads do not establish provenance. This compile gate is an author-facing
early closure only; the module-private runtime witness remains the enforcing authority at the sink.

**Derived vector datasets inherit authorization (normative).** The only supported transition from
managed owner-scoped/governed database data into a persistent non-engine vector/RAG artifact is the
exact module-constant `derived(adapter, { key: <non-empty static string>, kind: 'vector' })` door.
Every `query(request, query)` and `upsert(request, records)` operation MUST receive the exact
framework request carrier. The runtime re-runs `scopedKey(request, 'derived/vector/' + key)` for
every operation and constructs the physical namespace as
`kovo-derived-vector-v1/<sha256(complete-canonical-ScopedKey-frame)>`; neither an app call site nor a
query/write payload can provide or replace that namespace. Query results and upsert arrays are
dense, bounded, immutable snapshots at the adapter boundary, and adapter callables are pinned at
construction. Equal logical keys under different principals therefore produce different physical
artifact identities, while reads under the same principal reconstruct the identity used by writes.

The compiler tracks managed DB and derived reads as `governed-data` and emits **KV452** when that
provenance reaches storage `put`, framework egress, or a durable-task payload outside the exact
`derived()` door; transforms, aliases, containers, conditionals, and exact same-file helpers do not
erase the label. A derived read/write with a missing, forged, or non-request first argument is also
KV452. A same-spelled local, imported lookalike, alias, dynamic options object, spread, surplus key,
unsupported kind, or request-time constructor does not acquire derived-dataset authority and remains
inside the ordinary KV449 fail-closed rules.

The adapter is a deployment boundary, not an authorization proof: Kovo guarantees that it supplies
only the reconstructed opaque namespace, but the selected adapter/service MUST faithfully isolate
that namespace. An adapter that ignores, truncates, aliases, or externally broadens the namespace
invalidates the derived-artifact isolation claim and is a retained deployment obligation. Deliberate
same-process code that captures the adapter input and performs another raw write remains outside the
app-level proof per the capability-closure boundary above.

Memory storage keys by the complete frame. Filesystem storage hashes the complete frame with SHA-256
for its bounded physical slot and atomically records the exact frame in the sidecar, refusing a
digest collision. S3-compatible storage uses a framework-owned `kovo-storage-v1/<sha256(frame)>`
namespace. Consequently equal app keys in different principal/public/system postures never address
the same physical object. This is an unconditional technical-preview contract: no legacy string-key
fallback or compatibility namespace exists.

**Capability URLs for storage downloads (normative, by-construction at the verify sink).** A download URL for a stored object is signed, short-lived, and scope-bound so the object is _un-dereferenceable without a valid token_. `signCapability` mints a token over the canonical, length-prefixed tuple `(version, signing-key-id, method, scoped-key-frame, expiry, scope, one-time, nonce)` (canonicalize-before-sign, so no field-confusion collision or unsigned replay/key-selection field) using the framework signing secret; the framework-owned download route MUST restore the runtime-witnessed `ScopedKey` from the request path, then `verifyCapability` — re-canonicalizing the exact frame/method/scope it derives _from the request_ and comparing the HMAC in constant time — **before any storage read**. Because the route supplies the expected claims rather than trusting the token's, a token for object `a` cannot authorize reading object `b`, or the same app key in a different owner posture, even with a valid signature. Verification is fail-closed and ordered (bounded frame parse → token parse → constant-time signature → expiry → claim match → one-time burn); rejection reasons are never leaked to the client. This is **by-construction at the verify sink** (an object cannot be read without a verifying token), with one honestly-labeled limit: the URL is a **bearer credential** whose _leakage_ via `Referer`/logs/CDN is mitigated (short expiry by default, narrow scope, and an optional one-time token posture) but **not proven**. The framework storage **download route** that hosts the sink is **shipped**: `createStorageDownloadEndpoint` builds a prefix-mounted GET/HEAD `endpoint()` whose handler re-derives the expected scoped frame/method/scope from the request and runs `verifyCapability` before any storage read (a generic, reason-free 404 on any failure), and `ctx.signUrl({ key, method?, scope?, expiresIn?, oneTime? })` accepts only a witnessed `ScopedKey` and mints a URL pointing at that route (canonicalize-before-sign; short-expiry default). Capability signing and verification MUST reject before unbounded decode, parsing, canonicalization, or audit retention: complete scoped-key frames are limited to 4,096 code units, scopes and audiences to 1,024 each, decoded payloads to 12,000 bytes, complete wire tokens to 16,384 code units, and TTL to at most one hour. Production MUST refuse a missing, custom, or volatile download replay store and accept only the opaque durable store exposed by `createPostgresAppRuntimeDb().capabilityReplayStore`, even when the app currently mints only ordinary tokens; this keeps one-time posture from becoming a deployment-time footgun and makes replica/restart truth mandatory before the sink can serve. Production signing, verification, signer construction, and download-route construction MUST also refuse caller-injected clocks, so expiry is measured only against the framework-owned wall clock (and durable one-time insertion is additionally guarded by the database clock). Every mint records the normalized app key plus exact key posture in a capability fact surfaced by `kovo explain capabilities`.

Capability token `v4` supersedes the base tuple above by signing
`(version, signing-key-id, method, scoped-key-frame, expiry, scope, principal-epoch, one-time,
nonce)`, with the epoch field empty only for an unscoped token. A principal-scoped mint requires an
active authoritative epoch; verification requires the same current epoch after signature, expiry,
and request-derived claim matching but before a one-time replay burn or storage read. `v3` and all
older versions are intentionally rejected rather than accepted through a compatibility path.

---

---

<!-- Source: spec/07-navigation.md -->

# Interaction Ladder & Navigation (SPEC §7-§8)

This file is incorporated by reference from [../SPEC.md](../SPEC.md) and is normative for Kovo framework behavior.
The root spec remains the entry point and cross-reference index; this module owns the detailed contract below.

## 7. The Interaction Ladder

Interactions must use the lowest layer that suffices. The compiler enforces L0 substitutions; lints nudge the rest. Navigation between _places_ is always a real URL and server route (§8); enhanced navigation may intercept eligible clicks only as a progressive enhancement over that same full-document GET. The ladder governs interaction _within_ a place.

| Layer  | Mechanism                                                                                                                               | Example                               | JS shipped                             |
| ------ | --------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------- | -------------------------------------- |
| **L0** | Platform behaviors: invoker commands, Popover API, `<details>`, `<dialog>`, `:has()`, scroll-driven animations                          | Open cart drawer                      | 0                                      |
| **L1** | Pure client islands: local state + the update plan (bindings/derives/stamps, §4.8); loaded on interaction or a declared trigger (§4.7)  | Price-range filter UI, tabs, carousel | handler module on first touch          |
| **L2** | Mutations: real forms + enhanced fetch → fragment/query patch                                                                           | Add to cart                           | loader (already present) + form module |
| **L3** | Optimistic: compiler-derived or declared transforms over query values                                                                   | Instant badge tick                    | transform module                       |
| **L4** | Live: SSE pushing the same fragment/query vocabulary; BroadcastChannel tab sync + refetch-on-focus cover common lower-cost cases (§9.3) | Order status, presence                | `<kovo-live>` subscriber               |

**Cross-island coordination**, in order of preference: (1) **the URL** — filter writes `?max=500`, or is a GET form whose fragment response is the grid, both typed against the route's `search` schema (§6.4); (2) **scoped client state** — only when the state is local UI intent rather than server/query facts, and still lint-gated with a required justification comment. Typed fire-and-forget cross-island events (`emit(...)`/`on(...)`) are not a shipped v1 authoring surface; the repo's internal event-bus experiments do not create public runtime bindings or a registry contract.

---

## 8. MPA Spine & Navigation

- **No client router.** Each page has a complete server document; route handlers are server functions declared with `route()` (§6.4), which carries the path's literal type, param/search schemas, and per-route config. `<Link>`/`href()` are compile-time sugar lowering to plain `<a href>` — typed links whose native URL remains the canonical behavior.
- **Enhanced navigation is a progressive enhancement, not an app mode.** The loader may intercept only eligible same-origin, unmodified, GET anchor navigations. It fetches the target document in the structured `kovo-document-parts/v1` representation below, validates the envelope's app build token BEFORE constructing any DOM, builds the detached target document from the structured parts, validates compiler-derived segment metadata, updates or validates document-shell state, and morphs only compatible changed segments. The current document's layout chain is an optimization hint only; the target server document decides the route, guard outcome, layout chain, head, and body. A fetched response is eligible for in-document interpretation only when `Content-Disposition` is absent or one structurally valid, unambiguous `inline` value; `attachment`, extension dispositions, combined/duplicate field values, or malformed parameters preserve the browser's native download/navigation boundary through the normal full GET before any response-body read or apply. On non-parts content type (including a declined `text/html` answer), cross-origin URL, modified click, target/download/hash-only navigation, redirect/guard uncertainty, shell drift, build-token mismatch, envelope/construction failure, or any missing proof, the loader performs the normal full GET.
- **The document-part representation (normative).** The enhanced-navigation Accept header is `application/vnd.kovo.document-parts+json, text/html`, and the negotiated variant is a JSON envelope encoding the exact canonical rendered document — the same full-document oracle, never a server-side diff and never an HTML string the client would have to re-parse. The envelope is `{"protocol":"kovo-document-parts/v1","build":<exact §5.2.1 app build token>,"htmlAttrs":Attrs,"bodyAttrs":Attrs,"head":[Part…],"body":[Part…]}` where a `Part` is a JSON string (an entity-decoded text node), `["!", text]` (a comment node), `[tag, Attrs, [Part…]]` (an HTML-namespace element), or `[tag, Attrs, [Part…], ns]` (a foreign element; `ns` 1 = SVG, 2 = MathML), and `Attrs` is `[[name] | [name, value], …]` (`[name]` is a valueless attribute). Every 200 document response — parts and ordinary `text/html` alike — carries `Vary: Accept` and the same `Kovo-Build` transport token, and the envelope `build` MUST equal that transport token. The client validates `build` against the immutable page-load proof before any DOM is constructed, so a stale build can never be applied to a newer document (§14); the built document's `kovo-build` meta is then re-validated as the in-document proof.
  - **No string→DOM sink.** The client applier builds the detached target document exclusively through captured construction controls — `createElement`/`createElementNS`/`createTextNode`/`createComment`/`setAttribute` — and then morphs DOM-to-DOM. `DOMParser`, `innerHTML`, and every other HTML-string sink are absent from the navigation and lifecycle document paths, so the document CSP's `require-trusted-types-for 'script'` holds without any navigation parser policy.
  - **Inert by construction.** A parts document carries no executable content: the only admissible `<script>` shapes are the framework's JSON data script (`type="application/json"`) and the Speculation Rules data block (`type="speculationrules"`), never with `src`; native event-handler (`on*`) attributes, `srcdoc`, `is`, and `<base>` never encode. The server refuses to encode a document that violates this floor and the client independently aborts construction on the same conditions. The negotiated variant also omits the SPEC §4.4 inline bootstrap — the requesting realm already installed it — and the applier performs no script replay.
  - **Fail-closed encoding.** The server encoder tokenizes the exact canonical document bytes and MUST refuse — answering the canonical `text/html` document instead — whenever it cannot prove the built tree matches what the browser parser would produce for those bytes (parser-ambiguous markup, unknown character references, deferred/streaming documents, non-buffered outcomes). A refusal degrades enhanced navigation to a full load; it never degrades to a wrong or unsafe DOM.
- **Navigation partials are not a v1 protocol.** Enhanced navigation uses the full target document as its oracle; the parts envelope is an encoding of that complete document, not a partial. Header-selected navigation fragments, target-chain hints, or route-partial responses remain a possible optimization only after no-JS/full-load versus enhanced-navigation render-equivalence is proven over the corpus; app authors cannot opt into or hand-author a navigation partial response.
- **Segment persistence is derived.** Only unchanged compiler-stamped layout/region segments may keep DOM identity. Changed layouts, changed route leaves, changed parallel regions, active nav/search/auth/query-dependent chrome, inserted islands, removed islands, and route boundaries are morphed from the target document or applied through wholesale body replacement. Segment stamps only ever ADD preservation: a target document with no segment stamps preserves nothing and is applied wholesale inside the same realm. App TSX never authors navigation segment stamps or persistence policy. If a route's declared `regions` cannot be matched to compiler-derived segment metadata in the target full document, enhanced navigation performs the normal full GET rather than preserving uncertain DOM.
- **Navigation state emulates the browser.** Enhanced navigation owns `pushState`/`replaceState`, `popstate`, scroll restoration, hash scrolling, focus movement, and route-change announcements only for navigations it successfully completes. A newer navigation aborts older fetch/morph work. Pending optimistic state is reconciled from the target server document or discarded by full GET; it must not silently survive into an incompatible document.
- **bfcache and bootstrap budget stay load-bearing.** Enhanced navigation must not add `unload` handlers or global session heaps that block bfcache. Only code that ships in the inline bootstrap (§4.4, emitted only for documents carrying client surface) counts against `inlineKovoLoaderGzipByteBudget`; the deferred runtime implementation of navigation, morphing, mutation response application, and query hydration is intentionally outside that byte cap. Future increases to the bootstrap budget require comparable first-paint, security-boundary, or first-class-wire-capability evidence.
- **Speculation Rules** are opt-in config, never auto-emitted: `prefetch: 'conservative' | 'moderate' | false` per route, declared on the `route()` object (§6.4), **default off**. Auto-prerender has real hazards — analytics firing inside prerendered pages, non-idempotent per-user renders, discarded-render server cost — so apps opt in route-by-route where renders are idempotent and cheap. `prefetch: 'moderate'` (which prerenders the route's `page`/`meta`/queries with the user's credentials on hover) is gated at compile time: it is **KV419** (`error`) to set `moderate` on a route that is guarded, session-dependent, or whose `page`/`meta`/queries are not proven side-effect-free, unless a named justification is supplied at the route (mirroring KV229 export gating and KV320). `conservative` (no eager render) is unaffected. The feature is one `<script type="speculationrules">` tag; the MPA is fast without it.
- **Cross-document View Transitions** opt-in per element pair via `view-transition-name` props; the compiler stamps matching names across route templates.
- **bfcache hygiene** is a framework guarantee: no `unload` handlers, `keepalive: true` on in-flight mutations at navigation, pending optimistic logs discarded on document teardown (stale-optimism-outliving-its-mutation is structurally impossible).

**bfcache hygiene is conditional on cache posture.** A bfcache restore is a history traversal that bypasses the loader and the network, so neither the route guard nor `sessionProvider` (§6.5) re-runs — a persisted authenticated document would otherwise reappear after logout, expiry, or revocation. Posture is computed from the same proof the export path uses (§9.5/KV229): a document is **session-dependent** when its route carries a guard or its render reads session-dependent query data, and **anonymous** otherwise. Guarded/session-dependent route documents MUST be emitted with `Cache-Control: no-store` so disk persistence and shared-cache reuse are forbidden. Independently — because some user agents still keep a `no-store` page in the in-memory bfcache — the loader MUST register a `pageshow` handler that, when `event.persisted` is true and the document was rendered under a guard or session dependence, revalidates by reloading from the server (a full GET, which re-runs `sessionProvider` and the guard) rather than presenting the restored DOM. Anonymous/exportable documents carry no such posture and remain fully bfcache-eligible; the `pageshow` handler is a no-op for them. The `pageshow` revalidation handler does not itself add an `unload` handler; if it ships in the inline bootstrap it counts against `inlineKovoLoaderGzipByteBudget`, and if it ships in the deferred runtime it does not. A session-dependent or fingerprinted document always carries client surface under §4.4, so the bootstrap that registers this handler is never omitted from a document that needs it.

- **Out-of-order streaming:** App TSX uses the public `<Defer>` primitive from `@kovojs/server` to declare a server-rendered region whose fallback is ordinary JSX/text and whose real content streams later in the same response. The framework emits the owned `<kovo-defer>` placeholder, then streams the real fragment and morphs it in — the fragment protocol reused within first render. Deferred query JSON is guaranteed to arrive before or with its consumers. The internal `defer()` string helper is not an app-facing JSX child API; app-authored `{defer(...)}` is lint **KV244**.
- **Mutation response streaming:** chat-style post-submit streams are not SSE and do not reuse `<kovo-defer>`. They are one enhanced mutation POST response whose chunks use the §9.1 mutation vocabulary plus the narrow `<kovo-text>` text-source primitive below.
- **Degradation contract:** Safari/Firefox get normal navigations and normal forms. The MPA degrades to "a website" — where an SPA shows a blank screen on the same failure.

---

---

<!-- Source: spec/09-wire-protocol.md -->

# Wire Protocol (SPEC §9)

This file is incorporated by reference from [../SPEC.md](../SPEC.md) and is normative for Kovo framework behavior.
The root spec remains the entry point and cross-reference index; this module owns the detailed contract below.

## 9. Wire Protocol

One vocabulary, transport-agnostic: document load, enhanced fetch, and SSE live updates all carry the same fragment/query chunks (§9.3). All payloads are human-readable (Constitution #4).

### 9.1 Enhanced mutation round-trip

```http
POST /_m/cart/add HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Kovo-Fragment: true
Kovo-Build: <app-build-token>
Kovo-Targets: cart-badge=cart; cart-drawer=cart; recommendations=%21product%21product%3Ap1
Kovo-Live-Targets: cart-badge#cart-badge@<attestation>:{}; recommendations#recommendations@<attestation>:{"productId":"p1"}
Kovo-Idem: 7f3a-…                          ← stamped hidden field; server replays duplicates

productId=p1&quantity=2&kovo-csrf=…
```

```http
HTTP/1.1 200 OK
Content-Type: text/vnd.kovo.fragment+html; charset=utf-8
Kovo-Changes: [{"domain":"cart","keys":["cart"]},{"domain":"product","keys":["p1"]}]

<kovo-query name="cart">{"count": 3, "items": […]}</kovo-query>
<kovo-fragment target="recommendations">
  <!-- server-rendered HTML, produced by Recommendations.render(…) — the SAME
       render function full page loads use; partials cannot drift from pages -->
</kovo-fragment>
```

**Framework wire-input registry (normative).** Every framework-owned header, cookie, or URL-search
input belongs to one core-owned typed registry that declares its carrier, canonical name, and finite
grammar. Kovo's browser encoder and server decoder for a structured wire value MUST be derived from
the same exact codec; independent handwritten implementations of the grammar are not conforming.
The codec is covered by a seeded `decode(encode(value)) ≡ value` oracle, including delimiter,
escaping, Unicode, size, and malformed-input cases.

`Kovo-Build-Skew` is a framework-reserved, response-only field. Its sole valid value is the exact
ASCII token `true`, and it is meaningful only on an admitted HTTP 409 response that also carries
the selected app's `Kovo-Build`, the inline fragment media envelope, and the private/no-store
posture. An ordinary unmarked application 409 (including a typed stale-version conflict) is not a
deploy-skew verdict. Application response headers cannot mint, replace, or override this marker.
Intermediaries serving enhanced Kovo traffic MUST preserve it; if they instead alter or remove the
coupled build stamp, the unequal/missing-token recovery rule still forces a full reload.

Identity tokens use one canonical percent codec: RFC 3986 unreserved bytes remain literal and every
other UTF-8 byte is `%HH` with uppercase hex. Decoders reject raw delimiters, lowercase/non-minimal
escapes, invalid UTF-8, NUL, CR, and lone surrogates; they never trim ordinary data. `kovo-deps`
stores exact query-dependency tokens separated only by ASCII spaces. An unkeyed dependency is the
canonical query-name token. A keyed dependency is `!<canonical-name>!<canonical-full-key>`; `!` is
reserved as the structural delimiter and is `%21` inside an ordinary identity, so `{name, key}` is
injective and can never collide with an unkeyed name equal to the full key. Component identities are
encoded like target identities. A present empty DOM identity (for example `kovo-key=""`) remains distinct
from an absent attribute, but header/query names and instance identities are non-empty. DOM token
decoding is linear and has no HTTP-size ceiling; the 4,096-character ceiling belongs only to each
HTTP form/list encoder.

Every target-bearing browser request carries a required fragment-free `Kovo-Current-Url` of at most
1,536 ASCII characters and the exact immutable document app build token in `Kovo-Build`. The
framework headers together have an exact 9,216-byte HTTP/1 line budget,
counting each name, `: `, value, and trailing CRLF. A present form target is required to fit; target
and live-target lists truncate only at complete entries and empty list headers are omitted. Delegated
submit performs this preflight before `preventDefault()` and leaves the native submit untouched when
the URL, form target, or required lines cannot fit. Direct programmatic fetch fails explicitly.
Mutation, HMR, and lifecycle target refreshes use `Referrer-Policy: origin`; malformed responses or
refresh failures trigger the owning full-document recovery path rather than leaving stale truth.

Every framework-owned read of a registered carrier MUST pass through a named canonical reader and
be present in the exact TypeScript-symbol census enforced by `check:wire-input-boundary`. A literal
read binds the exact carrier and canonical name in the registry. Only an explicitly reviewed
dynamic door may bind the registry's `*` name, and that door remains responsible for applying the
selected entry's grammar before the value reaches framework behavior. Same-named lookalikes do not
satisfy the census. App-owned reads and reviewed third-party adapters are outside this registry
unless their value enters a named framework protocol door; at that point the normal registry and
reject-by-default rules apply.

- `Kovo-Targets` is read off the live DOM (`kovo-deps` stamps), so islands patched in after page load participate. Its semantic dependencies are exact `{name, key?}` facts, not colon-split strings. The header encodes the target and each complete dependency token through the canonical identity codec: for query `product` at full instance key `product:p2`, the physical entry is `product-form%3Ap2=%21product%21product%3Ap2`. The server holds **no session of what's on screen** — it answers a stateless question.
- `Kovo-Live-Targets` is the structured reconstruction companion for server-refreshable component targets. Each entry names the live target, its generated component registry key, and the serialized props/key identity the compiler proved sufficient to reconstruct the component instance. Every entry MUST carry a server-minted attestation over that canonical descriptor, the canonical source-document URL (origin, path, and query; never the fragment), the exact §5.2.1 app build token, the CSRF session binding (including the framework-minted anonymous CSRF cookie when there is no app session), the independently resolved framework principal, and a separate app authority audience. A mutation or HMR sink MUST same-origin validate that source URL, match it to one canonical app route, rerun the route's complete layout/route guard chain, and use only the resulting authorized source-route request for response-side query and component rendering; the mutation or HMR endpoint request is never a substitute render context. A typed failure may select only the compiler-owned component renderer that both matches the submitted form target and declares the submitted mutation key. `defineKovo({ appId })` supplies the replica-stable app part of the audience when its single `assemble()` closes the contract; `appId` MUST be a canonical UUIDv4 generated once per distinct app. `create-kovo` generates it and migrations preserve it. A production app with live-target renderers MUST declare it, and distinct apps MUST use distinct UUIDs and signing secrets even across processes or isolates. A rendererless production app or development app that omits `appId` receives only a boot-local audience, never distributed authority. The app build token is deploy-skew identity and MUST NOT be treated as the app security principal merely because two apps can share the same render contract and active module set; both values are signed independently. Dev mode keeps the descriptor explicit and inspectable; prod may replace the JSON with a versioned token only when `kovo explain` can recover the same value. App authors never construct this header, import target constants, or route mutations to fragments by hand.
- The synchronizer token, replay scope, and live-target attestation consume one exact CSRF binding. On a framework lifecycle request, `CsrfOptions.sessionId` MUST return an opaque non-empty string of at most 1,024 characters for a resolved session and `undefined` only for a genuinely anonymous request. A non-string, missing, empty, or oversized authenticated value, an unresolved framework session, or an anonymous framework posture paired with a defined id fails closed; it never falls back to the anonymous cookie. Standalone CSRF helper inputs without framework lifecycle posture continue to treat the callback as their declared session/anonymous authority. Session ids and anonymous-cookie secrets occupy separately labeled, canonical length-framed domains, and the framework-resolved authorization principal is independently framed into authenticated bindings, so shared or namespace-shaped rotation ids cannot validate or replay across principals. Every authorization principal and source-derived mutation identity entering that replay scope is non-empty and at most 1,024 JavaScript code units. An inbound anonymous-cookie secret is 32..1,024 base64url characters (framework minting produces 43); a present malformed or oversized cookie fails closed instead of being silently replaced.
- A `csrf: false` mutation never derives replay authority from a session field or a mutation-wide
  fallback. With replay storage active it MUST declare `machineReplayPrincipal(request)`, evaluated
  exactly once after parse/coerce and the successful guard/access decision. The callback receives
  that pinned post-guard request and MUST return a primitive 1..1,024-code-unit caller/tenant id.
  Missing, malformed, thrown, or rejected results produce the generic 422 idempotency conflict
  before replay-store or handler work. Kovo length-frames the value under a versioned
  machine-replay domain and SHA-256 commits the exact UTF-16LE encoding of every JavaScript code
  unit, including lone surrogates; raw identity bytes never enter store keys or diagnostics.
  Protected-CSRF declarations reject this field. Enhanced and no-JavaScript requests use the same
  claim namespace, while their response classifiers stay closed: a cross-mode retry conflicts and
  cannot execute under a second namespace. Buffered and streaming enhanced requests likewise share
  one claim. The endpoint first normalizes the requested mode to what the dispatched mutation can
  actually produce (a mutation without a stream hook is buffered), then binds the committed replay
  record and live response to that delivery vocabulary. A streaming record/response carries the
  exact framework-owned own-data header `Kovo-Stream: true`; a buffered one omits every casing of
  that name. The final framework response and replay-settlement seals remove app-authored marker
  attempts before minting that vocabulary. Replay release requires a stable header-name snapshot,
  at most one exact-cased marker, the exact string value `true`, and equality with the current
  normalized delivery mode. Missing stream markers, injected buffered markers, duplicates, wrong
  casing/value, accessors, and buffered-to-stream or stream-to-buffer retries produce the same
  sanitized 422 idempotency conflict before stored bytes are returned and without rerunning the
  handler. Same-mode streaming replay stores the complete settled body, including `<kovo-done>`.
- Principal-derived durable authority carries the §6.6 persistent epoch without making it a
  caller-selected wire knob. A capability URL payload uses version `v4` and includes signed `p`
  (the positive integer epoch) whenever signed `s` (the principal scope) is present; a scoped token
  missing either field, an unscoped token carrying `p`, and every older version are malformed. The
  route checks signature, expiry, request-derived key/method/scope, and authoritative epoch before
  burning one-time replay truth or reading storage, and exposes only the existing generic 404 on
  failure. Mutation `Kovo-Idem` remains the canonical `v1` client token: the server-minted durable
  replay receipt, not this untrusted header/field, appends the current epoch to its principal-bound
  replay namespace. Response release, handler admission, in-transaction completion, and settlement
  each recheck current epoch; stale receipts are never released or silently moved into the new
  namespace.
- The compiler's enhanced-form completeness check follows HTML successful-control semantics rather
  than treating every matching JSX element as one simultaneous value. A single checkbox is a
  supported scalar boolean: checked submits its string value (`on` when omitted), while absence
  coerces to `false` only through a declared `s.boolean()` schema. Same-name radio controls are one
  mutually exclusive scalar group, and same-name submit buttons are one mutually exclusive
  submitter field whose selected button supplies the value. Disabled controls and
  `button`/`input[type=button|reset]` do not participate. Repeated same-name controls that are not
  one radio group or one submitter group remain **KV242** until an authored array/multivalue
  primitive declares their semantics. `input[type=image]` remains KV242 because the browser derives
  coordinate-suffixed names (`name.x`/`name.y`), and submitter `form`/`formaction`/`formmethod`/
  `formenctype`/`formtarget`/`formnovalidate` controls cannot replace or escape the compiler-owned
  mutation transport. Direct, reactive, spread, composed, or externally associated overrides are
  KV242 and must not survive into `data-bind:*` or emitted update-plan stamps. A control `type` that is not a
  static string (or statically absent) is also KV242 because changing it at runtime could change
  successful-control and submitter-override semantics after compilation. The browser runtime is
  the fail-closed floor: once `data-mutation` identifies a typed form, any effective method/action
  that is not the exact same-origin `POST /_m/<mutation-key>` transport is prevented, marked
  `INVALID_MUTATION_TRANSPORT`, and never allowed to fall through to native submission. In
  particular, a tampered `formmethod="get"` cannot serialize CSRF, idempotency, or form-field values
  into a URL. Ordinary native forms without compiler-owned `data-mutation` retain native behavior.
- **Browser response-body disposition (normative).** Every framework-owned fetch path whose response bytes can become live document, fragment, stream, or query truth MUST snapshot `Content-Disposition` with the other response facts and admit the body only when the field is absent or is one structurally valid, unambiguous `inline` value. `attachment`, extension dispositions, comma-combined or duplicate field values, controls, and malformed parameters fail closed before the body is read or a stream reader is acquired. Enhanced navigation performs the normal full GET so the browser retains download behavior; an already-dispatched mutation performs source-document reconciliation without applying the response; lifecycle recovery reloads; and a background typed-read refetch discards and reports the response. The readable/generated inline loader and modular runtime use the same classifier.
- `Kovo-Changes` is the sanitized wire summary of committed writes: each entry is `{domain, keys}`. It never includes mutation input, user-provided values, failure reasons, stack traces, or internal diagnostic detail; richer typed change records are internal compiler/runtime artifacts.
- `<kovo-query>` replaces the client's query value and runs that query's update plan — bindings, named derives, stamps — across every dependent island. No runtime dependency tracking: the plan is the DOM itself (§4.8). Query JSON serialized inline MUST be encoded for the exact context it lands in so attacker-controlled JSON string content cannot end the host element early. A `<script type="application/json" kovo-query="…">` initial-page island is HTML **script-data** (entities are not decoded), so its JSON MUST escape `<` as the JSON unicode escape `\u003c` — `&lt;` would not decode there and would corrupt the value. A post-mutation `<kovo-query>{…}</kovo-query>` element has **parsed** content, so its JSON MUST HTML-escape (`<`→`&lt;`, `>`→`&gt;`, `&`→`&amp;`). Both neutralize the `</script`/`<!--`/`<script` break-out; JSON quoting alone escapes neither and is insufficient. This is a normative renderer rule with a conformance test (`tests/integration/specs/xss-escaping.spec.ts`), and it binds every transport that re-emits an island — including the §9.3 BroadcastChannel rebroadcast, which forwards already-encoded bytes and never re-serializes raw values.
- `<kovo-fragment>` is **DOM-morphed** by default (idiomorph-class algorithm): user-agent and DOM-resident state — focus, scroll position, selection, in-flight CSS transitions, and `<details>`/media element UA state — survives. The morph carries **no serialization of island-local `kovo-state`**, so a refreshed parent re-emits any nested island at its render-time default state (§4.5 rule 3 re-renders the full subtree from declared queries ∪ stamped props); island-private local state is therefore **not** preserved across a fragment morph of an enclosing target. The compiler forbids the position that would silently lose it: an island declaring local `state` may not render inside another component's server-refreshable fragment target (**KV420**, §4.5). `mode="append"` is the explicit append vocabulary for pagination ("load more") and streams; `mode="prepend"` is its companion for "load older" feeds, inserting the patch at the **start** of the target. Both are ordered keyed inserts: a row whose `kovo-key` is already present is **deduped** (matched/skipped, never re-inserted) per §13.2, so a re-shipped page never duplicates rows. `mode="prepend"` additionally carries a **normative scroll-anchor guarantee** — the runtime treats the patched target as the scroll container and adjusts its `scrollTop` by the inserted height so previously-visible content stays fixed (no viewport jump when older content lands above). This is a framework guarantee, not an app knob. The read-side companion is a keyed-delta `<kovo-query … delta>` whose `lists.<path>` upsert merges the page into the SAME held query instance (§9.1.1) — `prepend`-flagged so new rows accumulate at the front of the held array — so "load more"/"load older" fetch only the new page and never re-ship prior rows. Patched-in islands are inert-until-touched like everything else — _a fragment update is a tiny navigation, not a different programming model._
- A streaming enhanced mutation response may be applied incrementally from a `ReadableStream` as complete wire elements arrive. User message rows and assistant shells still use `<kovo-fragment mode="append">`; token text uses `<kovo-text target="..." mode="append">escaped text</kovo-text>` against a compiler/runtime-declared stream source such as `data-stream-text="assistant-message:a1"`. `<kovo-text>` appends text, not HTML. `mode="checkpoint"` replaces the accumulated source text for that target with server-confirmed text so far. A stream source may declare an app-authored sink renderer for presentation, but Kovo owns the escaped source buffer and never inserts model output as raw HTML. The sink-renderer signature is constrained so this guarantee survives app code: a sink renderer is `(escaped: string) => string | TrustedHtml` — it receives the framework's already-escaped source text (never the raw model bytes) and MUST return either further-escaped text, which Kovo appends as text, or an explicit `trustedHtml(…)` value (§4.8) whose escaping it has itself discharged. A sink that returns a plain string is treated as text and re-escaped at the append boundary; only a `trustedHtml` brand is inserted as markup, so a markdown/rich sink reintroducing model-output XSS is an explicit, audit-visible KV236 trust decision rather than a silent default. The streaming text path is governed by the same §5.2 #10 output-safety contract as bindings. The final successful chunk must reconcile the affected assistant message or message list with ordinary `<kovo-fragment>` or `<kovo-query>` server truth; streamed text is progressive rendering, not a new authority.
- Streaming mutations run the same lifecycle before any user-visible assistant chunks are emitted: CSRF, schema parsing, guards, replay/idempotency reservation, and transaction policy. Interruption, abort, validation failure, guard/session failure, renderer failure, missing target, or deploy/build-token skew must either mark the submitted form/message failed or refetch/navigate to server truth. The runtime must not silently present a partial assistant answer as confirmed. Without JS, or when the form is not opted into streaming, the endpoint remains the existing POST-redirect-GET or buffered enhanced mutation path.
- **Without JS:** the same endpoint sees no `Kovo-Fragment` header and answers POST-redirect-GET with errors re-rendered into the full page. One handler, two response modes. A deterministic declared application failure settles its fully rendered response for same-mode replay. Validation, 429/rate-limit, and 409 retryable failures release the reservation. Response-policy or failure-rendering errors also release it, so a retry may render again rather than inheriting incomplete response truth.

Success response selection is deterministic and generated. After commit, the server intersects
`Kovo-Changes` with the submitted live `Kovo-Targets`. For each affected server-refreshable target,
the generated live-target registry supplies the component render function, serializable props,
declared queries, and query-arg bindings. The first v1 implementation reloads **all declared queries
for each selected target** in the same request context and returns a complete `<kovo-fragment>` for
that target. Query JSON and prod deltas are optimizations layered on this registry when §4.8 update
coverage and change-record scoping prove they are smaller and equivalent; they are not app-authored
configuration knobs. If a target cannot be reconstructed from declared queries plus serializable
props, the compiler emits KV311/KV303 before the response path can be relied on.

There is no ordinary app-authored `mutationResponse` switch, `fragmentRenderers` list, generated
target constant import, or `render*RegionFromDb` hook in the success path. Raw endpoints/webhooks,
downloads, auth redirects, and other non-component responses use their own declared framework
surfaces rather than a general mutation-response body override. Mutation failure does not run the
success selector: it re-renders only the submitted enhanced form target with typed failure state
(§9.2), while the no-JS path re-renders the full page with the same state.

The round-trip above is the **dev** (and no-JS) form: complete `<kovo-query>` JSON and full self-describing `<kovo-fragment>` HTML. Prod ships the same vocabulary delta-encoded, described next.

#### 9.1.1 Prod delta encoding (dev ships full)

Shipping a full subtree re-render or an entire query value on every mutation is content-proportional waste — it does not compress away because it is real content, not repeated symbols. In prod the framework therefore sends the **minimal change**, automatically. There is **no knob**: the dev/prod build mode is the only switch (Constitution #2 — no per-call-site configuration), and within prod the runtime picks delta-vs-full _per response_. Names are **never** mangled in either mode; #1 is untouched.

The delta is **scoped by the change record, not diffed against client state.** This is what keeps the server stateless (§9.1 — it holds no session of what's on screen): the server never asks "what does the client currently have?" It emits only what the committed write provably touched — the `Kovo-Changes` record carries the changed `{domain, keys}` (§9.1) — and everything outside that scope is, by server truth (#5), unchanged. Every server-truth chunk additionally carries a **settlement set**: the `Kovo-Idem` tokens of the commits whose effects that chunk's re-run already reflects (the triggering mutation's own token plus any prior committed mutation whose effect is present in the post-commit query re-run). The client uses the settlement set to drop already-committed transforms before re-applying pending ones (§10.4), so a transform whose write is already folded into arriving truth is never double-counted. A delta is therefore sound _by construction_, not by reconciling two states the server would have to remember.

- **Delta query JSON.** A `<kovo-query delta>` carries only the change-record-scoped portion of the value, not the whole value. The client deep-merges it into the held query value under the **deep-merge semantics (normative)** below, then runs the **same** update plan (§4.8) — bindings, named derives, stamps.

Deep-merge semantics (normative). The merge of a delta `Δ` into a held base value is defined field-by-field, and the §5.2.1 prod gate is tied to these exact rules:

- **Non-keyed scalar fields** (numbers, strings, booleans, null) present in `Δ` **replace** the base field wholesale; the delta carries the field's new value verbatim, never a partial.
- **Non-keyed object fields** present in `Δ` **replace** the whole object subtree wholesale — the merge does not recurse into a non-keyed object to retain base sub-keys. A non-keyed object the change could have touched is sent whole (objects are cheap); an absent non-keyed field leaves the base field unchanged, and the **only** way to drop a non-keyed field is to send its parent object whole with the field omitted.
- **Keyed collections** (arrays bound with `data-bind-list` + `kovo-key`, §4.8) are the sole structures that **merge by identity, not position**: `Δ` sends only the touched rows (upsert, matched by `kovo-key` per §13.2) plus an explicit **removed-key list**. A row absent from both the upsert set and the removed-key list is left unchanged; a row is dropped **only** by appearing in the removed-key list — never by mere absence. Within an upserted keyed row, each field follows the scalar/object replace rules above against that row's prior value.
- **Deletion vocabulary.** The removed-key list is the only deletion primitive. There is no per-field tombstone and no "set to absent" merge: to remove a keyed row, name its key; to drop a non-keyed field, resend its parent object whole without it. This forbids the stale-sub-key hazard where a partially-merged object retains a key the server meant to drop.

A collection is delta-eligible only when its `kovo-key` corresponds to a domain the change record scopes with explicit keys; otherwise that collection ships whole. JSON stays schema-shaped; a frame reads as "these keyed rows of `cart` changed."

- **Smaller fragments.** The primary fragment win is _not_ sending a server-computed DOM diff (that would require the client state the stateless server refuses to hold). It is: **prefer a query delta + the client update plan over full `<kovo-fragment>` HTML** wherever the plan grammar (§4.8) covers the subtree, and for list fragments the change record can bound, send only keyed `mode="append"`/upsert rows rather than the whole list. A subtree the plan cannot express and the change record cannot bound ships as full fragment HTML — the §9.1 form, unchanged. The morph stays the same client path; it is simply fed query-driven updates or keyed rows instead of a whole subtree.
- **Base-version validation (mandatory).** A delta assumes a base — the client's held query value — that is present and was produced by the same build. Two ways it can be unsafe: the client has **no base** for that query (an island patched in after first paint, or a cold store), or a **build skew** (a long-open tab or stale prerender against a redeployed server whose query shape or active module set moved). Every page render, every delta response, and every `/_q/` read response carries the build's **app build token** (§5.2.1); the client applies a delta only when the token matches _and_ a base is present, and treats any token-mismatched read or delta as a §14 build-skew event. On either failure it does not guess — it discards the delta and **refetches the full value over the typed read endpoint** (`/_q/<key>`, §9.4), a cheap GET. The client may also send its token up on the mutation request so a skew-aware server emits full directly and saves the extra round-trip. Deploy skew goes from silently-wrong to loud-and-recoverable — see §14 for the version-recovery contract and the mandatory prior-version retention window.
- **Automatic full-vs-delta selection.** The runtime ships whichever is smaller and sound: a query with no delta-eligible collection, a tiny value, the first render of a patched-in island, or a build-token mismatch all ship full. The rule is deterministic so the fixpoint and render-equivalence gates (§5.2.2) stay sound — the prod gate is `apply_delta(base, render_prod(Δ)) ≡ render_dev(full)` over the corpus.
- **Reconstruction for debugging.** `kovo explain`/MCP reconstructs the full query value from a prod delta + the held base, so an owner or agent handed a prod frame recovers dev-equivalent legibility. This is a convenience, not load-bearing: names are intact and the partial payload is already named and schema-shaped.

Mutation handlers may attach response headers through a narrow context channel. The channel is for transport metadata such as `Set-Cookie` and cache headers; it does not let handlers replace the body, status vocabulary, query reruns, fragment rendering, or PRG redirect contract. Header values emitted on the enhanced and no-JS paths are merged with framework headers after CSRF, replay, parsing, guards, and transaction commit complete.

**Header-channel transport safety (normative).** Structured app response authoring is settable only through typed surfaces; it is not a raw string map. The direct `headers` bags on `respond.file()`/`respond.stream()` and configured error shells accept only `Cache-Control`, `Last-Modified`, and `Vary`, under case-insensitive runtime comparison; any other direct name is rejected with **KV415**. File/stream `Content-Type`, `ETag`, and `Content-Disposition` travel through the `contentType`, `etag`, and `filename`/`disposition` options. The shared live/generated filename serializer MUST replace Unicode bidirectional formatting controls before constructing both the ASCII fallback and RFC 8187 `filename*`, even when earlier upload ingestion already sanitized its metadata. The `Content-Disposition` filename serializer and raw `forwardSetCookie` reserializer MUST return only after the same fail-closed whole-value postcondition accepts the output: CR, LF, NUL, a reverse solidus outside a quoted field, an unterminated quote/escape, a quoted-pair other than `\"` or `\\`, or a second quote/escape after the field closes is a framework error. The emitted Node filename path MUST execute the exact same five-state transition and terminal-verdict functions as the live runtime. `Location` is minted by `redirect()`. The HTTP `Refresh` response header is forbidden under every casing and for every value: browsers treat it as navigation, so accepting it would bypass the typed `Location` redirect posture and origin allowlist. Statically visible `Response` init occurrences close at compile time, and the complete structured/raw response finalizers plus direct and generated Node, Vercel, and Cloudflare boundaries reject any remaining occurrence with **KV415** before it becomes browser-visible. This prohibition does not change the existing `Location` redirect contract. `Set-Cookie` is built only through the typed mutation cookie builder (`context.setCookie(name, value, options)`), which percent-encodes the value, validates the name against the cookie-name grammar, forbids CR/LF/NUL/`;` in name and value, and serializes attributes structurally so a user-supplied value can neither inject a second cookie nor add unintended attributes. `Kovo-*` names remain framework-only. Raw endpoint `Response` values and operator-owned static metadata may carry other end-to-end integration headers, but they remain subject to the adapter-owned transport floor below. Every name and every value a structured channel emits MUST be rejected if it contains CR (`\r`), LF (`\n`), NUL, or any control character outside the printable header grammar; the channel never strips-and-continues. The same rejection rule applies identically to enhanced and no-JS mutation paths. This is the header-channel analogue of the `<kovo-text>` and `Kovo-Changes` injection discipline (§9.1): values flowing out a header are contextually safe by construction, never by author care.

**Browser-state cache floor (normative).** `Set-Cookie` does not itself prevent an HTTP shared cache from storing and replaying a response. Final wire reconstruction therefore MUST replace any authored or declared cache policy with `Cache-Control: private, no-store` and merge `Cookie` into `Vary` whenever a structured or raw response carries `Set-Cookie`. The same floor applies to `Clear-Site-Data`: replaying a cached clear instruction can destroy an unrelated visitor's browser state without reaching the endpoint verifier. It also applies whenever a document or raw response emits a synchronizer token or live-target attestation derived from the anonymous-CSRF cookie, including when that binding already existed and the response carries no `Set-Cookie`; those body bytes are cookie-personalized authority and cannot be reused for another visitor. A streaming document with any pending deferred region MUST conservatively select this posture before headers cross the wire, because a callback can first emit mutation-local CSRF authority after the initial shell and that future path is not knowable from the shell. Before committing those headers, the framework MUST pre-mint every registered anonymous-CSRF binding that a deferred framework form or live-target descriptor can use, deliver its cookie, and share that exact binding with the later region render; this preflight MUST NOT invoke app-authored session extractors for unrelated registered mutations. A token whose binding cookie was minted only after header commit is invalid output and MUST fail closed. The same pre-header conservative floor applies to every raw endpoint backed by the framework's private browser-credential delegation and to every route outcome carrying a live `ReadableStream`: raw `Response` hides whether its source executes lazily, and a route stream's `pull()` can first consume request authority or mint anonymous-CSRF bytes after finalization. Credential-neutral raw endpoints and eager route bodies do not select this floor merely because their transport uses a Web `Response`. This floor is unconditional across typed mutation output, raw `endpoint()` responses, redirects, errors, direct Node adapter calls, emitted Node/Vercel runtimes, and future response channels; endpoint `cache` posture remains useful audit metadata but cannot relax it. Static export rejects both `Set-Cookie` and `Clear-Site-Data` because a durable artifact has no response-specific browser-state channel.

During the framework-managed response lifecycle, CSRF token-generation calls made with cloned, reconstructed, or otherwise derived `Request` values use the canonical response request and share its anonymous-CSRF binding/posture state and header-commit boundary. An exact framework-retained request remains a lifecycle receipt when an external callback loses async context. An arbitrary detached derivative is not such a receipt and MUST NOT first mint anonymous authority; without proof that the binding cookie can still reach the browser, the helper fails closed. This response-only canonicalization MUST NOT apply to CSRF validation or replay resolution, which consume the exact ingress request.

**Standalone CSRF response capture (normative).** A first-anonymous response helper MUST record its
exact binding cookie in the private lifecycle before exposing the token. The final route/raw sink
synchronously seals that lifecycle, snapshots and injects every pending distinct-name cookie,
deduplicates an exact authored copy, and rejects any non-identical plain/`__Host-`/`__Secure-`
alias under the same logical name. A safe-method or CSRF-exempt raw endpoint may receive that
injection only after its exact browser-state auth proof executed; direct `runEndpoint()` and direct
internal `renderRoutePageResponse()` have no managed cookie sink and reject pending authority.
Sealing precedes the snapshot, so queued work
either completes before that single boundary and is included or observes committed headers and
fails closed. An exact retained request context takes precedence over an ambient outer lifecycle;
nested dispatches MUST NOT cross-bind canonical authority, personalization witnesses, pending
cookies, or seal state. Each `createRequestHandler()` call is a distinct response boundary and MUST
clear an ambient caller lifecycle before pre-dispatch callbacks run. If a nested handler receives
the caller's exact retained `Request`, it MUST first reconstruct a detached native ingress carrier;
the inner success path and every auth, CSRF, access, error, or method early return therefore cannot
seal or consume the outer response's cookie channel.

Endpoint dispatch MUST combine raw-response posture verification and an immediate fresh header
snapshot in one synchronous choke. App code may continue producing an authorized body stream, but
a later microtask cannot add `Set-Cookie`, `Clear-Site-Data`, or any other header to the already
classified carrier. A privately captured anonymous-CSRF cookie consumes the same executed/private
browser-state proof as an authored cookie on every safe-method or CSRF-exempt endpoint.

**Transport compression and BREACH posture (normative).** Kovo's Node adapter and every generated
production runtime MUST negotiate response compression (`br` preferred over `gzip`, honoring
`Accept-Encoding` q-values) for compressible content types, and MUST apply it uniformly to
cookie-bearing, `no-store`, and `private` responses — a realistic logged-in page is exactly the
response whose wire size matters most, and refusing to compress it is not a BREACH mitigation.
Kovo's compression-oracle (BREACH/CRIME-class) posture is instead: (1) every framework-owned body
secret that repeats across responses is masked with fresh per-mint randomness — CSRF tokens are
XOR-masked at every mint, so the wire form of the one framework-owned body secret never repeats;
and (2) every compressed response carries `Kovo-Pad`, a per-response uniformly random 1..64
character length-noise header riding the same encrypted stream as the compressed body, so a
ciphertext-length observer must average away uniform noise instead of reading a deterministic
length signal. The pad is attached unconditionally on compression so its presence never marks a
response as sensitive. `Cache-Control: no-transform` (RFC 9111) is the sole authored opt-out and
MUST be honored. App-rendered secrets co-resident with attacker-reflected input remain the app's
responsibility; the framework mitigates, and cannot eliminate, length side channels for authored
content. Compressed responses MUST add the `Accept-Encoding` `Vary` dimension and drop any stale
`Content-Length`. Dynamic render paths MUST use a bounded-latency quality (brotli quality 5 class,
not the quality-11 class, which measured ~115x slower per document); expensive qualities are
reserved for static bytes cached by content identity.

**Static validators and connection reuse (normative).** Every statically served client file
(`/c/*`, `/assets/*`, static route documents) MUST carry a strong content ETag derived from the
exact served bytes and MUST answer a matching `If-None-Match` with **304** so `must-revalidate`
assets revalidate instead of re-downloading. A bodyless GET/HEAD that passed the payload-free
ingress gate (§9.5) MUST NOT be marked non-persistent before dispatch: there is no unread request
body to guard, and tearing down every document GET exhausts client ephemeral ports under load.
A peer that disconnects mid-body is ordinary transport teardown and MUST NOT surface through the
adapter's unhandled-error path.

**Proved-document caching (normative).** A route document may become publicly cacheable only
through the compiler-emitted `document:` cache-influence entry (§9.4) — never through an authored
header, an annotation, or an observed execution. When the registered manifest proves a route
`public-proved` AND the current 200 buffered `text/html` or document-parts representation carries
no runtime credential signal — no request Cookie or Authorization, no enforced authorization
graph, no resolved or unresolved principal/session state, no CSRF or deferred personalization
witness, no forwarded `Set-Cookie`, no live or deferred body — the server MUST stamp the canonical
validator posture: `Cache-Control: public, max-age=0, must-revalidate`, a strong content ETag
derived from the exact body bytes, and `Last-Modified`; it MUST answer a matching `If-None-Match`
with a 0-byte **304** carrying the same validator metadata. The server MAY serve subsequent
equivalent requests from a per-build in-memory document cache keyed by the manifest's cache-key
axes (URL path, URL search), each manifest `Vary` header value, the negotiated representation
(§8), and validated against the app build token; admission additionally requires the exact
canonical Cache-Control, the stamping witness on the exact response object, and the absence of
`Set-Cookie`. The safety property is that **no proof gap can widen**: a missing or closed manifest
entry, any credential signal above, or any admission-floor failure keeps the credential floor
(`private, no-store` + `Vary: Cookie`) and bypasses the cache in both directions — a
credential-bearing request is never served from the cache and its response is never stored.
Authored `public` Cache-Control on a document remains demoted to the credential floor until an
authored document cache declaration exists; `max-age=0, must-revalidate` is deliberate — freshness
rides the strong validator (a 0-byte 304), not a TTL the compiler cannot prove, and the in-process
cache invalidates with the build. Time- or randomness-dependent pages are outside the proof by
construction (the finite document cache language closes on such calls), so a proved document is
byte-stable for its build by proof, not by convention.

**Multi-process deployment posture (normative).** Kovo's server runtime is single-threaded by
design; the supported horizontal model is **N identical processes behind one reverse proxy**, not
an in-process cluster. Rate budgets (`requestLimits`, framework defaults included) describe the
deployment aggregate: each process MUST enforce `ceil(max / N)` per rate window when
`KOVO_PROCESSES=N` is set, MUST keep `maxKeys` and `windowMs` per process, and MUST fail loudly on
an unparseable `KOVO_PROCESSES` value rather than silently multiplying the authored budget by N.
Unset means N=1. The division is conservative under IP-affinity load balancing (a pinned client
sees a smaller budget, never a larger one). Every store that must outlive one process or cover
every replica — the §14 retention window, replay stores, principal-epoch stores — already carries
its own replication contract; the in-memory proved-document cache above is per-process by design
and needs none. A built-in cluster/worker mode is explicitly out of scope until the
proved-document tier has landed and been re-measured (plans/good-perf.md D10).

**Buffered document assembly (normative note).** Route documents are assembled and sent
**buffered**: status and every header — cache posture, cookies, CSP hashes, redirects,
authorization outcomes — are decided after the complete render, and measured TTFB on the buffered
path is milliseconds (plans/good-perf.md D11). Streaming an early `<head>` flush freezes status
and headers before render decisions that Kovo's security model makes late (guard failures,
`notFound()`, per-response cookie floors, hash-locked CSP over the final markup), so the document
does not stream. Revisit only for routes the compiler proves make **no post-render header
decisions**; §8's framework-owned deferred regions remain the supported progressive path.

**Adapter-owned framing and hop-by-hop fields (normative).** Application response channels MUST NOT supply `Content-Length`, `Connection`, `Keep-Alive`, `Proxy-Connection`, `TE`, `Trailer`, `Transfer-Encoding`, `Upgrade`, `Proxy-Authenticate`, `Proxy-Authorization`, or `HTTP2-Settings`, under any casing. Reject them with **KV415** at the complete response-header boundary; never silently strip them. Rejecting `Connection` rejects the field and every header name it could nominate before any nominated field can acquire hop-by-hop meaning. This floor applies equally to structured framework responses, `respond.file()`/`respond.stream()`, raw endpoint `Response` values, static-export header metadata, direct Node adapter calls, and emitted Node/Vercel runtimes, for HTTP/1.0, HTTP/1.1, and HTTP/2 compatibility paths. Only after that validation may a framework adapter attach or replace its own framing/connection metadata (for example exact static-file `Content-Length`, compression-derived `Content-Encoding`, or `Connection: close`). Thus an app-controlled length or transfer field can never disagree with the bytes Kovo writes or turn a keep-alive response body into a queued-response prefix.

Raw HTTP integrations use declared `endpoint()` entries, not ad-hoc server escape hatches. An endpoint is registry-visible, receives `Request -> Response`, requires an explicit HTTP `method` (there is no implicit any-method endpoint), requires an endpoint-level `reason`/`purpose`, and is enrolled in the endpoint and unguarded audits with the same auth metadata as routes, queries, and mutations. Prefix mounts require a `mountJustification` because they enlarge the routed surface beyond one path. Endpoint declarations also carry raw response posture metadata for the audit row: body class (`html`, `json`, `text`, `bytes`, `stream`, or `redirect`), cache posture, and whether app code owns body encoding plus response-header safety. That app-owned posture never transfers message-framing or hop-by-hop authority: the framework still reconstructs raw response headers and applies the adapter-owned-field KV415 floor above. The closed safe-method set is `GET`, `HEAD`, and `OPTIONS`; every other method, including an extension method unknown to Kovo, is unsafe and receives the default synchronizer-token check unless the endpoint explicitly opts out of CSRF with a named justification. A safe-method endpoint receives only a managed DB Reader from `ctx.actAs()` and MUST NOT emit `Set-Cookie` or `Clear-Site-Data`; an executable non-ambient verifier that actually succeeds for the exact request (or an equivalent private framework-owned self-verifying receipt) may authorize those browser-state effects. The runtime enforces both known capability boundaries even if application types are bypassed. App-owned side effects outside Kovo's capability and response sinks remain the application's responsibility, so authors MUST use an unsafe method for a state-changing operation. Endpoint handlers receive the raw `Request` before body parsing so signature verification can use wire bytes; exact and prefix mounts are declared; cookies are not interpreted and no ambient `req.session` is passed. A CSRF exemption is sound only because endpoint/webhook auth does not ride ambient browser authority. OAuth/SAML callbacks and adapter-owned mounts belong here; browser credential forms should still prefer typed `mutation()` flows so they keep schema validation, no-JS behavior, and the normal response vocabulary.

Runtime response-posture verification compares the parsed media-type essence, not a substring or
top-level-type approximation: `text` admits only `text/plain`, `html` only `text/html`, and `json`
only `application/json` or a structured `+json` subtype. `bytes` and `stream` deliberately leave the
media type unconstrained because a raw `Response` does not retain their authored representation;
declaring either therefore makes that union's media-type branch opaque. A redirect posture is
selected by a 3xx status and the separately validated `Location` contract. These classes are not
aliases: in particular, active `text/html` bytes never satisfy a declaration that names only
`text`.

An endpoint that legitimately streams or long-polls beyond the app deadline MAY declare
`response.longLived: { deadlineMs, justification }`. This is the only request-deadline escape: it
selects one endpoint-scoped finite deadline from 1 through 300,000 ms, requires a non-empty audited
justification, and is printed as `deadline=long-lived:<milliseconds>:<justification>` by
`kovo explain endpoints`. It does not disable or enlarge the app's occupancy budget, does not
apply to another endpoint or route, and does not exempt the response from adapter write-out
cancellation. Omitting the declaration uses the app deadline.

An endpoint `auth` declaration MAY carry an executable verifier from the webhook verifier kit. When present, the dispatcher MUST verify cloned raw wire bytes `{ headers, payload }` before CSRF validation and before the handler runs; verifier `false`, malformed input, or thrown verifier errors fail closed with `401 Unauthorized`, and the original request body remains readable by the handler after a successful check. Name-only endpoint auth declarations remain audit metadata. `webhook()` continues to emit name-only endpoint auth because it self-enforces raw-byte verification in its own lifecycle before parsing.

`webhook()` is the shaped machine-endpoint primitive for third-party POSTs that write Kovo-owned data. Shape: `webhook('/provider/path', { verify, input, idempotency, handler })`, lowering to a registry-visible endpoint with a source-derived webhook identity (§4.1) and `auth=verifier:<resolved scheme>` unless an explicitly justified custom/none verifier is used. The first string is the public HTTP receiver path, not the webhook registry name. The lifecycle is fixed: capture one request clock and the raw bytes → verify → parse/coerce a loose input schema (unknown provider fields pass through) → construct and validate an authenticated provider-event replay identity → atomically reserve/replay under the source-derived webhook identity → optional framework transaction wrapper → handler receives a machine-ingress context with no ambient session and dispatches Kovo-owned writes through `context.runMutation(mutation, input)` → the called mutation owns the audited DB write, touch set, and static diagnostics → commit/store the response and emit the unified change record `{domain, keys, input}` derived from the called mutation's committed changes.

`idempotency(input)` MUST return either `undefined` or the exact opaque value created by `webhookReplayIdentity(key, occurredAtMs)`. `key` is the non-empty provider event id (1..1,024 visible ASCII characters), and `occurredAtMs` MUST be the event's own occurrence time from the authenticated provider payload. Local receipt time, `Date.now()` inside the callback, an HTTP delivery timestamp, and an HMAC freshness timestamp are not event occurrence and MUST NOT be substituted. The constructor derives an immutable `expiresAtMs = occurredAtMs + 30 days`; its private TypeScript brand is only an authoring guardrail, while module-private runtime provenance rejects casts, structural copies, and forged objects. After verification and parsing but before any replay-store call or handler execution, the runtime validates the canonical identity against the one captured request clock: `expiresAtMs <= now` is stale, and `occurredAtMs > now + 5 minutes` is future-dated. Either temporal failure is a sanitized 422. Because asynchronous verification or parsing can cross the horizon after that captured-clock check began, the replay store MUST also reject fresh reservation at its current clock when `expiresAtMs <= now`; that refusal is a retry/unavailable response and the handler does not run. Settlement that crosses the horizon MUST leave the already-held claim pending and fail closed rather than create immediately removable committed truth. An invalid key, timestamp, unproven return value, callback throw, or otherwise malformed result is an internal posture failure answered with a sanitized 500 at that same boundary. The callback is evaluated exactly once per delivery.

The replay store receives the canonical `{ key, occurredAtMs, expiresAtMs }` facts intact, never a raw string or store-local TTL. A redelivery of the exact live identity replays the stored response and must not re-execute the handler or dispatched mutation. Reuse of a live `(webhook scope, key)` with different occurrence or expiry facts is an integrity conflict answered with sanitized 422, never an alternate event admitted alongside the first. Committed truth retires only at the exact authenticated expiry under §10.3; pending truth never expires automatically. `recordChange()` remains a narrow compatibility/manual-change bridge and is checked against declared `writes`; it is not the primary audit source for arbitrary raw transaction writes. Direct DB writes from webhook handlers remain KV330/KV406. `fail()` rolls back and answers the declared 4xx/5xx response so provider retry semantics are explicit.

The verifier kit is part of the normative surface for `webhook()`: `hmacSignature({ header, payload, encoding, tolerance, multiSig })` is the generic form, and `standardWebhooks({ secret })` is the shared non-vendor preset that resolves to printed generic HMAC configuration. Provider-specific HMAC recipes live in app/example code on top of `hmacSignature`, not in framework package exports. Verification is over raw bytes, uses constant-time comparison, enforces timestamp tolerance, and supports rotated secrets/multiple signatures. Non-HMAC providers use a custom `verify(request)` escape that appears as custom auth in the audit; `verify: 'none'` requires a named justification and appears as unauthenticated machine ingress.

The public webhook task path exports the constructors plus verifier/request contracts. Constructor
input vocabulary is contextually typed and may be named by inference
(`Parameters<typeof hmacSignature>[0]`), not by parallel exported HMAC option, secret, payload,
tolerance, or resolved-inspection records. Provider signing material and inspection snapshots remain
framework-internal.

### 9.2 Errors

#### Rejection equivalence and observation policy (normative)

Every remotely reachable surface for which account existence, resource existence/ownership, a
secret, or a governed value can change the response MUST have an explicit
`kovo-response-observation/v1` policy. Schema `owner:`, `secret:`, and `governed` facts nominate
surfaces for this review; they do not choose a product policy or prove equivalence. A nominated
surface without a policy is a build refusal. A policy names the two worlds being compared and one
of the canonical classes below. The only canonical world pairs are `exists-not-owned` versus
`absent`, `account-present` versus `account-absent`, and `unexpected-cause-a` versus
`unexpected-cause-b`; a product-specific pair requires a separately reviewed class rather than an
alias to one of these names.

An attacker observation is the tuple
`(status, redirect, selected end-to-end headers, normalized cookies/tokens, body relation,
connection behavior, work-factor class, timing distribution)`. Header names are compared
case-insensitively and order-independently after adapter-owned framing fields are removed.
`Set-Cookie` is compared by cookie name, security attributes, expiry class, and token
presence/shape; fresh random token bytes are never required to be equal. The body relation names
media type, encoding, length relation, and content relation. Connection behavior distinguishes a
complete response, reset/abort, and timeout. Work factor names the finite operation class and count.
Timing is a distribution checked against a versioned statistical budget; this is not a claim of
constant-time execution. An oracle compares the declared tuple fields and relations, not raw
response bytes. Provider delivery and other effects outside the framework-controlled HTTP boundary
are excluded unless the policy explicitly includes and measures them.

| Canonical class         | Required worlds                                                          | Required attacker-visible relation                                                                                                                                                                                                                                                                        |
| ----------------------- | ------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `input-validation`      | validly parsed but rejected inputs selected by the declared error schema | HTTP 422; no redirect or credential token; declared typed body vocabulary. Error codes and field paths may differ, so this class does not conceal which submitted field failed.                                                                                                                           |
| `authentication-needed` | enhanced versus no-JS transport for the same unauthenticated request     | The transport difference is intentional: enhanced HTTP 401 plus `Kovo-Reauth`; no-JS HTTP 303 to the same-origin login route with the same canonical `next`. Neither response establishes a session.                                                                                                      |
| `authorization-denied`  | authenticated principals denied by the same guard                        | HTTP 403 and the declared `unauthorized` failure body. This class does not conceal resource existence; a surface that must do so selects `resource-concealment`.                                                                                                                                          |
| `resource-concealment`  | `exists-not-owned` versus `absent`                                       | Identical 404 status, no redirect, `Cache-Control: private, no-store`, credential-varying headers, no cookie/token mutation, the same fixed body media type/bytes/length, complete connection behavior, equivalent storage/verification work class, and a timing distribution within the declared budget. |
| `account-creation`      | `account-present` versus `account-absent`                                | The same generic accepted status/redirect/body relation; no account-dependent cookie or token. A surface claiming this class cannot auto-establish a session in only one world. Framework-controlled lookup/write/credential work is normalized and its timing stays within budget.                       |
| `account-recovery`      | `account-present` versus `account-absent`                                | The same generic accepted status/redirect/body relation and no account-dependent cookie/token. Framework-controlled token-generation, lookup, queueing/decoy work, and timing are normalized; delivery by an external mail provider is outside the claim unless separately measured.                      |
| `unexpected-failure`    | `unexpected-cause-a` versus `unexpected-cause-b`                         | The surface-specific stable sanitized HTTP 500 tuple defined below; no cause-derived header, cookie/token, body content/length, connection behavior, or work-class difference before the response is committed.                                                                                           |

The table is a minimum floor. A surface may declare a stricter relation, but MUST NOT claim a class
while omitting one of the tuple axes or silently treating a raw-byte mismatch as acceptable. The
versioned policy and its dual-world oracle are release evidence; a passing unit fixture is not a
timing guarantee for every deployment.

Validation failures (schema, with field paths) and declared error codes return HTTP 422. The enhanced
path infers the submitted form instance from the request's compiler-emitted form target and returns a
`<kovo-fragment>` for that form only; the no-JS path re-renders the full page. Both paths call the
same component render function with the same typed failure state in `forms.<mutation>.failure`, so
expected failure UI is normal TSX (`<FieldError>`, `<FormError>`, or direct `forms` reads) rather
than a separate response template. `ctx.submit`'s `onError` receives the same typed union. Expected
failure responses never use committed invalidation or `Kovo-Targets` success selection.

**KV430 request-body posture (normative).** After successful JSON decoding, Kovo MUST enforce the
iterative depth/breadth/node budget before provenance decoration, schema traversal, CSRF-token field
extraction, or handler dispatch. URL-encoded segment and multipart-part ceilings enter the same
posture. Provenance decoration MUST keep every app-visible scalar read non-coercible and untrusted,
including reads through own-property descriptors, `Reflect.get`, `Object.assign`, and serialization,
but MUST NOT eagerly allocate one persistent poison object per scalar leaf; the validation-only raw
container view is module-private and unforgeable. A CSRF-exempt mutation that exceeds one of these
ceilings answers **422** with `{"code":"VALIDATION","payload":{"reason":"shape-budget"}}`. A
CSRF-protected mutation or endpoint cannot safely recover its submitted token from an over-budget
carrier and therefore fails through the ordinary CSRF response without exposing the body verdict.
After webhook authentication, malformed JSON remains **400** `Invalid JSON webhook body`, while a
valid JSON body that exceeds KV430 answers **422** with
`{"error":{"code":"VALIDATION","payload":{"reason":"shape-budget"}},"ok":false}`. None of
these expected input refusals calls the app's unexpected-error hook or handler.

Declared `fail()` payloads are client-bound wire values and MUST satisfy the same `JsonValue`
vocabulary as query values and island state: JSON primitives, arrays, and plain objects only. An
error schema may parse richer server-side values for internal use, but `context.fail(code, payload)`
rejects `Date`, `Map`, functions, class instances, and other non-JSON payloads at the TypeScript
boundary before they can enter `forms.<mutation>.failure` or the enhanced/no-JS error wire.

An **unauthenticated** mutation guard failure is not part of this typed validation union (§6.5). It does not render a `forms.<mutation>.failure` fragment: the enhanced path answers **HTTP 401** with a `Kovo-Reauth` directive (login route + same-origin `next`) the loader follows to re-authenticate, and the no-JS path answers a **303** redirect to the login route with `next`. An **authenticated-but-unauthorized** mutation guard failure answers **HTTP 403** and carries an `unauthorized` code in `forms.<mutation>.failure` so authorization-denied UI is typed and distinguishable from schema/app validation failures.

Unexpected server failures are not part of the typed union and must not leak internals. The typed query endpoint (§9.4) returns HTTP 500 with JSON `{"code":"SERVER_ERROR","payload":{}}`. Full-page route rendering returns HTTP 500 with the app's stable error shell or the fallback body `Internal Server Error`. Enhanced mutation responses that fail while rendering post-commit queries/fragments return a render-error fragment with HTTP 500 and `data-error-code="RENDER_ERROR"`; any `Kovo-Changes` header on that response remains sanitized to `{domain, keys}` for writes that already committed.

### 9.3 Liveness and Live

Kovo separates low-cost liveness from explicit live subscriptions:

- **BroadcastChannel rebroadcast** — a mutation's `<kovo-query>` response is rebroadcast to the user's other tabs; same-user multi-tab sync at zero server cost. Because BroadcastChannel is **origin-scoped, not principal-scoped**, every rebroadcast envelope MUST carry a **session/principal fingerprint** derived from the sender's `req.session` identity. A receiving tab MUST discard any message whose fingerprint ≠ its own current `req.session` identity, and MUST drop the channel on session change — so one user's private query data can never be morphed into a different user's UI on a shared or fast-user-switched device. This receive-side principal check is normative to the same degree as the SSE per-push guard re-check below; rebroadcast must not become a cross-principal disclosure side channel.
- **Refetch on focus/visibility** — a loader behavior (per-query opt-out) that re-runs queries (over the typed read endpoint, §9.4) when a stale tab returns; it fakes an embarrassing share of "live" UX for one conditional in the loader.
- **Live queries (roadmap; not shipped in v1 technical preview)** — `<kovo-live query="cart">` will subscribe over SSE to the identical `<kovo-query>`/`<kovo-fragment>` chunks; guards must be re-checked at subscription **and** at each push (a guard that passed at render must pass at patch time — fragments must not become a privilege-escalation side channel); in-process emitter (single node) or Redis pub/sub (multi-node); instance-key routing; `live: true` opt-in per query. Until this transport ships, `live: true` is not a valid `query()` definition field and `<kovo-live>` is not an implemented authoring primitive; accepting either as a silent no-op would violate the no-op-field contract.

The vocabulary is transport-agnostic by construction, so SSE is an additive transport, not a rearchitecture.

### 9.4 Typed reads: the query endpoint

Every query is addressable over GET — one read surface serving refetch-on-focus (§9.3), GET-form fragment responses (§7), async option/search reads, and the future SSE subscription key:

```http
GET /_q/product?id=p1 HTTP/1.1
Kovo-Build: <app-build-token>
Kovo-Fragment: true
```

```http
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8

<kovo-query name="product" key="product:f10:k2:ids2:p1" href="/_q/product?id=p1">{ "name": "Mug", "stock": 4 }</kovo-query>
```

Every enhanced `/_q/` request MUST carry its immutable document app build token as `Kovo-Build`, and every `/_q/` response MUST carry the selected build's token (§5.2.1). The current app rejects a missing or unequal enhanced-request token before decoding the query key or entering the query registry. A client compares the response token before generic failure-status handling, so the stamped 409 skew response triggers §14 recovery rather than becoming a silently skipped background failure. An admitted enhanced read whose guard now denies the current principal MUST return an exact, non-redirecting HTTP 401 (unauthenticated) or 403 (forbidden), `text/html`, and the selected build token; native query navigation retains the ordinary 303 login redirect. The enhanced client uses Fetch `redirect: 'error'` and treats only that exact-URL, same-build, admitted 401/403 envelope as revocation: it applies none of the fetched batch and performs a full navigation recovery of the current route, whose native guard then redirects or renders forbidden. A Fetch rejection alone is only a transport failure and MUST NOT be inferred to prove revocation. Args arrive as search params through the query's `args` schema (§10.2) — the same `s.*` coercion machinery as forms. A query with no declared `args` schema MUST reject a non-empty search input with 422 before running lifecycle providers, guards, or its loader; an absent schema is not an unvalidated-input mode. The query's `guard` (§10.2) is checked on **every** admitted read, and reads are part of the unguarded audit. Query `name` and optional canonical full instance `key` are separate exact facts: a raw string containing `:` is an unkeyed name, never something to split. A present raw empty `key` is malformed, while `name:` is a valid canonical instance identity with an empty value. Because canonical instance keys are intentionally not reversible search encodings, every server-emitted query truth that may be refetched MUST also carry its exact canonical `href`. The browser snapshots that `(name, key, href)` authority when it accepts server truth and refetches only that same-origin exact URL; it never reconstructs args by splitting or decoding the instance key. The instance key in the response (`product:f10:k2:ids2:p1`) remains the §10.2 single currency shared across client store, wire, and optimism.

**Caching contract (normative).** `/_q/<key>` is a credentialed GET whose body may vary by identity, so a URL that differs only by args is a shared-cache collision waiting to disclose one principal's data to another. Every compiler-emitted app graph with query, raw-endpoint, **or route-page** roots therefore carries one versioned `kovo-cache-influence/v1` manifest. Each query or raw-endpoint root records URL path and search as cache-key axes, each statically named request-header read as a possible `Vary` axis, and cookies, Authorization, principal/session facts, secrets, framework state, declared external-data versions, and unclassified influence as distinct axes. A declared external-data version is cacheable only when its version has a manifest-visible URL or named-request-header key contribution. Framework state without complete keyed external versions, a dynamic header name, an opaque call, or any influence outside the finite reviewed language closes shared caching. One observed execution is never positive evidence. A named audited escape may retain an explicit operator obligation, but it remains distinguishable from `public-proved` compiler evidence.

**Document cache-influence surface (normative).** Every JSX-authored `route()` page contributes one `document:<route path>` entry with surface `document`. Its authored intent is the route's own mandatory access decision — `publicAccess(reason)` is the authored public posture; a guard chain or machine-auth decision is a principal/authorization influence; a missing decision is unclassified. Influence derivation is the compiler's **finite document cache language**, and it is fail-closed in both directions: URL path and search are always cache-key axes; queries reached by the page, its regions, or its layouts are framework state; `ctx.signUrl` and ambient environment bags (`process`/`Deno`/`Bun`) are secret influences; a handler that binds the request identity parameter, awaits or renders asynchronously, performs construction, calls outside the analyzed same-module closure and a small pure allowlist, references imported or mutable module values, or composes layouts is unclassified. A route page proves `public-proved` only when every per-request handler (page, regions, meta sources) stays inside that language, every reference resolves to same-module function literals, build-constant literal data, or a reviewed pure framework constructor admitted as a direct callee (`trustedUrl`/`trustedHtml`), **and** the route module's own `defineKovo(...)` declaration proves the app's per-request `renderRoute` hook is absent or a one-parameter function inside the same language — a route module with no visible `defineKovo` cannot prove the render hook of whatever app later assembles it, and a `renderRoute` that binds its context parameter (which carries the raw request) closes every entry in the module. The access decision must be authored public. Entries are emitted for every route — a closed verdict is an explicit fact, never a missing one — the manifest binds to the exact app build that emitted it (§5.2.1/§14), and hand-authoring a manifest is the same KV235 violation as hand-authoring lowered IR (§5.2). The render-hook clause is sound because the only route modules that can prove carry their own assembling `defineKovo`; the manifest is trusted (KV235) to attest that the `defineKovo` visible in a route module is the app that assembles that module's routes. This attestation is a compile-time property, not a runtime-observable one, so the §9.5 proved-document runtime floors below deliberately do NOT depend on it: every runtime-observable credential signal (Cookie, Authorization, resolved/unresolved principal or session, CSRF or deferred-personalization witness, forwarded `Set-Cookie`) is rejected independently of the manifest — a proof gap in any of those fails closed regardless of a `public-proved` verdict.

The build MUST compare the evaluated cache declaration with the exact compiler manifest and fail closed on a missing public root or any intent, external-version, axis, `Vary`, or verdict drift. Runtime observations are rejection-only: a missing or closed compiler verdict cannot be widened by an anonymous-looking request. A typed query may emit its declared public `Cache-Control` only when the registered manifest has the exact root, surface, declaration, and a `public-proved` or named `audited-escape` verdict, and the current request has no Cookie, Authorization, resolved principal/session, or opaque request carrier. Otherwise it MUST emit `Cache-Control: private, no-store` and `Vary: Cookie`. Document execution applies the same current-response rejection floor for Cookie, Authorization, unresolved/resolved principal state, late executable bodies, and other existing personalization witnesses. These floors hold for every transport that hits `/_q/` — loader fetch, refetch-on-focus (§9.3), GET-form fragment responses (§7), and async option/search reads — and for document responses through every adapter.

`Vary` MUST be derived only from normalized, statically named request-header axes in the manifest. URL path/search already participate in the cache key and MUST NOT be encoded as `Vary`; principal/session state, Cookie, Authorization, secrets, framework state, and unclassified influence close shared caching rather than becoming attacker-controlled `Vary` tokens. A guarded or otherwise principal-dependent query may never be served from a shared cache: the guard-at-every-read invariant must not be bypassable by an intermediary.

### 9.5 Request shell

The request shell is the server-owned composition point for routing, document assembly, dev serving,
and export. Apps call `defineKovo()` once for provider/config context and call that contract's
`assemble()` once with explicit dense arrays of routes, layouts, mutations, queries, endpoints, and
tasks. Assembly closes those declarations together with an optional client-module **store**,
document options, unexpected-error shells, CSRF config, the lazy `db` provider, the §6.5 session/auth
provider, the frozen declared environment projection from §6.6, and the replica-stable `appId`.
The result is an opaque `KovoApp`, not that structural aggregate; only framework-owned functions may
resolve its private state.

The injected `VersionedClientModuleStore` exposes only `retain`, `readActiveSnapshot`, atomic
`replaceActiveSnapshot`, and retained-history `resolve`; assembly closes those methods behind a
framework-owned registry facade that alone derives representation hrefs and the direct app build
token (§5.2.1). Generated route IR and live-target registry artifacts are wired by the
compiler/build integration, not by app-authored generated/refresh options. The loader MUST establish
an app-owned registry scope before evaluating generated modules; concurrent or top-level-await app
graphs may not share a process-global pending registry, unscoped late/HMR registration is not
runtime authority, and mutation/HMR sinks may not fall back from their closed app inventory to a
process registry. Vite/dev integration points at an authored app entry, for example
`kovo({ app: '/src/app.tsx' })` from `@kovojs/server/vite`; the entry must default-export the opaque
`KovoApp` and must not point into `src/generated/*`. Compiler-owned plugins resolve route IR,
live-target registries, and generated client modules internally. The public handler currency is
web-standard `Request -> Response`; adapters such as `node:http` convert at the edge and receive
only that token.

The same bootstrap-first generated-registry channel carries the §6.6 browser response posture.
Vite/dev and production build scan the project source snapshot, serialize the exact
`kovo-browser-posture/v1` manifest into a framework-owned virtual/generated module, and execute its
registration before the app entry. Document assembly re-witnesses that carrier and derives CSP,
Permissions Policy, and optional COOP/COEP/CORP from it. App code cannot register, replace, or
release this boot fact, and a second non-identical registration is a boot error. Direct library
tests that omit the generated runner retain only the conservative non-isolated response posture;
they cannot opt into cross-origin isolation without an explicit compiler manifest.

Dispatch order is normative and printable: `/_m/<mutation-key>` mutations, `/_q/<query-key>` typed reads, `/c/__v/<representation-digest>/<module>` immutable client modules, declared `endpoint()` exact/prefix mounts, route table, then the 404 shell. There is no user middleware chain in v1. Extension points that can affect control flow are declared surfaces — `sessionProvider`, guards, `endpoint()`, `webhook()` — so audits can print them and no request behavior is registered from a distance.

**Generated Node public authority (normative).** A generated standalone Node entry MUST ignore
forwarded scheme and host headers by default. Behind TLS termination, the operator supplies either
`KOVO_NODE_ORIGIN` as one canonical absolute HTTP(S) origin or the exact opt-in
`KOVO_NODE_TRUSTED_PROXY=1`, never both. The fixed-origin posture reconstructs every Web `Request`
with the pinned scheme, hostname, and effective port and ignores forwarded authority. The
trusted-proxy posture accepts only the proxy-provided `X-Forwarded-Proto` scheme while retaining the
validated inbound `Host` authority; `X-Forwarded-Host` remains untrusted. These variables MUST be
snapshotted and validated before the authored handler graph is imported. Invalid, non-canonical,
ambiguous, or combined posture fails process boot. Authentication deployments MUST additionally
satisfy §6.6's exact configured-origin binding, so a trusted forwarded scheme paired with the wrong
host or port is rejected before auth state is read or changed.
The public Node adapter accepts only an absent origin or one fixed origin string. A per-request
origin callback is not a reviewed authority door: it could reinterpret `Origin`, forwarded, or other
hostile request fields as the trusted Web origin and is therefore rejected before it can run.
The deployment's proxy, TLS-edge, cache, cookie-domain, schema-writer, and bootstrap assumptions are
reported through the door-derived `kovo check env` contract in §11.4; configuring this adapter does
not by itself discharge facts the command cannot observe.

**Shared request-ingress decision (normative).** Transport-source selection and hostile-value
grammar are separate steps, and the supported source set is finite. An HTTP/1 Node source MUST
snapshot the exact method, authenticated transport encryption bit, normalized `Host`, and exact raw
`Host` count/value; it admits exactly one raw occurrence whose value is byte-identical to the
normalized field and rejects pseudo-headers. An HTTP/2 Node source MUST snapshot the exact method,
transport bit, `:authority`, and `:scheme`; it rejects ordinary `Host`, `X-Forwarded-Proto`, raw
HTTP/1 `Host` evidence, or a non-HTTP/2 version. `:scheme` is exact lowercase `http` or `https` and,
outside an explicit trusted-proxy posture, MUST match the authenticated transport bit. A generated
Vercel Node source is a distinct HTTP/1 posture: one exact raw/normalized `Host` plus mandatory,
canonical edge-overwritten `X-Forwarded-Proto` and `X-Vercel-Forwarded-For`; missing, ambiguous, or
non-canonical platform provenance has no fallback identity. Synthetic/custom carriers MUST declare
which enrolled source they emulate; a coincidental bag of fields is not source provenance.

A Fetch-native platform source instead consumes the exact method and canonical URL
scheme/authority selected by the named platform-owned HTTP-to-Fetch bridge; it cannot recover or
make claims about raw bytes that the platform normalized or discarded. Cloudflare's public edge
bridge is the supported Worker source; unauthenticated HTTP Service Binding ingress remains
unsupported. Unknown and mixed source postures fail closed before static serving or app import.

After source selection, live and emitted adapters MUST invoke the same finite classifier for method
token, authority, and scheme grammar. An absent verdict, unknown source posture, ambiguous value, or
lossy spelling is closed. The accepted verdict is immutable reconstruction input: one final target
object supplies the Web URL authority and app-visible `Host`, including when an operator-pinned
origin replaces the validated remote authority. No later sink may reread raw `Host`, `:authority`,
or forwarding fields to make a second decision, and a future adapter MUST enroll its source posture
and generated/source parity in the request-ingress C13 corpus before dispatching static or app code.

**Adapter request-target identity (normative).** The same classifier admits only (1) canonical
origin-form `path[?query]` beginning with exactly one `/`, or (2) a canonical absolute-form HTTP(S)
URL whose scheme and authority exactly match the selected ingress verdict. The accepted value is
reconstructed as canonical origin form before routing. Scheme-relative, authority-form,
non-HTTP(S) scheme-like (`javascript:`, `mailto:`), backslash, fragment, encoded dot/separator,
WHATWG-normalizing, mismatched-origin, and otherwise lossy targets MUST fail closed. Kovo assigns no
server-wide semantics to asterisk-form: `OPTIONS *` is explicitly unsupported and returns 400
before static or app dispatch. The 65,536-character and 10,000-query-entry ceilings apply before
target parsing on raw Node sources and on the platform-preserved absolute URL for Fetch-native
sources.

**Vercel pre-filesystem ingress (normative).** Every generated Vercel Build Output API v3 artifact,
including a static-only build, MUST route all paths through Kovo's generated Edge Routing
Middleware before `handle: filesystem`. That middleware applies the shared platform-Fetch
method/URL/target classifier and target ceiling, returns a closed 400/414 response on failure, and
uses `x-middleware-next: 1` only after acceptance. A mixed build then enters the Vercel Node
function's distinct platform-provenance posture. The function MUST prepare one immutable request
snapshot and accepted verdict before static/app dispatch, and both its pre-static metadata and final
Web `Request` MUST consume that same prepared value; carrier mutation after preparation cannot
trigger a second source, scheme, authority, method, or target decision.

**Adapter method identity (normative).** HTTP method tokens are case-sensitive (RFC 9110 §9.1),
while the Fetch `Request` constructor canonicalizes its standard methods and rejects several
others. A raw-capable adapter MUST reject before static serving or app dispatch whenever its raw
method token is invalid or cannot cross the Web `Request` boundary byte-for-byte unchanged. A
Fetch-native adapter applies the same verdict to the platform-preserved method and relies on the
named platform bridge—not Kovo—to preserve or reject the pre-Fetch raw token.
Thus raw `post` and `PoSt` are distinct unsupported methods and MUST NOT become `POST`; exact
`POST` remains valid, and a syntactically valid extension method is admitted only when Fetch
preserves that exact case-sensitive identity. Live and generated adapters MUST share one classifier.

**Adapter authority identity (normative).** A Node or platform adapter MUST accept an inbound
`Host` or HTTP/2 `:authority` only when it is one canonical serialized `host[:port]` identity that
crosses URL parsing and the Web `Request` boundary byte-for-byte unchanged under either supported
HTTP scheme. Percent-encoded, Unicode-to-IDNA, case-folded DNS, non-canonical IP, non-canonical
bracketed IPv6, explicit default-port (`:80`/`:443`), user-info, path/query/fragment, duplicate, and
otherwise ambiguous spellings MUST be rejected before static serving or app dispatch. Canonical
lower-case DNS names, non-default decimal ports, and canonical bracketed IPv6 remain valid. The Web
URL authority and app-visible `Host` MUST therefore expose the same one remote identity. Live and
generated Node/Vercel adapters MUST share this rule. A Fetch-native Worker validates the canonical
serialized URL authority delivered by its named platform bridge and reconstructs app-visible
`Host` from that verdict; this is not evidence about a raw authority the platform already erased.

**Pre-dispatch load shed (normative).** Because there is no user middleware chain, the request shell/adapter itself owns a coarse limiter that runs **ahead of** replay lookup, schema parse/coercion, and the guard chain (§10.3) — guard combinators such as `rateLimit({ per: 'session' })` shed load only after CSRF, replay, and parse have already paid out, and `per: 'session'` cannot distinguish a flood of null-session attackers, so they are insufficient as the only chokepoint. Before any `/_m/`, `/_q/`, `endpoint()`, or route dispatch the shell MUST enforce: (1) a maximum request/body size — a request exceeding it is rejected with **413** before the body is parsed; streamed bodies additionally have a hard 4,096-chunk budget and exceeding it is the same 413-class body-limit failure even when the byte count remains below the configured maximum, so adversarial transfer fragmentation cannot turn the byte limit into unbounded per-chunk work; (2) a serialized request-target ceiling of 65,536 JavaScript string code units and a 10,000-entry URL-query ceiling — Node/Vite/generated adapters MUST scan the raw target before constructing a Web `Request`, `URL`, or `URLSearchParams`, and a direct Web handler MUST scan `Request.url` before constructing `URL`/`URLSearchParams`; either target violation is rejected with **414**, including for static and not-found paths; (3) URL-encoded body segments and multipart parts share the same default KV430 breadth ceiling of 10,000 entries, counted before record reconstruction, split, or part adoption, so a compact separator-heavy carrier cannot amplify into an unbounded parser graph; and (4) a coarse per-IP and global request-rate budget — a request over budget is rejected with **429** carrying `Retry-After`, before replay+parse. The framework-default per-IP budget exempts ordinary document/endpoint `GET`/`HEAD` dispatch: behind a CDN, NAT, or load balancer every visitor shares one source IP, so a default per-IP page budget rejects legitimate traffic wholesale; the mandatory global budget still applies to those requests, the `/_m/` and `/_q/` per-IP budgets are unchanged, and an app-authored `requestLimits.perIp` is enforced on every surface including document reads. `defineKovo({ requestLimits })`, its body-size gate, and every base or per-surface rate budget are mandatory finite postures and MUST NOT accept `false`; author-supplied maxima are bounded to 67,108,864 body bytes, 100,000 query/list result items, 1,000,000 requests per rate window, 100,000 retained rate keys, and an 86,400,000 ms rate window. These limits are normative defaults closed by `assemble()` (per-IP and global `/_m/` and `/_q/` request rates, max body size, and a bound on fragment-targets reconstructed per response, §9.1); the coarse limiter is identity-blind on purpose so it survives the anonymous flood the session-scoped limiter cannot. This pre-dispatch posture is enrolled in and printed by the `endpoints` audit. The fine-grained `rateLimit` guard combinator still runs in the guard chain for per-principal policy. It admits a `per: 'ip'` (and global) dimension in addition to `per: 'session'`, so an anonymous or per-IP budget can also be expressed at the guard layer; the coarse shell limiter and the guard combinator compose rather than replace each other.

Node-family bridges MUST also close the Fetch GET/HEAD body-erasure gap before Web `Request`
construction, static routing, Vite SSR/app loading, DB admission, task startup, or authored code. A
GET or HEAD carrying a positive `Content-Length`, any `Transfer-Encoding`, or HTTP/2 HEADERS without
`END_STREAM` is rejected with **413**; `Content-Length: 0` remains payload-free. A custom HTTP/2
carrier MUST provide an exact, pinned `END_STREAM` witness or fail closed for GET/HEAD. This grammar
is deliberately finite and synchronous: the bridge does not wait for or indefinitely drain body
bytes. When an HTTP/1 request is incomplete, it flushes the 413 response and closes the connection.
Live Node/Vite and emitted Node/Vercel adapters MUST apply the same verdict.

The same pre-dispatch door MUST acquire one app-local occupancy slot and mint one framework-owned
request deadline before any DB provider, request-body read, guard, transaction, or handler work.
`requestLimits.deadlineMs` defaults to 30,000 ms and is a finite integer from 1 through 300,000;
`requestLimits.maxInFlight` defaults to 256 and is a finite integer from 1 through 10,000. Neither
posture accepts `false`. A request at the occupancy ceiling is rejected with **503 Service
Unavailable** and `Retry-After: 1` before that work starts.

The admitted request's framework-owned `AbortSignal` MUST be the signal visible to authored request
code and consumed by every Kovo-owned request effect door: outbound fetch (including bounded DNS
wait), DB admission/provider wait and transaction checkpoints, deferred-region selection, response
stream flush, and an adapter-owned final transport where Kovo controls one. The response-mint door
MUST discard a handler result that loses the deadline/disconnect race. An unfinished response body
MUST error and cancel its source at expiry. The Node adapter MUST additionally destroy an unfinished
response transport at expiry and retain the occupancy slot through actual `finish` or `close`; its
backpressure-aware pipeline therefore cannot turn a slow reader into an unbounded write. A
Fetch-native adapter can bound only the Web response stream Kovo owns; the platform's post-handoff
client transport is outside Kovo's proof.

Occupancy release is one-shot. It occurs immediately on deadline or ingress disconnect, on an
exception before response mint, on direct-Web response-body completion/cancel/error, and for a
bodyless direct-Web response at mint because no later transport receipt exists. When an adapter
claims the final transport, body completion alone does not release the slot: actual transport
`finish`/`close` does. Deadline/disconnect release does not assert that arbitrary authored work has
stopped; it prevents that abandoned work from retaining admission forever and revokes the
framework-owned capabilities it could otherwise continue to use.

This is cooperative cancellation, not JavaScript preemption. Kovo does not claim to terminate an
arbitrary Promise, a synchronous loop, native extension work, an uncooperative third-party API, SQL
already issued to a driver without cancellation support, or a transaction already committed. Such
work may continue after its result becomes ineligible for the response. A hard guarantee over those
cases would require app execution in a terminable worker or process.

**Trusted-proxy per-IP identity (normative).** When `trustedProxy` enables Kovo's built-in
`X-Forwarded-For`, `X-Real-IP`, or RFC 7239 `Forwarded` source, the selected proxy-nearest hop MUST
produce one canonical, address-only IPv4 or IPv6 key. A syntactically valid optional transport port
is stripped (an IPv6 port requires bracketed address syntax), equivalent IPv6 spellings are
canonicalized, and IPv4-mapped IPv6 is keyed as the corresponding IPv4 address. Unknown,
obfuscated, malformed, duplicate, or otherwise ambiguous nodes MUST NOT mint a per-IP key; the
mandatory global budget still applies when no trustworthy per-IP key exists. This classifier does
not reinterpret the app-owned opaque key returned by an explicit `requestLimits.clientIp` callback.
If more than one of the three built-in client-IP header families is present, the ingress is
ambiguous and none of them supplies a key; this prevents an unstripped client-authored family from
shadowing the family an operator-owned proxy appended.

Route matching is static-first at each path segment, and ambiguity is a compile error **KV228** rather than a runtime precedence footnote. Trailing slashes normalize to one canonical path with a 308 redirect before matching. Page routes answer GET and HEAD; other methods on a page path are 405 because mutations own POST via `/_m/`.

The shell owns document assembly. The default document contains the doctype, `<html lang>`, route/query meta, page hints (stylesheet links, modulepreloads, optional speculation rules), initial `<kovo-query>` scripts before consumers, the page body, and — for a document carrying client surface (§4.4) — the inline loader. Apps may provide `defineKovo({ document: { template } })`, but the template receives assembled parts rather than a blank canvas, so it cannot silently drop loader or hydration contracts. Deferred streams use the same assembled shell parts; partials must not drift from full documents.

Unexpected-error shells are app config with safe defaults: 404, 403, and 500 documents may be supplied by the app, while unexpected failures still use the stable no-internals bodies from §9.2 when no shell is provided. The shell resolves `db` and `sessionProvider` once before route, query, or mutation guards; route/query guard failures use the §6.5 unauthenticated redirect and 403 contract.

Static export replays synthetic GET `Request`s through the same handler. An exportable route writes `.html`, referenced immutable `/c/` modules, and static assets; there is no second render path. Export is L0/L1 only: a route with a guard, unproven session dependence, mutation-only interaction, or a param path without explicit static-path enumeration fails or skips loudly with **KV229** according to the configured export policy. Exported documents disable server refetch assumptions; the no-JS document is the artifact.

**Static subresource integrity (normative).** Once exact bytes are known, static export adds a
SHA-384 `integrity` value to first-party module-script, modulepreload, stylesheet, and style-preload
tags. An authored integrity assertion is accepted only when there is exactly one
ASCII-case-insensitive `integrity` attribute and its decoded value exactly equals the computed hash.
An empty placeholder, duplicate, malformed value, stale hash, or mismatch aborts export; the build
never hides a disagreement by deleting or replacing author text. A tag with no authored assertion
receives exactly one computed value before the artifact is published.

#### 9.5.1 Dev HMR

Hot module reloading is a dev-only request-shell enhancement over Vite transport. It is not a
client render graph, hydration mode, or router. Vite's websocket may carry Kovo `custom` events,
but every DOM-changing hot action still asks the app shell for server-owned route, query, or
fragment output before morphing. Unsupported or unproven edits delegate to Vite's full reload.

Kovo proves security posture per commit, not per keystroke. Whole-project analysis — the
data-plane analyzers of §10.2/§10.3/§11.4 and the project query/mutation fact census of §5.2
rule 10 — MUST stay off the dev hot-update blocking path: the edited module is staged and served
against the last-committed fact snapshot, the analysis re-runs asynchronously after edits settle,
its findings surface as teaching diagnostics when they land, and a changed fact snapshot
invalidates derived modules and publishes a full reload so the served output converges to the
proven snapshot. Because a dev-served page may therefore precede its analysis, every dev response
MUST carry the explicit `Kovo-Dev-Posture: dev-unproven` header — a dev page is never a posture
proof. `kovo check` and `kovo build` are unaffected: they derive every fact synchronously from the
current authored source and fail closed (§5.2 rule 9).

The app-facing dev API is a convenience wrapper around the compiler plugin and the app-shell dev
plugin. App authors should not hand-wire generated refresh registries, HMR endpoints, or client
module maps into `defineKovo()` or `assemble()`: the request shell remains the owner of dev serving, diagnostics,
and refresh dispatch. The wrapper wires compiler diagnostics into the same dev diagnostic ledger
used by page, fragment, and mutation requests, so a failed hot update and a failed direct request
render the same teaching document.

The supported `kovo dev` runner MUST bind the exact canonical HTTP origin of its owned loopback
listener after the socket is listening and before it loads the live app graph. A generated Better
Auth constructor with no explicit `BETTER_AUTH_URL` uses that one-shot framework fact, so an
ephemeral or conflict-shifted port remains identical to the Local URL printed by the runner. The
fact accepts only `localhost`, IPv4 `127/8`, or `[::1]` with the listener's actual effective port.
It MUST NOT be derived from `Host`, `Origin`, `Forwarded`, or `X-Forwarded-*` request fields, and app
code cannot replace it after binding. Outside the supported runner, a Better Auth app MUST
configure `BETTER_AUTH_URL` explicitly. An explicit development value remains fixed and validated;
every non-loopback value and every production value remains an explicit canonical HTTPS origin.

HMR impact classification is compiler-owned and fact-based. After parsing, impact decisions must use
typed lowering facts (§5.2 rule 9), not source-string heuristics. The impact ladder is:
server fragment/query refresh for a proven compatible live target; current-route document refresh
when the route shell is still compatible; `kovo:diagnostics` for compiler errors; and
`kovo:full-reload` for route table, app shell, query-plan, app build token, generated-registry,
bootstrap, stylesheet topology, pending optimistic work, missing fact, or any other unsafe change.

The stable dev event vocabulary is:
`kovo:component-render`, `kovo:route-shell`, `kovo:diagnostics`, and `kovo:full-reload`. Events carry
the source file, old/new client module hrefs when known, the impacted component/live-target ids when
proven, diagnostics summary when present, and old/new app build tokens and render-plan fingerprints when available. Stale
events whose token does not match the current document are rejected and escalate to full reload.

The dev-only browser entry is served or injected only by the Vite dev stack. It must be absent from
production builds and static export artifacts. Dev refresh endpoints are likewise Vite-dev-only and
must reuse existing app-shell render, query, live-target renderer, and fragment-wire code; production
`createRequestHandler()` never exposes HMR endpoints. Live-target refresh accepts POST only. Every
route or live-target refresh response, including method and authorization failures, MUST carry
`Cache-Control: private, no-store` and `Vary: Cookie`, because the bytes can depend on the resolved
session, route guards, queries, and component render context even in development.

An HMR live-target refresh carries the current document token in `Kovo-Build` like every other
target-bearing request. The Vite-dev-only endpoint may additionally accept the explicit `oldBuild`
URL parameter as the prior-render selector needed while an update is crossing builds. That exception
does not exist in production request dispatch. A malformed HMR snapshot, response envelope, missing
build token, or response-token mismatch is never partially applied; it escalates to full reload.

### 9.6 Durable tasks and scheduling

`task()` is the durable background-function primitive for non-transactional side effects. A task is a
typed registry entry with an `input` schema and a `run(args, ctx)` body; no opaque closures cross the
boundary. Task code may perform external I/O, but task DB writes must go through `ctx.runMutation(...)`
and reads through `ctx.runQuery(...)`, so every data change still reuses the audited mutation/query
surfaces (§10.2, §10.3). A task context may schedule more tasks, use external `fetch`/storage/secrets
capabilities, and receive a stable job id for external idempotency keys; it does not receive the
caller mutation's transactional `db`. The framework also exposes the current one-based claimed
attempt as immutable `ctx.attempt`. It is runner-owned retry metadata, not app authority: authored
code may pass the direct scalar through the compiler's finite plain-input grammar to an exact local
task/query/mutation declaration, while aliases, writes, computed access, proxies, and retention fail
closed.

`request.schedule(task, args, opts?)` is the only built-in way for a mutation handler to arrange
post-commit work. Scheduling writes a durable job row in the same transaction as the mutation's data:
commit means the job is ready to run, rollback means the job was never enqueued. The scheduled args
are validated by the task's `input` schema and serialized data, not captured process state.
`opts.afterMs` / `opts.at` set `run_at`; `opts.key` gives a witnessed `ScopedKey` pending-job
identity (§6.6). Principal work derives it from `ctx.actAs(id).stateKey(appKey)` (or the equivalent
framework request authority); deliberately shared work uses the named public posture, and framework
recurrence/system work uses only a finite registered posture. Strings, casts, proxies, forged
structures, malformed persisted frames, and reason-string system namespaces fail KV450 before the
queue is consulted. The queue persists the complete canonical scope frame in `logical_key`, and the
unique ready-job identity is `(task_key, scoped-key-frame)`. The default keyed behavior is debounce:
a ready job with the same complete frame has its `run_at` and args replaced by the latest schedule.
`coalesce: 'throttle'` keeps the earliest ready job and its first args. Equal app keys under different
principal/public/system authority never coalesce. A running or
already-finished job is never mutated; re-scheduling creates a new ready job. `request.schedule`
returns a typed handle, and `request.cancel(handle)` transactionally cancels a still-ready job and
returns whether cancellation happened.

The default node `JobRunner` drains the queue from Postgres with `FOR UPDATE SKIP LOCKED`, leases,
retry/backoff, and dead-letter rows. Multiple nodes may run the same drainer; row locks make claims
disjoint. Each job persists the declaration's positive `maxAttempts` ceiling. A lease reaper returns
an expired `running` job to `ready` only while its claimed-attempt count remains below that ceiling;
at the ceiling it moves the job to `dead`, so a task that repeatedly kills its worker cannot redeliver
forever. A false or failed heartbeat means the worker lost its lease: the runner aborts the
`AbortSignal` delivered as `ctx.signal`, propagates that signal through framework query/mutation
ingress, and refuses later framework state operations from that context. Task-authored external I/O
must likewise carry `ctx.signal`; arbitrary JavaScript cannot be forcibly preempted. Completion and
failure writes remain fenced by the claim's owner/token, and a rejected `markSucceeded` or
`markFailed` settlement is reported through task-runner diagnostics rather than silently discarded.
Delivery is therefore at-least-once. Exactly-once effects are obtained by idempotency: Kovo derives a
stable idempotency key per scheduled job and exposes the job id as the key a task passes to
non-idempotent external APIs. A retry must not double-charge, double-send, or otherwise commit an
effect without an idempotency key.

The in-process runner's lazy startup is itself pre-dispatch work. It MUST begin only after the
triggering request passes the coarse rate, target, and complete streamed-body admission gates from
§9.5; a rejected request MUST NOT resolve or provision the task database. Startup receives only a
request-free admission signal: the root queue database resolves through the app-root provider with
no request carrier. Every background `runQuery` / `runMutation` lifecycle and runner diagnostic uses
a newly constructed framework-owned, bodyless, credential-neutral `Request` at the exact non-public
URL `https://kovo.invalid/_kovo/task`; a remote request's scheme, authority, path, headers, body,
session, or other ambient browser/machine authority MUST NOT seed it. Task lifecycle MUST NOT call
the app's `sessionProvider`. Its only principal authority is the framework-minted explicit
`actAs(id)` or declared-system posture, attached before the per-operation `app.db` provider resolves;
the app-root queue handle is not substituted for that scoped provider resolution. A transient
startup failure may be retried by a later admitted request, but rejected traffic cannot drive that
retry loop.

Every preset that supports `task()` MUST declare a `JobRunner` capability. The node preset's
in-process runner is on by default; a runner-only mode may drain jobs without serving HTTP. A preset
with no runner capability MUST fail closed at build time when `task()`/`schedule()` is used, with an
actionable diagnostic; it must never silently enqueue work that no deployed artifact can run. Runner
capacity is bounded by the DB pool, per-task concurrency, priority lanes, and task timeouts/leases.
Delayed self-reschedule carries a lineage generation counter with a conservative default ceiling and
a delay floor, so polling/saga loops are explicit and runaway loops dead-letter instead of hammering
the database.

The capability declaration above is framework-internal build authority. The public value returned by
a built-in preset factory is only the opaque selection token defined by §6.6: it exposes neither the
`JobRunner` record nor any inspection/emission callback. Build preflight resolves the exact token and
checks the internal capability; app-authored structural or copied preset objects cannot declare a
runner and cannot bypass the missing-runner diagnostic.

---

---

<!-- Source: spec/10-data-plane.md -->

# Data Plane (SPEC §10)

This file is incorporated by reference from [../SPEC.md](../SPEC.md) and is normative for Kovo framework behavior.
The root spec remains the entry point and cross-reference index; this module owns the detailed contract below.

## 10. Data Plane

### 10.1 Schema as domain registry (Drizzle-blessed path)

```ts
// schema.ts
export const carts = pgTable('carts', { id: text('id').primaryKey() /*…*/ });
export const cartItems = pgTable(
  'cart_items',
  {
    /*…*/
  },
  kovo(() => ({ domain: 'cart' })),
);
export const products = pgTable(
  'products',
  {
    id: text('id').primaryKey(),
    stock: integer('stock').notNull(),
  },
  kovo((columns) => ({ domain: 'product', key: columns.id })),
); // row-level invalidation key
```

Tables default to a same-named domain; annotations group tables into logical domains and declare key granularity. App-authored `domain()`/`tag()` declarations derive their default stable names from their exported binding plus module path (§4.1); explicit domain annotation strings remain for shared schema vocabulary where several declarations intentionally speak the same invalidation currency. The reverse index (table → domain), the `DomainKey` type, and key extractors are all generated from this single file. An optional `owner:` annotation (`kovo((columns) => ({ domain: 'cart', owner: columns.userId }))`) names the column tying a table's rows to a principal — it powers the `unscoped` audit (§10.3). An optional `governed:` annotation (`kovo((columns) => ({ domain: 'account', governed: [columns.role, columns.balance] }))`) names columns that may only be written from a server-derived value — the declare-once mass-assignment fact (the primary `key:` and `owner:` columns are governed automatically); it powers the KV438 write-provenance gate (§10.3). Optional `atomic:`/`version:` annotations (`kovo((columns) => ({ domain: 'product', atomic: columns.stock, version: columns.lockVersion }))`) name a contended value column and an optimistic-concurrency counter — the declare-once lost-update facts that power the KV429 TOCTOU gate (§10.3).

`kovo` accepts exactly one inline annotation callback. Its parameter is the concrete Drizzle
column record for the table being declared; every column-bearing field uses a direct identity from
that record, never a string, an `unknown` selector, or a structurally forged column:

```ts
export const orderItems = pgTable(
  'order_items',
  {
    id: text('id').notNull(),
    orderId: text('order_id').notNull(),
    revision: integer('revision').notNull(),
  },
  kovo((columns) => ({
    atomic: columns.revision,
    domain: 'order-item',
    key: [columns.orderId, columns.id], // ordered composite row key
    ownerVia: {
      fk: columns.orderId,
      parent: orders,
      parentKey: orders.id,
    },
    version: columns.revision,
  })),
);
```

`key` is one concrete identity or a non-empty ordered list for a composite row key. `owner`,
`governed`, `secret`, `confidentialAtRest`, `atomic`, `version`, and each fan-out `via` use the
annotated table's callback identities. `ownerVia.fk` uses that same child identity,
`ownerVia.parent` is one concrete Drizzle table, and `ownerVia.parentKey` must be a concrete column
of that exact parent. The public types carry module-private witnesses so typo and wrong-table
references fail during ordinary TypeScript authoring. Kovo SQL handles likewise use private
witnesses over Drizzle's typed `SQL<T>` bridge; app-authored structural lookalikes are not valid
handles.

Those witnesses are ergonomics and defense-in-depth, not the security proof (§6.6). The compiler
independently resolves the callback parameter, table binding, column member, and SQL construction
from authored source. Runtime metadata extraction independently compares each concrete Drizzle
column's owning-table identity and physical name against the exact table being registered, then
binds the result to compiler-emitted security facts. Either layer fails closed on unresolved,
cross-table, or structurally forged values.

A table may opt out of domain mapping with `kovo(() => ({ exempt: true }))` (silencing KV404 for writes — append-only logs, outbox tables), but **exemption is write-side only**. An exempt table has no domain, so no write can ever invalidate a query reading it; a query whose read set includes an exempt table is therefore error **KV411** — the silent-staleness bug §10.6 exists to kill, reintroduced through the exemption. The teaching error's fix is to map the table after all: for an append-only log this costs nothing — inserts then invalidate exactly the timelines reading it. `exempt` is reserved for tables nothing queries.

### 10.2 Queries

```ts
// cart.queries.ts — `app` owns request/session/DB/env inference
export const cartQuery = app.query({
  access: [app.authenticated],
  load: (_input, { db, request }) =>
    db
      .select({
        count: count(cartItems.id),
        items: jsonAgg(cartItems),
      })
      .from(carts)
      .leftJoin(cartItems, eq(cartItems.cartId, carts.id))
      .leftJoin(products, eq(products.id, cartItems.productId))
      .where(eq(carts.id, request.session.cartId)),
});

// product.queries.ts — parameterized: args declared once, schema-style
export const productQuery = app.query({
  args: s.object({ id: s.string() }), // coerced wherever args arrive: props, route params, /_q/ search params (§9.4)
  access: [app.authenticated], // checked at page render AND at every typed read / live push
  // guards receive the validated instance key (§10.3): app.owns((a) => a.id, products.id)
  load: (args, { db }) =>
    db
      .select({ name: products.name, stock: products.stock })
      .from(products)
      .where(eq(products.id, args.id)),
});
```

The app-scoped query signature is `load(input, context)`. `input` is inferred from `args` and is
`undefined` for an unparameterized query. `context` exposes the contract's precisely inferred,
validated `request`, `session`, read-only managed `db`, read-only `env`, and request `signal`.
There is no alternate positional `(db, args, request)` app signature. The named
`QueryHandle<Input, Result>` carries the inferred JSON result without publishing loader callbacks
or private registry metadata; binding it in a component supplies that result under the declared
query property name.

Derived from this one expression, statically:

- **Registry key** from the exported binding plus module path (§4.1) — this key is the generated
  `QueryRegistry` identity and the base name for `/_q/<key>`, `<script kovo-query>`,
  `<kovo-query name>`, `kovo-deps`, and explain output.
- **Read set** `{cart, product}` — the JOIN _is_ the declaration (forgetting a joined entity's dependency is unrepresentable).
- **Result type** from the select shape — drives the client JSON, `data-bind` paths, derive inputs, and optimistic transform parameters. Query results are `JsonValue`-bounded client wire payloads; app-authored `query().load` results may use named interfaces and readonly JSON arrays/objects, but they cannot carry `Date`, `Map`, functions, class instances, or other non-JSON values. A column rename in `schema.ts` propagates through TypeScript static checking to every template. **Opaque projections are the read-side raw-SQL seam:** Drizzle's `sql<T>` generic is an unchecked assertion, so any `sql`/raw projection requires a declared `s.*` output schema (**KV410**), and the observed result shape is runtime-verified (§11.2). An opaque projection also hides which tables it reads, so its output schema says nothing about source tables; a KV410 site MUST therefore additionally declare a `reads:` table set — the exhaustive set of tables/relations the raw read touches. The `reads:` set is statically checked against exemption (§10.1): a `reads:` entry naming an `exempt` table is **KV411**, exactly as a statically-visible join would be, so an opaque projection cannot smuggle an exempt/outbox read past the static pass. The declared `reads:` set is folded into the query's read set (§11.1) and drives invalidation; a KV410 projection with no `reads:` declaration is itself a KV410 error. A query whose opaque projection reads a table absent from `reads:` is a CI failure under runtime verification (§11.2). The inferred-type chain stays sound or the seam is visible; never both unsound and silent.
- **Nullable result contracts.** A query whose successful result may be `null` declares
  `output: s.nullable(inner)`. The wrapper admits exactly `null` or a value accepted by `inner`,
  preserves `T | null` in the query handle, and contributes nullable query-shape metadata so the
  compiler continues to require optional traversal or explicit null handling (KV227). `undefined`
  is not admitted by this wrapper, and a local lookalike cannot mint framework query-shape facts.
- **Instance key** from client-visible `args.*`. The full identity is `name:keyValue`. For a
  genuine `s.object` args schema, Kovo derives `keyValue` from the parsed args in schema-declared
  field order with this typed, collision-free grammar (lengths are JavaScript UTF-16 code units):
  each present field is `f<length>:<k<length>:<field><value>>`; string, finite-number, and boolean
  values are `s<length>:<text>`, `n<length>:<text>`, and `b1:0|b1:1`; a dense scalar array is
  `a<length>:<concatenated scalar frames>`. Missing and `undefined` optional fields are omitted,
  while a declared empty args object produces an empty `keyValue`, so `name:` is valid. Thus
  `{ id: 'p1' }` for query `product` is `product:f10:k2:ids2:p1`, not a delimiter join that can
  alias strings, scalar types, optional positions, or array boundaries. A custom structural schema
  or unsupported value shape MUST declare `instanceKey`; compiler-generated keyed optimism accepts
  only compiler-resolved genuine `s.object` args using the same core-owned codec and rejects an
  authored `instanceKey` it cannot prove equivalent. This full key remains the single currency for
  the client store (`<script kovo-query="product:f10:k2:ids2:p1">`), optimistic transform keys
  (§10.4), and live-push routing. On `kovo-deps` and `Kovo-Targets`, it is carried as the key field
  of the exact `{query name, full instance key}` dependency encoding from §9.1; it is never
  recovered by splitting a colon-shaped string. Two instances of one query coexist on a page;
  `data-bind` inside an island resolves against that island's instance.

**Args bind locally (Constitution #2).** A component declares how its args derive from its own props — `queries: { product: productQuery.args((p) => ({ id: p.productId })) }` — so any page rendering the component satisfies the dependency without call-site knowledge. Route params reach queries as ordinary props through `route().page`; no call site enumerates query dependencies.

**Queries are the UI data contract.** A query-backed component's declared queries must contain the
data needed to render that component. "Skinny" queries maintained only for optimistic derivation
plus separate page/region loaders for presentation are rejected for ordinary app code: they split the
server-truth render path from the statically declared dependency graph and force app authors back
into manual fragment routing. The compiler may derive optimistic transforms, deltas, or §4.8 update
plans for only the fields and query shapes it can prove; unproved presentation fields still travel
through the same declared query and refresh via full server fragments.

#### Default-deny access decisions (normative)

Authorization is **default-deny, by construction**. Every request-reachable surface — a `query`, a
`mutation`, a route `page`, an `endpoint`, or a `webhook` — MUST carry an **explicit access
decision**. A surface's decision is **satisfied** by any one of:

- an **access guard chain** — one or more self-naming executable guards declared in `access:
[guard("name", fn), …]` on the surface (or inherited from a parent `layout`);
- a **public** decision — `access: publicAccess("reason")`, declaring the surface intentionally
  reachable without authentication, with a human-readable justification recorded in the ledger;
- a **verified machine-auth** decision — `access: verifiedAccess`, or (for an `endpoint`/`webhook`)
  an `auth:`/`verify:` scheme that authenticates a machine caller.

These alternatives are mutually exclusive on one declaration. A query, mutation, route, or layout
MUST NOT author both canonical `access` and the legacy top-level `guard`; doing so is **KV436** at
the static gate and MUST also fail closed in the public constructor and app snapshot. In particular,
`publicAccess(...)` or `verifiedAccess` MUST NOT suppress an authored guard by runtime precedence.
Authors compose executable guards in one `access: [guard(...), ...]` chain (or, while using the
legacy field alone, one `guards.all(...)` guard). Endpoints and webhooks do not accept the legacy
`guard` field at all; their executable guard chain belongs in `access`.

App-scoped factories accept only the canonical `access` shape. Their executable guard values come
from the contract-bound algebra (`app.authenticated`, `app.role`, `app.rateLimit`, `app.owns`, and
`app.all`) or another documented exact guard value. The legacy `guard` spelling exists only while
reading pre-contract primitive source during migration and is not a second app-contract behavior.

A surface with **none** of these is **undecided**: the static app graph classifies it
`decision: 'missing'` and the build fails with **KV436** (§11.3). An existing guard already _counts_
as a decision — guarded surfaces are not forced to re-declare `access`. The decision is recorded as a
static graph fact (`graph.access`) that the build derives from each surface's source-captured
`access` or legacy-`guard` posture; `kovo explain access` renders the full ledger (every surface, its decision, the
names of the guards that actually execute, and any public justification), and a reviewer audits the
`public` set before ship.

This is **by-construction**: the unsafe state (a request-reachable surface with no access decision)
is unrepresentable in a passing build, proven by the static graph fact rather than a TypeScript brand
(the compiler runs no type checker, §6.6). The audited decision MUST be the enforced decision:
`access` guard names are attached to the executable guard values, runtime dispatch runs those same
guards, and audit-only guard labels are not an accepted access decision. The proof is
**completeness, not correctness**: KV436 proves a decision _exists_, never that it is _right_ — a
no-op `return true` guard satisfies it. Row ownership / IDOR correctness remains KV414's obligation
(§10.3), and `publicAccess` reason strings are greppable, so they MUST NOT carry sensitive
operational detail.

#### SQL statement safety on managed DB handles

Framework-managed DB handles — `req.db`, query loaders, mutation domains, endpoint/webhook request
handles, and blessed-adapter wrappers — treat executable SQL text as a typed surface, not an
arbitrary string channel. The ordinary accepted forms are: Drizzle query builders and native SQL
objects that keep text separate from bound parameters, Kovo tagged-template SQL values (`sql` and
`staticSql`), and the single audited `trustedSql(...)` escape hatch. KV406/KV410 remain the
freshness/read-write proof
diagnostics; **KV422** is distinct and answers how executable SQL text was constructed before it
reached a managed handle.

App-provided driver streaming/cursor values such as node-postgres `Submittable` objects are not an
accepted v1 SQL carrier. Large owner-scoped reads, when needed after v1, must use a
framework-owned SQL-level cursor that reconstructs `DECLARE`/`FETCH` statements through the same
managed carrier boundary rather than blessing a driver-polymorphic app object.

Scalar/runtime values MUST bind as parameters, never by interpolating bytes into SQL text.
Identifiers, operators, sort directions, and clause fragments are not scalar values; they MUST come
from static schema facts or typed allowlists such as `sql.identifier(..., { allow })` /
`sql.allow(...)`, never directly from request strings.

For the static analyzer and explain/audit surfaces, the source set is the request-derived boundary:
`input`, `req.search`, `req.params`, form bodies, headers, and cookies. The sink set is every
framework-managed SQL construction/execution boundary: `db.execute(...)`, `db.query(...)`,
`db.exec(...)`, `db.prepare(...)`, `sql.raw(x)`, `sql.identifier(x)`, and untagged template/string
assembly routed into SQL execution. A request-derived or otherwise unproven value that can become
executable SQL text at one of those sinks is **KV422**.

Non-goals are explicit. Kovo does not sanitize arbitrary SQL strings into safety; it requires
parameterization, a typed allowlist, or an explicit `trustedSql(...)` brand. It does not prove
safety for driver handles captured before the framework wraps them. **Second-order injection is out
of scope**: a value read back from the database and later re-used in another query is governed by
the same `sql\`\``/`trustedSql(...)` discipline at the second query site, not by request-taint
tracking across storage.

### 10.3 Mutations & writes

> **Open design decision:** The domain-write declaration shape in this section is the target
> contract, not the current app-facing root API. `@kovojs/server` does not ship `write()` or `tag()`
> from its root public surface until the same change also ships the static enforcement and generated
> routing that make those declarations authoritative. Until then, authored apps use mutation
> handlers with analyzed Drizzle writes plus explicit `registry.tables`/`registry.touches` on opaque
> write sites.

**Declared raw secret reads have one finite app-authored grammar (normative).** The only authored
spelling that may attach audited secret-read authority to raw SQL is this exact same-block sequence:

```ts
import { sql, trustedSql } from '@kovojs/drizzle';
import { declareSecretReadCapability } from '@kovojs/server/secret-reading';

const statement = trustedSql(sql.raw('select id, classified from accounts'), {
  justification: 'reviewed raw secret read',
});
declareSecretReadCapability(statement, {
  columns: ['classified'],
  justification: 'review classified values on the server',
  source: 'accounts.classified',
  table: 'accounts',
});
const rows = await context.db.rawRead(statement, { reads: ['accounts'] });
```

`declareSecretReadCapability` MUST be an exact, unaliased named import directly from the semantic
`@kovojs/server/secret-reading` subpath. `statement` MUST be one immutable `const` initialized by
the exact `trustedSql(sql.raw(<string literal>), { justification: <string literal> })` composition.
That declaration, exactly one `declareSecretReadCapability` call, and exactly one directly awaited
`rawRead` call on the handler's compiler-recognized request-scoped DB (`context.db` or `request.db`,
as supplied by that callback signature) MUST occur in that lexical block and in that order. The
`rawRead` options MUST be an exact inline `{ reads: [...] }` object whose entries are non-empty,
duplicate-free string literals; the set MUST include the declared `table` and exhaust every relation
the statement actually reads. The literal SQL MUST reference the declared table. At runtime, the
same declaration is rejected if the executed SQL omits that table or references any other
secret-bearing table; one declaration cannot widen privileged read authority across secret tables.
Aliases, namespace imports, re-exports, lookalikes, statement escapes, computed/optional/extracted
members, spreads, dynamic metadata, duplicate uses, and missing or extra arguments do not establish
this authority. The legacy app-authored `.all(statement)` and `.execute(statement)` spellings are
closed; managed `rawRead` is the sole execution door. This declaration is audit-grade authority,
not a proof or declassification (§6.6): runtime enforcement still boxes secret result fields
(§11.2), and no result may cross the public wire without an independently named reveal or an owned
sink that enforces its own policy.

```ts
// cart.domain.ts — ALL writes flow through here (error KV330 bans db access in handlers)
export const cart = domain({
  addItem: write(async (db, cartId: string, productId: string, qty: number) => {
    await db.insert(cartItems).values({ cartId, productId, qty })
      .onConflictDoUpdate({ target: [cartItems.cartId, cartItems.productId],
                            set: { qty: sql`${cartItems.qty} + ${qty}` } });
    await db.update(products)
      .set({ stock: sql`${products.stock} - ${qty}` })
      .where(eq(products.id, productId));
  }),
  // Statically un-analyzable writes REQUIRE declaration, runtime-verified.
  // Raw-SQL writes MUST enumerate every table they touch via `tables:` — a
  // structurally-parsed allowlist the executor enforces (§11.2); `touches:`
  // names the resulting domains. The executor parses each emitted statement
  // and FAILS CLOSED (conservative whole-domain invalidation of `touches`,
  // plus a CI failure) on any production write to a table outside `tables:`.
  merge: write({ tables: ['cart_items', 'carts'], touches: ['cart'] }, async (db, …) => {
    await db.execute(sql`/* gnarly CTE */`);
  }),
});
```

**No `touches` on `addItem`, no `invalidate()` in handlers.** The static pass (§11) extracts `{cart_items→cart, products→product}` from the AST; calling `cart.addItem` _is_ the invalidation declaration. `invalidate()` survives only as a linted escape hatch for external-system effects (e.g., a Stripe webhook changing data Kovo should refresh).

**`touches`/`tables` declarations on opaque writes are statically required, not best-effort.** A write site whose touch set is not fully statically resolved — an `'unresolved'` runtime-flowing table value (§11.1 step 2.E) or a call into `node_modules` carrying a `db` arg (§11.1 step 3) — is **error KV406** when it lacks a manual `touches`; the dev/build/export gate blocks until one is supplied. A raw-SQL write (`db.execute(sql`…`)` / opaque projection write) MUST additionally declare `tables:` — the exhaustive set of tables the statement mutates — which the runtime executor parses and enforces (§11.2). On a production write to a table outside the declared `tables:`, the executor MUST fail closed: invalidate every domain in `touches` conservatively (whole-domain, ignoring key granularity) so no reader is left silently stale, and record a CI-failing violation; it MUST NOT skip invalidation on the unexpected table. Dev/test instrumentation under-approximates (executed branches only, §11.2), so passing dev/test coverage **does not prove KV406 completeness** — an unexercised conditional raw-SQL arm that writes an undeclared table is exactly the case the `tables:` allowlist and the production fail-closed rule exist to catch, since the dev cross-check never observes it.

For managed Postgres/PGlite, declared write scope has an engine backstop for the dangerous cases. The writer role is not blanket-granted over the schema: owner/owner-via tables receive writer privileges only with RLS `USING`/`WITH CHECK` policies that bind the row to the current principal, and unclassified/reference tables receive no writer grant. `reference: true` is reserved for immutable global lookup rows that contain no tenant membership or owner graph data. Team/org membership is tenant data and MUST be modeled as an owner/ownerVia/authzPolicy table so reads are scoped and request-time create/revoke flows stay behind an explicit policy. Therefore an out-of-declaration write to another principal's row, an ownership reassignment, or an unclassified table such as `verification` is denied by the database. The remaining declared-write wrapper obligation for benign over-declaration among writable owner/authzPolicy tables is coverage and invalidation honesty, not the primary confidentiality/integrity boundary.

**Compiler-bound custom policy authority (normative).** Generated dev/build/export paths MUST bind
each `authzPolicy` into the compiler-owned table-security manifest as an exact discriminated value.
A literal string is an exact guard assertion for SQLite only, where Kovo's bounded authorizer owns
the declared guard posture. A Postgres table MUST instead supply an engine predicate that static
analysis can canonicalize as zero-parameter literal SQL (currently a no-substitution SQL tagged
template or `sql.raw(<literal>)`). A Postgres string assertion cannot produce row-level security and
is **KV414** at compile time and `KV433_AUTHZ_POLICY_UNSUPPORTED` at runtime before any grant or
listener. Dynamic or interpolated policy authority also fails closed.
Runtime boot MUST compare the live Drizzle callback projection with that exact manifest value once,
then derive every RLS/grant/posture sink from the immutable compiler snapshot. It MUST NOT re-read
the callback slot after comparison; callback replacement between validation and asynchronous
policy installation cannot weaken the emitted engine policy (C9/C15, §6.6).

**Finite generated owner-policy correspondence (normative).** Framework-generated Postgres owner
policies are exactly a two-constructor algebra: `ownerColumn(table, column)` and
`ownerVia(table, fk, parentKey, parentTerm)`. A term contains exactly one owner/principal equality
and at most four `ownerVia` edges; a deeper chain is KV414 before policy installation. Kovo MUST
render the owner/owner-via RLS predicate and derive its framework-owned `ownsRow` evaluator from the
same immutable term. Its decision gate enumerates the complete three-valued domain —
`{true,false,null}` for the owner equality and `{present,absent,null}` for every relation edge — so
the exact model count is `3^1 · 3^e` (at most 243), not a sample or an SMT approximation. SQL
`EXISTS` admits only a TRUE inner predicate; missing/null edges and NULL/unset owner comparisons
therefore deny. Real PGlite evidence MUST materialize every enumerated model under the actually
generated policy on a `FORCE ROW LEVEL SECURITY` table and compare observed visibility with that
denotation.

This is deliberately not a broad guard≡RLS claim. The current public `guards.owns(keyOf, ownsRow)`
callback is app-authored and every explain record marks it `unproven` until a separately reviewed
migration binds the framework-derived evaluator. Hand-authored `authzPolicy`, arbitrary ownership
callbacks, the system/admin `USING (true)` policies, and `guards.role()` all lie outside the decided
fragment. Authorization explain records MUST place the generated RLS predicate and `explainGuard`
facts side by side and name that status. They MUST also warn that the managed transaction frame
writes `kovo.role` while no generated RLS predicate reads it; session-role guard success is not SQL
authorization. The engine's FORCE-RLS/effective-privilege closure remains the enforcement boundary.

**Finite grant-transition model (normative).** Build MUST derive one grant graph from the same
compiler-owned table-security manifest and mutation extraction that feed RLS and the touch graph; an
app-authored second grant registry is forbidden. The modeled principal kind is the current
request principal. Each `owner` table contributes an exact resource with
`{delegate, owner, read, write}` rights, each `ownerVia` table contributes
`{delegated-owner, read, write}` plus a `read`/`write` delegation edge from its parent resource, and
each `authzPolicy` table contributes `{policy, read, write}`. `public` and `reference` tables do not
become authorization resources. Every extracted mutation write, every exact `registry.tables`
declaration, and every declared touch whose domain contains one of those resources MUST contribute
a transition row; domain-level declarations conservatively select every authorization-bearing
table in that domain.

The decided fragment is deliberately small. An exact Drizzle `delete` from an `owner` or
`ownerVia` resource, with no unresolved receiver/helper path for that mutation, has the successor
right-set `∅`. The checker MUST enumerate the complete local powerset `P(R)` and verify
`successor ⊆ predecessor` for every state. The bound is 64 states (`2^|R|`, currently at most 16),
and exceeding it fails closed rather than sampling. Exact `insert` and `update` operations and an
explicitly table-declared raw-SQL transition lie outside that proof and MUST appear as stable named
escapes with `budget=1` and a retained review obligation. A custom `authzPolicy` does not establish
positive-grant semantics, so even its exact deletion is not in the decided fragment. Any opaque
helper/receiver flow, unresolved operation, authorization-bearing domain touch without an exact
operation witness, or other unclassified write is `⊤`; `kovo check` MUST fail it with KV414 before
production artifacts are emitted. `kovo explain grants` MUST print the derived principals,
resources, delegation edges, checked-state counts, `⊤` reasons, and named escape budget.

**Attenuating delegation (normative).** `createDelegationAuthority` is only a bridge from rights an
already-passed guard/RLS door established; it does not grant database authority and MUST NOT be
accepted as a substitute for that door. It snapshots an exact nonempty finite `kind:resource` set,
the acting identity, a revocation principal, and that principal's persistent epoch into a
module-private framework receipt. `onBehalfOf` accepts only such a receipt, rechecks the authoritative
epoch without a positive cache, and mints a child only when every requested right is a runtime member
of the parent set. Structural casts and widened sets fail. The TypeScript subset relation is an
authoring guardrail; the private receipt, runtime subset test, and epoch check own enforcement.

The honest claim is only that, in the exact positive `owner`/`ownerVia` deletion fragment, the
compiler-derived local right-set cannot grow, and that framework-receipted delegation cannot widen
its parent set before the bound epoch changes. Kovo does not claim arbitrary `authzPolicy` meaning,
audited escape safety, global HRU safety, or safety against an external schema writer. The live
FORCE-RLS/effective-privilege closure remains the authorization enforcement boundary.

**Engine-door completeness (normative).** Kovo may claim the storage engine is the sole authorization/confidentiality door only when the runtime itself holds no superuser/`BYPASSRLS` authority and cannot assume a privileged provision/admin role, and when a closure audit over the engine's actual grant graph proves that **every** object reachable by the app roles is one of: (i) a base table under `FORCE ROW LEVEL SECURITY` with a live `kovo` policy; (ii) a proven `security_invoker` view/function whose reachable base relations are themselves in that safe set; or (iii) a relation declared through the reviewed public escape, `declarePublicRelation(...)`, and surfaced as a `publicRelation` row in `kovo explain capabilities`. The audit MUST ask the engine's finest-granularity effective-privilege oracle instead of lossy grant views or direct-grant rows: table and column reachability both count for relations, `PUBLIC` and role membership count for every privilege decision, sequence reachability is audited separately from relation reachability, and `SECURITY DEFINER` routines are scanned across all non-system schemas. Reachable objects that cannot enforce RLS, including materialized views, foreign tables, unsupported relation kinds, non-allowlisted sequences, and reachable `SECURITY DEFINER` routines, MUST fail closed. App roles MUST also hold no unexpected privilege on other ACL-bearing catalog objects or default privileges that would create future reachable objects outside the audited relation/routine/sequence set; such grants are refused rather than ignored. Build-time lints and source enumerations remain defense-in-depth only — never the thing the authorization/confidentiality guarantee rests on.

**Production database driver floor (normative).** In-process PGlite is a dev/test-only,
single-tenant database. Its bootstrap identity is necessarily superuser because PGlite has no
connection-authentication boundary; an app-authored raw `new PGlite(dataDir)` handle therefore sits
outside Kovo's owner-scoping and confidentiality guarantee and is only warned by the
defense-in-depth raw-driver lint. A runtime whose bootstrap-pinned operator environment reports
`NODE_ENV=production` MUST refuse the PGlite driver before opening a listener or serving static or
dynamic traffic. Production requires `KOVO_DATABASE_URL` to select external Postgres, and the
external runtime login MUST pass the non-superuser/`NOBYPASSRLS`/non-admin-membership boot invariant
and the reachable-object closure audit above before the app serves. This relocation preserves
zero-dependency local development without claiming that an in-process superuser can be made into a
production least-privilege identity.

Every managed Postgres connection string — runtime, admin, system, provisioning, migration, and
posture-check alike — MUST explicitly name a nonempty authority login, a nonempty database path, and
a decimal port. Query-string login and database overrides are forbidden. Non-local URLs additionally
MUST keep host and port in the authority, use a DNS hostname rather than an IP literal, and
authenticate both the certificate chain and server hostname before credentials or queries cross the
network. Kovo accepts the exact `sslmode=verify-full` posture and fails closed before pool
construction on absent, malformed, duplicate-last-weaker, or weaker modes. The pinned node-postgres
transport does not check IP literals against certificate identity even in `verify-full` mode, and a
boot-pinned `NODE_TLS_REJECT_UNAUTHORIZED=0` disables Node certificate verification, so Kovo rejects
both for non-local transports. Only exact pg-effective `127.0.0.1`, exact query-host `::1`, and
validated Unix-socket carriers may omit TLS; a bracketed IPv6 authority remains `[::1]` in pinned pg
and is not the proven local carrier. Unix sockets use URL form with explicit login, database, and port;
the ambient-dependent historical `/socket/path database` shorthand is forbidden. Connection-string
URLs use an exact lowercase `postgres://` or `postgresql://` envelope with no raw whitespace or
control characters, and every raw percent sign MUST be followed by exactly two ASCII hexadecimal
digits. Malformed or truncated percent escapes are rejected globally before parsing so Kovo and the
pinned Postgres parser cannot disagree about security-relevant query keys; intentional credential
and query bytes must be canonically percent-encoded. The node-postgres driver requires an explicit
reviewed `databaseUrl`/`KOVO_DATABASE_URL`; it does not combine a pinned Kovo security decision with
pg's later live reads of ambient `PG*` destination or identity variables.

The real authorization boundary is split. **Privilege** belongs to unassumeable Postgres roles such
as `kovo_admin`/`kovo_system`, and those roles may be assumed only inside framework-owned scoped
clients for provisioning, migration, audit, or other system work. App/runtime roles cannot assume
them. **Principal** belongs to the request-scoped GUC that RLS reads, but only the confined app-SQL
statement surface may set it on a per-request scrubbed connection before executing app work. A
`set_config` revoke, routine inventory, or source-level wrapper audit is defense-in-depth: no
authorization code may rest on that revoke alone.

**Capability ownership is framework-owned, not comment-owned.** System/auth DB handles, privileged
role assumptions, raw driver clients, and secret-readable handles MUST have exactly one framework
mint site and MUST cross app-authored or public-package boundaries only as a narrowed, branded
facade with an audited consumer path. Generated app source MUST NOT export a raw system DB,
`AppDb`, provision/admin client, or equivalent ordinary value that app modules can import and route
around the managed read/write chokes. Better Auth and other framework integration code may consume a
module-private or opaque adapter capability, but request-authored code only receives request-scoped
read/write facades whose SQL methods are governed by the source/sink, statement-identity, guard, and
secret-read boundaries in this section and §11.2. Any public use of such a privileged facade MUST
appear in `kovo explain capabilities` with its reason and consumer surface.

**C9 — boundary-crossing doors are reconstructed, boxed, or framework-owned (normative).** Any
value crossing a security-relevant boundary in Kovo MUST do so through exactly one of three
mechanisms:

1. **Reconstructed carrier** — the framework snapshots or rebuilds the boundary value from
   normalized facts before the sink sees it, so caller-owned mutable carriers cannot change meaning
   between validation and execution.
2. **Boxed value** — the framework keeps the runtime value inside a non-coercible box until an
   explicit reveal/redaction path discharges the sink.
3. **Framework-owned door** — the only way across the boundary is a typed or branded framework
   channel whose implementation owns grammar, normalization, and fail-closed rejection.

Two corollaries are mandatory:

- **Complete engine-door enumeration.** Where Kovo claims the engine is the authorization or
  confidentiality door, the complete boundary-crossing set is the engine's effective privilege graph
  itself (§10.3 engine-door completeness), not a source scan, proxy wrapper, or comment inventory.
- **Secrets leave only boxed or owned sinks.** A secret, governed, or principal-bearing value MUST
  never rely on a read-only proxy or a best-effort caller convention at egress. It crosses only via
  a runtime box, a reconstructed carrier, or a framework-owned sink that is named in the proof
  inventory and hostile-value tests.

#### Principal-indexed label lattice and bounded non-interference (normative)

Kovo's data-plane obligations use one product label `L = Conf × Integ × Owner`. This is a statement
about the statically analyzable fragment and the named framework runtime doors in this specification,
not arbitrary JavaScript, termination, timing, resource use, or code behind an audited escape.

- `Conf = public | secret`, ordered `public ⊑ secret`; join selects `secret` when either input is
  secret.
- `Integ = literal | server | input | unknown`, ordered from least to greatest uncertainty in that
  sequence. Its join is the least upper bound. This is the normative kind-level meaning of
  `joinSymbolProvenance`: `unknown` dominates, then `input`, then `server`, then `literal`. Equal
  input/server paths remain precise; different paths join to the same kind with an unknown path.
- `Owner = public | principal(p) | framework`. `public` is bottom, `framework` is top, equal
  principal labels join to themselves, and labels for two different principals join to `framework`.
  The product join is componentwise. `framework` means that no ordinary principal inherits
  visibility or write authority merely because differently-owned values were combined.

The following clause IDs are stable machine-readable obligations consumed by
`check:label-clause-map`:

- **NI-C1 — Confidentiality confinement.** A `secret` value cannot influence a public/client/log
  observation except through an explicit framework-owned reveal or redaction door recorded as an
  audited declassification.
- **NI-I1 — Integrity confinement.** An `input` or `unknown` value cannot influence a governed,
  credential, authorization, or other security-sensitive sink that admits only `literal`/`server`
  provenance unless a framework-owned validator or guard reconstructs and re-witnesses a new fact.
- **NI-I2 — Semantic integrity and freshness.** Opaque reads must declare enough source and shape
  facts to preserve the query's freshness and wire contract; a source that cannot participate in
  invalidation cannot silently enter a live query read set.
- **NI-O1 — Principal isolation.** A value or effect labeled `principal(q)` is not observable by or
  writable for principal `p` when `p ≠ q`; row selection and engine policy must preserve that owner
  label at every supported authorization door.
- **NI-E1 — Audited exception visibility.** Any deliberate exception to NI-C1, NI-I1, NI-I2, or
  NI-O1 must pass a named escape with stable provenance, source span, and obligation text and remain
  visible to `kovo explain`; missing or unrecordable escape evidence fails closed.

**Termination-insensitive statement.** For any principal `p`, take two supported executions that
start from equal `p`-observable state and differ only in values whose confidentiality/owner labels
make them unobservable to `p`. If both executions terminate, their `p`-observable framework outputs
and governed/authorization effects are equal. Likewise, changing only `input`/`unknown` values does
not change a sink protected by NI-I1 unless a named validator, guard, or audited exception admits the
change. This statement excludes termination, timing, allocation, arbitrary app effects, unanalyzed
JavaScript, and the truth of author assertions; those are retained obligations, not implied claims.

The diagnostic clause denominator is exactly KV410, KV411, KV414, KV426, KV435, KV438, and KV439.
Their mapping is versioned in `security/label-clause-map.json`; a missing, duplicate, unknown, or
class-relabelled row is a root-check failure.

**C10 — security sets are closures or allowlists, never subsets or denylists (normative).** Any set
used for an authorization, confidentiality, or privileged-execution decision MUST be computed from
the boundary relation it represents: a reachability set is the complete closure over the relevant
engine edges, and a property set is an allowlist of the minimal safe members. The write-reachability
audit closes directly writable relations over structural write-propagation edges: FK referential
actions, partition/inheritance routing, and rewrite-rule redirects that can route app-role writes.
The runtime identity audit checks the runtime login plus the complete `pg_has_role(..., MEMBER)`
assumable-role closure against **two** allowlists that jointly range the complete escalation surface,
which is role ATTRIBUTES ∪ predefined-role MEMBERSHIP: (i) the classified role-attribute allowlist —
`rolsuper`, `rolbypassrls`, `rolreplication`, `rolcreaterole`, and `rolcreatedb` MUST be false, while
benign role metadata is classified explicitly and future unclassified `pg_roles` role-attribute
columns fail closed; and (ii) an allowlist over PostgreSQL predefined-role membership — the login and
every assumable role may be a member of only the framework's own roles plus an explicit benign
don't-care set, so membership in any `pg_*` predefined role outside that allowlist (e.g.
`pg_execute_server_program` ⇒ `COPY … FROM PROGRAM` OS command execution,
`pg_read_all_data`/`pg_write_all_data`, `pg_read_server_files`/`pg_write_server_files`, `pg_monitor`,
`pg_maintain`) is refused and named. The predefined-role allowlist is required because predefined
roles carry NONE of the five elevated role attributes and would otherwise pass the attribute
allowlist unflagged; predefined roles are detected by the reserved `pg_` name prefix and the
< `FirstNormalObjectId` (16384) system-OID range and surfaced through the same `MEMBER` closure. As
an allowlist (member-of-only-known-safe, not a denylist of known-bad roles), a new `pg_*` predefined
role in a future PostgreSQL release fails closed by default. The SECURITY DEFINER routine and
attached-code audits use that same login-plus-assumable identity set for execution reachability. The auth non-egress proof enumerates the request-reachable
secret-handling surface and boxes or confines each path; it MUST NOT use a named file as a proxy for
that surface, and its plaintext-API confinement enumerates every request-reachable `auth.api.*`
plaintext-reading endpoint so a new or unclassified endpoint used outside the trusted module fails
closed rather than sliding through a fixed subset regex. The Better Auth plugin secret classifier
follows the same rule in the confidentiality direction: a credential-shaped plugin column — one
whose final name segment is a credential noun (`key`, `token`, `secret`, `password`, `hash`, …) —
defaults to `secret:` unless the author explicitly annotates it non-secret, so the apiKey plugin's
`key` column and custom credential additional fields fail closed to secret rather than being emitted
as ordinary readable columns.

The implementation keeps this as one executable inventory in
`boundaryCrossingSinkInventory()`. `pnpm run check:c9-sink-inventory` MUST compare the union of its
`censusFamilies` with every family in `frameworkSourceSinkInventory()`, reject duplicate sink rows,
require a stable owner and an existing root proof command, and verify that every proof/hostile-value
citation names a live file. Adding a source/sink family without discharging it through this table
therefore fails `pnpm run check`.

Every C9 row MUST also classify `keyScoping` as exactly one of
`database-principal-policy`, `runtime-opaque-scoped-key`, or `not-stateful-keyed`. The database driver
row owns the first posture through its complete role/RLS privilege graph. Blob/file storage, derived
vector/RAG persistence, and durable-task coalescing own the second posture and MUST authenticate or
reconstruct from the §6.6 `ScopedKey` runtime witness before namespace use. All remaining current
rows are explicitly `not-stateful-keyed`.
Missing, unknown, or downgraded classifications fail `check:c9-sink-inventory`; adding a new
app-addressable stateful sink therefore cannot omit an owner-provenance decision silently.

The same executable inventory owns the finite compiler security-operation vocabulary from §4.3 and
§6.6. Every member of `securityOperationKinds` MUST occur in the `operationKinds` of exactly one C9
row. A missing operation, an operation unknown to the canonical union, or duplicate ownership is a
gate failure. This assignment ties terminal-effect evidence to the real sink owner and ties
`server.handler.root`/`server.helper.call` control records to capability closure. Those records keep
the root census and unresolved local-call summary edge visible; they do not assert a runtime effect.
The generated operation manifest is never itself the runtime door.

| Sink                                   | Owner                                  | Mechanism   | Key scoping                 | Sole door                                                                                                                                         | Root proof gate                             | Hostile-value evidence                                                                                                                                                     |
| -------------------------------------- | -------------------------------------- | ----------- | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| DB driver statement                    | `@kovojs/server/managed-db`            | reconstruct | `database-principal-policy` | Immutable managed-statement snapshot plus engine policy                                                                                           | `pnpm run check:single-choke`               | `packages/server/src/managed-db.test.ts`                                                                                                                                   |
| HTTP response body                     | `@kovojs/server/wire-output`           | reconstruct | `not-stateful-keyed`        | Typed wire/body envelope renderers                                                                                                                | `pnpm run check:wire-output-boundary`       | `packages/server/src/wire-html.test.ts`                                                                                                                                    |
| HTTP response headers                  | `@kovojs/server/response-finalization` | own         | `not-stateful-keyed`        | App-header classifiers plus final raw/structured adapter reconstruction                                                                           | `pnpm run check:wire-output-boundary`       | `packages/server/src/response-posture.test.ts`                                                                                                                             |
| Redirect URL                           | `@kovojs/server/response-posture`      | reconstruct | `not-stateful-keyed`        | Same-origin path normalization before `Location` finalization                                                                                     | `pnpm run check:wire-output-boundary`       | `packages/create-kovo/src/index.build.prod-artifact.redirect-capability.test.ts`                                                                                           |
| `Set-Cookie`                           | `@kovojs/server/cookies`               | own         | `not-stateful-keyed`        | Typed cookie builder and serializer                                                                                                               | `pnpm run check:wire-output-boundary`       | `packages/server/src/cookies.test.ts`                                                                                                                                      |
| Blob/file and derived-dataset write    | `@kovojs/core/storage`                 | own         | `runtime-opaque-scoped-key` | ScopedKey witness/frame namespace; exact `derived()` reconstructs and hashes the complete request-principal frame; static-export path containment | `pnpm run check:filesystem-boundary`        | `packages/core/src/scoped-key.test.ts`; `packages/compiler/src/derived-dataset-security.test.ts`; `packages/server/src/derived-dataset.test.ts`; static-export containment |
| Durable-task payload                   | `@kovojs/server/task-runner`           | own         | `runtime-opaque-scoped-key` | ScopedKey frame plus queue envelope and redaction-aware observability views                                                                       | `pnpm run check:security-test-builds`       | `packages/server/src/task-queue.test.ts`; `packages/server/src/task-observability.test.ts`                                                                                 |
| Request method/authority/scheme/target | `@kovojs/server/request-ingress`       | reconstruct | `not-stateful-keyed`        | Explicit transport-source snapshot plus one finite classifier, immutable prepared verdict, and pre-filesystem platform gate                       | `pnpm run check:security-classifier-corpus` | `packages/server/src/request-ingress-policy.test.ts`; HTTP/1/H2/Vercel/Fetch and generated middleware parity                                                               |
| Webhook payload                        | `@kovojs/server/webhook`               | own         | `not-stateful-keyed`        | Verifier-before-parse plus replay-scoped dispatch                                                                                                 | `pnpm run check:security-test-builds`       | `packages/server/src/webhook.test.ts`                                                                                                                                      |
| HTML/document/style render output      | `@kovojs/compiler/output-context`      | reconstruct | `not-stateful-keyed`        | Contextual render pipeline or explicit trusted-output escape                                                                                      | `pnpm run check:sink-policy`                | `packages/browser/src/security-output.test.ts`                                                                                                                             |
| Log/error output                       | `@kovojs/core/secret`                  | box         | `not-stateful-keyed`        | Non-coercible secret/redacted boxes plus normalized error emitters                                                                                | `pnpm run check:tcb-boundary`               | `packages/core/src/secret.test.ts`; `packages/server/src/task-observability.test.ts`                                                                                       |
| Outbound egress request                | `@kovojs/server/egress`                | own         | `not-stateful-keyed`        | Declared-origin, per-hop DNS/address classification and selected-address transport choke                                                          | `pnpm run check:egress-boundary`            | `packages/server/src/egress.test.ts`; `packages/server/src/egress-redirect.test.ts`                                                                                        |
| Authorization principal/data access    | `@kovojs/server/postgres-authz`        | own         | `not-stateful-keyed`        | Pinned principal plus least-privilege Postgres role/RLS/effective-privilege-graph closure                                                         | `pnpm run test:authz-paranoid`              | `packages/server/src/postgres-authz.test.ts`; served paranoid production-artifact matrix                                                                                   |
| Better Auth credential/non-egress      | `@kovojs/better-auth/credential-gate`  | own         | `not-stateful-keyed`        | Exact registered consumer, validated result, and same-consumer one-shot result opening                                                            | `pnpm run check:security-classifier-corpus` | `packages/better-auth/src/internal.trusted-plaintext.test.ts`                                                                                                              |
| Dynamic module/process execution       | `@kovojs/compiler/capability-closure`  | own         | `not-stateful-keyed`        | Compiler-owned immutable client-module registry plus reviewed build/runtime capability doors                                                      | `pnpm run check:sink-policy`                | `packages/browser/src/handlers.test.ts`; `packages/compiler/src/conformance-compat.test.ts`                                                                                |

The executable inventory retains the stable sink key `blob/file write` for this C9 row and maps
both `file.storage.static-export` and `data.derived.persistence` census families to that one
storage-operation owner. `server.storage.read` and `server.storage.write` therefore still have
exactly one finite-operation owner. The derived family adds the KV452 compiler proof plus runtime
request-principal namespace reconstruction described in §6.6; it does not create a duplicate
operation vocabulary or claim that an external vector service faithfully enforces its namespace.

**External Postgres role topology is a manifest, not environment inference.** The runtime config
MUST resolve reader, writer, admin, and system roles into one topology that records whether Kovo
creates or adopts each role, the runtime login, and required runtime-login membership edges. The
same topology facts drive provision, posture check, production boot, and `kovo db` output. Adopting
pre-created roles does not relax verification: provision/check/boot MUST verify required role
existence and runtime membership edges, fail before partial DDL when a required adopted role is
missing, and refuse configurations where the ordinary runtime login can assume privileged
admin/system roles outside framework-owned scoped clients. The runtime login and every role it can
assume through the `MEMBER` closure MUST have only the classified minimal-safe role attributes AND
must be a member of only framework-owned roles plus an explicit benign don't-care set; unknown
role-attribute columns and non-allowlisted `pg_*` predefined-role memberships (C10/C11) both fail
closed until classified.

**External Postgres posture lease (normative).** The least-privilege result above is not a
boot-only fact. Before an external-Postgres runtime becomes ready, Kovo MUST establish an immutable
boot baseline from a versioned, deterministic SHA-256 digest of a bounded authoritative witness.
The witness MUST cover the runtime identity, classified role attributes, role memberships and the
complete assumable-role closure, protected policy/grant facts, the migration-ledger head, and the
monotone posture epoch. Canonical facts are sorted by kind, key, and value. One witness accepts at
most 2,048 facts, 4 KiB per fact field, and 256 KiB of canonical evidence; a field or total exceeding
its bound fails closed. The digest MUST be stable across unchanged boots and MUST change when one covered grant,
membership, policy, identity, ledger head, or epoch changes. The random pooler probe and physical
backend PID prove the connection property below but are deliberately excluded from the stable
digest.

The lease TTL is 120 seconds with zero serve-degraded grace. Kovo renews from the authoritative
catalog on a 30-second base interval with one process-stable plus-or-minus 10% jitter and a
10-second witness timeout. PostgreSQL SQLSTATE `42501` requests the same renewal. Scheduled,
permission-triggered, and request-admission renewals MUST share one in-flight promise; a caller
cannot create one catalog scan per error or request. A failed witness, timeout, expired lease, or
digest different from the immutable boot baseline trips KV433 app load-shed immediately. Kovo MUST
reject new database-capability admissions, invoke the pool/session drain exactly once for that
outage transition, and await that drain before it may mark the lease fresh again. This drain retires
pooled sessions; it is not a claim that JavaScript can cancel SQL already issued to PostgreSQL.
Failed recovery attempts use exponential backoff starting at 1 second and capped at 30 seconds.
Only a successful authoritative witness whose digest exactly matches the boot baseline restores
service. An intentional posture change therefore requires the operator to finish migration or
provisioning and restart the process to authorize a new baseline; transient failures may recover in
place when the old baseline is restored.

Every witness MUST mechanize the pooler assumption inside one transaction. Statement one writes a
random transaction-local frame with `set_config(..., true)` and reads that frame plus
`pg_backend_pid()`, `current_database()`, `current_user`, and `session_user`; statement two reads the
same fields again. A changed backend PID, lost frame, database, current user, or session user fails
closed. This admits direct pools and transaction-preserving poolers, not statement-mode poolers.

Freshness binds both the checksum-validated migration-ledger head and a monotone posture epoch that
`kovo db migrate` reasserts with the expected ledger state. An epoch may advance but MUST NOT be
silently decreased or reused. This detects a restore that regresses relative to an already-running
lease or a deployment that re-runs migration; it does not let a brand-new process distinguish a
self-consistent stale backup without an external expected head/epoch. `kovo explain capabilities`
MUST print this static external-Postgres contract, including its bounds and recovery rules. Because
that command reads a build graph rather than a live process, it MUST label current status, digest,
and expiry `not-observed` and MUST NOT imply that external Postgres is the deployed driver.

**Split Postgres authority is bound to one live writable database (normative).** A runtime identity
witness and a privileged posture audit are one proof only when they address the same logical
database on the same current primary. Provisioning MUST mint one random framework-owned database
instance identity in `kovo_schema_state`. Before normalizing the session, the ordinary runtime
connection snapshots exactly one live-identity row containing the database name/OID, cluster system
identifier, timeline, recovery state, postmaster start time, and server address/port, plus exactly
one framework identity row. The selected system authority (preferred) or admin authority MUST
independently reproduce the same compound identity before its audit result is accepted. A mismatch,
recovery/standby endpoint, missing exact framework identity row, or provider that withholds the
read-only `pg_control_system()` /
`pg_control_checkpoint()` identity oracles fails closed. This supports one writable primary (or a
failover where both URLs reconnect to the same promoted primary). Independently writable physical
clones and split proxy routes are unsupported even if their logical schema bytes match; operators
must point every authority URL at the same live primary and rerun provision after a logical clone.

**Runtime Postgres session state is an allowlist (normative).** The runtime witness runs before Kovo
overwrites `search_path`. It MUST reject startup `SET ROLE`/session-authorization skew, require the
safe semantic baseline (`session_replication_role=origin`, `row_security=on`, UTF-8, standard
strings, and the other classified parser/transaction settings), and enumerate every setting whose
live source is client, database, role, or database-plus-role. Only the pinned UTF-8 driver
negotiation and semantics-neutral application naming are allowed. The privileged posture authority
also enumerates persisted settings for the runtime login and its complete assumable-role closure;
an unclassified setting fails closed rather than joining a denylist. Every live `pg_settings.source`
category MUST be explicitly classified; an unknown future source category fails closed. Every
framework-owned app SQL transaction then starts with the exact local `pg_catalog, public, pg_temp`
search path before it sets the principal, role selector, or app role.

**Postgres security metadata currently requires globally unique base table names (normative).**
Owner chains, secret-column grants, authorization classifications, and policy dependency sets share
the Drizzle base table name as their closed-world key. Until all of those keys are schema-qualified
end to end, a schema declaring two physical relations with the same base name MUST fail before any
database side effect, even when the relations live in different PostgreSQL schemas. This prevents a
public or partially secret classification from colliding with a whole-secret relation.

**C15 — classify-and-pin or reconstruct after runtime classification (normative).** When a
security-relevant sink accepts a caller-owned carrier whose bytes or object identity can still
change after runtime validation/classification, the framework MUST do one of the following before
the sink executes:

1. **Pin** the exact accepted value into an immutable framework-owned carrier that the sink then
   consumes without re-reading the caller-owned source; or
2. **Reconstruct/fix** the sink value from normalized facts, discarding the caller-owned carrier and
   failing closed to a fixed fallback when normalization does not bless the value.

Re-reading or re-stringifying the mutable caller-owned carrier at the sink after an earlier
classification decision is forbidden. A sink may be recorded as **N/A** only when the sink is
already framework-owned and structural from construction time, so no caller-owned carrier survives
to the decision point. The required audit inventory for this rule includes at least the egress
resolved-IP floor, redirect `Location`, managed SQL statement carriers, `sql.identifier(...)`, and
header/cookie serialization; each row must be classified as `pinned`, `fixed`, or `N-A` with
hostile-value evidence.

The closure audit is side-effect-inclusive. Attached code is reachable when the app role can reach
it by direct `EXECUTE`, DML trigger, rewrite rule, `CHECK`/domain constraint function,
default/generated expression function, or index/predicate expression function, including structural
write propagation through FK referential actions, partition/inheritance routing, and rewrite-rule
redirects. Each such attached code path MUST resolve to the same safe object set above or fail
closed.

**Principal-epoch revocation and mutation completeness (normative).** The app's
`principalEpochStore` is the §6.6 authoritative identity-lifecycle capability. Production apps that
combine a `sessionProvider` with mutations MUST use the module-private durable provenance exposed by
`createPostgresAppRuntimeDb().principalEpochStore`; a missing, memory, custom structural, or
global-symbol lookalike fails boot. Postgres persists one SHA-256 principal commitment rather than a
raw principal id, with strictly monotone epoch/change time, permanent tombstone status, a finite
last-reason constraint, and no ordinary reader/writer/admin/runtime-login table privileges. The
system role receives only `SELECT`, `INSERT`, and `UPDATE`. The SQLite/memory implementation is
development/test-only and preserves the same state semantics for one process lifetime.

An authenticated mutation captures an active authoritative epoch after parse and guards, then
rechecks that exact epoch inside the transaction callback after handler work and before the callback
may request commit. A concurrent password/role/tenant/admin/provider/deletion transition therefore
throws through the rollback path. A mutation that itself changes privilege MUST declare one exact
`principalEpoch` registry entry: `{ action: 'advance' | 'tombstone', principal(input, request),
reason }`, where `reason` belongs to the matching finite union. Kovo executes that declared
transition after handler success but before transaction-callback success. Only the exact current
request receives a module-private old-to-new receipt permitting its response/replay settlement
under the now-retired prior epoch; any different or later transition closes settlement. The
mutation registry is a completeness ledger, not semantic inference from table or mutation names.
An epoch transition may commit before a later app-transaction COMMIT failure; that conservative
case over-revokes, while an epoch failure prevents app transaction commit. Kovo does not claim
cross-database atomic commit.

Provider and out-of-band paths use `initializePrincipalEpoch`, `advancePrincipalEpoch`, and
`tombstonePrincipalEpoch`. Initialization atomically creates active epoch 1 or returns the existing
row unchanged and cannot revive a tombstone. Better Auth's first-party binding invokes it before an
authenticated provider identity reaches app code. Password changes, role/tenant changes,
administrative actions, external-provider revocation, and deletion MUST advance or tombstone the
same row even when no Kovo mutation initiated the event. Every current lookup is authoritative,
uncached, and limited to 1,000 ms. Missing/malformed rows, provider errors, timeout, stale epoch, and
tombstone all fail closed. This yields zero positive-cache staleness; only one bounded in-flight
authoritative read/action race remains.

**Principal erasure receipts (normative).** `erasePrincipal(principal, options)` is the public
framework door for erasing Kovo-owned residue. It MUST accept only an exact
`createPostgresAppRuntimeDb()` runtime, an exact framework `SigningKeyRing`, and a non-empty bounded
list containing every storage adapter wired by the app. It MUST permanently tombstone the
principal epoch before deletion; erase principal-indexed `_kovo_jobs` rows, mutation replay rows,
and objects from every supplied storage adapter; then independently re-enumerate all three sink
families. Any residue, malformed index carrier, incomplete pagination, unrecognized filesystem
generation, or non-enumerable structural storage lookalike MUST fail without a receipt. A receipt
is a signed point-in-time absence proof over precisely the supplied adapters and exact app runtime,
not a promise to recall external egress, browser cookies, omitted/derived third-party adapters, or
future writes by arbitrary app code. The receipt contains a one-way principal commitment, never the
raw principal, and is signed only through the fixed `principal-erasure-receipt` crypto purpose.

Every durable task scheduled from a proven request MUST persist that principal in a separately
indexed `_kovo_jobs.principal` column and propagate it to child jobs. Every durable mutation replay
row admitted from a proven request principal MUST persist a separately indexed, one-way
`principal_index`; mutation rows without a proven principal plus webhook and capability rows MUST
carry `NULL`. This replay column is non-authoritative and additive: it exists only for erasure and
reconstructive row validation, MUST NOT enter `mutationReplayScope()`, the canonical replay
`ScopedKey`, replay authorization, or uniqueness composition, and a row/index mismatch fails
closed. A pre-index task ledger containing rows or a pre-index replay ledger containing mutation
rows MUST require explicit operator reconciliation and fail provisioning rather than silently
claiming that legacy residue is enumerable. Memory, filesystem, and S3-compatible storage
constructors retain an internal enumerable authority without adding list methods to the app-facing
`StorageCapability`. Filesystem sidecars and S3 object metadata MUST reconstruct an exact
`ScopedKey` whose digest matches the physical object identity; S3 listing MUST be bounded, dense,
duplicate-free, and completely paginated.

The public S3-compatible client accepts only Kovo's stable positional operation vocabulary. The
operation object is contextually typed by `S3CompatibleObjectClient.create` (and may be named by
`Parameters<typeof S3CompatibleObjectClient.create>[0]`); provider request, response, metadata, and
parallel named operation records are framework-internal.

The credential-door census is exact: principal-scoped capability URL mint/verify plus mutation
replay-receipt reservation, response release, handler admission, in-transaction completion, and
settlement are applicable, while the exactly-once adapter continuation is inapplicable because it
cannot outlive its call frame. Capability v4 signs the epoch and checks it before replay burn/storage
read (§9.1). Mutation reservation appends the captured epoch to the already principal-bound
canonical scope. Every listed verifier rechecks authoritative freshness. A `Kovo-Idem` issued before the current
`changedAtMs` is a conflict before handler work; equality is accepted only for epoch-1 identity
initialization and is closed for every later revocation epoch because millisecond ordering is
unknowable. Expiry remains defense-in-depth, never a substitute for freshness.

**Request lifecycle (normative):**

```
(pre-dispatch shell: max-body-size → 413 · coarse per-IP/global rate → 429
 · app occupancy → 503 · finite deadline capability — §9.5)
CSRF validation → parse+coerce input (schema) → guard chain
→ resolve the protected-CSRF binding or post-guard machine replay principal
→ capture authoritative principal epoch where applicable and reserve by (binding, derived mutation identity, idem-token)
→ bind handler admission to that exact reservation epoch → BEGIN tx → handler (receives a transaction-scoped db whose public type hides raw transaction openers)
→ declared privilege transition, epoch freshness, and deadline checkpoints → COMMIT (recheck epoch, settle reservation, store response) → re-run invalidated queries (post-commit, same request context)
→ render <kovo-query>/<kovo-fragment> → respond
                    ⇘ on fail() or pre-commit deadline: ROLLBACK → abort replay reservation
                    ⇘ deadline after possible COMMIT: preserve replay truth → discard response only
```

The request deadline is a transaction-door capability, not a claim that JavaScript or a database
driver can undo committed work. The framework checks it before opening transaction work and again
inside the transaction callback after the mutation handler. Cooperative expiry at either checkpoint
throws before callback success, drives the transaction's rollback path, and invokes the replay
reservation `abort` hook because no successful callback requested commit. A timeout while an
adapter is committing is outcome-ambiguous: Kovo MUST preserve the pending replay claim. Once commit
is known to have succeeded, the durable settlement remains authoritative even if the request
deadline then expires; Kovo may discard the late wire response, but MUST NOT call that a rollback,
erase replay truth, or execute the mutation again on retry.

This ordering closes the read-your-writes hazard: responses can never render pre-commit data (which would visibly revert the user's optimistic update). A replay hit does not bypass authorization: the runtime MUST re-evaluate the guard chain against the **current** request before re-serving a stored response, so a replay never re-serves a private response after authorization changed. For CSRF-protected mutations, the replay store is keyed on (principal ∧ principal epoch ∧ CSRF session/rotation binding ∧ source-derived mutation identity ∧ idem-token), using canonical length framing for each identity component, so a replay can only ever return to the same principal and epoch that produced it even when an app supplies a shared rotation id. Session/rotation ids, independently resolved principals, anonymous-CSRF secrets, and mutation identities are each capped at 1,024 JavaScript code units before composition. A framework lifecycle binding embeds its pinned principal exactly once and replay rejects a later mismatch rather than appending duplicate identity. The bounded epoch suffix is appended only after the existing principal-bound scope is established; the complete raw scope MUST remain below the durable store's 4,096-code-unit ceiling. For `csrf: false` mutations, no session or mutation-wide fallback exists: replay storage requires `machineReplayPrincipal(request)` after the successful guard/access decision. Its exact primitive 1..1,024-code-unit result is length-framed under `kovo-machine-replay-principal/v1`, encoded as exact UTF-16LE code units (including lone surrogates), and SHA-256 committed before scope composition. The raw machine identity is absent from replay keys, metadata, diagnostics, and the non-authoritative Postgres `principal_index`; that erasure index remains derived only from an independently proven framework principal and MUST NOT participate in authorization or uniqueness.

Every mutation replay-store call MUST carry the runtime-witnessed canonical §6.6 system
`ScopedKey` under the finite `mutation-replay` posture. Its app-key is the exact injective length
frame `(scope, idem)`, including NUL and delimiter placement; the accompanying raw scope and token
are descriptive/expiry metadata and MUST match that witnessed frame before a store is invoked.
The volatile store keys its map by the complete frame. The durable Postgres store hashes the
complete frame into its physical replay namespace before SQL; hashing the raw scope and token as
independent authority is insufficient. Custom development/test stores receive the witnessed key
as their first argument through the same validating snapshot door. This registered composite
posture preserves the bounded enhanced scope plus the canonical 49-code-unit idem token and finite
epoch suffix while keeping the outer frame within 4,096 code units. Public/principal keys and every other
system posture retain the ordinary 1,024-code-unit app-key bound.

The app-scoped mutation signature is `handler(input, request, context)`. `input` comes from the
declared schema; `request` carries the validated contract request/session and transaction-scoped
managed DB posture; `context` exposes `fail`, the declared error union, signal, and other
framework-owned mutation operations. `fail(code, payload)` is keyed to the mutation's exact
`errors` object, and both code renames and payload-field renames propagate to form slots and tests.
Endpoint method, access, authentication, CSRF, body, cache, and response posture remain explicit
even though their request/result types are inferred. A factory facade MUST NOT synthesize an
allow/public/CSRF/response decision from a provider type.

The handler `request.db` type is a defense-in-depth authoring guardrail, not the security proof:
it preserves the configured DB provider's read/write surface while hiding public transaction
openers such as `.transaction()`, so nested transaction/open-handle misuse is a TypeScript error
on the normal `app.mutation()` path. TypeScript cannot prove arbitrary object lifetime
or reject every closure/module-scope capture of a handler parameter; runtime transaction ownership,
rollback-on-throw, SQL provenance gates, and fail-closed sinks remain authoritative. External I/O
inside a mutation handler is not a mutation-specific type or KV-gate error; it is governed by the
uniform outbound-egress floor (§6.6). Durable tasks (§9.6) are the framework primitive for
retryable/idempotent after-commit effects, not the only syntactically legal place to call `fetch`.

**Replay is an atomic reservation, not a lookup (normative).** The replay step MUST atomically claim its complete replay identity before executing a write — an `INSERT … ON CONFLICT` against the replay store (or an equivalent unique-key claim) inside the same serialization boundary that the commit settles. A request that wins the claim proceeds; a concurrent or sequential request carrying the same identity MUST block on the in-flight reservation and then replay the settled response, never re-execute the handler. For browser mutations, the identity is `(principal, principal epoch, CSRF session/rotation binding, source-derived mutation identity, idem-token)` under canonical length framing. For `csrf: false` mutations, it is `(SHA-256(UTF-16LE(versioned length-frame(machine replay principal))), source-derived mutation identity, idem-token)`, where UTF-16LE preserves every exact JavaScript code unit. Both claims occur after parse/coerce and the current guard chain but before handler work. The protected store scope follows the current principal epoch and validated CSRF binding (a different `req.session` identity, credential rotation, or revocation epoch never replays a prior response); the machine scope follows only the explicit post-guard caller identity. Both include the specific mutation, so an idem-token reused across mutations cannot cross-replay. Enhanced and no-JavaScript delivery share this one claim identity. If an existing record belongs to the other response vocabulary, the closed classifier returns a 422 conflict without re-executing. Deterministic declared application failures settle the complete rendered response. Validation, 429, and 409 failures abort their reservation; response-policy or rendering failure while producing a deterministic failure does the same. A failure after a successful transaction may preserve pending truth as required by the outcome-ambiguity rule above. For `webhook()`, the identity is the source-derived webhook scope plus the canonical authenticated provider-event facts from §9.1; its claim occurs after verify, loose parse, and temporal validation but before the handler. The provider key is the unique lookup key, and a live row for that key whose stored `occurredAtMs` or `expiresAtMs` differs from the supplied canonical identity is an integrity conflict, not a replay hit and not a second admissible event. These rules cover the enhanced and no-JS `mutation()` lifecycle, `webhook()`, and the streaming path, so concurrency, not merely strictly sequential retries, is deduplicated.

**Durable replay storage is bounded and refuse-never-evict (normative).** The shipped Postgres store gives mutation and webhook pending truth separate, database-enforced admission pools of 1,000 claims each. Only a pending claim owns a unique numbered slot, so concurrent replicas cannot race above the in-flight ceiling; successful settlement atomically clears its slot while retaining the committed row. At pending capacity, an existing identity still joins its in-flight claim or replays committed truth, while unseen work is refused before its handler runs (the mutation 429 / webhook retry outcome). No pending claim is evicted merely to admit new work. Aborting a pre-commit reservation or the explicit generation-fenced operator reconciliation path releases its slot.

Pending truth never expires or loses its slot automatically, because the application transaction may already have committed. Committed mutation truth has a canonical token-mint horizon of 24 hours; committed webhook truth has the authenticated event horizon from §9.1, `expiresAtMs = occurredAtMs + 30 days`. At either exact persisted expiry, committed truth becomes eligible for bounded batched deletion; expiry does not slide on lookup or replay. Fresh admission MUST perform eligible committed cleanup before applying its bounded-retention refusal and MUST compare the supplied expiry against the store's current clock atomically with reservation, so request latency cannot admit already-stale work. Once cleanup reclaims committed truth, the store MUST advance a monotonic `reclaimedThroughMs` high-water mark and reject fresh reserve or settlement with `expiresAtMs <= reclaimedThroughMs`, even if the wall clock later moves backward; durable storage persists this watermark, while a volatile development/test store guarantees it only for that store lifetime. Settlement performs the same current-clock and watermark checks; if the horizon elapsed after reservation, it leaves the reservation pending/fail-closed with its slot for reconciliation rather than publishing committed truth that the next cleanup could delete and re-execute. The same identity key may be admitted only after its prior committed row has actually been removed; while a pending or unexpired committed row exists, mismatched canonical facts conflict. The request path MUST NOT apply a receipt-time TTL, slide the deadline, evict the oldest row, or retire pending ambiguity. Volatile and durable stores implement the same temporal and conflict lifecycle, although a volatile store may additionally cap its total retained entries.

One committed snapshot is additionally limited to 1,048,576 UTF-16LE body bytes and 65,536 UTF-8 header bytes. Oversized settlement stores no oversized bytes and leaves the already-claimed key pending/fail-closed, so a retry cannot repeat a write whose application transaction may have committed; operator reconciliation must first establish the application outcome. The schema posture audit proves the exact non-deferrable `(surface, scope, idem)` primary key, nullable pending-slot column, 1..1,000 slot constraint, unique per-surface pending-slot index, canonical mint/occurrence/expiry columns and constraints, the nullable mutation-only principal erasure index and partial index, persisted per-surface reclamation watermark, response-byte constraint, and exact replay-table ACL before production serves. Ordinary app/runtime roles MUST have neither table-level nor column-level replay privileges; only the isolated system role receives the exact `SELECT, INSERT, UPDATE, DELETE` set. Provisioning repairs missing canonical identity constraints, revokes stray table/column grants, and fails closed if duplicate or temporally ambiguous legacy truth prevents repair.

<!-- kovo-model-boundary:replay-reservation/v1 -->

**Bounded replay-model honesty boundary (normative disclosure).** The optional
`ReplayReservation` state model explores exactly 2 replicas, 2 admission slots, 2 replay identities,
one backward clock step, and one crash point. Its **Postgres-CTE atomicity axiom** treats each
registered transition CTE as one atomic model action. The watermark row's `FOR UPDATE` lock is the
reviewed justification for that abstraction. It remains a **human assumption** about Postgres
transaction and row-lock behavior, not machine-verified evidence about the database implementation.
`kovo explain model-boundaries` MUST print that axiom, these bounds, every registered modeled
action, and the exact action complement plus excluded phenomena below.

The checked status is `bounded-model-checked`: TLC v1.7.4 under the exact-pinned
`formal/replay/tlc-toolchain.json` exhausts the committed `formal/ReplayReservation.cfg` state space
for type safety, no double execution, refuse-never-evict, monotonic reclamation, no resurrection,
and bounded admission. The same gate MUST reproduce the committed evict-pending/double-execute and
naive-watermark/backward-clock resurrection counterexamples. This bounded evidence validates the
declared abstraction and its historical mutants; it **does not prove Postgres**, its CTE/row-lock
implementation, unbounded cardinalities, or any excluded phenomenon below.

The model explicitly does not cover:

- <!-- kovo-not-modeled:durable-task-semantics --> durable-task queue transitions or scheduling;
- <!-- kovo-not-modeled:driver-network-failures --> driver retries, network partitions, or outcomes
  hidden by connection failure;
- <!-- kovo-not-modeled:postgres-lock-implementation --> the implementation correctness of Postgres
  transactions, CTEs, or row locks;
- <!-- kovo-not-modeled:principal-erasure-interleavings --> principal erasure and absence-probe
  interleavings with replay reservation;
- <!-- kovo-not-modeled:response-serialization --> response serialization, byte limits, or
  application response correctness;
- <!-- kovo-not-modeled:schema-and-posture --> schema provisioning, migration, ACL posture, or
  catalog-audit queries; and
- <!-- kovo-not-modeled:unbounded-cardinality --> replicas, slots, identities, clock steps, or crash
  points beyond the printed finite bounds.

**Idem-token minting, horizon, and entropy (normative).** `Kovo-Idem` is a per-submit token, not a per-form constant. Its only accepted production grammar is `v1_<issued-at-ms>_<nonce>`, where `issued-at-ms` is exactly 13 decimal Unix-epoch-millisecond digits and `nonce` is exactly 32 lowercase hexadecimal digits produced from 16 cryptographically random bytes. UUID v4 version/variant bits do not count toward this ≥128-bit nonce floor, so browser minting requires `crypto.getRandomValues(new Uint8Array(16))`; there is no timeless UUID/base64url fallback. A server-rendered/no-JavaScript form stamps server time. Enhanced modular and inline submits preserve that stamped issued-at value while replacing only the nonce, so JavaScript enhancement cannot silently extend the document's retry/deploy horizon. A direct seedless browser API has no server stamp to preserve and uses its boot-captured client clock.

The nominal mutation retry horizon is 24 hours, aligned with the required deploy-skew retention floor (§14): admission requires `nowMs < issuedAtMs + 24 hours` and `issuedAtMs <= nowMs + 5 minutes`. Thus the exact expiry millisecond is stale, the exact future-skew boundary is accepted, and one millisecond beyond either boundary is rejected. Malformed, legacy timeless, stale, and farther-future tokens are answered as a 422 idempotency conflict before any replay-store call or handler execution on enhanced, streaming, and no-JavaScript paths. Parsing produces an immutable `{ token, issuedAtMs, expiresAtMs }` fact so durable storage/cleanup consumes the already-snapshotted token rather than re-reading a request carrier. The timestamp is not a MAC or authorization claim: a client may always mint a new logical token, and changing the timestamp also changes the exact replay key. The security invariant is instead that an already-used exact token cannot become admissible again after its row is safely reclaimed.

`Kovo-Idem` is read from its reserved carrier and validated independently of parsed mutation input. Although parse/coerce and guards precede replay admission so guards see validated args, the token MUST NOT be derived from those args. The fixed 49-character grammar is shared by volatile, custom, and durable replay paths so a client-controlled key cannot become a storage or memory amplifier. A re-submit that edits visible fields therefore mints a distinct token — eliminating the silent lost-update where an unchanged hidden field replayed the first commit. Token collision within one caller binding and source-derived mutation identity is a server-detectable integrity fault answered as a 422 schema-class failure (§9.2), never a silent replay of an unrelated commit.

**Guards (arg-aware, normative).** A guard is a refinement run before `page`/`load`/`handler`. Beyond `req.session`, every guard receives the query's or mutation's **validated args / resolved instance key** — the same `s.*`-coerced values the loader and handler see (§9.4, §10.2). A guard may therefore express ownership over a client-visible key, not only session-wide roles. Guards run after schema parse/coerce so the args they inspect are already validated (§10.3 lifecycle).

Validated query and mutation args MUST be reconstructed before providers, guards, or final
consumers can observe them. The accepted graph is bounded primitives, plain own-data records, dense
arrays, and exact framework-witnessed capability/file leaves. A JavaScript `Date` is not an
immutable leaf: `Object.freeze()` and shadowed instance methods do not prevent an unchanged native
mutator such as `Date.prototype.setTime.call(value, replacement)` from changing its internal slot.
The receipt boundary therefore MUST reject `Date` values and direct authors to an ISO timestamp
string or epoch number until Kovo ships an immutable temporal value. A Proxy membrane is not a
substitute for this reconstruct-or-reject rule (SPEC §6.6 and C15 above). A stored upload is
reconstructed at its schema-owned door: its returned object metadata is pinned to own data and its
`lastModified` field is exposed to guards/handlers as an ISO timestamp string, never as the storage
adapter's mutable `Date` carrier.

**`owns()` ownership combinator.** `owns((args) => args.id, table.ownerColumn)` is the sanctioned ownership guard: it passes only when the principal (`req.session`, the column declared by the table's `owner:` annotation, §10.1) owns the row the key selects. `owns()` is composable with the other combinators (`all(authed, owns(...))`) and discharges the KV414 IDOR obligation for the key it covers. The shipped runtime contract is `guards.owns(keyOf, ownsRow)` where `ownsRow(req, key)` is an app-provided ownership predicate (so `@kovojs/server` stays decoupled from the data layer); the `table.ownerColumn` column-form above is the planned compile-time sugar that lowers to it.

```ts
export const adminRefund = mutation({ guard: role('admin') /*…*/ });
export const orderQuery = query({
  args: s.object({ id: s.string() }),
  guard: all(
    authed,
    owns((a) => a.id, orders.id),
  ), // args.id ownership — discharges KV414
  load: (db, args) => db.select().from(orders).where(eq(orders.id, args.id)),
});
// composable: guard: all(authed, rateLimit({ per: 'session', max: 10 }))
// rateLimit also admits per: 'ip' and a global dimension; a coarse per-IP/global
// body-size + rate limiter runs PRE-DISPATCH (413/429) ahead of replay+parse (§9.5)
// static audit: `kovo explain unguarded` lists every mutation, route, and query reachable without `authed`
// static audit: `kovo explain unscoped` lists every query/write touching an owner-annotated
// table (§10.1) whose key predicate is not traceable to req.session and not authorized by an
// ownership guard — the IDOR audit; the §11.1 predicate extractor does the tracing
```

**KV414 — IDOR audit is a blocking gate, not advisory.** A query or write whose key predicate touches an `owner:`-annotated table (§10.1) MUST resolve that key to either `req.session.*` or an `owns()`-class ownership guard. A site that reaches an owner-table row through a client-visible `args.*` key with neither is **KV414** (`error`) — runtime-verified by the §11.2 cross-check against the executed read/write predicates and the §11.1 predicate extractor's session-traceability result, so a smuggled or branch-hidden arg-keyed owner read fails CI as loudly as silent staleness (KV407/KV411). The `unscoped` audit prints the same set; KV414 is its enforced form. A genuinely public read suppresses KV414 only with a recorded justification at the site, which `kovo explain unscoped` surfaces verbatim.

**KV429 — single-row lost-update is by-construction for declared contended columns.** A column declared `kovo((columns) => ({ atomic: columns.stock }))` (a contended value column) or `kovo((columns) => ({ version: columns.lockVersion }))` (an optimistic-concurrency counter) signals that its single-row read-modify-write MUST fold the check and the act into one statement. A self-referential write to an `atomic` column — `set({ stock: stock - qty })`, lowered to a §10.5 SymbolicValue arithmetic over a self-`col` reference — whose `where()` carries NO eq-predicate on that atomic column (a compare-and-set guard) NOR on a declared `version` column is **KV429** (`error`): a lost-update race where two concurrent read-decide-write requests survive auth and validation and overwrite each other (oversell, double-spend, coupon reuse). The fix is a CAS predicate (`where(and(eq(t.id, id), eq(t.stock, prevStock)))`) or a carried row version, with a typed 409/422 on a stale write; a DB `CHECK`/unique constraint is the fail-closed backstop. Framework-owned Postgres mutation transactions MUST pin `READ COMMITTED` before app SQL, and boot posture MUST reject a different default; that stable ceiling does not replace the CAS/version predicate. **Honest ceiling (single-row only):** a write whose `where()` is opaque (range/`IN`/no key) or multi-row is NOT flagged — multi-row/aggregate invariants need `forUpdate`/`SERIALIZABLE` + retry and are **not** by-construction (the mutation transaction's READ COMMITTED alone does not prevent lost-update). The cross-function check-then-act is a false-negative floor until the interprocedural write-summaries land.

**KV433 — a read-surface that writes is a confused deputy.** A `query({ load })` loader is a read surface; reaching a Drizzle write (insert/update/delete/execute/run/batch) from it is a state change on an idempotent GET. A loader whose body directly reaches such a write is **KV433** (`error`) — the static no-write-reachable proof (Stage 2). There is no public GET-write query escape: move user-triggered state changes to `mutation()`/domain writes, and move explicitly side-effecting machine/API paths to `endpoint()`. **Current scope:** Stage 1 is shipped where Kovo owns the handle: the managed loader `db` is a read-only proxy whose write verbs throw at runtime, and its `Reader<Db>` type mirror makes those verbs a `tsc` error. That proxy is defense-in-depth, not the proof (§6.6). Stage 2's broader interprocedural case (a loader calling an imported `domain()` function that writes through some captured handle) still needs the bottom-up write-summaries that are not yet built and remains documented residue; today's direct static check covers writes directly reachable in the loader body and treats legacy/demoted query-write spellings as read-surface writes, never as escapes.

**KV438 — mass-assignment is by-construction write-provenance.** Where KV414 governs _which row_ a write touches, KV438 governs _which value_ reaches a **governed column**. A column is governed when it is the table's primary `key:`, its principal `owner:` column (both AUTO-governed), or is named in the `kovo((columns) => ({ governed: [columns.role] }))` annotation (the declare-once fact for `role`/`balance`/`isAdmin`-class columns, Constitution #2 — no call-site allowlists). A write that lands **request input** on a governed column — directly, through an alias/destructure, or via a `.values(input)` / `{ ...input }` spread — is **KV438** (`error`). The gate is **fail-closed**: a value the static analyzer cannot prove server-derived, literal, or explicitly asserted is rejected on a governed column (over the §11.1 AST symbol-identity provenance engine, never a branded type or runtime taint, §6.6). This is stronger than Rails `strong_parameters` / Django serializer denylists because it is schema-anchored and provenance-checked rather than an enumerated allowlist.

Two author-assertion escapes (SPEC §6.6: audit-grade, not proofs) route the residue. `serverValue(value, reason)` discharges only a value the analyzer independently proves literal or private/server-derived (`serverValue(input.x,…)`, `serverValue(opaque(input.x),…)`, and a missing value all fail). The louder `trustedAssign(input.x, obligation)` is the deliberate privileged-write path. Its second argument MUST be an inline, exact object literal with these three fields: `invariant: 'governed-write.authorized-principal'`; `why`, exactly `{ kind: 'guard-chain', guard: <machine reference> }` or `{ kind: 'policy', policy: <machine reference> }`; and `evidence`, exactly `{ kind: 'test' | 'policy-review', reference: <machine reference>, digest: 'sha256:<64 lowercase hex>' }`. Machine references are 1..256 characters from the closed `[A-Za-z0-9._:/#-]` alphabet and begin alphanumerically. Variables, spreads, shorthand/computed/accessor properties, substitutions, duplicate/surplus/missing fields, prose strings, and malformed references or digests do not discharge KV438. The runtime chokepoint independently validates stable own-data properties and reconstructs a frozen framework-owned snapshot; the type is ergonomics, not the proof. App-authored analyzer summaries cannot declare general server provenance or discharge KV438; opaque same-package and cross-module helpers fail closed. The exact structured fact and scanner-owned call-span identity are surfaced by `kovo explain capabilities`.

For every accepted `trustedAssign`, `kovo build` emits an **unsigned** `.kovo/escape-obligations.json` subject binding the exact call-span identity and structured obligation to the reviewed graph's SHA-256 artifact subject. The build and app-facing execution surfaces MUST NOT acquire or expose the signer. A reviewer outside the build/coding-agent environment may sign the canonical `kovo.escape-obligation-review/v1` subject with the existing runtime-posture Ed25519 authority; this is domain-separated but uses the same out-of-band trust-anchor fingerprint, never a second trust anchor. `kovo explain attest <url> --artifact <graph.json> --trust-anchor <fingerprint> --escape-reviews <reviews.json>` requires exactly one matching valid envelope for every emitted subject and verifies it together with the live runtime attestation. This establishes only the process fact that the pinned key holder approved the exact site, obligation, and artifact. It does not prove the obligation true, identify a human reviewer, or extend the by-construction claim through the escape. **Ceiling:** by-construction write-provenance for statically analyzable Drizzle writes; the escapes remain author assertions, and only `trustedAssign` deliberately accepts request input.

### 10.4 Optimistic updates

Optimism is keyed to **queries** (the data), never islands. One transform per (mutation × invalidated query); every island consuming the query updates from it — including islands written after the mutation (Constitution #2).

**Hand-written:** transforms are authored in the mutation file as pure `(data, input)` functions against the query's inferred result type. **Explicitly deferred:** `'await-fragment'` documents "considered; 1-RTT latency accepted here."

The public authoring spelling binds by query-handle identity rather than by registry augmentation
or an authored query key:

```ts
optimistic: [
  cartQuery.optimistic(cartMutationInput, (data, input) => ({
    ...data,
    count: data.count + input.quantity,
  })),
  productQuery.optimistic(cartMutationInput, {
    keys: (input) => [{ id: input.productId }],
    apply: (product, input) => ({ ...product, stock: product.stock - input.quantity }),
  }),
];
```

The first argument of every hand-written transform is the exact schema object also passed to
`app.mutation({ input })`. This gives TypeScript a concrete inference source for the callback input
without an explicit generic and lets runtime assembly reject schema drift by identity; the
`'await-fragment'` status needs no schema because it consumes no mutation input. The callback form
is legal only for a query with no client-visible instance key. A keyed query requires
`{ keys, apply }`; `keys(input)` returns a bounded dense list of that query's exact inferred args
shape and each instance receives the same pure transform. Each invalidated query handle occurs
exactly once with status `derived`, one hand-written transform, or `await-fragment`. Duplicate,
unrelated, cross-app, copied, or non-invalidated handles are hard errors, as is a keyed query with
missing, empty, or malformed instance keys. The compiler resolves handle symbol identity to the
source-derived registry identity and emits the same string-keyed wire/loader IR internally; app
source does not author that key or augment `InvalidationSets`.

Optimistic handle references must be statically initialized without an import cycle. A top-level
query-handle read whose module strongly depends back on the declaring mutation module is a teaching
diagnostic naming the cycle and the extraction fix; runtime `undefined` or import-order-dependent
registration is not accepted. The transform remains a pure value-returning function. Kovo may use
framework-owned bounded copy-on-write internally, but it MUST NOT expose a mutable draft contract or
weaken determinism, touched-path bounds, rollback, settlement, or emitted-transform equivalence.

**Derived:** for writes whose dataflow is closed over `{mutation input, schema constants, data the query already ships}` and queries within the shape grammar `{scalar-from-keyed-row, COUNT, SUM(arith), jsonAgg, filtered-COUNT, membership transitions}`, the compiler generates the transform (full derivation algebra in §10.5). Hand-written transforms share the same IR, so an app can override generated transforms pair by pair.

```ts
// cart.mutations.ts — app-authored override of a pair the compiler cannot derive
const addToCartInput = s.object({
  productId: s.string(),
  quantity: s.number().int().min(1),
});

function predictCart(cart: Readonly<CartResult>, input: InferSchema<typeof addToCartInput>) {
  const current = cart.items.find((item) => item.productId === input.productId);
  return {
    ...cart,
    count: current ? cart.count : cart.count + 1,
    items: current
      ? cart.items.map((item) =>
          item === current ? { ...item, qty: item.qty + input.quantity } : item,
        )
      : [...cart.items, { productId: input.productId, qty: input.quantity, pending: true }],
  };
}

export const addToCart = app.mutation({
  input: addToCartInput,
  optimistic: [
    cartQuery.optimistic(addToCartInput, predictCart),
    productQuery.optimistic(addToCartInput, {
      keys: (input) => [{ id: input.productId }],
      apply: (product, input) => ({ ...product, stock: product.stock - input.quantity }),
    }),
  ],
  // access, registry, and handler omitted
});
```

Compiler-derived transforms lower to the same internal plan, but that string-keyed representation
is generated ABI/IR. App source authors query-handle bindings and pure predictors, never a
standalone optimistic-plan object.

**Runtime protocol:** snapshot the affected query values → apply transforms to the shared query values and run their update plans (all dependent islands update at once; affected islands get `kovo-pending` + `aria-busy` automatically) → on success, `<kovo-query>`/morph reconciles over the prediction (right guess ⇒ near-no-op; wrong guess ⇒ silent correction) → on error, restore snapshots, render error fragment.

**Bounded snapshot (normative).** The snapshot MUST cover only the change-record-touched subset of each affected query value — the keyed rows, scalar fields, and aggregate inputs a transform can mutate — under structural sharing (copy-on-write of the touched path), not an unconditional deep `structuredClone` of the whole value. The `JsonValue` constraint bounds serializability, not size; cloning a large dataset per mutation or per rebase is forbidden. A transform may mutate only within its declared touch scope, so an untouched subtree is retained by reference and restored by reference on rollback.

Successful enhanced mutation responses should include `<kovo-query>` chunks for every invalidated query instance the server can derive and rerun in the request (§10.3). When server truth for an optimistic transform is missing, the client MUST emit a visible runtime diagnostic (**KV313**) and **discard** the prediction — roll the affected query back to its pre-transform snapshot (§10.4 bounded snapshot) or force a `/_q/<key>` refetch (§9.4) — never freeze the unconfirmed prediction on screen as authoritative-looking data. "Settle" here means "discard the transform and reconcile against server truth," not "promote the prediction." This is a development escape valve for explicitly fragment-only or temporarily uncovered responses: KV310 exhaustiveness (§10.6) makes a covered mutation that ships no truth for an invalidated query a build failure, so a missing-truth discard never reaches a production end user as the steady-state contract.

**Concurrency:** a per-query pending-transform log keyed by `Kovo-Idem` token (§9.1). On each arriving server-truth chunk the runtime first **settles** the log: it drops every pending transform whose token is in the chunk's settlement set (§9.1.1) — those commits are already reflected in the arriving truth — then morphs the truth in, then re-applies **only the not-yet-committed** transforms in log order (rebase). Purity gives determinism but not idempotency, so settlement-before-rebase is mandatory: re-applying an already-committed additive transform would double-count the write. A transform whose token is absent from every truth chunk remains pending until its own response settles it. The settlement-matching rule is exact token-set membership; a truth chunk that carries no settlement set is treated as settling its triggering mutation's token only.

**Named FIFO queues (`queue: 'cart'`, normative).** A queue serializes the mutations declaring the same conceptual group name. Queue names are not mutation registry identities and are intentionally allowed to span declarations (§4.1). Its semantics are pinned so two conforming implementations cannot diverge between "frozen cart" and "dropped actions":

- **Transform-apply timing.** A queued mutation applies its optimistic transform on **enqueue** (immediately, against the current optimistic value including earlier queued-but-unsent transforms), not on dequeue — so the UI reflects the full queued intent without waiting for the head to drain. Its network request is sent only when it reaches the head.
- **Head-of-line timeout/abort.** The in-flight head MUST carry a bounded timeout; on timeout or transport error the head is aborted, its transform is rolled back via its bounded snapshot, an error fragment is rendered, and the queue advances to the next entry. A hung head MUST NOT block the tail indefinitely.
- **Failed/hung-head drain.** When the head fails or times out, the tail is **not** silently dropped: each surviving entry is re-validated against the rolled-back optimistic value and either advances or is discarded with a visible KV313 diagnostic; ordering among survivors is preserved.
- **Queued-but-unsent fate on navigation.** Entries already in flight complete via `keepalive`; entries still queued-unsent at navigation are abandoned with the document (their optimistic transforms die with the log), exactly as for un-queued in-flight work — navigation is a reconciliation point, not a delivery guarantee.
- **Queue bound.** Each named queue has a bounded depth; enqueue past the bound is refused with a visible diagnostic rather than growing without limit.

Navigation is a free reconciliation point: in-flight requests complete via `keepalive`, the log dies with the document.

### 10.5 Derivation algebra

The compiler may derive an optimistic transform only when the write can be reduced to symbolic
row-effects over mutation input, schema constants, and columns already present in the affected query,
and the query shape fits the grammar named in §10.4. The derivation is all-or-nothing per affected
field: an opaque server computation, non-key match, unsupported aggregation/windowing shape,
interprocedural opacity, or untraceable parameter punts that field to `await-fragment` or a
hand-written transform. Every punt is named in `kovo explain mutation <name> --optimistic` with the exact expression
and reason.

**Soundness is property-tested:** for derivable pairs, generated-state tests assert
`patch(clientShape(s), i) ≡ clientShape(apply(effect, s, i))` — the commuting diagram is the
deriver's test suite. The expanded derivation grammar and examples live in
`site/content/guides/optimistic.md`.

### 10.6 Exhaustiveness

Per mutation, coverage = invalidated-query set (derived) × status. Valid statuses are `derived`, `hand-written`, and `await-fragment`:

```
kovo check optimistic
mutation cart/applyCoupon:
  cartQuery.items      hand-written ✓
  cartQuery.subtotal   hand-written ✓
  cartQuery.discount   UNHANDLED ⚠ KV310
     → hand-write in cart.mutations.ts, or declare 'await-fragment'
```

Punts report their reasons inline (e.g. `PUNTED (Opaque: compute_discount)`).

The check runs at two altitudes off the same derived set. Query-handle bindings make each
hand-written predictor editor-visible against the exact query result and mutation schema, and
`app.mutation({ optimistic: [...] })` rejects copied, cross-app, duplicate, or schema-mismatched
bindings. The compiler resolves those handles against §6.1 `InvalidationSets`; `kovo check` reports
KV310 when any invalidated query lacks either a transform or `'await-fragment'`. The generated
registry remains compiler/runtime IR rather than an app-authored type augmentation.

Forgetting an optimistic update is a visible, suppressible diagnostic with the suppression recorded in source — never a silent UI inconsistency.

---

---

<!-- Source: spec/11-diagnostics.md -->

# Diagnostic Registry (SPEC §11.3)

This file is incorporated by reference from [../SPEC.md](../SPEC.md) and is normative for Kovo framework behavior.
The root spec remains the entry point and cross-reference index; this module owns the detailed contract below.
This file is the normative owner for the KV### diagnostic table. The generated diagnostics reference compares framework source and diagnosticDefinitions against this table.

### 11.3 Diagnostic codes (registry)

| Code  | Severity | Enforcement class   | Meaning                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| ----- | -------- | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| KV201 | error    | compile-error       | Closure captures unserializable value (shows lowering + fixes)                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| KV210 | lint     | audited-escape      | Anonymous handler — name it for stable identity                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| KV211 | lint     | audited-escape      | `on:load` eager trigger — justification comment required (the greppable eager-JS budget)                                                                                                                                                                                                                                                                                                                                                                                                                      |
| KV212 | lint     | audited-escape      | Unknown `on:*` event or trigger name (DOM event names; the closed trigger set, §4.7)                                                                                                                                                                                                                                                                                                                                                                                                                          |
| KV220 | error    | compile-error       | Literal `href`/form `action` matches no declared route (full-origin URLs / `external` opt out)                                                                                                                                                                                                                                                                                                                                                                                                                |
| KV221 | error    | compile-error       | IDREF (`commandfor`, `popovertarget`, `for`, `aria-*`) references an id not present in scope                                                                                                                                                                                                                                                                                                                                                                                                                  |
| KV222 | error    | compile-error       | Hand-written binding stamp disagrees with the typed expression it wraps (§4.8)                                                                                                                                                                                                                                                                                                                                                                                                                                |
| KV223 | lint     | audited-escape      | Redundant hand-written stamp in sugar — the compiler derives it (§4.8)                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| KV224 | error    | compile-error       | Static `id` in a repeatable component / duplicate id in a page composition (§4.5)                                                                                                                                                                                                                                                                                                                                                                                                                             |
| KV225 | error    | compile-error       | JSX nesting violates the HTML content model — the parser would re-parent (§4.2)                                                                                                                                                                                                                                                                                                                                                                                                                               |
| KV226 | error    | compile-error       | `kovo-deps`/`kovo-c` names an unknown query instance or component in emitted IR fixpoint validation                                                                                                                                                                                                                                                                                                                                                                                                           |
| KV227 | error    | compile-error       | Binding path traverses a nullable segment without `?.` or a null-handling derive (§4.8)                                                                                                                                                                                                                                                                                                                                                                                                                       |
| KV228 | error    | compile-error       | Ambiguous route table: two routes can match the same canonical request path or duplicate route path (§9.5)                                                                                                                                                                                                                                                                                                                                                                                                    |
| KV229 | error    | compile-error       | Static export constraint violation: route/session/mutation/param usage cannot be exported as L0/L1 (§9.5)                                                                                                                                                                                                                                                                                                                                                                                                     |
| KV230 | error    | compile-error       | Fragment-target children not lowerable to a component reference (shows the hoisting + fixes)                                                                                                                                                                                                                                                                                                                                                                                                                  |
| KV231 | error    | compile-error       | Unmergeable attribute conflict in primitive composition (shows both sources + the §4.6 rule)                                                                                                                                                                                                                                                                                                                                                                                                                  |
| KV232 | lint     | audited-escape      | Author override of a primitive-owned ARIA/state attribute                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| KV233 | error    | compile-error       | Two writers for one binding target                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| KV234 | error    | compile-error       | Package component prefix registration conflict or reservation violation (§6.1.1)                                                                                                                                                                                                                                                                                                                                                                                                                              |
| KV235 | error    | compile-error       | App source hand-authors lowered IR/string-rendered components or derivable runtime stamps; write TSX (`queries`, `key`, typed expressions) and let the compiler emit IR (§5.2)                                                                                                                                                                                                                                                                                                                                |
| KV236 | error    | compile-error       | Binding/derive/sink value reaches an unsafe output context (raw-HTML insertion; URL-scheme attribute against the javascript:/data: denylist for href/src/action/formaction/xlink:href/ping/poster/CSS url(); `on*`; style attribute or `<style>` text; srcdoc; `<script>`/`application/json` island text) without the typed trusted-HTML escape hatch (`trustedHtml`/`trustedUrl`, §4.8); contexts and URL-scheme allowlist defined in §4.8, contract in §5.2 #10                                             |
| KV237 | error    | compile-error       | Duplicate derived component registry key (§4.2, §4.8, §6.1.1)                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| KV238 | error    | compile-error       | Duplicate derived fragment-target registry key (§4.5, §6.2, §9.1)                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| KV239 | error    | compile-error       | Duplicate static view-transition name (§8)                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| KV240 | error    | compile-error       | Duplicate or ambiguous query-plan identity (§4.8)                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| KV241 | warn     | audited-escape      | Derived component registry key changed since the previous emitted graph (§4.2, §4.8)                                                                                                                                                                                                                                                                                                                                                                                                                          |
| KV242 | error    | compile-error       | Enhanced mutation form control names do not match the bound mutation input schema (§6.2, §6.3)                                                                                                                                                                                                                                                                                                                                                                                                                |
| KV243 | error    | compile-error       | Invalid stream text target; streaming text targets are framework-owned source IDs, not arbitrary selectors or ambiguous DOM queries (§9.1).                                                                                                                                                                                                                                                                                                                                                                   |
| KV244 | lint     | audited-escape      | `defer()` used directly as a JSX child; use the public `<Defer>` primitive so fallback/render output follows JSX escaping (§8)                                                                                                                                                                                                                                                                                                                                                                                |
| KV245 | error    | compile-error       | TypeScript/TSX parse failed; later compiler phases cannot operate on a recovery tree (§5.2).                                                                                                                                                                                                                                                                                                                                                                                                                  |
| KV246 | warn     | audited-escape      | Derived mutation registry key changed since the previous emitted graph (§4.1, §10.3)                                                                                                                                                                                                                                                                                                                                                                                                                          |
| KV247 | warn     | audited-escape      | Derived query registry key changed since the previous emitted graph (§4.1, §10.2)                                                                                                                                                                                                                                                                                                                                                                                                                             |
| KV301 | lint     | audited-escape      | Server fact in island-local state                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| KV302 | error    | compile-error       | `data-bind` path is not present in the declared query shape (§4.8)                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| KV303 | error    | compile-error       | Inferred refresh-target render input is not declared as query data or serializable stamped props (§4.5)                                                                                                                                                                                                                                                                                                                                                                                                       |
| KV304 | error    | compile-error       | Reserved query name such as `state` is not allowed (§4.8 binding roots)                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| KV310 | warn     | audited-escape      | Invalidated query lacks optimistic transform (write/defer/derive)                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| KV311 | warn     | audited-escape      | Query/state-dependent DOM position with no update status — plan/isomorphic/fragment/renderOnce (§4.9)                                                                                                                                                                                                                                                                                                                                                                                                         |
| KV312 | error    | compile-error       | Time-dependent rendered position has no declared clock/query refresh cadence (§4.8/§4.9)                                                                                                                                                                                                                                                                                                                                                                                                                      |
| KV314 | error    | compile-error       | `renderOnce` position reads a query invalidated by a modeled write, so the immutable declaration would hide stale UI (§4.9).                                                                                                                                                                                                                                                                                                                                                                                  |
| KV315 | warn     | audited-escape      | Raw `Date.now()`/`new Date()` read in a derive has no declared clock cadence; use a declared `clocks` input (§4.8/§4.9)                                                                                                                                                                                                                                                                                                                                                                                       |
| KV320 | lint     | audited-escape      | Event payload overlaps query data — use a transform                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| KV330 | error    | compile-error       | Direct db access in a mutation handler — route through domain                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| KV402 | error    | fail-closed-runtime | Write touched a domain not covered by the derived or declared mutation touch set (silent stale UI)                                                                                                                                                                                                                                                                                                                                                                                                            |
| KV403 | warn     | audited-escape      | Declared domain never observed written (stale claim / untested branch)                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| KV404 | error    | fail-closed-runtime | Write to unmapped table (map it or mark `exempt`, e.g. append-only logs — write-side only, §10.1)                                                                                                                                                                                                                                                                                                                                                                                                             |
| KV405 | error    | fail-closed-runtime | Conditional/un-fully-resolved write site has branches never executed under instrumentation — CI-gating; static touch set is unproven (§11.1/§11.2)                                                                                                                                                                                                                                                                                                                                                            |
| KV406 | error    | compile-error       | Statically un-analyzable write site (unresolved/node_modules; raw SQL) — manual `touches` (and `tables:` for raw SQL) required, executor-enforced + runtime-verified (§10.3/§11.1/§11.2)                                                                                                                                                                                                                                                                                                                      |
| KV407 | error    | fail-closed-runtime | Query read from a domain not covered by the derived or declared query read set (missed invalidations)                                                                                                                                                                                                                                                                                                                                                                                                         |
| KV408 | error    | fail-closed-runtime | Declared row key ≠ observed row predicate                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| KV409 | notice   | audited-escape      | Non-eq predicate — degraded to table-level invalidation                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| KV410 | error    | compile-error       | Opaque query projection (`sql<T>`, raw SQL) — declared output schema AND `reads:` table set required; `reads:` checked against exemption (KV411) and folded into the read set, shape runtime-verified (§10.2)                                                                                                                                                                                                                                                                                                 |
| KV411 | error    | compile-error       | Query read set includes an `exempt` table — exemption is write-side only (§10.1), runtime-verified (§11.2)                                                                                                                                                                                                                                                                                                                                                                                                    |
| KV412 | error    | compile-error       | Query reads an unmodeled relation (view / materialized view) with no derived or declared domain (§10.1/§11.1)                                                                                                                                                                                                                                                                                                                                                                                                 |
| KV413 | error    | compile-error       | Database trigger / engine side-effect needs a declared fan-out edge before invalidation can be proven (§10.1/§11.1)                                                                                                                                                                                                                                                                                                                                                                                           |
| KV313 | error    | fail-closed-runtime | Optimistic transform settled with missing server truth — prediction discarded/refetched, not frozen (§10.4); a covered mutation shipping no truth for an invalidated query is also caught by KV310 (§10.6)                                                                                                                                                                                                                                                                                                    |
| KV420 | error    | compile-error       | Island declaring local state (`kovo-state`) nested inside another component's server-refreshable fragment target — the morph carries no local-state serialization and would clobber the child's live state on refresh (§4.5/§4.9/§9.1)                                                                                                                                                                                                                                                                        |
| KV316 | error    | compile-error       | `isomorphic: true` on a children/slot-accepting component whose render cannot be partitioned into self-render positions plus preserved projected-children regions (client self-render has no slot arguments, would drift from server output) (§4.5/§4.8)                                                                                                                                                                                                                                                      |
| KV317 | error    | compile-error       | Static state-bearing `aria-*` value contradicts the primitive's render-time state — frozen-vs-clobbered; distinct from the visible-override lint KV232 (§4.6)                                                                                                                                                                                                                                                                                                                                                 |
| KV318 | lint     | audited-escape      | `isomorphic: true` lacks an adjacent justification comment (§4.8)                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| KV414 | error    | compile-error       | Authorization fails closed: either a query/write reaches an `owner:`-annotated table (§10.1) through a key predicate not traceable to `req.session` and not authorized by `owns()` (IDOR), or an authorization-bearing mutation transition is `⊤` because the finite grant analyzer cannot classify it (§10.3). IDOR is runtime-verified (§11.2); suppress only a genuinely public read with a recorded justification. Grant `⊤` transitions are not suppressible.                                            |
| KV415 | error    | fail-closed-runtime | Response header channel: a direct structured app header is outside `Cache-Control`/`Last-Modified`/`Vary`, a dedicated field bypasses its typed option, an app writes browser-navigation (`Refresh`) or message-framing/hop-by-hop metadata, or a name/value contains CR/LF/NUL/control chars; `Set-Cookie` must use the typed cookie builder (§9.1.1)                                                                                                                                                        |
| KV416 | error    | compile-error       | Prod render-equivalence gate failed (§5.2.2): `apply_delta(base, render_prod(Δ)) ≢ render_dev(full)` over the corpus, or a corpus edit violated §5.2.1 identity separation/monotonicity (shape/grammar changes must move the render-plan fingerprint and app build token without moving an unchanged representation digest). Build-failing.                                                                                                                                                                   |
| KV417 | error    | compile-error       | Configured supported deploy-skew window (§6.6/§14) is below the required 24-hour prior-version retention floor, or the serving layer cannot retain prior immutable modules and per-token `/_q` reads for the window (§14).                                                                                                                                                                                                                                                                                    |
| KV418 | error    | compile-error       | `csrf: false` mutation references ambient browser authority (reads `req.session` or runs a session/cookie-derived guard) — the CSRF exemption is unsound; route non-browser writes to `endpoint()`/`webhook()` (§6.6, §9.1)                                                                                                                                                                                                                                                                                   |
| KV419 | error    | compile-error       | `prefetch: 'moderate'` set on a guarded, session-dependent, or not-proven-side-effect-free route without a named justification (§8)                                                                                                                                                                                                                                                                                                                                                                           |
| KV421 | error    | compile-error       | Duplicate mutation key: generated mutation registry indexing and server dispatch would disagree (§6.1, §9.5)                                                                                                                                                                                                                                                                                                                                                                                                  |
| KV422 | error    | compile-error       | Request-derived or otherwise unproven data reaches executable SQL text on a framework-managed DB handle; bind scalar values as parameters and choose identifiers/keywords from typed allowlists or schema facts (§10.2/§10.3)                                                                                                                                                                                                                                                                                 |
| KV423 | error    | compile-error       | Raw `endpoint()` declaration lacks required audit metadata such as explicit method, reason, mount justification, response body posture, cache posture, or app-owned encoding/header-safety posture (§9.1)                                                                                                                                                                                                                                                                                                     |
| KV424 | error    | compile-error       | App-authored dangerous sink is not registered or behind a safe Kovo helper/trust API; direct raw HTML, URL/navigation, selector, header, file/path, dynamic-code, process sinks must use the matching safe surface or audited escape hatch (§4.8, §5.2, §9.1)                                                                                                                                                                                                                                                 |
| KV425 | error    | compile-error       | Source/sink drift detection found a framework sink token that is not in the shared registry and has no narrow repo-internal exclusion                                                                                                                                                                                                                                                                                                                                                                         |
| KV426 | error    | compile-error       | Trust escape hatch such as `trustedHtml`, `trustedUrl`, raw endpoint, custom/no verifier, static export path override, or future trusted SQL lacks auditable provenance/source-span/justification (§4.8, §9.1)                                                                                                                                                                                                                                                                                                |
| KV428 | error    | fail-closed-runtime | Inline rendering of an unverified-content-type upload; attacker bytes must default to attachment plus `nosniff` unless verified-safe or audited (§6.6, §9.1).                                                                                                                                                                                                                                                                                                                                                 |
| KV429 | error    | compile-error       | Read-then-write on a contended column without an atomic/version guard; use compare-and-set or a version guard for lost-update safety (§10.3, §11.1).                                                                                                                                                                                                                                                                                                                                                          |
| KV430 | warn     | audited-escape      | Schema admits unbounded breadth/depth on an untrusted source; declare explicit bounds while the runtime budget remains the protection (§6.6, §9.5).                                                                                                                                                                                                                                                                                                                                                           |
| KV431 | warn     | audited-escape      | Referenced client module is absent from the integrity/CSP manifest; the provenance allowlist cannot audit a module it does not list (§6.6).                                                                                                                                                                                                                                                                                                                                                                   |
| KV432 | error    | fail-closed-runtime | Insecure cookie downgrade without a recorded justification; credential cookies use the typed cookie builder security floor by default (§6.6, §9.1).                                                                                                                                                                                                                                                                                                                                                           |
| KV433 | error    | compile-error       | `query()` loader reaches a write; read surfaces must not cause state changes (§6.6, §9.4).                                                                                                                                                                                                                                                                                                                                                                                                                    |
| KV434 | error    | compile-error       | Non-linear-safe pattern literal in a wire string validator; use blessed formats, safe literal patterns, or audited `unsafeRegex` (§6.6, §9.5).                                                                                                                                                                                                                                                                                                                                                                |
| KV435 | error    | compile-error       | Secret-classified query result field, or an opaque/unresolved projection from a table carrying secret columns, reaches the client query wire; remove the field or rewrite the projection so the analyzer proves that only public columns cross the wire. Declassification does not suppress this request-boundary error because §6.6 makes the policy constructor and reveal doors request-closed with KV448 (§6.2, §6.6, §10.2, §11.3).                                                                      |
| KV436 | error    | compile-error       | Query, mutation, route/page, endpoint, or webhook has no explicit access decision (default-deny, §10.2); satisfy it with an access guard chain (an existing `guard`/layout guard counts), `publicAccess("reason")`, or `verifiedAccess`/an `auth`/`verify` scheme, and review the ledger with `kovo explain access`. Proves a decision exists, not that it is correct — IDOR correctness stays KV414 (§10.2, §11.3)                                                                                           |
| KV437 | error    | compile-error       | Server-only value captured into a client handler reaches the client bundle; client modules may emit only values proven client-safe or audited (§6.2, §6.6).                                                                                                                                                                                                                                                                                                                                                   |
| KV438 | error    | compile-error       | Request input or opaque provenance reaches a governed column (mass assignment); governed columns must receive structurally proven server/private values, literals, or `trustedAssign(input, obligation)` with the exact inline invariant/why/evidence grammar. Prose, dynamic/malformed obligations, app analyzer summaries, and `serverValue(unknown, ...)` do not discharge the gate. Accepted escapes emit artifact-bound unsigned review subjects (§6.6, §10.3, §11.1).                                   |
| KV439 | error    | compile-error       | DB table row reaches the client query wire without an explicit projection; row provenance crosses the client wire only through intentional field projection (§6.2, §9.4, §11.3).                                                                                                                                                                                                                                                                                                                              |
| KV445 | error    | compile-error       | Build registers durable tasks but the selected preset declares no JobRunner capability; deploy with a preset/adapter that emits a real drainer, or remove `task()`/`request.schedule()` for that target (§9.6).                                                                                                                                                                                                                                                                                               |
| KV446 | error    | compile-error       | Node preset durable tasks are registered for a SQLite/better-sqlite3 server bundle even though the default JobRunner persists jobs in Postgres `_kovo_jobs`; use a Postgres-compatible app database, a supported durable queue adapter, or remove durable tasks from that deployment (§9.6).                                                                                                                                                                                                                  |
| KV447 | warn     | audited-escape      | SQLite owner annotations are advisory only in the experimental SQLite runtime: `kovo((columns) => ({ owner: columns.ownerId }))` and `ownerVia` remain available to static audits, but SQLite has no engine role/RLS layer, so multi-principal authorization requires the default PGlite/Postgres runtime (§10.3).                                                                                                                                                                                            |
| KV448 | error    | compile-error       | An untrusted-data-reachable root reaches raw network, filesystem, process, VM, worker, database-driver, or unresolved module-loading authority; or a reachable package summary is absent, stale, contradictory, or incomplete for the installed version/conditional exports. Use the reviewed Kovo capability door or repair the exact-version summary; diagnostics include the root-to-capability provenance path (§6.6).                                                                                    |
| KV449 | error    | compile-error       | A browser/server effect is outside the finite security IR, or its same-file-helper summary is unsupported, recursive, or over budget. Unknown operations and partial mutation forms close; server diagnostics name root, transfers, sink, and reason. Use typed `<form mutation>`, a reviewed operation, an exact immutable helper inside the bounded semantics, or a named explain-visible exceptional door (§4.3, §5.2, §6.6, §9.1). It is not a general code evaluator or a same-realm JavaScript sandbox. |
| KV450 | error    | fail-closed-runtime | A non-database stateful sink key has no framework-witnessed owner scope, carries a forged/malformed frame, or names an unregistered system posture. Derive principal keys with `scopedKey(request, key)` or task `actAs(id).stateKey(key)`; use `publicScopedKey(key)` only for deliberately shared state. Storage and durable-task coalescing doors reject strings and casts before namespace use (§6.6, §9.6, §10.3).                                                                                       |
| KV451 | error    | compile-error       | A compiler-derived value cannot be represented by the shared structural source-emission grammar for its exact role (`jsStringLiteral`, `jsIdentifier`, `tsPropertyKey`, or `importSpecifier`). Emission fails before writing an artifact instead of interpolating the value as executable sibling syntax (§5.2).                                                                                                                                                                                              |
| KV452 | error    | compile-error       | Owner-scoped or governed database data reaches a persistent non-engine sink without the framework-owned `derived()` door, or a derived vector operation lacks the exact framework request principal binding. `derived(adapter, { key, kind: 'vector' })` reconstructs every physical namespace from the complete runtime-witnessed `ScopedKey` frame, and reads re-derive that same request-principal scope (§6.6, §10.3 C9).                                                                                 |

The enforcement class is the code's primary SPEC §2 posture. `compile-error` means static analysis,
build, export, or check rejects before an affected artifact is accepted. `fail-closed-runtime`
means the decisive fact is only available while executing a bounded framework door, which refuses
the operation or discards unsafe state. `audited-escape` is non-blocking, degraded, or explicitly
reviewed posture that must remain visible to check/explain. A runtime floor may backstop a
`compile-error`; the static class wins when the same violation is provable before execution.

The shared `diagnosticDefinitions` registry is the source of each diagnostic's severity; surfaces
must not override severity or invent local blocking policies. A diagnostic with `error` severity
blocks the Vite dev transform by throwing a teaching error rendered by Vite's overlay and terminal,
blocks build and static export before output is written, and makes dev-mode page, fragment, or
mutation requests that depend on the failed module return a server-rendered teaching-error
document with HTTP 500. `warn`, `lint`, and `notice` diagnostics are non-blocking on dev transform,
build, and static export; they may be summarized or streamed through the surface's non-blocking
diagnostic channel, but they do not trigger dev teaching-error documents. MCP tools expose the same
structured diagnostics (code, severity, message, help, and position when available) from the
compile/check/explain APIs; MCP is a rendering/query surface, not a second diagnostic channel.

Structured diagnostic provenance is runtime-registry identity, not structural shape. That private
identity is realm-local: when a supported compiler, build, or export boundary crosses an isolate or
bundled SSR module graph, the originating realm MUST first verify exact registry membership and
emit a bounded own-data diagnostic wire record; the receiving realm MUST validate that record and
reconstruct it through its own generated diagnostic constructor before collection or rendering.
Copying a diagnostic object, sharing a public symbol brand, or accepting a wire record without the
originating registry check cannot transfer framework diagnostic authority.

### 11.5 Finite MCP stdio transport

Kovo's CLI and devtool MCP surfaces use newline-delimited JSON over stdio only. The closed method
set is `initialize`, `ping`, `tools/list`, and `tools/call`; the only lifecycle notification with
state is `notifications/initialized`. The server has three ordered phases: it first accepts one
valid `initialize` request, then waits for `notifications/initialized`, then serves `ping` and tool
requests. Duplicate initialization, or a request before the ready phase, fails closed. Other
notification-shaped messages receive no response and cannot change lifecycle state.
Initialization changes phase only after the exact id-bound success response is proven to fit; an
oversized success returns a bounded error and leaves the server able to accept a shorter retry.

Every request MUST be a JSON-RPC 2.0 own-data object with the exact request envelope and a string or
safe-integer id whose echo fits the bounded error envelope. Each method has a closed top-level
parameter grammar: initialize requires
`protocolVersion`, `capabilities`, and name/version `clientInfo`; ping permits only optional `_meta`;
list permits only optional string `cursor` and `_meta`; call requires `name` and permits only object
`arguments` and `_meta`. `_meta`, capabilities, and arguments remain inert JSON data. Surplus fields,
accessor-bearing direct inputs, invalid ids, and malformed params are rejected. Supported protocol
versions are `2025-11-25`, `2025-06-18`, `2025-03-26`, `2024-11-05`, and `2024-10-07`; an unknown
requested version negotiates to `2025-11-25` so the client can decide whether to continue.

Each input or output payload is at most 4 MiB of UTF-8 bytes; the finite engine accepts configured
ceilings from 256 bytes through that maximum. LF and CRLF are equivalent delimiters, and their bytes
do not consume the payload ceiling. The parser preserves code points split across byte or string
chunks, rejects malformed UTF-8, malformed JSON, and duplicate object members (including escaped
key aliases), processes a final nonempty EOF segment as one line, discards an oversized line through
its next delimiter, and then resumes. Direct and tool-provided JSON is snapshotted through own data
descriptors before field reads and is limited to 128 container levels, 65,536 value/member nodes,
and an exact non-allocating estimate no larger than the applicable serialized line ceiling.

The CLI adapter has five tools and an exact, non-extensible argument language:

- `compile_component` accepts only `fileName` and inline `source`. `sourceProvenance` is implicitly
  `app`; package-prefix, query-shape, and registry fact carriers are not protocol inputs.
- `kovo_check` accepts only an optional `family` (`all`, `coverage`, or `optimistic`) and an optional
  inline `graph`.
- `kovo_docs` accepts a required 1..256-byte task string and an optional result limit from one
  through eight. It searches only the digest-authenticated local snapshot selected by
  `kovo update-docs`; the result includes the exact Kovo version, snapshot digest, file digest, and
  bounded excerpt. It has no network or caller-selected path input.
- `kovo_explain` accepts only an optional inline `graph` plus one exact options mode: agent,
  endpoints, access/unguarded/unscoped audit, or a `kind`/`target` lookup. Surplus option fields and
  multiple modes are errors.
- `list_diagnostics` accepts the empty object only.

The protocol has no `graphPath` or other caller-selected filesystem input. Human `kovo check` and
`kovo explain` argv commands may still read an operator-selected graph file; that authority is not
part of MCP. At server construction the CLI canonicalizes the launch working directory once.
It retains that directory's device/inode witness; a rename, replacement directory, or symlink at
the original canonical path cannot become new authority for a later compile.
Compiler `fileName` is a slash-separated relative path with no absolute, empty, dot, dot-dot,
backslash, or NUL segment, and has at most 64 path segments. Both package discovery's root and its
hard canonical boundary are the pinned launch directory. Bounded discovery admits at most 128
unique bare packages before sorting or filesystem probes. Directory and manifest symlinks cannot
escape that boundary; FIFO and other non-regular manifest candidates are ignored; and a package
manifest above 256 KiB is ignored before JSON parsing. Bounded manifests are read from a no-follow,
nonblocking descriptor with a fixed-size loop and one-byte growth probe, so a same-inode grow race
cannot allocate past the cap. The accepted descriptor and pathname facts must retain device,
inode, size, mtime, and ctime through the read, rejecting same-size in-place rewrites as well as
path swaps. Changing process cwd after construction cannot move this capability.

One inline compiler source is at most 256 KiB UTF-8. Before TypeScript parsing, the adapter performs
a linear scan capped at 32,768 token starts/punctuation units and 512 potential structural opener
tokens. After parsing and before lowering, an iterative whole-AST walk admits at most 20,000 nodes
and depth 256. Recursive parser exhaustion is normalized to one closed parser-budget error rather
than exposing the host runtime failure text. These are transport work limits, not substitutes for
the compiler's semantic fail-closed budgets. They keep flat, deeply nested, alias-heavy, and
malformed boundary inputs local to one bounded call.

Graph admission is also a pre-verifier resource proof. A linear walk counts every traversed object
property and array entry, then charges their conservative pair envelope plus the explicit
render-once `updateCoverage × queries × (mutations + touchGraph)` domain work with overflow-safe
saturating arithmetic. The aggregate ceiling is 65,536 comparison units. It applies before the
linear graph validator and therefore bounds mutation/query, query/consumer, endpoint/runMutation,
scope/ownership, session-authority, event/query, and endpoint-posture joins as well as the named
cubic path. A graph string is at most 4,096 bytes; materialized output is preflighted at 2,048
estimated rows and 2 MiB of amplified graph text before the transport's independent 4 MiB response
ceiling. One stdio session admits at most 256 tool calls.

Dispatch and output are sequential and backpressure-aware. A false output write MUST wait for a
provided drain capability or fail explicitly; it cannot be treated as successful delivery.
Protocol failures use JSON-RPC errors; a tool-domain failure is a successful JSON-RPC result with
`isError: true`, with id-aware text truncation when necessary. Static tool descriptors or
instructions that cannot fit the output ceiling are rejected at construction. An oversized or
unserializable tool result emits one bounded protocol error and leaves the connection usable. The
surface provides no HTTP, SSE, OAuth, resources, prompts, sampling, tasks, logging channel,
server-to-client request, or extensible method router. MCP diagnostics remain the registry-owned
diagnostics above, not a second diagnostic channel.

---

<!-- Source: spec/11-verification.md -->

# Static Analysis & Verification (SPEC §11 except §11.3)

This file is incorporated by reference from [../SPEC.md](../SPEC.md) and is normative for Kovo framework behavior.
The root spec remains the entry point and cross-reference index; this module owns the detailed contract below.

## 11. Static Analysis & Verification

### 11.1 Touch-set extraction (the static pass)

Rests on one property: **Drizzle's table argument is always an imported identifier with a statically known declaration site.**

```
For each write() body (ts-morph over the program):
  1. Find CallExpressions where callee.name ∈ {insert, update, delete}
     AND receiver's TYPE originates in drizzle-orm        ← type identity, not variable names;
                                                            renames/destructuring irrelevant
  2. Resolve argument 0:
     A. imported identifier        → follow symbol → pgTable declaration   (90%+)
     B. namespace/re-export chains → getAliasedSymbol loop
     C. alias(T, …)                → recurse on T
     D. conditional initializer    → union both branches (over-approximation is safe:
                                     missing = bug, excess = warning)
     E. runtime-flowing value      → 'unresolved' → KV406 (error: manual touches REQUIRED;
                                     dev/build/export gate blocks until supplied — §10.3)
  3. Interprocedural: helpers receiving a Drizzle-typed value are summarized bottom-up
     (memoized fixpoint); calls into node_modules with a db arg → KV406 (error, same gate).
     `update…from(R)` / `insert…select` contribute R to the READ set, not touches.
     Opaque/raw query projections (KV410, §10.2) contribute their declared `reads:`
     table set to the READ set; a `reads:` entry naming an `exempt` table is KV411.
  4. Parameterized keys: extract eq(T.keyCol, expr) from .where(); expr traceable to a
     write param ⇒ key derivation recorded; ranges/IN ⇒ table-level (KV409 notice).
  5. Whenever a write site's touch set is not fully statically resolved (any 'unresolved'
     table at step 2.E, any node_modules db call at step 3, or any raw-SQL statement whose
     mutated tables cannot be read off the AST), it is **KV406 (error)** absent a manual
     `touches`/`tables` declaration, and an unexecuted conditional write on such a site is
     **KV405 (error, CI-gating)** — see §11.2. KV405 is no longer advisory: a write site
     whose touch set is not fully statically resolved and whose branches were not all
     observed under instrumentation blocks build and static export, because the runtime
     cross-check (§11.2) cannot have proven the unexecuted arm's touch set sound.
```

Output is **reproducible on demand** through `kovo emit` / `kovo explain` and mechanically proven
by fixpoint plus render-equivalence gates. The emitted graph is also the runtime authority for
derived query reads and mutation touches; manual `reads` / `touches` are checked overrides for
opaque sites, not the default authoring model. Invalidation-graph changes are inspected through
those commands and CI evidence, not by committing app-local generated files:

```ts
// emitted generated/touch-graph.ts — DO NOT EDIT
export const touchGraph = {
  'cart.addItem': {
    touches: [
      { domain: 'cart', via: 'cart_items', site: 'cart.domain.ts:8', keys: null },
      { domain: 'product', via: 'products', site: 'cart.domain.ts:12', keys: 'arg:productId' },
    ],
    unresolved: [],
  },
} as const;
```

### 11.2 Runtime verification (independent cross-check)

Dev server and the test harness wrap `db`; every executed statement is parsed by the configured dialect path (Postgres uses `pgsql-ast-parser`; SQLite normalizes `?` placeholders before the same structural walk) and checked. Static over-approximates (all branches); runtime under-approximates (executed branches). **Invariant: `observed ⊆ static ∪ KV406-annotated`** — violation means analyzer bug or smuggled SQL; either is a CI failure. For raw-SQL writes this invariant is enforced structurally: the executor parses each statement with the configured dialect path and checks its mutated-table set against the write's declared `tables:` allowlist (§10.3). A statement that mutates a table outside `tables:` is a CI failure under instrumentation and, in production where instrumentation is absent, fails closed — the executor conservatively invalidates every domain in the write's `touches` and records the violation, never silently dropping the unexpected table's invalidation.

For managed SQL handles, runtime verification is over a framework-owned statement artifact, not the
caller-owned JavaScript object. The first managed boundary MUST snapshot every accepted carrier
(`sql` template calls, Drizzle SQL, separated `{ text, values }`, prepared statements, trusted SQL)
into an immutable statement value containing the exact SQL text, parameters, dialect, and provenance
that the framework validates. Validation, table/function classification, diagnostics,
instrumentation, and driver execution MUST consume that same immutable artifact. A mutable object,
getter-backed carrier, proxy, or object identity reused across calls cannot present one statement to
the verifier and a different statement to the driver; Kovo must either reject it or make a one-time
snapshot before any check. Passing the original carrier to the driver after validating a snapshot is
a verification bug.

On the Postgres/PGlite managed path, the engine also enforces the dangerous write-scope cases below the framework declared-write wrapper. Owner and owner-via tables are granted to the writer role only with row-level security and `WITH CHECK` policies that bind writes to the current principal, so cross-owner writes and ownership reassignment are denied by the database. Unclassified/reference tables are not granted to the writer role at all, so attempts to mutate tables such as `verification` fail with engine permission denial even if an app smuggles a raw statement past the declared-write wrapper. The framework declared-write wrapper remains load-bearing for coverage and invalidation: over-declaring among writable owner/authz-policy tables can still produce stale or excessive invalidation behavior and is a KV406 contract violation, but it is not the confidentiality/integrity boundary for cross-owner or unclassified-table writes on this engine path. Full per-mutation engine roles remain outside v1.

Because instrumentation under-approximates (executed branches only), passing dev/test runs do **not** establish KV406 completeness; an unexercised raw-SQL arm is proven sound only by its statically-declared `tables:`/`touches`, which is why those declarations are KV406-`error` (not advisory) and an unexecuted such branch is KV405-`error` (§11.1). Read-side gets identical treatment (query loaders' SELECT/JOIN tables vs. derived read sets, **and observed result shapes vs. declared/inferred types — the runtime half of KV410**, so an opaque projection's schema claim is tested against what the database actually returns; an opaque projection that reads a table absent from its declared `reads:` set (§10.2) is a CI failure on the same `observed ⊆ static ∪ declared` invariant, but the static `reads:` declaration — not this dev/test-only observation — is what proves an unexercised branch sound). An observed read of an `exempt` table is the runtime half of **KV411** (§10.1) — the same CI failure whether the exempt read was statically visible or smuggled through raw SQL.

**Security-decision event completeness (normative).** The generated production runtime has one
closed, build-checked answerability denominator: the canonical `auth`, `authorization`,
`declassification`, `egress`, `storage`, `task`, and `replay` decision chokes. Every enrolled choke
MUST route both allow and deny outcomes through the single `securityEvent()` journal and emit
exactly these no-payload facts: the door, an outcome, a build-stable decision-site identity, an
honest principal scope including its epoch (or an explicit unresolved reason when the epoch or
principal is unavailable), and an opaque resource scope consisting only of its registered kind and
`global` or a framework-produced SHA-256 identity. Raw credentials, URLs, keys, rows, secret values,
task arguments, replay tokens, and other payload data MUST NOT enter the record.

The reviewed decision-site census and production markers are a closed emission-coverage recorder.
The root gate MUST fail when a door lacks exactly one enrolled site, a marker or site exists without
the other, an enrolled constructor disappears, a constructor bypasses `securityEvent()` (or the
journal-free core-to-server transport that immediately feeds it), a required fact becomes optional,
an allow/deny branch disappears, or an extra field is added. The core transport MUST NOT own a
second journal, buffer, export surface, or verdict; generated registration installs it before
authored app evaluation and the server journal remains the only event authority.

This completeness claim begins when the compiler-generated runtime-posture registry evaluates.
Production artifact emission MUST refuse a runtime entry that can evaluate the authored app before
that registration. Direct low-level library calls and unit calls made before registration are
explicitly outside the claim. Registration arms decision recording only after any configured
deployment journal is installed; after arming, an enrolled decision with no journal MUST fail closed
before proceeding. This is emission completeness for the seven named chokes, not a claim about
arbitrary app or third-party decisions, host compromise, fleet-wide delivery, or infinite retention.
The bounded journal's dropped count and every unresolved principal scope remain explicit
`unanswerable` outcomes for retrospective tooling; absence of a matching retained event is only
`not-observed`, never a no-impact proof.

**C9 sink-proof inventory (normative).** The verification surface MUST keep a single reviewed
inventory for the required boundary-crossing sinks named in §10.3 C9. Each row names: the sink, its
mechanism (`reconstruct`, `box`, or framework-`own`), the sole door, at least one lint/check/build
proof, at least one hostile-value test file or command, and the stable owner responsible for a gap.
The machine gate MUST compare its covered-family union with the complete source/sink census and
fail on a missing or unknown family, duplicate sink row, missing owner, absent root proof command,
or stale evidence path. The inventory is a proof index, not a runtime policy source: if a sink
exists without an inventory row, or a row has no hostile-value evidence, the verification surface
is incomplete even if the implementation happens to be sound.
It MUST also compare the exact runtime `securityOperationKinds` union with every row's
`operationKinds`, requiring one and only one C9 owner per finite compiler operation and rejecting
missing, unknown, or duplicate kinds. Terminal-effect rows name their real sink owner; the
`server.handler.root` and `server.helper.call` control rows name capability closure and remain
explicitly non-semantic until the latter receives a Phase 2C call summary. Component graph facts and `kovo explain component` render the
compiler-derived operation rows in stable order so a review can connect authored handlers to those
owners without reading generated files.
For engine-door claims the inventory row points at the engine-closure audit; for
wire/file/derived/task/log surfaces it points at the single framework-owned choke or box, never at a
proxy-only wrapper. The `data.derived.persistence` family is discharged by the storage-operation row
only when its proof evidence includes both KV452 provenance closure and runtime reconstruction of the
complete request-principal `ScopedKey` namespace.

Every source/sink census row MUST also declare one closed residency posture:
`none`, `db-owner`, `ledger`, `adapter-enumerable`, or `unerasable:<reason>`. A family that combines
multiple runtime sinks takes the least erasable posture of any covered sink; it cannot hide retained
task, replay, client, adapter, or external-recipient state behind a transient member. The C9 inventory
gate MUST fail on a missing or unknown posture, an empty or malformed `unerasable` reason, or an
`unerasable` count of zero. `kovo explain sources-sinks`, `kovo check sources-sinks`, and the
versioned inventory artifact MUST publish each row's posture, and the text summaries MUST publish the
current `unerasable` count. This count is an honesty metric and erasure-work denominator, not a claim
that the enumerable postures already implement principal erasure.

**Finite provenance relation (normative proof boundary).** The compiler MUST publish the current
server/browser provenance vocabularies and the complete server member-projection relation as the
versioned, diffable `security-provenance-relation/v1.json` artifact. The current denominator is 43
server states (including explicit `derived-dataset`, derived query/upsert call, `governed-data`, and `scoped-key-call` states),
20 browser states, and the quotient member alphabet recorded in that artifact.
`check:provenance-closure` MUST fail when either operation
vocabulary gains a state without a relation row, when any table cell differs from the scanner, or
when least-fixpoint reachability finds an operation without its C9 door owner. Unknown future states
default to authority-bearing; `unknown-authority` closes under exactly the declared
`SecuritySemanticClosedReason` domain.

This decidability claim is deliberately narrow. Five `serverExpressionProvenance` arms are
compositional over child provenance values; identifier lookup is an environment leaf, the implicit
object-protocol check remains syntax-dependent, and the four fallthrough subtree searches (foreign
executable, governed data, unsafe wire data, and authority) are one named nondeterministic oracle
edge with outcomes `local`, `foreign-executable`, `governed-data`, `unsafe-wire-data`, and
`unknown-authority`. The table does not decide general JavaScript, dynamic properties, Proxy
behavior, imported executable semantics, or the browser classifier's syntax-dependent transfers.
The artifact publishes those exclusions, the four semantic-analysis resource bounds, and the
remaining extraction gaps beside the relation so a finite proof cannot be mistaken for a whole-
JavaScript soundness claim.

**Differential analyzer-soundness oracle (normative evidence boundary).** The server semantic
analyzer MUST consume the versioned `kovo-security-abstract-interpreter-census/v1` lattice,
resource bounds, and transfer vocabulary. `check:analyzer-soundness-oracle` MUST bind every census
transfer to a production marker and one seeded generator production, fail on an uncensused
production transfer, compile a canonical program that actually reaches every transfer marker, and
behavior-check every declared lattice element through the production transfer functions against
independent expected results. Intended closed and resource-edge programs MUST produce their exact
declared closed reason. The generated language is the finite
`kovo-security-analyzer-language/v1` grammar recorded beside the census; its declared JavaScript
exclusions and generation bounds are part of the claim, not implementation notes.

For every accepted generated program, an independent concrete interpreter predicts the reviewed
effect-door calls without consuming the analyzer's provenance relation. The compiler-emitted server
module is then executed with explicit framework-door stubs (never general `Proxy` observation), and
the oracle requires both concrete/emitted agreement and `observed ⊆ abstract-predicted`. A
counterexample MUST be minimized and persisted as `kovo.security-fuzz-counterexample/v1`; a scoped
canary that weakens the production `effect.invoke` transfer and a canary that deletes one effect
observation MUST fail. A persisted artifact MUST remain unconfirmed and MUST NOT claim
`replayVerified: true` or an unsafe verdict until a program-specific replay reloads the serialized
seed, minimized program, and canary (empty for an organic finding), recompiles that program, and
reproduces the exact disagreement. The fixed-seed
`analyzer-soundness` family runs in the nightly security campaign. Passing this falsification search
is evidence for only the declared finite language. It does not prove soundness for general
JavaScript, imported executable semantics, dynamic properties, browser provenance, asynchronous
scheduling, implicit protocols beyond the explicit object-literal close production, or behavior
beyond the published resource bounds.

**Async-context non-interference (normative evidence boundary).** Runtime scheduling evidence is
separate from the finite abstract-interpreter claim above. `check:async-context-confinement` MUST
derive every deployable authority cell from `kovo.async-context-confinement/v1`, reject a raw or
uncensused `AsyncLocalStorage` door, and mutation-check the exact lifecycle, close, isolated-root,
and verifier-observer revocation obligations. Its seeded runtime oracle MUST exercise distinct
principals concurrently through microtasks, stream backpressure, and thenable callbacks and observe
no cross-lifecycle cell value. This proves the shared runtime contract over those interleavings; it
does not mean the abstract interpreter models arbitrary event-loop scheduling. Only a separately
declared finite check→await→use production may enter the analyzer oracle's generated language.

### 11.4 The verification surface (the Keppo contract)

For a Kovo app, the following are checkable **without executing a browser**:

1. TypeScript static checking — all wiring (handlers, routes & links, forms, targets, bindings, IDREFs, transforms, guards).
2. `kovo check` — TypeScript plus a compiler/security graph derived from the current app source,
   followed by touch-graph consistency, optimistic exhaustiveness (KV310), update coverage (KV311),
   fixpoint + render-equivalence invariants, capability closure (KV448), and unguarded and unscoped
   audits.
3. Graph queries over `kovo explain` output — intent-level assertions ("every component displaying cart data is refreshed by cart/add") as set operations over printed, stable-format graphs, including each component's finite operation rows and the `capabilities` root/door/package/closed provenance ledger from §6.6.
4. Property suite — prediction ⊆ eventual-truth generative tests over hand-written transforms and derivation soundness (commuting diagrams).
5. HTTP-level integration tests — mutations as request/response assertions against pglite (real Postgres semantics, in-memory, no container).

**Source-proof and deployment-proof split (normative).** Bare `kovo check` derives the app from
`./src/app.tsx`; `kovo check source [app-module]` selects another authored entry. Both regenerate
TypeScript and compiler/security facts from that current source before emitting the stable
`kovo-check/v1` result. They MUST NOT require, infer, or fabricate a deployment preset, emitted
artifact, least-privilege deployment posture, or §14 retention declaration, and they MUST NOT write
deploy artifacts. A graph-consuming compatibility form such as `kovo check coverage [graph.json]`
MUST fail non-zero when neither its explicit graph nor a conventional graph exists; absence is
never converted into an empty passing verifier input.

`kovo build` reruns the same current-source proof, then additionally verifies the selected preset,
artifact, least-privilege, and deploy-skew/retention obligations before promoting deploy output.
Those deployment obligations remain fail-closed, including KV417 under §14. A passing source check
therefore means “current authored source satisfies the source verifier,” not “this deployment is
ready.”

**Foreground incremental source proof (normative).** `kovo check source [app-module] --watch
--format json` keeps one project-confined foreground process open and emits one versioned JSONL
record for each serialized source revision. It is not a global daemon. Its queue is bounded to the
currently executing revision plus the latest pending filesystem state; intermediate edit bursts may
coalesce, but checked revisions never overlap or publish out of order. Delete, rename, symlink, or
import/config-closure ambiguity fails closed instead of retaining the previous passing result.

One-shot and watch forms MUST execute the same ordered source-proof pipeline. A watch revision still
performs fresh command-owned app evaluation, recomputes the security graph, verifier fixpoint,
render-equivalence proof, and diagnostics, and emits the exact `kovo-diagnostic/v1` command result a
fresh one-shot invocation would emit for the same bytes. Reuse is limited to compiler-owned facts
whose authenticated cache key binds every source, config, package-closure, and external-version
input; app objects, runtime authority, diagnostics, and partially assembled graphs are never cached.
The JSONL record binds exact source/config/closure digests and carries the complete ordered phase
census. Census v2 distinguishes `executed` from `reused-authenticated`, names the input digest for
every phase, and retains every phase after a finding as `not-reached`; it may not omit a phase to
claim a latency win. Rejected filesystem states carry an explicit rejected proof record and never
invent a digest for missing or ambiguous bytes.

**Command result and diagnostic protocol (normative).** `kovo check`, `kovo build`,
`kovo explain`, `kovo doctor`, and `kovo verify` accept exactly
`--format human | json | github` and retain the exit classes declared by the semantic command AST:
success is 0, proof/build findings are 1, and invocation/configuration errors are 2. Every finding
crosses the framework-owned `kovo-diagnostic/v1` record before presentation. Code, severity, help,
and source range are producer-owned facts; renderers may escape or lay them out but may not parse
prose, consult a second severity table, or manufacture a location. JSON carries the diagnostic
envelope plus the command's existing versioned result protocol and exact result text. GitHub output
emits escaped workflow annotations from the same records and preserves the same result facts.
`kovo-check/v1` and `kovo-explain/v1` therefore remain byte-for-byte payloads inside the common
envelope rather than being silently replaced by an empty diagnostic array.

The semantic command AST is the sole source for argv parsing, semantic request types, root and
subcommand help, shell completion, and command-reference data. It includes aliases, argument kind,
enum, default, repeatability, category, examples, exit behavior, and result protocol. Programmatic
callers consume its discriminated request union and never an argv-shaped bag of flags.

**Versioned starter policy (normative).** The starter owns a compact declarative
`kovo.policy.json`; versioned CLI implementations own lifecycle allowlist validation, sound-subset
analysis, endpoint-posture orchestration, and fail-closed parallel scheduling. Generated apps do
not copy those algorithms. The default `kovo check` runs source proof and the applicable declared
policies. Deployment endpoint probes run through `kovo verify --artifact <dist>`, after a successful
build. App package scripts name Kovo, Vitest, and the package manager only; Vite Plus may remain a
framework/CI implementation tool but is not app-facing vocabulary.

**Daily coherence and copy-in commands (normative).** `kovo doctor [root]` reads bounded local
configuration and package facts without evaluating authored modules or contacting the network. It
checks the required Node and pnpm versions, duplicate Kovo installations, Kovo peer ranges,
config/preset selection, origin posture, database-role posture, migrations, deploy-skew retention,
writable framework paths, and cache freshness. Findings are finite producer-owned
`kovo-diagnostic/v1` records. `--fix` is restricted to framework-classified derived state: it may
create the project-owned `.kovo` directory or remove a stale real `.kovo/cache` directory after
containment and non-symlink checks; it does not rewrite security posture, credentials, package
versions, migrations, or authored source.

`kovo add --list` is the exact copy-in registry. A component typo suggestion is derived from that
same registry rather than a second alias table. `kovo add ... --dry-run` performs no filesystem or
process writes. `--install=never` copies source and reports the dependency follow-up without
editing the manifest; `--install=auto` stages component files, atomically updates the captured
manifest, captures the package-manager lockfile, runs the declared package manager, and promotes
source only after install succeeds. An install or promotion failure restores staged
manifest/lockfile/source edits and reports completed, planned, rolled-back, and possible
package-manager-owned `node_modules` work distinctly. `kovo test` is the app-facing, schema-owned
one-shot Vitest command; the CLI may delegate to its pinned Vite Plus implementation dependency,
but generated app scripts and help do not expose that implementation command.

**Safe cost-to-green rewrites (normative).** `kovo fix` accepts exactly one regular, non-symlink
app-authored `.tsx`/`.jsx` file inside the invocation root, excluding `.kovo`, `dist`, `generated`,
and `node_modules` trees. It MUST NOT synthesize a trust wrapper, justification, allowlist entry, or
other escape. A rewrite is available only through this closed compiler-owned recipe set:

- KV223 may remove one exact `data-bind` JSX attribute only when the genuine compiler reports that
  the attribute is redundant with its typed child expression. Because hand-authored lowered IR is
  already rejected by KV235 and can suppress compiler-owned escaping, this is a security-hardening
  rewrite, not a claim that the invalid input had accepted behavior to preserve.
- KV232 may remove one exact author-owned `role`, `aria-*`, or `data-state` override only when the
  rewritten compiler output has the exact same semantic behavior fingerprint.

The independent post-rewrite pass MUST prove that the candidate differs only by the approved typed
AST nodes, that every target obligation is absent, and that the complete genuine compiler analysis
is green before a write. Unknown diagnostics, mixed proof classes, overlapping edits, stale source,
an analyzer residue, or a changed behavior fingerprint where equality is required MUST fail closed
without returning candidate source. `--check` is read-only and non-zero when a safe rewrite is
available.

`kovo fix --cost-report` emits `kovo.cost-to-green/v1` over the versioned agent-authored corpus. Its
per-diagnostic metric is `safe AST-node edit atoms − escape argv atoms`, where deleting one typed
AST node costs one atom and `--allow-diagnostic CODE` costs two. A missing safe recipe has unbounded
safe cost. Every row where escape is cheaper, including an unbounded safe cost, MUST be reported as
a framework defect with a non-empty owner; the report exits non-zero while any such row exists. This
is an ergonomics and routing measurement, not evidence that an escape is safe or should be chosen.

**Deployment assume-guarantee contract (normative).** Every current `SECURITY.md` guarantee MUST
carry a machine-readable `antecedents` list. That list is derived, never independently authored:
the versioned `kovo.deployment-environment-doors/v1` registry binds each environment fact to the
exact framework door that consumes it, the door's source anchors, and the affected published or
normative conditional guarantee IDs. The security-guarantee gate MUST reject an unknown fact or
guarantee, a missing consumer anchor, a current guarantee absent from the registry, or any
`SECURITY.md` antecedent list that differs from the door-derived relation. A prose assumption or an
operator-authored success verdict is not evidence.

`kovo check env [deployment.json]` consumes `kovo.deployment-environment/v1`, probes only facts
observable from its pinned command-entry environment, and prints every remaining fact as a
`RETAINED` obligation with the exact guarantees it suspends. A canonical `KOVO_NODE_ORIGIN`
discharges only the zero-forwarded-hop proxy-chain fact; it does not authenticate the TLS
terminator. `KOVO_NODE_TRUSTED_PROXY=1` retains the edge-identity/hop obligation, a host preload
cannot be disproved from an absent `NODE_OPTIONS`, and database writers, shared-cache behavior,
registrable-domain occupancy, and TLS edge identity remain retained unless a future framework-owned
probe owns corresponding evidence. Contradicted, retained, or posture-withheld antecedents produce
a non-zero result. The command reports conditional status; it is not a deployment-integrity proof.

The composition domain has exactly three input shapes. `single-kovo` retains external occupancy
facts. `shared-registrable-domain` requires at least two unique canonical HTTPS Kovo origins under
one explicitly declared DNS suffix and contradicts `sole-registrable-domain-occupant`; the command
therefore withholds the CSRF principal-binding claim because another app can compete for browser
cookie namespace. This declaration is not a Public Suffix List proof. `foreign-host` is accepted
only with posture `mounted` and one canonical non-root mount path made of non-empty RFC 3986
unreserved segments, excluding `.` and `..`. Mounted posture unconditionally
withholds the host-owned CSRF, request-origin, and browser-state-cache claims; an author cannot turn
them back on with a flag or asserted verdict. All other composition/posture pairings fail input
validation.

**Authenticated advisory contract (normative).** `kovo.security.advisory/v1` is an exact,
closed record containing `id`, one of `low | moderate | high | critical`, one finite
`affectedRange`, `fixedIn`, `retracts[]`, `tcbChokes[]`, and `graphSchemaVersion`. The only range
grammar is `>=VERSION <VERSION` over strict SemVer, with an increasing exclusive upper bound and a
`fixedIn` version at or above it. Applicability predicates, package-name selectors, executable
expressions, host facts, and arbitrary extension fields are forbidden. `retracts[]` names exact
guarantee IDs in the normative `SECURITY.md` register; `tcbChokes[]` names exact current TCB entry
IDs. The canonical `kovo.security.advisory-feed/v1` record contains a positive monotone `epoch`, a
canonical `issuedAt`, `maxFeedAgeSeconds`, and an ID-sorted advisory array. The repository gate MUST
keep the feed schema exact, fresh within a maximum 90-day release window, and equal in advisory IDs
and retraction sets to the public guarantee register; unknown guarantee or TCB IDs fail the gate.
It MUST compare the checked-in feed to the first-parent feed: a lower epoch, or any canonical feed
change without an epoch increase, fails before release signing.

`kovo check advisories [graph.json]` MUST first read build-owned
`kovo.artifact.provenance/v1` from the graph and obtain every exact `@kovojs/*` package version plus
the graph schema version. When no graph path is explicit, exactly one conventional graph artifact
MUST exist; multiple candidates produce UNKNOWN instead of a precedence-based choice. The command
then fetches the feed over HTTPS (or reads an explicit in-root regular
file for an offline drill), computes its SHA-256 digest, and obtains an attestation by that digest.
The accepted bundle MUST pass Sigstore signature and Fulcio-chain verification, the GitHub Actions
OIDC issuer, the exact `.github/workflows/release.yml@refs/heads/main` certificate identity, and at
least one certificate-transparency and one transparency-log check. Kovo MUST independently parse
the verified DSSE payload and require exactly one matching feed digest plus the exact Kovo main
release-workflow repository, ref, and path. The release job producing this attestation has only
checkout/read and attestation OIDC authority: it performs no dependency installation, repository
script, build, or long-lived private-key operation. The Fulcio certificate identity is workflow-
wide rather than job-ID evidence. Default remote verification therefore also depends on the
digest-indexed Kovo repository attestation API, while the workflow grants `attestations: write` only
to the exact two-action attestation job; the package-publish job's npm OIDC authority cannot attach
repository attestations. An explicit local bundle is caller-supplied offline evidence and retains
only the cryptographically checked workflow-level identity.

After authentication, the command rejects a feed future-dated by more than five minutes, stale
beyond its own bounded `maxFeedAgeSeconds`, below the highest locally accepted epoch, or different
from a previously accepted digest at the same epoch. It persists only
`kovo.security.advisory-state/v1` (`highestEpoch`, `feedDigest`) by an atomic regular-file write
inside the invocation root. Concurrent processes MUST serialize on an exclusive sidecar lock,
re-read and compare state while holding that lock, fsync the state file, atomically rename it, and
fsync the parent directory where the platform exposes durable directory handles. A corrupt state
file, busy lock, symlink target, symlinked parent component, or unwritable state is failure, not
permission to forget rollback history. An advisory matches when
its graph schema equals the artifact schema and at least one exact Kovo package version lies in its
range. There are only three verdict classes:

- `AFFECTED`: print every matching advisory; exit 1 when any match is at or above the configured
  severity floor, otherwise exit 0 while retaining the AFFECTED label.
- `NOT-AFFECTED`: exit 0 only after the complete authentication, freshness, rollback, and matching
  sequence succeeds with no match.
- `UNKNOWN`: exit 2 for every inability to read the artifact, fetch, parse, authenticate, freshness-
  check, rollback-check, or persist state. UNKNOWN is never treated as an empty advisory set.

Every verdict MUST print a non-claim: this command detects authenticated advisories Kovo has
published for the artifact posture; even NOT-AFFECTED is not proof that the artifact has no
vulnerability or no impact outside the feed's version-and-schema scope.

`kovo explain attest` is the deployment-review composition surface. It first recomputes the
reviewed graph's artifact subject and posture digest. If the graph contains a `trustedAssign`
capability, `--escape-reviews <reviews.json>` is mandatory. The detached file has schema
`kovo.escape-obligation-reviews/v1` and contains one exact signed envelope per graph-derived
`kovo.escape-obligation-review/v1` subject; missing, duplicate, surplus, malformed, stale-artifact,
replacement-key, wrong-anchor, and invalid-signature rows all fail closed. The same out-of-band
fingerprint MUST verify both the escape envelopes and the nonce-bound live deployment response.
The build also emits `.kovo/escape-census-review-subjects.json`, schema
`kovo.escape-census-review-subjects/v1`: one unsigned subject for every exact
`(artifactSubject, door, root)` counted by Metric E, with the complete canonically sorted set of
producer sites collapsed into that root. Each site is the exact record
`{ encoding: "utf16le", file, sourceHash, sourceLength, sliceHash, span: { start, end } }`.
`file` is a canonical invocation-root-relative POSIX path; lengths and spans are JavaScript UTF-16
code units; `sourceHash` hashes the full UTF-16LE source; and `sliceHash` hashes exactly
`source.slice(start, end)` as UTF-16LE. Absolute paths, backslashes, empty/`.`/`..` components,
control characters, and bidirectional controls are invalid. Every source-root door other than
`csrf:false` and `ctx.fetch` has exactly one site and its root is `file:start:end`; the two
relation-root doors retain every canonically sorted unique producer site. Metric E series v3
re-reads each site as a regular blob from the retained `codeSubjectSha` and rejects any source,
length, span, or slice mismatch before accepting review evidence. When this set is non-empty,
`--escape-census-reviews <reviews.json>` is mandatory. Its
`kovo.escape-census-reviews/v1` file MUST contain exactly one valid, domain-separated
`kovo.escape-census-review/v1` envelope per emitted subject under that same trust anchor. A missing,
duplicate, surplus, malformed, stale-artifact, wrong-anchor, or invalid-signature envelope fails the
whole set; partial signature coverage never reduces the unsigned count. Build, app code, and the
app-facing/internal execution graph expose subject construction and verification but no signer.
Success reports both verified review counts and explicitly states the non-claim: a signature records
only that the pinned key holder approved those exact bytes; it does not prove an obligation true,
identify an independent human, or prove executed-code/host integrity. The two build-emitted subject
files are unsigned reviewer inputs, not approval evidence.

The canonical v3 series MAY contain zero rounds. In that state both `series.comparability` and
`series.reviewAnchor` MUST be `null`: `PENDING 0/3` carries neither a source-comparability claim nor
review evidence, so initialization does not stamp the repository's source hashes. The first
authenticated append MUST create one validated document that adds the round and locks both the full
computed comparability record and the externally pinned review anchor. A partial or pre-seeded empty
state, or a nonempty state with either lock null or mismatched, fails closed. Later rounds MUST retain
both locks unchanged. Every nonempty series MUST be verified against an exact external policy
artifact that pins the already-existing `kovo-runtime-posture-attestation/v1` fingerprint.
`node scripts/metric-e-rounds-gate.mjs --init` is the sole initializer and MUST refuse to overwrite
a nonempty or non-v3 ledger.
The verifier MUST derive authority from that supplied policy, never from `series.reviewAnchor`, a
round, or a caller-provided fingerprint; coherently replacing repository evidence and its embedded
anchor therefore still fails against the external pin. In addition to the exact root set, each
round retains one detached aggregate `kovo.metric-e-independent-review/v3` envelope under the same
Ed25519 key. Its canonical payload binds the exact code subject, round number and calendar date,
report and ceiling digests, signed root-set path/digest/anchor, reviewer identity and UTC review
time, explicit `accept` verdict, and closed assertions that build, review, and signing-key custody
were outside the build/coding-agent environment. Missing, duplicate/reused, malformed, surplus,
stale-subject, replacement-key, wrong-anchor, and invalid-signature aggregate evidence fails closed.
Reusing an identical aggregate or signed root-set digest under another path or round is not a new
independent review and MUST fail closed.
Only then are verified root signatures counted as reviewed and subtracted from unsigned escapes.
The signature authenticates the pinned key holder and those exact bytes; it does not prove the
asserted custody or human independence true, identify the reviewer, or establish review correctness.

`kovo explain endpoints` is the stable machine-ingress audit. Its diffable table lists every declared endpoint and webhook, every `mutation()`, plus every route that returns `respond.file()`/`respond.stream()`: source-derived registry identity where applicable, method, path, mount mode, auth scheme (`session+guard`, `verifier:<resolved scheme>`, `custom:<name>`, or `none:<justification>`), CSRF/effect posture, and for webhooks the write→domain chain. Endpoint posture is `safe:read-only` for the closed `GET`/`HEAD`/`OPTIONS` set from §9.1, `checked` when an unsafe method receives the default synchronizer-token check, or `exempt:<justification>` when an unsafe endpoint explicitly opts out. Mutation posture remains `checked` or `exempt:<justification>`; a `csrf: false` mutation appears here with the latter posture, and KV418 (§6.6) guarantees it references no ambient session. The pre-dispatch coarse limiter posture (§9.5) is enrolled and printed here too. The command is snapshot-locked with the rest of P8 output so security review can answer "what can reach this app, and what can it touch?" without executing a browser.

Browser tests are a first-class part of the **framework's** own suite: morph runs on every mutation response, and its survival contract (focus, caret, scroll, transitions) plus L0 platform behaviors are irreducibly browser-bound. The reconciliation suite splits accordingly: a browser-free structural property suite (`morph(a, b) ≡ b` with keyed-node identity preserved — runs in jsdom-class DOM), and a named browser suite for the survival contract. The claim is bounded: **application wiring is proof-carrying**, so apps need few or no browser tests of their own — most SPA testing exists to compensate for unverifiable wiring, and Kovo removes that category, not testing itself.

---

---

<!-- Source: spec/12-testing.md -->

# Testing API (SPEC §12)

This file is incorporated by reference from [../SPEC.md](../SPEC.md) and is normative for Kovo framework behavior.
The root spec remains the entry point and cross-reference index; this module owns the detailed contract below.

## 12. Testing API

The testing surface mirrors the framework proof surface. Mutations execute as functions with
touch-checking enabled, pages render to inspectable HTML without a browser, typed error paths expose
the declared error union, and generated optimistic transforms have property tests for
`patch(shape(s), input) ≡ shape(apply(effect, s, input))`. Handlers unit-test as `(event, ctx)`
functions; transforms as pure `(data, input)` functions; the wire as HTTP.

API examples and integration harness guidance live in `docs/integration-testing.md` and
`site/content/guides/testing.md`.

An app-scoped test harness accepts the opaque `KovoApp` plus a digest-verified compiler proof graph.
Compile-time types flow from the imported contract and declaration handles: query input/result,
mutation input/result/error union, route params/search, request/session/DB/env, and task/endpoint
types. The graph supplies runtime identities and proof facts; the token and TypeScript types do not
substitute for it. A stale, partial, failed-build, digest-mismatched, or wrong-app graph fails before
one handler runs. Public app tests do not inspect the token, call private aggregate constructors, or
mock framework internals.

Direct query and mutation tests run their declaration handles in the assertion process. Page and raw
request assertions exercise HTTP against an explicit, separately bootstrapped app origin; they do
not start an app request handler inside Vitest's mutable realm. The harness rejects a missing origin
or a request whose origin differs from that configured base URL. This preserves the §6.6
runtime-bootstrap boundary while keeping page assertions browser-free.

---

---

<!-- Source: spec/14-deploy-skew.md -->

# Deploy Skew & Version Recovery (SPEC §14)

This file is incorporated by reference from [../SPEC.md](../SPEC.md) and is normative for Kovo framework behavior.
The root spec remains the entry point and cross-reference index; this module owns the detailed contract below.

## 14. Deploy Skew & Version Recovery

A long-open tab, a stale prerender, or a cached document may outlive the build it was produced by. Kovo makes this **loud and recoverable** rather than silently wrong (§9.1.1): a payload whose app build token (§5.2.1) does not match the receiver is never merged.

**Recovery contract (normative).** On a token mismatch the client MUST NOT apply the delta, the `/_q/` read, or the fragment merge. It instead refetches the full value over the typed read endpoint (`/_q/<key>`, §9.4). If the refetch itself returns a token that still differs from the document token, the document is fundamentally skewed: the client performs a full navigation reload of the current route so the document, its modules, and its query bases are all reissued against one build. Optimistic state on a discarded delta is reconciled or rolled back per §10.4; recovery never promotes an unconfirmed prediction. Recovery is idempotent and side-effect-free: it issues GETs and, at most, one reload.

**Build-bound request routing (normative).** Every enhanced `/_q/` read and every enhanced mutation
or HMR request MUST carry the immutable document app build token in `Kovo-Build`. Mutation dispatch
recognizes enhanced traffic from the framework fragment, target, live-target, form-target,
idempotency, and streaming carriers; a matching build header alone does not classify a native form
as enhanced. A serving layer may route build-bound traffic only to the exact retained app/decoder
identified by the token. Once dispatched to one app build, a missing or unequal token is rejected
before query-key decoding, either target-header decoder, query/component selection, or mutation
handler work. That typed 409 carries the current app build token, the framework-reserved
`Kovo-Build-Skew: true` marker, and the inline fragment envelope. The marker has meaning only on
that admitted 409; an unmarked application 409 is an ordinary typed conflict. App response channels
cannot mint or override the marker. Equality proves compatibility only, never authentication. The
sole prior-token override is the explicit `oldBuild` selector on the Vite-dev-only HMR endpoint
(§9.5.1); production dispatch has no exception and no heuristic dual decoder.

**Prior-version retention window (required minimum).** The serving layer MUST retain prior immutable artifacts so a skewed document can recover without a 404. For the **supported deploy-skew window** (§6.6) — a deployment-configured duration with a normative floor of **24 hours** of wall-clock retention across redeploys, configurable upward but not below the floor — the server MUST keep resolving: (a) every emitted immutable client-module URL `/c/__v/<representation-digest>/<module>` (§9.5) and its generated-ABI imports, and (b) the `/_q/<key>` read surface for every prior in-window app build token, returning a token-tagged full value the stale document can recover from. An interaction or refetch from an in-window document MUST NOT 404 (§6.6). The current active-module manifest used to derive a new app build token is distinct from this retained resolver history: history never enters the token merely because it is still resolvable, while simultaneous active representations of one logical path remain distinct exact hrefs. Artifacts older than the window MAY be evicted; a request for an out-of-window digest/token is answered as a build-skew event that triggers the full navigation reload above, never a silent stale patch. Retention MUST survive process restart and cover every serving replica; the default in-memory store does not prove that production property. A deployment that cannot meet the retention floor MUST surface the gap; shipping a window below the floor is **KV417**.

**Proof ownership (normative).** Retention is a claim about the selected serving layer across
redeploys, not a property of authored app source. `kovo build` MUST validate that claim and fail
KV417 before emitting or promoting a deployment that does not establish it. Source-backed
`kovo check` MUST neither require the claim nor manufacture a passing retention value; its success
does not attest deployment skew recovery.

**Self-contained artifact filesystem roots (normative).** A deploy artifact MUST NOT depend on
files outside its own output to boot. A **relative** `rootedFiles()` root resolves against the
process working directory in dev and while `kovo build` evaluates the app; the build MUST stage a
snapshot of each such root into the artifact (the node preset emits `rooted/root-<encoded-spec>/`),
and the generated server MUST publish the staged directory (via `KOVO_ROOTED_FILES_DIR`, set
before the handler graph is imported) so the same relative spec resolves to its staged copy at
serve time — deterministically, regardless of launch working directory. A published staging
directory that lacks a requested relative root fails closed at capability construction; it never
falls back to the launch cwd. An **absolute** root is operator intent for a live deploy-host path
and is never staged or remapped.
