Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Introduction

This book is conceptual documentation for Graviola: a schema-driven semantic CRUD framework used across several projects while it matures. It is written for developers who are comfortable with architecture, integration, and data modeling—not for end users.

What you will find here

What this book is not

  • Not the full framework API or package-by-package reference (that lives in the monorepo and will grow in separate technical docs).
  • Not a Storybook substitute: UI components and interactive examples belong in Storybook; this book points there when useful.
  • Not a single customer narrative: examples are illustrative across domains (heritage, internal tools, offline-first, etc.).

How to read progressively

  1. Start with The shape of a federated application if the problem is new.
  2. Read What Graviola is and Capabilities today for the current product story.
  3. Use Architecture and data flow as the map of layers and pipelines.
  4. Treat Architectural trajectory and its chapters — The sidecar pattern, Calculated fields, Lenses and bidirectional transforms, Provenance and metadata, Store topology — plus Graviola in the age of generative tools, Outlook and open questions, and the Glossary as deepening material—optional until you need precision on lenses, sync, trust, provenance, federation, generative workflows, or vocabulary.
  5. If authoring fragmentation (many files per model) matters to your team, read LinkML as an authoring source for schemas for a build-time pattern that leaves Graviola's runtime unchanged.

Canonical seed sources for this edition live under seed/ in the same repository; chapters here are the book-shaped rearrangement of that content.

See also

The shape of a federated application

A reader's primer to the problems Graviola addresses.


1. Where the difficulty begins

Most software is built on a comfortable assumption: there is one database, the application owns it, the schema is what the team decided, and every record is under the same roof. Frameworks, tooling, and conventional wisdom all rest on this picture. It works well for a great many systems and should not be abandoned where it suffices.

Some applications, however, cannot live inside that picture. A cataloging system needs to reference biographies that are maintained by a national library. A research tool needs to align its records with public datasets that change on their own schedule. A personal information system needs to make sense of files, messages, and bookmarks that arrived from many different programs over many years. In each of these cases, the application has data of its own — but it is surrounded by, and dependent on, data it did not produce.

This document is a guided tour of that surrounding landscape, written for readers who have not yet built systems at this shape. It introduces the conceptual terrain in two halves: the data side (where information comes from) and the representation side (how that information is given visible form). Both halves shape Graviola's design.


2. The data landscape

It is common to speak of data sources in terms of ownership — your data versus theirs — but ownership is a coarse instrument. The more useful question is how much interpretive work is required to bring data into your application's working model. The landscape can be sketched in roughly four bands.

flowchart LR
    A["Primary data<br/>under your control"]
    B["External, aligned<br/>peer triple stores, Solid pods,<br/>federation partners"]
    C["External, structured<br/>open data, public APIs,<br/>authority files"]
    D["External, unstructured<br/>PDFs, web pages,<br/>scanned documents"]

    A --> APP["Application's<br/>working model"]
    B -->|federated query| APP
    C -->|declarative mapping| APP
    D -->|extraction process| APP

Primary data is the application's own. Its schema is decided by the team that builds the application; its migrations are run on the team's terms; its reliability is the team's responsibility. This is the comfortable case.

External, aligned data is held by others but already speaks a vocabulary the application can understand. Another organization's triple store, a federation partner's Solid pod, a peer running the same software at a different site — in each case the data arrives in a form that requires identification but not interpretation. The work is to query it, to merge it, and to keep track of provenance.

External, structured-but-unaligned data is the largest band by volume. It is well-formed — public datasets, authority files such as Wikidata or the German Integrated Authority File, REST APIs returning JSON — but it speaks someone else's vocabulary. To enter the application's working model, it must be transformed: a birthDate in one schema becomes a dateOfBirth in another; a flat string is split into structured components; a nested array is flattened or restructured. This is the territory of declarative mapping.

External, unstructured data carries information with no schema at all: PDFs, scanned documents, web pages, audio transcripts, photographs of receipts, and so on. In order to become usable, this data must undergo a structuring process—whether that's a hand-written extractor, a rule-based pipeline, supervised machine learning, or (increasingly) a large language model. The goal of such processes is to output structured data, which then enters the same declarative mapping funnel as the structured-but-unaligned data described earlier.

It's important to note that the boundaries between these bands are rarely clear-cut. A federated peer's data may be perfectly aligned in some areas and completely foreign in others; a language model extractor may yield structured output along with confidence scores or provenance metadata requiring further interpretation. The key takeaway is not to obsessively classify, but rather to understand the distance and transformation work required to integrate any given data source into your application's model.

This layered view has deep parallels with Tim Berners-Lee's 5-star deployment scheme for Linked Open Data, which describes a progression from raw data on the web, to structured formats, to standardized schemas, to linked data, and finally to full interlinking with external sources. But this is not a concern only for "open data" or public datasets: every application—whether its sources are open, closed, or internal—faces this gradient of alignment, structuring, and integration. The five-star model offers a lens for thinking about all data sources and the varying levels of effort required to bring them "home" into your application's ecosystem.


3. From having data to showing data

Once information has reached the application's working model, a second question opens. People do not consume models; they consume views of models. The same record will be encountered in a list of search results, a row in a table, a card in a sidebar, an entry on a map, a node in a graph, a full-page detail screen. Each appearance shows part of the same underlying entity, but the part shown — and the way it is shown — varies enormously.

The variety can be organized along two axes.

The first is the arrangement of many entities. A table arranges entities into rows and columns. A list arranges them vertically with custom layout per row. An explorer view arranges them as a folder hierarchy. A map arranges them by geographic coordinate. A timeline arranges them by date. A graph arranges them by relationship. Each is an answer to "how should many of these be shown together?" and each is appropriate to different data and different tasks.

The second axis is the size of the canvas given to a single entity. The same person record may need to appear:

flowchart TB
    E["A single entity<br/>e.g. a person record"]

    E --> Cell["Cell in a table<br/>name only, perhaps abbreviated"]
    E --> Chip["Chip in a query result<br/>label + icon + color"]
    E --> Card["Card in a sidebar<br/>portrait + summary + key facts"]
    E --> Page["Full detail page<br/>all fields, all links"]

Each of these is, in some sense, a "detail view" — but the term flattens an important distinction. A detail view is not a single thing. It is a family of representations of an entity, parameterized by available space, by the user's current task, and by the device on the other end of the screen. A chip has perhaps thirty pixels of width and must communicate identity in a glance: a label, perhaps an icon, perhaps a color band. A sidebar card has more room and can introduce an image, a brief summary, a few key facts. A full page is unconstrained and can show everything the schema describes.

The harder design question is not how to render any one of these. It is how to choose, among the many possible representations of a given entity, the one that fits the current context — and to do so in a way that does not require the application's authors to write a separate component for every entity type at every size.


4. How Graviola approaches representation

Graviola does not prescribe a fixed library of representations. It provides a dispatch mechanism that allows representations to be registered and selected based on the data they encounter and the role they are filling.

The mechanism is built around what Graviola, following the convention of JSON Forms, calls testers. A tester is a small function that examines a piece of data and a context, and reports how well it can render that data in that context. Multiple testers may claim the same data; the one that reports the best fit wins. New testers can be added to a Graviola application without modifying existing ones, and the dispatch table can be inspected, reordered, or overridden per deployment.

Testers operate at every level of the rendering surface. There are testers that decide how a single cell in a table should be rendered — whether the value is shown as plain text, as a link, as a colored badge, or hidden entirely if the column is irrelevant in the current context. There are testers that decide how an entity should be shown as a chip, with the limited vocabulary chips offer: one label, perhaps a popover for more detail, perhaps an icon, perhaps a pattern or color drawn from a category. There are testers that select among detail-view layouts when an entity is opened in a sidebar, a panel, or a full page.

The principle that unifies these uses is structural dispatch: testers match against the shape of the data, not against an entity's nominal type. A tester written to render any object with a latitude and longitude field will fire for places, events, and observations alike, without those types being declared as related. A tester written to render any object with a signedBy field will recognize signed records wherever they appear. The same principle scales from individual fields (where JSON Forms applies it) to whole entities (where Graviola extends it).

The result is that a Graviola application's representation layer is composed, not architected. New representations are added incrementally, conflicts are resolved by ranking rather than by code change, and the same entity can be presented differently in different parts of the application without the application's authors enumerating those differences in advance.


5. Why both halves matter

Discussions of data federation often focus on the data side: how to query across sources, how to merge results, how to maintain provenance. These are real problems and Graviola addresses them. But the representation side is where federated applications most often fail to scale.

A system that brings together data from many sources, in many vocabularies, at many levels of structure, will encounter a corresponding multiplicity of entities and entity shapes. If each shape requires a hand-written representation for each role (cell, chip, card, page), the cost of maintaining the representation layer grows faster than the value of the data being represented. If, on the other hand, the representation layer is fixed — one card design, one detail page — the application loses the ability to show specialized data well.

The middle path is to make representation, like data, a layer that can be composed from declarative pieces and dispatched by shape. This is the design Graviola pursues. The data side and the representation side share a common discipline: in both, the framework's job is to provide structure for cooperation among many small contributions, not to produce a single answer that fits all situations.

A reader who carries away one observation from this primer should carry this: federation is not only the problem of bringing information together. It is also, and equally, the problem of giving that information form once it has arrived.


See also

What Graviola is

A semantic CRUD framework for schema-driven applications


Overview

Graviola is a TypeScript framework for building applications whose central abstraction at runtime is a JSON Schema (or Zod-derived JSON Schema) describing the shape of domain entities. Teams may maintain that schema by hand or generate it upstream in the build (for one documented pattern, see LinkML as an authoring source for schemas); the framework consumes the same artifact shapes either way. From the schema definition Graviola generates and operates: forms for creating and editing entities, tables for browsing them, queries against the storage backend, and validation of the data flowing in and out. The same schema drives the user interface, the persistence layer, and the integration layer.

The framework is storage-agnostic at its core. The same schemas, forms, and tables operate against an in-browser SPARQL store (Oxigraph compiled to WebAssembly), a remote SPARQL endpoint, a Prisma-backed relational database, a REST API, or an in-memory store for testing. This is not abstraction for its own sake: Graviola has been deployed in each of these configurations across different projects.

Graviola also includes a declarative mapping layer for ingesting structured data from external authority sources — Wikidata, the German Integrated Authority File (GND), DBpedia — into the application's local data model. This layer is the framework's most mature non-CRUD subsystem and is currently the primary mechanism by which Graviola handles cross-source data integration.

The framework is published as a monorepo of approximately fifty packages under the @graviola/ scope, designed to be consumed individually rather than as a bundle.


Why Graviola exists

A recurring pattern in domain-specific applications — cultural heritage catalogs, scientific data collection, internal tooling, knowledge management — is the gap between two competing needs:

  • The data model is rich and evolving: nested entities, references between records, multilingual fields, links to external authorities, schema changes over the lifetime of the project.
  • The development resources are bounded: the team cannot afford to hand-write a bespoke form, table, validation rule, and query for every entity type, and cannot afford to rewrite them every time the schema changes.

The conventional answers to this gap each fall short for one of Graviola's core use cases. ORM-driven scaffolding (Django admin, Rails forms, etc.) assumes a relational backend and a single deployed schema. Generic form libraries solve the form problem but not the persistence or query problem. Hand-rolled CRUD abstractions accumulate domain logic and resist reuse across projects.

Graviola's response is to take JSON Schema as the runtime single source of truth (Zod is supported where JSON Schema is derived from it) and derive everything else from it: the form (via JSON Forms), the table (via material-react-table with Graviola wrappers), the query (via the framework's schema-to-SPARQL translator or the equivalent for other backends), and the validation (via Ajv, against the same schema). The schema travels with the data; tooling built on Graviola can be ported between storage backends with minimal change.


See also

Capabilities today

This chapter describes Graviola as it exists in production today. Directions that are not yet implemented in the form described are kept in Architectural trajectory.


Schema-driven CRUD

Given a JSON Schema definition with @id and @type semantics, Graviola provides:

  • GenericForm — a top-level component that, given a schema and an entity IRI, generates a form, loads the entity from the configured store, manages dirty state and validation, and writes changes back. No per-entity-type code is required.
  • SemanticJsonForm — the lower-level component, used when explicit control over schema, UI schema, or data flow is needed.
  • CRUD hooksuseFormData, useFormEditor, useCRUDWithQueryClient, integrated with TanStack Query for caching and invalidation.

The CRUD pipeline translates JSON Schema definitions into store-appropriate operations. For SPARQL backends, this means generating CONSTRUCT queries for reads and INSERT/DELETE patterns for writes; for Prisma backends, it means typed ORM operations; for REST, configurable endpoint patterns.

Whether JSON Schema (and companion UI or mapping files) are authored by hand or generated in the application build — for example from LinkML — does not change this pipeline: Graviola consumes the same outputs at runtime.


