# demarkus-library — Roadmap & Resume Pick-up-where-we-left-off doc. Authoritative plan is [/plans/universe-library.md](/plans/universe-library.md); the Reading Room redesign lives in [/plans/reading-room.md](/demarkus-library/plans/reading-room.md) (invariants locked in [ADR 0005](/demarkus-library/adr/0005-reading-room-spatial-trail.md)). Build model: Opus 4.8 default, Fable 5 for load-bearing design. ## Where we are (2026-06-13 — Reading Room fully LIVE in prod, R3 deployed) `main` HEAD `e8a136b` (library). **The whole Reading Room is deployed and working in prod**: R1 (Tufte pane) → R2 (trail engine) → the floor (pane zero) → **R3 (previews, backlinks, graph pane)**, both transports (decision 16). Cluster versions: **library 0.1.7, broker 0.1.36, worlds server 0.17.18** — ArgoCD Synced/Healthy (R3 deployed via PR #17 → v0.1.7, rev 0.1.7 confirmed on the running pod). Next: floor enrichment / Phase 2. Detail in [journal 2026-06-13](/demarkus-library/journal/2026-06-13.md) + [journal 2026-06-12](/demarkus-library/journal/2026-06-12.md). - **Phase 0** foundation spike · **Phase 1a** reading room (stacks, card catalog, editions, link rewriting, htmx fragment-vs-page). - **Phase 1b — library card: DONE.** Broker confidential web clients (demarkus `117e2a6`) · oauth client (PKCE S256, Basic auth §2.3.1 escaping) · server-side sessions + single-flight refresh · ctx threading on ports · broker MCP `WorldGateway` with **per-reader pooled sessions** (1 request per read; fixed the live 429s) · login turnstile · composition-root swap `DEMARKUS_TRANSPORT=quic|broker` · native TLS for local e2e (env at `~/.config/demarkus-library/dev.env`, **client id `library-web-dev`** — dedicated dev broker client; prod is `library-web`). E2e verified live. - **Multi-world floor + federation** (fc4277c): (world, path) addressing, `/w//...` routes, federated composite gateway — KS names via broker, host[:port] via direct QUIC (`DEMARKUS_FEDERATION` default true). Read token never leaves home host (test-pinned). Cross-world mark:// links clickable. - **Deploy + CI/release — DONE** (PR #10, v0.1.0): goreleaser-pro, distroless Dockerfile, helm chart (replicaCount 1 + Recreate — in-memory sessions), hardened workflows. Module paths on latebit-io. **Releases auto-cut on merge to main** (PaulHatch per-module semver). Deploy = ArgoCD watching `latebit-io/demarkus-knowledge-system-deploy`; image+chart pins in `apps//applicationset.yaml`, world list + webClients in `deployment.yaml`. - **Rendering — DONE** (PRs #11/#12): chroma classes-mode · GFM alerts · emoji · mermaid + KaTeX islands (lazy, degrade to source). - **R1 — Tufte pane: MERGED (PR #13), LIVE.** 60ch column + 250px margin · pinned margin trust signals (status badge: metadata `status:` axis → frontmatter fallback → draft; tags; provenance; parsed-frontmatter properties; `mark://` + raw-source escape) · sidenote goldmark extension (footnote→margin) · lookup-backed tag pages · `/w/:world/raw/*` · search box removed · authoring contract published (`mark://soul.demarkus.io/.well-known/library/authoring.md`). - **R2 — trail engine: MERGED (PR #14), LIVE** (plans §"R2 build decisions"): - Trail URL `/t//~/?focus=i`; `~` = separator; pure codec + click algebra (truncate/append, dedup→focus, spine→focus). Cap 10, raw slashes. Pane kinds: `/d/` (doc; trailing slash/empty = listing), `/tags/`, bare `u` = floor. - Rendered-document cache (decision 9): port.DocumentCache + LRU. **Focused-live: every click costs exactly 1 world read.** - Canvas: spines / body-only parent / focused Tufte pane; tombstone spine for unreadable unfocused panes; trailizeLinks rewrites /w/ hrefs to post-click trail URLs + active-link breadcrumb. Spec published (`.well-known/library/trails.md`). - **Codec note:** `paneAddrFromParts(world,kind,value)` (trail.go) is the single source of truth for pane semantics; `parsePaneChunk` (/t/) and `paneAddrFromRoute` (/w/) both call it (PR #16). Share semantics, not syntax — don't re-duplicate. - **The floor — universe view (pane zero): MERGED (PR #15), LIVE** (plans §"Universe-view research" + 2 addenda): - **demarkus prereqs:** `mark_worlds` broker tool (#182) lists authorized worlds; LOOKUP match-all `*` (`b8aedb7`) returns the whole catalog in importance order. World must run server ≥ 0.17.18 or it tombstones. - `WorldGateway.Worlds()`: broker parses mark_worlds; QUIC returns the home world; federated prefers Names→Hosts. - `service.Floor/FloorCached`: worlds × Lookup("*"); failed world → tombstone (dimmed, never dropped); ErrUnauthorized → re-login. - Web: pane kind `u`; `/` → `/t/u`; **SSR SVG** floorSVG (worlds as circles r∝satellite count, top-10 docs on orbit r∝importance, stroke=status, every node a plain ). **ADR 0003's canvas island stays UNSPENT.** - PR #16 fix: parser rejected the world-root listing chunk (`/d/`); fixed + consolidated the two parsers. - **R3 — previews + backlinks + graph pane: MERGED (PR #17), `v0.1.7`, LIVE** (plans §"R3 build decisions"; verified live in QUIC): - **Edge source = render-time observed-links map** (the load-bearing decision). `rewriteLinks` already resolves every link to (world,path); capturing edges is free and **library-internal → transport-symmetric** (no broker graph store, so decision 16 holds). Rejected: broker store (broker-only), library-side crawl (over-built). Cold state fills as the room is read; durable hub-sourced topology (addendum 2) is the upgrade layered in front later — the service edge methods are the seam. - Architecture: `domain.Ref` + `domain.Neighborhood`; edge store is an internal `service.linkGraph` (fwd+reverse maps, mirrors floorCache — no new port/adapter/wiring). Inbound port gains `RecordLinks`/`Backlinks`/`Neighborhood`. `rewriteLinks` returns `(html, []Ref)`, `rewriteHref` returns `(route, Ref, isDoc)`; web resolves+records, core owns the store. - **Hover previews:** `previewize` wraps in-app doc anchors with a preview-host/preview-card popover + htmx (`mouseenter delay:300ms once` → `/w/:world/preview/*` fragment). Pure htmx+CSS, **no JS**. One component for body links AND backlinks. Snippet = first paragraph, from `ReadCached` (budget-safe). mouseenter overrides click so hx-boost nav still fires. - **Backlinks "referenced by"** block in shared doc-meta (trail URLs on the canvas, /w/ permalinks single-doc). Empty ⇒ absent. - **Graph pane:** kind `/g/` via shared `paneAddrFromParts`; route `/w/:world/g/*`; **SSR SVG** graph.go (center + backlinks left / outbound right, each node continues the trail). Canvas island still unspent. Margin "graph" affordance opens it. - `docs/trail-format.md` + in-universe trails.md (v3) updated with the `g` kind (decision 14). - **Repo `pre-commit.sh`** added (from demarkus, single-module): fmt/vet → golangci-lint (pinned to CI **v2.9.0**) → build → test. Run before wrapping a phase/task. - **Broker-mode R3 smoke still pending** (interactive OAuth); parity is architectural (edge source is library-internal, no broker-only path). ## How to resume ```sh cd ~/latebit/demarkus-library && ./pre-commit.sh # fmt/vet/lint/build/test go build -o /tmp/dl ./cmd/demarkus-library # quic mode (no auth): open /t/u (floor), a doc, hover a link, click "graph". PORT=8080 DEMARKUS_INSECURE=true DEMARKUS_HOST=soul.demarkus.io \ DEMARKUS_AUTH= /tmp/dl # broker mode: source ~/.config/demarkus-library/dev.env && /tmp/dl # OAuth on broker.knowledge.demarkus.io. Port held? lsof -ti :8443. # cluster (GKE): kubectl ctx gke_knowledge-49722_...; creds expire mid-session # → ! gcloud auth login. ArgoCD ns argocd; check operationState, not just # sync.status (stale on a retrying op). ``` ### File map (hexagonal — ADR 0002) - `internal/core/{domain,port,service}` — entities (Document, Floor, WorldInfo, **Ref, Neighborhood**), ports; ReadingService = Read/Browse/History/Search/Tag/Raw + *Cached + Floor/FloorCached + **RecordLinks/Backlinks/Neighborhood**; WorldGateway adds Worlds(); DocumentCache; service/floor.go (catalog parse), **service/graph.go (linkGraph observed-links store)** - `internal/adapter/inbound/web` — Echo routes (`/t/*` canvas incl. `u`/`g`, `/w/:world/...` incl. tags/raw/**g**/**preview**), trail.go (codec + click algebra + paneAddrFromParts, incl. `g`), trail_links.go, links.go (rewriteLinks → edges), trail_handlers.go (+ floorPaneView + graphPaneView), floor.go + **graph.go** (SSR SVG), **preview.go** (cards + previewize + backlinkLinks), templates (+ **preview/preview-link**), static/ - `internal/adapter/outbound/{world,broker,federated,oauth,markdown,cache}` — direct QUIC, MCP pool (Worlds parses mark_worlds), shape-router, broker OAuth, goldmark, LRU cache - `cmd/demarkus-library` — composition root, AppConfig, brokerAuth shim - `docs/` — authoring-contract.md + trail-format.md (sources of the in-universe `/.well-known/library/` docs; keep both in sync) - `pre-commit.sh` — the phase/task gate (golangci-lint pinned to CI v2.9.0) ## Next sessions — queued work ### 1. Floor enrichment (pairs with R3's edge-source seam) Hub-sourced topology (plans addendum 2 — hubs are the primary map: durable, versioned, transport-symmetric) layered in front of the observed-links map; portal nodes for externally-linked hosts in quic mode; world-view zoom level. Hub discovery convention still open (manifest self-declaration vs DEMARKUS_HUB env — lean manifest + override). Graph-pane aggregation/cap for high-degree hubs (a doc with ~40 outbound links crowds the right arc). ### 2. Phase 2 — restricted collections (per /plans/universe-library.md) Per-world/per-path authz surfaced in the reading room. Depends on broker-side authz semantics — re-read the plan + broker `authz.go` first. ### Maybe / observed - **Broker-mode R3 smoke** (`dev.env`): hover preview, backlinks, graph pane behind the turnstile — last unrun acceptance check. - **Deploy CI papercut**: `apps-immutable-check` runs the guard via `... | tee`, so the pipe masks the script's exit — the check can NEVER fail the build, only posts a PR comment. Add `set -o pipefail` / drop the tee. - **Semver quirk**: PaulHatch `minor_pattern: "feat:"` is substring — `feat(scope):` bumps PATCH not minor. Fix via regex `/^feat(\(.+\))?:/`. - Broker rate-limit drop (60/30 → 30/15) on `fix/broker-rate-limit-down` — confirm merged/synced. - Visual polish by eyeball (pane widths/gaps, spine legibility, dark mode, canvas scroll; floor at many-worlds scale + label crowding; preview-card placement near pane edges; graph pane at high node counts). ### Standing deferred - Find + fix publishers that wrote metadata fences into bodies (R1's friendly frontmatter reduces harm; source fix still open). - Shared session store (or sticky sessions) → replicaCount > 1 + RollingUpdate + PDB — the R2 doc cache, single-slot floor cache, and R3 observed-links graph are all per-pod (fine at replicaCount 1; revisit together — per-pod graph means backlinks differ across replicas). - Adopt demarkus's strict `.golangci.yml` (18 issues to clean first). - Phase 3 cataloging desk (writes, CodeMirror island) · Phase 4 AI librarian (SSE; joins the canvas as a pane, trail = its context) · Phase 5 public face. ## History (condensed — details in journals + ADRs) - 2026-06-10: Phases 0 + 1a built + merged (PRs #1–2). - 2026-06-11: Phase 1b planned + shipped (PRs #3–#9); floor + federation; frontmatter strip. Gotchas in [debugging.md](/demarkus-library/debugging.md). - 2026-06-12: v0.1.0 deploy/CI (#10) · latebit-io migration · v0.1.1/v0.1.2 rendering (#11/#12) · ADR 0005 · **R1 (#13)** · **R2 (#14)** ([journal](/demarkus-library/journal/2026-06-12.md)). - 2026-06-13: universe-view research · demarkus **mark_worlds (#182)** + **LOOKUP match-all (`b8aedb7`)** · **floor merged (#15)** · cluster rollout of R1+R2+floor (server 0.17.18 / broker 0.1.36 / library → 0.1.6) · **trail root-listing fix + parser consolidation (#16)** · **R3 merged + deployed (#17, `v0.1.7`)** — observed-links edge source, hover previews, backlinks margin, graph pane; repo `pre-commit.sh` added (golangci-lint pinned to CI v2.9.0). **Reading Room fully live in prod** (library 0.1.7) ([journal](/demarkus-library/journal/2026-06-13.md)). ## Git / process notes - Work on `feature/*` / `fix/*` branches. **Never auto-commit** — Fritz commits and opens PRs; CodeRabbit reviews via `.coderabbit.yaml`. Releases auto-cut on merge. - **Run `./pre-commit.sh` before wrapping a phase/task** (fmt/vet/lint/build/ test; golangci-lint pinned to CI's v2.9.0). - Deploy = ArgoCD on `latebit-io/demarkus-knowledge-system-deploy`. Bump chart `targetRevision` + `image.tag` together in `apps//applicationset.yaml`. Library/broker are Deployments (clean roll); worlds are StatefulSets. - PR scopes: domain, port, service, web, world, markdown, cmd, view, deps, ci, broker, auth, session, oauth, federated, deploy, docker, helm. --- ## ⭐ CURRENT STATUS (2026-06-14) — supersedes "Where we are" above The Reading Room is **feature-complete and fully deployed**. Cluster (all ArgoCD Synced/Healthy): **library 0.1.10, broker 0.1.37, worlds server 0.17.19, agent 0.12.43**. Shipped + verified since the stale section above: - **R3** (PR #17, library 0.1.7): hover previews, backlinks margin, graph pane. - **Floor enrichment**: hub-graph topology → cross-world edges + portal nodes. - library hub-topology reader (0.1.8) reading `/graph.md`; `DEMARKUS_HUB` chart wiring (0.1.9); host↔name join via broker `mark_worlds` `address` column (broker 0.1.37 + library 0.1.10). - agent publishes `/graph.md` (`--publish-graph`, client 0.12.42/43). - **server token-ownership fix** (0.17.19): token Secrets created by a pre-install bootstrap Job, not chart-templated (ArgoCD was pruning/ corrupting the runtime-mutated tokens.toml — see debugging.md). - **External federation verified**: floor portals to public worlds reachable from the cluster (`docs.demarkus.io` cloud + `soul.demarkus.io` once its residential ingress was opened). docs in debugging.md. Full detail: [journal 2026-06-13](/demarkus-library/journal/2026-06-13.md) (latest entries run into 2026-06-14) + [debugging.md](/demarkus-library/debugging.md). ### Next phases (queued) 1. **World-view zoom** — deferred floor-enrichment piece: one world's catalog as dir clusters + aggregate bubbles → listing panes (plan §Universe-view research, zoom level 2). 2. **Phase 2 — restricted collections** — per-world/path authz in the room (re-read /plans/universe-library.md + broker authz.go). 3. **Standing deferred** — shared session store → replicaCount>1 (R2 cache, floor cache, R3 link graph all per-pod); strict `.golangci.yml`; deploy CI papercuts (apps-immutable-check tee-masks exit; PaulHatch `feat(scope):` bumps patch not minor). ### Loose end Demo links in `world-a/index.md` (`mark://root/index.md` + `mark:// soul.demarkus.io/index.md`) added to exercise floor edges/portals — keep as a live example or strip (then restart the agent to re-publish graph.md). ### Process `feature/*` branches; **Fritz commits, never the agent**; `./pre-commit.sh` gate (golangci-lint pinned CI v2.9.0); CodeRabbit per PR; releases auto-cut on merge; deploy = bump `apps//applicationset.yaml` targetRevision+image.tag (ArgoCD). `knowledge` MCP joined for root/world-a writes. ### Update (2026-06-14, later) — world-view zoom SHIPPED Cluster now **library 0.1.11** (others unchanged). Next-phases item 1 (**world-view zoom**) is DONE + deployed: PR #21, reader-only, floor world node zooms to `/u/` (catalog as dir clusters + "+N more" → listing panes) + doc-margin "map" affordance. In-universe trails.md → v4 with the `/u/` chunk. **Loose end resolved**: demo links in world-a/index.md KEPT (legit related-links footer; floor edges stay live). Remaining queued phases: Phase 2 (restricted collections) and standing deferred. Only open item on this phase: human browser smoke of the world map at library.knowledge.demarkus.io. ### Phase 2 plan recorded (2026-06-14) Detailed Phase 2 plan written: [/plans/phase-2-restricted-collections.md](/demarkus-library/plans/phase-2-restricted-collections.md) — sub-world read-authz tier (collections), broker-led, library projects. Settles D1–D4 (rules in broker WorldConfig; thread read identity; per-collection locked-vs-hidden; path globs) with recommendations; broker/library work split, acceptance, build order. Important, NOT urgent — scheduled later. Authz today is world-level only (reads tokenless/open within an allowed world), so this is a real broker security change, not just a library projection. ### Phase 3 plan recorded (2026-06-14) — flagged important Detailed Phase 3 plan written: [/plans/phase-3-cataloging-desk.md](/demarkus-library/plans/phase-3-cataloging-desk.md) — the WRITE side: create/edit docs in markdown source, a projection of the broker's `mark_publish`/`mark_append` protocol. Settles D1–D5 (htmx textarea + live server preview first, CodeMirror island only on escalation; editing is a focused-pane mode NOT a trail chunk; metadata as separate form fields — no `---` fence; expected_version + merge flow; write authz coupled with Phase 2). First write path through the library (ends the read-only invariant). Library is the bulk; broker work only if write-collections (D5) are in scope. Fritz: Phase 3 is important — likely ahead of Phase 2 in priority. ### Phase 3 STARTED — edit slice live (2026-06-14) Cluster now **library 0.1.12**. Phase 3 (cataloging desk) is underway: the first write slice — **edit an existing document** — is merged (PR #22) + deployed (ArgoCD Synced/Healthy). The room is now reader+writer. Margin "edit" → htmx textarea + live server preview → publish via mark_publish (metadata out of band, expected_version conflict guard) → 303 back to the doc. Auth-gated; no broker/worlds/agent change. Plan: /plans/phase-3-cataloging-desk.md. Remaining Phase 3 slices: create-new document, mark_append affordance, conflict merge-candidate flow, write-collections authz (couples with Phase 2). Open: human browser write-smoke at library.knowledge.demarkus.io. ### Next session candidate — R4 reader mode (2026-06-14) Fritz wants a **reader mode**: a focus overlay that centers the document you want to read over the dimmed trail (context kept behind, one click/Back to return). Plan: [/plans/reader-mode.md](/demarkus-library/plans/reader-mode.md). It's a lens over the trail (NOT a new pane), URL-state (`?reader=`, ADR 0003 shareable + Back-closes + no client state), web-layer only — no port/service/ read/broker/deploy change. Settle before building: persist-on-navigate (recommend yes), Esc-to-close, margin shown/collapsible. Phase 3 (cataloging desk: edit + create + append) is live at library 0.1.14 — a clean pause point; reader mode is the next nice-to-have. Remaining Phase 3 slices (world-map "new", conflict merge, write-collections authz) still queued but lower priority. ### R4 reader mode BUILT (2026-06-15, branch feature/reader-mode) The focus overlay is built + locally verified (NOT yet merged). Web-only lens over the trail: `?reader=` renders the focused document centered over the dimmed canvas (context preserved behind), ✕/backdrop/Esc close to the bare trail. Decisions settled (Fritz, all matched recommendations): persist-on- navigate, standalone `?reader=`, ✕+backdrop+Esc (vanilla in islands.js, not _hyperscript), margin shown. Load-bearing: a valid `?reader=` also takes Focus, so the overlay reuses the focused pane's already-fetched doc (no 2nd read; ADR 0005 d9 holds). No port/service/domain/broker/deploy change. Gate green; live QUIC smoke passed. Detail: [plans/reader-mode.md](/demarkus-library/plans/reader-mode.md) SETTLED+BUILT appendix + [journal 2026-06-15](/demarkus-library/journal/2026-06-15.md). Next: Fritz + CodeRabbit → merge (library 0.1.15) → deploy bump (reader-only). ### R4 reader mode COMPLETE — deployed + lens fix (2026-06-15) Cluster now **library 0.1.16**. R4 shipped in two PRs: - **PR #25 (0.1.15)** — reader overlay built (`?reader=`); merged with a CodeRabbit DRY fix (`trailBasePath` extracted). - **PR #26 (0.1.16)** — **lens-not-navigation fix**. The original "reader implies focus" decision was wrong: opening reader on a non-focused pane re-focused it, collapsing the canvas behind. Fixed by decoupling reader from focus — the overlay reuses the *reader* pane's already-fetched doc (every pane is read in the loop; cached if unfocused), `trailReaderURL` preserves a non-default focus, close keeps it. The overlay is now a pure lens; the canvas behind never relayouts. Both reader-only deploys (no broker/worlds/agent change). ArgoCD Synced/Healthy on 0.1.16; Fritz confirmed live. Detail: [journal 2026-06-15](/demarkus-library/journal/2026-06-15.md). Remaining queued: Phase 2 (restricted collections) + Phase 3 leftover slices (world-map "new" for empty worlds, conflict merge-candidate flow, write-collections authz) + standing deferred. ### Update (2026-06-18) — Phase 3 leftover slices SHIPPED + strict golangci; releases out to v0.4.0 All merged to `main`; releases auto-cut. **Cluster currently runs library 0.3.0** (`apps/demarkus-library/applicationset.yaml` targetRevision+tag 0.3.0; broker 0.2.0, worlds 0.17.19, agent 0.12.43) — so **0.3.1 and 0.4.0 are released but NOT yet deployed** (bump the applicationset to roll them). Releases since 0.1.16 (intermediate fixes: 0.1.17 floor TTL-cache #27 · 0.1.18 no-store #28 · 0.1.19 empty-floor zero-world #29 · 0.1.20 signed-in email nav #30 · 0.1.21 markdown H1-title + ci #31/#32): - **World-map "new" affordance — SHIPPED** (PR #33, `v0.2.0`, `d2ad556`): create-doc entry from the floor for empty/unreadable worlds; degrade on unreadable catalog. Clears the queued Phase 3 "world-map new" slice. **Deployed** (cluster ≥ 0.3.0 supersedes). - **Conflict merge-candidate flow — SHIPPED** (PR #34, `v0.3.0`, `633a9ab`): cataloging-desk write path reviews the broker's `mark_publish` merged candidate (on_conflict=merge) before republish. Clears the queued Phase 3 "conflict merge-candidate flow" slice. **Deployed — current cluster (0.3.0).** - **Strict `.golangci.yml` — SHIPPED** (PR #35, `v0.3.1`, `895aac1`/`ac13ca0`): adopted demarkus's strict config (revive/gocritic/modernize/…), findings fixed. Clears the standing-deferred golangci item. **Released, not yet deployed.** Phase 3 remaining: **write-collections authz only** (couples with Phase 2). Also shipped, NOT a tracked phase — **login knowledge-graph backdrop** (PR #36, `v0.4.0`, `be8fdc0`): login.html redesigned to a B&W dark theme with a faint full-page SVG knowledge-graph backdrop (jittered-grid network, degree-sized hub nodes, center-fade opacity for text legibility; decorative, aria-hidden; ADR 0003 no-JS form behavior unchanged). Incidental visual polish — see journal 2026-06-18. **Released, not yet deployed.** **Deploy action queued:** bump `apps/demarkus-library/applicationset.yaml` targetRevision+image.tag 0.3.0 → 0.4.0 to roll golangci + login backdrop. ### Still next (unchanged) 1. **Phase 2 — restricted collections** (`/plans/phase-2-restricted-collections.md`) — couples with the last Phase 3 slice (write-collections authz). 2. **Standing deferred** — shared session store → replicaCount>1 (R2/floor/R3 caches per-pod); deploy CI papercuts (apps-immutable-check tee-masks exit; PaulHatch `feat(scope):` bumps PATCH not MINOR); publishers writing `---` metadata fences into bodies. 3. Phase 4 AI librarian (SSE pane) · Phase 5 public face. ### Decision (2026-06-18) — write-collections authz (Phase 3 D5) DROPPED Fritz: **not important.** World-level write access is acceptable — if an identity may write to a world, it may write to ANY path in that world. The coarse grant (world `AllowConfig` + the broker's per-world write token, op `["publish"]`, path globs) is the intended model, not a stopgap. Consequences: - **Phase 3 (cataloging desk) is COMPLETE.** Edit (#22) · create (#23) · append (#24) · world-map "new" (#33) · conflict-merge (#34) all shipped; D5 was the only open slice and it's now a non-goal. No further write-authz work. - The library keeps gating the edit/new affordance on writability and degrading to "not permitted" on a broker reject — that's just honoring the world-level token, no per-path tier. - **Decouples Phase 2 from Phase 3.** Phase 2 (restricted READ collections) stands on its own if/when wanted; it no longer has a write-side twin to co-design. Phase 3 plan D5 + its "coordinate with Phase 2" coupling are void. Phase 3 plan (`/plans/phase-3-cataloging-desk.md`) D5 should be read as superseded by this note. ### Phase 5 anonymous-read question SETTLED — plan recorded (2026-06-18) Plan: [/plans/phase-5-public-face.md](/demarkus-library/plans/phase-5-public-face.md) — settles the universe-library open question ("does the broker expose any anonymous-readable surface"). **Recommendation: yes, but a narrow, default-closed, operator+author opt-in `public` carve-out — NOT a relaxation of the org-scoped default.** Load-bearing finding (grounded in broker `ratelimit.go`/`mcp_tools_read.go`): **the world read layer is already identity-blind** — reads dispatch with an empty bearer (`tokens.toml` is write-only), so `requireAuth` at the broker edge is the *only* thing making org reads private. Anonymous-public is therefore a narrow fail-closed bypass for explicitly-public resources, **the inverse end of Phase 2's read-authz axis — build them on one model** or a public world × restricted subtree will leak. Decisions: D1★ tier yes/default-closed · D2 operator-eligible ∧ author-marked (`visibility:public`) · D3 one evaluator shared with Phase 2 (`public: true` rule flag) · D4 dedicated fail-closed anonymous read path (not requireAuth-bypassed gateway) · D5 follow-a-link only, NO anonymous catalog/list/worlds in v1 · D6 session-less library render + OG + bluemonday · D7 ipRateLimit + cacheable + residential worlds stay behind the broker. **Settle D1/D2 with Fritz before building.** Cross-repo (broker leads). Prefer Phase 2 first or joint. ### Direction (2026-06-18) — DEFER forward phases; focus usage + robustness + code quality Fritz: pause net-new feature phases. The reading room is feature-complete and deployed; next priorities are **(1) get real usage, (2) make the system robust, (3) raise architecture/code quality** before continuing the phase ladder. **Deferred (plans recorded, not scheduled):** Phase 2 restricted collections · Phase 5 public face / anonymous read · Phase 4 AI librarian. Write-collections authz already dropped (world-level write is fine). These resume after the robustness bar is met. **Now (robustness / quality agenda — pick up here):** - **Observability for usage** — without metrics/structured logs/error tracking there's no "usage" signal and no way to know it's robust. Likely prerequisite to everything else. - **Resilience / no SPOF** — the standing-deferred shared session store → replicaCount>1 (in-memory sessions + per-pod R2 doc cache / floor cache / R3 link graph are all single-pod today; HA needs them shared or sticky). This is the biggest reliability gap. - **Architecture + code-quality audit** — verify hexagonal adherence (ADR 0002), ADR conformance, error-handling/degradation paths, test coverage gaps, the deploy CI papercuts (apps-immutable-check tee-masks exit; PaulHatch `feat(scope):` bumps PATCH not MINOR). Starting point under discussion: an architecture + code-quality audit to set the bar, paired with observability so usage is measurable. ### Architecture + code-quality audit DONE (2026-06-18) Full report: [/demarkus-library/audit-2026-06-18.md](/demarkus-library/audit-2026-06-18.md) (5 parallel agents: architecture, concurrency, error-handling, security, tests/CI). **Verdict: strong foundation, clear hardening gap.** Hexagon clean (core imports zero adapters — verified), `go vet`/`go test -race` clean, XSS closed, OAuth/PKCE/ session correct, `.mcp.json` git-ignored (NOT a committed secret — earlier worry void), coverage high (service 93 / web 90 / markdown 92.6 / broker 87.3%). Work is **production-hardening + HA**, not rearchitecting. Robustness backlog (detail in the audit), do in order: - **Tier 1 hardening (small, first):** CSRF on POST routes (**CRITICAL** — none today, only SameSite=Lax) · HTTP server timeouts + ctx into QUIC fetch · body-size limit on writes · post-turnstile `ErrUnauthorized`→re-login (dead-ends on a 401 today). - **Tier 2 leaks/staleness:** inflight lock leak (`manager.go:139`) · worldMapCache has no TTL/eviction (永 stale) · cache invalidation on write · linkGraph unbounded · broker errors matched by substring (typed status instead) + tests. - **Tier 3 arch (optional):** split `ReadingService` god-interface (port.go) into Reader/Graph/Map/Editor · move listing-vs-doc rule into a core `Open` dispatch. - **Tier 4 HA (big, when scaling >1 replica):** shared session + pending store (Store iface is the seam) · shared linkGraph · cross-pod cache invalidation. - **Quick-win tests:** mapWriteError branches · Append tool-error · config fail-fast. **Standing-deferred CI papercuts RESOLVED/MOOT:** the `apps-immutable-check` tee-mask is NOT in this repo (likely the broker repo); the PaulHatch `feat(scope):` semver bug is already fixed (`release.yml:73` anchored regex). Removed from the library's deferred list. Recommended start: **Tier 1** — small, high-value, directly serves "robust enough for real usage." CSRF first. ### Tier 1 hardening BUILT (2026-06-18) — branch `feature/tier1-hardening` All four Tier-1 items done, gate-green (0 lint, build, all tests + 6 new), NOT merged (Fritz commits). Detail: [journal 2026-06-18](/demarkus-library/journal/2026-06-18.md). - **CSRF** (the CRITICAL gap) — Echo v5 CSRF (Sec-Fetch-Site + token fallback), `web.CSRFMiddleware`, `{{ csrf }}` renderer injection, tokens in all forms + htmx preview header. Tested. - **HTTP timeouts** — ReadHeaderTimeout/IdleTimeout via BeforeServeFunc (both plain + TLS), ContextTimeout 30s handler bound. WriteTimeout left unset (future SSE). - **Body limit** — 2 MiB on write POSTs. - **Post-turnstile re-login** — presentError ErrUnauthorized clears the session + re-logins (HX-Redirect for htmx); QUIC mode keeps plain 401. Tested. Deferred follow-up: thread ctx into the QUIC fetch adapter (`world.go` ignores it) — ContextTimeout bounds the response but won't abort the QUIC goroutine. Next: Tier 2 (leaks/staleness — inflight lock leak, worldMapCache TTL, cache invalidation on write, linkGraph cap, typed broker errors) then Tier 3 / Tier 4. ### Tier 2 hardening BUILT (2026-06-18) — branch `feature/tier2-hardening` Leaks/staleness/eviction + the quick-win tests. Gate-green, race-clean, NOT merged. Branched off main (independent of the Tier 1 PR #37; no file overlap). - **inflight lock leak** — `manager.go` dead-grant path now `dropLock`s (was leaking a mutex per revoked-token session). Test asserts no leak. - **worldMapCache TTL + invalidate** — per-world timestamped entries; mirror `floorTTL`; `WorldMapCached` rebuilds when stale (was serving the first-ever assembly forever). - **cache invalidation on write** — `Publish`/`Append` now `invalidateTopology` (floor + world map) so an edit shows at once, not at TTL; a merge candidate (nothing written) leaves caches intact. - **linkGraph bounded** — `maxLinkGraphSources` 5000 with LRU-by-last-observed eviction (was unbounded for a long-lived pod). - **broker error tests** — `mapWriteError`/`mapToolError` branch coverage + Append tool-error path (the substring-brittleness the audit flagged). NOTE: the typed-status broker change stays a cross-repo follow-up; library-side this is test coverage, not a behavior change. - **config fail-fast tests** — `getEnvAsBoolStrict`/`getEnvAsDuration` error paths + NewAppConfig rejects a bad strict switch (was 0% covered). Remaining: Tier 3 (split `ReadingService` god-interface; core `Open` dispatch for listing-vs-doc) — optional; Tier 4 (HA: shared session/pending store, shared linkGraph, cross-pod invalidation) — the big one, when scaling >1 replica. ### Tier 1 + Tier 2 MERGED; Tier 3 BUILT (2026-06-18) - **PR #37 MERGED** — Tier 1 (CSRF, timeouts, body limit, re-login) + Tier 2 (inflight leak, worldMap TTL, write-invalidation, linkGraph bound, broker/config tests) + the CodeRabbit fixes, all folded into one PR, now on `main`. - **Tier 3 BUILT** — branch `feature/tier3-interface-split`, gate-green, NOT merged. The two architecture refinements: - **Split the `ReadingService` god-interface** (port.go) into `Reader` / `GraphService` / `MapService` / `Editor`, composed as `ReadingService`. The concrete `*service.ReadingService` satisfies all four; a narrower consumer (preview-only handler, Phase 4 librarian) can depend on one slice. Pure organization — no behavior change; the web handler still uses the composite. - **Core `Open`/`OpenCached`** own the listing-vs-document read dispatch; the rule itself is `domain.IsListingPath` (single definition). `Doc` + `readPane` call Open/OpenCached instead of re-deciding by trailing slash; the margin/edge-source presentation checks route through `domain.IsListingPath` too, so the rule lives in exactly one place. Robustness push status: **Tiers 1–3 done** (T1/T2 merged, T3 in review). Only **Tier 4 (HA / replicaCount>1)** remains — the big one: shared session + pending store (the `Store` interface is the seam), shared linkGraph, cross-pod cache invalidation. Do it when actually scaling past one replica; single-replica + Recreate is correct until then. ### Deployed — library 0.5.1 LIVE (2026-06-19) Cluster bumped 0.3.0 → **0.5.1** (deploy repo `apps/demarkus-library/applicationset.yaml` targetRevision + image.tag, branch `bump/demarkus-library-0.5.1`, merged). ArgoCD verified: app Synced, `revision=0.5.1`, `operationState phase=Succeeded` ("all tasks run"); pod `...c5cfcb554-gxg74` running image `:0.5.1`, ready. Single replica (Recreate), as intended pre-HA. Now live: golangci (0.3.1) · login knowledge-graph backdrop (0.4.0) · **Tier 1+2 robustness hardening (0.5.0)** · **Tier 3 interface split + Open dispatch (0.5.1)**. Reader-only deploy — broker 0.2.0 / worlds 0.17.19 / agent 0.12.43 unchanged. Robustness push complete + deployed (Tiers 1–3). Tier 4 (HA) still deferred until >1 replica is needed. Next per direction: get usage → observability. ### Deployed — library 0.5.2 LIVE (2026-06-19) Cluster 0.5.1 → **0.5.2** (sign-in page restyled to the reading room's Tufte palette — paper+ink, Charter serif, light-dark() adaptive; knowledge-graph backdrop recolored to faint ink; CSS only). ArgoCD verified: Synced, rev 0.5.2, operationState Succeeded, Healthy; pod `...78c68d5c7f-cl2gn` on image `:0.5.2`, ready. Reader-only (no broker/worlds/agent change), single replica. Gotcha: after merging the deploy bump, the ApplicationSet git-generator hadn't regenerated the Application yet (live target still 0.5.1) — a hard refresh (`kubectl annotate ... argocd.argoproj.io/refresh=hard`) made it pick up 0.5.2 immediately instead of waiting for the ~3min appset requeue.