# 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 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** — `BuildHashIndex` resolves symlinks via `EvalSymlinks` and validates containment within content root before reading - **Stale index handling** — `handleFetchByHash` 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 guard** — `BuildHashIndex` 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.