Form rendering

Graviola uses JSON Forms as its UI rendering substrate. The framework ships a renderer registry covering:

  • Standard field types (text, number, date, boolean, enum)
  • Linked-data-aware renderers (entity pickers that query the configured store, authority lookup widgets)
  • Layout renderers (grids, tabs, sections)
  • Specialized renderers for color input, MapLibre GL maps, and Markdown editing

Renderers are registered once and dispatched by schema shape rather than by entity type. Adding a new entity type to a Graviola application typically requires no new renderer code.


SemanticTable

SemanticTable is a schema-driven table component providing:

  • Pagination, sorting, and filtering against the configured store
  • Soft-delete (move to trash, restore from trash)
  • CSV export
  • Column visibility configuration
  • Row selection and inline editing hooks

The table derives its columns and filters from the same JSON Schema used by the forms, so a change in the schema propagates to both surfaces without intervention.


Semantic detail views

Forms and tables answer editing and browsing. Detail views answer how a single entity should look when space is tight (a chip in a search result), medium (a card in a gallery or sidebar), or unconstrained (a full detail page or modal). Graviola treats these as one family of read-only, schema-driven representations, not separate components per entity type.

The production component is DetailRenderer (@graviola/edb-detail-renderer). It selects a layout and field renderers from the same JSON Schema that drives forms and tables, using the same structural dispatch principle described in The shape of a federated application: small tester functions rank how well they can render a schema node or entity in a given context; the best match wins.

View sizes

DetailRenderer accepts a view size that constrains how much of the entity is shown:

SizeTypical useWhat the user sees
chipInline references, filter tags, table cells linking to entitiesA compact label — often with icon or color — identifying the entity at a glance
listItemVertical lists, pickers, search resultsOne row: primary label plus a few secondary fields
cardGalleries, dashboards, sidebarsA summary block — headline, optional image, selected key facts, optional actions
detailFull-page views, drawer panels, modalsThe entity laid out in sections with linked nested entities resolved

The same Person schema can appear as a chip in a table column, a card in a browse grid, and a full detail layout in a modal — without three hand-written React components.

Detail UI schema

Like JSON Forms editing UI, detail rendering uses a separate UI schema — a JSON Forms UISchemaElement tree scoped to schema nodes, not a duplicate of the domain schema. Defaults are generated by generateDefaultDetailUISchema (with skipScope / scopeOverride for per-field control). Card layouts have a parallel default via generateDefaultCardUISchema. Applications override presentation — which fields appear at which size, section groupings, header image — without changing the underlying data model.

Modals and composite surfaces

Higher-level components wrap DetailRenderer for common application patterns:

  • EntityDetailModal — read-only entity inspection in a dialog (from @graviola/edb-advanced-components)
  • EditEntityModal — detail view paired with the form pipeline for in-place editing

SemanticTable column cells and linked-data form fields reuse the same chip and compact renderers when an entity reference needs to be shown inline.

Headless core, MUI bindings

Dispatch logic, testers, and scope resolution live in @graviola/edb-detail-renderer-core (no MUI dependency). The MUI implementation — layouts, chips, card variants, control renderers — lives in @graviola/edb-detail-renderer. Custom design systems can attach to the core package the same way custom form renderers attach to JSON Forms.

Interactive examples and renderer overrides are documented in the framework Storybook (apps/storybook in the Graviola monorepo); apps/testapp shows DetailRenderer wired against a local Oxigraph store.


Declarative authority mapping

Graviola's mapping layer is the production-tested mechanism for transforming records from external authority sources into the application's local data model. Mappings are written as JSON-LD-flavored declarative documents, not code. Each mapping entry pairs a source path (JSONPath against the authority response) with a target path in the local schema, optionally invoking a named strategy for non-trivial transformations.

The strategy catalog includes operations for concatenation, first-match selection, date-string-to-integer conversion, entity creation with authoritative back-links, template substitution, and recursion into nested mappings. The catalog is extensible, and new strategies can be added without modifying the mapping engine.

This layer is currently used for ingestion from Wikidata, GND, and DBpedia in cultural heritage applications. It is documented and has been refined across multiple deployments.


Storage backends

The current storage contract is the Store interface in @graviola/store-core (capability facets + CapabilityDescriptor). Many packages still expose the legacy AbstractDatastore name; behavior is the same seam.

Concrete Store implementations and providers available today:

BackendStackStatusTypical use
In-browser Oxigraph (WebAssembly)Oxigraph in a WebWorkerProductionLocal-first applications, no-server deployments
Remote SPARQL endpointHTTP SPARQL against Fuseki, Oxigraph, Blazegraph, …ProductionFederated data, existing institutional triple stores
N3 in-memory@rdfjs/data-model DatasetCore backed by n3 Store, queried via Comunica (@comunica/query-sparql-rdfjs)ProductionFast in-browser RAM store — tests, Storybook, prototyping (InMemoryStoreProvider)
IndexedDB hexastoreSame Comunica SPARQL layer over @graviola/indexeddb-dataset (persistent hexastore in the browser)Experimental (slow)Durable browser persistence without the Oxigraph worker (IndexedDBStoreProvider)
Prisma (PostgreSQL, SQLite, others)Typed ORMProductionInternal tools, classical web applications
REST APIConfigurable HTTP patternsProductionIntegration with existing HTTP services
HDT (WASM)Read-only access to compressed HDT dumps via a WASM implementationIn developmentLarge read-mostly RDF corpora without full materialisation

The two Comunica + @rdfjs dataset providers live in @graviola/indexeddb-store-provider. Both expose the same SPARQL CRUD surface to the framework; they differ only in where triples are held — RAM (n3.Store) versus IndexedDB. Oxigraph and remote SPARQL use separate engines (@graviola/local-oxigraph-store-provider, @graviola/sparql-store-provider).

The SPARQL path supports multiple dialects for remote and Oxigraph backends (standard SPARQL 1.1, Oxigraph, Blazegraph, Allegro) selectable per deployment. Comunica-backed stores use standard SPARQL 1.1.

Federation across multiple registered stores is trajectory — see Store topology.


Browser/server symmetry

Graviola's foundation and schema-to-query layers are constrained to be free of React, MUI, or any browser-only dependency. This constraint is enforced because the same packages are consumed by command-line tools (@graviola/edb-cli) and a REST API server (apps/edb-api) running on Bun. The translation from JSON Schema to SPARQL, the graph-to-JSON extraction, and the data-mapping engine all run identically in browser and server environments.

This symmetry is a load-bearing property of Graviola's design and shapes how new capabilities are added.


See also

Architecture and data flow

The framework is organized into six layers, each consuming only from layers below it:

graph TD
    L6["Layer 6 — UI Components<br/>SemanticTable, EntityFinder, advanced components"]
    L5["Layer 5 — Form Rendering<br/>SemanticJsonForm, GenericForm, JSON Forms renderers"]
    L4["Layer 4 — Store Providers<br/>SPARQL, Oxigraph, REST, Prisma, in-memory"]
    L3["Layer 3 — State Management<br/>React hooks, data mapping hooks"]
    L2["Layer 2 — Schema → Query Translation<br/>sparql-schema, graph-traversal, db-impl packages"]
    L1["Layer 1 — Foundation<br/>Core types, utils, JSON Schema utilities, JSON-LD utilities"]

    L6 --> L5
    L5 --> L4
    L5 --> L3
    L4 --> L3
    L3 --> L2
    L2 --> L1

Layers 1 and 2 are the server-safe core: no frontend dependencies, consumed by both browser applications and command-line tooling. Layers 3 and 4 introduce React and storage-specific code. Layers 5 and 6 are the user-facing surfaces.

The data flow for a typical read operation:

flowchart LR
    A["JSON Schema<br/>definition"] --> B["sparql-schema<br/>translator"]
    B --> C["SPARQL CONSTRUCT<br/>query"]
    C --> D["RDF graph<br/>from store"]
    D --> E["graph-traversal<br/>extractor"]
    E --> F["Typed JSON<br/>object"]
    F --> G["State hooks<br/>TanStack Query"]
    G --> H["React<br/>component"]

Writes follow the inverse pipeline: form data is validated against the schema, transformed into RDF triples (or the equivalent for non-RDF stores), and committed via INSERT/DELETE operations.


See also

Deployment scenarios

The scenarios below describe shapes of deployment that Graviola has supported or is designed to support. Each scenario indicates which capabilities are involved and which are drawn from Architectural trajectory rather than current production.


Cultural heritage and library catalogs

The original driver of Graviola's design. A cataloging team needs to enter records about books, persons, places, exhibitions, or works, with frequent reference to external authorities (GND for German-language records, Wikidata for cross-domain links, VIAF for international author identifiers). The data model is rich, evolves slowly, and must produce valid RDF Linked Data for publication.

Graviola serves this scenario today through:

  • JSON Schema definitions with @id and @type semantics, enabling round-trip to RDF
  • GenericForm for manual data entry, with linked-data-aware renderers for authority lookups
  • SemanticTable for catalog browsing
  • The declarative mapping layer for ingesting authority records into the local model
  • A SPARQL endpoint as the storage backend, allowing the catalog to be queried as Linked Open Data

Trajectory capabilities relevant to this scenario: signed states for expert-curated records; lens-based migration as the schema evolves across project lifetimes.


Offline-first field deployments

A team operates in an environment with intermittent or absent connectivity — a research vessel, a field site, a remote installation. Multiple devices need to share a working data model and current data, without depending on a central server.

Graviola has been deployed in this configuration with a JSON Forms-based schema designer producing schemas at runtime, distributed alongside the data over a Yjs-based WebRTC transport. The application operates entirely offline; reconnection synchronizes both schema and data changes between peers.

The current implementation handles a single shared schema version per peer group. The trajectory direction extends this to peer-specific schema versions reconciled via lens application — a capability whose architectural shape is clear but whose implementation has not been completed.


Privacy-sensitive data collection

An application handles data whose disclosure to a server operator is unacceptable: personal records under regulatory protection, sensitive interview transcripts, internal information that must not be visible to infrastructure providers. The deployment requires that the server function as a transport and storage layer only, never gaining access to plaintext.

Graviola's browser/server symmetry is the load-bearing property here. The schema, the form, the validation, and the encryption all run in the browser before data leaves the device. Servers handle ciphertext. The same Graviola components used for non-sensitive applications operate in this mode without modification, given the appropriate AbstractDatastore implementation.


Internal tools with classical backends

Not every Graviola deployment requires the federated, peer-to-peer, schema-evolving model. The framework is also used as a productivity layer over conventional Prisma-backed PostgreSQL or MongoDB databases, where the team owns the model and uses standard migration tooling.

In this configuration, Graviola provides JSON Schema-driven forms, tables, and validation over a Prisma-managed store. Schema evolution is handled by Prisma migrations in the conventional way; the lens engine is not enabled. This deployment shape is an intentional first-class case, not a downgrade.


Authority-linked reference databases

A reference database — biographical, geographical, terminological — needs to maintain links between local entities and one or more external authorities, allowing data to be re-fetched or cross-referenced without losing local annotations.

Graviola provides this through its primary/secondary IRI distinction: each local entity carries a canonical local IRI and any number of sameAs links to authority entries. The mapping layer governs how authority data is transformed into local schema shape on initial ingestion; subsequent updates can re-fetch from the authority and merge changes against the local annotations.

Trajectory capabilities relevant here: signed states allowing experts to annotate or correct authority-derived data with an audit trail.


See also

Limits, fit, and evaluation


What Graviola is not

Equally important to scope is what Graviola does not attempt:

  • Graviola is not a database. It is a layer over storage backends. The choice of triple store, relational database, or REST service is the application's, not the framework's.
  • Graviola is not a reasoner. The conceptual model is reasoning-shaped (property-driven class derivation, transitive sameAs), but inference, where required, is performed by the underlying store or by application code. The framework does not ship an OWL reasoner.
  • Graviola is not a CMS. There is no built-in role model, publication workflow, asset pipeline, or page composition system. Applications building such features do so on top of Graviola's CRUD primitives.
  • Graviola is not a complete frontend stack. It provides components for forms and tables, but page routing, application shell, theming, and authentication are application concerns. The example application (apps/testapp) demonstrates one way to compose these but does not prescribe.
  • Graviola is not a substitute for a hand-tuned schema in performance-critical scenarios. Schema-driven query generation introduces overhead. For high-throughput services with stable schemas, Graviola is appropriate at the application layer but should not be assumed performant in the inner loop of a search engine or analytics system.

Evaluating Graviola for a project

The framework is most appropriate when the following hold:

  • The application is built around a domain data model with multiple related entity types.
  • The data model is expected to evolve, or already exists in multiple representations across data sources.
  • Forms and tables for these entity types would otherwise need to be hand-written and maintained.
  • JSON Schema is acceptable as the central description language.
  • The deployment can accept TypeScript on the application side.

