# KitJS 1.0.0 browser contract

Status: stable browser contract represented by the standalone
`@kitwork/kitjs` source and generated artifacts in this checkout. Exact public
npm/CDN availability, signatures, provenance, and npm `latest` resolution are
separate release evidence. The historical `1.0.0-rc.2`, `1.0.0-rc.1`, and
`0.9.0-next.15` evidence does not widen this contract.

This document defines the observable behavior shared by the readable
`dist/kit.js` and `dist/hydrate.kit.js` classic scripts. “Must”, “must not”,
“should”, and “may” describe compatibility requirements. Implementation details
that are not stated here are not public API.

## 1. Delivery profiles

The package has two mutually exclusive profiles:

- **Kit** provides components, closed expressions, directives, delegated
  events, conditional branches, keyed structural templates, form models, and
  boundary rendering.
- **Hydrate** contains the exact Kit profile plus private Morph and Drive
  document continuity.

A document must load no more than one profile. Loading the same base release
and profile again is a no-op. Loading Kit over Hydrate, or Hydrate over Kit,
must fail before the second runtime installs event listeners or component
code.

Both base files are classic browser scripts and publish one frozen
`globalThis.kit` object. They are not ESM or CommonJS modules. The complete base
API is:

```text
kit.version                       // "1.0.0"
kit.component(name, plainObject)
```

No parser, evaluator, renderer, lifecycle control, package registry, Morph, or
Drive method is public.

The package also generates `dist/integrity.json`. It is delivery metadata, not
a browser API. Its deterministic schema records the package and exact version,
the `sha256` algorithm, and one fixed `kit` and `hydrate` record containing the
profile filename, bytes, lowercase SHA-256, and canonical SRI. It contains no
timestamp or machine path and must describe the exact checked distribution
bytes.

## 2. Reactive boundary hosts

A connected element may declare an anonymous scope, a named component, or a
component seeded by a scope. Descendant directives resolve the nearest such
host and one host always owns one shallow reactive store.

### 2.1 Scope declarations

`data-kit-scope` accepts either a semicolon-separated shorthand or one object:

```html
<section data-kit-scope="count: 3; open: true"></section>
<section data-kit-scope='{ count: 3, open: true }'></section>
```

The final shorthand semicolon is optional. A declaration must be non-empty.
Every top-level key, quoted or unquoted, must be a unique ASCII identifier
matching `[A-Za-z_][A-Za-z0-9_]*` and must not be a forbidden keyword or
blocked member name. In particular, `$`-prefixed names are not top-level scope
fields.

At nested object levels, unquoted keys use the same identifier rules. A quoted
key may be any JSON string, including an empty, `$`-prefixed, or hyphenated
string, but it must be unique within that object and must not be a blocked
prototype member name.

The data decoder may preserve quoted payload keys that the expression evaluator
still blocks as global-escape member names. Parsing data never grants authored
expressions additional member authority.

Values are a closed data grammar containing only `null`, booleans, finite
numbers with an optional unary sign, strings, arrays, and plain objects. Bare
identifiers as values, property reads, calls, lambdas, and other operators are
invalid. Strings accept the supported JSON-style escapes but reject lone or
invalidly paired UTF-16 surrogates. Parsed objects have null prototypes. A
declaration is limited to 16,384 UTF-16 code units, a nesting depth of 32, and
1,024 data nodes.

`data-kit-scope` and `data-kit-component` are invalid on a `<template>` host.
A boundary needed by structural content must be placed inside the template
content so cloned nodes have an unambiguous nearest owner. An ordinary element
with `data-kit-if` may itself carry a scope or component boundary, but that
boundary belongs to the conditional branch and exists only after the branch
mounts. Its `if` condition resolves the nearest enclosing parent boundary; it
cannot read state declared by the conditional host itself.

A scope-only host creates one anonymous store whose existing top-level fields
may be assigned by actions and models. It has no methods, initialization or
cleanup hook, alias, component identity, or retention key. The authored scope
source becomes immutable when prepared. Changing it requires disposal and a
new mount; it does not reseed a live boundary.

