LearnNewsExamplesServices
Frontmatter
id12987
titleDelta-grammar kernel: op vocabulary + universal batch predicates
stateClosed
labels
enhancementaiarchitecturecore
assigneesneo-fable-clio
createdAtJun 12, 2026, 1:25 PM
updatedAtJun 12, 2026, 2:45 PM
githubUrlhttps://github.com/neomjs/neo/issues/12987
authorneo-fable-clio
commentsCount0
parentIssue12986
subIssues[]
subIssuesCompleted0
subIssuesTotal0
blockedBy[]
blocking[ ] 12994 Delta-grammar guards: atomic pre-apply validation behind a dev config, [ ] 12991 Delta capture API: unified stream capture with epoch windows
closedAtJun 12, 2026, 2:45 PM

Delta-grammar kernel: op vocabulary + universal batch predicates

Closed v13.1.0/archive-v13-1-0-chunk-1 enhancementaiarchitecturecore
neo-fable-clio
neo-fable-clio commented on Jun 12, 2026, 1:25 PM

Context

First leaf of Epic #12986 (the vdom delta-stream contract, graduated from Discussion #12942). The OQ1 grammar census (#12942, comment DC_kwDODSospM4BB6bh — source-grounded, peer-verified by @neo-gpt + @neo-fable) enumerated the complete op taxonomy and invariant classification at the DeltaUpdates boundary. Today that knowledge lives only in the census prose; nothing in src/ encodes it, so every guard, helper, capture tool, or registry the epic builds would re-derive (and drift from) the same contracts. The kernel makes the census executable: one pure module that names the vocabulary and checks the universal grammar.

Release classification: post-release (v13.0.0 tagged 2026-06-12; the contract layer is post-release hardening substrate).

The Problem

The delta wire format has strong implicit contracts and zero explicit ones:

  • 8 actions + 1 implicit spellingfocusNode, insertNode, moveNode, removeAll, removeNode, replaceChild, updateNode, updateVtext; the differ's updateNode is action-less by construction (me[delta.action || 'updateNode'], src/main/DeltaUpdates.mjs:940) — the dominant spelling of the most common op is the absence of a field.
  • Enforcement today is three accidents: insertNode console.errors on a missing parent (DeltaUpdates.mjs:392,395), replaceChild can throw natively on bad child ids (:724-728), everything else silently no-ops. An unknown action string is a TypeError that aborts the batch mid-loop with partial application (src/Main.mjs:325-346 contains it per-operation) — leaving the DOM divergent from the worker's vnode model.
  • The misdirection hazard: an id-less updateNode resolves via getElementOrBody(undefined) → default param → document.body (src/main/DomAccess.mjs:481-487) — accidental writes silently style the body.
  • Ordering is a producer convention, not a checked contract: deltas.default.concat(deltas.remove) (src/vdom/Helper.mjs:833-835) — manual producers can violate remove-last undetected.