The framework is less appropriate when:

  • The data model is fixed, simple, and unlikely to change.
  • The application is dominated by a single bespoke interaction surface (a custom editor, a domain-specific visualization) rather than CRUD over structured records.
  • The existing technology stack is not JavaScript/TypeScript and crossing that boundary is undesirable.

For teams considering Graviola, the recommended starting point is apps/testapp in the framework's monorepo. It is a minimal Vite + React application demonstrating GenericForm over a small schema with nested entities. The application is approximately one screen of code and exercises the core CRUD path end-to-end.


Repository and reference

The framework is published at github.com/gravio-la/graviola-framework. The monorepo contains approximately fifty packages under the @graviola/ scope, organized by the layer architecture described in Architecture and data flow. The canonical example application is apps/testapp.

A separate Glossary defines the framework's terminology, with references to the literature and prior projects underlying each concept.


See also

Graviola in the age of generative tools

This chapter addresses a question readers may bring from the current tooling landscape: whether a schema-driven framework like Graviola remains relevant when generative models can produce working application code from a single prompt.


1. The question worth asking

A reader could be forgiven for asking why a framework like Graviola should exist at all in a moment when a single prompt can produce a working application. The trajectory of generative coding tools has been steep, and the comfortable assumption that hand-written software is the durable form of an application is being tested in real time. If an LLM can write the form, the validator, the database access, the storage layer, and the UI in one shot, the case for a structured framework is at least worth re-examining.

This chapter takes the question seriously rather than waving it away. It argues, briefly, that the moment generative tools become genuinely capable of producing application code is precisely the moment a framework like Graviola becomes more valuable to its users — not less. The argument rests on what kind of artifact is produced, who can revise it, and how AI assistance can be layered onto a structured system in ways that are difficult to layer onto a hand-rolled monolith.

A working starting point for this chapter is the assisted-forms-designer, an existing project in the Graviola orbit. It is a WYSIWYG editor for JSON Forms (and Graviola forms) that has recently been extended with an AI assistant. The assistant can produce a full schema and form from a prose description of what the application needs, can take an existing schema and propose a form layout, or can offer incremental suggestions while a domain expert builds a form by hand. The project is small, it is real, and it sketches the shape of a broader pattern.


2. The economics of generation

When generation is cheap, the question shifts from can the system produce code to what should the produced artifact look like. Two extremes are worth contrasting.

In one direction, a generative tool produces an entire bespoke application — its own forms, its own validation rules, its own database access, its own UI. The application is a self-contained monolith. Reviewing it requires reading all of it. Modifying it requires understanding how its pieces fit together, none of which has been factored against any external convention. Regenerating part of it risks invalidating the rest. The result is fast to produce and slow to evolve.

In the other direction, a generative tool produces a small set of declarative artifacts — a schema, a form definition, a few annotations, perhaps a custom tester or two — that plug into an existing framework with known semantics. The framework supplies the form rendering, the validation, the persistence, the query engine, the UI components, the storage abstraction. The generated surface is small, its boundaries are clean, and each part can be regenerated independently. Reviewing the generated artifacts is the same task as reviewing hand-written ones. Domain experts who could not read application code can read a JSON Schema, or a form layout, or an annotation set.

The second pattern is what Graviola enables. The framework's architecture — JSON Schema as source of truth, structural dispatch for representation, declarative mappings for integration, the entire structure described in earlier chapters — is precisely the structure that makes generative assistance tractable. The model an LLM is asked to produce is small, well-defined, and reviewable. Most of the application is supplied by the framework, not by the model.

This is a less glamorous claim than the headline that AI will write entire applications. It is also closer to what teams actually need.


3. Three layers of assistance

A schema in a Graviola application has a lifecycle. It is authored, then it is used to fill in data, and over its lifetime it accumulates relationships with external records that need to be mapped into its terms. AI assistance can attach at each of these stages, doing different work in each, but always against the same schema.

flowchart TB
    subgraph LIFECYCLE ["The schema's lifecycle in a Graviola application"]
        A["Authoring<br/><i>schema, form,<br/>annotations</i>"]
        F["Filling<br/><i>creating instances<br/>against the schema</i>"]
        I["Integration<br/><i>mapping external<br/>records into the model</i>"]
        A --> F
        F --> I
    end

    AID_A["AI assistance:<br/>generate or refine<br/>schema and form"] -.-> A
    AID_F["AI assistance:<br/>guide the filler<br/>using field annotations"] -.-> F
    AID_I["AI assistance:<br/>suggest mappings<br/>for unfamiliar records"] -.-> I

3.1 Authoring assistance

This is where assisted-forms-designer already operates. A domain expert — a librarian, a curator, a researcher, a small-NGO administrator — describes what their application needs. The assistant produces a draft schema and form. The expert reviews the draft in a WYSIWYG editor, adjusts what the assistant got wrong, and adds the constraints that only a domain expert can know. The result is a schema and form definition that the framework consumes directly.

The crucial property is that the artifact under review is the deliverable. The expert is not reviewing generated code that will then be deployed; they are reviewing a schema that is itself the description of the application. If the assistant misunderstood the domain, the expert sees the misunderstanding in a form they can read, and can correct it in the same WYSIWYG editor. There is no opaque code layer between the description and the running application.

3.2 Filling assistance

Once the schema and form exist, the application's users are not the same people who authored it. A field researcher uses the form to enter observations. A volunteer enters event registrations. A cataloger enters bibliographic records. These users are domain-knowledgeable but may not be familiar with every field, every constraint, or every edge case the schema admits.

A second layer of AI assistance attaches here, drawing its instructions from the same annotations the authors placed on form fields. An annotation might say "describe the substrate's texture as rough, smooth, or granular"; the assistant uses this to help a researcher whose hands are full convert a verbal description into a structured field value. Another annotation might say "this field expects the canonical English title; if the source uses a translated title, prefer the original"; the assistant uses this to flag an inconsistency in what the user entered.

The pattern is that the schema's annotations become the assistant's instructions. The application author writes guidance for human users; the same guidance, read by an AI assistant, helps users follow it. No separate authoring effort is required for the AI layer. The annotations exist because human users benefit from them, and the AI assistance is a derivative use of the same content.

3.3 Integration assistance

The third layer is the one most familiar to projects that work with linked data. A cataloger encounters a record from an external authority — a Wikidata entry, a GND person, a record from a partner institution — and needs to bring it into the local model. The existing declarative mappings cover the common cases, but the cataloger has found a record that does not quite fit. Some fields are present in unfamiliar shapes; some have no obvious counterpart; some carry information at a different level of granularity than the local schema expects.

An AI assistant placed at this point in the workflow has access to the local schema, the existing mapping configurations, and the unfamiliar record. It can suggest a candidate mapping, flag which fields would be lossy, and propose either a one-off transformation for this record or a new general mapping rule for the project. The cataloger reviews the suggestion in the same way an expert would review a draft from a junior colleague — accepting, refining, or rejecting — and the accepted result becomes part of the project's mappings, available to the next cataloger who encounters a similar record.

This is the kind of integration work that is genuinely tedious for humans, genuinely tractable for AI assistants, and genuinely consequential when it goes wrong. The framework's existing structure makes it possible to assist without taking over: the assistant proposes against an explicit, reviewable schema; the human's role is judgment, not transcription.


4. Why this configuration works

Three properties of Graviola's existing design make the layered assistance pattern viable, and none of them was added with AI assistance in mind. They are consequences of the framework's structural-dispatch and schema-as-source-of-truth choices.

Small surface area for generation. An LLM asked to produce a Graviola application produces a schema, possibly a form definition, possibly some annotations, and possibly a custom tester. It does not produce the form rendering, the validation, the database access, the query engine, or the UI scaffolding. The model's output is small, its shape is well-defined, and its correctness can be checked by reading the artifact rather than by running it.

Reviewable artifacts. The artifacts the assistant produces — schemas, form definitions, annotations, mappings — are the same artifacts a domain expert authors by hand. They are not intermediate representations or scaffolds for code that will be generated next. The expert reviews the actual deliverable. When the assistant is wrong, the wrongness is visible at the level the expert can correct.

Attachment points for guidance. The same annotations that drive UI rendering, that mark calculated fields, that declare authorization rules, also serve as the natural places to attach guidance for human users — and, by extension, instructions for AI assistants helping those users. The annotation surface is unified; there is no separate "AI configuration" layer.

These properties are independent. A framework could have any one of them without the others. Graviola has all three because they fall out of the same design discipline.


5. What this future is not

Equally important to the vision is the boundary on what the framework will not become.

This is not a pivot to AI-first development. Graviola's primary commitment remains to applications that domain experts can build, evolve, and own without AI assistance. The pattern described here is additive: applications that never use any AI assistance run identically to applications that use it at every stage.

This is not autonomous agents replacing human authors or users. At every stage described in section 3, a human reviews and accepts the assistant's output. The assistant proposes; the human decides. The framework's audit trail (the schemas, the mappings, the annotations) reflects the human's decisions, not the assistant's suggestions.

This is not a claim that AI will replace the framework's structural choices. Reasoning, dispatching, validating, and querying are still the framework's responsibilities. Generative tools change what is supplied to the framework, not what the framework does with it.

This is not a roadmap of features. The assisted-forms-designer is the only piece of this picture currently implemented. The form-filling assistance and integration assistance described in sections 3.2 and 3.3 are directional: they require building, not just enabling. They are sketched here because the framework's existing structure makes them feasible without architectural change, not because they are imminent.


6. A modest closing claim

The most defensible claim about Graviola in the age of generative tools is the modest one: a framework whose central artifact is a small, reviewable, declarative schema is well-positioned for a world in which schemas can be drafted, refined, and used with AI assistance. The same properties that make the framework approachable for human authors — small artifacts, explicit annotations, structural dispatch — make it approachable for assistants working alongside human authors.

The earlier chapters of this book describe what Graviola is today and where its architecture is heading. This chapter sits beside them rather than in front of them: the future glimpsed here does not require the framework to become something it is not. It requires the framework to remain what it has been — small, structured, schema-driven, oriented toward domain experts — while letting new tools attach themselves to the surfaces that already exist for human use.

The first place to look, for readers wanting to see this in motion, is the assisted-forms-designer repository. It is the smallest concrete instance of the pattern this chapter describes, and it is the foundation on which the rest can be built.


See also

Architectural trajectory

The capabilities below are not yet implemented in production in the form described. They represent the architectural direction of the framework, informed by both prior research and the requirements of Graviola's existing users. Each is documented so that current development decisions remain compatible with these directions.

The discipline applied to this section: a capability is described here only when its shape is clear enough that the team has chosen not to foreclose it through current design choices.

For what ships today, see Capabilities today.

Authoring versus trajectory: build-time modeling choices (for example generating JSON Schema and UI schema from LinkML as an authoring source for schemas) are separate from the capabilities below. LinkML is an optional application build step; it does not move unfinished runtime features into production.

Generative tooling versus trajectory: assistance that drafts or refines schemas, forms, and mappings (see Graviola in the age of generative tools) attaches to the same declarative surfaces Graviola already uses; it does not substitute for the runtime capabilities sketched below.

Vocabulary note: the Store interface in @graviola/store-core (capability facets + CapabilityDescriptor) is current reality. The term AbstractDatastore is legacy in new material; trajectory extensions are expressed as new capability facets, descriptor extensions, and ReadResult envelope extensions — not as methods on a monolithic interface.


Detailed trajectory chapters

The topics below have dedicated chapters with design detail, invariants, and examples:

TopicChapterStatus
Scope-keyed sidecars (UI, calc, meta)The sidecar patternUI schema: production; calc + MetaSchema: proposed
Calculated fields, stratification, compilationCalculated fieldsProposed / partially designed
Lenses, writable computeds, x-inverseOf retirementLenses and bidirectional transformsProposed
Fact-level and entity-level metadataProvenance and metadataProposed
Federation registry, composites, CBD-cutStore topologyProposed

Schema evolution via lenses

Schemas evolve over the lifetime of an application. In Graviola's current deployments, this is handled either by classical migration scripts (where the application owns its database) or by manual rewriting of mapping configurations (where data is ingested from a versioned authority).

The architectural direction is to express version-to-version transformations as bidirectional lenses — small, composable, declarative documents that describe how to migrate data forward to a newer schema and, where possible, backward to an older one. This is a well-studied pattern; the closest existing implementation is Project Cambria from Ink & Switch.

In Graviola's intended model, each entity carries a gra:version property identifying the schema version under which it was authored. A consumer encountering an entity at a different version applies the appropriate lens chain at query time. The lens engine is an opt-in capability of a Store implementation, not a requirement.

The July 2026 design session reframes lenses as the unifying concept behind inverse properties, writable computeds, reversible mappings, and version migrations — see Lenses and bidirectional transforms.

Related glossary entries: Lens, Entity version, Schema drift, Lens-as-data.