Servers may emit a scope as public initial state. They must serialize only the
closed data grammar and HTML-attribute-escape the entire completed declaration
for its quote context. Untrusted text must not be concatenated into grammar,
and secrets must not be placed in HTML. This release defines neither
`data-kit-props` nor `$props`; a scope snapshot is not a live parent-to-child or
server-to-client channel.

### 2.2 Components and scope seeds

`kit.component()` must receive exactly two arguments. A component name must
match `[A-Za-z_$][A-Za-z0-9_$.-]*` and must not be a prototype or global escape
name. A definition must be a plain object. Duplicate registration fails.

When the standalone runtime is a classic external `defer` script, first render
must run in the capture phase of `DOMContentLoaded`, after all ordered `defer`
scripts and before ordinary authored `DOMContentLoaded` listeners. The
missing-definition audit must run afterward. Ordered later `defer` scripts may
therefore register trusted components before the runtime audits or mounts their
hosts, including while `readyState` is already `interactive`.

Each connected component host creates an isolated shallow instance:

```html
<section data-kit-component="counter" data-kit-scope="count: 3">
  <button data-kit-click="count = count + 1">Increment</button>
  <output data-kit-text="count"></output>
</section>
```

When `data-kit-scope` and `data-kit-component` occur on the same host, the
scope is an initial seed for that component's one store. It may override only
an own writable non-function data field declared by the component definition.
Unknown fields, methods, accessors, and `init` cannot be seeded. The complete
seed is validated and applied before `init()`; invalid seed data fails the
boundary without partially changing its instance.

Component data is copied for each host. Methods retain receiver identity. An
optional `init(context)` runs once for an instance. `this` is the component
store; the argument is a frozen trusted lifecycle context with exactly these
five enumerable keys:

The lifecycle hook is optional authority, not the default component shape. A
component that needs only local state and trusted methods should omit `init`;
semantic HTML and directives continue to own its DOM presentation and events.

| Key | Normative behavior |
|---|---|
| `host` | An enumerable getter returning the live host, then `null` after disposal. It must not permanently retain the detached host. |
| `owned(selector)` | Return a fresh frozen array. Include the host when it matches. Prune complete `data-kit-ignore` subtrees and every nested `data-kit-scope` or `data-kit-component` boundary. Repeated calls query the current DOM. |
| `listen(target, type, fn, options)` | Attach a native listener to the supplied event target and return an idempotent disposer. Snapshot the effective capture flag for removal. Disposal removes every still-owned listener; prior `AbortSignal` removal is harmless. |
| `cleanup(fn)` | Register a synchronous disposer and return an idempotent function that runs and unregisters it. Disposal drains active registrations exactly once in last-in-first-out order and continues after errors. |
| `afterRender(fn)` | Register a one-shot callback for the boundary's next completed render and return an idempotent cancellation function. A callback registered during `init` runs after the first binding and directive render. Disposal cancels pending callbacks. |

The context and all four methods are frozen. Lifecycle cleanup and
`afterRender` callbacks receive the component store as `this`. Context methods
fail closed after disposal: `host` is `null`, `owned()` is empty, and no new
listener, cleanup, or render callback is scheduled. Compatible retained Morph
reconciliation preserves the instance and context; `owned()` therefore sees
the reconciled descendants without remounting.

For backward compatibility, `init()` may return one synchronous cleanup
function. A Promise returned by `init()` is observed for state settlement but
cannot later provide cleanup as its resolved value. A cleanup owner is released
once after a real removal; moving a host within the same document is not
removal.

The lifecycle context is trusted JavaScript authority. It is only an argument
to `init()`, is not a component data field, and must never be inserted into the
reactive store, expression locals, aliases, or any authored expression facade.

