36 KiB
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:operator-dsl-thing-script, thing:ruling:operator-member-names-keep,
thing:ruling:operator-aetheria-eve-rename, and the six thing2:ruling:* of
2026-10-10 (code-home, thing-script-form, advertisement-owner,
port-cutover, campaign-repo-limit, retired-repo-names).
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 P1-P16 ran 2026-10-10, P17-P19 2026-10-12, 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).
P12. Rust CultCache writes a union as serde's external tag; the C# reference
writes [key, fields]. They do not read each other. Probe pass
imagination-thing2-contract-cut, 2026-10-10, local scratch
imagination-thing2-contract-cut/{rs,cs,ts} against CultLib 6bdc23c9. One
record type in all three runtimes: id, version, root: Node with Node
one of Box { id, children: [Node] }, Text { id, content },
Use { id, composition, slots: [Node] }, and outcomes: [Outcome] with a unit
arm. Rust (#[derive(DatabaseEntry)], serde enums) writes the payload
94 af..31 07 81 a3"Box" 92 ...: each arm a one-entry map keyed by the variant
name, fields positional, a unit arm the bare string "Denied". C# (MessagePack
[Union(n, ...)], the reference) writes 94 af..31 07 92 00 92 ...: each arm
[key, [fields]], the unit arm [2, []]. C# reading the Rust store fails:
MessagePackSerializationException: Failed to deserialize ProbeSurface value. <- Unexpected msgpack code 129 (fixmap) encountered. Rust reading the C# store
fails: failed to decode CultCache entry "probe.surface" ... a value does not fit the store format. Each runtime round-trips its own store. The
cultcache-rs derive handles structs only
(cultcache-rs-derive/src/lib.rs:51); enum members go through rmp_serde.
Owner: CultLib (thing2:follow_up:cultcache-union-parity).
P13. TypeScript CultCache has no typed codec. cult-cache.ts:679-692: with
no formatter, a payload decodes to plain arrays and maps and schema.parse
sees them raw; the interop test hand-writes a positional formatter per type
(test/cult-cache.test.ts:1263-1310). In the probe, a hand-written TS codec
for the union read the Rust store, wrote a store Rust read back equal, and
re-encoded byte-identical to Rust's payload; against the C# store the same
codec refused the [key, fields] arm (node is not a tag map). A TS reader
matches whichever encoding its author hand-codes.
P14. Nothing live builds from the six retired family repos. git grep at
origin/HEAD of Ghostlight 327cbe3, Odin 8e1de66, VoidBot 46d891b,
Heimdall 7904e17, Muninn 1d0ea0b, weksa d16cff5, Stonks 3f6e91c, Vili
8773c15, Gjallar 6a92976, StreamPixels 8d40541, CultLib, Idunn 6667f0f,
gamecult-site 8169ee5 and Ymir f8c0e39 for EveUnity|EveFlutter|EveElectron| EveTui|EvePlugins|EveConformance outside *.md: no match. gamecult-ops 9aed01f
matches only docs/repo-census-2026-09/synthesis.html; Eve matches a local
smoke script (scripts/run-aetheria-conformance-consumer-smoke.ps1:35, a local
path) and a spec; parked Mimir names them in one configuration file. GitHub,
2026-10-10: all six unarchived; active workflows only on EveFlutter and EveTui
(runtime.yml) and EveConformance (conformance.yml); GameCult/Thing and
GameCult/AetheriaThing do not exist; AetheriaEve is archived.
P15. Ghostlight's deploy compares its Eve clone's origin by exact string.
gamecult-ops 9aed01f scripts/deploy-ghostlight-yggdrasil.sh:20
(eve_origin=https://github.com/GameCult/Eve.git) and :871-876
(require_idunn_repo refuses "a foreign origin" when git remote get-url origin differs), called at :883; the binding template carries the same URL
(idunn/yggdrasil/bindings/ghostlight.toml.in:14). Editing either URL without
re-pointing Yggdrasil's Idunn-owned clone stops the next Ghostlight deploy;
leaving both on the old name keeps working through GitHub's rename redirect.
The Repo = "Eve" targets (idunn-deployment-targets.ps1:223, :236,
:262) are Thing 1 targets (iPad, Android, Nightwing browser reference) paired
with LocalPath = "$projectsRoot\Eve".
P16. gamecult-site carries a generated Thing 1 surface.
scripts/generate-vn-eve-surface.mjs writes
GameCult/static/interactive/gamecult-compound/gamecult-compound.eve-gui.json
(gamecult.eve.* ids), referenced by GameCult/Pitch.md:15
(data-eve-gui=). The census (C) does not list the site as a consumer.
P17. The contract's integer leaves are signed because TS and Python have no
unsigned leaf; the C# reference has one. CultLib b3bd8dd5:
src/GameCult.Caching/CultSchemaTypeNames.cs names any CLR type (so UInt32,
UInt64 and Byte slots exist in the reference); cultcache-ts
src/codec.ts exports only int32 and int64 integer codecs and
cultcache-py leaves.py:147-148 only int32 and int64. A u64 above
2^63 has no TS or Python reading, so versions stay Int64 and counts Int32,
and the non-negative rule is the contract's, not the leaf's. Rgba keeps u8
(the reference's Byte); the TS and Python packages mirror its 0-255 range
from a refusal golden until CultLib gives them a byte leaf.
P18. A domain newtype's own Deserialize holds its rule at every decode
door, and leaves the bytes unchanged. Probe imagination-contract-rs-r2/
(throwaway commit fbbdaf79 on 649844aa, Yggdrasil, 2026-10-12): Seq(i32)
refusing below 1 and Version(i64) refusing below 0, each Serialize the inner
integer and Deserialize through new. A result payload with seq 0 or -7 is
refused by rmp_serde::from_slice ("Seq: at least 1") and by
cultcache_rs::decode_payload (opaque: "a value does not fit the store
format"); seq 0 through serde's in-memory IntoDeserializer is refused the same
way, so the rule holds for any serde format. With CommandResult.seq: Seq and
both composition versions Version (an Int32 slot widened to Int64),
goldens_match still compares byte-identical, surface.cc included. The
derive reads each slot with seq.next_element::<FieldType>()
(cultcache-rs-derive/src/lib.rs, slot_codec, at b3bd8dd5), so a field's
type is the one owner of its domain on both paths. DatabaseEntry requires
DeserializeOwned (cultcache-rs/src/lib.rs:162), so the public
Deserialize of every kind cannot be removed: it is the decode door.
P19. Dotted provider ids are the Verse's norm; dotted local names are not
needed. git grep over Ghostlight, VoidBot, Odin, Heimdall, Muninn, Stonks
and Eve (operator checkouts, 2026-10-12): provider ids voidbot.swarm,
odin.allseer, stonks.market, sleipnir.input-mirror.raven, heimdall;
Thing 1 surface ids repeat the provider as a prefix (voidbot.swarm.surface,
ghostlight.play in crates/ghostlight-dungeon/src/mesh.rs:37). No
gamecult.thing.* record exists outside the contract's tests, and Thing 1
records are rebuilt, not read (thing:ruling:operator-compat-rebuild), so the
Thing 2.0 key grammar strands nothing. Probe evidence of the collision itself:
Soul's soul-thing2-contract-rs/rust1.log KEY lines.
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>, where label is one segment (non-empty, no .), so the key splits at its last dot and is injective while provider ids stay dotted (P19); version is an Int64 at least 0 in the record (P17) |
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>, name one segment (no .), n an Int64 at least 0 |
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 an Int32 from 1, refused below 1 on construction and decode; the key is injective because seq is the last dot segment and never holds a dot; 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>, catalogId one segment (no .); version an Int64 at least 0; asset width and height Int32 at least 0 |
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) |
Not a Thing kind (thing2:ruling:advertisement-owner): owned by the Verse discovery owner and renamed once there (thing2:follow_up:discovery-owner-campaign) |
Until its owner lands, the eve advertisement is read through core's discovery port, unchanged | The discovery 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 }.payloadis a typed record ofpayload_schema. The caller's identity is set by the transport session and has no field a consumer can write.CommandResult { invocation_id, seq, outcome }, withoutcomeone ofPending { message },Completed { source_version, clear_drafts, effect },Denied { reason, node }andFailed { reason }.effectis closed:OpenSurface(key)orOpenExternal { url, resume: CommandRef }(P7). The receipt is the result:command.v1,command_receipt.v1,transientProjectionandpluginPayloadhave no successor.- Handlers in every language receive a decoded invocation and return an
Outcome; the language package writes the result. No package takes a state as a string or converts a map into a result. Decoding is the one other door (every CultCache kind is deserializable, P18): it holds the same domain as the constructors because each field's type owns its rule. A decoded result can name any invocation; binding a result to an invocation core issued is core's validation (thing:ruling:thing2-one-solver-taffy), not the contract's.
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. Discovery leaves Thing (thing2:ruling:advertisement-owner);
the contract has no advertisement type, and core reads discovery through a port.
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.
- 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). - Core turns a result that fails to decode, or one for an unknown
invocation_id, into a visibleMalformedstate for that invocation and a log line; no invocation stays pending past its timeout without one. outcomeis an enum in every package; no API takes a state as a string.- 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. - A
Usethat does not resolve renders a visible placeholder with a diagnostic; it is never dropped silently. - A
Bindwhose record has a different schema, or whose member is missing, renders a typed binding error; no value is coerced. - Core holds no API that writes typed state; its only outbound write is a
CommandInvocation(negative check over the core crate). - No backend crate or package depends on
taffyorparley(negative check on the dependency tree): backends receive positioned commands and shaped glyph runs. - A lower or equal
versionof a document is ignored. - 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_bindingand 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)
stale-voidbot-cleanup(Eve main, any time): specthing2:cut_spec:cut-stale-voidbot-cleanup.r1. Owned here bything:follow_up:stale-voidbot-cleanup-on-main.- Contract (code home per
thing2:question:code-home): the contract crate with the types above, published schemasschemas/gamecult.thing.*, golden wire fixtures, and the TypeScript and C# packages held to them (the live consumers' languages: Rust, JS/TS, C#). The first proof (P9) was run: a Rust-written recursive union does not decode in the C# reference, nor the reverse (P12), and TS decodes only what a hand-written codec says (P13). The contract waits on CultLib's union parity (thing2:follow_up:cultcache-union-parity). Three cuts:contract-rs, thencontract-tsandcontract-cs. The published schema of each kind is its CultCache catalog entry, identical in the three packages; no JSON Schema files are written, because Thing 2.0 has no JSON reader of these kinds (the browser reads them through core, notajv). Python and Kotlin packages follow as their own deliverables.contract-rsattempt 1 landed onhands/thing2-contract-rs(649844aa); Soul'sthing2:verdict:cut-contract-rs.s1returned seven findings. Its fix batch isthing2:cut_spec:cut-contract-rs.r2. Waiting, one line each:contract-ts,contract-cs: their r1 specs predate r2's domain types, key grammar and refusal goldens, and are refreshed when the frontier reaches them.- CultLib pin bump: when
cultlib-gaps:follow_up:decode-stack-safetylands, move the pin and rundeep_treeon the default thread (its 16 MiB thread goes); the crate docs' hazard note goes with it. - Missing-slot golden: re-added only when
cultlib-gaps:follow_up:rust-missing-slot-vs-referencesettles the rule.
- 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; (a)'s decode of Verse surfaces waits oncultlib-gaps:follow_up:decode-stack-safety; (b) Parley text, focus, editing and IME state; (c) the AccessKit tree. - 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.
- 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.
- Consumer ports, one cut per live consumer, each carrying its rename and
deleting its Thing 1 path (
consumer-port-carries-rename). Cutover shape perthing2: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 incultcache-tsandcultmesh-kotlinare deleted, B26). These need more repos than the campaign lists:thing2:question:campaign-repo-limit. - Retirement: delete Thing 1's tree from the kernel repo. The renames,
archives and site prose are re-specified as
thing:cut_spec:cut-repo-renames.r2andcut-site-prose.r2and do not wait for it (P14).
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). They are archived under their Eve names
(thing2:ruling:retired-repo-names) by thing:cut_spec:cut-repo-renames.r2,
ahead of retirement, because nothing live builds from them (P14). The kernel
repo is renamed Thing in the same cut (thing2:ruling:code-home); its URLs in
gamecult-ops stay on the redirect until the port that owns each one (P15).
AetheriaEve stays under its rulings.
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,cardand 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;
Formatcovers 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
pluginPayloadandtransientProjectionwere a navigation and a status, which areeffectandoutcome. - Why each integer's rule lives in its field type. The decode door cannot be
closed (P18), so a rule held only by a constructor is a rule the wire skips.
A newtype per domain (
Seq,Version,Count,Segment) is read by both paths, keeps the bytes, and gives the TS and C# packages one refusal golden each to mirror. This is serde'stry_frompattern, written by hand because the CultCache derive emits the codec itself. - Why the key grammar constrains only the local segment. Provider ids and
publishers are owned by discovery (
thing2:ruling:advertisement-owner) and are dotted in the Verse today (P19); the contract owns onlylabel,nameandcatalogId. Refusing the separator there makes every key split at its last dots, the way DNS labels and the mind's own<root>:<kind>:<local>keys stay unambiguous. A different separator between provider and label would instead impose a grammar on ids the contract does not own. Listing a provider's records filters on the record's provider field, not a key prefix, because a dotted provider is a prefix of a longer one. - Why contract-rs merges before CultLib's stack fix. The overflow is the
decode door's (
cultlib-gaps:follow_up:decode-stack-safety) and every Rust reader of these kinds inherits it whether or not the contract merges. The contract decodes no hostile input; core's Verse decode does, so that is where the wait sits (cut 2a). The crate docs name the hazard meanwhile.