Sense 61, Part 2 realized: The Reservoir — the spec
Sense 61 · In bloom · Last touched 2026-08-20
- last_verified: 2026-07-27
Synced from
docs/design/sense_61_the_reservoir.mdin the engine repo — that’s the source; this page is a build-time mirror.
Status: skeleton draft for co-authoring — sister-5 (persistence, runs) + sister-3 (identity, roster; her sections marked ⟨s3⟩) · sister-1 reviews as run-record implementer · Mig arbitrates scope and names Date: 2026-07-18 Parent: Sense 61 The Strata — the position paper this spec makes buildable. Strata names the three strata (Authored / Built / Operational); this spec designs the Operational stratum’s home. Companions: Run registry in the SIS (ADOPTED — the interim host this spec supersedes for run rows) · Sense 56 The Run · Sense 62 The Fleet (internal) · Sense 25 The Pass
The Reservoir is the tenant’s state-of-record for everything that is neither Authored nor Built: the operational stratum — run records, roster assignments, ledgers, attestations. One store per tenant, locally resident, optionally synced behind the scenes, never in any hot path. SIS and the sys DB are today’s partial views of it; both converge here and retire as separate concepts.
Substrate honesty (Mig’s correction, 2026-07-18): the Authored stratum’s substrate is the filesystem — the AFS is files first. Git is one optional history/transport overlay; R2 tarballs are another (the cloud runner already treats the bucket as the substrate, no git assumption); others may come. Strata Part 1 said it — “love git, don’t depend on git” — and this spec inherits it: nothing below may assume a git repo exists.
What the Reservoir is not: a second authorization authority, a
cross-tenant channel, a cache of anything rebuildable, or a dependency
of make.
Name (Q5, resolved 2026-07-22, Mig). The store is the OPS — the Operational Store — the concrete per-tenant file, peer of AFS and KLS. The Reservoir stays this Sense’s name for the concept, exactly as KLS is the file and “the Knowledge Store” is the idea. This spec uses OPS for the store/file and “the Reservoir” for the Sense.
Principles (argued and settled before any layout)
Section titled “Principles (argued and settled before any layout)”These are constraints, not options. Each earned its place by a scar.
-
Local-first; every sync layer optional. The Reservoir must work in all five operating topologies — a pure-Local tenant runs a Reservoir with no R2 behind it at all; P2P2P serves it through the tunnel; Cloud/MaaS replicate it via R2. Sync (R2 or whatever succeeds it) is an overlay on a local store, exactly as git is an overlay on the AFS — never a requirement.
-
Definitions vs assignments. ⟨s3⟩ The identity tree (identities, roles, groups, passes) is Authored — pack/system YAML, git-reviewed, rebuildable — but tenant-scoped, so it bakes to the OPS, not the KLS (the scope axis — see Strata; refined 2026-07-23 with sister-5). Only assignments and roster state (who is bound, membership, invites, acceptance) are Operational. The tree lives in the OPS but is not of the operational rows: it is overwrite-on-bake, and a bake is the only thing that changes it — the Reservoir holds it without swallowing it. Overwrite-on-bake stays coherent only if, at every scope, exactly one authored source produces the bake; so when identity promotes above the tenant (Part 6), promotion re-homes the authoring source (a distinct org-scoped AFS) — it never merges N tenant trees, which would reintroduce “which tenant owns identity?” one scope up. (Scar: the clerk-pin retirement.)
-
No shadow authority. ⟨s3⟩ Clerk stays the sole online authorizer. Reservoir roster rows are state-of-record for display and operations, never a second authorization path. Offline Level-3 fallback reads the local Reservoir where one exists; cloud-offline has no fallback, by design. (Scar: the pin-resurrection bug.)
-
No R2 in the request path. ⟨s3⟩ Capability resolution stays local and request-cached; the Reservoir syncs behind it.
-
No R2 in the run path. (s1’s twin of #3.)
run.py’s writes stay local and best-effort; a make must never slow down or fail because a bucket hiccuped. The Reservoir syncs behind, never in front. -
Write discipline: best-effort write-through + healing sweep. Producers write the Reservoir opportunistically at the moment of truth (lifecycle transition, roster change); a reconcile sweep heals gaps from the filesystem/source-of-truth artifacts. Proven by
run_registry+jin runs reconcile— the pattern generalizes. -
Rows index, objects carry. Bulky artifacts (run event streams) archive as R2 objects; the Reservoir row is the durable index that says where. The registry is never the payload.
-
Facets classify, never connect implicitly. (S62 discipline.) A shared facet value — two tenants grouped under one org-taxonomy node in an operator’s map — never auto-opens a channel between them; isolation is the floor. Connection is possible, but only ever a deliberate, audited act through the window (principle 12) — never a side effect of classification. So the Reservoir is not barred from being a connection point; a facet is barred from being one. Fleet- level records may live as records; org-level Reservoirs stay two flight levels below (Strata Part 6) — operational identity stays addressable, never hard-welded, but nothing here builds the org store.
-
makeneither reads nor writes the Reservoir as a build input. The Built stratum remains reproducible from (AFS + source) alone. Bakes that today copy operational state into the KLS meta schema (recents, bell resolutions) become publishes — explicit, Phase-1d-style — or retire. -
A KLS alone must suffice — and the default posture is generous (Mig, 2026-07-18). JinDesk stays fully operational with only a KLS — a packaged .dmg, a published showcase copy, a cloud KLS with no Reservoir replica. This and principle 8 reconcile through the publish seam (Phase-1d publish into the AFS, baked from there — today’s recents and bell-resolutions path, generalized). The default is to bake much, not little: notes, selections, and — the closure rule — everything a note references bakes into the KLS on every run where possible, so no citation arrives broken. Sometimes it isn’t possible; the aspiration stands. Yes, this is redundant — deliberately, with precedent: the KLS already carries the entire zipped AFS (
_jinflow_afs_archive). Self-containment outranks normalization; the Reservoir remains the state-of-record; make’s inputs stay (AFS + source). -
Ownership is first-class; visibility is a distraction filter — “no secrets in the family” (Mig, 2026-07-18, thrice refined). This is team software, not a security system. Every personal-scope citizen carries an
owner(a Sense 25 identity): attribution and edit-control — who wrote it, who may change it. Visibility is a separate axis whose purpose is attention, not concealment: filters that lower every participant’s distraction level. Nothing in the Reservoir is a secret from the team.
- Utility-shaped (bookmarks, selections, saved views, journal): default to their owner’s view — not because they’re secret, but because they’re noise to everyone else. Reachable when wanted.
- Contribution-shaped (notes foremost — notes are special): exist to inform the tenant; their natural state is visible, and they bake into the KLS with their reference closure (9).
- Participation view: the tenant admin sees who contributes and how much — trivially consistent with no-secrets; it’s an oversight lens, not surveillance.
What remains for the working session (open question 6) is the attention model, not a privacy model: per-citizen visibility defaults, filter UX, and whether visibility wants more granularity than owner-view/tenant-view.
-
Tenant-scoped; the Reservoir outlives every KLS. One Reservoir per tenant. A tenant has many KLSes — the working copy, each snapshot, every rebuild — but one Reservoir, stable across all of them. The KLS is disposable and plural; the Reservoir is singular and durable. Operational state is never pinned to a KLS’s lifecycle — that pinning is the exact bug the strata split exists to end.
-
The Reservoir is the tenant’s window to the world. It is the single, governed membrane for every outward interaction — sync (R2), fleet presence, inviting people (the roster is how someone enters the tenant), and any deliberate cross-tenant connection (the S62 “explicit instance-to-instance API — the API is the membrane”). The window is closed by default (principle 7); only an explicit, audited act opens it. No Reservoir, no window: a bare KLS is a sealed island — no sync, no presence, no invites, no connection. That sealedness is precisely why a KLS alone (principle 9) is the safe distribution unit — the packaged
.dmg, the published showcase copy. P9 stops being a mere convenience and becomes a security property: the KLS suffices and it is sealed, because it has no window. -
Recovery is restore-from-backup, not rebuild. The defining property of the Operational stratum, and the reason it needed naming: it is not derivable, so it cannot be regenerated. The KLS you rebuild (from AFS + source); the Reservoir you restore. Every deployment mode owes the Reservoir a backup story; losing it is data loss, not a cache miss.
-
The Reservoir is authoritative; any KLS copy is a derived cache. Where operational state also appears in the KLS (the P9 self- containment cache — baked notes, a roster snapshot), the Reservoir is the source of truth and the KLS copy is a possibly-stale mirror. Readers key on the Reservoir; the bake serves KLS-only mode, never the authority. This inverts today’s world, where the KLS bake is the display source — the SHIPPED-but-not-LIVE lag class.
-
Citizens are versioned for migration — logical schema, not dialect. Each citizen carries a
schema_version; the freeze is of the logical schema (columns + semantics), so the SIS-DuckDB → SQLite move is a table move, not a redesign. Migration is a citizen-by-citizen, engine-portable operation. (sister-1, review R1.) -
Staleness is honest. Every citizen carries
recorded_at. A local reader is current; a cloud replica may be minutes behind and says so rather than pretending to be live. Don’t let the freshness contract re-engineer what the live event-feed already provides — the history pages can be honestly stale. (sister-1, review R3.) -
The KLS is overwritten; the OPS is merged — sync must know the difference. A KLS is a rebuildable cache: last-build-wins, safe to overwrite anywhere. The OPS is non-rebuildable state-of-record: a blind overwrite-push from one location destroys operational writes made in another. So the OPS is excluded from the KLS-overwrite sync path and reconciled by a merge-sync instead. Getting this wrong is silent operational-data loss — the one failure this whole stratum exists to prevent. (See Distributed OPS & merge.)
-
Structure is the engine’s; rows are the consumer’s. The OPS schema is defined in exactly one place —
scripts/opsdb.py(OPS_TABLES) — and applied only by the engine:jin initat a tenant’s birth,jin ops migrateon upgrade. Consumers (the JinDesk, any future client) read and write rows; they neverCREATEa table. This is the boundary that keeps the schema single-sourced: the moment a second process defines the same table, the two definitions drift silently underCREATE TABLE IF NOT EXISTS(which is a no-op on an already-present table, so a diverged column is never reported). A consumer that meets a missing table degrades exactly like the sealed island (P12) — writes no-op, reads return empty — and the fix isjin ops migrate, never a create in the consumer. (Scar: the second citizen —saved_selectors— briefly self-healed its table in JinDesk, duplicating the DDL across Python and TypeScript; backed out the moment a second copy existed. See Schema ownership /docs/design/ops_schema_ownership.md.)
The scope table — the citizens
Section titled “The scope table — the citizens”Start here; the R2 layout falls out of the citizen shapes. Legend for
shape: rows (relational, UPSERTed), ledger (append-only lines),
object (opaque blob, row-indexed).
Scope · Sharing (principle 11 / principle 10): tenant = survives
every rebuild; KLS-ref = points at a specific build; user /
tenant = default visibility. See Scope & sharing below the table.
| Citizen | Scope · Sharing | Today | Shape | Writer | Wave |
|---|---|---|---|---|---|
Run records (run_registry v1) | tenant · tenant (KLS-ref) | SIS table, schema frozen for this move | rows | engine (run.py) | 1 — first citizen |
Archived run directories (events.jsonl + manifest + dag) | tenant · tenant (object per-run) | filesystem runs/<id>/ | object (row-indexed) | worker at run-terminal | 1 |
| Roster assignments + invites ⟨s3⟩ | tenant · tenant | Clerk publicMetadata + KLS-baked caps display | rows | intermediary endpoints | 2 |
| Error reports / contact submissions | instance · tenant | state/*.jsonl (homeless) | ledger | JinDesk server | 2 |
| Smoke attestation ledger | instance · operator | state/smoke_log.jsonl | ledger | smoke runner | 2 |
| Tenant-creation / fleet-level records | fleet · operator | nowhere (DDD ch. 01 excluded init from tenant runs) | rows | engine (init) | 2 |
| Instrument states / build journal / compilation log | tenant · tenant | sys DB (retiring, Strata Part 4) | rows | compilers | 3 |
| Navigation journal, bookmarks, saved views | tenant · user | SIS | rows (owned) | JinDesk | 3 (later — SIS-resident works today) |
| Selections (EDA Selector — saved named filters) | tenant · user | SIS | rows (owned) | JinDesk | 3 — new citizen, born with owner+visibility |
| Bell snoozes | tenant · user | SIS (deliberately unflushed tier) | rows | JinDesk | 3 |
| Notes / conversations | tenant · tenant | SIS, publishable to AFS | rows + publish path | JinDesk | 3 — keep workspace→publish, internal to the Reservoir |
| Compare / diff session | KLS-ref (spans 2) · user | transient (URL/session) | rows (owned) | JinDesk | later — the only genuinely KLS-scoped citizen |
Explicitly not citizens: signal/thesis/tile registries (Built),
bell resolutions (published to AFS — Authored once flushed), config
resolution (Built), anything jin make can regenerate.
Scope & sharing — the two axes over the citizens
Section titled “Scope & sharing — the two axes over the citizens”Two reframes decide the Scope · Sharing column, and one cost falls out
of them:
-
“KLS-scoped” is almost never a home — it is a reference. Nearly every citizen is tenant-scoped: a bookmark to
materials, a recent, a note, a saved filter — all about the tenant’s world, which survives every rebuild (principle 11). The KLS enters only as a foreign key: a run produced that KLS; a diff spans two; a bookmark may pin a snapshot. Only the Compare / diff session is genuinely KLS-scoped. Everything else lives at tenant scope in the one Reservoir, with at most a pointer at a build. -
Sharing is a column, not a store. User-default and tenant-default citizens live in the same tenant Reservoir;
owner+ a visibility flag decide the default view (principle 10) — there is no separate “personal DB.” Utility-shaped citizens (bookmarks, recents, selections, snoozes) default to their owner’s view — noise to others, never secrets. Contribution-shaped citizens (notes foremost) default to tenant-visible.
Reference stability is the price of tenant scope. A tenant-scoped
citizen that points at a build-derived id — a bookmark to a
finding_id, a note’s citation — can dangle when the next make
regenerates those rows. Stable ids (or a re-resolution pass on read) are
a requirement, not an afterthought — the cost of surviving rebuilds,
owed by every citizen that cites built content. KLS-references (run →
KLS, diff → two KLSes) follow a dangling-but-honest rule: when the
referenced build is pruned, the row survives (the tenant timeline is the
record) and the pointer reads as gone, never as broken state.
Identity & roster sections ⟨s3⟩
Section titled “Identity & roster sections ⟨s3⟩”Constrained by principles 1–3; where a design choice repeats a scar, the scar is cited.
The assignment row
Section titled “The assignment row”One row per (tenant, principal). UPSERTed, never deleted — lifecycle is
recorded in status + timestamps, so the roster’s history survives
revocation (a ledger’s memory with a row’s ergonomics).
| Column | Meaning |
|---|---|
tenant | tenant key — rows are tenant-scoped, per principle 7 |
principal_id | stable principal identifier (today’s roster id) |
kind | user / service (mirrors today’s principal registry) |
identity | assigned identity NAME — a reference into the authored tree (AFS-authored, now OPS-baked), never a definition (principle 1) |
extra_roles | capability-grained additions, same reference discipline |
status | invited → active → revoked / left |
clerk_user_id | nullable — set at bind; the record of the binding, never an authorization input online (principle 2) |
label, email | display fields — what the cockpit renders |
invited_by, invited_at, accepted_at, revoked_at | lifecycle provenance |
bound_by | public_metadata / offline_pin / declared — how this row last learned its binding |
recorded_at | staleness contract — surfaced wherever the row is displayed |
schema_version | frozen at v1 on adoption, the run-registry discipline |
What is deliberately absent: any capability column (capabilities
resolve from the tree at request time), and the fleet marker
(system_admin is a fleet-level fact — it lives with the fleet-level
records citizen, never in a tenant roster row; principle 7).
The tree is authored; the assignments are operational (revised 2026-07-23, Mig)
Section titled “The tree is authored; the assignments are operational (revised 2026-07-23, Mig)”Today’s principals/*.yaml conflates two things the strata split finally
separates — and the separation is by kind, not by a “declared roster”
of who-should-exist:
- The tree —
role_*/group_*(and any non-Clerkserviceprincipal): the identity definitions. Authored (AFS YAML, git-reviewed, pack-shippable) and rebuildable — but tenant-scoped: identical across a tenant’s base KLS, its generations, and its variations. So it bakes into the OPS, not the KLS (the scope axis — see Strata), as an overwrite-on-bake table (principalcompileretargets KLS→OPS). The Reservoir holds the tree; it does not swallow it — the YAML stays the source of record and git stays its review surface (P1’s spirit intact; only the built home moves). - The assignments — every
user_*: the bindings (which identity, which Clerk invitation, invited / accepted / revoked). Purely operational. There is no “declared user” — a user’s existence is Clerk’s to know (the authority) and the OPS’s to record; the reconcile sweep re-derives it. This is Option 1: nouser_*.yaml, ever.
Why no declared-user YAML. On the ground, every user_*.yaml that
exists today already carries lifecycle fields (clerk_invitation_id,
invited_by, invited_at) — it is living state persisted as authored
YAML, which is exactly what let the playworks canary litter one tenant’s
AFS with 45 files. Retiring the concept kills that class at the root: the
invite flow writes an OPS row + Clerk, never a YAML.
Lifecycle fields never write back into any authored file — retiring
the stamp_acceptance write-back (the machine editing authored files
that pin-retirement Phase 3 flagged as a lie waiting to happen) and
completing Phase 3b’s “true strip” as a side effect: the sidecar that
phase wanted for the derived pin is the OPS.
Two table disciplines in one store. The OPS now holds the tree (authored-baked — overwritten from AFS each bake, like the KLS) beside the assignments (operational-merged — restore-not-rebuild, merged on sync). Each OPS table is marked with its kind; that is P17’s overwrite-vs-merge distinction turned inward.
Invite lifecycle (issue → accept → bind → revoke)
Section titled “Invite lifecycle (issue → accept → bind → revoke)”The CLI proposes, the intermediary disposes — unchanged. What changes is where the record lands:
- Issue. The intermediary endpoint (
requireTenantOwner-gated) creates the Clerk invitation and writes through aninvitedrow (principle 5). No git, no make. - Accept. Clerk fires; on the user’s first bound request the
intermediary stamps
accepted_at+clerk_user_idwrite-through. Missed stamps heal in the sweep — acceptance is re-derivable from Clerk, so best-effort is safe. - Bind. The intermediary writes the assignment into
publicMetadata(the authority, principle 2) and flips the rowactive(the record). Order matters: authority first, record after — a crash between the two leaves a healable gap, never a phantom grant. - Revoke. Authority cleared in Clerk first, row goes
revokedafter,revoked_atstamped. The row remains — the cockpit’s history view is the Reservoir, not Clerk’s audit log.
At no step does anything read the Reservoir to decide authorization. The rows are consequences, not causes.
The display path
Section titled “The display path”Cockpit, roster, and principals pages read Reservoir rows only — never Clerk-live, never the KLS bake:
- Not Clerk-live: a Clerk API call in a page load is a request-path network dependency (principle 3’s spirit) and rate-limit bait.
- Not the KLS bake: that is the SHIPPED-but-not-LIVE lag class — the
engineer-no-admin change sat correct-in-source and wrong-on-RMC for
days because caps display rode a
make. Reservoir rows are current the moment the endpoint writes through.
recorded_at renders as the staleness badge on cloud replicas (read
path section). The session identity (SignedInBadge, whoami)
remains what it is today — resolved from the live Clerk session per
request — because that is authorization surface, not roster display.
The offline Level-3 story
Section titled “The offline Level-3 story”- Local / P2P2P instances: the offline fallback reads the local
Reservoir row (
bound_byrecorded honestly asoffline_pinwhen it fires). Same narrow semantics as today’s KLS pin — fires only when Clerk is unreachable, never resurrects a binding Clerk has dropped (scar: the pin-resurrection bug) — but fresher, because the row updates at write-through instead of at the lastmake. The KLSclerk_user_idpin bake retires in wave 2. - Cloud instances with Clerk unreachable: no fallback, by design. Reads degrade to the anonymous/demo surface; writes fail closed. Unchanged from today, restated here so nobody “fixes” it later.
The reconcile sweep (sync-principals successor)
Section titled “The reconcile sweep (sync-principals successor)”Principle 5 applied to identity. One sweep, two healing sources (Option 1 removed the third — there is no declared-user YAML to heal from), one direction of authority:
- Clerk heals existence and authorization-adjacent fields (a bound user with no row gets one; assignment, binding, acceptance) — Clerk always wins on these, and is now the sole source of who exists.
- The OPS itself carries what no other source knows (lifecycle timestamps, invitation provenance, display labels) and is never overwritten by a source that lacks the field.
The tree is not reconciled — it is baked from AFS by
principalcompile into the OPS (overwrite-on-bake). Reconcile is the
assignment sweep only.
Runs at verb boundaries (after invite/revoke), on the sync timer, and
explicitly as jin roster reconcile — the citizen-vocabulary rename of
sync-principals, which retires with the write-back it existed to
perform.
Wave-2 cutover, concretely
Section titled “Wave-2 cutover, concretely”principalcompileretargets therole/groupbake KLS→OPS (overwrite table); theuser_*assignment bake is dropped.- Endpoints (invite/revoke) gain OPS write-through (+ Clerk) and stop
writing
user_*.yaml. - Cockpit/roster/principals pages switch reads to OPS rows — tree and assignments now both read from the OPS.
jin roster reconcileships (Clerk + OPS, two sources); the KLSprincipal_registryandclerk_user_idpin retire from the KLS meta schema.- The
user_*.yamlretire from every tenant’s AFS (vai carries 51: 45 canary + 6 real);stamp_acceptanceandsync-principalsretire. principal_overlay.tscollapses into the OPS roster read (no more SIS overlay).
Each step is independently shippable and reversible; the bake keeps working until step 3, so a failed cutover is a revert, not an outage.
Write path
Section titled “Write path”- Local Reservoir file per tenant:
<live>/<pack>/<tenant>/reservoir/…engine decided (Q1): SQLite (WAL) for row-shaped citizens, JSONL for event-shaped ledgers, no DuckDB. The name stays open (Q5). - Producers write through best-effort (principle 5); every citizen declares its healing source (manifests on disk, Clerk, state/ files).
- Sync (when a layer is configured — principle 0): per-citizen cadence — rows sweep on a timer / at verb boundaries; objects upload at terminal events (principle 6). A Local-topology tenant simply has no sync layer and loses nothing but multi-machine durability.
Read path
Section titled “Read path”- Local readers (JinDesk on the owner’s machine, CLI) read the local Reservoir directly.
- Cloud JinDesks read a Reservoir replica hydrated from R2 —
eventually-consistent, staleness surfaced honestly in the UI
(
recorded_atis part of every citizen’s schema). - Nothing in a request path blocks on R2 (principles 3–4).
Distributed OPS & merge
Section titled “Distributed OPS & merge”When a tenant is operated in more than one location — a laptop and a cloud instance, two clouds — the KLSes are identical rebuildable caches (AFS + source → the same KLS; overwrite freely). The OPSes diverge: each accumulates its own notes, bookmarks, invites, runs. That divergence cannot be rebuilt away (P13), so it must be merged, never overwritten (P17).
ship and sync. ship stays a Built-stratum operation — rebuild
the KLS, push it to R2 with last-writer-wins. The OPS is excluded
from that overwrite path (not for privacy, the way the publication
blacklist excludes a tenant — but because it needs merge, not
overwrite) and rides its own merge-aware sync track, decoupled from
ship.
Merge is union-mostly — the citizen shapes were chosen for it. Each shape carries a trivial merge rule:
| Shape | Merge rule |
|---|---|
| Append-only ledger (runs, error/smoke, run archives) | union + dedup by id — two locations each ran a build → both rows valid, keep both. No conflict, ever. (This is why Q1 put ledgers in JSONL: “sync as dumb objects” is union merge.) |
| Per-owner row (bookmarks, recents, selections, snoozes) | union per owner — two people never touch the same row |
| Identity / roster | no OPS-merge at all: Clerk is the authority (P2); the reconcile sweep heals every location from Clerk |
| Contribution row (notes) | union of distinct notes (the common case); the rare same-note edit resolves last-writer-wins by recorded_at |
The tiebreaker for the one real conflict — LWW by recorded_at — is
already P16; safe cross-version merge is already P15. The
principles carry the merge machinery; this section only names it. So
OPS-merge is union-mostly, LWW-on-conflict — not a general merge
engine, not CRDTs.
Default topology (open question 7): single-home (one authoritative OPS per tenant — the owner’s machine in P2P2P, the cloud instance in cloud mode; everywhere else a read-only replica — no merge needed) versus multi-master (every location writes locally and merges on sync). Leaning: ship single-home as the default, but design every citizen merge-clean from day one (the rules above baked into each shape), so multi-master is a config flip, not a re-architecture.
R2 layout — filled LAST, from the citizen shapes
Section titled “R2 layout — filled LAST, from the citizen shapes”(Deliberately empty until the scope table stabilizes. Constraints already known: EU-jurisdiction endpoints; content-addressed objects for run archives; per-tenant prefixes mirroring the live-root layout; the versioning registry from Strata Part 3.)
Migration order
Section titled “Migration order”- Wave 1 — runs.
run_registryrows move table-for-table (schema frozen for exactly this); run-directory archival begins. Validates write-through + sweep + row-indexes-object end to end. - Wave 2 — identity + ledgers. Roster display cut over from
KLS-baked caps ⟨s3⟩; homeless
state/JSONLs adopted. - Wave 3 — SIS convergence + sys-DB retirement. SIS tables migrate
citizen-by-citizen; the standalone sys DB retires (Strata Part 4).
SIS-the-file dies last, after its final tenant table moves. The
note-migration slice (
docs/design/ops_note_migration.md, internal) is the proving vertical of this wave: it stands up the OPS on SQLite, migrates notes as the first user-visible citizen, provides the AFS→OPS import, retires the SIS/flush path, and adds the seal-time blend for standalone KLSes.
Open questions
Section titled “Open questions”- Store engine — RESOLVED (2026-07-21, Mig). The workload is row-shaped (tiny UPSERTs, point lookups — never analytical scans), which is SQLite’s home turf, not DuckDB’s. DuckDB’s cross-process model (one read-write process OR many read-only processes, never both) is the documented scar — three W010-failed bakes in one afternoon while JinDesk held handles, plus the journal SIGBUS. SQLite in WAL mode gives one writer + many readers concurrently across processes; DuckDB can still ATTACH the file read-only via its sqlite scanner when a page wants to join. Decision: the per-citizen split is SQLite (WAL) for state-shaped citizens (rows), JSONL for event-shaped ledgers (appends are lock-free, corruption-resistant, sync as dumb objects). No DuckDB in the Reservoir.
- Replica freshness contract for cloud reads — sync interval, staleness badge, and whether verbs can force a push.
- Publish seams: which citizens keep an AFS publish path (notes, bell resolutions do today) and does publishing move engine-side?
- The borderline objects from Strata’s open question 2 (narratives, bell, config resolution) — default Built until argued otherwise.
- Names — RESOLVED (2026-07-22, Mig). The store is OPS (Operational Store) — the per-tenant file, peer of AFS/KLS; the Reservoir stays the Sense’s name for the concept (see the Claim). Whether this ships as Sense 61 Part 2 or its own Sense stays Mig’s to set.
- The attention model — its own working session (Mig + sister-3 + sister-5), not settled here. Framing fixed by principle 10: no secrets in the family; visibility is a distraction filter, not access control. On the table: per-citizen visibility defaults (utility vs contribution shapes), the filter UX (how a participant tunes what they see), whether visibility wants more granularity than owner-view/tenant-view (topic? role?), and the shape of the admin participation view. Principle 10 fixes what is certain: owner on every row, visibility as a separate attention axis, participation visible to governance, contributions visible by nature.
- Default OPS topology: single-home (one authoritative OPS, others read replicas — no merge) vs multi-master (merge always). Leaning: single-home default, every citizen merge-clean from day one so multi-master is a config flip. See Distributed OPS & merge.
Numerical neighbors: ← Sense 61 — The Private OPS (bring your own operational store) · Sense 61: The Strata →