`data-kit-component` contains one unversioned name registered through
`kit.component(name, plainObject)`. Authored HTML never selects, downloads, or
upgrades component code. A missing definition is reported after
`DOMContentLoaded`; the authored fallback DOM remains present but the boundary
does not mount.

`data-kit-as="$name"` gives a component an action-only alias. The name must
match `$[A-Za-z][A-Za-z0-9_]*` and must not use a runtime-reserved alias. An
action resolves the alias from connected DOM at execution time. A missing,
detached, or duplicate alias fails. Bindings cannot observe alias state.

The split `data-kit-version` attribute is invalid in the 1.0 contract. Its
presence makes the component declaration fail closed; a version is never
assembled from multiple attributes. The old public `0.9.0-next.15` artifact
accepted that split form only as deprecated 0.9 compatibility input.

There is no `data-kit-local` directive or component marker. Direct client
registration is already represented by an unversioned
`data-kit-component="name"`.

Host-specific server assembly is outside this standalone package contract.

## 3. Directive grammar

The authored surface is `data-kit-*`. Unknown directives and malformed
modifier combinations fail closed.

| Directive | Required behavior |
|---|---|
| `data-kit-scope="declaration"` | Create anonymous state or seed the component on the same host. |
| `data-kit-component="name"` | Create one component instance from a trusted definition registered through `kit.component()`. |
| `data-kit-text="expression"` | Set `textContent` from a binding result. |
| `data-kit-show="expression"` | Set the `hidden` property from truthiness. |
| `data-kit-bind="name: expression;"` | Update safe attributes and permitted form properties. |
| `data-kit-class="expression"` | Own dynamic class tokens while retaining static classes. |
| `data-kit-style="property: expression;"` | Transactionally own continuous CSS property values while retaining unrelated authored styles. |
| `data-kit-model="field"` | Two-way bind one existing writable field matching `[A-Za-z_][A-Za-z0-9_]*` on the nearest boundary. |
| `data-kit-<event>="action"` | Execute an action through delegated events. |
| `data-kit-if="expression"` | Mount or unmount one ordinary element branch or one `<template>` fragment. |
| `data-kit-for="item, index of items"` | Reconcile repeated `<template>` clone groups. |
| `data-kit-key="expression"` | Provide a unique string or finite-number row identity. |
| `data-kit-ignore` | Make this host and its complete subtree inert to KitJS ownership. |

`data-kit-if` may be placed directly on an ordinary element. That host and its
complete subtree form one conditional branch. The expression reads the nearest
enclosing parent boundary and any enclosing row locals. A scope or component
declared on the direct host is part of the mounted branch and cannot provide
state for its own condition.

`data-kit-if` may instead be placed on `<template>`. Its `template.content`
forms one fragment, may contain multiple top-level nodes, and remains natively
inert until materialized. `data-kit-for` and `data-kit-key` remain template-only.
One host cannot combine `if` and `for`; `key` requires `for` on the same
template. A loop also accepts `item of items`; its index is the default identity
when no key is present. Row locals are read-only. Nested structural depth is
limited to 64. Structural directive identity becomes immutable after
preparation. A retained component cannot be the direct conditional host or
live anywhere inside either structural form.

An ordinary conditional element is authored fallback DOM before preparation.
The browser may paint it and may start images, frames, custom elements, or
other resources before KitJS evaluates the condition. When preparation
succeeds and the initial condition is true, the runtime must retain the exact
authored direct host and its DOM identity. A false result removes and disposes
the branch deepest first; only a later false-to-true remount creates fresh node
and component identity. `data-kit-show` must be used when the node and its state
need to remain mounted while concealed.

The runtime must validate the direct condition and forbidden structure before
taking structural ownership. An invalid direct expression or a direct host
that is, or contains, a `script` reports once, leaves the authored fallback DOM
unchanged, and does not activate its `if` directive. A structural template that
contains a script is also invalid and never materializes. Direct authored
scripts may already have executed through the browser parser before the runtime
can report them, so client `if` is neither an execution boundary nor an
authorization, secrecy, or no-flash mechanism.

