ADR-0032: The document registry — apps, Surfaces, and packages are graph entities in a compiled-in `system` realm; install and publish are Acts
ADR-0032: The document registry — apps, Surfaces, and packages are graph entities in a compiled-in system realm; install and publish are Acts
Status
Accepted
Date
2026-07-04
Context
Phase A shipped the app layer as a headless TypeScript package (@vinxi/surface-runtime):
parseApp, a path-spine router, roster-based resolveInstall. It is entirely file-based —
resolveInstall reads parsed *.app.json / *.json / *.realm.json / *.package.json
objects, and the manifest header already promises “when the kernel’s package registry lands,
its projection replaces the file.” App-system canon A9/A11 set the target: app / Surface /
package documents become kernel documents and graph entities; install / publish / uninstall
become Acts; every document carries an origin (bundled|local); documents move through a
draft → published lifecycle gated by registry.publish; the launcher becomes a plain Surface
querying system.App.
A9/A11 fix the shape, not the kernel modeling. Two machineries already exist and are
Postgres-tested. The ontology layer (SchemaDocument) declares kinds: documents are
immutable-versioned, and the snapshot is the latest version of each document merged by
kind_id (SELECT DISTINCT ON (document_id) … ORDER BY version DESC). The world/graph
layer (create_entity / assert_link / query_entities / upsert_entity) holds
instances. A parallel workstream (site-identity) also introduces a compiled-in system
realm (system.User / system.Site / grants); A12 already reconciled the naming (one
system realm), but not the code.
Decision
App, Surface, and package documents are graph entities in a compiled-in system realm;
their lifecycle rides the world/graph layer as Acts, not SchemaDocument versioning.
-
One realm, ownership by document. Phase B seeds a compiled-in
system.registrySchemaDocument(realm: system) declaring the kindssystem.App,system.AppRevision,system.Surface,system.SurfaceRevision,system.Package. Site-identity later appends a separatesystem.identitydocument to the same realm. The realm is the shared namespace; the document is the ownership boundary — the snapshot merges them bykind_id, so neither workstream edits the other’s document. -
Boot-seed is a lazy get-or-create. The realm is per-scope (snapshots are per
(tenant, universe, branch)), and the HTTP server owns no scope, so there is no server-boot seed.ensure_system_realm(scope)runs the existinginstall_schema_documentAct (underOperationContext::system) idempotently at the top of every registry write Act — self-healing per scope. -
Thin/opaque entities. Each document’s content lives in a
Document-constructor trait; the graph indexes only identity,origin,installed, and package membership (provided_by → system.Package). The app roster stays inside the app payload (links are property-less, so per-entry nav metadata has no edge home); Surfaces are their own entities, referenced by id, and the install gate checks each roster reference resolves (folding in the deferred symmetric app-doc install check). -
Immutable revisions + a link pointer. Content is an immutable
system.AppRevisionentity (create_entity; a revision id can never be rewritten). Thesystem.Appidentity entity carriespublished_revanddraft_revlinks to the live / draft revision. Editing a published app creates a new draft revision; publish is an Act that re-pointspublished_rev, gated byregistry.publish; prior revisions are retained as queryable entities (cheap rollback). Uninstall is a softinstalled = false(there is no hard delete;clear_alarmis the precedent). Because cardinality-one links cannot be re-pointed today, this adds aretract_linkkernel primitive (port method + syscall +TruthStatement::link_retracted). Link mutation statements are identified by the Act’sidempotency_key, not by(kind, subjects)alone — an assert/retract cycle (rollback) would otherwise collide under ADR-0013’s create-once deterministic ids. -
Factory replay. Package files are the transport: a
system install-packagecommand (mirroringreality install) installsprovides.realmsSchemaDocuments through the existing ontology Act, then creates the Package / App / Surface entities +@1revisions + links withorigin: bundled, idempotently. -
Read seam. A query/projection returns installed + published documents for a scope;
resolveInstallbecomes a projection consumer (its source flips files → kernel query; its validation gates are unchanged). Phase B proves the query via the CLI; the@vinxi/clientHTTP adapter and theresolveInstallsource-flip are Phase C. -
Capability.
registry.publishis enforced as an actor role/capability check (thesystemmachine-authority actor satisfies it, per the ticket precedent). Who holds the capability — the grant graph — is site-identity’ssystem.identityconcern, deferred.
Alternatives Considered
- A single shared
systemdocument for registry and identity kinds: rejected — immutable versions + latest-per-document snapshot force each workstream to re-declare the other’s kinds on every version bump, resurrecting the code-level collision A12 killed at the naming level. - A
vinxi-surface-registrycrate / non-ontology registration path: rejected — discards the dogfood; the existinginstall_schema_documentAct is the proven template. - Mutable single entity with published/draft
Documentslots (“Model Z”), publish flips a trait (mirrors ticket transitions, needs no new primitive): a viable, cheaper v0 — rejected in favour of immutable revisions + pointer because A11 wants retained, browsable revisions and cheap rollback, and the extraretract_linkprimitive is reusable kernel capital (ticketing already worked around its absence with agrouped_intomany→many relaxation). - Published pointer as a mutable trait (a revision-id string) instead of a link: buildable with no new primitive — rejected because a trait pointer is not traversable; a first-class link lets the launcher reach published content by graph traversal, at the cost of building link retraction.
Consequences
- A new reusable kernel primitive —
retract_link— and a scoped amendment to how link-mutation statement identity works under ADR-0013 (Act-instance-identified, not create-once). - The launcher is a plain Surface querying
system.Appand traversingpublished_rev; no special launcher machinery, per A9. system.Surfacejoins the enumeratedsystemkinds (A12 listed App / Package / User / Site); a minor canon note, folded here.- Site-identity coordination becomes a clean seam, not a merge: it appends
system.identityto the shared realm and supplies theregistry.publishgrant graph. Phase B does not block on it. - Deferred (to
TODO.md): the@vinxi/clientHTTP read path +resolveInstallsource-flip (Phase C); browsable revision history / changelog diff (studio, Phase C); the real capability grant graph (site-identity);type:"code"app routing and the browser path-spine shell (Phase C).