Cross-references each candidate host IP with lan_devices (known) so a tile shows
e.g. 'H Tower' instead of '192.168.1.15:32400'.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
migration 026 backup_runs; POST ingest (owner) from offsite-backup.sh, GET for the
Sacred Valley card showing last run, per-guest sizes, Farm free, schedule.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Icon route used Cache-Control: public, max-age=86400, so changed icons stayed
stuck in CF + browser caches for a day. Switch to no-cache (revalidate; Express
ETag => 304 when unchanged) so icon edits show up immediately.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Icons.display path only handles svg/png, so jpg-backed icons never
rendered. Remove jpg/jpeg: drop from EXT map and magicOk in ingest.js,
narrow FILE regex in sets.js to (svg|png), update the file input
accept attribute in icon_sets_panel.js, and simplify the content-type
ternary in the icon_sets route (jpeg branch was now unreachable).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Move the softAuth middleware from devices.js into a new shared
lib/api/soft_auth.js module. Apply router.use(softAuth) and
router.use(errorMiddleware) to icon_sets.js so that POST/DELETE
owner-only routes return 401 (not 500) when no auth is present.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Export reusable iconRef zod validator (set:<set>:<name> | brand:<slug> | null)
and add it as an optional field to patchBody so PATCH /devices/:mac accepts icon.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Read-only Proxmox storage health (same PROXMOX_RO_TOKEN as the cluster card):
ZFS pool health+usage, dropped zfspool storages (the donatello/leonardo SATA
signal), and per-LXC rootfs fill, with a HEALTHY/WATCH/ATTENTION roll-up.
Closes the monitoring gap from the 2026-06-09 audit (C1 + H2 were invisible).
Pure normalizeStorage() unit-tested (4 tests).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
'Scan Now' triggers POST /api/devices/scan from the band header. '+ Add by MAC'
renamed '+ Manual Add' with an optional IP field (addBody/addManual accept ip)
and a MAC input that auto-inserts colons as you type. Frontend test 4/4; DB-backed
api/repo tests written (run with the suite — skipped locally to avoid colliding
with a concurrent test run on void_test).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
'+ Add by MAC' in the band header → POST /api/devices → lan_devices.addManual
(status=known, present=false; enriched on next scan). Repo + API + frontend tests.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This work (network_hosts inventory + infra_audit MCP tool, /api/cluster +
Sacred Valley cluster card, topbar cluster-health pill + SW self-heal) was
built in an earlier session and DEPLOYED to CT 311 as alpha.24–26, but was
never committed to git — prod was running code absent from the repo. Commits
it as-is (already prod-validated) so git matches the live state, and restores
its alpha.24/25/26 CHANGELOG entries. Files are disjoint from the fold-in
work; both now ship together under alpha.27.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add position column to pages (migration 020), update listBySpace to ORDER BY position, title,
expose position in update(), add to patchSchema, and replace the space view flat table with a
tree renderer grouping pages by parent_id under h4 section headers.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sidebar: Spaces / Agents / Navigate sections, accent pill on active item, status
dots on agents. Settings Agents rows expand to show the agent's persona (soul) +
capabilities/scopes via GET /api/agents/:id/profile.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Summarises the Homelab Monitor (CT300 :8080) into a blackflame card: Claude Code
token usage today/week + top model, and OpenClaw/Ollama p50/p95 latency + error rate.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
clock/weather etc. default to 1/6 width; sizes store an integer span 1-12
(legacy s/m/l still accepted by /api/dashboard/layout).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A cryptographically-verified CF Access JWT (signature vs team JWKS + audience +
email allow-list) now counts as the owner, so browser requests through the CF
tunnel don't need the owner token copied onto each device. Fails closed → owner
token remains the fallback (LAN-direct + dev/tests unaffected). Opt-in via
CF_ACCESS_TEAM_DOMAIN / CF_ACCESS_AUD / CF_ACCESS_OWNER_EMAILS.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Q3: prod void DB role NOSUPERUSER (vector marked trusted; deploy/README documents it)
- Q4: buildChildEnv allow-list for the claude subprocess (no OWNER_TOKEN/DATABASE_URL/secrets leak)
- Q5: pending-change approve claims-before-applying + reopens on failure (no re-approvable dup)
- Q6: /capture/upload validates space_id (UUID+existence); pg pool statement_timeout 30s
- Q9: disabled failing syncoid-donatello timer on Z
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds GET /api/health/services returning registry services grouped by
category with merged cached status and per-group healthy counts, and
POST /api/health/check (owner-only) that enqueues a health.check
pg-boss job. Registers the health_check worker in the jobs index.
- pool.js: add pool.on('error') handler — an idle-client error (DB restart /
.215 failover) previously crashed the process (no 'error' listener → throw)
- context tool: project a SAFE_COLUMNS allow-list for resources (never the
monitoring/metadata JSON blobs); also add 'resource' to TABLE (was unhandled)
- applyPendingChange: guard the 'upsert' arm so a non-upsertable entity_type
fails with a clear ValidationError instead of a bare TypeError
Tests: pool_error, context (resource case), pending_extended_actions (guard).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Owner bearer token was compared with === / !==, which short-circuits on the
first differing byte and leaks token length+prefix via response timing
(security-sweep-2026-06-01.md). New timingSafeStrEqual (crypto.timingSafeEqual
with a length pre-check so it never throws on length mismatch); wired into both
owner.js and agent_auth.js.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The pending_changes.action CHECK only permitted create/update/delete, so a
suggest-tier agent hitting POST /api/refs/upsert (or the resource dependency
routes) 500'd on the INSERT (docs/security-followups.md HIGH finding).
- migration 009: widen CHECK to include 'upsert'
- applyPendingChange: dispatch 'upsert' -> refsRepo.upsertByExternal on approve
- resources.js: add_dependency/remove_dependency are now owner-only (requireOwner),
infra wiring is never diverted to pending_changes
- tests/api/pending_extended_actions.test.js: regression coverage
Full suite green (278 pass / 1 skip).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- system prompt = Dross (Ozriel's construct fragment, per Void 1.0), with tool guidance
- migration 008 renames the seeded agent 'companion' → display name 'Dross'
- removed lib/ai/anthropic.js + lib/ai/agent/runtime.js + tests + @anthropic-ai/sdk dep (companion now runs via the claude CLI; kept lib/ai/secret.js for the Vaultwarden roadmap)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- driver: tool_results arrive as type:'user' content blocks (not bare); parse them
- route: tool_result content is a JSON string; parse it for pending_change_id → draft event
- propose_change: inject ctx.space_id into create payloads (model can't know the uuid; tables require it)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
claude resolves the MCP server command against the child env (no PATH), so a
bare 'node' failed to spawn (status:failed). Use process.execPath. Also pass
--tools to drop claude's built-ins (Bash/Read/Write/…) — companion gets only
the four mcp__void__* tools.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replaces the runTurn/callModel/Anthropic-API-key path in POST /turn with
runClaudeTurn (claude CLI) backed by a per-turn MCP config that spawns
companion-stdio.js. Extracts pending_change_id from tool_result events
defensively (structuredContent → text-JSON fallback). Rewrites companion
test to inject fake-claude-draft.js via app.locals.claudeExe.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
POST /api/capture with a youtube.com / youtu.be / vimeo.com URL
enqueues ingest.video (Python worker) instead of ingest.url
(Node worker). Detection by URL hostname; idempotency_key + response
shape unchanged.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
POST /api/ingest/karakeep accepts Karakeep webhook payloads. HMAC
signature on the raw body captured by express.json's verify hook.
Mounted on app before mountApi so it bypasses agentOrOwner — the
shared secret IS the auth.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
safe_fetch.js validates URLs before fetch: rejects non-http(s), literal
or DNS-resolved loopback / RFC1918 / link-local / CGNAT / metadata
addresses; follows redirects manually with the same checks on each hop.
Test fixtures gate the check with VOID_INGEST_ALLOW_PRIVATE for offline
fixtures that hit 127.0.0.1.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Single GET /api/search?q=&space_id=&kinds=&limit=&offset= unions FTS
hits across pages / refs / source_docs / messages with a `kind`
discriminator and ts_rank ordering. Each branch's to_tsvector matches
the GIN index expression on its source table so indexes are used.
Messages have no space_id and are excluded when a space filter is set.
Hybrid vector / RRF lands in Plan 3.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>