The Architectural Reality

  • The consumer dispatch surface: src/main/DeltaUpdates.mjs (per-op handlers + the :940 fallback + the insertNodeBatch coalescing optimization :911-936 that trusts sequential indices).
  • The producer field contracts: src/vdom/Helper.mjs — insertNode {action, parentId, index, vnode|outerHTML[, postMountUpdates]} with NO top-level id (the differ emits exactly the active renderer's payload key; the WIRE contract is at-least-one — manual producers may legally carry both) (:665-703); moveNode {action, id, parentId, index} (:730-769); removeNode {action, id[, parentId iff vtext/fragment]} pushed to the remove queue (:790-805); action-less updateNode accumulating all prop-diffs per node (:64-201); updateVtext {action, id, parentId, value} (:68-73); removeAll {action, parentId} (:291). replaceChild + focusNode have zero producers in src/ (consumer-only).
  • Sentinel encodings: null/'' attribute values mean remove ('' on value clears instead), voidAttributes boolean-coerce from strings, attributes.id renames a live node (DeltaUpdates.mjs:768-769).
  • The three-sorted id space: element ids (getElementById / [data-neo-id]), fragment ids (comment-anchor ranges <!-- id-start/end -->), vtext ids (comment pairs) — getElement(id) === null does NOT imply not-live.
  • Sibling precedent for placement: src/vdom/util/ holds the pure vnode utils (DomApiVnodeCreator.mjs, StringFromVnode.mjs); src/main/DeltaUpdates.mjs:4 already imports the plain-module src/vdom/domConstants.mjs cross-realm — a pure src/vdom/util/DeltaGrammar.mjs is consumable from Main, the VDom worker, and the Node test env. Structural pre-flight: Stage 1 sibling fast-path match.

The Fix

New pure module src/vdom/util/DeltaGrammar.mjs (domConstants-style plain exports — NOT a Neo class; zero Neo-global requirements at import) + unit spec test/playwright/unit/vdom/DeltaGrammar.spec.mjs:

  1. Vocabulary: the action set (8 + implicit-updateNode rule), per-op field contracts (required/optional, the insertNode payload contract (at least one of vnode/outerHTML, both legal, pinned renderer requires its active key), removeNode's conditional parentId), sentinel-encoding constants, the reserved-target id allowlist (window/document/document.body/body), and the three-sort id model (enum + per-op addressable-sort documentation — sort classification of arbitrary ids is NOT this module's job; that requires live-tree/ledger context owned by the future registry sub).
  2. Universal predicates (pure functions, batch in → structured findings out, never throw): U1 action-validity; U2 per-op required fields; U3 remove-last ordering; U4 explicit-target updateNode (id present OR reserved). A validateBatch(deltas, {rendererFlag}) aggregator returns {valid, findings[]} — findings carry {rule, deltaIndex, detail} so future guard wiring (the guards sub) can reject atomically BEFORE first apply.
  3. U5 (≤1 structural op per id per batch) ships as a documented CANDIDATE: exported, marked candidate: true, excluded from the default predicate set — per the #12942 Step-Back carry-through it stays non-guard-grade until a falsification run (owned by the guards sub).
  4. JSDoc is the canonical spec text, self-contained behavior description (Anchor & Echo; no tracking-ticket refs in durable comments per check-ticket-archaeology — provenance lives in PR/commit).

Contract Ledger Matrix

Target Surface Source of Authority Proposed Behavior Fallback Docs Evidence
src/vdom/util/DeltaGrammar.mjs (NEW) Epic #12986 / census DC_kwDODSospM4BB6bh Plain-module exports: ACTIONS, per-op field contracts, sentinel constants, reserved-target set, id-sort enum, U1U4 predicates, U5 candidate, validateBatch() n/a (new surface; nothing imports it until the guards/capture subs land) Module JSDoc = spec text Census §B table, peer-verified 2×; sibling-placement precedent src/vdom/util/* + DeltaUpdates.mjs:4 cross-realm import
src/main/DeltaUpdates.mjs dispatch (EXISTING — encoded, not modified) Source Encoded as vocabulary data; this ticket does not modify DeltaUpdates unchanged unchanged DeltaUpdates.mjs:183-943 read in full for the census; :940 fallback verified
src/vdom/Helper.mjs producer contracts (EXISTING — encoded, not modified) Source Encoded as per-op field contracts unchanged unchanged Helper.mjs:64-201,291,665-805,833-835 verified
test/playwright/unit/vdom/DeltaGrammar.spec.mjs (NEW) unit-test skill conventions Red/green coverage per AC6 n/a spec @summary Sibling: test/playwright/unit/vdom/* suite

Decision Record impact: none — first formalization of the delta wire contract; an ADR may graduate from Epic #12986 once the layer stabilizes (explicitly NOT this leaf's scope).

Acceptance Criteria

  • src/vdom/util/DeltaGrammar.mjs exports the action vocabulary (8 actions), the implicit-updateNode rule, per-op field contracts (incl. the insertNode payload contract — at least one of vnode/outerHTML, both legal, a pinned renderer requires its active key — and removeNode's conditional parentId), sentinel-encoding constants, the reserved-target allowlist, and the id-sort enum with per-op addressable-sort documentation.
  • U1–U4 implemented as pure, never-throwing predicates + validateBatch() aggregator returning structured findings ({rule, deltaIndex, detail}).
  • U5 exported as a documented candidate (candidate: true), excluded from validateBatch()'s default rule set.
  • Pure-module discipline: importable in the Node unit env with no Neo globals required at import time (domConstants precedent).
  • Unit spec: legal-batch exemplars pass (differ-shaped updateNode runs incl. recycling patterns, fragment/vtext removes with parentId, remove-last ordering, keyless-reserved-target updateNode) AND historical illegal shapes produce findings (unknown action, missing required fields per op, remove-before-default ordering, id-less updateNode without reserved target, vnode+outerHTML both present).
  • Module JSDoc reads as the self-contained spec (no tracking-ticket refs in durable comments).

Out of Scope

  • Wiring guards at Main pre-apply + the useDeltaGrammarGuards config + the rejection/error surface (guards sub).
  • The unified capture API / epoch markers / four-pattern subsumption (capture sub).
  • Operation-conditional signature shape-classes (signatures sub; OQ2).
  • The stateful coherence registry / live-id ledger / attributes.id re-keying (registry sub).
  • U5 falsification run (guards sub, before any guard-grade promotion).
  • Replay fixtures (OQ5 versioning gate) + NL streaming (OQ4).
  • Any modification to DeltaUpdates.mjs / Helper.mjs behavior.

Avoided Traps

  • Kernel as a Neo class: a Neo.core.Base subclass drags lifecycle + globals into Main-thread import paths; the domConstants plain-module precedent is the correct weight.
  • Magic id-sort classifier: an id string does not reveal its sort; pretending otherwise bakes a false oracle into the kernel. The kernel exports the model; classification needs live context (registry sub's job).
  • Throwing predicates: validators that throw recreate the partial-application hazard they exist to prevent; findings-out, never-throw.
  • Exact-count shape exemplars in the spec: the #12941 rot class; spec asserts findings presence/absence and rule identity, not delta counts.

Related

Epic #12986 (parent). Source Discussion #12942 (closed RESOLVED; census DC_kwDODSospM4BB6bh). Adjacent leaves to follow: capture API, guards, signatures, registry. Sibling context: #12940 (logDeltaUpdates docs), #12931 (NL motion granularity).

Live latest-open sweep: checked latest 20 open issues at 2026-06-12T11:24Z; no equivalent found (closest: #12986 itself = the parent). A2A in-flight sweep: clean (last-30-min claims are @neo-gpt's #12971/#12985 review lanes; the only kernel-scope claims are this author's own broadcasts).

Origin Session ID: 3e1566f6-6336-4db4-8726-189004578d8b

Retrieval Hint: "delta grammar kernel census vocabulary universal predicates validateBatch DeltaGrammar.mjs"

Evolution note (2026-06-12, review cycle 1): payload-contract wording aligned with source reality per PR review — manual producers legally emit insertNode with BOTH vnode and outerHTML (src/mixin/VdomLifecycle.mjs:590), so the contract is at-least-one with the pinned renderer requiring its active key; the prior XOR phrasing was the drift. Module export renamed payloadOneOfpayloadAnyOf accordingly.

tobiu referenced in commit 6dd5d4c - "feat(vdom): delta-grammar kernel — op vocabulary + universal batch predicates (#12987) (#12988) on Jun 12, 2026, 2:45 PM
tobiu closed this issue on Jun 12, 2026, 2:45 PM