`data-kit-model` rejects `$`-prefixed names and nested paths. It supports text inputs, textareas, single/multiple selects,
checkbox booleans or arrays, radio groups, and finite number/range values. It
accepts a bare field, not a path. Invalid or empty numeric input becomes
`null`. Composition events prevent intermediate IME text from committing.

`data-kit-bind` must reject event-handler names, `style`, `srcdoc`, HTML/text
replacement sinks, unsafe URL schemes, and names beginning with `data-kit-`.

`data-kit-style` has exactly one grammar: a semicolon-separated list of
`property: binding-expression` entries with an optional final semicolon. Outer
braces and comma-separated maps are invalid. Source is limited to 16,384 UTF-16
code units and 128 unique entries. Regular property names are lowercase CSS
kebab names; custom properties match `--[A-Za-z_][A-Za-z0-9_-]*` and preserve
case. Runtime-owned custom-property prefixes, CSS shorthand properties,
`cssText`-like properties, `behavior`, and binding properties are invalid.

Every expression is synchronous. The runtime evaluates and validates every
entry before the element receives any write. Strings and finite numbers are
set through `CSSStyleDeclaration.setProperty`; `null`, `undefined`, `false`,
and the empty string restore the authored inline baseline for that property.
Booleans, objects, arrays, Promises, non-finite numbers, declaration-breaking
tokens, `!important`, and CSS URL/networking, `var()`, or `attr()` indirection
fail closed. The directive owns only its declared longhand or custom properties
and must retain all other authored or externally owned styles.
KitJS does not pre-render dynamic CSS. Authors must provide an inline or class
fallback when first-paint geometry matters.

## 4. Event contract

The exact event set is:

```text
click dblclick submit input change keydown keyup
pointerdown pointerup focusin focusout
```

Event modifiers are colon-separated. The exact modifiers are:

```text
self prevent stop once outside enter escape debounce(ms)
```

The debounce delay must be an integer from 1 through 60,000 milliseconds.
`enter` and `escape` are valid only on `keydown` and `keyup`, and are mutually
exclusive. `outside` is valid only on `click`, `dblclick`, `pointerdown`,
`pointerup`, and `focusin`, and is incompatible with `self`.

One document listener is installed for each supported event type. Actions
receive a frozen, read-only `$event` snapshot with bounded scalar fields rather
than the native event object. Direct and outside candidates are resolved from
the current connected DOM.

## 5. Closed expression language

The expression language is not ECMAScript. From low to high precedence it
supports:

```text
action sequencing:  a; b; c;
assignment:         name = value        (actions only)
update:             ++name name++ --name name-- (actions only)
conditional:        condition ? yes : no
nullish:            ??
logical:            || &&
equality:           == != === !==
relational:         < <= > >=
arithmetic:         + - * / %
unary:              ! - +
postfix:            .name [key] (arguments)
optional postfix:   ?.name ?.[key] ?.(arguments)
```

Primary values are finite decimal numbers, quoted strings with supported
escapes, booleans, `null`, identifiers, arrays, null-prototype objects,
parenthesized expressions, and expression lambdas such as
`(item) => item.name`.

The safe method allowlist is:

- arrays: `join`, `includes`, `indexOf`, `slice`, `map`, `filter`, `find`,
  `some`, `every`;
- strings: `includes`, `startsWith`, `endsWith`, `trim`, `toLowerCase`,
  `toUpperCase`;
- numbers: `toFixed`.

Ordinary `.`, computed `[key]`, and call links are strict when their receiver or
callee is `null` or `undefined`. `?.name`, `?.[key]`, and `?.(arguments)` use
continuous optional-chain semantics: a nullish optional receiver skips the rest
of that chain without evaluating a computed key or call arguments, while
parentheses end the chain. A present non-callable value remains an error.
Receiver identity is preserved.