Calculated fields

Some schema properties are best expressed as derivations rather than stored values. The intended mechanism is a declarative formula language with dependency graphs, auth/completeness stratification, and capability-aware evaluation placement.

See Calculated fields for the full design: defaults ladder, calc profile sidecar, compilation and stratification, and the compile/runtime invariants.

Related glossary entries: Calculated field, Capability context, Stratification, Compiled profile.


Provenance and administrative metadata

Beyond pipeline-level provenance in read results, the trajectory adds statement-level metadata (Wikidata-model $stmt siblings) and entity-level $meta (framework-guaranteed on all backends). Both integrate via The sidecar pattern and pure Layer-1 schema derivations.

See Provenance and metadata.


Store federation and topology

Multiple stores — authoritative triple stores, derived search indexes, read-only application translators — are intended to register behind a federation layer with explicit dimensions (authority, derivability, shape fidelity) and composite stores that hide boundaries cutting through an entity.

See Store topology.


Signed states and authoritative value

For applications where the credibility of data matters — historical databases, cultural heritage catalogs, expert-curated reference works — the framework's intended trust model is built on signed states: cryptographically signed snapshots of an entity (or of a lens, or of a schema) attesting that a named party vouches for its correctness at a moment in time. Multiple signatures, weighted by the trust graph among signers, contribute to a computed authoritative value used to surface plausible versus contested entries.

The cryptographic substrate is intended to be the W3C Verifiable Credentials Data Model.

Related glossary entries: Signed state, Authoritative value.


Schema and lens as syncable data

Graviola's existing storage layer treats data as documents. The intended extension is to treat schemas and lenses themselves as documents — JSON-LD documents with stable @ids, syncing through the same transport (Yjs, Solid, SPARQL endpoints) as application data. This generalizes a pattern observed in field deployments where domain experts authored schemas via JSON Forms-based designers and distributed them peer-to-peer alongside the data.

When schemas, lenses, and data all flow through one transport, signing extends uniformly to all three. MetaSchema extends the same pattern to administrative metadata documents.

Related glossary entries: Schema-as-data, Federated sync layer, MetaSchema.


See also

The sidecar pattern

Orthogonal concerns — rendering, computation, administrative metadata — must not pollute the domain schema. Graviola's intended model keeps the domain JSON Schema pure and carries each concern in a scope-keyed sidecar: a companion document whose keys are Scopes (JSON Pointers into the schema document) and whose values are concern-specific payloads.

This pattern is proposed, not yet fully implemented across all three instances described below. It is documented here so current authoring and build pipelines can converge on one structural convention.

For what ships today, see Capabilities today. UI schema sidecars are in production use via JSON Forms; calc profiles and MetaSchema are trajectory material.


Three sidecars, one dispatch rule

SidecarConcernStatus
UI schemaRendering hints for JSON FormsProduction (hand-authored or generated)
Calc profileComputation declarationsProposed — see Calculated fields
MetaSchemaEntity-level administrative metadataProposed — see Provenance and metadata

All three dispatch identically: a TBox pointer (scope) on the outside, a concern-specific payload on the inside. Consumers that care perform a scope lookup; consumers that do not remain ignorant.

This resolves the Scope vs. binding path duality structurally: sidecar keys are always scopes (which schema slot does this apply to?); binding declarations inside sidecar entries are always binding paths (what instance data feeds the concern?). The category error — using a scope where a path is needed, or vice versa — becomes impossible to express in the sidecar format.


Fingerprint binding

Each sidecar declares which domain schema it applies to:

{
  "appliesTo": {
    "schema": "https://myapp/schema",
    "fingerprint": "sha256-…"
  }
}

The fingerprint binds the sidecar to a concrete schema state. When the domain schema changes, drift detection is a compile failure naming the dangling scope — the same discipline applies to calc profiles, completeness metadata, and compiled computation artifacts. Sidecars are regenerated or updated in the build pipeline; they are not silently stale at runtime.


Domain schema stays portable

Computed slots in the domain schema appear as ordinary readOnly: true properties — no x-graviola-computed, no computation vocabulary in the domain artifact. Whether a read-only field is computed or stored-but-immutable is determined only by sidecar presence at that scope, exactly as JSON Forms decides whether a control has custom UI schema.

Consequences:

  • A consumer without the calc runtime sees a valid schema with read-only fields — graceful degradation.
  • DetailRenderer can show a formula badge or an "explain this value" affordance by scope lookup without changing the domain schema.
  • The LinkML authoring generator emits a clean domain JSON Schema plus separate sidecars mechanically, one-to-one.

Composition at read time

Sidecars compose with the domain schema only where the extended structure is queried or rendered:

  • deriveExtendedSchema(domainSchema, metaSchema) grafts typed $meta onto each CBD boundary.
  • deriveProvenanceSchema(schema) grafts $stmt siblings onto properties where fact-level provenance applies.

The write-validation path consumes the domain artifact only. The write validator does not know $meta or calc machinery exists — which enforces that administrative and computed metadata are system-asserted, not user-supplied.


See also

Calculated fields

Some schema properties are best expressed as derivations rather than stored values: a person's full name from forename and surname; an aggregate across linked entities; a status flag from temporal conditions. Graviola's intended mechanism is a declarative formula language (HyperFormula-shaped), with dependencies, stratification, and capability-aware evaluation placement.

Everything in this chapter is proposed or designed but not implemented in production unless explicitly marked. For what ships today, see Capabilities today.

Related: The sidecar pattern, Lenses and bidirectional transforms, Provenance and metadata, Store topology.


Three artifact forms, one concern

FormAudienceExpansion policy
LinkML annotations (graviola.computed)Schema authorMaximally terse; defaults ladder
Calc profile sidecar (JSON, scope-keyed)Generated artifact, reviewed/diffedTerse — omitted defaults stay omitted; regenerated, diffable
Compiled profileRuntime + debuggingFully expanded plus derived facts: cardinality per binding, assigned stratum, resolved eval placement, reverse dependents adjacency

Debuggability lives in the compiled form, where expansion is deterministic and derived — never in the authored forms, where expanded defaults would masquerade as intent. A CLI affordance (graviola calc explain '<scope>') is intended to print the expanded slot with its strata chain.

The LinkML generator emits two build artifacts: (a) a clean domain JSON Schema in which computed slots are ordinary readOnly: true properties; (b) the calc profile sidecar. Mapping is mechanical, one-to-one.


The defaults ladder

The authoring surface escalates only when convention is insufficient.

Level 0 — bare formula string; bare variable names auto-bind to same-named sibling slots; implied eval: auto, cache: reactive, readOnly: true:

full_name:
  range: string
  annotations:
    graviola.computed: 'CONCAT(forename, " ", surname)'

Level 1 — dotted names in formulas are binding paths (ABox traversal, compile-time validated):

graviola.computed: 'CONCAT(owner.display_name, " — ", TEXT(area_sqm))'

Level 2 — structured annotation when convention is insufficient (renamed bindings, context roots, explicit eval):

graviola.computed:
  bindings:
    owner_id: { path: owner.id }
    me: { context: currentUser.id }
  formula: 'EQ(owner_id, me)'
  eval: client

Level 3 — aggregates over relationships; LinkML multivalued: and inverse: supply relationship facts (cardinality derived, never declared):

billable_area_total:
  annotations:
    graviola.computed:
      aggregate: { type: sum, over: plots, field: billable_area }

Chained computeds across parent–child (Plot.billable_area → Patch.billable_area_total → Garden.total_billable → Garden.annual_fee) stratify automatically: stratum = max(dependencies) + 1.

Deliberate v1 omission: no where: filter inside aggregate. The idiomatic pattern is define an intrinsic computed, then aggregate it — intermediates stay individually inspectable, renderable, provenance-carrying, and stratify more cleanly. Relation-query bindings (relation + Prisma-style where) are the level-4 escape before a resolver hatch.

The graviola.computed annotation schema itself is intended to ship as a LinkML model (graviola-annotations.yaml) so authors get editor validation.


Calc profile sidecar

Example shape:

{
  "$schema": "https://graviola.top/calc-profile/v1",
  "appliesTo": { "schema": "https://myapp/schema", "fingerprint": "sha256-…" },
  "slots": {
    "#/definitions/Person/properties/fullName": {
      "formula": "CONCAT(forename, \" \", surname)"
    }
  }
}

See The sidecar pattern for fingerprint binding and scope/path duality.


Compilation, dependency graph, and stratification

Formula × auth stratification [DESIGNED]

Stratum 0  →  ground data (stored values)
Stratum 1  →  intrinsic formulas (S0 + S1 dependencies only)
               ↑ auth rules MAY reference up to here
────────────── AUTH BOUNDARY ──────────────
Stratum 2+ →  contextual formulas (operate over auth-scoped / boundary-scoped data slices)

An auth rule referencing a Stratum-2+ slot is a hard compiler error with the dependency chain and a concrete fix named in the message. Cycles are errors, never fixpoints — grouped topological sort + cycle detection (O(V+E)); no Datalog engine.

The boundary profile is parameterized: auth rules on a server, and/or completeness-scoped slots on the client. In browser-only deployments the same mechanism enforces correctness, not security: "this aggregate ran over an incomplete set" is the client-side sibling of "this aggregate ran over an auth-scoped set."

Graph operations strategy [PROPOSED]

  • Never analyze JSON Schema directly. Pipeline: schema(s) → extract computed + policy declarations → intermediate dependency graph (IR) → analyze → emit compiled profile. Needed for $ref resolution, cross-type binding paths, virtual nodes (the AUTH BOUNDARY node), and stable slotAddress node IDs.
  • Library: graphology at compile time (Layer-2-safe; runs in Bun and browser). Domain logic (boundary node insertion, stratum assignment, boundary check) remains self-owned.
  • Zero graph libraries at runtime. The compiled profile is serialized JSON:
type CompiledSlot = {
  stratum: number;
  dependents: SlotAddress[];   // reverse adjacency, precomputed
  sources: StoreId[];
  cost: CostHint;
};
// Map<SlotAddress, CompiledSlot> + schemaFingerprint

Recomputation after a write = collect transitive dependents of the dirty slot, order by precomputed stratum, evaluate. This is interim incremental view maintenance — sufficient until true delta computation is needed (Outlook).

Compile/runtime split is temporal, not topological

Invariant: "Compiler" ≠ "server". Compilation runs when a schema arrives or changes; runtime runs on every write. In local-first deployments the browser runs both.

Compilation must be a pure function (schemaSet) → compiledProfile with no environment assumptions. The compiled profile is persisted next to the data (for example IndexedDB alongside the hexastore), keyed by schemaFingerprint. Contract tests must run the compiler under Bun and in a browser context to enforce Layer-1/2 browser/server symmetry.

Two passes: structure, then weights

Invariant: Pass 1 (structural stratification) is purely structural — weights must never influence strata (otherwise auth soundness becomes cost-dependent).

  • Pass 1 — structural: stratification via grouped topological sort.
  • Pass 2 — cost/placement: annotate binding nodes with candidate sources + cost class (static, compile time), then select per query (dynamic, runtime): a cheap argmin over candidates consulting live completeness metadata and source availability. Per-node lookup, not a graph algorithm. Local completeness === true trumps everything ("zero network calls" property).

Complexity and capability context

Each calculated field declares its computational cost class and the resources it requires. Graviola's deployment targets range from in-browser applications on commodity hardware to server-side processes with substantial compute. The runtime chooses between eager and lazy evaluation, or refuses to evaluate, based on the host's declared capability context.


Provenance tie-in

A computed field materialized as a triple can carry prov:wasGeneratedBy{formulaId, stratum, inputFingerprint}. Invalidation becomes provenance-driven: dirty input ⇒ every triple whose generating activity references it is stale — the same reverse-dependents walk from the compiled profile, persisted in the graph. See Provenance and metadata.


See also

Lenses and bidirectional transforms

Graviola's trajectory treats lenses — bidirectional transformations with get/put pairs and round-trip laws — as a unifying concept spanning several surfaces that today look unrelated: inverse properties, writable computed fields, declarative mappings with a reverse direction, and version migrations (Cambria-shaped).

This chapter is proposed concept material. Version lenses are trajectory; x-inverseOf exists in production JSON Schema today but is on a retirement path toward the general lens mechanism.

For foundational lens vocabulary, see Glossary — Lens. For calculated fields, see Calculated fields.


One concept, four surfaces

SurfaceDirection todayLens framing
x-inverseOfBidirectional relationship writesSelf-inverse, total, lossless lens — the trivial case
Writable computedget only (read-only derived fields)Lens with explicit put block in calc sidecar
Declarative mappingForward-only (authority → local)Lens between external and local schema when reverse is authored
Version migrationCambria / trajectoryLens between schema versions

