Skip to content

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.

  1. One realm, ownership by document. Phase B seeds a compiled-in system.registry SchemaDocument (realm: system) declaring the kinds system.App, system.AppRevision, system.Surface, system.SurfaceRevision, system.Package. Site-identity later appends a separate system.identity document to the same realm. The realm is the shared namespace; the document is the ownership boundary — the snapshot merges them by kind_id, so neither workstream edits the other’s document.

  2. 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 existing install_schema_document Act (under OperationContext::system) idempotently at the top of every registry write Act — self-healing per scope.

  3. 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).

  4. Immutable revisions + a link pointer. Content is an immutable system.AppRevision entity (create_entity; a revision id can never be rewritten). The system.App identity entity carries published_rev and draft_rev links to the live / draft revision. Editing a published app creates a new draft revision; publish is an Act that re-points published_rev, gated by registry.publish; prior revisions are retained as queryable entities (cheap rollback). Uninstall is a soft installed = false (there is no hard delete; clear_alarm is the precedent). Because cardinality-one links cannot be re-pointed today, this adds a retract_link kernel primitive (port method + syscall + TruthStatement::link_retracted). Link mutation statements are identified by the Act’s idempotency_key, not by (kind, subjects) alone — an assert/retract cycle (rollback) would otherwise collide under ADR-0013’s create-once deterministic ids.

  5. Factory replay. Package files are the transport: a system install-package command (mirroring reality install) installs provides.realms SchemaDocuments through the existing ontology Act, then creates the Package / App / Surface entities + @1 revisions + links with origin: bundled, idempotently.

  6. Read seam. A query/projection returns installed + published documents for a scope; resolveInstall becomes a projection consumer (its source flips files → kernel query; its validation gates are unchanged). Phase B proves the query via the CLI; the @vinxi/client HTTP adapter and the resolveInstall source-flip are Phase C.

  7. Capability. registry.publish is enforced as an actor role/capability check (the system machine-authority actor satisfies it, per the ticket precedent). Who holds the capability — the grant graph — is site-identity’s system.identity concern, deferred.

Alternatives Considered

  • A single shared system document 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-registry crate / non-ontology registration path: rejected — discards the dogfood; the existing install_schema_document Act is the proven template.
  • Mutable single entity with published/draft Document slots (“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 extra retract_link primitive is reusable kernel capital (ticketing already worked around its absence with a grouped_into many→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.App and traversing published_rev; no special launcher machinery, per A9.
  • system.Surface joins the enumerated system kinds (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.identity to the shared realm and supplies the registry.publish grant graph. Phase B does not block on it.
  • Deferred (to TODO.md): the @vinxi/client HTTP read path + resolveInstall source-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).