A binding is read-only. An action may assign or apply prefix/postfix `++`/`--`
to a direct writable identifier: an existing writable data field on its nearest
scope or component boundary, or a writable expression-lambda local. Updates use
JavaScript numeric coercion; postfix returns the previous number and prefix the
new number. Writes remain pending and commit only after the whole synchronous
action succeeds. `!!value` is ordinary composition of the supported unary `!`
operator.

Member assignment or update, implicit field creation, comma expressions,
declarations, statements, loops, constructors, template literals,
compound/bitwise operators, page globals, and
prototype escape names are rejected. Evaluation is capped at 10,000 node
visits and 64 nested calls. An individual expression source longer than
65,536 UTF-16 code units, or one that would exceed the 32,768 stored-token
budget, fails closed before evaluation.

Component methods are trusted JavaScript and are outside the expression
transaction. A top-level Promise returned by an action is observed so its
originating live boundary can render after settlement.

The lifecycle context described in section 2.2 is likewise outside the closed
expression language. Authored bindings and actions cannot resolve `host`,
`owned`, `listen`, `cleanup`, `afterRender`, or the context object unless an
application deliberately exposes a separate component method of its own.

## 6. Ownership and rendering

The connected DOM is the binding registry. Compiled directives and reactive
boundary records are DOM-owned or weakly held. No page-lifetime collection may
keep a detached element or scope alive.

Every scope or component boundary has one shallow dirty bit. A successful field
change queues that owner at most once in the next microtask. Rendering queries
only elements whose nearest reactive boundary host is that owner. This is
boundary scheduling, not property-read dependency tracking.

Structural directives own only branch nodes they materialize. Keyed loops may
reuse, move, or dispose complete row groups. Conditional remount creates fresh
node and component identity after a prior false result removed the branch. An
initially truthy direct condition preserves the exact authored host identity;
identity is not preserved across an actual unmount/remount boundary. Removal
disposes private descendants deepest first. DOM writes are diffed against their
previous value.

The presence of `data-kit-ignore` makes its host and complete subtree an opaque
ownership region. KitJS must not prepare, validate, mount, render, dispatch,
model-bind, structurally materialize, or resolve aliases from that region.
Other `data-kit-*` attributes inside it are inert. The attribute's value has no
meaning; only its presence controls ownership.

Directive source, boundary kind, scope source, and component identity are
immutable after preparation. Removing a value or event attribute disables it;
restoring it reuses the first compiled program. Structural and boundary
attributes must not be changed or removed after preparation.

## 7. Hydrate continuity

Hydrate considers ordinary unmodified same-origin links and same-origin GET
forms for Drive. A `data-kit-drive="false"` value on the origin element or an
ancestor opts out. Downloads, external relationships, non-self targets,
modified clicks, non-GET forms, and cross-origin URLs remain browser
navigation.

On an authored executable `<script>`, `data-kit-drive="stable"` has a separate,
script-only meaning. It explicitly permits one same-origin script without SRI
to participate in the executable-script identity below. The script must still
be a classic external direct child of `head`, use `defer`, and keep the same
resolved URL, ordered position, and complete attribute set. The marker does not
make inline, body, module, import-map, speculation-rule, asynchronous,
`nomodule`, or unsigned cross-origin scripts compatible, and it does not enable
Drive on a link or form. A cross-origin script must omit the stable marker and
carry valid SRI; stable plus a cross-origin URL is incompatible even when SRI
is present. The stable marker is an author promise: Drive compares the tag
identity but does not fetch or hash that script. Replacing bytes behind its
unchanged URL violates this contract.

An eligible link whose resolved path and query identify the currently rendered
document and whose URL has explicit fragment syntax remains native browser
navigation, including an empty `#`. Drive saves the leaving history entry but
does not prevent the click. The browser therefore owns the URL change,
`hashchange`, scrolling, and CSS `:target` state. A `popstate` between fragment
entries for that same rendered document must restore the saved scroll position,
or apply the fragment when no position exists, without fetching or Morphing.

