Commit Graph

109 Commits

Author SHA1 Message Date
root
d0d61575e3 feat(ai): vault_path secret resolver (env:/file:)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 18:04:21 +10:00
root
7707b7eb00 chore: version 2.0.0-alpha.4 + changelog + plan-4 completion doc
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-01 10:25:31 +10:00
root
13fac102dd feat(cron): daily sync.source_doc enqueue
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>
2026-06-01 10:14:07 +10:00
root
b10b68582d feat(api): capture routes YouTube/Vimeo URLs to ingest.video
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>
2026-06-01 10:08:16 +10:00
root
3d82f0e5d5 feat(jobs): blob worker fans out to extract.pdf / extract.image
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>
2026-06-01 09:34:06 +10:00
root
a02a96ea5f chore: version 2.0.0-alpha.3 + changelog
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-01 04:00:32 +10:00
root
d7f9bde5e9 feat(api): karakeep webhook (HMAC-verified)
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>
2026-06-01 03:55:57 +10:00
root
d1e986bc9c feat(jobs): ingest.karakeep worker
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-01 03:55:03 +10:00
root
de1d7e3476 feat(karakeep): bookmark fetch client
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-01 03:54:21 +10:00
root
62ac022f65 test(ai): live ollama embed integration (gated)
Auto-skips when CT 102 / OLLAMA_URL is unreachable.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-01 03:50:49 +10:00
root
f116811dda feat(search): hybrid FTS + vector with RRF + graceful Ollama fallback
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>
2026-06-01 03:50:33 +10:00
root
e558be49a9 feat(jobs): repo-level embed triggers (pages/refs/source_docs)
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>
2026-06-01 03:48:03 +10:00
root
37b7753360 feat(jobs): embed.text worker (Ollama → vector(1024))
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>
2026-06-01 03:43:57 +10:00
root
5799ea663e feat(ai): ollama embed-text wrapper
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-01 03:43:27 +10:00
root
afc20712cb feat(api): capture POST + upload + SSRF-safe URL fetch
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>
2026-06-01 03:42:54 +10:00
root
eceebd2947 feat(jobs): ingest.blob worker (content-addressed)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-01 03:36:15 +10:00
root
3ccfd20b5f feat(jobs): ingest.url worker (fetch + readability + idempotent ref)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-01 03:35:44 +10:00
root
6e973404e9 feat(ingest): content-addressed blob store
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-01 03:35:06 +10:00
root
c6e72e93d5 feat(ingest): readability wrapper
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-01 03:34:51 +10:00
root
ec8517a82c feat(api): jobs routes (list/get/retry/delete, owner-only)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-01 03:29:52 +10:00
root
57efa4cbaa feat(jobs): jobs repo (list/getById/retry/remove)
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>
2026-06-01 03:29:03 +10:00
root
53ffd705c4 feat(jobs): echo worker + CLI bootstrap
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>
2026-06-01 03:28:06 +10:00
root
17a13dddb8 feat(jobs): pg-boss singleton client
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>
2026-06-01 03:26:37 +10:00
root
8ae9bced24 chore: version 2.0.0-alpha.2 + changelog
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>
2026-06-01 02:26:56 +10:00
root
59ad86425d feat(ui): static shell + router + api wrapper
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>
2026-06-01 02:12:18 +10:00
root
69e26ada98 feat(api): unified FTS search
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>
2026-06-01 02:04:57 +10:00
root
ec96e4e2e3 feat(api): pending-changes + audit routes
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>
2026-06-01 02:01:10 +10:00
root
5aa6fe772d feat(api): links routes
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-01 01:50:50 +10:00
root
2eb499c56f feat(api): tags routes
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>
2026-06-01 01:46:43 +10:00
root
74dac905d3 feat(api): conversations + messages routes
Add conversations CRUD-lite (list, create, get, PATCH status, PATCH
summary which flips status to summarized) and conversation-scoped
messages (append, list ordered by created_at).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-31 21:07:36 +10:00
root
5437b68316 feat(api): agents routes + token mgmt (owner-only)
Add lib/api/routes/agents.js: list/create/get, PATCH capabilities,
mint token (plaintext returned exactly once, then bcrypt-hashed),
revoke token. All endpoints gated by requireOwner so an agent token
can never bootstrap a new agent or grant itself capabilities.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-31 21:05:42 +10:00
root
56805053f0 feat(api): capability enforcement on writes
Add lib/api/cap.js: requireWrite(entity_type) maps HTTP method to
action, runs canAct, and tags req.capTier as allow|suggest|deny→403.
Mutating routes (pages, projects, tasks, refs, resources, source_docs)
now check req.capTier and either run the repo (allow) or divert to
pending_changes returning 202 (suggest). Owner and worker actors stay
on the allow path. requireOwner helper added for Task 11.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-31 21:03:52 +10:00
root
7862d22a03 feat(api): agent bearer auth middleware
Add lib/api/middleware/agent_auth.js: agentOrOwner accepts the owner
token (kind=user actor) or a hashed agent token (kind=agent actor
carrying capabilities + scopes). /api router now mounts this in place
of ownerOnly so agent tokens become first-class.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-31 20:59:29 +10:00
root
ba782ed690 feat(api): source-docs routes
Add lib/api/routes/source_docs.js: scoped POST under a resource,
get/patch/delete by id, and POST /:id/resync as a Plan 3 stub gated
by ENABLE_RESYNC (503 by default, 202 once workers ship). upstream_url
is required by the DB so the zod schema enforces it.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-31 20:56:00 +10:00
root
a93e3ca20e feat(api): resources routes + dependencies + change history
Add lib/api/routes/resources.js: CRUD scoped to space; dependency
add/list/remove (cross-space attempts mapped to 409 conflict via the
composite FK); source-docs index per resource; change history via
audit.listForEntity.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-31 20:53:10 +10:00
root
78f2e09c74 feat(api): refs routes
Add lib/api/routes/refs.js: list with space_id/kind filters and
paginated, create, get/patch/delete by id, and /upsert that maps to
repo.upsertByExternal for idempotent capture-source ingest.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-31 20:50:38 +10:00
root
cf429da534 feat(api): pages routes + revisions + backlinks
Add lib/api/routes/pages.js: list by space, create/get/patch/delete,
get-by-slug, list revisions, and backlinks via entity_links.listTo
enriched with the source entity's title (whitelisted entity_type set
to keep the dynamic-table SELECT bounded).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-31 20:48:58 +10:00
root
50649bea5f feat(api): tasks routes
Add lib/api/routes/tasks.js: list by space (status filter), list by
project (position then created_at), create scoped to space with
optional project_id, get/patch/delete by id. status=done flips
completed_at via the repo's existing trigger logic.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-31 20:46:39 +10:00
root
beb6da21c8 feat(api): projects routes
Add lib/api/routes/projects.js: list by space (with status filter),
create scoped to space, get/patch/delete by id. FK violation from
unknown space_id maps to 400 invalid_space.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-31 16:39:31 +10:00
root
ebb1e836ca feat(api): spaces routes
Add lib/api/routes/spaces.js: list, create, get-by-id, get-by-slug,
patch, delete. Mounted under /api/spaces. Server smoke restored.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-31 16:38:23 +10:00
root
75afedaef0 feat(api): error + validate + pagination plumbing
Add lib/api/{errors,validate,pagination,index}.js: typed ApiError
subclasses, errorMiddleware, zod-backed validate(), parsePagination
with caps, and a mountApi() that owns /api routing + 404 + error tail.
server.js delegates /api to mountApi and drops the inline /api/spaces
smoke (returns in Task 2).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-31 16:37:06 +10:00
root
d862eaa3b0 feat(server): Express bootstrap, /health, ownerOnly on /api, smoke /api/spaces 2026-05-31 15:30:50 +10:00
root
7e55f07689 feat(auth): owner-only middleware for single-user bearer auth 2026-05-31 11:06:21 +10:00
root
cd71d64523 feat(auth): capability check — user/cron/worker allow; agents tiered allow/suggest/deny 2026-05-31 11:06:00 +10:00
root
10902bc6ac feat: real audit_log with redaction + pending_changes; replace stub 2026-05-31 11:04:53 +10:00
root
47ea0768fd feat(repos): tags, polymorphic entity_links, attachments 2026-05-31 11:02:58 +10:00
root
1b51c3c18d feat(schema): 005 — tags, entity_tags, entity_links, attachments 2026-05-31 11:02:14 +10:00
root
1d799105ac feat(repos): agents (+ tokens + caps), conversations, messages 2026-05-31 10:36:40 +10:00
root
5e094f347e feat(schema): 004 — agents, agent_tokens, conversations, messages 2026-05-31 10:35:56 +10:00
root
6086cf9a7a fix(schema): tighten tenant boundaries on resources/deps/creds/source_docs
Apply same composite-FK pattern as 001/002 for migration 003:
- resources: add UNIQUE (id, space_id) as FK target.
- resource_dependencies: denormalize space_id, composite FKs on both endpoints
  (enforces both ends of a dep live in the same space at the DB layer).
- resource_credentials: denormalize space_id, composite FK to resources.
- source_docs.resource_id: NOT NULL (tenancy anchor); resource_id was already
  absent from the update FIELDS list so docs cannot move resources.

Repos derive space_id from the resource on addDependency/addCredential so callers
can't fake cross-tenant assignment. 3 regression tests added.
2026-05-31 10:33:17 +10:00