CF Access multi-aud: CF_ACCESS_AUD now accepts a comma-separated allow-list so requests through either the void.hynesy.com or void2-app.hynesy.com CF Access app are honoured as owner. Fails closed; unlisted auds rejected. Adds multi-aud test. Void 1 (CT 301) becomes legacy but stays running untouched as an instant rollback. -alpha tag kept pending owner sign-off. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
21 KiB
21 KiB
Changelog
All notable changes to Void 2.0 are documented here. Format: Keep a Changelog.
2.0.0-alpha.18 — Plan 8b cutover: void.hynesy.com now serves Void 2
- Go-live.
void.hynesy.com(CT 301 → Void 1) is repointed at Void 2 (CT 311,.216:3000) at the Traefik edge. Void 1 is now legacy — CT 301 stays running untouched as an instant-rollback fallback; nothing is retired or renamed yet. The-alphatag is intentionally kept pending owner sign-off. - CF Access multi-aud (
lib/auth/cf_access.js):CF_ACCESS_AUDnow accepts a comma-separated allow-list so a request through either CF Access app —void.hynesy.com(aud0e7190f4…) orvoid2-app.hynesy.com(auda381f270…) — is honoured as owner. Still fails closed; an unlisted aud is rejected. Prod env updated to carry both auds. - Cutover is fully reversible: revert the Traefik
voidservice URL tohttp://192.168.1.11:2424anddocker restart traefik.
2.0.0-alpha.17 — Settings, project management, terminal, AI Usage, "The Void" space + UI polish
- Settings (
#/settings): API tokens (mint/list/revoke), Agents list with an expandable profile viewer (persona/"soul" + capabilities/scopes viaGET /api/agents/:id/profile), Orthos Mode placeholder. - Per-space project management: Void-1-style expandable cards with inline status, Details, Tasks, Linked references, ↻ Research (Eithan stub →
POST /api/projects/:id/research), Edit/New modal, Delete-with-confirm. Migration 019 adds research fields;GET /api/projects/:id/linksresolves linked pages/refs. - Terminal tab (
#/terminal): embedded blackflamettyd→ persistenttmux/claudeon CT 300; works via Traefik (CF-Access) and the LAN IP (app proxies/terminal+ its WebSocket to ttyd). - AI Usage Sacred Valley card +
GET /api/ai-usage— summarises the Homelab Monitor (Claude tokens + local OpenClaw/Ollama p50/p95). - "The Void" space: Void 1.x / Void 2.0 / Void 3.0 as projects (tasks + linked references), charting the project's evolution.
- Migration: BookStack re-imported with Book › Chapter › Page hierarchy; Void 1 project
research_notesbackfilled. - UI: page header actions (Edit/Revisions/Export), breadcrumbs, themed markdown tables,
Cache-Control: no-cache, live sidebar active-sync, hybrid sidebar (Spaces/Agents/Navigate + active pill + agent dots), themed scrollbars + topbar, +1 font bump, Sentinel → Yerin (red).
2.0.0-alpha.16 — Little Blue + action framework (Agent Layer brick 2)
- Little Blue, the caretaker fix-it agent, is online at
#/little-blue: chat + a manual Actions panel. She can restart whitelisted services and power-manage Proxmox guests —safeactions run on her word,riskyones queue for your approval. - Least-privilege action framework: a version-controlled whitelist (
config/actions.json), two server-side-enforced channels (scoped Proxmox API token + SSH forced-command wrapper), tiered approval, and a fullagent_actionsaudit trail. Infra creds live ONLY in the main server; Little Blue's MCP child proposes actions via the local API with a scoped token — it can only name a whitelisted id, never a command.
2.0.0-alpha.15 — Yerin online (Agent Layer brick 1)
- Yerin, the read-only security agent, is now a usable agent: a global
#/sentinelchat surface backed by her 5 security tools (audit/agents/pending/exposure/tokens). She investigates + reports; she never acts. - Extracted the shared agent-chat foundation —
runAgentTurn(backend) +agent_chat(frontend) — now used by both Dross and Yerin. Personas live inlib/ai/personas/.
2.0.0-alpha.14 — MCP HTTP transport for external agents
- MCP Streamable HTTP at
/mcp: external agents can connect over the network, authenticated by a Space-scoped Void agent bearer (owner / CF-Access identities are rejected here — external agents never inherit owner powers; CF Access service tokens gate the hostname at the edge). - Read + suggest-only: a dedicated external registry exposes
search/read/context+propose_change(which always routes to the pending-changes inbox,applied:false). Kept separate from Dross's registry so future companion tools never auto-leak. - The
readtool now enforces Space membership for bound callers; reads are hard-scoped to the agent's bound Space (client-supplied space args are ignored). Per-token rate limit + audit on every external tool call.
2.0.0-alpha.13 — Finer Sacred Valley tile scaling
- Cards now sit on a 12-column grid with a per-card width −/+ stepper (span 1–12) in edit mode, replacing the coarse S/M/L. "Small" defaults to 1/6 width (half its previous size) so clock/weather aren't oversized.
- Layout
sizesnow store an integer column span (legacy 's'/'m'/'l' still accepted).
2.0.0-alpha.12 — Editable Sacred Valley layout
- "Edit layout" mode on the dashboard: per-card resize (S/M/L column span), show/hide (with a hidden-cards tray to re-add), clearer drag-to-reorder via a dedicated grip handle, and a Reset to defaults.
- All changes persist through the existing
/api/dashboard/layout(order/sizes/hidden) — no backend changes.
2.0.0-alpha.11 — DB-backed service registry + LAN auto-discovery
- The health-band registry is now in Postgres (
monitored_services, migration 015) instead of the hand-editedconfig/services.json— which becomes a one-time boot seed (auto-populated if the table is empty). - Owner CRUD over the registry:
POST/PATCH/DELETE /api/health/services(add/edit/enable/disable/remove);GET /api/health/servicesis now DB-backed. - LAN auto-discovery:
discover.lanpg-boss worker (pure-Node TCP sweep + HTTP-title probe, no nmap) +POST /api/health/discover. Found host:ports become disableddiscoveredcandidates that never clobber curated entries;GET /api/health/services/discoveredlists them. - Dashboard: a "Scan" button + a "Discovered (N new)" section in Little Blue's band, with one-click promote.
2.0.0-alpha.10 — Cloudflare Access SSO as owner auth
- Browser requests through the CF tunnel no longer need the owner token copied onto each device: a cryptographically-verified Cloudflare Access JWT (
Cf-Access-Jwt-Assertion) for an allow-listed email now counts as the owner (lib/auth/cf_access.js, wired intoagentOrOwner). - Security: verifies signature against the team JWKS + audience (app AUD) + email allow-list; the plain email header is never trusted alone. Fails closed → falls back to the owner token (LAN-direct
:3000path and dev/tests unaffected). - Opt-in via env:
CF_ACCESS_TEAM_DOMAIN,CF_ACCESS_AUD,CF_ACCESS_OWNER_EMAILS(absent → feature disabled).
2.0.0-alpha.9 — Hardening pass (Void 3.0 quick wins)
- Security: prod
voidDB role revoked SUPERUSER (CT 310;vectormarked trusted so the test harness still creates it as non-superuser). An app-process compromise no longer escalates to full-cluster compromise. - Security: the
claudecompanion subprocess now gets an explicit env allow-list (buildChildEnv) instead of the fullprocess.env—OWNER_TOKEN/DATABASE_URL/Karakeep/ANTHROPIC secrets no longer reach the CLI. MCP tools are unaffected (they get DB env via the explicit--mcp-config). - Correctness: pending-change approve now claims the change (atomic
WHERE status='pending') before applying, and reopens it on apply failure — eliminates the re-approvable duplicate after a crash. - Hardening:
/api/capture/uploadvalidatesspace_id(UUID + existence); pg pool gets a 30sstatement_timeout. - Ops: disabled the failing
syncoid-donatellotimer on Z (pools out pending parts). - Deferred: per-space tag uniqueness needs a
space_idcolumn ontags→ folded into the polymorphic-space_idproject.
2.0.0-alpha.8 — Sacred Valley (Plan 6)
- Two-band #/sacred-valley dashboard: draggable data cards (clock, weather, host-perf, speedtest, jobs, inbox, search) with server-persisted layout (custom CSS-grid reorder, no resize).
- Little Blue Health band: config service registry, 60s pg-boss health checks, grouped status tiles, locally-cached service icons (no CDN leak).
- New endpoints: /api/dashboard/layout, /api/weather, /api/host, /api/speedtest/{history,run}, /api/health/{services,check}, /api/icons/:slug.png.
- Migrations 012 (dashboard_layout), 013 (speedtest_results), 014 (service_status).
[2.0.0-alpha.7] — 2026-06-02
Security & hardening
pending_changes.actionCHECK fix (migration 009):upsertis now a valid suggestion action (approval dispatches torefsRepo.upsertByExternal); resource dependency mutations (add_dependency/remove_dependency) are now owner-only.- Constant-time owner-token comparison (
lib/auth/safe_compare.js) — replaces===, closing a timing side-channel onOWNER_TOKEN. - O(1) token verification (migration 010): selector+verifier split replaces the
O(n) bcrypt scan over all tokens. New format
vk_<selector>.<verifier>; legacy tokens still verify. Dropped the uselessidx_agent_tokens_hash. pool.jserror handler — an idle pooled-client error no longer crashes the process.contexttool projects a safe column allow-list for resources (nomonitoring/metadatablobs); now also handlesresourceviews.applyPendingChangeguards theupsertarm (clearValidationError).
Added (Yerin — security agent)
- Read-only
securityRegistry(lib/ai/agent/tools/security/) with five tools:audit_log,agent_inventory,pending_review,resource_exposure,token_audit— no secret material in any output. - Migration 011 seeds the read-only
yerinagent. - The stdio MCP server selects its toolset via
VOID_TOOL_REGISTRY(security→ Yerin's tools; default → Dross's companion tools).
[2.0.0-alpha.6] — 2026-06-01
Changed (Plan 5b: companion backend → Claude CLI subprocess)
- Companion model backend switched from the Anthropic API to the
claudeCLI subprocess, authenticated by the owner's Claude Max subscription (no API key — the Agent SDK can't use subscription auth headlessly, and Max doesn't issue API keys). Mirrors Void 1.0'slib/agent.js: spawnclaudewithANTHROPIC_API_KEY/ANTHROPIC_AUTH_TOKENstripped so it uses the logged-in subscription. The CLI owns the agentic loop; the four companion tools are exposed to it via a local stdio MCP server (lib/mcp/). lib/ai/claude_cli.js— spawnsclaude --print --output-format stream-json --include-partial-messages --append-system-prompt … (--session-id | --resume) --mcp-config … --strict-mcp-config --tools … --allowedTools …, maps stream-json →{delta,tool,tool_result,result,error}. Prompt fed via stdin (variadic--toolswould eat a positional). Multi-turn continuity via--resume.lib/mcp/companion-stdio.js— stdio MCP server re-exposingcompanionRegistry; per-turn Space/agent context passed via env in the--mcp-config.propose_changenow stamps the current Space onto created space-scoped entities (model can't know the Space uuid).- CT 311 runs the
claudeCLI (logged in asvoid,HOME=/var/lib/void). - Built-in CLI tools (Bash/Read/Write/…) disabled via
--tools; the companion has only the fourmcp__void__*tools. - The old
@anthropic-ai/sdkAPI-key path (lib/ai/anthropic.js,runTurn) is retained in-tree but no longer the companion's execution path.
[2.0.0-alpha.5] — 2026-06-01
Added (Plan 5: Companion chat)
- Right-rail companion chat — an always-visible, per-Space AI assistant.
Label-led turns (YOU / Companion) with left/right alignment, live
tool-activity chips, streamed answers (markdown via DOMPurify), and inline
approve/reject draft cards. Loads its space's history on first paint via the
space-activestate event. - Lean agent runtime (
lib/ai/agent/runtime.js) on the Anthropic SDK directly — no Mastra.runTurndrives a tool-use loop (max-iteration guarded), streams text deltas, and emitstool/delta/draftevents.callModelis injectable (the SSE endpoint takes a fake in tests, so the suite never hits the network). - Extensible shared tool registry (
lib/ai/agent/registry.js) with four v1 tools:search(hybrid FTS),read,context(resolves the active view), andpropose_change. Adding a tool is a one-lineregisterTool; a future MCP server re-exposes the same defs. propose_changenever applies — it only writes apending_changesrow, capability-gated viacanAct(defaultsuggest). Prompt-injection containment is structural: a poisoned document can at most produce a draft the owner must approve. Drafts render inline in chat AND in the Inbox (same row; approving from either flips it).- Companion API —
GET /api/spaces/:id/companion(history) andPOST /api/spaces/:id/companion/turn(SSE). One ambient conversation per Space (conversations.space_idvia migration 007); one assistant message per turn with the tool trace + draft ids inmetadata. @anthropic-ai/sdkdependency; key resolved via theenv:/file:vault_pathresolver (lib/ai/secret.js) — Vaultwarden swap still deferred.- Default model
claude-sonnet-4-6, overridable per-agent (agents.model) and viaANTHROPIC_MODEL— the seam for scope-C local personas.
[2.0.0-alpha.4] — 2026-06-01
Added (Plan 4: Python void-workers)
void-workers.service— Python 3.13 service alongsidevoid-serveron CT 311. psycopg-based pg-boss client matches Node's claim/finish semantics viaSELECT ... FOR UPDATE SKIP LOCKED. Forcesclient_encoding=UTF8on every connection (void2-db cluster is SQL_ASCII).extract.pdf—pdftotext -layoutfirst; per-pagepdftoppmrasterization + Tesseract OCR fallback when extraction yields < 200 chars.extract.image— Tesseract OCR (English) for images stored in the blob store.ingest.video—yt-dlpmetadata + audio extract + faster-whisper (small.endefault). CUDA at startup; CPU fallback when HA failover to Z3 (no GPU) happens. URLs validated as http(s) and--separator passed to yt-dlp to defeat argv smuggling.sync.source_doc— fetchesupstream_urlvia Pythonsafe_fetch(port of the Node helper) + sha256-diffs against the prior body_sha in metadata; updates body_text only when content changed.- Node
blob.jsfans out toextract.pdf/extract.imageafter creating PDF / image refs. - Node
capture.jsroutesyoutube.com/youtu.be/vimeo.comURLs toingest.videoinstead ofingest.url. - Daily cron (
lib/cron/sync_source_docs.js) enqueuessync.source_docjobs at 03:00 local for everysource_docsrow withsync_source='url'. - CT 311 infrastructure: resized to 6 cores / 8 GB RAM, NVIDIA RTX A2000 device-nodes passed through (shared with CT 102's Ollama).
deploy/push-workers.sh+deploy/void-workers.service— push the workers package, chown tovoidworkers, recreate the venv, install deps undersu voidworkers -c, restart the unit.
[2.0.0-alpha.3] — 2026-06-01
Added (Plan 3: Capture pipeline + hybrid search)
- pg-boss job queue embedded in void-server (Node). Queue tables live
alongside Void's in the shared void2-db. Tests manage their own boss
lifecycle via
stopBoss()/waitForJob()helpers. /api/jobs(owner-only) — list / get / retry / delete with state and name filters. Minimal#/jobsSPA view fronts it, polling every 10 s./api/capturePOST — URL →ingest.urljob. Idempotent bysha256(space_id + url)stored asrefs.external_id; duplicate POST returns the existingref_id./api/capture/upload— multipart file →ingest.blobjob → content-addressed/var/lib/void/blobs/<sha-prefix>/<sha>→refsrow. Drag-drop in the SPA wired to the main panel;space_idpre-filled from the last-viewed space.ingest.urlworker —@mozilla/readability+jsdomextract; fetch protected bylib/ingest/safe_fetch.js(SSRF mitigations: http(s) only; DNS-resolved hostnames checked against loopback / RFC1918 / link-local / CGNAT / metadata; resolved IP pinned via an undici dispatcher to defeat DNS rebinding; redirects re-validated).ingest.blobworker — content-addressed storage, image/pdf/file kind classification.embed.textworker — Ollamanomic-embed-text(768 dims) padded tovector(1024); emits aworker-actor audit log entry.- Repo-level triggers — pages/refs/source_docs
createandupdateenqueue anembed.textjob with a singleton key so rapid edits coalesce. No-op when the queue is not running (tests). - Hybrid
/api/search— FTS + pgvector ANN unioned with reciprocal rank fusion (k=60). Vector branch silently skipped when Ollama times out, leaving FTS-only results — graceful degrade. /api/ingest/karakeep— HMAC-verified webhook. Enqueuesingest.karakeepforbookmark.created; worker fetches the bookmark via Karakeep's API, normalizes to arefsrow taggedsource_kind='karakeep'.
Deferred (Plan 4+)
- Python
void-workersservice for Whisper / Tesseract OCR / yt-dlp (heavy ML). - AI Space/Project suggestion on capture.
- Embedding chunks table (whole-doc embedding only in Plan 3).
- pdftotext for born-digital PDFs.
pg LISTEN/NOTIFYreal-time Jobs UI.
[2.0.0-alpha.2] — 2026-06-01
Added (Plan 2: API surface + UI shell)
- REST routes for the full entity tree:
/api/spaces,/api/projects,/api/tasks(with project + space scoping)/api/pages+ page revisions +/api/pages/:id/backlinks/api/refs+/api/refs/upsert/api/resources+ dependencies + change history/api/resources/:id/source-docs+/api/source-docs/:id/resync(gated byENABLE_RESYNC)/api/agents(owner-only) + agent token mint/revoke/api/conversations+ nested/messages/api/tags+ entity-scoped attach/detach via/api/:entity_type/:entity_id/tags/api/links(POST/GET from|to/DELETE) for polymorphic entity links/api/pending-changes+ approve/reject with dispatch table covering page/project/task/ref/resource/source_doc × create/update/delete/api/audit/entity/:type/:id+/api/audit/actor/api/searchunified FTS across pages, refs, source docs, messages
- Agent bearer auth middleware + capability tiering: owner allow, agent
write+scope→ allow, agentsuggest→ 202 + pending row, else 403. - Approve and reject emit explicit
approve/rejectentries in the audit log with the original agent id preserved in the diff. - Static SPA shell served from
public/:- Three-column Cradle aesthetic (blackflame palette, Cinzel display headings, Cormorant Garamond body)
- Hash-based router with views for home / space / project / page / reference / resource / search / inbox / sacred valley
dom.jssafe builders — noinnerHTMLon API data anywhere; the explicithtml:opt-in is used only by the markdown editor's preview pane, which sanitizes with DOMPurify- Sidebar Spaces tree with lazy project expansion, bottom Navigate
section, pending-count badge shared with the topbar bell via a tiny
state.jsevent bus - Topbar: brand, capture modal stub, global search (Enter →
#/search?q=), pending bell, owner toggle - Page editor: split-pane markdown via marked + DOMPurify, save
PATCHes
/api/pages/:id, backlinks card - Reference detail: media block (image / YouTube embed / link), summary, metadata table, tag attach/detach, linked-from list
- Resource detail: status header, dependencies + source docs + runbook pages columns, change history
- Inbox: pending changes grouped by agent, approve → navigate to the resulting entity
- Test coverage: 185 tests across 43 files (113 new for Plan 2 routes + search + GET / shell smoke).
Security follow-ups (deferred)
- Polymorphic IDOR risk on entity_links / entity_tags / attachments —
acceptable today since the entire API is owner-token gated and there
is one tenant; see
docs/security-followups.mdfor the tighten-now vs defer decision. pending_changes.actionCHECK constraint blocks'upsert'/'add_dependency'/'remove_dependency'actions emitted by some routes'divertToPendingpaths. Latent — only fires when an agent at suggest tier hits those specific endpoints. Mitigation options documented indocs/security-followups.md.
[Unreleased]
Added
- Initial repo scaffolding
Added (Plan 1: Foundation)
- LXC provisioning for
void2-db(Postgres 16 + pgvector) andvoid2-app - Schema migrations 001-006 covering core, knowledge, resources, agents, cross-cutting, audit
- Repos with capability-checked
actorparameter and audit trail - Real audit log with redaction of sensitive keys (token, password, api_key, etc.)
pending_changestable for agent suggestions awaiting owner approval- Capability check module (allow / suggest / deny) for user vs agent actors
- Owner-token bearer auth
- Express server with
/healthand smoke/api/spaces - Test coverage: 72 tests across migrations, repos, capability, owner middleware, server