For an eligible cross-route visit, the requested fragment must survive the
response URL and followed redirects even when Fetch omits it from
`Response.url`. After a compatible commit, fragment lookup tests the exact raw
identifier and then its UTF-8 percent-decoded identifier. For each candidate,
an exact `id` wins over an exact `name` on an `a` element; `name` on other
elements does not qualify. Lookup is not CSS-selector parsing and performs no
Unicode normalization, so NFC and NFD identifiers remain distinct. Invalid
percent syntax remains a literal raw candidate. Ill-formed percent-encoded
UTF-8 uses replacement decoding and must not throw. An empty fragment and the
case-insensitive identifier `top`, when not shadowed by an exact target, resolve
to the document root.

Drive guarantees the committed URL, focus target, and scroll destination for a
cross-route fragment. It does not emulate native CSS `:target` activation for a
history entry committed through `pushState()`.

Hydrate validates the initially authored executable-script topology before
Drive claims navigation or history ownership. If that initial topology is
incompatible, Drive remains disabled for the document: it installs no
navigation listeners, does not change scroll-restoration policy, emits no
`kit:navigation` event, and performs no Drive fetch. Links and forms retain
ordinary browser navigation, so scripts in the next document execute through
the browser's document loader. The runtime emits one console warning that
identifies KitJS Drive as disabled. The diagnostic states the cause, describes
the offending script when one exists, redacts URL details that are not safe to
expose, and gives a remedy. Its exact wording is not public API.

Drive rejects a fetched response whose valid declared length exceeds 8 MiB and
always counts decoded response bytes while reading. After parsing, a fetched
document with more than
100,000 nodes or a depth greater than 256 is likewise incompatible. Each such
visit produces one fallback outcome and hands navigation to the browser before
title, head, history, or body mutation.

Before mutating the current document, an incoming response must pass all
compatibility checks, including:

- the standalone Hydrate profile tag itself is the same classic external direct
  child of `head`, uses `defer`, uses either canonical valid SRI for the exact
  profile bytes or the exact `data-kit-drive="stable"` same-origin policy, and
  preserves its resolved URL (including its query), direct-head position, and
  complete attribute set;
- an identical ordered executable-script contract: every authored executable
  script must be the same classic external direct child of `head`, use
  `defer`, use either valid SRI or the exact same-origin
  `data-kit-drive="stable"` policy, and preserve its resolved URL and complete
  attribute set;
- compatible explicit `<base>` semantics;
- every named component is already registered in the current document;
- no active embedded document or meta refresh that requires hard navigation;
- valid retained-host structure.

Inline scripts, body scripts, modules, import maps, speculation rules, unsigned
scripts without the eligible stable policy, event-handler script attributes,
and an authored executable script that is added, removed, reordered, or changed
are incompatible. An unknown `data-kit-drive` value on an executable script is
also incompatible. Inert data scripts whose type is not executable, such as
`application/json`, are ignored by this comparison. When a fetched destination
fails this comparison after Drive has started, Drive falls back to normal
navigation before title, head, history, or body mutation. The browser's
document loader then owns script execution; Drive never inserts or evaluates
the fetched nodes.

A compatible route group must rely only on
component definitions registered by its initial direct page or an identical
identity-bound persistent shared bundle. A destination cannot introduce a new
component definition during a compatible visit because Drive does not execute
scripts from fetched HTML. If the required definition is absent, navigation
falls back before Morph. A superseded visit must not commit. The public `kit`
object and document roots keep their identity; retained component and form
state remain governed by the ordinary Morph rules.

No executable script node discovered in fetched HTML may be inserted or run.

Morph replaces `body` while reconciling safe identity. A dirty form control is
preserved only when its non-empty `id`, tag, and input type remain compatible.
Select state follows option value, not old numeric index.

When current and incoming elements of the same namespace and local name both
carry `data-kit-ignore`, Morph must preserve the current host, attributes,
properties, and descendants without reconciling them. Adding or removing the
marker makes the elements incompatible and replaces that boundary. If no
incoming counterpart exists, ordinary removal still applies. An ignored host
does not participate in component retention and needs no stable key.