The insight for the concept book: x-inverseOf is a special case hardcoded where a general concept belongs. It can become a compiler-recognized bidirectional slot pattern — self-inverse lens (put = assert mirrored triple, delete = retract) — while user-visible behavior (set the relationship from either side) survives as the simplest instance of the general mechanism.


Round-trip laws

Well-behaved lenses satisfy the canonical lens laws:

  • GetPut: put(get(s)) = s
  • PutGet: get(put(s, v)) = v
  • PutPut: putting twice equals putting once with the latest value (very well-behaved lenses)

The compiler checks round-trip laws where possible (property-testing with generated instances in dev mode). The author writes the reverse direction explicitly; formulas are never inverted symbolically (computer algebra is a tarpit).


Invertibility spectrum

  1. Bijective — unit conversions, inverseOf. Both directions total and exact.
  2. Injective, partial — invertible where defined; out-of-range write fails validation.
  3. Lossy get, recoverable put — classic lens: fullName loses the split point; put recovers it from current source state — hence put(source, newValue), never inverse(newValue).
  4. Non-invertibleSUM(...). No canonical put. An application may author a distribution policy (pro-rata etc.), but that is domain logic, explicitly authored, never a default.

Writable computed fields

Mechanics (proposed):

  • A calc profile slot entry gains an optional put block (bindings + assignment expressions).
  • No put → derived schema keeps readOnly: true.
  • put present → derived schema drops readOnly; forms render the field editable; a write compiles into writes to binding targets.
  • Writability of the derived schema is computed from the sidecar — not declared by the domain author.
  • Puts may only target Stratum 0 slots (stored values) in v1. Chained inversion (put targeting another computed) is composable in theory but forbidden until a real use case argues it in.
  • Put effects re-enter the dependency graph at their targets' strata; existing cycle detection covers pathological get/put loops.

See Calculated fields for stratification and the calc profile sidecar.


Retirement path for x-inverseOf

On its own refactoring schedule, the JSON Schema extension can be removed while behavior is preserved via the self-inverse lens pattern. The same track leads to mappings-with-reverse and version lenses (Cambria, natively) as instances of one abstraction.


Open question: cross-CBD puts

v1 instinct: restrict put targets to bindings within the same named entity (CBD), because cross-entity puts reopen authorization, provenance attribution, and transactionality simultaneously.

