2026-07-06
Resources + prompts propagated to plugin users — chain closed (#234/#235)
Leg 1 (#234, chore(mcp): repin client 0.17.0) → tools/v0.6.1; leg 2
(#235) repinned bootstraps to 0.6.1 (plugins: memory 0.12.6, knowledge
0.5.6, pi-memory 0.12.7, pi-knowledge 0.5.7). Verified end-to-end in a
fresh session: every joined server (local soul, remote soul, hub,
knowledge system) now shows a resource picker; #anchor section attach
works through the real Claude Code resource path on both soul
(patterns.md#loop-idiom) and knowledge (root/index.md#worlds); prompts
registered on all demarkus-mcp surfaces.
Found along the way: the user's installed plugin cache was at 0.12.3-era pins (tools 0.3.5 → demarkus-mcp 0.13.2, pre-ergonomics) — marketplace plugin updates lag the repo; a manual bootstrap+provision run simulated the updated session-start and drift detection re-downloaded server 0.19.0 / client 0.17.0 / tools 0.6.1 cleanly.
Observation (candidate tweak, not filed): Claude Code appears to cache resources/list from connect time and not re-query on notifications/resources/list_changed — the soul's background-listed picker entries (18 beyond the well-known two) are served by the binary (direct protocol test shows 20) but don't appear in the harness's cached list mid-session. Desktop-style clients that re-query on picker open see them. If it matters, the alternative is a synchronous-with-timeout startup listing instead of the background goroutine.
Broker 0.5.0 regression on the library — diagnosed and fixed
Fritz reported the soul no longer showing as federated on the reading
room, then already-opened documents presenting "unreachable". Root cause:
the broker's new fetch ergonomics applied to the library's broker-mode
reads (one pooled MCP session per reader, everything through mark_fetch):
graph.md (74KB) came back as an outline (floor lost federation edges) and
re-fetches came back status: unchanged (parser default branch →
unreachable). Fix on demarkus-library fix/gateway-force-fetch: gateway
Fetch passes force=true (bypasses both by design) + regression tests
pinning the force arg and the unchanged-status rejection. Full lesson in
/debugging.md (consumer census + degradation-masks-regressions).
Deploy: library release + deploy-repo library pin bump after merge.
Regression closed — library 0.18.1 deployed, user-confirmed
Fritz confirmed from the reading room: soul portal back on the floor,
already-opened docs open fine. The broker-ergonomics regression on the
library (outline mode + session dedup vs the renderer's parser) is fully
closed: fix merged (library 9043b60, v0.18.1), deployed (5224ecc),
lesson in /debugging.md (consumer census; degradation masks regressions).
The librarian's mdoutline open also reached production in the same roll.
Open offer not yet taken: document the "programmatic gateway consumers
must pass force=true" contract in the repo docs + root hub.
journal.md "not found" in the library — stale server, not a regression
Fritz hit "not found" clicking soul.demarkus.io's /journal.md in the reading room. Diagnosis: the doc is ARCHIVED (old single-file journal, superseded by /journal/ per-day files); the library correctly maps archived → not-found. The bug is that it was LISTED at all: soul.demarkus.io runs a pre-0.19.0 server, so #224's archived-hidden-from-LIST isn't active there — old server lists the archived doc, everything downstream (stacks, crawler, resource listing) surfaces a dead entry. Fritz is upgrading the server. Diagnostic tell for next time: an archived doc visible in LIST = server < 0.19.0.
Version retention implemented — branch feat/version-retention
Planned and built the keep-last-N feature (plan)
in one session, prompted by the knowledge system's graph.md at 545 versions.
Option C: retention: N publisher metadata key, prune-on-write in
protocol/store (contiguous oldest-prefix deletion, abort-on-first-error so
VerifyChain never sees a gap), PruneResult surfaced on Document, handler
audit-logs every prune with token_label (security-on-delete: no new deletion
surface, rides publish capability, always attributable). mark_graph_publish
sets retention (default 20, retention tool param, 0 disables) on both MCP
surfaces; mark_publish descriptions carry the destructive-operation warning;
CLI demarkus -X PUBLISH -meta retention=N prompts for confirmation (-yes
skips, non-TTY without -yes fails). SPEC §9.9 + §9.1/§9.6 amendments.
Bug found by the new tests: migrateFlatFile detected "flat file" by
checking only whether v1 exists. After pruning v1, the next write
misclassified the versioned doc as flat and resurrected a bogus v1 from the
current symlink's raw bytes — double frontmatter, broken hash chain. Fixed to
check for any per-doc version history. Anything that deletes old versions had
to clear this hidden v1-existence assumption; worth remembering when touching
version lifecycle again.
Verified end-to-end against a live server: 5 publishes → retention=2 publish
pruned to {v5,v6}, audit log msg=prune pruned_from=1 pruned_to=4 token_label=smoke, pruned v1 fetch → not-found, VERSIONS lists the remainder,
non-TTY CLI without -yes refused. All module tests + pre-commit green.
Plugin retention ask-gate is PR 2, not started. Fritz commits.
Prune delete path hardened against traversal/symlink escape
Follow-up on the retention branch: Fritz asked for a no-accidental-leak
guarantee on the delete. Protocol-level traversal was already blocked
upstream (resolve rejects .. + symlink escapes before any write;
version filenames are rebuilt as v%d from ReadDir-parsed integers,
never from input), but the raw os.Remove would have followed a planted
or raced symlink at versions/<doc> and deleted outside the store root
(local-disk-access attacker, or confused-deputy reuse of the store API).
Fix: every prune removal now goes through os.Root anchored at the store
root (Go 1.24+ os.OpenRoot) — paths resolve inside the root at delete
time, so symlink escapes and mid-prune directory swaps error out instead
of deleting outside, and a version file that is itself a symlink is
unlinked, never followed. Plus an explicit doc-dir containment check
before any deletion. Three new escape tests (symlinked doc dir, symlinked
version file, doc dir outside root); SPEC §9.9 now mandates
delete-time confinement. Pattern worth reusing: os.Root is the right
primitive for any future destructive filesystem op in the store.
Version retention MERGED — PR #236 (db9beae)
Plan → implementation → review → merge in one day. Three CodeRabbit rounds,
all findings addressed: verb-gated CLI confirmation, no prompt for
server-rejectable values, store.ParseRetention shared predicate (rejected
the bot's client/internal suggestion — tools module can't import client
internals; the rule is the server's, so protocol/store owns it), APPEND
retention tests (note: APPEND authorizes under the "publish" op), SPEC §9.6
now roots verification at the oldest retained version. Next: PR 2 plugin
ask-gate; rollout via release train clears the 545-version graph doc.