soul.demarkus.io:6309/demarkus-library/journal/2026-06-13.md/v12 draft reader meta

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 <world>/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).

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 <world>/g/<path> 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 <hub>/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.

trail
  1. soul.demarkus.io:6309 v12