But inverse-property users already perform cross-entity writes (adding a child writes the parent's collection). The restriction may not survive real usage. This is recorded as open in Outlook and open questions.


See also

Provenance and metadata

Graviola distinguishes two granularities of metadata: fact-level (statement-level) and entity-level (record-level / administrative). The framework intends to guarantee entity-level metadata on every backend while negotiating fact-level provenance through store capabilities.

Everything in this chapter is proposed, not yet implemented in production unless noted. Pipeline-level provenance in the ReadResult envelope exists today for progressive materialization; fact-level statement metadata and MetaSchema are trajectory.

Related: The sidecar pattern, Calculated fields, Store topology.


Two granularities, two names

GranularityNameExamples
Fact levelStatement-level metadataSource, rank, generated-at, qualifiers on a single asserted value
Entity levelAdministrative / record-level metadataCreated, modified, schema version, custodial history of the record

Conflating administrative and descriptive metadata is a classic modeling failure mode. Entity metadata describes the record as a unit; fact metadata describes individual assertions within it.

Literature anchor for fact granularity: Ding et al., Tracking RDF Graph Provenance using RDF Molecules (2005) — graph/document, molecule, triple granularity.


The named-entity boundary is a CBD

Graviola's named entity — a document that can be deep but stops wherever something links to another named thing — is precisely the Concise Bounded Description (CBD) extraction rule: descend into anonymous/subordinate structure; halt at named IRIs.

The extract-graph pipeline has implicitly used this boundary; naming it makes entity-level metadata rigorous:

Entity metadata is metadata whose subject is the CBD as a unit, not any triple within it.

(DDD analogy: Aggregate + root.)

Write granularity rule

Invariant: The granularity of the write determines the granularity of the metadata. Replacing a CBD (saving an entity) stamps entity-level modified; a sub-CBD mutation (setting certain fields) produces statement-level metadata.


Fact-level provenance: the Wikidata statement-node pattern

Canonical logical model

Direct ("truthy") property alongside a statement node reached by a one-hop-longer property (p: → statement → ps: value + qualifiers + references; wdt: as truthy shortcut).

Chosen as canonical because it is expressible in plain SPARQL 1.1 triples — everything else is a storage encoding of it. Consistent with capability-declaring philosophy: the abstract model is universal; the encoding is negotiated.

Capability extension

New capability facet on the Store descriptor (naming to align with @graviola/store-core conventions):

provenance?: {
  statementLevel: 'rdf-star' | 'statement-node' | 'named-graph' | 'side-table' | 'none';
  entityLevel: boolean;
}

Typical assignments: Oxigraph → rdf-star; generic SPARQL 1.1 → statement-node; quad stores → named-graph; Prisma → generated _statements side table; REST → none.

none still isn't zero provenance: pipeline-level provenance (which store answered, when materialized, query fingerprint) is framework-guaranteed — it exists as ReadResult.provenance and progressive-materialization triples (prov:wasAttributedTo, prov:generatedAtTime). Only fact-level provenance is capability-gated; where a store cannot carry statement annotations natively, the materialization layer carries them (declared, not hidden — same honesty discipline as capability simulators).

Schema derivation: $stmt

deriveProvenanceSchema(schema) — pure Layer-1 function. For each property, emits the original (truthy) plus a $stmt sibling array:

{ value, rank: preferred|normal|deprecated, source, generatedAt, wasGeneratedBy, qualifiers }

Because the derived artifact is ordinary JSON Schema, existing machinery works unchanged: sparql-schema emits the one-hop-longer CONSTRUCT (per SPARQL flavour), graph-traversal extracts it, DetailRenderer can render a provenance panel, typed filters can constrain on it:

where: { birthDate$stmt: { some: { source: 'nas01', rank: 'preferred' } } }

Write policy — do not reify everything

Statement nodes multiply triple count 3–5×. Per-property declarative policy: provenance: always | on-conflict | never (LinkML annotation → sidecar/derived artifact).

on-conflict is the federation-relevant mode: the direct triple exists alone until a second source asserts a different value; then both values get statement nodes with source provenance and the truthy triple becomes a resolved value. Resolution = rank + source trust weight — computing the truthy triple is itself a Stratum-1-style derivation, connecting provenance to the weights pass.

Invariant: Truthy property and statement array are dual-asserted on write (as Wikidata does). Never derive truthy at query time — otherwise every read pays resolution cost and completeness guarantees get murky.


Entity-level metadata: $meta

Asymmetric guarantees

  • Entity-level metadata is framework-guaranteed, never capability-gated. It degrades to plain triples (or columns) on any backend including REST and Prisma. Preferred encoding where quads exist: named graph per entity (graph-per-aggregate; the graph node carries dct:created, dct:modified, gra:schemaVersion, prov:wasAttributedTo).
  • Fact-level metadata is the negotiated capability (above).

Every store can say when a document changed; only some can say when a field changed.

$meta derivation

One $meta block per named entity in derived schemas — nested named entities in a deep result each carry their own $meta; anonymous nested structure never does. The CBD boundary decides mechanically; no per-schema annotation.

Interlock, not duplication: entity modified is derivable as max(generatedAt) over statement metadata where fact-level exists, stored directly where it doesn't (same dual-assertion discipline as the truthy triple). gra:schemaVersion at entity level is the anchor the future lens/migration system needs (Entity version).

Invariant: $meta is system-asserted, never user-asserted. It is excluded from the write-validation schema; client-supplied $meta on upsert is rejected or ignored. Otherwise administrative metadata silently becomes descriptive data with a funny name.

Reads opt in via include: { $meta: true } (mirroring the typed filter surface). SemanticTable meta columns, DetailRenderer provenance panels, and typed filters over $meta require zero new rendering or query machinery — the composed artifact is ordinary JSON Schema.


The MetaSchema sidecar

Application-extensible document-level metadata must not live in the domain schema. It is the third sidecar instance: data schema / UI schema / meta schema.

  • A MetaSchema is an ordinary JSON Schema document with its own $id, registered alongside domain schemas (metaSchemata: { default, byType } in provider config, resolved with the same discipline as extended schemas).
  • The framework ships a base profile (created / modified / schemaVersion / provenance — the framework-guaranteed floor) with dct:/prov: vocabulary mappings; applications extend via allOf (e.g. reviewStatus, importBatch, catalogingAgency, syncState).
  • Extension fields require IRI mappings like domain fields so entity-level and fact-level metadata land in the same RDF graph with real semantics — never a JSON-blob property.
  • MetaSchema is schema-as-data: storable in the triple store, versioned, referenced from entity metadata (gra:metaSchemaVersion alongside gra:schemaVersion) so administrative metadata is migratable with the same future lens machinery as domain data.
  • Composition: deriveExtendedSchema(domainSchema, metaSchema) grafts typed $meta onto each CBD boundary. The write-validation path consumes the domain artifact only.

Stratification and provenance

Computed triples carry prov:wasGeneratedBy{formulaId, stratum, inputFingerprint}. Invalidation is provenance-driven — see Calculated fields — Provenance tie-in.


See also

Store topology

Graviola's storage layer today is the Store interface in @graviola/store-core: capability facets composed by intersection, mirrored at runtime by the CapabilityDescriptor. Concrete backends implement subsets of those facets; the framework simulates missing capabilities honestly.

The Store Registry (federation across multiple stores) is not yet implemented in the form described here. This chapter records registry-level design vocabulary so deployment planning and new backends stay compatible.

For what ships today, see Capabilities today. The legacy term AbstractDatastore still appears in some packages; new concept material uses Store only.

Related: Provenance and metadata, Calculated fields, The shape of a federated application.


Store roles are dimensions, not categories

Deployment lists ("main DB", "cache", "helper DB", "translator") are folk taxonomy. The intended orthogonal dimensions:

DimensionQuestionNotes
AuthoritySource of truth, or derivable from one?A deployment role, never an engine property — the same Oxigraph is authoritative in one deployment and a cache in another
DurabilitySurvives process/session/device loss?in-memory Oxigraph vs IndexedDB vs server store
DerivabilityRebuildable from a named other store?New registry relation: derivedFrom: storeId
Shape fidelityReturns schema-shaped documents, or raw triples needing extract-graph?New capability flag. Prisma/REST: shaped; SPARQL: raw. The graph-traversal last mile is skipped when the store declares shape fidelity
NativenessReal database vs translator over another application's stateThunderbird, mount hierarchies — read-only registry stores; no new concept

derivedFrom

This relation pays three ways:

  1. Reindexing is a defined operation (rebuild(meilisearch) = replay from its authority).
  2. Invalidation has a direction (truth changed → derived stale).
  3. Federation "winning strategies" get an objective ordering (authoritative beats derived at equal recency; then rank/trust weights — see on-conflict reification).

The composite store pattern

Two (or more) physical engines behind one Store — the internal seam invisible to the registry. One component holds authority; the other is a derived specialization.

Examples:

  • QLever + writable endpoint (read speed + write path)
  • Blazegraph + Meilisearch (full-text; Meilisearch always reindexable from Blazegraph)
  • Oxigraph + PostGIS (geo)
  • PostgreSQL + TimescaleDB (fast-accumulating properties)

The composite declares the union of its components' capabilities in its descriptor and routes internally.

Why composites exist: to hide a store boundary that would otherwise cut through an entity. Postgres+Timescale is the sharp case — an entity's fast-accumulating properties live in a different physical engine than its stable properties; the store boundary cuts through the CBD. Exposed to the registry: consistency nightmare. Encapsulated: the entity stays whole from outside.


The CBD-cut invariant

The entity boundary (CBD) is the unit of consistency and metadata; the store boundary is the unit of availability, capability, and provenance. The store boundary must never visibly cut the entity boundary: either an entity's CBD lives wholly within one registered store, or the cut is hidden inside a composite store that presents wholeness. Cross-store links between entities are normal federation; cross-store splits within an entity are the composite's job.

Existing design already assumes this quietly (entity $meta stamped per store, completeness per type+filter+source, put targets restricted to one CBD) — this promotes the assumption to a stated invariant.

See Concise Bounded Description (CBD).


React Query — position defended

TanStack Query stays in the UI layer. Its position is architecturally defensible: it caches extract-graph outputs keyed by query — denormalized render-shaped JSON, a different artifact from anything below (not triples, not entities).

What degrades as store complexity grows is only its invalidation heuristic (key-pattern matching). The eventual fix is not moving RQ down but feeding it signals from below: completeness metadata + the compiled profile's reverse-dependents already know which type+filter sets a write dirties → emit affected query fingerprints upward; RQ invalidates those keys. RQ demotes from deciding invalidation to delivering it. Incremental, no rework.


The guardrail principle

Every store scenario must be expressible with existing vocabulary — role, capability facet/descriptor, derivedFrom, composite, shape fidelity. If a new deployment ever seems to require a new mechanism, that is the smell to investigate before building.

Net-new registry vocabulary from the July 2026 design session is deliberately modest: one relation (derivedFrom), one wrapper pattern (composite store), one invariant (CBD-cut), one capability flag (shape fidelity). No new algorithms, no new artifact kinds.


See also

LinkML as an authoring source for schemas

This chapter describes an optional authoring path for teams using Graviola: adopt LinkML as a single document they edit, then run a build-time generator that emits the same artifacts Graviola already consumes — JSON Schema or Zod, JSON Forms UI schema, mapping and other declarative configuration. Graviola at runtime is unchanged and takes no LinkML dependency.

For what ships today in the framework, see Capabilities today. Planned features mentioned below (for example calculated fields, MetaSchema) are described under Architectural trajectory and its detailed chapters — Calculated fields, The sidecar pattern.


Context

Graviola is built around JSON Schema at runtime. The choice is deliberate: JSON Schema is widely understood, well-tooled, and used across many communities outside the semantic-data world. JSON Forms consumes it directly. Validators are abundant. The translation from JSON Schema to SPARQL, to relational queries, and to TypeScript types is well-explored in the framework. Some applications use Zod 4 instead, deriving JSON Schema from Zod where the framework requires it; the framework supports both shapes.

In practice, however, a Graviola application's model rarely lives in a single file. Around the central JSON Schema (or Zod schema) accumulate complementary declarations for the same conceptual model:

  • A UI schema giving JSON Forms rendering hints that the schema alone cannot supply.
  • Declarative mappings for transforming data from external authorities (Wikidata, GND, DBpedia) into the local model.
  • Application-specific configuration for authorization, calculated fields, default views, and other cross-cutting concerns.
  • Occasional JSON Schema extensions (x-* keys) for things the standard does not express — for example the inverse of a property.

At runtime these pieces are unified by a shared addressing convention: scopes (JSON Pointer-like paths), mapping-layer selectors, and type IRIs for entity classes. That co-existence is intentional.

What is awkward at authoring time is fragmentation: domain experts may edit several files in several formats, each with its own naming and reuse conventions.

The intended trajectory consolidates orthogonal concerns into scope-keyed sidecars while keeping the domain JSON Schema pure — see The sidecar pattern. UI schema is already a sidecar in production; calc profiles and MetaSchema follow the same dispatch rule.

One concrete example: an x-inverseOf extension can declare that one property is the reverse of another. JSON Schema has no native inverse; the extension lets the query planner and graph-to-JSON extractor know which side is canonical. It works, but it pushes semantic detail into a vocabulary that was not designed for it — and is on a retirement path toward the general lens mechanism. Similar pressures appear as the framework grows.


What LinkML offers

LinkML is a modeling language (YAML-based, linked-data aware) aimed at describing models richly enough that many downstream representations can be generated: JSON Schema, OWL, SHACL, RDF, documentation, and more.

For Graviola-oriented authoring, four properties stand out:

  1. Native constructs for several things JSON Schema only covers by extension — for example inverse:, equals_expression:, identifier: true, multivalued slots, slot reuse across classes. The inverse-property case can be expressed as first-class LinkML instead of only as x-inverseOf in JSON Schema.
  2. Namespaced annotations on classes, slots, and types — for example ui.label, auth.read, calc.complexity. LinkML carries them; a project-specific generator decides how they map to emitted files.
  3. Compile-first workflow — author one schema, generate artifacts per consumer. JSON Schema becomes an output, not necessarily the hand-maintained source.
  4. Single-document legibility — for humans and for tooling (including LLM-assisted authoring), one file can hold structure, relationships, presentation hints, and cross-cutting metadata in one place.

Further reading:


Build-time pattern

The authored source is a LinkML schema. The application author runs a generator in the build pipeline. It emits artifacts that would otherwise be maintained by hand: JSON Schema (or Zod), JSON Forms UI schema, mapping configuration, authorization rules, a calc profile sidecar (when the application adopts calculated fields), a MetaSchema companion (when entity-level metadata extensions are needed), and any other agreed outputs.

flowchart LR
    subgraph buildLayer [Application build]
        LK["LinkML schema"]
        M["Generator"]
        JS["JSON Schema or Zod"]
        UI["UI schema"]
        MC["Mapping config"]
        AC["Authorization config"]
        CC["Calc profile sidecar"]
        MS["MetaSchema sidecar"]
    end

    subgraph runtimeLayer [Application runtime]
        APP["Graviola"]
    end

    LK --> M
    M --> JS
    M --> UI
    M --> MC
    M --> AC
    M --> CC
    M --> MS
    JS --> APP
    UI --> APP
    MC --> APP
    AC --> APP
    CC --> APP
    MS --> APP

The generator is an application concern — CLI, build script, bundler plugin, or small program — not part of Graviola. Generated files can be committed for review or produced in CI; either fits the framework.

Two consequences:

  • No framework change is required for this path. CRUD, forms, SemanticTable, and mapping keep consuming the same artifact shapes; Graviola does not care whether they were handwritten or generated.
  • Hand authoring remains fully supported. LinkML is one possible upstream; others are equally valid.

Annotated example

The following LinkML sketch shows classes and slots with annotations in several namespaces. A real project's generator maps each namespace to its target format.

id: https://example.org/schemas/library
name: LibrarySchema
description: Persons and works in a small library catalog
prefixes:
  ex: https://example.org/
  linkml: https://w3id.org/linkml/
default_prefix: ex
imports:
  - linkml:types

classes:
  Person:
    description: A natural person
    tree_root: true
    slots:
      - id
      - forename
      - surname
      - fullName
      - authoredWorks
    annotations:
      ui.list_renderer: chip
      ui.detail_layout: two_column
      auth.read: public
      auth.write: "role:editor"

  Work:
    description: A book, article, or other authored work
    tree_root: true
    slots:
      - id
      - title
      - author
      - publicationYear
    annotations:
      ui.list_renderer: card
      auth.read: public
      auth.write: "role:editor"

slots:
  id:
    identifier: true
    range: uriorcurie

  forename:
    range: string
    required: true
    annotations:
      ui.label: "First name"
      ui.detail.priority: 10

  surname:
    range: string
    required: true
    annotations:
      ui.label: "Last name"
      ui.detail.priority: 10

  fullName:
    range: string
    annotations:
      graviola.computed: 'CONCAT(forename, " ", surname)'
      ui.detail.priority: 1
      ui.label: "Full name"

  authoredWorks:
    range: Work
    multivalued: true
    inverse: author
    annotations:
      ui.list.collapsed: true
      ui.label: "Works"

  author:
    range: Person
    required: true
    annotations:
      ui.label: "Author"

  title:
    range: string
    required: true
    annotations:
      ui.detail.priority: 1

  publicationYear:
    range: integer
    annotations:
      ui.label: "Year of publication"
      ui.detail.priority: 5

Emitted JSON Schema (or Zod) — ranges, identifiers, required and multivalued flags, and class structure carry over. Computed slots appear as ordinary readOnly: true properties with no computation vocabulary in the domain artifact.

Emitted calc profile sidecar — from graviola.computed annotations, scope-keyed, fingerprint-bound to the domain schema. See Calculated fields — defaults ladder.

Emitted UI schema — from ui.* annotations: labels, layout hints, list renderers, field ordering, collapsed lists.

Inverseinverse: author on authoredWorks replaces a hand-maintained x-inverseOf-style declaration. The generator emits whatever companion shape the project uses for the query planner and graph-to-JSON layer; runtime still does not read LinkML. Long term, inverse is a self-inverse lens pattern.

Authorizationauth.* maps into the application's own rule format; the vocabulary is project-defined.


Implementation footprint

Adopting this pattern is bounded work for the application author:

  • A LinkML reader — often the official linkml tooling from a build subprocess, or a TypeScript reader for the subset the project uses.
  • A generator that walks parsed LinkML and emits the chosen artifact set, ideally as small per-namespace handlers (ui, auth, calc, view, …) so new concerns add handlers rather than entangling the whole pipeline.
  • A documented registry of annotation namespaces and meanings for the team.

Graviola packages continue to consume the same outputs as before.


Boundaries

This is a new authoring path, not a mandate.

  • Per-project or per-schema LinkML adoption is fine; the same monorepo can mix LinkML-backed and hand-authored apps.
  • The generator runs at build time; Graviola at runtime sees only generated (or handwritten) artifacts.
  • The generator should translate only a documented subset of LinkML plus agreed annotations. Constructs outside that subset should be ignored or reported, not silently mis-translated.

The generator stays small by design: its contract is what Graviola and the application can act on, not everything LinkML can express.


Summary

Runtime Graviola stays centered on JSON Schema (or Zod-derived JSON Schema) plus companion declaratives, coordinated by scopes, selectors, and type IRIs. Optional LinkML authoring reduces authoring-surface fragmentation: one edited document and a build step that produces the same artifact bundle the framework already expects — with no runtime LinkML dependency and no requirement to abandon hand-maintained schemas.


See also

Outlook and open questions

This chapter collects unresolved design tensions and research-style questions that arise from combining Graviola's trajectory (lenses, calculated fields, signing, federated sync, provenance, store topology) with real deployments. It is intentionally separate from the Glossary, which stays focused on definitions and stable vocabulary.

For capabilities that are directionally chosen but not yet production guarantees, see Architectural trajectory. For how generative tooling may attach to schema-driven workflows without changing the framework's core contract, see Graviola in the age of generative tools.


Cross-version calc sync

How to reconcile a Calculated field computed on a peer at V_a with one computed on a peer at V_b when the underlying schemas are linked by a Lossy lens. Likely requires the calc to declare its valid version range and the runtime to skip cross-version cache reuse.


Lens inference

Whether (and to what extent) lenses between adjacent schema versions can be inferred from a structural diff of the schemas themselves, rather than authored by hand. Promising for trivial cases (rename, add-with-default); intractable in general.


Provenance through lenses

How Signed state survives forward-and-back migration. A signature over Person_V1 is not a signature over Person_V2 — but if the lens is signed and well-behaved, the trust can be transitively reconstructed. Design unclear.


Calc migration across lossy boundaries

When a Calculated field reads a field that gets split or merged by a Lossy lens, the formula no longer references valid sources in the new schema. Auto-rewriting formulas across lossy boundaries silently produces wrong results; the safe default is to mark the calc as invalidated under that migration and surface it. A better answer is open.


Cross-CBD puts

When a writable computed's put targets bindings outside the current CBD: one transaction, or rejected write? v1 restricts puts to Stratum 0 slots within the same named entity, but inverse-property usage already performs cross-entity writes (adding a child updates the parent's collection). The restriction may not survive real usage — decide consciously, not by default. See Lenses and bidirectional transforms.


Provenance descriptor naming

The fact-level provenance capability block on the Store descriptor needs naming aligned with @graviola/store-core facet conventions before implementation. See Provenance and metadata.


Sidecar default expansion

Resolved as "terse authored forms, expanded compiled form." Revisit only if debugging pain contradicts. See Calculated fields.


Relation-query bindings and where-in-aggregate

Deferred to level 4 in the defaults ladder. Admit only with a concrete use case that the intermediate-slot idiom cannot express.


True IVM vs reverse-dependents

Reverse-dependents recomputation from the compiled profile is the interim incremental strategy; true delta computation (differential dataflow-style) is a later trajectory item.


See also

Graviola Glossary

Navigation: This glossary deepens vocabulary used across the book. For the product overview first, see What Graviola is and Capabilities today. For future direction, see Architectural trajectory. For unresolved design questions that span multiple terms, see Outlook and open questions.

A working vocabulary for the Graviola framework: federated, schema-evolving, local-first semantic data infrastructure. This glossary names the concepts Graviola relies on, points at the literature and prior projects that defined or refined each one, and gives short examples grounded in Graviola's actual use cases (cultural heritage, personal information management, offline-first field deployments).

The glossary is organized in layers, working roughly from foundations outward. Cross-references between entries use bold on first mention. Each entry has a short definition, an Example where one helps, See also cross-references, and References with links to literature or prior projects.

A note on optionality. Graviola is built from small, composable libraries. Most of the machinery described here — lenses, IVM, signed states, reasoning — is optional. Many Graviola applications use a single fixed schema with classical migrations (see Classical Migration) and never touch the lens engine; others use only the UI dispatch layer over a Prisma-backed PostgreSQL or MongoDB store. The architecture is designed so that none of these advanced concepts becomes a blocker for the simple cases.


1. Foundations

1.1 Schema-as-Data

Schemas (LinkML, JSON Schema, SHACL shapes) are not ambient configuration baked into deploys but first-class documents with @ids that travel through the same sync layer as the data they describe. A consequence: schemas can be authored, versioned, signed, and migrated by the same machinery as any other entity.

