Skip to content

Commit 88f0e4b

Browse files
mpstatonclaude
andcommitted
spec(context-v): Source Content Storage — SurrealDB-primary, local as a per-user toggle
Captures the operator's 2026-08-02 decision to store the fetched body in SurrealDB (content field on canonical `sources`; Extracts per `source_usages`) and demote the local filesystem write to an explicit per-workspace + per-user toggle — an export for each collaborator's own local RAG, not the store. Aligns to prior decisions rather than re-litigating them: R2/rclone stays the backup tier, Syncthing stays the (now-optional) FS-mirror leg. Key reframe: content-in-DB unifies content access with canonical-layer access (one didi.sh auth path), erasing the filesystem-vs-entity-layer split the Syncthing exploration flagged. Forks A/B/C recorded; migration + collaboration transport left open. Cross-links the Path-Off-Local / RAG / Syncthing explorations. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018UYTYu4MAFZ7iyr2VTo2kq
1 parent f38e7d7 commit 88f0e4b

1 file changed

Lines changed: 128 additions & 0 deletions

File tree

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
---
2+
title: "Source Content Storage — SurrealDB-Primary, Local as a Per-User Toggle"
3+
lede: "Flip the content store: the fetched article body becomes a field on the canonical `sources` row and analyst Extracts become per-`source_usages` — so content access unifies with canonical-layer access, both through the didi.sh-authed DB. The local filesystem write demotes to an explicit per-workspace + per-user toggle: an export for each collaborator's own local RAG, not the primary store. R2 (backup) and Syncthing (live FS mirror) stay as already-scoped substrate."
4+
date_created: 2026-08-02
5+
date_modified: 2026-08-02
6+
authors:
7+
- Michael Staton
8+
augmented_with:
9+
- Claude Code on Claude Opus 4.8
10+
semantic_version: 0.0.0.2
11+
status: Draft — Forks A/B/C Decided; Migration + Collaboration Transport Open
12+
tags:
13+
- Spec
14+
- Augment-It
15+
- Content-Ingest
16+
- SurrealDB
17+
- Storage
18+
- Corpus
19+
- RAG
20+
- Cloudflare-R2
21+
- Syncthing
22+
- Path-Off-Local
23+
---
24+
25+
# Source Content Storage — SurrealDB-Primary, Local as a Per-User Toggle
26+
27+
## Why Care?
28+
29+
Today the storage model is the inverse of what it should be. The markdown body
30+
lives **only on the filesystem** (`CLIENTS_ROOT`); SurrealDB is "the rebuildable
31+
**index**" (domains.ts:16) and holds no content. There's **no local toggle**
32+
(the file write is unconditional) and the write order is **local-first, then
33+
DB, non-atomic**.
34+
35+
That shape is a fossil of an ambition never shipped: corpus files on a **remote
36+
filesystem**. We explored it ([[JuiceFS-Pinned-Path-Off-Local-Substrate]]),
37+
rejected a live mount, and landed on **R2 via rclone for backup** — but the
38+
content itself never left the filesystem, so local-first bought nothing but
39+
drift. The body is just a markdown **string**; SurrealDB stores that trivially.
40+
41+
**Decision (operator, 2026-08-02): store the content in SurrealDB; make the
42+
local write an explicit per-workspace + per-user toggle.**
43+
44+
## The North Star this serves
45+
46+
Enable **RAG/KAG for a whole team, by topic**. A topic = a domain/corpus. Every
47+
collaborator should be able to retrieve over the corpus — some through the
48+
hosted app, some by syncing a topic's content down to *their own* local folder
49+
for local retrieval. For that, the canonical content has to live somewhere every
50+
authed collaborator can reach: the **DB**, not one operator's laptop.
51+
52+
## The key structural win
53+
54+
The [[Syncthing-For-Collaborator-Access-To-The-Corpus]] exploration drew a hard
55+
line: *Syncthing only solves the filesystem-corpus leg; the SurrealDB canonical
56+
layer (persons/orgs/observations) is a separate access problem needing didi.sh
57+
auth.* **Putting content in the DB erases that line** — content access becomes
58+
canonical-layer access, one auth path (didi.sh) for both. Collaborators no
59+
longer *need* a filesystem mirror to read content; the mirror becomes an
60+
optional local-RAG convenience.
61+
62+
## The decision, stated
63+
64+
1. **SurrealDB is the primary content store.** The fetched body is a `content`
65+
field on the canonical **`sources`** row (client-agnostic — same URL, same
66+
body). Analyst **Extracts** are stored per **`source_usages`** (each client's
67+
own curation of that source). Reads serve both from the DB.
68+
2. **Local filesystem write is an explicit toggle**, off by default, scoped
69+
**per-workspace AND per-user** — each collaborator names their own local FS
70+
location for a topic-scoped sync of the content, for their local RAG. (This
71+
is the export/sync leg the Syncthing exploration was circling; content-in-DB
72+
makes it optional, not load-bearing.)
73+
3. **Write order is DB-first.** DB success = "saved." When a local destination
74+
is enabled, that write must **also** succeed for the operation to report done
75+
— a failure surfaces explicitly ("saved to db, local export failed"), never a
76+
silent half-write.
77+
4. **`corpus_path` becomes optional** — populated only where a local mirror
78+
exists. R2 backup (rclone) and any Syncthing FS mirror operate on that
79+
optional local tier, unchanged.
80+
81+
## Resolved forks (operator, 2026-08-02)
82+
83+
| Fork | Decision |
84+
|---|---|
85+
| **A — body location** | `content` field on **`sources`** (client-agnostic). *Implementation note:* project it away on list/metadata reads so the blob loads only when content is actually requested. |
86+
| **B — Extracts location** | Per **`source_usages`** (per client). Two clients annotate the same canonical source independently. |
87+
| **C — local toggle** | **Per-workspace + per-user**, default **off**. Each user picks their own local sync location. |
88+
89+
## Still open (need a call before/at build)
90+
91+
- **D — Migration.** Bodies + Extracts currently live only in files, across
92+
**three** diverging copies: Michael's laptop (`clients/*/corpus`), the Railway
93+
volume (`/data/clients`), and git (committed corpus). Backfill has to read
94+
existing corpus `.md`, split body vs Extracts, write into the new columns —
95+
*and* reconcile which of the three copies is source-of-truth per record.
96+
- **Collaboration transport.** With content in the DB, is the per-user local
97+
sync a **DB→FS export** (new, simple, one-directional) or does it reuse the
98+
**Syncthing** `Receive-Only` mesh from the exploration? Leaning DB→FS export
99+
now that content is canonical in the DB; Syncthing/R2 remain for FS-tier
100+
mirror/backup, not the primary access path.
101+
- **Blob size in Surreal.** Bodies are ~tens of KB (the Springer one was ~83KB);
102+
fine as a field, but confirm query/storage patterns don't regress on large rows
103+
(drives the "project away on list reads" note in Fork A).
104+
105+
## Write contract (target)
106+
107+
```
108+
fetch/save(source):
109+
1. resolve content + metadata (Jina, two-profile parser)
110+
2. WRITE DB: sources.content (body) + source_usages (Extracts, status='fetched', bib)
111+
└─ fail → abort, report "save failed (db)"; nothing half-committed
112+
3. FOR each enabled local destination (per-workspace and/or per-user):
113+
WRITE file (dest/…/<slug>.md), preserving Extracts
114+
└─ fail → report "saved to db, local export failed at <dest>" (explicit)
115+
4. return { ok, source_uuid, stored: ['db', ...enabledLocalDests] }
116+
```
117+
118+
Reads serve body + Extracts from the DB; local files are mirrors, and R2 backs
119+
up the local tier via rclone as already decided.
120+
121+
## Cross-references
122+
123+
- [[Syncthing-For-Collaborator-Access-To-The-Corpus]] — the FS-mirror leg; this spec makes it optional.
124+
- [[Per-Client-Privacy-and-the-Path-Off-Local]] — isolation posture that shapes per-user/per-client scoping.
125+
- [[Best-Way-to-RAG-Over-the-Corpus]] — the retrieval design this storage model feeds.
126+
- [[JuiceFS-Pinned-Path-Off-Local-Substrate]] — the R2/rclone backup decision, not re-litigated here.
127+
- [[Fetch-Full-Content-Clobbers-Operator-Metadata]] · [[Jina-Metadata-Parser-Is-Blog-Only-Needs-Two-Profiles-And-Routing]] — the fetch/parse steps feeding step 1.
128+
- [[Strategy-Curator-Entry-Point-for-Augment-It]] — the surface.

0 commit comments

Comments
 (0)