Accept page-centric markdown SoR with dual authoring, live includes, and lazy projections; accept thin dir+git plane with PageOps and supported upgrades to shard-wiki and kontextual. Demote ADR-0002 Forgejo-as-SoR.
16 KiB
Architecture Blueprint — coulomb.social content & platform
| Field | Value |
|---|---|
| Status | draft — decisions recorded 2026-08-13 |
| Product | coulomb.social rebuild (coulomb-social) |
| Related | stage-1 cut, capability maps, authoring, stale/derived, upgrade path |
| Non-goals | Implementing UI chrome here; full markitect feature build in this doc |
0. Decision log (2026-08-13)
| # | Topic | Decision |
|---|---|---|
| D1 | Content plane | Start thin: directory + markdown files, secured in git. Supported upgrade path: thin → shard-wiki (when wiki/federation matters) via markitect-shaped content; then → kontextual (structure, scale, rich media) as a supported upgrade of the information base. |
| D2 | asset-store | Extract later. Stage-1 may use local files next to markdown (and/or temporary blob helpers); do not block on a new repo. |
| D3 | PolicyGateway / content authz ownership | Do not invent a parallel owner. Prefer NetKingdom consolidation so identity and authorization decisioning for app resources become the obvious path (flex-auth / NK as one story). Until that lands, coulomb uses coarse product authz (session + SpaceMembership + tenant); content plane stays fail-closed when a PDP is wired. |
| D4 | Structure UX | Aspire to outline-editor flexibility: explode one markdown into smaller connected documents; implode selection(s) back into one file. Default = live (spreadsheet-like: includes/derived resolve to current inputs automatically). Pin only when the user freezes a snapshot. |
Earlier freezes (unchanged): NetKingdom identity, Railiance deploy, markdown SoR + DB cache, markitect-tool not host, page-first, dual authoring, lazy projections + lock, stage-1 cut.
1. Architectural aim
Deliver a co-creation product where:
- Identity (users, groups, tenants) is NetKingdom — not reimplemented.
- Runtime is Railiance — not reimplemented.
- User-owned content is markdown (+ assets) in a git-secured directory tree, exportable without the app database.
- The database is a cache / projection for cards, memberships, search speed.
- markitect-tool makes markdown structured and operable (parse, include, explode/implode, later derived slots) — not the product host.
- coulomb owns co-creation UI (cards, spaces, outline-oriented editing, copy/transfer).
- Knowledge work stays fluent: top-down and bottom-up; live reuse by default; pin when freezing matters.
- Growth does not strand data: thin git base → shard-wiki → kontextual are supported upgrades, not rewrites from a proprietary store.
2. Non-negotiable rules
| # | Rule |
|---|---|
| R1 | Markdown (frontmatter + body + backmatter) + assets under a directory tree are the exportable SoR. |
| R2 | App DB holds projections and app linkage only; disposable for content bodies. |
| R3 | Authn/IdP = NetKingdom; coulomb maps OIDC → Member; no password store. |
| R4 | Authz: prefer NetKingdom-consolidated decisioning; no second user directory; no permanent coulomb-specific PolicyGateway fiefdom. |
| R5 | markitect-main is legacy reference, not a runtime foundation. |
| R6 | markitect-tool is the markdown structure dependency. |
| R7 | Default content plane = thin dir+git. PageOps hide the plane; upgrades swap adapters. |
| R8 | Stage-1 must: onboarding; spaces/pages with Title / Abstractor / Visual; page copy; page transfer. |
| R9 | Pages first — address / live-transclude / explode-implode / promote; not a priori chunk storage. |
| R10 | Live by default for includes and other formula-like derived content; pin freezes a snapshot; pinned/stale use badge → alternative → replace | keep + lock. |
| R11 | Explode / implode (outline-editor flexibility) are first-class structure operations (markitect primitives + coulomb UX). |
| R12 | asset-store extraction is deferred; assets may live beside markdown until then. |
3. System shape
┌──────────────────────────┐
│ NetKingdom │
│ OIDC · groups · tenants│
│ (authz consolidation →)│
└────────────┬─────────────┘
│ principal
▼
┌─────────────────────────────────────────────────────────────┐
│ coulomb.social (product) │
│ • Cards, spaces, dual authoring, outline explode/implode │
│ • PageOps; live resolve; pin when asked │
│ • Railiance deploy │
└───────────────┬─────────────────────────────┬───────────────┘
│ cache │ PageOps
▼ ▼
┌───────────────┐ ┌───────────────────────────┐
│ Postgres │ │ Content plane │
│ projections │ │ ★ A thin dir + git │
└───────────────┘ │ (default / stage-1) │
│ ↗ B shard-wiki upgrade │
│ ↗ C kontextual upgrade │
└─────────────┬─────────────┘
│
┌─────────────▼─────────────┐
│ markitect-tool │
│ structure · include LIVE │
│ explode/implode · query │
│ pin/derived (proposed) │
└─────────────┬─────────────┘
▼
┌───────────────────────────┐
│ Assets (files next to md) │
│ later: shared asset-store │
└───────────────────────────┘
PageOps (store-agnostic)
| Op | Meaning |
|---|---|
list_pages / get_page / put_page / archive_page |
CRUD |
copy_page / transfer_page |
Duplicate / re-home |
explode_page / implode_pages |
Outline-style split/join (markitect) |
resolve_view |
Live includes/derived for display (default) |
pin_slot / unpin / refresh_pinned |
Freeze snapshot; stale → alternative → replace|keep |
project_field / regenerate / lock |
Title/abstractor/visual lifecycle |
put_visual / get_asset |
Binary next to tree (later asset-store) |
Upgrade path (supported)
Stage now A dir + .md + assets (+ git)
│ markitect-tool always
│
When wiki / B same markdown becomes shard-wiki shard(s)
federation │ PageOps adapter swap; export tree still valid
│
When scale / C kontextual KnowledgeAsset + blobs
rich media / │ markdown remains portable representation
governance │ PageOps adapter swap; migration tooling required
Requirement: every upgrade preserves user export of markdown+assets; no dead-end proprietary body store.
4. Information model (product)
| Concept | Representation |
|---|---|
| Page | One markdown file (or connected set after explode) |
| Space | Directory / container + metadata page |
| Tree | Git-secured directory layout |
| Card | UI projection (title, abstractor, visual, actions, stale) |
| Outline | Headings / exploded connected docs — not Bubble chunk rows |
| Live include | mkt:include resolves current source (default) |
| Pinned derived | Optional frozen snapshot; explicit refresh |
| Member / tenant | NetKingdom + app session |
4.1 Outline editor flexibility (explode / implode)
Aspire to outline-editor UX powered by markitect explode / implode (and related compose/include):
| Action | User intent | Markitect analogue |
|---|---|---|
| Explode | Split a large page into smaller connected documents (sections as pages or files) | mkt explode (+ connectivity via includes/refs) |
| Implode | Merge selected docs/sections back into one markdown file | mkt implode / compose |
| Navigate outline | Treat structure as editable hierarchy | Headings + connected pages view |
Connectivity after explode: prefer live includes or content references so the outline stays one logical document until the user pins or permanently severs links.
4.2 Live by default (spreadsheet metaphor)
| Live (default) | Pinned (exception) | |
|---|---|---|
| Includes / formula-like derived | Resolve to current inputs on view/export | Stored snapshot until refresh |
| Body of a normal page | User text as written | n/a |
| Projections (title/abstractor/visual) | Lazy fill then persist; regenerate on demand | Lock freezes; stale offers alternative |
| Stale pinned / locked derived | — | Badge → generate alternative → replace or keep |
Spreadsheet rule: formulas stay live unless the user pastes values (pin).
Pinning is deliberate, not the default co-creation mode.
4.3 Dual authoring + lazy projections
Unchanged intent: top-down and bottom-up both natural; missing display fields lazy-fill then persist; regenerate + lock per field.
See authoring-modes-and-lazy-projections.md, stale-derived-content.md.
5. Content plane — decided start + upgrades
5.1 Default: A — thin directory + git
spaces/<space-slug>/
pages/<page-slug>.md
assets/…
# entire tree in git (user- or service-mediated commits)
| Why | Simplest SoR; user data is files; matches “secured in git” |
| coulomb role | PageOps over filesystem/git; DB indexes cards |
| markitect role | Parse, live include, explode/implode, validate |
5.2 Upgrade B — shard-wiki
When multi-shard wiki behavior, overlays, or federation become product-relevant:
- Ingest the same markdown tree (or git repo) as a shard.
- PageOps adapter targets shard-wiki; UI unchanged.
- markitect-tool remains structure layer for resolve/explode.
5.3 Upgrade C — kontextual-engine
When asset identity, scale, rich multi-format media, audit/lineage, or ECM-class governance dominate:
- Supported migration of the information base into kontextual assets/blobs.
- Markdown remains the portable authoring/export form where applicable.
- Not required for stage-1.
5.4 What we do not do now
- Do not build coulomb on markitect-main.
- Do not require kontextual or shard-wiki for first productive transfer.
- Do not extract asset-store before a second consumer (D2).
6. Assets (deferred extract)
- Stage-1: files under
assets/(or equivalent) in the content tree; optional simple content-addressing later. - asset-store shared repo: later (seed from kontextual BlobStorage when extracted).
- Visual field stores a ref into that tree (or future store).
7. Identity & access control
Browser → NetKingdom OIDC → coulomb session
├─ product: tenant_id, SpaceMembership (and future NK groups)
└─ long-term: NetKingdom-consolidated authz (flex-auth / NK)
so PolicyGateway ownership becomes trivial or unused
| Now | Target |
|---|---|
| Session + membership + tenant isolation | Same + NK-group claims |
| Content plane fail-closed if PDP present | PDP = consolidated NK story |
| No coulomb-owned IdP | Unchanged |
D3: Avoid multi-team thrash on “who owns PolicyGateway”; push consolidation in NetKingdom so apps only pass principal + resource attributes.
8. Deploy sketch (Railiance)
| Component | Deploy |
|---|---|
| coulomb-social | Existing chart; app.coulomb.social |
| Content tree | Volume or git-backed storage accessible to the app; git is SoR discipline |
| Assets | Beside markdown until asset-store |
| NetKingdom | Existing KeyCape / Authelia |
9. Stage-1 product scope
Must:
- Onboarding (NetKingdom)
- Spaces/pages: Title, Abstractor, Visual, body
- Copy, transfer
- Cards; dual authoring; lazy projections
- Content as dir+git via PageOps
- Live includes when markitect is wired; pin optional
Aspire early (structure UX):
- Outline-oriented explode / implode
Later: social, marketplace, monetization, shard-wiki/kontextual upgrades, asset-store extract.
10. ADRs
| ADR | Topic | Status |
|---|---|---|
| ADR-0001 | NetKingdom identity | accepted |
| ADR-0002 | Forgejo vertical slice | partly superseded by 0003/0004 |
| ADR-0003 | Page-centric markdown SoR, authoring, live/pin | accepted |
| ADR-0004 | Thin git plane, PageOps, upgrades B/C | accepted |
| ADR-0005 | asset-store extraction | later |
| — | NetKingdom authz consolidation | platform (NK) |
Markitect-tool: live include (done), explode/implode (done), pin/derived proposal (proposed-stale-derived-and-include-pinning.md).
11. Spikes (narrowed)
With D1–D4, prioritize:
- Thin tree layout + git write path (PageOps on files).
- markitect parse + live include in get/view.
- Explode/implode UX prototype on real pages.
- Lazy title/abstractor + stale replace|keep.
- Export zip/tar of tree without DB.
Defer deep shard-wiki/kontextual spikes except export/import round-trip sketches that prove upgrade feasibility.
Full list remains in spike-checklist.md (update scorecard: A = chosen start).
12. Closed discussion prompts
| Prompt | Resolution |
|---|---|
| A vs B vs C for start | A; B/C = supported upgrades |
| asset-store now? | Later |
| PolicyGateway owner | Consolidate under NetKingdom; don’t fork ownership |
| Chunk UX | Outline editor + explode/implode, not chunk tables |
| Live vs pin default | Live; pin is explicit |
13. Working recommendation (frozen)
| Layer | Decision |
|---|---|
| Product UI | coulomb cards, spaces, outline explode/implode, dual authoring |
| Markdown structure | markitect-tool; live includes by default |
| Identity / deploy | NetKingdom / Railiance; authz → NK consolidation |
| Content plane | Thin dir + git now |
| Upgrades | → shard-wiki → kontextual (supported, markdown-preserving) |
| Assets | Beside tree now; asset-store later |
| Derived/stale | Live default; pin + badge → alternative → replace|keep |
| markitect-main | Avoid |
References
coulomb-social
docs/architecture/2026-08-12-content-substrate-exploration.mddocs/architecture/transclusion-and-chunks.mddocs/architecture/authoring-modes-and-lazy-projections.mddocs/architecture/stale-derived-content.mddocs/architecture/spike-checklist.mddocs/capability/stage-1-cutover.mddocs/capability/model-v0.yamldocs/capability/existing-bubble-capability-map.mddocs/decisions/2026-08-12-feature-cut-stage-1.md
fleet
- markitect-tool: transform-compose-include, explode-implode, content-references, proposed-stale-derived-and-include-pinning.md
- shard-wiki: INTENT (Coulomb as consumer; upgrade target)
- kontextual: blob-storage, asset-registry (upgrade + later asset-store seed)
- markitect-main: successor-gap (legacy only)