Example: On a ship with intermittent connectivity, a domain expert authors a JSON Schema via JSON Forms. The schema syncs across peers via Yjs/WebRTC alongside the data conforming to it.

See also: Lens-as-Data, Federated Sync Layer, Entity Version.

References:


1.2 Structural Dispatch

The architectural principle that behavior — UI rendering, mapping, validation, calculation, lens application — is bound to the shape declared by a schema (or to a property carried by an entity), not to a nominal type or class. This single pattern recurs at every layer of Graviola and is what allows components to survive Schema Drift without code changes.

Dispatch is itself a schema-level operation: testers match against Scopes (schema-node pointers) rather than Binding Paths (data traversals). This is why a renderer registered for #/properties/birthDate works against any Person instance without further configuration.

Example: JSON Forms resolves a renderer for {type: "string", format: "date"} regardless of which entity type contains the field. The same principle drives lens dispatch by Entity Version and class derivation by property (see Derived Versioned Class).

See also: Scope, JSON Forms, Declarative Mapping.

References:


1.3 Federated Sync Layer

The transport-and-replication substrate (in Graviola: Yjs, optionally over WebRTC, WebSocket, or Solid Pods) that moves both data and schema documents between peers without assuming a central authority. The sync layer makes no semantic decisions; it only guarantees eventual consistency of opaque documents.

Example: Ship deployment where servers are pure relays and have no plaintext access; peers sync end-to-end-encrypted documents and resolve schema versions locally on reconnection.

References:

  • Yjs
  • Local-first software (Kleppmann, Wiggins, van Hardenberg, McGranaghan, Ink & Switch, 2019)
  • Shapiro, Preguiça, Baquero, Zawirski, "Conflict-Free Replicated Data Types" (2011)

1.4 Reasoning-Compatible, Reasoner-Optional

Graviola's conceptual model is shaped by description-logic and rule-based reasoning (property-driven class derivation, entailment, transitive sameAs), but Graviola does not ship a reasoner. Where the underlying datastore supports reasoning (e.g., a triple store with OWL RL or a SHACL-AF engine), derivations can be materialized; where it does not, the same derivations can be computed at extraction time by Graviola's pipeline. Both paths produce the same query semantics for the cases Graviola cares about.

See also: Derived Versioned Class.

References:


1.5 Scope

A pointer into a schema document — a JSON Pointer such as #/properties/name — that identifies a schema node: a property definition, a type definition, or a rule. Scopes exist at schema-compile time and are absolute relative to a single schema document; they answer the question where in the schema does this apply? JSON Forms uses scopes to bind UI elements to schema nodes (scope: "#/properties/name" means "this UI element corresponds to this schema node"). A scope navigates the schema document, not the data graph; it never crosses a $ref to another entity and has no concept of a current instance.

In knowledge-representation terms a scope is a TBox pointer: it addresses the schema, not the data.

Example: A renderer registry tester matching scope: "#/properties/birthDate" selects the schema property declaration regardless of which Person instance is rendered. The same scope is correct on the empty form, on a half-filled form, and on a saved entity.

See also: Binding Path, Structural Dispatch, JSON Forms.

References:


1.6 Binding Path

A traversal through instance data, guided by the schema, that resolves to one or more data values relative to a current entity. A binding path such as patch.lane.owner starts at "the current instance," follows declared relations across $ref boundaries, and may fan out to many values when any hop is an array. Paths exist at runtime, against a live store; they answer the question what value do I need from the data graph?

In knowledge-representation terms a binding path is an ABox traversal, shaped by the TBox: the schema declares which hops are valid; the data supplies the values.

Example: A formula binding EQ(ownerId, currentUserId) resolves ownerId by following lane.owner.id from the current Patch. Substituting a Scope here would be a category error: schema nodes have no runtime values to compare.

Rule of thumb: use a Scope when you are describing something about the schema structure itself — which property a rule applies to, which field a renderer corresponds to, which definition an annotation targets. Use a binding path when you are describing a traversal through data — what value to retrieve, what relation to follow, what to compute over. Scopes are answered by the schema alone; binding paths are answered by the schema plus the data.

See also: Scope, Calculated Field, Structural Dispatch.


2. Schema Evolution & Versioning

2.1 Schema Version

A specific, content-addressable state of a schema document, identified by an @id and typically a semver tag. Schema versions are themselves documents and live in the same sync layer as data (see Schema-as-Data).

See also: Entity Version, Lens.


2.2 Entity Version

A property — gra:version or equivalent — carried by each named entity, recording which Schema Version the entity was authored under. Version is a property of the entity, not (only) of its container or store. This is what makes mixed-version data within a single store the normal case rather than an exception: a query for entities of a given conceptual type returns instances of all versions, and refinements by version are just property filters.

Example: A query against a federated person index returns {@id: ..., @type: ex:Person, gra:version: "0.3.2", name: ...} and {@id: ..., @type: ex:Person, gra:version: "0.4.5", forename: ..., surname: ...} side by side. The consumer's tool decides whether to apply a Lens based on the gra:version it sees.

See also: Derived Versioned Class, Schema Drift.


2.3 Derived Versioned Class

A conceptual subclass of an entity type, derived at runtime by the value of a property — most importantly Entity Version, but the pattern is general. ex:Person_V2_3_0 is the (conceptual) subclass of ex:Person whose members carry gra:version "2.3.0". Such derived classes can drive query refinement, dispatch in the graph-to-JSON extraction pipeline, and lens selection.

This is the same pattern as deriving ex:Author from ex:Person by the presence of an authored work, or ex:SignedDocument from ex:Document by the presence of a Signed State — property-driven subclass derivation, well-understood in description logics. Graviola applies it to versioning.

Example: The graph-to-JSON pipeline for a visualization plugin declared at V0_3_8 selects entities that are either ex:Person_V0_3_8 directly or are reachable via lens composition from another version. The selection is expressed as a query over gra:version, not as a separate negotiation step.

See also: Structural Dispatch, Reasoning-Compatible, Reasoner-Optional.

References:

  • Baader, Calvanese, McGuinness, Nardi, Patel-Schneider, The Description Logic Handbook (Cambridge University Press, 2003) — for the general theory of property-driven class derivation.

2.4 Schema Drift

The condition in which entities within a federation — or within a single store — carry different values of Entity Version for the same conceptual type. Drift is the normal case in Graviola, not an error to recover from. Tools handle drift either by applying a Lens chain, by restricting their query to a specific Derived Versioned Class, or by falling back to Classical Migration where the application owns the model.

References:

  • COPE / Edapt (Herrmannsdoerfer et al.)
  • Curino, Moon, Zaniolo, "Graceful database schema evolution: the PRISM workbench" (VLDB 2008).

2.5 Lens

A pair of transformations consisting of a forward function (get) and a reverse function (put), ideally satisfying Lens Law round-trip properties. Lenses are Graviola's primary mechanism for handling Schema Drift between schema versions.

The trajectory direction (July 2026) reframes lenses as the unifying concept behind inverse properties (x-inverseOf), writable computed fields, reversible Declarative Mapping, and version migrations — see Lenses and bidirectional transforms. The lens engine is an optional, pluggable component; many Graviola applications run without it.

Example: A lens from Person_V1 (single name field) to Person_V2 (forename, surname) defines how to split forward and how to recombine backward.

See also: Asymmetric Lens, Symmetric Lens, Lens Law, Lens Composition, Put (Graviola sense).

References:


2.6 Asymmetric Lens

A lens where one side is canonical and the other is a view. The reverse direction reconstructs the source from the view plus the original source. Most version-pair migrations are asymmetric.


2.7 Symmetric Lens

