Thing 2.0 map: model page, consumer contract, cut order; fork prior art

Imagination pass imagination-thing2-contract (session self-2026-10-10c-eureka).
docs/thing2-map.md holds body facts P1-P11, the model page, the consumer
contract (closed node set, compositions, one binding path, one command
envelope), the fate of every Thing 1 kind, the authority map and the cut
order by deliverable. docs/research/thing2-contract-forks-prior-art.md holds
the prior art for the forks code-home, thing-script-form,
advertisement-owner and port-cutover.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Meta Vens 2026-10-10 20:06:28 +02:00
commit fa7d95191a
2 changed files with 466 additions and 0 deletions

View file

@ -0,0 +1,97 @@
# Thing 2.0: prior art for the contract forks (Imagination, 2026-10-10)
Forks served, all raised in `thing2:campaign:self`: `thing2:question:code-home`,
`thing2:question:thing-script-form`, `thing2:question:advertisement-owner`,
`thing2:question:port-cutover`. Agent `imagination-thing2-contract`, session
`self-2026-10-10c-eureka`. Facts read today through `gh api repos/<r>/contents`
(top-level and named subdirectories); **[background]** marks prior knowledge not
re-fetched in this pass. It does not repeat `thing2-foundation-prior-art.md` or
`thing2-text-input-a11y-prior-art.md`.
## 1. How established toolkits split their code across repos (fork `code-home`)
| Project | What one repo holds (read today) | What lives in other repos |
| --- | --- | --- |
| Slint (`slint-ui/slint`) | `internal/` = compiler, core, interpreter, `backends/` (android-activity, linuxkms, qt, selector, testing, winit), `renderers/` (anyrender, femtovg, skia, software); `api/` = cpp, node, python, rs, wasm-interpreter. `api/node` is a Rust napi crate (`Cargo.toml`, `build.rs`, `binding.cjs`) in the same tree. | none needed for the shipped language APIs |
| egui (`emilk/egui`) | `crates/` = egui, epaint, emath, ecolor, eframe, egui-wgpu, egui-winit, egui_glow, egui_kittest (tests), egui_extras, egui_plot, demo app | community backends (Bevy, macroquad) live with their hosts **[background]** |
| iced (`iced-rs/iced`) | core, runtime, widget, renderer, wgpu, tiny_skia, winit, graphics, futures, test, tester | none for first-party backends |
| Xilem (`linebender/xilem`) | masonry, masonry_core, masonry_winit, masonry_testing, xilem, xilem_core, xilem_web | Parley and Vello are separate linebender repos (independently consumed libraries) |
| AccessKit (org) | `accesskit` = schema crate, consumer, every platform adapter | `accesskit-c` (C bindings, CMake) and `accesskit-python` (maturin) are separate repos |
| Flutter | `flutter/flutter` now holds `engine/` beside `packages/`; `flutter/engine` is archived (last push 2025-02-25). The engine was merged into the framework repo to end cross-repo "rolls" **[background on the reason]**. | plugins and federated platform packages live in `flutter/packages` **[background]** |
| Adaptive Cards (`microsoft/AdaptiveCards`) | `source/` = android, dotnet, ios, nodejs, uwp, shared: every host renderer of one card schema in one repo; `source/nodejs` adds `adaptivecards-templating`, `ac-typed-schema`, the designer | hosts (Teams, Outlook) are not in the repo |
| Taffy | one crate, `test_fixtures/` + generated tests | Python binding `stretchable` is a third-party repo |
Pattern: the schema, the core and the first-party backends and language bindings
share one repo when they share one build system or can be built from one
workspace. Separate repos appear where a binding needs a different packaging
toolchain (AccessKit C/Python) or is an independently consumed library (Parley,
Vello). Flutter is the counter-example in the other direction: two repos with a
version roll between them were merged into one.
The operator's July concern (`thing2-foundation-prior-art.md` 1.1, 2026-07-10)
names what made the Thing 1 monorepo risk a swamp: "every runtime build system,
plugin fixture, platform SDK, and product demo starts sharing one git status".
Under the Thing 2.0 target, plugin fixtures and per-renderer lowerers do not
exist (invariants `compose-not-kind`, `closed-commands`); the remaining platform
SDKs with their own toolchains are Unity (UPM, Unity Editor) and Flutter (pub,
Android/iOS SDKs). Neither has a live consumer (census section 5: EveUnity and
EveFlutter are BUILD only; Aetheria publishes nothing).
## 2. How surface documents are authored (fork `thing-script-form`)
- **Typed document published by services, rendered by hosts:** Adaptive Cards
(JSON card schema; `ac-typed-schema` package read today; hosts render with
their own HostConfig theme **[background]**). Data binding is a separate
templating package (`adaptivecards-templating`, read today) that expands a
template against data before render. Slack Block Kit: JSON blocks with builder
libraries per language **[background]**.
- **Text language compiled to a typed tree:** Slint `.slint`, compiled by
`internal/compiler` and also loadable at run time by `internal/interpreter`
(both read today as directories); QML, XAML **[background]**.
- **Code builders only:** Flutter, SwiftUI, Jetpack Compose, egui, Xilem
**[background]**. All are single-process: the tree never crosses a wire.
- **Body:** Thing 1 has both. The text DSL `web/eve-dsl.js` (544 lines) parses
`.eve` fixtures; its local mesh lets a button `append`/`set` state directly in
the renderer and computes `derive ... count|latest` there (`eve-dsl.js:103-155`).
The C# builder `packages/org.gamecult.eve.surface/Runtime/EveSurfaceBuilder.cs`
(876 lines) and every provider's hand-built JSON (census) are the builder side.
Ruling `thing:ruling:operator-dsl-thing-script` names the authoring DSL Thing
Script and keeps the retained tree schema-named; target r1 calls the published
typed documents Thing Script.
## 3. Who owns service discovery (fork `advertisement-owner`)
- Body: `gamecult.eve.provider_advertisement.v1` (329 lines, 18 properties,
11 required) carries Verse discovery (`verseId`, `serviceId`,
`canonicalService`, `locatedService`, `cultMeshAddress`, `freshness`,
`schemas`, `witnesses`) beside UI facts (`surfaces`, `commands`). Odin reads it
as `EveProviderAdvertisementRecord { value: Value }`
(`Odin crates/odin-core/src/documents.rs`, origin/main): an untyped blob.
CultLib's CultMesh runtimes declare no service-advertisement document of their
own (`git grep -i advertis` over `src/` and `packages/cultmesh-ts/src`: only
transport endpoint and certificate-pin mentions). So the Verse's only provider
discovery document lives in the UI library's namespace.
- Weksa also writes `gamecult.eve.interface_binding.v1` holding a third copy of
its routes (`provider.endpoints`, `routes`) and its whole surface
(`weksa-daemon.mjs:321-341`); Odin reads it as
`EveInterfaceBindingCompatRecord { value: Value }`.
- Prior art: app or service manifests are separate from UI payloads in the
service-driven UI systems above: a Teams app manifest declares bots and
commands, and cards are message content **[background]**; service registries
(Consul, Kubernetes Services) are not owned by any UI toolkit **[background]**.
## 4. How a cutover crosses many live consumers (fork `port-cutover`)
- Thing 1 plan (campaign `thing` revision 5): one Verse window, every E1 carrier
flips together, because a rename of one kind cannot be read under two ids
without an alias (ruling `thing:ruling:operator-compat-rebuild` forbids it).
- Expand/contract ("parallel change") and the strangler-fig pattern migrate
consumers one at a time while the old and new paths coexist, then delete the
old path when the last consumer leaves **[background: Fowler]**.
- Body: Thing 2.0 kinds are not renames of Thing 1 kinds. The document shape,
the command envelope and the binding model differ (see the map's model page),
so an aggregator reading both is reading two kinds, not one kind under two ids.
Live viewers today: Ghostlight's web client (vendored `eve-browser-lowering`,
`ajv` in `web/package.json`) and the Nightwing browser reference
(`nightwing-eve-browser-reference`, `Repo = "Eve"`, runtime-enforced, `Deploy =
$null`, gamecult-ops `idunn-deployment-targets.ps1:262-272`).

