From 4da63a258c45242e867b8e935afc547eb90d1ba6 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 19 Aug 2026 18:51:01 +0000 Subject: [PATCH 1/3] docs(adr): record document model split (ADR 0002) Proposed split of document envelope, relational filing, and graph argument. No engine or API changes. Co-authored-by: Travis Gilbert --- docs/adr/0002-document-model.md | 257 ++++++++++++++++++++++++++++++++ docs/adr/README.md | 1 + 2 files changed, 258 insertions(+) create mode 100644 docs/adr/0002-document-model.md diff --git a/docs/adr/0002-document-model.md b/docs/adr/0002-document-model.md new file mode 100644 index 0000000..f7d4f08 --- /dev/null +++ b/docs/adr/0002-document-model.md @@ -0,0 +1,257 @@ +# ADR 0002 — Split document, filing, and argument across three models + +Status: Proposed +Date: 2026-08-19 +Decision drivers: Travis Gilbert + +The decision is accepted as product direction. Status is Proposed +because it is not implemented. This ADR is not a claim that the +current public `0.9.1` snapshot already has document tables, collection +tables, or a published-ref API. + +## Context + +RustyRed `0.9.1` is graph-first. The shipped store is a directed +property graph with epistemic edges, Git-like version packs at +`/graph/version/*`, BM25 full-text, and HNSW vector search. Canonical +types live in `crates/rustyred-core/src/graph_store.rs`. There is no +document envelope, no collection membership table, and no publish +semantics for prose. + +Product work still needs a place to put writing. The editor opens a +document. A public site lists essays. A field note becomes an essay. +Those are filing and argument problems, not graph-label problems. + +The cheap encoding is to store Essay / FieldNote / Project as +first-class types or as node labels that replace Document. That freezes +genre into species. Changing a field note into an essay becomes a type +migration instead of a membership change. Folders become edge walks. +Presentation chrome (callout styling, hero colors, annotation offsets) +lands in the same row as the body. + +The live editor already has a CRDT buffer +(`/v1/tenants/{t}/sync/yjs/:doc_id`) that persists as `YjsDoc` graph +nodes. Graph version packs already compile snapshots, move refs +(default branch `main`), and checkout commits. Search already indexes +designated node properties. markdown-theory already treats +article / note / log as templates, not types. None of that is a +document model. The missing piece is the split: what holds the +document, what holds filing, what holds argument. + +The first consumer is the extracted thin public site +(`public-site/` on travisgilbert.me). It will keep markdown on disk +for now and later read published documents through this model. + +## Decision + +RustyRed is multi-model. Split responsibilities. Do not collapse +writing, filing, and argument into one graph label set. + +### 1. Document model holds the document + +A Document is the thing the editor opens. The envelope is: + +- `id` +- `body` (markdown, v1) +- optional `title` +- a working ref and a published ref + +Publish is a ref move, like updating `main` through +`/graph/version/ref`. It is not a boolean on the row. + +This is also why the same model works as an IDE backend: the editor +buffer is the working tree; publish is the commit you ship. + +Existing version packs (`/graph/version/compile`, `/diff`, `/ref`, +`/log`, `/checkout`, `/merge`) are the revision substrate. Do not +invent a second versioning system for documents. + +The live Yjs room remains the collaborative working buffer. This ADR +does not replace that transport. It defines the durable envelope the +editor opens and the published ref a reader may serve. + +Markdown body is v1. Do not wait on a block model. + +### 2. Relational model holds filing + +Collections, genres, membership (document in collection), and slugs +belong here. Slugs are unique per collection. + +"Essay", "field note", and "project" are not types and not labels that +change the species of a document. They are metadata that connect +documents: collection membership, or a row that says this document is +filed as an essay. A field note can become an essay without changing +type. + +The public site query is a join: published documents in the Essays +collection. + +Folders are tables. Do not encode the folder tree as graph edges. + +This is the product direction for filing. It is not a statement that +`0.9.1` already ships SQL tables. + +### 3. Graph model holds argument + +Related, cites, contradicts, and "this note became that essay" belong +on the graph. Use the existing epistemic types (`cites`, `contradicts`, +and the rest of `EpistemicType`) where they already fit. Add ordinary +relationship edges for related / became when those are not epistemic +claims. + +Do not use the graph as a folder tree. + +### Search and consumers + +BM25 (`/graph/fulltext/*`) and vector search (`/graph/vector/*`) +already exist. Document body should be searchable through them once +the envelope is stored — designate the body property; do not stand up +a parallel index. + +Stay aligned with markdown-theory: article / note / log are templates, +not storage types. + +`public-site/` keeps markdown on disk until it can read the published +ref of documents in a collection. + +### Out of the document envelope + +Callout chrome, hero colors, and annotation offsets are views. They +do not live on the document row. + +## Alternatives considered + +### Option A — Split document / relational / graph (chosen) + +- **Upside:** The editor, the public site, and argument traversal each + hit the model that matches the query. Genre changes do not rewrite + the document type. Publish reuses the version-pack ref machinery + already shipped at `/graph/version/*`. Search reuses BM25 and HNSW. + markdown-theory templates stay templates. +- **Risk:** Three models to keep coherent. A consumer that wants "all + essays" must join published refs to collection membership instead of + filtering a label. Mitigated by making that join the documented + public-site query. +- **Validation:** A field note can be filed as an essay without a type + change. Publish updates a ref, not a boolean. The public site lists + published documents in a named collection. Graph queries for cites / + contradicts / became do not double as folder walks. + +### Option B — Essay / FieldNote / Project as first-class types or replacing node labels + +- **Upside:** Fast to query "all essays" as `labels = ["Essay"]`. + Matches a graph-only reading of the `0.9.1` snapshot. +- **Rejected because:** Filing becomes species. A field note that + becomes an essay is a type migration, not a membership change. + Conflicts with markdown-theory's template-not-type stance. The editor + would open a subtype instead of a Document. + +### Option C — Collections and folders as graph edges only + +- **Upside:** No relational surface. Everything is already in + `GraphStore`. +- **Rejected because:** Folder trees and unique-per-collection slugs + are relational constraints. Encoding them as edges makes the public + site query a traversal and makes uniqueness a convention. Folders + are tables. The graph keeps argument. + +### Option D — Store presentation chrome on the document envelope + +- **Upside:** One row has everything a renderer needs: body, callout + styling, hero colors, annotation offsets. +- **Rejected because:** Those are views. They change without changing + the document. Putting them in the envelope couples publish to + presentation and bloats the thing the editor opens. + +### Option E — Wait for a block model + +- **Upside:** Structured body from day one. Callouts and annotations + could be first-class spans. +- **Rejected because:** v1 body is markdown. A block model can layer + later without changing Document from "the thing the editor opens." + Waiting blocks the public site and the editor for no filing benefit. + +### Option F — `published` boolean on the document row + +- **Upside:** Simple filter. No ref machinery. +- **Rejected because:** It throws away the version packs already + shipped. Working vs published is the same shape as working tree vs + `main`. A boolean cannot name which compiled pack is live, cannot + roll back by moving a ref, and does not match the IDE-backend + analogy this model is built on. + +### Option G — A second document-specific versioning system + +- **Upside:** Document history could look like a CMS (drafts table, + revision rows) without using graph packs. +- **Rejected because:** `/graph/version/*` already compiles + content-addressed packs, moves refs, logs, checkouts, and merges. + A second history is drift. Documents use that substrate. + +## Consequences + +### Positive + +- The editor has one species to open: Document. +- Filing can change without rewriting the document. +- Publish is a ref move over the existing version-pack substrate. +- Argument stays on the graph, next to `cites` / `contradicts`. +- The public site has a stable query shape: published documents in a + collection. +- Search work is designation of body onto indexes that already exist. + +### Negative + +- `0.9.1` docs (`docs/technical/data-model.md` and the HTTP graph + surface) remain the shipped snapshot. This ADR is ahead of the + code. Readers must not treat Proposed as released. +- Until a relational surface exists, any implementation that stuffs + collections into graph edges would contradict this decision even if + it "works" on today's store. +- Yjs persistence as `YjsDoc` nodes is a CRDT implementation detail, + not the document envelope. Bridging buffer → working ref → + published ref is follow-up work, not specified here as an API. + +### Operational + +- No engine, proto, or HTTP change in the PR that records this ADR. +- `public-site/` continues to serve on-disk markdown until it can read + published documents through this model. +- When implementation starts, document body is designated into the + existing BM25 and vector indexes rather than growing a third search + path. +- markdown-theory templates remain the presentation vocabulary for + article / note / log. They do not become RustyRed storage types. +- Technical reference for the live graph stays in + `docs/technical/data-model.md`. This ADR is the product split, not a + replacement graph schema. + +## Reversibility + +Fully reversible until implementation lands. To revert the decision: + +1. Mark this ADR Superseded and point at the replacement. +2. Keep storing collaborative buffers as `YjsDoc` graph nodes and + filing as labels or edges, which is what `0.9.1` already allows. + +After implementation, reversal is a migration: documents and +membership tables would fold back into graph records. The version-pack +refs can stay; they predate this ADR. + +## Related + +- `docs/technical/data-model.md` — shipped `0.9.1` graph snapshot + (nodes, edges, epistemic types, content addressing). Not the + document envelope. +- `docs/technical/http-api.md` — `/graph/version/*`, + `/graph/fulltext/*`, `/graph/vector/*`. +- `crates/rustyred-core/src/versioned_graph.rs` — + `DEFAULT_GRAPH_BRANCH` (`main`), compile / ref / checkout / merge. +- `crates/rustyred-core/src/graph_store.rs` — `NodeRecord`, + `EdgeRecord`, `EpistemicType` (`cites`, `contradicts`, …). +- `crates/rustyred-core/src/fulltext.rs` — BM25 designation keyed by + `(label, property)`. +- `crates/rustyred-server/src/yjs_sync.rs` — live CRDT buffer; persists + as `YjsDoc` graph nodes. Transport, not the document model. +- `crates/rustyred-server/src/router.rs` — version, fulltext, vector, + and Yjs routes as they exist today. diff --git a/docs/adr/README.md b/docs/adr/README.md index 032d990..ca0b516 100644 --- a/docs/adr/README.md +++ b/docs/adr/README.md @@ -10,3 +10,4 @@ are marked as such and link to the replacement. | # | Title | Status | |---|---|---| | [0001](0001-vendored-proto-for-railway-build.md) | Vendor `rustyred.proto` for hermetic Docker / Railway builds | Accepted | +| [0002](0002-document-model.md) | Split document, filing, and argument across three models | Proposed | From df18d39dfbe84cb90f846d2a2eb4e7af23332de7 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 19 Aug 2026 19:01:21 +0000 Subject: [PATCH 2/3] docs(adr): scope 0002 to the Theorem extract, not 0.9.1 The three-way split still stands. Do not implement it against this public snapshot; public-site stays on-disk markdown until the extracted backend exists. Co-authored-by: Travis Gilbert --- docs/adr/0002-document-model.md | 75 +++++++++++++++++++++------------ docs/adr/README.md | 2 +- 2 files changed, 49 insertions(+), 28 deletions(-) diff --git a/docs/adr/0002-document-model.md b/docs/adr/0002-document-model.md index f7d4f08..4448723 100644 --- a/docs/adr/0002-document-model.md +++ b/docs/adr/0002-document-model.md @@ -3,11 +3,26 @@ Status: Proposed Date: 2026-08-19 Decision drivers: Travis Gilbert +Implementation target: extracted RustyRed / Theorem line, not this +public `0.9.1` graph-first snapshot -The decision is accepted as product direction. Status is Proposed -because it is not implemented. This ADR is not a claim that the -current public `0.9.1` snapshot already has document tables, collection -tables, or a published-ref API. +The three-way split below is accepted as product direction. Status is +Proposed because it is not implemented. + +This standalone repo (`RustyRed-Graph-Database`, current `0.9.1`) is +drastically different from Theorem. It does not take agents yet. +Extracting the real RustyRed and updating this repo is follow-up work. +The snapshot here is not a useful backend or control plane for the +public site. + +Do not implement document tables, collection membership, or a +published-ref API against `0.9.1` in this repo as it stands. This ADR +still records the split (document / relational filing / graph +argument). That decision travels with the extract. It is not a claim +that this snapshot is the place to build it. + +`public-site/` stays on-disk markdown until the extracted backend +exists. ## Context @@ -39,8 +54,9 @@ document model. The missing piece is the split: what holds the document, what holds filing, what holds argument. The first consumer is the extracted thin public site -(`public-site/` on travisgilbert.me). It will keep markdown on disk -for now and later read published documents through this model. +(`public-site/` on travisgilbert.me). It stays on-disk markdown until +the extracted backend exists. The snapshot in this repo is not that +backend. ## Decision @@ -88,8 +104,9 @@ collection. Folders are tables. Do not encode the folder tree as graph edges. -This is the product direction for filing. It is not a statement that -`0.9.1` already ships SQL tables. +This is the product direction for filing on the extracted line. It is +not a statement that `0.9.1` already ships SQL tables, and it is not +permission to add those tables to this snapshot. ### 3. Graph model holds argument @@ -111,8 +128,8 @@ a parallel index. Stay aligned with markdown-theory: article / note / log are templates, not storage types. -`public-site/` keeps markdown on disk until it can read the published -ref of documents in a collection. +`public-site/` stays on-disk markdown until the extracted backend +exists. ### Out of the document envelope @@ -202,12 +219,13 @@ do not live on the document row. ### Negative -- `0.9.1` docs (`docs/technical/data-model.md` and the HTTP graph - surface) remain the shipped snapshot. This ADR is ahead of the - code. Readers must not treat Proposed as released. -- Until a relational surface exists, any implementation that stuffs - collections into graph edges would contradict this decision even if - it "works" on today's store. +- This repo's `0.9.1` docs (`docs/technical/data-model.md` and the HTTP + graph surface) remain the shipped snapshot. This ADR is not a build + ticket against that tree. Readers must not treat Proposed as + released here. +- Implementing collection membership as graph edges on this snapshot + would still contradict the split, even if it "works" on today's + store. The fix is the extract, not a workaround in `0.9.1`. - Yjs persistence as `YjsDoc` nodes is a CRDT implementation detail, not the document envelope. Bridging buffer → working ref → published ref is follow-up work, not specified here as an API. @@ -215,11 +233,13 @@ do not live on the document row. ### Operational - No engine, proto, or HTTP change in the PR that records this ADR. -- `public-site/` continues to serve on-disk markdown until it can read - published documents through this model. -- When implementation starts, document body is designated into the - existing BM25 and vector indexes rather than growing a third search - path. +- Do not implement document tables, collection membership, or a + published-ref API against `0.9.1` in this repo as it stands. +- `public-site/` stays on-disk markdown until the extracted backend + exists. +- When implementation starts on the extracted / Theorem line, document + body is designated into the existing BM25 and vector indexes rather + than growing a third search path. - markdown-theory templates remain the presentation vocabulary for article / note / log. They do not become RustyRed storage types. - Technical reference for the live graph stays in @@ -228,15 +248,16 @@ do not live on the document row. ## Reversibility -Fully reversible until implementation lands. To revert the decision: +Fully reversible until implementation lands on the extracted line. To +revert the decision: 1. Mark this ADR Superseded and point at the replacement. -2. Keep storing collaborative buffers as `YjsDoc` graph nodes and - filing as labels or edges, which is what `0.9.1` already allows. +2. Leave this `0.9.1` snapshot as it is: collaborative buffers as + `YjsDoc` graph nodes, no document tables here. -After implementation, reversal is a migration: documents and -membership tables would fold back into graph records. The version-pack -refs can stay; they predate this ADR. +After implementation on the extract, reversal is a migration: +documents and membership tables would fold back into graph records. +The version-pack refs can stay; they predate this ADR. ## Related diff --git a/docs/adr/README.md b/docs/adr/README.md index ca0b516..eeeeada 100644 --- a/docs/adr/README.md +++ b/docs/adr/README.md @@ -10,4 +10,4 @@ are marked as such and link to the replacement. | # | Title | Status | |---|---|---| | [0001](0001-vendored-proto-for-railway-build.md) | Vendor `rustyred.proto` for hermetic Docker / Railway builds | Accepted | -| [0002](0002-document-model.md) | Split document, filing, and argument across three models | Proposed | +| [0002](0002-document-model.md) | Split document, filing, and argument across three models | Proposed (Theorem extract, not 0.9.1) | From 61dd91f8f096063d8d68715f5ae87ac4f5e44fb8 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 19 Aug 2026 19:05:35 +0000 Subject: [PATCH 3/3] docs: add public projection plan as ADR 0002 companion travisgilbert.me is a Theorem compile target, not a CMS. Do not implement against 0.9.1; public-site stays on-disk markdown until the extract exists. Co-authored-by: Travis Gilbert --- docs/adr/0002-document-model.md | 5 +- docs/plans/public-projection.md | 187 ++++++++++++++++++++++++++++++++ 2 files changed, 191 insertions(+), 1 deletion(-) create mode 100644 docs/plans/public-projection.md diff --git a/docs/adr/0002-document-model.md b/docs/adr/0002-document-model.md index 4448723..8d5d5ef 100644 --- a/docs/adr/0002-document-model.md +++ b/docs/adr/0002-document-model.md @@ -22,7 +22,8 @@ argument). That decision travels with the extract. It is not a claim that this snapshot is the place to build it. `public-site/` stays on-disk markdown until the extracted backend -exists. +exists. The compile/projection story is +`docs/plans/public-projection.md`. ## Context @@ -261,6 +262,8 @@ The version-pack refs can stay; they predate this ADR. ## Related +- `docs/plans/public-projection.md` — travisgilbert.me as a Theorem + compile target. Companion plan; not a second model split. - `docs/technical/data-model.md` — shipped `0.9.1` graph snapshot (nodes, edges, epistemic types, content addressing). Not the document envelope. diff --git a/docs/plans/public-projection.md b/docs/plans/public-projection.md new file mode 100644 index 0000000..ce72042 --- /dev/null +++ b/docs/plans/public-projection.md @@ -0,0 +1,187 @@ +# Public Projection + +Status: Proposed +Date: 2026-08-19 +Decision drivers: Travis Gilbert +Implementation target: extracted RustyRed / Theorem line, not this +public `0.9.1` graph-first snapshot + +Companion plan to [ADR 0002](../adr/0002-document-model.md). 0002 is +the model split (document / relational filing / graph argument). This +file is the compile story for travisgilbert.me. It is not a second +ADR and it does not change the split. + +Do not implement this against `0.9.1` in this repo. `public-site/` +(travisgilbert.me thin extract) stays on-disk markdown until the +extracted backend exists. + +## The thing + +travisgilbert.me is a **compile target of Theorem**, not a CMS and not +a giant backend application. + +Protect the simplicity of `public-site/`: static, reads simple Markdown +collections, strips CommonPlace / Studio / graph explorer / Monaco / +etc. + +The relationship is: + +``` +work → Theorem/RustyRed → public projection → travisgilbert.me +``` + +Not: + +``` +travisgilbert.me → giant backend application +``` + +Theorem can know everything. The site can know almost nothing. + +When you publish, a Theorem capability builds a versioned +`public-site-manifest` of only approved objects and relations. That +materializes into the public-site build as JSON/Markdown. Next.js +statically renders it the way it currently renders `content/`. For +anything interactive later (Ask My Work, public MCP) there is one +narrow public read-only Theorem service. Everything else works if +Theorem is down. The job portfolio must never be unavailable because +the backend is having opinions. + +## Stay aligned with ADR 0002 + +- One Document: `id`, `body` (markdown v1), optional `title`, working + and published refs. Publish is a ref move, not a boolean. +- Essay / field note / project / writing are not storage types. Filing + is relational: collections, membership, slugs unique per collection. +- Graph holds argument: related, cites, contradicts, became. +- Callout chrome, hero colors, and annotation offsets stay out of the + envelope. Those are views. + +## Facets and non-document objects + +**publishable** is a facet, not a type: slug, visibility, summary, +published date, featured. Combined with the published ref. + +Project, Writing, Artifact, Skill, Experience, Demo, ResumeClaim, +Source, and Publication are not first-class storage types that replace +Document. + +Some of those are documents filed a certain way. Some are relational +or graph objects that cite documents (ResumeClaim, Skill, Experience, +Artifact, Source). + +Related work on the site: three useful connections with a reason +("shared question", causal, etc.). Do not bring back the force-directed +graph UI. + +## Do not migrate markdown now + +Keep the new Markdown site as canonical initially. Ingest those +documents into Theorem. Build projection features around them. Reverse +the flow (Theorem materializes Markdown/static projection) only once +authoring in Theorem is actually preferred. + +The job hunt is active. Do not let "improve personal site" become +"finish Theorem's document platform." + +## Ship ladder + +Restrained. Each rung assumes the previous is already serving the +portfolio. + +1. **First ship:** thin static site + Theorem public graph + Related + Work + structured project/artifact evidence. +2. **Then** job-specific portfolio views (for example + `/for/product-systems`, or a private application link). No + compatibility-percentage meter. +3. **Then** Ask My Work: search over the public projection only, + answers with real projects / essays / artifacts — not a homepage + chatbot. +4. **Then** a tiny read-only public MCP/API (`search_work`, + `get_project`, `get_writing`, `get_resume_fact`, `get_artifact`). + Footer can say the portfolio is machine-readable. + +Standing Brief / derived `/now` is later: draft from activity, human +approves before publish. + +## Alternatives considered + +### Option A — Compile target (chosen) + +- **Upside:** `public-site/` stays a static Markdown/JSON build. The + portfolio survives Theorem being down. Publish is still ADR 0002's + ref move; the manifest is a projection of approved published refs + and relations, not a second CMS. +- **Rejected the inverse because:** a giant backend behind + travisgilbert.me makes the job site depend on Theorem's opinions at + request time. That is the failure mode this plan exists to prevent. + +### Option B — CMS in the site + +- **Upside:** Edit essays in Next.js. No extract required to ship + copy. +- **Rejected because:** the site becomes the system of record. Filing, + argument, and publish refs leak into `public-site/`. Theorem then + has to scrape its own compile target. Conflicts with "the site can + know almost nothing." + +### Option C — Project / Writing as storage types that replace Document + +- **Upside:** The site query looks like `type = Project`. +- **Rejected because:** ADR 0002. Those names are filing or citing + objects, not species. A publishable facet plus collection membership + plus a published ref is enough for the manifest. Typed + Project/Writing storage would freeze genre into the envelope. + +## Consequences + +### Positive + +- `public-site/` can stay thin: Markdown collections in, static pages + out. +- Publish remains a ref move on a Document. The manifest is a + downstream compile of approved published objects and relations. +- Interactive features (Ask My Work, public MCP) have one narrow + read-only service. The rest of the site does not wait on it. +- The job hunt is not gated on finishing Theorem's document platform. + +### Negative + +- Until the extract exists, the site cannot actually compile from + Theorem. On-disk markdown stays canonical; ingest is one-way. +- A versioned manifest is another artifact to keep coherent with + published refs. It is a projection, not a second versioning system + (ADR 0002: reuse `/graph/version/*` on the extract). +- Related Work is three reasoned links, not a graph explorer. Anyone + expecting the old force-directed UI will not get it. + +### Operational + +- Do not implement document tables, collection membership, a + published-ref API, or this projection against `0.9.1` in this repo. +- Do not migrate markdown off disk until authoring in Theorem is + preferred. +- No "Powered by Theorem" chatbot on the homepage. +- No compatibility-percentage meter on job-specific views. +- Standing Brief / `/now` waits; a human approves before publish. + +## Out of scope + +- Engine, proto, or HTTP work in this public `0.9.1` snapshot. +- Turning `public-site/` into CommonPlace, Studio, a graph explorer, + or a Monaco editor. +- Force-directed graph UI. +- Homepage chatbot. +- Reversing Markdown canonicalization before Theorem authoring is + actually preferred. +- Inventing first-class storage types for Project / Writing / + Artifact / Skill / Experience / Demo / ResumeClaim / Source / + Publication. + +## Related + +- [ADR 0002](../adr/0002-document-model.md) — document / filing / + argument split. This plan consumes that split; it does not replace + it. +- `public-site/` on travisgilbert.me — thin static extract. Canonical + markdown until the extracted backend exists.