A lens where neither side is a strict view of the other; both sides may hold information the other lacks. Necessary for cross-vocabulary alignment (e.g., Graviola's local model ↔ Wikidata) where each side has fields the other doesn't.

References:


2.8 Lens Law

A property a well-behaved lens must satisfy. The three canonical laws:

  • GetPut: getting a view and putting it back unchanged yields the original source.
  • PutGet: putting a view, then getting, yields what was put.
  • PutPut: putting twice equals putting once with the latest value (very well-behaved lenses only).

Lenses that fail PutGet are Lossy and require Witness Preservation to round-trip safely.


2.9 Lens-as-Data

Lenses are themselves serializable JSON-LD documents with @ids, syncing through the same Federated Sync Layer as schemas and data. A lens can be authored, versioned, and signed independently of code.

Example: A historian signs a lens migrating heritage Person_V1 → Person_V2, vouching that the split of name into forename/surname was performed correctly for their corpus. The signature itself is a Signed State over the lens document.

References:


2.10 Lens Composition

The act of chaining lenses (A→B, B→C, C→D) into a single lens (A→D). Composition is associative; well-behavedness composes. In Graviola, a peer encountering data at V0_3_2 while running V0_4_5 assembles the migration chain by composition.

See also: Lens Fusion.


2.11 Lens Fusion

Static algebraic simplification of a composed lens chain before execution: a rename followed by a rename of the same field collapses; an add followed by a remove cancels. Graviola's "compile to fast runtime struct" step is fusion, not codegen.

References:


2.12 Lens Operator Catalog

The fixed, small set of lens primitives from which all migrations are built. Cambria's catalog: rename, hoist, plunge, wrap, head, add, remove. Graviola's catalog will likely overlap heavily; the design question is granularity (more primitives = more fusion opportunities; fewer = simpler authoring).


2.13 Lossy Lens

A lens whose forward direction discards information that the reverse direction cannot recover from the view alone. Splitting name → (forename, surname) is lossy in reverse if the original whitespace, ordering, or particle handling matters.

See also: Witness Preservation.


2.14 Witness Preservation

The technique of carrying a small companion record alongside migrated data that records what would otherwise be lost. The reverse lens consults the witness when reconstructing the source.

Example: Forward migration of "van der Berg, Jan" to {forename: "Jan", surname: "van der Berg"} emits a witness {originalName: "van der Berg, Jan", splitStrategy: "comma-first"}.


3. Mapping & Integration

3.1 Declarative Mapping

A JSON-LD-flavored DSL (Graviola's existing implementation) describing how to transform a source document into a target document via path-based source/target pairs and optional named Mapping Strategies. Used today primarily for ingesting authority data (GND, Wikidata, DBpedia) into the local model.

Example: The wikidataPersonMapping in Graviola's existing codebase: source $.claims.P569[*].mainsnak.datavalue.value.time → target birthDate via the dateStringToSpecialInt strategy.

See also: Mapping Strategy, R2RML / RML.


3.2 Mapping Strategy

A named, reusable transformation function (concatenate, takeFirst, createEntity, dateStringToSpecialInt, etc.) referenced by id from a Declarative Mapping entry. Strategies receive the source value, the current target value, options, and a Strategy Context.


3.3 Strategy Context

The runtime environment passed to a mapping strategy: logger, IRI minter, authority access, secondary-IRI resolver, mapping table, and a createDeeperContext continuation for recursive mapping into nested entities.


3.4 Migration Lens vs. Cross-Source Query vs. Tool Projection

Three distinct mapping shapes that Graviola deliberately keeps separate:

  • Migration Lens: between two versions of the same conceptual schema; bidirectional in principle; used for Schema Drift.
  • Cross-Source Query: assembles a target document from one or more foreign sources (Wikidata, GND); typically forward-only; the existing Graviola Declarative Mapping is this.
  • Tool Projection: narrows a canonical entity to the fields a specific tool needs (e.g., a filelight visualization needs only {path, size, parent}); read-only; cheap.

Conflating these is a known failure mode of "universal data integration" projects.


3.5 Mediated Schema (LAV / GAV / GLAV)

The classical data-integration framings:

  • GAV (Global-as-View): the global schema is defined as views over local sources. Adding a new source requires updating the global schema.
  • LAV (Local-as-View): each local source is described as a view over the global schema. New sources join without touching the mediator. Best fit for Graviola.
  • GLAV: a hybrid.

References:


3.6 R2RML / RML

W3C-standard declarative mapping languages from relational (R2RML) or heterogeneous (RML) sources to RDF. Graviola's declarative mapping is a JSON-LD cousin of these, optimized for JSON Linked Data rather than serialization-level transformation.

References:


3.7 Ontology Alignment

The (largely separate) problem of relating concepts across vocabularies, e.g., declaring that schema:Person and foaf:Person refer to the same class. Often expressed via owl:sameAs, skos:exactMatch, skos:closeMatch. Distinct from Lens-based migration: alignment is about identity of concepts, lenses are about transformation of representations.

References:

  • Euzenat & Shvaiko, Ontology Matching (Springer, 2nd ed. 2013).

4. Calculated Fields & Reactivity

4.1 Calculated Field

A schema property whose value is derived from other fields by a declarative formula (HyperFormula-style or similar) rather than stored directly. Structurally equivalent to a one-directional lens (get only).

Formula inputs are addressed by Binding Paths, not by Scopes: a calc must read live values from the current entity (and its declared relations), so its references are ABox traversals shaped by the schema. Schema-node pointers would be a category error here — there is nothing to compute over until a path is resolved against actual data.

Example: Person.fullName calculated as CONCAT(forename, " ", surname); an aggregate calc on a Patch reads lane.owner.id via a binding path that crosses a $ref boundary.

See also: Binding Path, Stratification, Incremental View Maintenance.

References:

  • HyperFormula — the formula engine Graviola's calc layer is patterned after.

4.2 Dependency Graph

The DAG of which calculated fields read which other fields. Used to determine recomputation order and to detect cycles.


4.3 Stratification

The ordering of a Dependency Graph into layers (strata) such that each layer depends only on previous layers. In Graviola's intended calc design, stratification assigns computed slots a stratum via grouped topological sort; an auth or completeness boundary separates Stratum 1 (intrinsic formulas, auth-rule-safe) from Stratum 2+ (contextual formulas over scoped data slices). Auth rules referencing Stratum 2+ are compile errors. Cycles are errors, never fixpoints.

Status: designed for @graviola/formula-dependency; not yet in production. See Calculated fields.

See also: Boundary profile, Compiled profile.

References:


4.4 Incremental View Maintenance (IVM)

The technique of updating a derived view in response to input changes by computing only the delta, rather than re-evaluating from scratch. The performance backbone of any nontrivial Calculated Field system at scale.

References:

  • Gupta & Mumick, "Maintenance of Materialized Views: Problems, Techniques, and Applications" (IEEE Data Eng. Bulletin, 1995).
  • Differential Dataflow (McSherry et al.).

4.5 Differential Dataflow

The modern, industrial-grade form of IVM: a dataflow framework that maintains the result of arbitrarily complex relational and iterative computations under input changes, with provable efficiency. Likely overkill for in-browser Graviola but the right reference point for server-side calc-heavy workloads.

References:


4.6 Complexity Annotation

A declarative tag on a Calculated Field describing its computational cost class (e.g., O(1), O(n), O(n²)) and optionally its memory footprint. Used by the runtime to choose between eager and lazy evaluation strategies and to decide whether the calc is admissible in a given Capability Context.


4.7 Capability Context

The set of resources available to the current Graviola host: memory, persistence, network, server-presence, GPU, etc. Calculated fields and visualizations declare what they need; the runtime matches and either runs, degrades, or refuses.

Example: A calc that needs {memory: "high", server: true} is skipped on the encrypted-ship deployment and surfaced as "unavailable in this environment."


4.8 Calc-as-Pure-Derivation vs. Calc-as-Cached-Materialized-View

The unresolved design tension for federated calculated fields:

  • Pure derivation: each peer recomputes locally from synced inputs. Clean, always consistent, potentially expensive.
  • Cached materialized view: results are computed once (e.g., server-side) and synced; must invalidate correctly across version skew.

Genuinely an open problem when combined with Schema Drift across CRDT-synced peers. See Cross-version calc sync.


4.9 Calc profile (sidecar)

A scope-keyed JSON document carrying computation declarations for a fingerprinted domain schema. Third instance of the sidecar pattern after UI schema and MetaSchema. Keys are Scopes; binding declarations inside entries are Binding Paths.

Status: proposed. See Calculated fields.


4.10 Compiled profile

Fully expanded, derived computation artifact: strata, cardinalities, reverse dependents, eval placement, source candidates. Serialized JSON keyed by schemaFingerprint; the only form containing derived facts (never the authored sidecar). Zero graph libraries at runtime.

Status: proposed.


4.11 Defaults ladder

Authoring principle for calculated fields: simplest form is a bare formula string; every escalation (paths, bindings, context roots, aggregates, resolvers, puts) is opt-in. Debug expansion lives in the Compiled profile, not in authored forms.

Status: proposed. See Calculated fields — The defaults ladder.


4.12 Boundary profile

Parameterized input to stratification: auth rules (server) and/or completeness-scoped slots (client). Same mechanism enforces security on the server and correctness ("aggregate over incomplete set") in the browser.

Status: proposed.


4.13 Put (Graviola sense)

Explicitly authored reverse direction of a computed slot in the calc profile; targets Stratum 0 slots only in v1. Presence makes the derived-schema property writable; absence keeps readOnly: true. Formulas are never inverted symbolically.

Status: proposed. See Lenses and bidirectional transforms.


5. Authority, Trust, & Provenance

5.1 Authority

An external data source treated as a reference for entity identity and attributes (Wikidata, GND, DBpedia, VIAF). Graviola's Declarative Mapping layer was built primarily to ingest from authorities into the local model.


A link from a local entity to its corresponding entry in an Authority, typically expressed as owl:sameAs or via a domain-specific property. Enables later re-fetching, cross-referencing, and trust evaluation.

Example: A local Person with sameAs http://www.wikidata.org/entity/Q42 for Douglas Adams.


5.3 Primary IRI / Secondary IRI

Graviola's distinction between the local canonical IRI of an entity (primary) and any external Authority IRI it is linked to (secondary). The getPrimaryIRIBySecondaryIRI resolver in the Strategy Context mediates this.


5.4 Signed State

A cryptographically signed snapshot of an entity (or a subset of its fields, or a Lens document) attesting that a named party (historian, expert, institution) vouches for its correctness at a moment in time. Multiple signatures on the same data raise its Authoritative Value.

See also: Lens-as-Data.

References:


5.5 Authoritative Value

A computed score for a piece of data based on the number, identity, and reputation of its Signed States (and possibly the trust graph among signers). Used in open historical databases to surface plausible vs. contested entries. Concrete formula is application-defined.


5.6 Statement-level metadata

Fact-granularity metadata on individual asserted values: source, rank, generated-at, qualifiers. Canonical logical model: Wikidata statement-node pattern (truthy property + one-hop-longer statement structure), expressible in plain SPARQL 1.1. Capability-gated by store encoding (rdf-star, statement-node, named-graph, side-table, none).

Status: proposed. See Provenance and metadata.


5.7 Truthy property / statement sibling ($stmt)

Wikidata-model pair in derived schemas: direct resolved value plus a $stmt array of per-statement records. Dual-asserted on write; never derived at query time.

Status: proposed.


5.8 On-conflict reification

Provenance write policy: statement nodes created only when a second source asserts a conflicting value; the truthy triple becomes a resolved value (rank + source trust weight).

Status: proposed.


5.9 Administrative (record-level) metadata

Entity-granularity metadata ($meta): created, modified, schemaVersion, provenance. Framework-guaranteed on all backends; system-asserted only (never user-supplied on write). Distinct from descriptive schema fields.

Status: proposed. See Provenance and metadata.


6. Architecture & Deployment

6.1 Local-First

The architectural stance, articulated by Ink & Switch, that user data lives primarily on user devices and remains available, editable, and useful without a central server. Graviola is local-first by default; servers, when present, are transports or accelerators, not authorities.

References:


6.2 Browser/Server Symmetry

The Graviola constraint that core layers (lens engine, validator, IVM) run identically in browser and server environments. Drives the choice of pure JS / WASM implementations and forbids server-only dependencies in core packages.


6.3 Classical Migration

The traditional path of evolving a schema by writing imperative migration scripts run in staging and production, typically against a relational or document database via an ORM. Graviola supports this path explicitly: where an application has strong authorship over its data model and runs a centralized backend (e.g., Prisma on PostgreSQL or MongoDB), the Lens machinery is unnecessary and the application uses Prisma migrations directly. The lens engine is plugged into a concrete Store implementation only when Schema Drift across uncoordinated peers is actually a concern.

This dual path is deliberate. Lenses solve a real but specific problem (federated, uncoordinated, version-skewed peers); classical migration solves the common case (one team owns the database). Graviola treats both as first-class.


6.4 Spine vs. Tissue Packages

A monorepo discipline distinguishing spine packages (interfaces, contracts, types — versioned slowly, broadly depended on) from tissue packages (implementations — versioned freely, narrowly depended on). Reduces the sync burden of a 100-package monorepo.

Example: @graviola/mapping-contracts (spine) defines the DeclarativeMapping types; @graviola/mapping-strategies-cultural-heritage (tissue) implements specific strategies for that domain.


6.5 JSON Forms

The schema-driven form rendering library Graviola uses for UI generation. Embodies Structural Dispatch: a renderer registry resolves shape→component at runtime, decoupling UI from concrete entity types.

UI schema elements bind to schema nodes via a Scope (e.g., scope: "#/properties/name"). Scopes here address the schema, not the data — a property a Graviola application reuses when it adds annotations or rules at the same surface. Operations that read or write entity values (calculated fields, formula bindings, traversals across $ref) use Binding Paths instead.

See also: Scope, Binding Path.

References:


6.6 AbstractDatastore

Legacy interface contract for a concrete data backend, still present in many packages. The current successor is the Store interface in @graviola/store-core. New concept material uses Store and CapabilityDescriptor only.

See also: Store, Capability Context, Classical Migration.


6.19 Concise Bounded Description (CBD)

Formal name for Graviola's named-entity boundary: descend through anonymous/subordinate structure; halt at named IRIs. Unit of consistency, metadata, and write granularity. The extract-graph pipeline has implicitly used this rule; naming it makes entity-level metadata rigorous.

See also: CBD-cut invariant, Administrative metadata.

References:


6.20 MetaSchema

Registered companion JSON Schema describing entity-level $meta; base profile (created / modified / schemaVersion) plus application extensions with IRI mappings. Schema-as-data, versioned (gra:metaSchemaVersion). Third sidecar instance alongside UI schema and calc profile.

Status: proposed.


6.21 Store

Graviola's current storage interface in @graviola/store-core: capability facets composed by intersection, mirrored at runtime by the CapabilityDescriptor. Trajectory extensions (provenance encoding, shape fidelity, etc.) are new facets and descriptor blocks — not methods on a monolithic interface.

See also: CapabilityDescriptor, Store topology.


6.22 CapabilityDescriptor

Runtime mirror of a Store's composed capability facets. Declares what the store can do natively versus what the framework simulates. Supersedes the informal "capability matrix" vocabulary.


6.23 deriveExtendedSchema / deriveProvenanceSchema

Pure Layer-1 derivations that graft $meta (per CBD) or $stmt (per property) onto domain schemas for read/query/render paths. Write validation consumes the domain artifact only.

Status: proposed.


6.24 derivedFrom

Registry relation: this store is rebuildable from a named authority store. Drives reindexing, invalidation direction, and trust ordering in federation.

Status: proposed. See Store topology.


6.25 Composite store

Multiple physical engines behind one Store, declaring the union of capabilities; exists to hide store boundaries that would cut a CBD.

Status: proposed.


6.26 Shape fidelity

Capability flag: store returns schema-shaped documents; when true, the graph-traversal extract step is skipped.

Status: proposed.


6.27 CBD-cut invariant

The entity boundary (CBD) is the unit of consistency and metadata; the store boundary is the unit of availability, capability, and provenance. The store boundary must never visibly cut the entity boundary.

Status: proposed invariant. See Store topology.


Appendix: Reading Order for Newcomers

For a developer new to Graviola who wants to understand the conceptual stack, roughly in this order:

  1. Local-first software (Kleppmann et al., 2019) — the why.
  2. Project Cambria — the closest existing system.
  3. Foster et al., Combinators for Bidirectional Tree Transformations — the lens foundations.
  4. Lenzerini, Data Integration: A Theoretical Perspective — the federation framing.
  5. Existing Graviola Declarative Mapping code and example mappings (Wikidata person, GND).
  6. JSON Forms documentation — for the UI dispatch pattern that mirrors the data layer.

Further reading

This chapter collects curated pointers for deepening beyond this book. It does not duplicate framework API documentation or Storybook; those remain the right places for component-level detail.


From the glossary

The Glossary appendix: reading order for newcomers lists a sensible sequence of external papers and tools. Start there if you want a single ordered list.

For unresolved design questions that are not tied to a single glossary entry, see Outlook and open questions. For how generative assistants can attach to schema-driven workflows, see Graviola in the age of generative tools.


Framework and examples

  • Graviola framework monorepo — packages, layers, and apps/testapp as the minimal CRUD walkthrough.
  • Storybook (in the monorepo) — interactive documentation for JSON Forms renderers, tables, and some conceptual demos (e.g. mapping); intended to be cleaned up over time, but already useful for experiencing behavior.

Concepts touched in this book

TopicStarting point
Local-firstLocal-first software (Ink & Switch)
Schema evolution / lensesProject Cambria, Pierce et al. on lenses (linked from Glossary)
Data integrationLenzerini, Data Integration: A Theoretical Perspective (linked from Glossary)
UI structural dispatchJSON Forms
Generative tooling + schema-first UIGraviola in the age of generative tools, assisted-forms-designer

See also