New securityRegistry (separate from companionRegistry) with two read-only,
secret-free tools for the Yerin security agent:
- audit_log: query the redacted audit trail by actor_kind/actor_id
- agent_inventory: list agents + capabilities/scopes (explicit projection,
never SELECT *, no token material)
Follows the existing createRegistry() pattern. Design + wiring roadmap in
docs/yerin-security-agent.md. Not yet seeded/exposed over MCP (left for review).
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>
resetDb() DROPs schema; dev DATABASE_URL pointed at the shared prod void DB on
.215. setup.js now forces a dedicated void_test DB (TEST_DATABASE_URL or derived)
and throws if it would target prod. Created void_test + pg_hba rule on CT 310.
Verified: full suite green, prod void space count unchanged (2→2).
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>
Implements runClaudeTurn() — spawns the claude CLI for a single companion
turn using subscription/OAuth auth (strips ANTHROPIC_API_KEY +
ANTHROPIC_AUTH_TOKEN from child env), streaming normalised events (delta,
tool, tool_result, result, error) via onEvent callback.
Includes hermetic test + fake-claude.js fixture that mimics real 2.1.159
stream-json output; zero network/CLI calls in the test suite.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
node-cron schedules runSync at 03:00 local time; runSync enqueues
sync.source_doc for every source_docs row with sync_source='url'.
Started from server.js's CLI gate alongside the job queue.
Co-Authored-By: Claude Opus 4.7 <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>
After creating a ref, the Node-side ingest.blob worker enqueues a
follow-up job for the Python void-workers (Plan 4) to OCR / extract
text. Other kinds (file) get no follow-up.
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>
Replaces FTS-only /api/search in place. RRF (k=60) fuses ts_rank and
pgvector cosine distance rankings. Vector branch silently skipped when
Ollama times out / errors, keeping search snappy and resilient.
Messages have no embeddings in Plan 3, so they participate in the FTS
branch only.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
create/update on embeddable repos enqueue embed.text with a singleton
key that coalesces rapid edits. No-op when the queue is not running
(server tests construct createApp without booting pg-boss).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Pads nomic-embed-text's 768 dims to 1024 zeros so a later 1024-dim model
swap is a re-embed, not a migration (per master spec).
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>
Unifies pgboss.job (current, per-queue partitioned) and pgboss.archive
under one SELECT for operator views. retry promotes archived rows back
into the active partition.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Job queue starts only in the CLI gate (not inside createApp), so tests
manage their own queue lifecycle. waitForJob() takes a (name, id) pair
to match pg-boss v10's getJobById signature.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Per-name ensureQueue promise dedup so concurrent enqueue+subscribe
on the same queue do not race createQueue (Postgres deadlock).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Search view: read ?q from hash, call /api/search, group hits by kind
with rank + space_id; sidebar filters for kinds and space_id; updates
on Enter or filter change.
Bumps package.json + server.js VERSION to 2.0.0-alpha.2 and pins the
/health version assertion to match.
CHANGELOG: full Plan 2 entry covering API surface, capability tiering,
audit chain extension (approve/reject events), and the SPA shell.
Security: adds safeHref() to dom.js and applies it everywhere an
API-supplied URL becomes href / src (reference media block + reference
source_url anchor + resource url anchor). javascript: and other
non-http(s)/mailto schemes from agent-suggested content can no longer
execute in the owner's browser.
Plan 2 surface is feature-complete: 22/22 tasks landed, 185 tests
across 43 files, SPA renders end-to-end including the suggest -> approve
agent flow.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Three-column grid (sidebar / main / right rail) with Cradle aesthetic:
blackflame accent on Cinzel display headings + Cormorant Garamond body
in cards, system UI for chrome. Hash-based router covers all entity
routes plus search, inbox, sacred-valley. api.js stores OWNER_TOKEN in
localStorage and prompts via a modal on 401. dom.js provides safe el()
+ mount() builders so no component ever assigns innerHTML from API data
(the only exception is an explicit, scary-named html: opt-in for
sanitizer output, used later by the markdown editor).
state.js is a tiny event bus for shared chrome state (pending count).
Components and views are loaded as ES modules — sidebar / topbar /
rightrail + 9 view stubs that the later Phase E tasks fill in.
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>
Owner-only routes wired with an applyPendingChange dispatch helper
covering page/project/task/ref/resource/source_doc create/update/delete.
Approve and reject emit their own audit_log entries (actions already in
the CHECK vocab) so the audit trail is self-contained.
Documents a latent bug in security-followups.md: pending_changes.action
CHECK constraint blocks 'upsert' / 'add_dependency' / 'remove_dependency'
divertToPending paths in refs/resources routes when an agent at suggest
tier hits them.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Add lib/api/routes/tags.js: list + upsert at /api/tags, and an
entity-scoped router mounted at /api/:entity_type/:entity_id/tags
for attach (idempotent), list, and detach. entity_type is bounded by
a zod enum covering space/project/task/page/ref/resource/source_doc/
conversation.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>