2026-07-07
Version retention rollout — deploy repo merged
Closed the retention plan's rollout leg. Chain of merges today/yesterday: monorepo #236 (store core) → #237 (gate binary) → #238 (auto pin bump) → #239 (guidance + tools 0.8.1 repin) → #240 (agent publishes /graph.md and hash indexes with retention=20 — the gap found during rollout prep: the cluster's graph publisher is the crawl agent, not the MCP tools, so #236 alone would never have cleared the backlog). Deploy repo PR bumps worlds to server 0.20.0 (retention core), broker to 0.9.0 (retention tool surface; broker versions now track tools releases), agent to 0.19.0 (agent versions now track client releases — jumped from 0.13.1).
Also: em dashes removed from all retention user-facing strings and the rule recorded in /conventions.md (no em dashes in CLI help, errors, prompts, tool descriptions). CodeRabbit round on #240 fixed -publish-retention silently swallowing values below -1 (now fails loudly).
Verification baseline before the roll: mark://root/graph.md at 554 versions (agent publishes hourly at :53), chain-valid: true. Expected after ArgoCD syncs and the agent pod rolls: the first hourly publish prunes 534 versions in one write (the designed unbounded backlog prune), VERSIONS shows 21 (newest 20 + the new one), the root world logs a msg=prune audit line with pruned_from=1, and chain-valid stays true for the retained suffix. Check with mark_versions on the knowledge system.
Prune confirmed in production — retention plan complete
Verified end-to-end on knowledge.demarkus.io after the deploy PR merged. The
race on the first attempt was instructive: the agent Deployment rolled faster
than the root StatefulSet, so the agent's startup crawl published v555 with
retention=20 to the OLD server 27 seconds before root-0 came up on 0.20.0
(old servers store the unknown key as inert metadata — graceful). A manual
kubectl rollout restart deploy/demarkus-agent triggered the crawl-at-startup
path against the rolled world: /graph.md went 556 → 20 versions
(pruned_from=1 pruned_to=536, token_label=admin, chain-valid true), and the
hash indexes pruned in the same cycle (root index 531, soul index 528,
servicing/origination 83 each) — ~1,714 version files deleted across five
documents, every deletion audit-logged. Note retention keeps the newest N
INCLUDING the just-written version (total 20, not 21). Agent scheduling for
the record: single-replica Deployment running demarkus-agent daemon,
crawl at startup + hourly ticker phase-locked to pod start, config rendered
from deployment.yaml worlds[].
Session wrap-up — version retention, question to production in one session
Fritz opened with "there are 545 versions of the graph, can we keep N?" and the session closed with the prune verified in production. The arc: feasibility analysis (store layout makes contiguous-prefix pruning chain-safe) → plan doc → accidental-set guard design (tool-layer confirmation, two-write rule deferred) → implementation → five PRs (#236 store core, #237 gate binary, #238/#239 pin/guidance chain, #240 agent) → deploy rollout → kubectl-verified prune (556 → 20 on /graph.md, ~1,714 files across five docs).
Durable takeaways beyond the per-day entries:
- Two real bugs were found by building, not reviewing: migrateFlatFile's v1-existence assumption (deletion resurrects a bogus v1) and the agent being the actual graph publisher (the MCP-tool retention default alone would never have cleared the backlog). Both are the same lesson — trace the real producer/consumer, not the surface you happen to be editing. Details in /debugging.md.
- Security-on-delete shape that held up through review: no new deletion verb, prune rides existing publish capability, os.Root confines every removal, every deletion audit-logged with token label. Reusable for any future destructive store op.
- Review-round judgment calls worth remembering: CodeRabbit's int-case test suggestion would have codified a silent-allow hole (fixed the code instead); its client/internal dedup suggestion was right about the problem, wrong about the home (protocol/store owns the server's rule; tools can't import client internals).
- New conventions recorded: no em dashes in user-facing strings (/conventions.md); retention is never set on authored docs without an explicit user ask (plugin session guidance, all four plugins).
- Confirmed there is no 1000-version limit anywhere in core — the 1000s are LIST entries, LOOKUP results, mark_index docs, and the agent's crawl cap.
Documentation style guide published to the knowledge system
New org convention doc at mark://root/.well-known/demarkus/style.md (v1, type Guide, category:governance): audience and assumed-knowledge baseline upfront, plain language and active voice, one-sentence summary under the H1, progressive disclosure, task-oriented runbook shape (prerequisites, numbered steps, troubleshooting), show-not-narrate with text-only diagrams (server is markdown-only), redundancy rules, the metadata-channel mechanics, and the no-em-dash rule. policy.md v6 gained a Writing standards section pointing at it (enforced core untouched, policy_version still 3). The style doc itself contains zero em dashes. Candidates for later gate enforcement from it: body opens with a frontmatter fence, missing H1, em dashes in body.
Documentation style gate built (tier 1 of KS best-practice enforcement)
New style guard in the shared demarkus-plugin gate binary (internal/gate/style.go), firing on publish for both soul and knowledge surfaces: body opening with a YAML frontmatter fence (the June footgun, finally a write-time check), missing # H1 (index.md/log.md exempt, matching the policy's type exemption), em dashes anywhere in the body, and duplicate headings (headings are #section anchors; duplicates take shifting -1 suffixes). Heading parsing reuses client/mdoutline.Headings, the same implementation the anchors come from, so the check can never drift from the slicer; code-fence # lines are excluded for free. Default severity warn (style steers, it does not wall), DEMARKUS_STYLE_STRICTNESS overrides, and warns ride the PostToolUse additionalContext channel (verified live through the built binary: one payload tripped all four checks with teaching reasons). Appends are exempt: fragments have no H1 and a fence heuristic would misfire. evalKnowledge refactored (knowledgeTagDecision extracted) after gocyclo flagged it. knowledge-doctor (both plugin ports) gained a style-guide deep check for the pre-existing corpus; session guidance in all four plugins points at style.md. Versions: memory 0.12.9, knowledge 0.5.9, pi 0.12.10 and 0.5.10. Tier 2 (broker-side enforcement for non-plugin clients) and the scheduled doctor sweep remain open.
Pin chain converged; pin-bump release loop found and fixed
Shipping the style gate through the pin chain surfaced a perpetual loop in the automation: the bump script edited fallbackToolsVersion (dev-only, in tools/) every run and used it as the currency signal, so each merged bump PR cut a junk tools release that reopened the PR. Fixed in #244 (currency signal is now the bootstrap TOOLS_VERSION, sampled across all four bootstraps with lowest-wins self-healing; fallback rides along only with real provision changes), #243 closed as superseded, workflow re-dispatched, regenerated PR #245 merged. Verified converged: Release ran on the merge with no new tags, no new auto PR, cron will read pins as current. Full lesson in /debugging.md (self-referential automation). Also learned: bot-pushed bump branches need a manual CI approve or admin merge; the fork-PR approve API does not apply. End state for plugin users: demarkus-plugin 0.10.1 (retention ask-gate + style gate), server 0.20.0, client 0.19.0 on next session start.