---
title: Diagnostics
description: Every KV### diagnostic the framework emits, with its severity, message, and how to fix it.
order: 1
---

# Diagnostics

Generated from `diagnosticDefinitions` in `packages/core/src/diagnostics.ts` — 92 codes (72 error, 10 warn, 9 lint, 1 notice). Do not edit by hand.

Every diagnostic Kovo emits has a stable `KV###` code. The compiler and CLI draw their messages from the one registry below, so the code you see in your terminal is the code you look up here. Each entry lists what triggered it and the fix.

## Error

Build-blocking. `kovo check` fails until you resolve it.

| Code | Message | Fix |
| --- | --- | --- |
| `KV201` | Closure captures unserializable value. | Fixes: move the value into component/query state via ctx; pass serializable element params with data-p-*; or keep shared constants in module scope.<br>Handlers may reference only state/ctx/event, data-p-* element params, named imports, and statically serializable module constants.<br>Blocked reason: captured runtime values cannot be serialized into the generated handler module boundary.<br>SPEC §4.3 and §5.2 require handler lowering to cross only explicit serializable capture channels. |
| `KV220` | Literal href or form action matches no declared route. | Would lower to: a route-checked href/action that participates in the typed route registry.<br>Blocked reason: the literal target does not match any declared canonical route path.<br>Fixes: use a typed route helper, declare the route, correct the literal path, or mark an intentional full-origin/external navigation with the external escape hatch.<br>SPEC §6.4 and §9.5 require navigation targets to stay type-checked against the route table.<br>Escape: external/full-origin URLs opt out because they are outside the app route graph. |
| `KV221` | IDREF references an id not present in component scope. | Would lower to: light-DOM IDREF wiring whose target id exists in the same component scope.<br>Blocked reason: the referenced id is absent, outside the validated scope, or hidden behind a different component boundary.<br>Fixes: add the target id in this component scope, pass a generated id through props, or correct the IDREF attribute value.<br>SPEC §4.5 and §6.4 require IDREFs such as commandfor, popovertarget, for, and aria-* to resolve at compile time. |
| `KV222` | Hand-written binding stamp disagrees with the typed expression it wraps. | Would lower to: the compiler-derived data-bind stamp for the typed JSX expression.<br>Blocked reason: a hand-written stamp names a different path than the expression it wraps, so server render and client update semantics could drift.<br>Fixes: remove the hand-written stamp and let the compiler derive it, or make the stamp path exactly match the typed expression.<br>SPEC §4.8 treats typed expressions and binding stamps as one fact and rejects drift. |
| `KV224` | Static id is duplicated in component scope or appears inside a repeatable stamp. | Blocked reason: duplicate static ids make IDREF proofs ambiguous, and static ids inside repeatable stamps can produce multiple elements with the same id.<br>Fixes: generate ids from props/kovo-key, move the id outside the repeatable subtree, or pass a unique id down to the component.<br>SPEC §4.5 requires ids to be unique by construction so KV221 IDREF validation remains meaningful. |
| `KV225` | JSX nesting violates the HTML content model. | Would lower to: HTML whose parsed DOM preserves the authored JSX tree.<br>Blocked reason: the HTML parser would re-parent or drop invalid children, changing morph identity and fragment targets after serving.<br>Fixes: use content-model-valid wrapper elements, move table rows into table/section parents, or split paragraph/block content into valid siblings.<br>SPEC §4.2 requires compiler-served HTML and parsed DOM shape to agree. |
| `KV226` | kovo-deps or kovo-c names an unknown query instance or component. | Would lower to: emitted IR stamps whose kovo-c and kovo-deps names resolve to known components and query instances.<br>Blocked reason: residual compiler stamps reference a component or query that is not present in the module/registry facts.<br>Fixes: recompile from TSX source, correct the generated stamp, or add the missing component/query fact to the compile graph.<br>SPEC §5.2 allows lowered IR only as compiler output/fixpoint input, and fixpoint validation must reject stale names. |
| `KV227` | Binding path traverses a nullable segment without ?. | Blocked reason: the binding path crosses a nullable query segment without declaring empty-on-null behavior.<br>Fixes: write the nullable traversal with ?., extract a named derive that handles null explicitly, or make the projection non-null in the query.<br>SPEC §4.8 requires empty-on-null semantics to be explicit so the server renderer and loader cannot drift. |
| `KV228` | Ambiguous route table: two routes can match the same canonical request path or duplicate route path. | Blocked reason: static-first route matching cannot choose a single canonical handler for at least one request path.<br>Fixes: remove duplicate route facts, split overlapping patterns, add a static segment, or make one route path more specific.<br>SPEC §9.5 requires route matching to be unambiguous at compile time. |
| `KV229` | Static export constraint violation. | Would lower to: an L0/L1 static export artifact whose route can be replayed through a synthetic GET with no guard-only, session-dependent, mutation-only, or unenumerated-param requirement.<br>Blocked reason: this route depends on runtime session/guard state, mutation-only interaction, or parameter paths that static export cannot prove and enumerate.<br>Fixes: remove the export target, make the route session-independent, enumerate static paths, or serve the route dynamically instead of exporting it.<br>SPEC §9.5 requires static export to fail or skip loudly when a route cannot be emitted as an L0/L1 artifact. |
| `KV230` | Fragment-target children cannot lower to a component reference. | Blocked reason: fragment responses must fully describe the DOM they produce, but these children cannot be hoisted through serializable props.<br>Fixes: pass serializable props, move browser/request/db values behind a server fragment, or render children inside the fragment target itself.<br>SPEC §4.5 requires fragment-target children to lower to component references when they cross the target boundary. |
| `KV231` | Unmergeable attribute conflict in primitive composition. | Would lower to: a single composed attribute set for primitive composition.<br>Blocked reason: both primitive and author write an attribute whose merge rule is ambiguous or unsafe, such as IDREF, data-p-*, kovo-c, or kovo-state.<br>Fixes: keep one writer, pass the value through the primitive API, or move the relationship/state ownership to one component.<br>SPEC §4.6 defines primitive attribute merge rules and treats double-wired relationships as errors. |
| `KV233` | Two writers target the same binding slot. | Would lower to: exactly one writer for each data-bind target slot.<br>Blocked reason: multiple bindings target the same text/attribute slot, so the client loader cannot choose a single update source.<br>Fixes: keep one binding, split values across distinct elements/attributes, or combine the values in a named derive before binding.<br>SPEC §4.6 and §4.8 require binding slots to have a single writer. |
| `KV234` | Package component prefix registration conflict or reservation violation. | Would lower to: package-scoped component names, CSS scopes, and behavior attributes using one effective prefix.<br>Blocked reason: the prefix is missing, invalid, duplicated, or reserves kovo-* outside @kovojs/* packages.<br>Fixes: assign a lowercase dash-terminated unique prefix, alias one package, or use kovo-* only for framework packages.<br>SPEC §6.1.1 requires app-wide unique package component prefixes. |
| `KV235` | App source hand-authors lowered IR/string-rendered components; write TSX and let the compiler emit IR. | Blocked reason: app source is hand-authoring lowered string/render IR instead of TSX.<br>Fixes: write JSX with typed expressions and let the compiler emit renderSource(), kovo-c, kovo-deps, and data-bind.<br>SPEC §5.2: TSX is the sole app-authoring surface.<br>Escape: there is no v1 suppression or ejection workflow for hand-authored lowered IR. |
| `KV236` | Unsafe output context requires an explicit trusted Kovo escape hatch. | Blocked reason: the output context can execute script, navigate unexpectedly, inject unsafe CSS, or bypass normal JSX escaping.<br>Fixes: route URLs through typed route helpers; mark intentional external links with external; keep dynamic styling to compiler-generated safe properties; or pass raw HTML only as a Kovo TrustedHtml value.<br>SPEC §1 and §5.2 require compiler output to be auditable; unsafe output contexts cannot depend on implicit browser or runtime sanitization. |
| `KV237` | Duplicate component effective wire name. | Would lower to: one derived component registry key per component across the app graph.<br>Blocked reason: duplicate derived registry keys make component identity, CSS scoping, fragment routing, and graph facts ambiguous.<br>Fixes: rename the exported component binding, or move one component so its derived module path namespace differs.<br>SPEC §4.2 and §4.8 make derived component names load-bearing for identity, scoped CSS, fragments, and graph facts; duplicate registry keys are ambiguous. |
| `KV238` | Duplicate fragment-target wire name. | Would lower to: one derived fragment-target registry key that maps to exactly one component render entry.<br>Blocked reason: duplicate fragment-target wire names make enhanced fragment patch routing ambiguous.<br>Fixes: rename the exported component binding, add stable authored key identity for repeated instances, move one component so its derived module path namespace differs, or set disableServerRefresh: true on the query-backed component that should not receive enhanced patches.<br>SPEC §4.5, §4.8, and §6.2 make fragment-target names derived registry-visible identities; duplicate keys make enhanced fragment patches ambiguous. |
| `KV239` | Duplicate static view-transition name. | Would lower to: static view-transition-name values that uniquely pair old and new DOM elements.<br>Blocked reason: duplicate static transition names leave the browser and compiler without one canonical element pair.<br>Fixes: give one static viewTransitionName a distinct value, or make the transition name dynamic only when page composition proves uniqueness.<br>SPEC §8 uses view-transition-name as a cross-document element-pair identity; duplicate static names in one rendered module or supplied registry facts are ambiguous. |
| `KV240` | Duplicate or ambiguous query-plan identity. | Would lower to: one query-shape fact per query name and one component-local alias per canonical runtime query for server render, client updates, and binding validation.<br>Blocked reason: duplicate query-shape facts or ambiguous component aliases would make graph indexing or update-plan ownership silently choose one source for multiple generated bindings.<br>Fixes: emit exactly one query-shape fact per query name, keep one component binding per canonical runtime query, or rename/split the query so every generated binding has one source of truth.<br>SPEC §4.8 query binding validation depends on one stable shape per query and one unambiguous alias-to-runtime identity per component; duplicate identities would otherwise silently last-write-wins during graph indexing or DOM update ownership. |
| `KV242` | Enhanced mutation form fields do not match mutation input schema. | Would lower to: an enhanced mutation form whose successful control names exactly match the bound mutation input schema.<br>Blocked reason: form field names are part of the mutation input contract; unknown or missing names would only fail after submit.<br>Fixes: rename the control, add the missing required control, or change the mutation input schema so the field set matches the form.<br>SPEC §6.2 and §6.3 require form control names to be statically checked against the bound mutation input schema. |
| `KV243` | Invalid stream text target. | Would lower to: data-stream-text="source:id" on a declared text source element and kovo-text target="source:id" chunks.<br>Blocked reason: streaming text targets are framework-owned source IDs, not arbitrary selectors or ambiguous DOM queries.<br>Fixes: use streamText="source:id" with a literal namespace and stable id, or remove the streaming text target.<br>SPEC §9.1 scopes &lt;kovo-text&gt; to compiler/runtime-declared data-stream-text targets and forbids arbitrary selector targeting. |
| `KV245` | TypeScript/TSX parse failed. | Would lower to: typed JSX facts before generated server, client, CSS, and registry artifacts.<br>Blocked reason: TypeScript could not parse the authored TSX, so later compiler phases would operate on a recovery tree.<br>Fixes: correct the TSX syntax at this location and re-run the compiler.<br>SPEC §5.2 requires app source to be TSX and generated artifacts to come only from parsed compiler facts. |
| `KV302` | data-bind path is not present in the declared query shape. | Would lower to: a data-bind path that the server renderer and loader can both read from the declared query/state shape.<br>Blocked reason: the path is absent from the declared shape, so a server render or client update would read undefined.<br>Fixes: correct the binding path, update the query projection/schema, or extract a named derive with declared inputs. For Drizzle-backed queries, bindings follow the statically proven projection shape; flatten or declare the projected shape you bind to instead of relying on a broader load return annotation.<br>SPEC §4.8 and §6.2 require bindings to type-check against query shapes. |
| `KV303` | Fragment target render input is not declared as query data or stamped props. | Would lower to: a fragment target that can be re-rendered from declared query data plus stamped props.<br>Blocked reason: the render input is outside those channels, so a fragment response could not reconstruct the subtree.<br>Fixes: declare the value as query data, stamp it as a serializable prop, or move the dependency inside the fragment target.<br>SPEC §4.5 requires fragment targets to be reconstructible from declared server inputs. |
| `KV304` | Reserved query name is not allowed. | Blocked reason: the query name collides with a reserved binding root such as state.<br>Fixes: rename the query instance to an app-owned root and update its bindings.<br>SPEC §4.8 reserves binding roots so query paths and island-local state paths stay unambiguous. |
| `KV312` | Time-dependent rendered position lacks a declared cadence. | Would lower to: an explicit clocks input or query refresh cadence that re-runs the time-dependent rendered position.<br>Blocked reason: the position reads wall-clock-sensitive data without a declared cadence, so rendered output can go stale without any modeled write.<br>Fixes: declare a component clocks entry, add a query .refresh({ every \| at \| until }) binding modifier, or mark the clock renderOnce when freezing the value is intentional.<br>SPEC §4.8 and §4.9 require every changing rendered fact, including time, to have declared update coverage.<br>Escape: renderOnce is the documented suppression for intentionally immutable clock output. |
| `KV313` | Optimistic transform settled with missing server truth. | Blocked reason: an applied optimistic transform settled without the server truth needed to reconcile the affected query.<br>Fixes: return the invalidated query truth in the mutation response, declare await-fragment for fragment-only reconciliation, or refetch the affected query before presenting settled data.<br>SPEC §10.4 requires missing server truth to discard or refetch the prediction instead of freezing optimistic data as authoritative. |
| `KV314` | renderOnce position reads a query invalidated by a modeled write. | Would lower to: immutable render output that never receives query update plans or fragment refresh.<br>Blocked reason: a modeled write invalidates the query read by this renderOnce position, so the immutable declaration would hide stale UI.<br>Fixes: remove renderOnce, add a data-bind/query update plan, move the position behind a fragment target, or narrow the write invalidation set.<br>SPEC §4.9 requires write -&gt; invalidated query -&gt; rendered position coverage to be checked edge by edge. |
| `KV316` | isomorphic: true on a children/slot-accepting component would drift on self-render. | Would lower to: a client self-render that morphs only the island's own positions while leaving each projected-children/named-slot region (kovo-slot="children"/kovo-slot="&lt;name&gt;") in place as a morph-stable hole.<br>Blocked reason: a client self-render has no slot/children arguments (projected content ships once in the initial HTML), so an isomorphic island that composes children or slots would re-render those regions as fresh Html and drift from the server output.<br>Fixes: lift the dynamic part above or below the slot so the slot region stays a contiguous static hole, make the children a stamped-prop-hoistable inferred fragment target (§4.5/KV230), or drop isomorphic: true and use a server fragment.<br>SPEC §4.5 and §4.8 require a children/slot-accepting isomorphic island to partition its render into self-render positions plus preserved projected-children regions.<br>Escape: a server fragment (no isomorphic: true) re-renders the whole subtree including projected children with no self-render drift risk. |
| `KV317` | Static state-bearing aria-* value contradicts the primitive's render-time state. | Would lower to: a static state-bearing ARIA attribute whose author value contradicts the primitive's render-time state.<br>Blocked reason: state aria-* (aria-expanded/selected/checked/pressed/current, state-driven aria-disabled) is primitive-wins; the primitive's runtime derive keeps writing it, so a static author value that disagrees with the render-time state is a frozen-vs-clobbered ambiguity the author cannot have meant — distinct from the visible-override lint KV232.<br>Fixes: drop the contradicting static value (let the primitive own it) or set it to match the primitive's render-time state.<br>SPEC §4.6 makes a contradicting static state aria-* an error (KV317), not the override lint (KV232). |
| `KV330` | Direct db access in a mutation handler; route through domain. | Blocked reason: direct request/db access in a mutation handler bypasses the domain write surface and weakens touch-graph analysis.<br>Fixes: move writes behind a domain() module, inject the domain operation into the handler, or use the typed transaction context only inside the domain layer.<br>SPEC §11.4 and §14 require writes to flow through domains so invalidation and verifier diagnostics stay complete. |
| `KV402` | Write touched an undeclared domain. | Would lower to: a mutation whose declared writes and invalidations cover every domain observed by the static and runtime touch graphs.<br>Blocked reason: the write reached a domain outside the declared mutation touch set, so affected queries could remain silently stale.<br>Fixes: add the observed domain to the mutation writes/invalidates declaration, move the write behind the correct domain operation, or remove the unintended write; then rerun kovo check.<br>SPEC §10.3/§11.2 makes the derived touch graph a superset check over declared mutation domains. |
| `KV404` | Write to unmapped table. | Would lower to: a Drizzle table mapped to one Kovo domain, or an explicit write-only exempt table that no query reads.<br>Blocked reason: a write reached an unmapped table, so Kovo cannot assign its invalidation domain or prove which query data became stale.<br>Fixes: add kovo((columns) =&gt; ({ domain: "name" })) metadata to the table, or mark a genuinely write-only table exempt; then rerun kovo check.<br>SPEC §10.1/§11.2 requires every written table to be mapped or explicitly exempt, and rejects queries over exempt tables with KV411. |
| `KV405` | Conditional write branch was never executed under instrumentation. | *See message.* |
| `KV406` | Statically un-analyzable write site; manual touches required. | Would lower to: declared mutation touch metadata plus a managed write handle whose runtime DB policy rejects writes outside the declared tables.<br>Blocked reason: the static extractor could not prove the write touch set, so the advisory graph cannot explain which invalidation or declared-table policy the write belongs to.<br>Fixes: make the write analyzable, add the manual touch declaration, or route the operation through a managed mutation/write surface that carries declared-table policy.<br>SPEC §10.3/§11.2 and fundamental-fixes-followup-3 DEC-F: KV406 is a static defense-in-depth signal; the runtime declared-table write choke is the security boundary and must fail closed independently of static completeness. |
| `KV407` | Query read from undeclared domain. | No mutation touch graph writes that domain. |
| `KV408` | Declared row key differs from observed row predicate. | *See message.* |
| `KV410` | Query result shape failed declared output schema. | Opaque query projection requires a declared output schema. |
| `KV411` | Query read set includes an exempt table. | *See message.* |
| `KV412` | Query reads an unmodeled relation. | *See message.* |
| `KV413` | Database engine side-effect needs a declared fan-out. | Would lower to: an explicit DB-engine fan-out edge that unions trigger-written domains into the mutation touch graph.<br>Blocked reason: a detected database trigger can mutate data outside the static Drizzle write chain, so invalidation would miss the affected domain.<br>Fixes: declare kovo((columns) =&gt; ({ fans: [{ via: columns.parentId, domain: 'parent', when: 'update' }] })) for the trigger fan-out, move the side-effect into a modeled domain write, or mark the table exempt only when no UI reads it.<br>SPEC §10.1 and §11.1 require DB-engine side effects that cannot be derived statically to be declared and checked. |
| `KV414` | Owner-table access is not scoped to the session principal (IDOR). | Would lower to: an owner-scoped read/write whose key predicate is traceable to req.session or an owns() ownership guard.<br>Blocked reason: this query or write reaches an owner-annotated table through a client-visible key that is not tied to the session principal, so one user could read or mutate another user's rows (IDOR).<br>Fixes: scope the predicate by a session field (e.g. eq(table.id, req.session.userId)), add an owns() ownership guard, or record a public-read justification if the table is genuinely public.<br>SPEC §10.1/§10.3/§11.2 make the unscoped audit a blocking gate: owner-table access must be session-traceable or ownership-guarded. |
| `KV415` | Response header channel contains a forbidden header name or unsafe header value. | Would lower to: a typed response header record that serializes only framework-allowed header names and cookie values produced by the typed cookie builder.<br>Blocked reason: arbitrary header names/values can trigger browser navigation outside the typed redirect posture, smuggle forbidden response metadata, override adapter-owned framing/hop-by-hop fields, or split headers when they contain CR/LF/NUL/control characters.<br>Fixes: remove Refresh; keep direct structured headers to Cache-Control/Last-Modified/Vary; use contentType, etag, filename/disposition, redirect(), or the typed cookie builder for dedicated fields; remove transport-owned writes.<br>SPEC §9.1.1 keeps response headers in a typed channel so generated wire responses remain auditable. |
| `KV416` | Prod render-equivalence gate failed. | Would lower to: a production delta payload whose app-build token covers the full dev render contract and whose delta applies back to the same HTML.<br>Blocked reason: production delta output or render-plan fingerprint monotonicity failed, so a stale tab could patch DOM produced by a different render contract.<br>Fixes: include every query shape and the update-plan grammar version in the render-plan fingerprint, fix the delta encoder, or disable the production build until the corpus gate passes.<br>SPEC §5.2.2 makes this a build-failing production render-equivalence gate. |
| `KV417` | Deploy-skew retention window is below the required floor. | Would lower to: a deploy-skew policy that retains prior immutable /c/__v/... modules and per-token /_q reads for at least 24 hours.<br>Blocked reason: the configured serving layer cannot retain the previous render-plan contract long enough for stale documents to recover safely.<br>Fixes: raise the deploy-skew retention window to at least 24 hours, configure immutable client-module retention, and keep prior-token query reads available for the window.<br>SPEC §14 requires stale documents to fail loud or recover instead of silently merging cross-build data. |
| `KV418` | csrf-exempt surface uses browser authority (forgeable). | Would lower to: a csrf-exempt surface that authenticates by a signature/verifier (e.g. a webhook), not by an ambient browser cookie.<br>Blocked reason: this surface opts out of CSRF protection (csrf: false) yet depends on ambient browser authority or mutates browser credentials — it reads req.session, reads Cookie/Authorization/Proxy-Authorization, escapes an unproven request carrier, runs a session/cookie-derived guard (authed, role(), owns()), or emits Set-Cookie/Clear-Site-Data. Disabling CSRF while retaining inbound or outbound browser authority is forgeable.<br>Fixes: keep CSRF protection (remove csrf: false) for browser credential-dependent writes; or authenticate a genuine machine/third-party callback from an explicit non-ambient custom signature header, remove every browser-authority dependency, and do not mutate browser cookies/storage.<br>SPEC §6.6 and §9.1 make a csrf: false surface that depends on ambient browser authority a compile error. |
| `KV419` | prefetch "moderate" prerenders a guarded, session-dependent route. | Would lower to: a speculationrules prerender that renders this route server-side, with the user's credentials, on hover/pointerdown.<br>Blocked reason: prefetch "moderate" prerenders a guarded (session-dependent) route, which executes its render — and any per-user side effects — for a navigation that may be discarded.<br>Fixes: use prefetch "conservative" (prefetch document bytes, no prerender) or false; restrict prefetch "moderate" to public, idempotent routes; or remove the guard if the route is genuinely public.<br>SPEC §8 requires auto-prerender to be opt-in only where renders are idempotent and not session-dependent. |
| `KV420` | Island with local state nested inside a server-refreshable fragment target loses its state on refresh. | Would lower to: a full-subtree re-render from (declared queries ∪ stamped props) on every fragment patch of the enclosing server-refreshable target.<br>Blocked reason: the fragment morph carries no serialization of island-local kovo-state (§9.1), so re-emitting the enclosing target would reset the nested island to its render-time default and clobber the child's live local state.<br>Fixes: lift the child's state into a declared query so it travels in the refreshable channel, mark the child isomorphic: true so it self-renders rather than being server-refreshed (§4.8), set disableServerRefresh: true on the enclosing component so the child reclassifies under §4.9, or move the stateful island outside the refreshable target.<br>SPEC §4.5/§4.9/§9.1 forbid an island declaring local state from rendering inside another component's inferred server-refreshable fragment target.<br>Escape: document-lifetime-immutable local state is renderOnce and does not trip KV420. |
| `KV421` | Duplicate mutation key. | Would lower to: one mutation fact per mutation key for the invalidation registry and server dispatch table.<br>Blocked reason: two mutation declarations share one key, so graph indexing silently last-write-wins the invalidation set while server dispatch first-match-wins the handler — the two layers disagree, an invalidation can be computed for a mutation that never runs, and the wrong handler (with the wrong input schema and guards) executes against attacker-shaped input.<br>Fixes: emit exactly one mutation fact per mutation key, or rename one mutation so its key is unique across the app graph.<br>SPEC §6.1 makes the mutation registry key-addressed and §9.5 dispatches a POST to exactly one keyed handler; duplicate mutation keys would otherwise silently last-write-wins the invalidation registry while first-match-wins server dispatch — like routes (KV228), components (KV237), fragment targets (KV238), view transitions (KV239), and query shapes (KV240), mutation keys must be unique. |
| `KV422` | SQL text injection risk. | Would lower to: SQL text and SQL values crossing the managed DB seam as separate facts.<br>Blocked reason: executable SQL text was built from an unbranded raw string, an unsafe raw chunk, or an unchecked identifier/keyword fragment, so request data could become SQL syntax instead of a bound value.<br>Fixes: use Drizzle builders or Kovo sql`...` placeholders for scalar values, staticSql`...` for literal-only SQL text, sql.identifier(value, { allow }) or sql.allow(value, allowlist) for allowlisted identifiers/keywords, or trustedSql(..., { justification }) for the audited raw-SQL escape hatch.<br>SPEC §10.2/§10.3 and §11.2 require framework-managed DB handles to reject unbranded executable SQL text independently from KV406/KV410 read/write freshness declarations. |
| `KV423` | Raw endpoint declaration is missing required audit metadata. | Would lower to: a raw endpoint audit row with explicit method, purpose/reason, mount scope, response body posture, cache posture, and app-owned encoding/header-safety declarations.<br>Blocked reason: endpoint() is the raw HTTP escape hatch; without complete audit metadata, reviewers cannot tell why the route exists, what methods it accepts, or who owns output/header safety.<br>Fixes: add the missing endpoint metadata, give prefix mounts a mountJustification, and keep csrf:false justifications separate from the endpoint purpose.<br>SPEC §9.1 makes raw endpoint ingress registry-visible, and the source/sink inventory requires every raw endpoint to explain its trust and output posture. |
| `KV424` | App-authored dangerous sink is not registered or behind a safe Kovo surface. | Would lower to: a framework-owned safe helper, typed trust API, or registered source/sink row for the dangerous output operation.<br>Blocked reason: app-authored direct writes to dangerous sinks such as raw HTML, URL/navigation, selectors, headers, files, dynamic import, eval, or process execution bypass Kovo contextual encoding and audit surfaces.<br>Fixes: route the value through the corresponding Kovo helper, use an explicit trustedHtml/trustedUrl-style escape hatch with provenance, or move app-owned raw protocol code behind an audited endpoint.<br>SPEC §4.8, §5.2 rule 10, and §9.1 require dangerous sinks to be safe-by-default or explicit in the source/sink inventory. |
| `KV425` | Framework source/sink registry drift detected an unregistered sink. | Would lower to: a source/sink registry entry, runtime chokepoint, diagnostic, or explicit repo-internal exclusion for each dangerous framework sink token found by drift detection.<br>Blocked reason: a new framework-owned sink appeared without being enrolled in the generated source/sink inventory, so future audits can miss a path from attacker-controlled input to output.<br>Fixes: add the sink to the shared registry with spec/test evidence, attach it to an existing safe chokepoint, or record a narrow exclusion proving it is build/test-only or outside request paths.<br>The source/sink plan requires drift detection for sink tokens such as innerHTML, Headers, Location, Set-Cookie, querySelector, import(), new Function, child_process, fs, and path resolution. |
| `KV426` | Trust escape hatch lacks auditable provenance. | Would lower to: a trust-audit row naming the escape hatch, source span, justification, and owning safe path or app review boundary.<br>Blocked reason: raw endpoint, trustedHtml/trustedUrl, custom/no verifier, static export path override, or future trustedSql use without provenance becomes invisible to kovo explain trust.<br>Fixes: add a named justification/source span, use a typed safe helper instead of the escape hatch, or remove the trust override.<br>SPEC §4.8/§9.1 and fundamental-fixes-followup-3 DEC-D/DEC-F: KV426 is an auditable static provenance signal; contextual renderer/header/URL runtime chokes and unforgeable trusted constructors remain the security boundary when static provenance is incomplete. |
| `KV428` | Inline rendering of an unverified-content-type upload. | Would lower to: an upload served Content-Disposition: attachment + X-Content-Type-Options: nosniff, with the served Content-Type minted from sniffed bytes (server truth), not the client-declared MIME.<br>Blocked reason: rendering an upload inline (disposition inline, or trusting the client Content-Type) lets attacker-controlled bytes — HTML/SVG/polyglots — execute as active content same-origin.<br>Fixes: serve attachment + nosniff (the default), or opt into inline only for verified-safe bytes (deep-sniffed or framework re-encoded/rasterized); use accept.unverified() as the audited escape, surfaced in kovo explain capabilities.<br>SPEC §6.6/§9.1 and secure-framework Phase 6: the guarantee is "attacker bytes are never rendered inline as active content", a runtime defense-in-depth floor, not "the sniffed type is unspoofable". |
| `KV429` | Read-then-write on a contended column without an atomic/version guard. | Would lower to: a compare-and-set (UPDATE ... WHERE) or kovo((columns) =&gt; ({ version: columns.lockVersion })) optimistic-concurrency guard folding the check and the act into one statement.<br>Blocked reason: a read-then-write on a declared atomic/version column without a CAS/version guard is a lost-update race — two concurrent read-decide-write requests survive auth and validation and overwrite each other (oversell, double-spend, coupon reuse).<br>Fixes: use the typed compare-and-set helper or carry the row version and reject a stale write with the typed 409/422 path; add a DB CHECK/unique constraint as the fail-closed backstop.<br>SPEC §6.6/§10.3 and secure-framework Phase 6: the mutation transaction (READ COMMITTED) alone does not prevent lost-update; multi-row invariants need forUpdate/SERIALIZABLE and are not by-construction. |
| `KV432` | Insecure cookie downgrade without a recorded justification. | Would lower to: a Set-Cookie minted through the typed cookie builder with HttpOnly + Secure(prod) + an explicit SameSite derived from the cookie class.<br>Blocked reason: an insecure downgrade (HttpOnly/Secure false, or SameSite=None) of a session/auth-reachable cookie strips the floor that defends the session id against XSS theft, MITM, and CSRF.<br>Fixes: keep the class-derived secure floor, or record the downgrade explicitly with unsafeCookie({ downgrade, justification }), surfaced in kovo explain cookies.<br>SPEC §6.6/§9.1 and secure-framework Phase 5: the cookie-attribute floor is by-construction at the single Set-Cookie sink; the downgrade path is audit-grade. |
| `KV433` | query() loader reaches a write. | Would lower to: a read-only query() loader handle enforced by the actual runtime layer in play — either a dedicated engine read-only connection/role where the adapter provides one, or the framework-owned read-only capability proxy plus managed SQL/storage choke otherwise — with a static reachability check as defense-in-depth.<br>Blocked reason: a query() loader that reaches a write — directly or via an imported domain() function called with a captured handle — is a state change on an idempotent read surface (the confused-deputy case).<br>Fixes: move the write to a mutation()/domain write, or use endpoint() for an explicitly side-effecting machine/API path.<br>SPEC §6.6/§9.4 and fundamental-fixes-followup-3 DEC-A/DEC-F: KV433 names the real runtime enforcement layer for this path; static no-write reachability is advisory/defense-in-depth and must not be treated as a static-only security proof. |
| `KV434` | Non-linear-safe pattern literal in a wire string validator. | Would lower to: a wire string validator backed by a blessed linear matcher (email/url/uuid/slug) or a compile-visible literal pattern with known exponential structures rejected; runtime also applies an input-size cap, not a CPU bound.<br>Blocked reason: a non-linear-safe or non-literal pattern in a wire string validator is a ReDoS vector — catastrophic backtracking turns a short input into unbounded CPU.<br>Fixes: use a blessed format, give pattern() a compile-visible literal with no nested/overlapping quantifiers, or take the ReDoS risk explicitly with unsafeRegex(re, justification), surfaced in kovo explain capabilities.<br>SPEC §6.6/§9.5 and secure-framework Phase 6: blessed formats are by-construction; pattern() is by-construction-ish (literal structural reject + runtime input-size cap); a full linear engine is deferred. |
| `KV435` | Secret query value reaches the client wire. | Would lower to: a client-readable kovo-query payload embedded in the document and hydrated by the browser query store.<br>Blocked reason: the projected query shape contains a secret-classified field, or an opaque/unresolved projection reads a table carrying secret columns, so rendering this query could serialize confidential data onto the client wire.<br>Fixes: remove the secret field or opaque projection, select explicit non-secret columns, select a non-secret surrogate, or add an explicit reveal/redaction escape once the audited reveal surface lands.<br>SPEC §6.2/§10.2/§11.3 and fundamental-fixes-followup-3 DEC-C/DEC-F: KV435 is static defense-in-depth for client-wire confidentiality; the active runtime boundary is the Secret provenance box plus the egress choke that throws at the wire, and on dialects that install reader-role revocation the database engine can add its own REVOKE floor. |
| `KV436` | Missing explicit access decision. | Would lower to: a query, mutation, route, endpoint, or webhook with a total access decision recorded in the app graph.<br>Blocked reason: the surface has no explicit access decision, so review cannot distinguish an intentional public or machine-verified entry from an accidentally reachable handler.<br>Fixes: add an access guard chain, public("reason"), or verified machine-auth decision; use kovo explain access to inspect the ledger before enabling the strict gate.<br>SPEC §10.2/§11.3 and the secure-by-construction Phase 2 plan require authorization to be default-deny through explicit access decisions, not through inferred defaults. |
| `KV437` | Server-only value captured into a client handler reaches the client bundle. | Would lower to: a client handler module whose captured imports and same-file module constants are explicitly proven client-safe before emission.<br>Blocked reason: a client handler closure that captures a server-only binding (a secret/process.env-derived value, any cross-module import not provably client-safe, or a same-file literal not explicitly public) re-emits it verbatim into the client bundle, leaking confidential server state to the browser.<br>Fixes: do not capture the server value in client code; pass a server-computed safe value as a prop, or use publishToClient(value, { reason }) as the audited escape, surfaced in kovo explain capabilities.<br>SPEC §6.6/§6.2 and secure-framework Phase 4/Tier 0: the emit filter is fail-closed whole-channel (a narrow process.env/brand-only gate is unsound — call-wrapped secrets escape). |
| `KV438` | Request input reaches a governed column (mass assignment). | Would lower to: a write whose governed columns (owner/principal columns, the primary key, and columns marked kovo((columns) =&gt; ({ governed: [columns.role] }))) receive only server-derived, literal, or explicitly-asserted values — never raw request input.<br>Blocked reason: a governed column (owner/principal/role/privilege/identity) set from request input — directly, through an alias/destructure, or via a .values(input) / .set(input) spread — is mass assignment: a client can over-write a field the server never meant to expose (privilege escalation, ownership takeover, balance tampering).<br>Fixes: assign the column from a structurally proven server/private value (req.session/guard/tenant) or a literal; serverValue(value, reason) accepts only an independently proven non-input value and rejects opaque helpers; for a deliberate privileged write use trustedAssign(input.x, { invariant: 'governed-write.authorized-principal', why: ..., evidence: ... }) with the exact inline structured obligation (surfaced in kovo explain capabilities and emitted for detached review). App analyzer summaries cannot declare server provenance.<br>SPEC §10.3/§11.1 and secure-framework Phase 3: governed-column write-provenance is by-construction (input-reaching a governed column fails the build, fail-closed on unprovable provenance); serverValue/trustedAssign are author-assertion escapes (audit-grade). |
| `KV439` | DB table row reaches the client query wire without an explicit projection. | Would lower to: a query result whose client wire shape carries a whole DB/table row value instead of an explicit projected object.<br>Blocked reason: table-row provenance crossing the query wire hides the intended response contract and can serialize columns the author did not deliberately allow, even when those columns are not secret-classified.<br>Fixes: project the exact response fields, for example db.select({ id: users.id, name: users.name }), or map rows to an explicit object shape before returning.<br>SPEC §6.2, §9.4, and §11.3 make query results JsonValue-bounded client wire values; DB/table row provenance must cross that boundary through an intentional projection allowlist. |
| `KV445` | Durable tasks require a preset JobRunner capability. | Would lower to: a durable task deployment whose preset declares and emits a real JobRunner drainer for every registered task().<br>Blocked reason: the build registers durable tasks, but the selected preset declares no JobRunner capability, so scheduled work could be accepted without any deployed process able to drain it.<br>Fixes: use the node preset in serve-and-run mode, configure a preset/adapter with a cron-drain or external queue runner, or remove task()/request.schedule() before deploying to that target.<br>SPEC §9.6 requires durable tasks to be backed by a real persistent runner instead of a best-effort in-memory promise. |
| `KV446` | SQLite deployments cannot use the node preset durable-task store. | Would lower to: durable task enqueue/drain operations persisted in the node preset JobRunner store with the same durability contract the app can actually host.<br>Blocked reason: the node preset default JobRunner persists jobs in Postgres _kovo_jobs, but this server bundle uses SQLite/better-sqlite3, so durable task storage would not be available in production.<br>Fixes: use a Postgres-compatible app database for durable tasks, configure a supported durable queue adapter when one exists, or remove task()/request.schedule() from SQLite deployments.<br>SPEC §9.6 requires node durable tasks to use the Postgres durable-task store until a supported SQLite durable queue adapter exists. |
| `KV448` | Untrusted-data-reachable module graph acquires raw or unresolved authority. | Would lower to: an untrusted-data root whose complete module/callback closure contains only ordinary pure modules and reviewed Kovo capability doors.<br>Blocked reason: the provenance path reaches raw network, filesystem, process, VM, worker, database-driver, or unresolved loading authority; alternatively an external package summary is absent, stale, contradictory, or incomplete for the installed version and conditional exports.<br>Fixes: route the operation through the named Kovo capability, remove the raw authority from the reachable module graph, make a dynamic import/require target literal, or add/update an exact-version reviewed pure-package summary. The diagnostic path names the root and every transfer edge.<br>SPEC §6.6 capability-closed module graph: this is a static supported-subset gate, not a JavaScript process sandbox; deliberately hostile same-realm app/package code remains outside the app-level proof. |
| `KV449` | Security-critical operation is outside the compiler-owned finite IR. | Would lower to: a compiler-owned kovo-security-operation-ir/v1 operation with an exact reviewed door plus a kovo-security-semantic-graph/v3 root-binding-to-invocation-to-sink trace.<br>Blocked reason: the handler uses an unknown/computed raw DOM or capability operation, an unreviewed executable call, an unjustified exceptional door, an unsupported/recursive/budget-exhausted helper transfer, or a hand-written mutation form that cannot carry the complete server-stamped CSRF plus Kovo-Idem field set.<br>Fixes: use typed &lt;form mutation={definition}&gt; (or the exact mutationFormAttributes(definition) JSX spread); use component state, delegated event reads, reviewed focus/dialog/form operations, managed DB/egress/response APIs, or a named trustedSql/trustedHtml/raw-response door with its required justification.<br>SPEC §4.3, §5.2, §6.6, and §9.1 make the finite compiler-owned operation set and its bounded helper summaries fail closed. Server diagnostics name root, ordered transfers, sink, and the exact closed reason. There is no general raw-DOM/capability or hand-authored lowered-IR escape; only the named exceptional doors documented by their owning sink are accepted.<br>Escape: trustedSql, trustedHtml, and endpoint/webhook raw Response are the only exceptional IR operations, and each remains visible in kovo explain with its justification/posture. |
| `KV450` | Stateful sink key lacks framework-witnessed owner scope. | Blocked reason: a non-database stateful sink received a bare string, forged structure, proxy, unregistered system posture, or malformed persisted scope frame, so owner provenance is absent or ambiguous.<br>Fixes: derive a principal key with scopedKey(request, key) or task actAs(id).stateKey(key); use publicScopedKey(key) only for deliberately shared state; framework internals must use a finite reviewed system posture.<br>SPEC §6.6, §9.6, and §10.3 require storage objects and durable-task coalescing keys to carry one canonical runtime-witnessed ScopedKey frame before namespace use. |
| `KV451` | Compiler-derived value is outside the structural source-emission grammar. | Would lower to: one grammar-validated JavaScript/TypeScript leaf produced by the shared structural emission constructor for the exact source role.<br>Blocked reason: the value is not valid for that grammar role, so direct interpolation could create executable sibling syntax or an ambiguous generated artifact.<br>Fixes: derive a valid source identifier/specifier, keep data in a jsStringLiteral/tsPropertyKey leaf, or repair the typed fact producer before emission.<br>SPEC §5.2 requires generated artifacts to remain valid, auditable Kovo source and security-sensitive emission to fail closed. |
| `KV452` | Owner-scoped or governed data reaches a persistent non-engine sink. | Would lower to: a framework-owned derived vector dataset operation whose physical namespace is reconstructed from the complete request-principal ScopedKey frame.<br>Blocked reason: owner-scoped or governed data reaches a persistent non-engine sink directly, or a derived dataset operation lacks the exact framework request principal binding.<br>Fixes: wrap the vector/RAG adapter with derived(adapter, { key, kind: 'vector' }) and pass the exact handler request to every query/upsert; keep ordinary storage, egress, and durable-task payloads free of owner-scoped database rows.<br>SPEC §6.6 and §10.3 C9 require derived artifacts to inherit owner scope through the existing provenance engine and runtime-opaque ScopedKey namespace. |

## Warn

Non-blocking warning. The check passes, but the framework wants your attention.

| Code | Message | Fix |
| --- | --- | --- |
| `KV241` | Derived component registry key changed since the previous emitted graph. | Blocked reason: derived component registry keys are deploy-load-bearing; changing one can strand in-flight documents whose morph identity still names the prior emitted component.<br>Fixes: keep the component binding and module path stable across deploys, or review the rename/move as an intentional identity migration and refresh the previous registry facts.<br>SPEC §4.2 and §4.8 make derived component names load-bearing for kovo-c identity, scoped CSS, fragments, and graph facts. |
| `KV246` | Derived mutation registry key changed since the previous emitted graph. | Blocked reason: source-derived mutation keys are deploy-load-bearing; changing one can strand in-flight documents, CSRF audiences, replay records, form actions, and generated invalidation facts that still name the previous mutation.<br>Fixes: keep the mutation export binding and module path stable across deploys, or review the rename/move as an intentional identity migration and refresh the previous registry facts.<br>SPEC §4.1 and §10.3 make mutation registry identities source-derived and load-bearing for /_m dispatch, CSRF audience binding, replay scopes, and invalidation graphs. |
| `KV247` | Derived query registry key changed since the previous emitted graph. | Blocked reason: source-derived query keys are deploy-load-bearing; changing one can strand in-flight documents, kovo-query stores, kovo-deps, query endpoint URLs, and generated invalidation facts that still name the previous query.<br>Fixes: keep the query export binding and module path stable across deploys, or review the rename/move as an intentional identity migration and refresh the previous registry facts.<br>SPEC §4.1 and §10.2 make query registry identities source-derived and load-bearing for /_q dispatch, hydration, dependency stamps, and mutation invalidation graphs. |
| `KV310` | Invalidated query lacks optimistic transform. | Would lower to: an optimistic status for each invalidated query edge, such as a transform or await-fragment decision.<br>Blocked reason: a mutation invalidates a query without declaring how the UI should predict or defer that update.<br>Fixes: add an optimistic transform, declare await-fragment, or narrow the invalidation so the query is not touched.<br>SPEC §11.4 requires mutation writes, query invalidations, and optimistic coverage to be checked edge by edge. |
| `KV311` | Query/state-dependent DOM position has no update status. | Would lower to: a data-bind/update plan, fragment boundary, isomorphic component, or renderOnce marker for the rendered position.<br>Blocked reason: the compiler found a query/state-dependent DOM position without an update strategy.<br>Fixes: add a data-bind/query update plan, mark the expression renderOnce, move the subtree behind a fragment target, or make the component isomorphic.<br>SPEC §4.9 requires every query/state-dependent rendered position to have plan, fragment, isomorphic, or renderOnce coverage. |
| `KV315` | Untracked clock read in derive; use a declared clocks input. | Would lower to: a derive that re-runs from an explicit clocks input such as now.ago.<br>Blocked reason: Date.now() and new Date() read the wall clock without a declared cadence, so the update plan can freeze time-derived UI.<br>Fixes: declare a component clocks entry and pass now.&lt;name&gt; into the derive, or mark the clock renderOnce when freezing the value is intentional.<br>SPEC §4.8 and §4.9 require derive inputs to name every fact that can change rendered output.<br>Escape: renderOnce is the documented suppression for intentionally immutable clock output. |
| `KV403` | Declared domain was never observed written. | *See message.* |
| `KV430` | Schema admits unbounded breadth/depth on an untrusted source. | Would lower to: an s.* wire schema carrying an explicit breadth/depth bound (.max(...)) on every unbounded array/record reachable from an untrusted source.<br>Blocked reason: an unbounded s.array()/s.record() on a wire-reachable schema lets a small request body drive unbounded parser work (depth/breadth/node blow-up) that the byte+rate limiter cannot see.<br>Fixes: declare the legitimate bound with .max(n) (the runtime depth/breadth/node budget already protects by default — this lint just makes the bound explicit and auditable).<br>SPEC §6.6/§9.5 and secure-framework Phase 6: the runtime budget is the protection; KV430 is an auditable lint nudging an explicit bound, not an error. |
| `KV431` | Referenced client module is absent from the integrity/CSP manifest. | Would lower to: a completeness manifest listing every client module the document references against the integrity/CSP allowlist.<br>Blocked reason: a referenced client module absent from the integrity/CSP manifest cannot be audited for provenance; the CSP/allowlist cannot vouch for a module it does not list.<br>Fixes: add the module to the manifest/allowlist, or remove the dangling reference. Note: this is an advisory completeness/provenance audit, not byte-integrity — browser import() has no SRI gate.<br>SPEC §6.6 and secure-framework Phase 7: the real module-tamper defense is immutable versioned URLs + same-origin + the CSP self restriction, not SRI. |
| `KV447` | SQLite owner annotations are advisory only in the experimental SQLite runtime. | Would lower to: a SQLite owner-annotated table whose owner metadata is available to static audits but is not backed by database roles or RLS at runtime.<br>Blocked reason: SQLite has no engine role/RLS layer, so kovo((columns) =&gt; ({ owner: columns.ownerId })) and ownerVia annotations cannot provide Kovo's multi-principal authorization guarantee in the experimental SQLite starter.<br>Fixes: use the default PGlite/Postgres runtime for multi-tenant authorization, or treat the SQLite starter as single-principal/local-only and do not rely on owner scoping for confidentiality or integrity.<br>SPEC §10.3 and fundamental-fixes-followup-6 DEC-A: SQLite is explicitly experimental and non-guaranteeing for owner-scoped runtime authorization. |

## Lint

Style/clarity guidance. Surfaced by `kovo check`; non-blocking by default.

| Code | Message | Fix |
| --- | --- | --- |
| `KV210` | Anonymous handler; name it for stable identity. | Would lower to: a generated Component$element_event handler export with a stable source-derived URL.<br>Blocked reason: anonymous handler identity is less stable for generated artifacts, explanations, and agent repairs.<br>Fixes: extract a named function in module scope or reference a named local handler from the JSX event.<br>SPEC §5.2 requires readable, source-derived emitted names; this lint is advisory and has no suppression beyond accepting the generated fallback name. |
| `KV211` | on:load eager trigger requires a justification comment. | Blocked reason: on:load runs at parse time and adds eager JavaScript to the page budget.<br>Fixes: use a user/event trigger instead, or attach an adjacent KV211 justification comment when parse-time execution is intentional.<br>SPEC §4.7 keeps on:load grep-visible as the eager-JS escape hatch.<br>Escape: an attached KV211 justification comment preserves the lint trail without blocking compilation. |
| `KV212` | Unknown on:* event or execution trigger name. | Blocked reason: unknown on:* triggers cannot be mapped to the closed event/trigger vocabulary the loader understands.<br>Fixes: use a DOM event name, use one of Kovo's declared execution triggers, or move the behavior into a component primitive that owns the attribute.<br>SPEC §4.7 requires declared execution so generated artifacts remain auditable. |
| `KV223` | Redundant hand-written binding stamp in sugar; the compiler derives it. | Would lower to: the same data-bind stamp the author already wrote by hand.<br>Blocked reason: the stamp is redundant in app-authored TSX because the compiler can derive it from the typed expression.<br>Fixes: remove the hand-written data-bind stamp and keep the typed JSX expression as the source of truth.<br>SPEC §4.8 permits residual stamps for emitted IR fixpoint validation, but app TSX should not hand-author derivable stamps.<br>Escape: emitted compiler artifacts may retain residual stamps for fixpoint checks; app source should use TSX sugar. |
| `KV232` | Author overrides a primitive-owned ARIA or state attribute. | Would lower to: author-visible override of a primitive-owned ARIA, role, or state attribute.<br>Blocked reason: the override is allowed but can change accessibility semantics or be clobbered by runtime-updated primitive state.<br>Fixes: prefer the primitive API, remove the override, or keep it intentionally and audit the generated merge explanation.<br>SPEC §4.6 keeps this override as a lint-level escape hatch so author intent stays visible.<br>Escape: compilation continues; the lint documents the override for review. |
| `KV244` | defer() used as a JSX child; use &lt;Defer&gt; instead. | Would lower to: &lt;Defer target="..." fallback={...} render={...} /&gt; emitting a framework-owned &lt;kovo-defer&gt; placeholder.<br>Blocked reason: defer() is an internal string-composition helper; as a JSX child it bypasses JSX fallback escaping and can render framework markup as text.<br>Fixes: import Defer from @kovojs/server and render &lt;Defer ... /&gt; with JSX fallback content, or keep raw HTML behind an explicit trustedHtml(...) boundary outside JSX child position.<br>SPEC §8 makes Defer the public route-region deferral API and keeps raw string composition internal.<br>Escape: trustedHtml(...) remains the explicit raw-HTML escape hatch, but app JSX children should use &lt;Defer&gt;. |
| `KV301` | Server fact stored in island-local state. | Blocked reason: server/query facts stored in island-local state create a second client-owned copy of server truth.<br>Fixes: keep the value in query data, derive UI-only state from client intent, or store only local presentation state.<br>SPEC §4.1 keeps query data server-owned and local state private/client-owned. |
| `KV318` | isomorphic: true requires an adjacent KV318 justification comment. | Would lower to: a client-bundled copy of the component render function plus a self-morph update plan.<br>Blocked reason: isomorphic islands are the bounded SPA-creep escape; without an adjacent justification, review cannot distinguish an intentional client self-render from accidental overuse.<br>Fixes: add an adjacent JSX justification comment naming KV318 before the isomorphic component, or drop isomorphic: true and use bindings, derives, keyed lists, or a server fragment.<br>SPEC §4.8 makes isomorphic: true the explicit client self-render escape and requires a recorded justification.<br>Escape: an adjacent KV318 justification comment preserves the lint trail without blocking compilation. |
| `KV320` | Event payload overlaps query data; use a transform. | Blocked reason: a fire-and-forget event payload is carrying data that overlaps server-owned query facts.<br>Fixes: send only client intent, use an optimistic transform for query data, or route the change through a mutation/domain write.<br>SPEC §6.4 keeps cross-island events for intent, not as a shadow transport for server facts. |

## Notice

Informational. The framework degraded behavior and is telling you how.

| Code | Message | Fix |
| --- | --- | --- |
| `KV409` | Non-eq predicate degraded to table-level invalidation. | *See message.* |
