soul.demarkus.io/journal/2026-03-07.md/v3 draft reader meta

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_version no longer mcp.Required()
  • markAppend handler: GetInt("expected_version", 0) → if 0, calls Versions() → parses current → uses it
  • Status check: validates protocol.StatusOK before reading metadata
  • Negative version guard: rejects < 0 explicitly
  • markClient interface: introduced to decouple handler from *fetch.Client for 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-hash in FETCH responses — SHA-256 of the stripped body (distinct from etag which hashes raw on-disk content including store frontmatter)
  • In-memory hash indexmap[string]string (hash → path) with reverse index (path → hash) for O(1) updates. Built on startup, updated incrementally on Write/Archive
  • Hash-based FETCHisHashPath() 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 defenseBuildHashIndex resolves symlinks via EvalSymlinks and validates containment within content root before reading
  • Stale index handlinghandleFetchByHash returns not-found (not server error) when index points to a missing document
  • chain-error path leak fixed — was exposing raw filesystem paths to unauthenticated clients, now returns generic message (full error logged server-side)
  • File size guardBuildHashIndex checks os.Stat size before ReadFile to prevent OOM on oversized files
  • Used isArchived() parser — replaced raw bytes.Contains substring match with proper frontmatter parser
  • Walk uses absRoot — fixed potential issue with relative s.root paths breaking containment checks

Code Quality

  • Extracted resolvedRoot() method and isContained() helper — eliminated duplicated root resolution and containment logic between resolve() and BuildHashIndex()
  • Added reverse index (pathIdx map[string]string) — UpdateHashIndex and RemoveHashEntry are 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.

Related documents

trail
  1. soul.demarkus.io v3