An application-owned component host may carry one exact `data-kit-retain` key
matching `[A-Za-z][A-Za-z0-9._:-]{0,127}`. The key must be unique and directly
identify a component host. Changing its tag, component name, alias, or
namespace replaces it. Retained hosts cannot nest or live in a
template/structural region. Retention preserves the host and scope but still
reconciles its attributes and children. A scope-only host cannot be retained.
A retained component preserves its live store and ignores an incoming scope
seed. On a non-retained boundary, a changed scope declaration causes disposal
and a fresh mount rather than mutating the live store in place.

Drive emits a non-cancelable `kit:navigation` event on `document`. Its frozen
detail has `start`, optional measured `progress`, and exactly one `finish` per
visit ID. Terminal outcomes are `loaded`, `cancelled`, `error`, or `fallback`.
Byte progress is reported only for a trustworthy identity-encoded
`Content-Length` response and remains below completion until Morph commits.

Drive stores scroll coordinates in its private history state. Scroll-event
writes must be deduplicated and throttled to at most one write per 250 ms. The
latest exact coordinates are synchronously flushed before an outgoing Drive
visit and on `pagehide`, unless a cross-route `popstate` has already activated
the destination entry. In that case, pending writes from the still-rendered old
document are cancelled and must not clobber the destination state.

## 8. Security and error behavior

Authored source must never execute through `eval()` or `Function`. Object
literals use null prototypes, computed keys accept only strings or finite
numbers, and blocked names prevent access to browser globals and prototype
chains.

Invalid scope declarations, component metadata, directives, programs, lookups,
members, calls, or budgets must be rejected and reported
without granting additional authority. Failed authored actions must discard
their pending assignments. Cleanup errors may be reported but must not prevent
other owned cleanup from continuing.

Trusted component scripts retain ordinary browser JavaScript authority and are
part of the application's trusted computing base. Client component scripts
remain ordinary page scripts and are governed by the executable-script
compatibility rule in section 7.

`data-kit-ignore` is an ownership and Morph boundary, not a sanitizer or
security boundary. Scripts, resources, custom elements, and other active
content in the initially authored DOM retain ordinary browser semantics.
The same rule applies to an ordinary element carrying `data-kit-if`: its
authored fallback content is not inert before preparation, and a client-side
condition must never carry authorization truth or secret data. Use a template
fragment for inert-first client materialization. Make authorization and secrecy
decisions before delivering the HTML.

## 9. Versioning

`1.0.0` identifies this stable runtime contract. It includes the direct
ordinary-element form of `data-kit-if`, first published in `1.0.0-rc.2`; the
`<template>` fragment form remains supported, while `data-kit-for` and
`data-kit-key` remain template-only.

The deterministic build in this checkout produces these identities:

| Profile | Bytes | SHA-256 | SRI |
|---|---:|---|---|
| Kit | 206,607 | `2d7b750cae101b8decbac50dc334d0a7b1f4e3a1b5fe038d74e84101c5d52192` | `sha256-LXt1DK4QG43susUNwzTQp7H046G1/gONdOhBAcXVIZI=` |
| Hydrate | 314,424 | `01b23e3e45ce5604b1643362323402c5e86b70049642e5be9891eb4a67d2e7b9` | `sha256-AbI+PkXOVgSxZDNiMjQCxehrcASWQuW+mJHrSmfS57k=` |

These local identities do not establish public availability. The immutable
`1.0.0-rc.2` publication evidence remains historical. The earlier
`1.0.0-rc.1` artifact requires `<template data-kit-if>` and does not acquire
the direct-element behavior from this document. Their exact identities,
together with the older `0.9.0-next.15` migration evidence, remain recorded in
`RELEASE_READINESS.md`.

Any change to the public surface or normative behavior requires a KitJS release
version change and regenerated distribution identities.
