# NAC3 v2.2 -- roadmap > **v2.3.0-alpha.1 preview now available (2026-05-12)** -- adds `NAC.invoke` and `NAC.utter` for payload-bearing actions. The v2.2 contracts described in this document still hold; v2.3 is additive. See SPEC.md sec 5.1.4 and CHANGELOG for details. NAC3 = **Native Agent Contract**. Started 2026-05-09. This file accumulates the evolution items for the next minor of the NAC3 spec. Each section is self-contained: a problem statement, the bug class it prevents, the proposed contract change, and the implementation notes. **Status as of 2026-05-10:** v2.2 SHIPPED. Items V22-01 + V22-02 + V22-03 + V22-04 are all in `js/nac.js` + the `@nac3/runtime` 2.2.0 NPM package. This file is now the canonical changelog for the version. | Item | Status | Commit | |------|--------|--------| | V22-01 strict validator | SHIPPED | 6c2b1866 | | V22-02 bindAction helper | SHIPPED | 6c2b1866 | | V22-03 locale detector hardening | SHIPPED 2026-05-09 | f631d77a | | V22-04 tab_by_label parens normalisation | SHIPPED 2026-05-09 | f631d77a | | V23-01 field editor primitive (preview) | DEMO SHIPPED 2026-05-11 | (example-v23-editor.php + packages/nac/test/v23-editor.mjs 8/8 PASS) | --- ## V22-01 -- Constructor (`NAC.register`) becomes a strict validator **Problem class.** Brownfield demos can declare manifest elements with non-canonical role values (`role:'navigation'` on a tab, `role:'button'` instead of `'action'`, etc). The current constructor accepts whatever shape it receives and stores it as-is. The bug only surfaces in runtime when the API (`NAC.tab()`, `NAC.tab_by_label()`, `NAC.click()`) cannot find the element, because the canonical DOM query (`[data-nac-role="tab"]`) does not match. By then the demo is already deployed, the user already hit the broken voice command, and the runtime correctly throws `tab X missing` -- a misleading error since the element IS in the DOM, just under the wrong role. **Concrete trigger (2026-05-09).** Pablo dictates `ve a pestana permisos` on `example-v21-data-table.php`. The LLM resolves to `NAC.tab('invoice_edit_modal','tab.permissions')`. The button exists in the DOM but with `data-nac-role="navigation"` (set by the demo author on HTML-semantic grounds: tabs ARE navigation). The runtime throws "tab tab.permissions missing" even though the button is right there. Same root cause caused `tab_by_label('Lines (collection)')` to miss earlier in the same session. **Why three guard layers should have caught it but didn't.** | Layer | Should detect... | What it does today | |---|---|---| | Pre-commit lint | role drift in PHP/HTML demo files | does not exist | | `NAC.register(manifest)` (register-time) | non-canonical roles, id/role mismatch | accepts everything silently | | `NAC.validate_global()` (lint-time) | role drift inside `m.elements[]` | only checks `m.tabs[]` presence | The runtime API layer (`NAC.tab` etc.) is the **fourth** guard, and the only one that fires today -- as a runtime error to the end user. By then the cost is highest. **Proposed contract change for v2.2.** `NAC.register` MUST validate the manifest before storing it. Validation rules: 1. **Known role enumeration.** Every `m.elements[i].role` must be a member of the canonical role set (extends `_CLICK_EVENT_FAMILY`): ``` action, field, option, tab, breadcrumb-item, accordion-toggle, step, pagination-item, confirm-button, sort-control, filter-control, data-table, plugin, section, region, navigation, banner, complementary, contentinfo, button ``` Unknown roles -> `console.error` + reject the register call. Landmark roles (`navigation`, `banner`, etc) are accepted but only on elements whose corresponding DOM node is a region container, not a clickable widget. 2. **Id/role coherence.** If `e.id` matches `^tab\.` then `e.role === 'tab'` is required. If `e.id` matches `^modal\.` then `e.role === 'action'` (or the action's sub-role) is required. Any mismatch -> `console.error` + reject. The grammar of the id field is a contract too; today it is implicit. 3. **DOM coherence (best effort).** When `register` is called after the DOM is parsed (the typical path), look up `[data-nac-id=""]` in the DOM. If found and its `data-nac-role` differs from `e.role`, `console.error` + reject. This catches the case Pablo hit on 2026-05-09: the manifest says `role:'tab'` but the HTML still says `data-nac-role="navigation"` (or vice versa). When called before the DOM is ready, defer the check to a `DOMContentLoaded` post-pass. 4. **Migration helper (one release window).** For v2.2.0 the above produce `console.error` but do NOT throw -- adopters need a window to migrate. Starting v2.3.0 they throw a `RegisterError` and the manifest is rejected outright. Tracked in the runtime via `NAC.STRICT_VALIDATION` flag defaulting to `false` in v2.2 and `true` in v2.3. **`NAC.validate_global()` extension.** Add three new findings: - `manifest_role_unknown` -- an element's role is outside the canonical set. - `manifest_dom_role_mismatch` -- the manifest's role for `` differs from the DOM's `data-nac-role` attribute. - `tab_role_drift` -- a `