# 2026-06-13 — floor live + trail root-listing fix ## Floor in production R1+R2+floor rolled to the cluster (server 0.17.18, broker 0.1.36, library 0.1.5). The worlds bump needed a one-time orphan-delete recreate (root + world-a) — caused by the chart stamping churning version labels on the immutable volumeClaimTemplates; fixed at the chart layer (#184: VCT uses selectorLabels), so future server bumps are clean. Full rollout detail in journal 2026-06-12. ## Trail root-listing bug (library PR #16 → v0.1.6) Fritz hit `{"message":"malformed trail"}` on `/t/u/~/world-a/d/` — the floor's world-node link (opens a world's stacks = root listing, path "/"). Root cause: the trail parser rejected any pane chunk with an empty value (`if !ok || value == ""`), but the world-root listing legitimately encodes as `/d/` (doc-kind, empty value → "/"). Predated the floor — every root-listing link was broken since R2; the round-trip test only covered a non-empty subdir (/plans/) and missed root. Three CodeRabbit findings, all real, fixed in sequence: 1. Root-listing rejected — drop blanket `value==""`, handle per kind (doc empty → "/", tag still non-empty). 2. **Two parsers diverged** — `parsePaneChunk` (/t/ chunks) and `paneAddrFromRoute` (/w/ route hrefs in the trailize pass) both had the bug; fixed one, not the other. Extracted the shared semantics into `paneAddrFromParts(world, kind, value)` — single source of truth; the two wrappers keep only their input-specific framing (chunk-split+floor vs URL-parse+fragment). Divergence now structurally impossible. 3. Tag slash check ran on the RAW value, so `tags/foo%2Fbar` slipped through then unescaped to `foo/bar`. Moved empty/slash checks AFTER PathUnescape (in the shared helper → fixed for both decoders at once). Pattern worth keeping: two parsers for the same logical address = a divergence bug waiting to happen. Share the semantics, not the syntax framing. Deploy bump library 0.1.5 → 0.1.6 staged (deploy `fix/library-0.1.6`; clean Deployment roll, no recreate). ## R3 built — previews + backlinks + graph pane (branch feature/reading-room-r3) The last planned Reading Room phase, built and verified live (QUIC against soul.demarkus.io). Not committed — awaiting Fritz + CodeRabbit per PR. **Load-bearing decision (recorded in plans/reading-room.md §"R3 build decisions"): edge source = render-time observed-links map.** rewriteLinks already resolves every link to a (world,path); capturing those edges is free and transport-symmetric (library-internal, no broker store → decision 16 holds). Rejected: broker graph store (broker-only), library-side crawl (over-built). Honest limit: only edges among docs rendered this pod's lifetime; hub-sourced topology (addendum 2) is the durable upgrade layered in front later. Backlinks/graph fill in as the room is read. **Architecture:** `domain.Ref{World,Path}` + `domain.Neighborhood`. Edge store is an internal `service.linkGraph` (forward+reverse maps under a mutex), mirroring floorCache — no new port/adapter/wiring (NewReadingService signature unchanged). Inbound port gains `RecordLinks` / `Backlinks` / `Neighborhood`. `rewriteLinks` now returns `(html, []Ref)`; `rewriteHref` returns `(route, Ref, isDoc)`. Web records edges after rendering doc panes (resolution stays in web, store in core). **Pieces:** - Hover previews: `previewize` wraps in-app doc anchors with `preview-host`/`preview-card` + htmx (`mouseenter delay:300ms once` → `/w/:world/preview/*` fragment). Pure htmx+CSS, no JS (ADR 0003). One component serves body links AND backlink entries. Snippet = first paragraph, served from ReadCached (budget-safe). mouseenter overrides click trigger so hx-boost nav still works. - Backlinks margin block ("referenced by") in shared doc-meta — trail URLs on the canvas, /w/ permalinks on the single-doc view. Empty ⇒ absent. - Graph pane: new kind `/g/` via paneAddrFromParts (shared, not duplicated). Route `/w/:world/g/*`. SSR SVG (graph.go) — center + backlinks (left, blue) + outbound (right, green), each node a plain continuing the trail. Canvas island stays unspent. Margin "graph" affordance opens it. **Docs (decision 14):** docs/trail-format.md + in-universe mark://soul.demarkus.io/.well-known/library/trails.md (v3) both updated with the `g` kind — kept in sync. **Verified live (QUIC):** index.md → 43 wrapped body links; preview fragment renders title/status/snippet/mark; reading index.md recorded index→architecture so architecture.md shows index.md under "referenced by" (trail-linked); graph pane SVG renders, nodes continue the trail; /w/g permalink uses /w/ routes. `go vet` + full suite green; gofmt clean. **Open / observed:** broker-mode acceptance is architecturally guaranteed (edge source is library-internal, no broker-only path) but not run here (needs interactive OAuth via dev.env) — Fritz to smoke. Visual: a hub doc with ~40 outbound links crowds the graph's right arc (aggregation/cap is future polish, same family as the floor's satellite crowding note). Suggested PR slices: (A) edge source + backlinks, (B) previews, (C) graph pane + docs. ### R3 merged (PR #17, v0.1.7) Merged to main, `v0.1.7` auto-cut, branch deleted. CodeRabbit comments addressed pre-merge: (1) `pre-commit.sh` now pins golangci-lint to CI's **v2.9.0** (hook only checked existence — a mismatched local binary could pass while CI fails); (2) clarified the `//nolint:gosec` wording on the canvas content; (3) fixed staticcheck QF1002 in graph.go (tagged `switch i`). Gate (`./pre-commit.sh`) green. Reading Room is code-complete on main. **Cluster still at library 0.1.6** — R3 deploy (ArgoCD bump to v0.1.7) is the next session's first task. `pre-commit.sh` is the standing phase/task gate. ### R3 deployed to the cluster (v0.1.7) Deploy bump merged (`demarkus-knowledge-system-deploy`, `apps/demarkus-library/applicationset.yaml`: chart `targetRevision` + image `tag` 0.1.6 → 0.1.7). Verified v0.1.7 artifacts on ghcr before sync (image + chart manifests both 200). ArgoCD: **Synced / Healthy, rev 0.1.7**; pod running `ghcr.io/latebit-io/demarkus-library:0.1.7`. Broker/worlds unchanged (already ≥ 0.1.36 / 0.17.18). Reading Room R1→R3 now fully live in prod. Broker-mode R3 smoke still pending (interactive OAuth) — parity is architectural (edge source is library-internal). ### Floor enrichment PR1 merged (v0.1.8) Library hub-topology reader merged to main, `v0.1.8` auto-cut, branch deleted. Floor now reads `/graph.md` (mark_graph_export) ∪ R3 observed map, masked by mark_worlds, rendering world-level edges + portal nodes; reader-only, degrades to baseline when no hub. `DEMARKUS_HUB` config (default self in quic). NOT yet deployed (cluster still 0.1.7) and NOT yet set in the cluster — edges stay observed-only until (a) `DEMARKUS_HUB=root` is set in deployment.yaml and (b) the federation agent publishes a graph export to the hub. **Next phase (Fritz approved): agent publish-graph** — demarkus repo. Extend `demarkus-agent` (`client/cmd/demarkus-agent` + `client/internal/fedcrawl`, which today publishes hash indexes only via `PublishToHubs`) to also build the link graph (`client/graph/crawl.go` exists) and publish a graph export to each hub at `/graph.md`, behind a `--publish-graph` flag. Then deploy: bump agent image + flip the flag, and set `DEMARKUS_HUB=root` + bump library to 0.1.8. ### Agent publish-graph BUILT (demarkus repo, branch feature/agent-publish-graph) The producer side of floor edges. demarkus repo, NOT committed. `client/internal/fedcrawl/crawl.go`: the federation crawler now accumulates a `graph.Graph` during its existing walk (`recordEdges` — every doc's outbound mark:// links become edges; external links skipped), and exposes `GraphExport` (reuses `graphstore.New().Merge().Export()` → the exact mark_graph_export format the library parses) + `PublishGraphToHubs` (publishes one aggregated `/graph.md` per hub — cross-server edges are the point). `client/cmd/ demarkus-agent/main.go`: `--publish-graph` flag on crawl + daemon. Test `TestCrawlerGraphExportAndPublish` (cross-server edge + /graph.md publish). Client module: build/vet/golangci-lint(v?)/test all green. **Deploy to light it all up (3 repos, in order):** 1. demarkus: merge agent change → new demarkus-agent chart/image version. 2. deploy: bump `apps/demarkus-agent` image+chart; add `--publish-graph` to the agent args (it already runs `--publish --per-server`); the agent's root token already has `/**` write, so `/graph.md` publishes without new authz. 3. deploy: set `DEMARKUS_HUB=root` in the library values + bump library to 0.1.8 (the PR1 reader). Then the floor shows durable cross-world edges + portals. Until then library degrades to observed-map edges. Known follow-up: host-form join — graph edges use whatever host form the link author wrote (often portless, or KS name) vs mark_worlds URLs; worldEdges degrades unmatched endpoints to portals. Tune host normalization when wiring the cluster (library `service/hub.go` + `floor.go` host2name). ### Gap: agent chart didn't render --publish-graph (follow-up demarkus PR) PR #185 added the binary flag but NOT the helm chart's arg rendering, so a deploy `publishGraph: true` would do nothing. Fixed on branch feature/agent-chart-publish-graph: `deploy/helm/demarkus-agent/templates/ deployment.yaml` renders `--publish-graph` when `.Values.publishGraph`; `values.yaml` adds `publishGraph: false`; 2 unittest cases added. helm lint + helm unittest green (23 tests). Chart version is set by the release pipeline (source Chart.yaml is stale 0.1.0), so no manual bump. Remaining to light up floor edges (in order): 1. ✓ client 0.12.42 — binary flag (#185). 2. this chart PR → merge → new demarkus-agent chart version (verify on ghcr). 3. deploy repo: bump `apps/demarkus-agent` image+chart to the new version + set `publishGraph: true`; set `DEMARKUS_HUB=root` in library values + bump library image/chart to 0.1.8. Then ArgoCD syncs → agent publishes /graph.md → floor renders durable cross-world edges + portals. ### Floor-edges deployed; blocked by pre-existing agent→root token mismatch Deploy PR merged + synced: library 0.1.9 (DEMARKUS_HUB=root, verified in pod env), agent 0.12.43 (--publish-graph in args, verified). Both ArgoCD Synced/Healthy. BUT the agent's hub publishes return `unauthorized` — both the hash index and /graph.md. Root cause (full diagnosis in debugging.md): the agent's raw token (root-token-values → ESO) ≠ the token root enforces (hash in root-tokens). sha256(agent token) != root stored hash, no whitespace variant matches. Pre-existing since install (06-01); the hash index had been failing silently. Fix is operational secret reconciliation (sensitive — left for Fritz). Floor degrades to observed-map edges meanwhile (working, sparse). Floor-enrichment code/deploy is COMPLETE; only the token blocks durable edges. ### Token-ownership fix shipped + floor edges live (2026-06-14) The durable fix landed across all three repos and the floor-edges loop is closed: - demarkus: server chart 0.17.19 — token Secrets are now created by a pre-install/pre-upgrade **bootstrap Job** (own SA+Role), atomic+consistent, hands off to the broker; chart no longer templates them (PR #187). Agent publish-graph (client 0.12.42) + chart flag (0.12.43) already shipped. - deploy: worlds → 0.17.19. - **Rollout incident + recovery** (full detail in debugging.md): the 0.17.18→ .19 sync PRUNED the existing token Secrets (ArgoCD tracked them; it does NOT honor helm `resource-policy: keep`), downing both world pods. Recovered by manually creating consistent token pairs per world (untracked → prune-safe), restarting broker+agent, ESO refresh. One-time tracked→untracked transition cost; fresh installs are clean. - Verified: root+world-a Synced/Healthy 0.17.19; agent logs `publish: complete` + `publish-graph: complete`; library 0.1.9 DEMARKUS_HUB=root. The agent now publishes root/graph.md hourly → library reads it → floor renders durable cross-world edges + portal nodes. Floor enrichment (PR1 reader + agent producer + token fix) is COMPLETE. Remaining: human browser smoke of the floor edges at library.knowledge. demarkus.io /t/u (broker mode, behind login). ### Host↔name join fixed + deployed — floor enrichment COMPLETE (2026-06-14) The last gap is closed. broker 0.1.37 (#188) adds an `address` column to mark_worlds = each world's internal dial host (resolveWorldAddress); library 0.1.10 reads it (WorldInfo.Address; broker parser tolerates the legacy 2-col table) and builds the floor's host→name map from Address (falls back to URL for older brokers / QUIC). Deployed: broker 0.1.37 + library 0.1.10 Synced/Healthy. Verified live: `mark_worlds` returns `| world-a | | mark://world-a.world-a.svc.cluster.local:6309 |` (+ root) — the addresses match graph.md's node hosts exactly, so the cross-world edge `world-a…/index.md → root/index.md` now joins to world-a→root (a cluster line, not a crawl-host portal); `world-a → soul.demarkus.io` stays a portal (correctly external). Floor enrichment (PR1 reader → agent producer → token ownership fix → host↔name join) is fully shipped, deployed, and verified. Remaining: human browser confirm at library.knowledge.demarkus.io /t/u that the world-a↔root line + soul portal render. Plan §"Floor edges — live demo" gap is resolved. ## Related documents - [Reading Room plan](/demarkus-library/plans/reading-room.md): R3 build decisions and floor edges recorded there - [ADR 0005 reading room spatial trail](/demarkus-library/adr/0005-reading-room-spatial-trail.md): decisions 14 and 16 governing R3 and the floor - [demarkus-library debugging](/demarkus-library/debugging.md): agent token mismatch and prune incident diagnosed there - [Library trail format](/.well-known/library/trails.md): trails spec v3 with the graph pane kind - [Previous day journal](/demarkus-library/journal/2026-06-12.md): full floor rollout detail - [Universe Library plan](/plans/universe-library.md): parent plan for the library sub-project