369
docs/thing2-map.md Normal file
View file

@ -0,0 +1,369 @@
# Thing 2.0 map
Campaign `thing2` in the eureka mind. Imagination pass `imagination-thing2-contract`,
session `self-2026-10-10c-eureka`, 2026-10-10. Target `thing2:target:r1`
(`docs/thing2-target.md`). Rulings in force and cited by id:
`thing:ruling:thing2-one-solver-taffy`, `thing:ruling:thing2-thing-owns-text`,
`thing:ruling:wire-rename-freeze-fold`, `thing:ruling:operator-compat-rebuild`,
`thing:ruling:hold-renames-for-thing2`, `thing:ruling:operator-dsl-thing-script`,
`thing:ruling:operator-member-names-keep`, `thing:ruling:operator-aetheria-eve-rename`.
This page holds body facts, the model page, the consumer contract at the level
consumers need, the cut order at the level of deliverables, and rationale. The
questions it raises are documents in the mind (`thing2:question:*`); it does not
restate their options. Research for the forks:
`docs/research/thing2-contract-forks-prior-art.md`. Evidence it builds on:
`docs/research/thing2-consumer-census.md` (census, cited as C§n, BD1-BD9),
`docs/research/thing2-foundation-prior-art.md`,
`docs/research/thing2-text-input-a11y-prior-art.md`, and `docs/thing-campaign.md`
(rename table T, edges E1-E7, body facts B1-B28).
## Body facts
Probes run 2026-10-10 against `origin/<default>` after `git fetch` unless a
local path is named. Scripts are in the session scratchpad
`imagination-thing2-contract/`.
**P1. The Thing 1 contract is 38 schemas, most of them process machinery.**
`p1_schemas.py` over Eve `schemas/` at `a758787`: 38 files. Eleven describe
conformance, hand-off, split-target, release, capture and witness bookkeeping
(`conformance_*`, `*_handoff`, `runtime_*`, `capability_matrix`,
`provider_scenario`); five describe one renderer's output (`unity_*`,
`electron_shell_projection`, `web_layout_probe`, `tui_grid`); seven describe the
plugin system (`plugin*`). Every schema but `command_invocation`,
`command_result`, `entity_soa_view`, `input_*` and `asset_catalog` sets
`additionalProperties: true`.
**P2. Commands travel in four overlapping envelopes, and the result is hand-built
JSON.** `command.v1` (descriptor, open), `command_invocation.v1` (closed),
`command_receipt.v1` (open, `state` enum `accepted|denied|pending|reconciled`)
and `command_result.v1` (closed: exactly `schema, receipt, transientProjection,
pluginPayload, draftDirective`). Ghostlight `crates/ghostlight-dungeon/src/eve.rs`
at `327cbe3`: `validate_invocation` (`:665-700`) re-checks the envelope by string
comparison; `command_result` (`:734-`) takes `state: &str` and builds the receipt
with `json!`; its own log filter names a state `"completed"` that the receipt
enum does not admit (`:746`). Today it passes only `accepted` and `denied`
(tally of call sites). The browser rejects a flat result whole through `ajv`
(`web/package.json` depends on `ajv`; `eve.rs:723-731` comment). Nothing but
convention stands between a provider and a result the viewer refuses.
**P3. One surface kind has two schemas and is stored as an untyped blob.**
`surface.v1` declares `type: const "surface-state"`; `surface_state.v1` exists
only as code: CultLib `packages/cultcache-ts/src/swarm-documents.ts:82-96`
(members `providerId, title, version, updatedAt, surface: object`) and Odin
`crates/odin-core/src/documents.rs` (`EveSurfaceStateRecord { surface: Value }`;
advertisement and interface binding are `{ value: Value }`). The load-bearing
shape is JSON inside CultCache.
**P4. `interface_binding.v1` is not a binding.** weksa
`scripts/weksa-daemon.mjs:321-341` writes routes (`endpoints`, `routes`,
`cultMeshAddress`) and the whole operator surface into it; its document type
parses anything (`schema: { parse: (value) => value }`, `:418`). It is a third
copy of the advertisement's routes and the surface.
**P5. The text DSL lets the renderer write state.** `web/eve-dsl.js:103-155`:
`var`, `collection`, `derive` (`count`, `latest`) live in a renderer-local mesh,
and `applyAction` appends or sets values there on a button press
(`web/fixtures/reactive-composition.eve`: `button "Append Event" append
swarm.events ...`).
**P6. A domain-looking kind is layout plus a server-side command.** Muninn
`crates/sleipnir-daemon/src/main.rs:1374-1415`: `binding.target` is a labelled
target with `x, y, anchorX, anchorY` over a controller image; "listening" is the
daemon's `pending_learn` state. Absolute positioning (Taffy `position:
absolute` with insets), a button and a bound label compose it.
**P7. Login needs a host effect, not a plugin payload.** Ghostlight
`web/src/heimdall-access-adapter.ts:48-66`: a `pluginPayload` of schema
`heimdall.auth_navigation_receipt.v1` makes the client call
`window.location.assign(url)`; on return it reads the URL and resumes
(`:82`, `:134`).
**P8. Two live viewers render surfaces.** Ghostlight's web client vendors
`@gamecult/eve-browser-lowering` and `eve-contracts` from `vendor/eve`
(`web/package.json`). gamecult-ops `scripts/idunn/idunn-deployment-targets.ps1:262-272`
at `9aed01f`: `nightwing-eve-browser-reference`, `Repo = "Eve"`,
`Status = "runtime-enforced"`, `Deploy = $null`. Odin aggregates and renders
nothing.
**P9. CultCache-rs members are serde types.** CultLib
`packages/cultcache-rs/src/lib.rs:5501-5520`: a `DatabaseEntry` whose member is a
struct holding an enum round-trips through the MessagePack store. Whether the TS,
C# and Python CultCache runtimes decode a recursive tagged union written by Rust
with parity was not established in this pass. It is the first thing the
contract cut proves (see the cut order).
**P10. The stale `.voidbot` lines are still on Eve main.** `a758787`:
`.gitignore:28-30` and `scripts/stage-to-eve.ps1:24` (`--exclude=.voidbot`).
`git grep -n '\.voidbot' -- .gitignore scripts` prints exactly those four lines;
`Sources/EVEViewController.mm` mentions `voidbot.swarm` (no dot) and is not
matched.
**P11. The campaign's repo list has room for the early cuts only.** Mind,
`thing2:campaign:self`: repos `GameCult/Eve`, `GameCult/gamecult-ops`.
`eureka-substrate:cut_spec:cut-campaign-repos.r1` (target-revision repo sets, up
to 16) is in force with no report; both its questions are answered
(`eureka-substrate:ruling:campaign-repo-set-home`, `:campaign-repo-removal`).
## The model page
Every persistent kind Thing 2.0 has. Ids follow rename table T: schema
`gamecult.thing.<x>.v<n>`, mesh key `thing:<family>:<id>`. No row keeps a
compatible eve id (`operator-compat-rebuild`).
| Kind | What names it | What happens to it over time | Who decides (one writer) |
| --- | --- | --- | --- |
| Thing Script document (a published surface) | schema `gamecult.thing.surface.v1`; key `thing:surface:<providerId>.<label>`; `version: u64` in the record | Republished whole when its structure changes, `version + 1`; core ignores a version at or below the one it holds. Live values never cause a republish (they arrive through bindings). Retired when the provider deletes the record. A contract revision is a new `v<n>`; stores holding the old one are deleted and rebuilt by their daemon | The publishing provider. Core and viewers only read |
| Composition | schema `gamecult.thing.composition.v1`; key `thing:composition:<publisher>.<name>.v<n>` | Immutable once published: a change is a new `v<n>` under a new key; references pin the version. The standard library (`thing.std.*`) ships compiled into core and is also published by the Thing repo's release | The publisher (a provider, or the Thing repo for `thing.std`). Core resolves and expands; nothing else writes |
| Binding | not a document: a field of a node in a Thing Script document or composition (`Bind`) | Lives and dies with the document that holds it | Declared by the document's writer; resolved only by core |
| Bound state record | the provider's own schema and key (e.g. `voidbot.swarm_control_state.v1`) | The provider's lifecycle | The provider. Core never writes typed state; a change goes through a command |
| Viewer session state (focus, scroll offsets, editing drafts, selection, IME composition, open overlays) | the core instance; node ids within a resolved document | Not persisted and never published; dies with the session. A draft that must outlive the session is state, and state belongs to a provider | Thing core |
| Command declaration | not a document: an entry of the Thing Script document (`CommandDecl`, id local to the document) | With its document | The document's writer |
| Command invocation | schema `gamecult.thing.command_invocation.v1`; key `thing:invocation:<invocationId>` (a UUID minted by core per user action, also the idempotency key) | Written once, never edited; retained by the provider's command boundary until its final result, then the provider's retention | Thing core, on behalf of the viewer; the transport sets the caller identity |
| Command result | schema `gamecult.thing.command_result.v1`; key `thing:result:<invocationId>.<seq>` (`seq` from 1; a pending result may be followed by a final one) | Written once per `seq`; the final outcome ends the invocation | The provider that handles the command, through the contract's constructors only; core validates on receipt |
| Asset catalog | schema `gamecult.thing.asset_catalog.v1`; key `thing:asset_catalog:<providerId>.<catalogId>`; `version: u64` | Republished whole, `version + 1`; image nodes reference `catalogId` + asset id | The provider |
| Theme | schema `gamecult.thing.theme.v1`; key `thing:theme:<themeId>` | Versioned like a document; documents reference tokens, never a theme | The host application that embeds Thing (the GameCult brand theme is one such document); `thing.std` default theme in core |
| Provider discovery (today `gamecult.eve.provider_advertisement.v1`) | Under the recommended option of `thing2:question:advertisement-owner`, not a Thing kind: owned by the Verse discovery owner and renamed once there | Until its owner lands, the eve advertisement is read through core's discovery port, unchanged | Pending `thing2:question:advertisement-owner`; the provider writes, Odin aggregates |
| Accessibility tree | not persisted: derived by core per frame from the resolved document and layout (AccessKit tree updates) | Rebuilt on every change | Thing core; backends bridge to the platform |
| Render-command list | not persisted and never published: in-process from core to backend | Per frame | Thing core |
| Geometry and render-command goldens | test fixtures in the Thing repo (`tests/goldens/`) | Regenerated only by a reviewed change to core; a backend never writes one | The Thing repo's core tests |
## The consumer contract
Consumers see four things (target, "The consumer contract"). Each is a set of
types in the contract crate and its language packages; nothing in core is
visible to them (`narrow-consumer-contract`).
### 1. Thing Script
**The node set is closed.** A document's node is exactly one of:
| Node | Carries | Covers today's kinds (census C§7) |
| --- | --- | --- |
| `Box` | layout style (Taffy: display flex, grid or block; direction, wrap, gap, padding, margin; size, min, max in px, %, fr, auto, content; position relative or absolute with insets; overflow visible, clip or scroll), paint (background, border, radius as theme tokens or literal colours), accessibility role from a closed set (group, region, list, list item, heading, dialog, toolbar), label, children | `pane`, `card`, `panel`, `dashboard`, `cockpit`, `rail`, `row`, `grid`, `list`, `partition`, `surface`, `form`, `interface`, `binding.target` layout |
| `Text` | content (literal or `Bind`), text style token, wrap, max lines, selectable | `text`, `stat`, label parts of `metric` |
| `Image` | asset reference (catalog + id), fit, alt text | `image.*`, `avatar` |
| `Button` | content children, `on_activate: CommandRef`, enabled (literal or `Bind`) | `control.button`, `command` (Vili) |
| `Toggle` | checked (`Bind`), `on_change: CommandRef` | `control.toggle` |
| `Slider` | min, max, step, value (`Bind`), commit command | `control.slider`, `control.range` |
| `TextInput` | single or multi-line, value kind text or number, placeholder, initial value (`Bind`), submit command | `control.input.text`, `control.input.textarea`, `control.input.number`, `input.number` |
| `Select` | options (literal or a bound collection), value (`Bind`), command | `input.select`, `control.select`, `control.choice-group` |
| `Repeat` | a bound collection and a template subtree instantiated per item, keyed by item key; template bindings may read item members | `list ... bind <collection>` (BD5) |
| `Overlay` | anchored to a node or the viewport, modal or not, dismiss command | `modal`, `control.popup`, tooltips |
| `Plot` | a bound numeric series, closed marks (line, bar, area), axis ranges | `graph`; sparklines a dashboard consumer expects |
| `Embed` | a host slot kind (string the host declares it supports) and slot id; laid out as a leaf of a given size; draws one opaque host command | `embed.norn`, `embed.tex`, `world.scene*`, `field.*`, `canvas.editor`, game viewports |
| `Use` | a composition reference (`publisher.name.v<n>`) and typed arguments | `metric`, `inspector.kv`, `control.tabs`, `heimdall.access_gate`, `vn.stage`, every domain kind |
Every node has an id (unique within its document after expansion), `visible`
(literal or `Bind`) and an optional accessibility label. A domain need is met by
a composition (`compose-not-kind`); a need no composition can meet is an `Embed`
slot drawn by the host, never a new node.
**Compositions are documents, resolved by core.** A composition declares typed
parameters (text, number, bool, `Bind`, `CommandRef`, child slots) and a body of
nodes that reference them. Core expands `Use` by substitution only: there is no
expression language. Compositions never own commands; a composition that needs
an action takes a `CommandRef` parameter, and the command is handled by the
publisher of the document that uses it. `thing.std` provides the common ones
(`metric`, `card`, `pane`, `dashboard`, `rail`, `key-value`, `stat`), so no
consumer language re-implements them.
**Text form.** Whether Thing Script also has a text syntax beside the typed
document, and which is the authority, is `thing2:question:thing-script-form`.
The node set, compositions and rules here hold under every option.
### 2. Binding (BD1-BD9 collapse to one path)
`Bind { source: StateRef, member: MemberPath, format: Option<Format> }`, where
`StateRef` is `{ provider: Option<ProviderId>, schema: SchemaId, key: RecordKey }`
(absent provider = the document's publisher) or `Item` inside a `Repeat`
template. A bound collection is `{ provider, schema, key_prefix }` (the records of
one type) or a list member of one record. `Format` is closed: number (precision,
percent, unit), duration, timestamp, prefix and suffix text.
| Shape | Fate |
| --- | --- |
| BD1 `stateBindings` | Survives as `Bind`; `routeKind`, `routeDescription` and `sourceId` die (core routes through discovery, a document names no transport) |
| BD2 local drafts | Die as bindings: a draft is core's editing state of `TextInput`, `Slider` or `Select` |
| BD3 `writeCommand`, `captureBindings`, `draftDirective` | Fold into commands: a `CommandDecl` lists the input nodes it captures; a completed result says whether captured drafts clear |
| BD4 world-root props | Leave the contract: an `Embed` slot whose host owns the world view |
| BD5 `var`, `collection`, `derive`, `bind` | `bind` and `collection` survive as `Bind` and `Repeat`; renderer-local `var` and `derive` die (P5): derived values are published by the state's owner |
| BD6 whole-document republish | Survives for structure only (`version + 1`); per-tick republish of values dies |
| BD7 `interface_binding` | Dies (P4): routes are discovery's, the surface is the document |
| BD8 embedded documents | `Embed` |
| BD9 reload and poll | Dies: core subscribes through the CultMesh port |
### 3. Commands (one envelope)
- `CommandDecl { id, payload_schema: SchemaId, captures: [NodeId], confirm: Option<Text> }`
in the Thing Script document. A node may reference only a declared command.
- `CommandInvocation { invocation_id, surface: (key, version), command, payload, captured: [(NodeId, Value)], client, issued_at, delegation }`.
`payload` is a typed record of `payload_schema`. The caller's identity is set by
the transport session and has no field a consumer can write.
- `CommandResult { invocation_id, seq, outcome }`, with `outcome` one of
`Pending { message }`, `Completed { source_version, clear_drafts, effect }`,
`Denied { reason, node }` and `Failed { reason }`. `effect` is closed:
`OpenSurface(key)` or `OpenExternal { url, resume: CommandRef }` (P7). The
receipt is the result: `command.v1`, `command_receipt.v1`,
`transientProjection` and `pluginPayload` have no successor.
- Handlers in every language receive a decoded invocation and return an
`Outcome`; the language package writes the result. No package exposes a way to
write a result record from a map, a JSON value or a string state.
### 4. Advertisement
What Thing needs from discovery is only "this provider exists and is reachable
here": its surfaces are its `gamecult.thing.surface` records and its commands are
declared inside them. Who owns discovery is
`thing2:question:advertisement-owner`.
### Rules that must die under their own mutation
Each is a test or negative check in the cut that lands it; deleting the rule's
code fails it.
1. A result decodes only into `CommandResult`; a record with an extra or missing
field is refused at decode in every language package (golden fixture with an
extra field).
2. Core turns a result that fails to decode, or one for an unknown
`invocation_id`, into a visible `Malformed` state for that invocation and a
log line; no invocation stays pending past its timeout without one.
3. `outcome` is an enum in every package; no API takes a state as a string.
4. A document with an unknown node kind, an undeclared `CommandRef`, or a
duplicate node id after expansion is refused whole; nothing falls back along
a kind path.
5. A `Use` that does not resolve renders a visible placeholder with a diagnostic;
it is never dropped silently.
6. A `Bind` whose record has a different schema, or whose member is missing,
renders a typed binding error; no value is coerced.
7. Core holds no API that writes typed state; its only outbound write is a
`CommandInvocation` (negative check over the core crate).
8. No backend crate or package depends on `taffy` or `parley` (negative check on
the dependency tree): backends receive positioned commands and shaped glyph
runs.
9. A lower or equal `version` of a document is ignored.
10. Geometry and render-command goldens for the fixture set are byte-identical
across runs; a backend's parity test consumes the core's list and asserts it
drew every command it was given.
### Boundaries
- Consumers depend on the contract packages only. Core, backends and the
standard library are internal.
- Core reaches CultMesh, the clock, fonts and the platform through narrow ports
(traits), so a parser, resolver, layout or command test runs without a daemon.
- Backends draw and bridge: input events in, render commands and the
accessibility tree out. IME uses the platform channel the ruling names (hidden
textarea on the web).
## Thing 1 kinds and their fate
| Thing 1 id (`gamecult.eve.*`) | Fate |
| --- | --- |
| `surface.v1`, `surface_state.v1` (code only) | Collapse into `gamecult.thing.surface.v1` (P3) |
| `command.v1`, `command_invocation.v1`, `command_receipt.v1`, `command_result.v1`, `operation_payload.v1` | Collapse into `command_invocation.v1` and `command_result.v1` (P2) |
| `local_draft.v1`, `interface_binding.v1` | Die (BD2, P4) |
| `asset_catalog.v1` | Survives as `gamecult.thing.asset_catalog.v1` |
| `provider_advertisement.v1` | Per `thing2:question:advertisement-owner` |
| `plugin.v1`, `plugin_advertisement.v1`, `plugin_abi.request.v1`, `plugin_abi.response.v1`, `plugin_abi_fixture.v1`, `plugin_receipt.v1`, `plugin_witness.v1`, `plugin_handoff.v1` | Die: domain plugins become compositions or `Embed` slots |
| `conformance_*`, `capability_matrix.v1`, `provider_scenario.v1`, `provider_handoff.v1`, `runtime_*`, `local_provider_catalog.v1` | Die: parity is geometry goldens; discovery is not a fixture catalog |
| `unity_*`, `electron_shell_projection.v1`, `web_layout_probe.v1`, `tui_grid.v1` | Die: renderer outputs are not contract kinds |
| `entity_soa_view.v1`, `input_bindings.v1`, `input_capability.v1` | Die (no carrier outside the kernel, B25) |
| `mimir.eve_*`, `cultmesh.eve_surface.v0` | Mimir's own ids, renamed by Mimir's port when it is revived (Mimir is archived) |
## The authority map
- **Owner of geometry, text, focus, editing, accessibility, binding resolution
and result validation:** Thing core (`one-solver`, `core-owns-text`,
`live-state-core`).
- **Owner of a surface's content and of its commands' outcomes:** the provider
that publishes the document.
- **Owner of the contract types:** the contract crate; each language package is
held to the crate's golden wire fixtures and owns nothing of its own.
- **Derived:** the resolved tree, layout, render-command list, accessibility
tree and goldens. Viewer session state is core-owned and never published.
- **Demoted:** the browser is no longer the layout oracle; renderers are no
longer owners of kinds, drafts, pointer resolution or result validation; the
text DSL's local mesh is no longer a writer of state; `surface_state`,
`interface_binding` and the receipt are no longer documents.
- **Forbidden writers:** a backend computing layout or shaping text; a provider
writing a result through anything but the package constructor; core writing
typed state; a document naming a transport; any compatible eve id; a new node
kind added for one domain.
- **Shared paths:** every viewer (web shell, Ghostlight's client, TUI, future
hosts) embeds the same core; every consumer language publishes through a
package held to the same goldens.
- **Deletion line:** Thing 1's tree in the kernel repo, its six family repos and
every `gamecult.eve.*` reader and writer in the ported consumers. Each port
deletes its consumer's Thing 1 path in the same cut.
## Cut order (deliverables)
0. **`stale-voidbot-cleanup`** (Eve main, any time): spec
`thing2:cut_spec:cut-stale-voidbot-cleanup.r1`. Owned here by
`thing:follow_up:stale-voidbot-cleanup-on-main`.
1. **Contract** (code home per `thing2:question:code-home`): the contract crate
with the types above, published schemas `schemas/gamecult.thing.*`, golden
wire fixtures, and the TypeScript and C# packages held to them (the live
consumers' languages: Rust, JS/TS, C#). First proof: a Rust-written recursive
node union decodes with parity in the TS and C# CultCache runtimes (P9); if it
does not, that stops the cut as a CultLib fork. Python and Kotlin packages
follow as their own deliverables. Blocked on `thing-script-form`,
`advertisement-owner` and `code-home`.
2. **Core**, in three cuts: (a) resolution, compositions with `thing.std`,
binding through the CultMesh port, Taffy layout, render commands, the command
dispatcher and result validation, goldens; (b) Parley text, focus, editing and
IME state; (c) the AccessKit tree.
3. **TUI backend** (Rust): cell measurement in the Taffy measure function,
terminal drawing, keyboard input. First, because it exercises the whole
pipeline with no platform bridge and gives agents a reader.
4. **Web backend and shell**: core to wasm, a thin JS host (canvas drawing,
hidden-textarea IME, DOM accessibility mirror), the discovery client, and the
web shell that replaces the Nightwing browser reference.
5. **Consumer ports**, one cut per live consumer, each carrying its rename and
deleting its Thing 1 path (`consumer-port-carries-rename`). Cutover shape per
`thing2:question:port-cutover`. Proposed order: Odin (reads Thing 2 kinds),
VoidBot (richest binding, one command), Ghostlight with Heimdall (text inputs,
the auth effect, the flat-result case; two cuts, Heimdall's access gate
becomes a composition), Muninn and sleipnir, weksa, Stonks, Vili, Gjallar,
StreamPixels, then gamecult-ops (the Nightwing target), then CultLib (its eve
document definitions in `cultcache-ts` and `cultmesh-kotlin` are deleted, B26).
These need more repos than the campaign lists: `thing2:question:campaign-repo-limit`.
6. **Retirement**: delete Thing 1's tree from the kernel repo; archive the six
family repos; the repo renames and site prose re-specified on what survives
(`thing:cut_spec:cut-repo-renames.r1` and `cut-site-prose.r1` revised).
**Repos this map retires:** EveUnity, EveFlutter, EveElectron, EveTui, EvePlugins
and EveConformance (no Thing 2.0 role: no domain kinds, no per-renderer
lowerers, parity by goldens). How they are named when archived is
`thing2:question:retired-repo-names`. AetheriaEve stays under its rulings. The
kernel repo survives as Thing under the recommended `code-home` option; once
that question is ruled, the hold on `repo-renames` and `site-prose`
(`thing:ruling:hold-renames-for-thing2`) has its answer.
**Parked (dormant or blocked) consumers:** Bifrost, repixelizer, Mimir, Hermodr,
Loki, Sai, AquaSynth, Brokkr, Ymir. Their Thing 1 pins keep resolving at the
archived repos; each ports when it is next revived. Unity and Flutter backends
have no live consumer and are parked until one appears.
## Rationale
- **Why the contract before core.** Every later cut, and every consumer port,
depends on the types; the cross-runtime decode of a recursive union (P9) is the
riskiest unknown and is cheapest to find before core exists.
- **Why TUI before web.** It has no platform bridge, so the first end-to-end run
tests core, not a host; the web backend then adds only drawing, IME and the
accessibility mirror.
- **Why compositions are resolved by core, not expanded by each language's
builder.** Builder-side expansion would put `metric`, `card` and every shared
composition in each consumer language, which is Thing 1's per-renderer lowerer
problem moved to the publishers.
- **Why no expression language in documents.** P5 shows where it leads: the
renderer becomes a second owner of derived state. Owners publish derived
values; `Format` covers presentation.
- **Why Odin ports first.** It is the aggregator every viewer discovers
through; under either cutover option it must read Thing 2 kinds before a
provider publishes them.
- **Why the result has no free-form payload.** P2 and P7: the only live uses of
`pluginPayload` and `transientProjection` were a navigation and a status,
which are `effect` and `outcome`.