coulomb-social/docs/adr/ADR-0002-space-content-forgejo-markdown.md
tegwick be32432d39 Add Space metadata model and provisional Forgejo content ADR
Introduce tenant-scoped Space/SpaceMembership with list and detail views.
Document markdown-in-Forgejo as content SoR (ADR-0002). Run migrations on
container start so app.coulomb.social picks up the new tables.
2026-08-12 01:30:05 +02:00

123 lines
4.2 KiB
Markdown

# ADR-0002 — Space content as markdown in Forgejo
| Field | Value |
|-------|--------|
| Status | **Provisional** (accepted for T04 vertical slice; revisit write path) |
| Date | 2026-08-12 |
| Deciders | bernd |
| Workplan | CSOC-WP-0004-T03 |
## Context
coulomb.social co-creation **spaces** need durable page/artefact content.
Bubble stores opaque page graphs. The rebuild should:
- keep content **agent- and human-editable** in git,
- version and review changes like normal software,
- avoid making Postgres the system of record for long-form prose,
- align with Forgejo already hosting fleet code on railiance01.
Identity and space **metadata** stay in the app DB (`Space`, memberships).
## Decision
### 1. Canonical content form
- **Markdown** (CommonMark + limited GFM: tables, fenced code, images).
- Static assets (images, attachments) live beside markdown under a content root.
- Postgres stores **metadata and binding only** — never full page bodies as SoR.
### 2. Repository layout (v1)
**One Forgejo repository per space** under a dedicated org (recommended:
`coulomb-spaces` or tenant-scoped org later).
```text
<forgejo_owner>/<forgejo_repo>/
README.md # optional space intro
pages/
index.md # default landing page
<page-slug>.md
assets/ # images etc. referenced from pages
```
| Field on `Space` | Meaning |
|------------------|---------|
| `forgejo_owner` | org/user |
| `forgejo_repo` | repo name |
| `default_branch` | usually `main` |
| `content_root` | default `pages` |
**Branch policy (v1):** app reads from `default_branch` only. PRs for review
are a human/Forgejo workflow; the app does not merge PRs in v1.
**Monorepo alternative (deferred):** single repo with `spaces/<slug>/…`
rejected for v1 to keep permissions and migration packages simple.
### 3. Read path (T04)
1. App resolves `Space` by tenant + slug (authz already enforced).
2. If binding incomplete → empty state (no silent Bubble fetch).
3. Fetch file via **Forgejo raw/contents API** (preferred) or shallow cache:
- HTTP GET with service token from env/OpenBao.
- Cache rendered HTML or raw markdown in memory/disk with short TTL +
optional webhook invalidation later.
4. Render markdown → HTML with a locked-down sanitizer (no raw script).
5. Fail closed on 404/403/network errors with operator-visible reason.
### 4. Write path (T05 — provisional)
**v1 preference:** **edit in Forgejo** (web UI or git) + app refresh.
- Lowest security surface (no app-held write credentials required if public-read
internal repos use deploy token read-only).
- Optional later: in-app editor → commit as bot user via API.
**Not in v1:** bidirectional live sync with Bubble.
### 5. Mapping from Bubble (later CSOC-WP-0001)
| Bubble concept | Target |
|----------------|--------|
| Space / room-like container | `Space` row + Forgejo repo |
| Page / chunk prose | `pages/<slug>.md` |
| Attachments | `assets/…` |
| Permissions | `SpaceMembership` + NetKingdom groups (refine later) |
Export scripts should emit markdown files + a manifest JSON for binding fields.
### 6. Secrets
| Secret | Storage |
|--------|---------|
| Forgejo API token (read, later write) | K8s Secret / OpenBao — env e.g. `FORGEJO_TOKEN` |
| Forgejo base URL | non-secret config e.g. `FORGEJO_BASE_URL=https://forgejo.coulomb.social` |
Never commit tokens. Never render tokens in Session details.
## Consequences
**Positive**
- Content is git-native and agent-friendly.
- Clear boundary: app = membership + UX; Forgejo = document history.
- Migration can ship repos per space without rewriting history into SQL.
**Negative / follow-ups**
- Need Forgejo org, tokens, and network reachability from the app pod.
- Offline/local dev needs a stub or fixture markdown path.
- Search and cross-space queries need a separate index later.
## Open questions (non-blocking for T04)
1. Org name: `coulomb-spaces` vs per-tenant org.
2. Private repos only vs public read for some community spaces.
3. When to add webhook-driven cache purge.
4. Whether user OAuth to Forgejo is required for in-app write (T05).
## References
- CSOC-WP-0004, Space model in `coulomb_social.apps.spaces`
- ADR-0001 NetKingdom identity
- Host posture: `docs/deploy.md` (app.coulomb.social)