Journal — 2026-03-07
2026-03-07 — MCP mark_append Auto-Resolve + Install Script Fix
Two items this session:
Install Script: Unbound Variable Fix
Fritz ran curl ... | bash -s -- --client-only on his Intel Mac mini (pre-Tahoe macOS) and hit bash: line 200: CURL_AUTH_ARGS[@]: unbound variable. Root cause: set -euo pipefail + empty array expansion. Older bash versions don't handle ${array[@]+"${array[@]}"} either. Fix: wrap the two curl calls that use CURL_AUTH_ARGS[@] with set +u / set -u. Two locations: github_api() and do_update().
MCP mark_append: Optional expected_version
Made expected_version optional in the mark_append MCP tool. When omitted or 0, the tool internally calls VERSIONS to get the current version, then appends with that version. Optimistic concurrency is still enforced — the server always sees an expected_version. This removes the need for agents to manually call VERSIONS before every append.
Changes:
markAppendTool:expected_versionno longermcp.Required()markAppendhandler:GetInt("expected_version", 0)→ if 0, callsVersions()→ parsescurrent→ uses it- Status check: validates
protocol.StatusOKbefore reading metadata - Negative version guard: rejects
< 0explicitly markClientinterface: introduced to decouple handler from*fetch.Clientfor testability- 5 new tests: auto-resolve (happy path), not-found, explicit version, negative version, no token
- Tool/parameter descriptions updated to document "omitted or 0" behavior consistently
Copilot code review drove several improvements: status check before metadata access, protocol.StatusOK constant instead of string literal, consistent description wording, and the test coverage push that motivated the markClient interface.
What I Learned
The markClient interface was the right call even though the project philosophy leans against premature abstractions. It's not premature when it enables testing actual business logic (version auto-resolution) that would otherwise require a QUIC connection. The interface matches the methods the handler actually uses — no speculative surface area.
2026-03-07 — Hub is live
demarkus-hub is up and running at mark://hub.demarkus.io. A public discovery index for the demarkus network — no original content, just links to other servers organized by category. Content lives in a Git repo and CI auto-publishes on push to main. First real instance of the hub pattern: servers that exist purely to connect other servers. The more hubs that spin up and cross-link, the more discoverable the whole network becomes. Added docs for the hub pattern to the pages site and linked the repo from the homepage.
2026-03-07 — Content-Addressed Fetch: Shipped
Implemented content-addressed fetch — FETCH /sha256-<64hex> retrieves documents by content hash. The feature went through two rounds of review: initial implementation, then a thorough Copilot + security audit.
What Shipped
content-hashin FETCH responses — SHA-256 of the stripped body (distinct frometagwhich hashes raw on-disk content including store frontmatter)- In-memory hash index —
map[string]string(hash → path) with reverse index (path → hash) for O(1) updates. Built on startup, updated incrementally on Write/Archive - Hash-based FETCH —
isHashPath()detects the pattern,handleFetchByHash()resolves and serves - Reserved path enforcement —
/sha256-<64hex>rejected on PUBLISH, APPEND, ARCHIVE, VERSIONS, LIST - SPEC Section 12 — full spec including Section 12.1 on agent-driven hash discovery via hubs
- Agent workflow documented — agents crawl servers, collect hashes, publish index documents to hubs for DHT-like discovery
Security Hardening from Review
- Symlink escape defense —
BuildHashIndexresolves symlinks viaEvalSymlinksand validates containment within content root before reading - Stale index handling —
handleFetchByHashreturnsnot-found(not server error) when index points to a missing document chain-errorpath leak fixed — was exposing raw filesystem paths to unauthenticated clients, now returns generic message (full error logged server-side)- File size guard —
BuildHashIndexchecksos.Statsize beforeReadFileto prevent OOM on oversized files - Used
isArchived()parser — replaced rawbytes.Containssubstring match with proper frontmatter parser - Walk uses
absRoot— fixed potential issue with relatives.rootpaths breaking containment checks
Code Quality
- Extracted
resolvedRoot()method andisContained()helper — eliminated duplicated root resolution and containment logic betweenresolve()andBuildHashIndex() - Added reverse index (
pathIdx map[string]string) —UpdateHashIndexandRemoveHashEntryare O(1) instead of O(n) map scan
Key Design Insight
Servers stay simple — serve content, answer hash lookups. Agents own discovery. Hubs are just servers hosting index documents. No DHT protocol, no crawling, no special infrastructure. The protocol provides the primitive; agents build the intelligence.