@cerefox/memory 1.17.4 → 1.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENT_GUIDE.md +8 -3
- package/AGENT_QUICK_REFERENCE.md +3 -3
- package/README.md +1 -1
- package/dist/bin/cerefox.js +1542 -1123
- package/dist/frontend/assets/index-01k5C3sG.css +1 -0
- package/dist/frontend/assets/index-HNs8rgY1.js +121 -0
- package/dist/frontend/assets/index-HNs8rgY1.js.map +1 -0
- package/dist/frontend/index.html +2 -2
- package/dist/server-assets/_shared/ef-meta/index.ts +3 -3
- package/dist/server-assets/_shared/embeddings/onnx-embedder.ts +25 -9
- package/dist/server-assets/_shared/mcp-tools/_document-meta.ts +24 -2
- package/dist/server-assets/_shared/mcp-tools/_utils.ts +92 -0
- package/dist/server-assets/_shared/mcp-tools/audit-log.ts +6 -2
- package/dist/server-assets/_shared/mcp-tools/audit-ops.ts +27 -0
- package/dist/server-assets/_shared/mcp-tools/get-document.ts +11 -1
- package/dist/server-assets/_shared/mcp-tools/get-help-content.ts +3 -3
- package/dist/server-assets/_shared/mcp-tools/ingest.ts +55 -17
- package/dist/server-assets/_shared/mcp-tools/metadata-search.ts +3 -1
- package/dist/server-assets/_shared/mcp-tools/partial-edits.ts +8 -13
- package/dist/server-assets/_shared/mcp-tools/search.ts +11 -2
- package/dist/server-assets/_shared/mcp-tools/set-document-metadata.ts +31 -5
- package/dist/server-assets/db/migrations/0012_content_format.sql +1 -1
- package/dist/server-assets/db/migrations/0014_document_relations.sql +1 -1
- package/dist/server-assets/db/migrations/0020_ingest_preserves_source.sql +1 -1
- package/dist/server-assets/db/migrations/0022_rls_on_document_relations.sql +1 -1
- package/dist/server-assets/db/migrations/0032_derive_grants.sql +1 -1
- package/dist/server-assets/db/migrations/0034_drop_vector_indexes.sql +22 -0
- package/dist/server-assets/db/migrations/0035_drop_content_hash_unique.sql +21 -0
- package/dist/server-assets/db/migrations/0036_metadata_revision.sql +43 -0
- package/dist/server-assets/db/rpcs.sql +260 -58
- package/dist/server-assets/db/schema.sql +49 -18
- package/dist/server-assets/supabase/functions/cerefox-get-document/index.ts +8 -1
- package/dist/server-assets/supabase/functions/cerefox-ingest/index.ts +42 -6
- package/docs/api/openapi.json +77 -3
- package/docs/guides/access-paths.md +7 -3
- package/docs/guides/api.md +1 -1
- package/docs/guides/cli.md +5 -3
- package/docs/guides/configuration.md +23 -2
- package/docs/guides/connect-agents.md +60 -25
- package/docs/guides/ops-scripts.md +1 -1
- package/docs/guides/quickstart.md +7 -3
- package/docs/guides/setup-local.md +30 -6
- package/docs/guides/setup-supabase.md +5 -0
- package/docs/guides/upgrading.md +16 -1
- package/package.json +7 -7
- package/dist/frontend/assets/index-B0H_N4if.css +0 -1
- package/dist/frontend/assets/index-D1kdLLr8.js +0 -121
- package/dist/frontend/assets/index-D1kdLLr8.js.map +0 -1
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
-- Requires extensions: vector (pgvector), uuid-ossp
|
|
6
6
|
-- These are enabled at the top of db_deploy.py before this file is applied.
|
|
7
7
|
--
|
|
8
|
-
-- @version: 0.
|
|
8
|
+
-- @version: 0.21.1
|
|
9
9
|
-- The `@version` marker above is read by the schema-version-mismatch banner
|
|
10
10
|
-- (see /api/v1/schema-version). Bump it whenever schema.sql OR rpcs.sql
|
|
11
11
|
-- changes in a way that requires `cerefox server deploy` to be re-run —
|
|
@@ -40,7 +40,9 @@ CREATE TABLE IF NOT EXISTS cerefox_documents (
|
|
|
40
40
|
source TEXT NOT NULL DEFAULT 'manual',
|
|
41
41
|
-- source values: 'file' | 'paste' | 'agent' | 'url' | 'manual'
|
|
42
42
|
source_path TEXT,
|
|
43
|
-
-- SHA-256 of raw markdown content
|
|
43
|
+
-- SHA-256 of raw markdown content: the optimistic-concurrency token, and
|
|
44
|
+
-- the create-time duplicate check (live documents only, in
|
|
45
|
+
-- cerefox_ingest_document). Deliberately NOT unique (0.20.0, #332).
|
|
44
46
|
content_hash TEXT NOT NULL,
|
|
45
47
|
-- #212 (0.12.2): must be a JSON OBJECT — jsonb accepts any JSON value,
|
|
46
48
|
-- but every reader assumes an object, and non-object values were silently
|
|
@@ -51,6 +53,12 @@ CREATE TABLE IF NOT EXISTS cerefox_documents (
|
|
|
51
53
|
metadata JSONB NOT NULL DEFAULT '{}'::jsonb
|
|
52
54
|
CONSTRAINT cerefox_documents_metadata_object
|
|
53
55
|
CHECK (jsonb_typeof(metadata) = 'object'),
|
|
56
|
+
-- #333 (0.21.0): the metadata's optimistic-concurrency token, independent
|
|
57
|
+
-- of content_hash (which covers the body only). Starts at 1 and goes up by
|
|
58
|
+
-- one whenever `metadata` actually changes; maintained by the
|
|
59
|
+
-- trig_cerefox_documents_metadata_revision trigger below, so every writer
|
|
60
|
+
-- is covered and no caller can set it.
|
|
61
|
+
metadata_revision INT NOT NULL DEFAULT 1,
|
|
54
62
|
chunk_count INT NOT NULL DEFAULT 0,
|
|
55
63
|
total_chars INT NOT NULL DEFAULT 0,
|
|
56
64
|
-- review_status: human governance flag. 'approved' = validated by human,
|
|
@@ -71,7 +79,6 @@ CREATE TABLE IF NOT EXISTS cerefox_documents (
|
|
|
71
79
|
-- Search indexes exclude soft-deleted docs. Purge does the real CASCADE DELETE.
|
|
72
80
|
deleted_at TIMESTAMPTZ DEFAULT NULL,
|
|
73
81
|
|
|
74
|
-
CONSTRAINT cerefox_documents_hash_unique UNIQUE (content_hash),
|
|
75
82
|
CONSTRAINT cerefox_documents_review_status_check CHECK (review_status IN ('approved', 'pending_review'))
|
|
76
83
|
);
|
|
77
84
|
|
|
@@ -127,6 +134,11 @@ CREATE TABLE IF NOT EXISTS cerefox_audit_log (
|
|
|
127
134
|
-- author_type: 'user' (human via web UI/CLI) or 'agent' (AI via MCP/Edge Function)
|
|
128
135
|
size_before INT,
|
|
129
136
|
size_after INT,
|
|
137
|
+
-- #333 (0.21.0): on 'update-metadata' entries, the document's metadata
|
|
138
|
+
-- before and after the change, so every metadata change is recoverable
|
|
139
|
+
-- (content versions never captured metadata). NULL on other operations.
|
|
140
|
+
metadata_before JSONB,
|
|
141
|
+
metadata_after JSONB,
|
|
130
142
|
description TEXT NOT NULL DEFAULT '',
|
|
131
143
|
-- description: free-text explaining what changed and why.
|
|
132
144
|
-- Auto-generated for system actions (approval, archival, retention cleanup).
|
|
@@ -166,7 +178,7 @@ CREATE TABLE IF NOT EXISTS cerefox_document_projects (
|
|
|
166
178
|
-- ── Document relations (iteration 29) ─────────────────────────────────────────
|
|
167
179
|
-- Typed, directed edges between documents. rel_type is free text by design so
|
|
168
180
|
-- agents can define new types without a migration; the type dictionary lives in
|
|
169
|
-
-- the RPCs and gives KNOWN types
|
|
181
|
+
-- the RPCs and gives KNOWN types behavior (symmetry, lifecycle side effects).
|
|
170
182
|
-- Design: docs/research/document-relations-and-semantic-graph.md §2.2.
|
|
171
183
|
|
|
172
184
|
CREATE TABLE IF NOT EXISTS cerefox_document_relations (
|
|
@@ -267,17 +279,11 @@ CREATE INDEX IF NOT EXISTS idx_cerefox_chunks_fts
|
|
|
267
279
|
ON cerefox_chunks USING GIN(fts)
|
|
268
280
|
WHERE version_id IS NULL;
|
|
269
281
|
|
|
270
|
-
-- Vector similarity
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
-- Vector similarity — upgrade embedding, current chunks only
|
|
277
|
-
CREATE INDEX IF NOT EXISTS idx_cerefox_chunks_emb_upgrade
|
|
278
|
-
ON cerefox_chunks USING hnsw (embedding_upgrade vector_cosine_ops)
|
|
279
|
-
WITH (m = 16, ef_construction = 64)
|
|
280
|
-
WHERE version_id IS NULL;
|
|
282
|
+
-- Vector similarity: deliberately NO index (0.19.0, migration 0034). Search
|
|
283
|
+
-- ranks vector candidates by an exact scan, which is both correct and fast at
|
|
284
|
+
-- knowledge-base scale; the HNSW indexes it replaced returned too few, mostly
|
|
285
|
+
-- wrong candidates on stores with version churn. Do not add one back without
|
|
286
|
+
-- the measurements in docs/specs/search-calibration.md (scale section).
|
|
281
287
|
|
|
282
288
|
-- Partial unique index: enforces (document_id, chunk_index) uniqueness for
|
|
283
289
|
-- current chunks. Archived chunks are excluded and may share chunk_index values
|
|
@@ -295,7 +301,7 @@ CREATE INDEX IF NOT EXISTS idx_cerefox_chunks_version
|
|
|
295
301
|
CREATE INDEX IF NOT EXISTS idx_cerefox_docs_metadata
|
|
296
302
|
ON cerefox_documents USING GIN(metadata);
|
|
297
303
|
|
|
298
|
-
-- Documents by content_hash (
|
|
304
|
+
-- Documents by content_hash (the create-time duplicate check and hash lookups)
|
|
299
305
|
CREATE INDEX IF NOT EXISTS idx_cerefox_docs_hash
|
|
300
306
|
ON cerefox_documents(content_hash);
|
|
301
307
|
|
|
@@ -350,6 +356,31 @@ CREATE OR REPLACE TRIGGER trig_cerefox_chunks_updated
|
|
|
350
356
|
BEFORE UPDATE ON cerefox_chunks
|
|
351
357
|
FOR EACH ROW EXECUTE FUNCTION cerefox_set_updated_at();
|
|
352
358
|
|
|
359
|
+
-- ── metadata_revision trigger (#333, 0.21.0) ─────────────────────────────────
|
|
360
|
+
-- Bumps metadata_revision when, and only when, metadata actually changes. A
|
|
361
|
+
-- trigger rather than per-RPC code because metadata has several writers (the
|
|
362
|
+
-- metadata RPC, the ingest RPC, direct table updates from older clients), and
|
|
363
|
+
-- a counter that one of them forgets to bump is a token that lies. It also
|
|
364
|
+
-- pins the column on every other UPDATE, so no caller can set it directly.
|
|
365
|
+
CREATE OR REPLACE FUNCTION cerefox_bump_metadata_revision()
|
|
366
|
+
RETURNS TRIGGER
|
|
367
|
+
LANGUAGE plpgsql
|
|
368
|
+
SET search_path = public, pg_catalog
|
|
369
|
+
AS $$
|
|
370
|
+
BEGIN
|
|
371
|
+
IF NEW.metadata IS DISTINCT FROM OLD.metadata THEN
|
|
372
|
+
NEW.metadata_revision := OLD.metadata_revision + 1;
|
|
373
|
+
ELSE
|
|
374
|
+
NEW.metadata_revision := OLD.metadata_revision;
|
|
375
|
+
END IF;
|
|
376
|
+
RETURN NEW;
|
|
377
|
+
END;
|
|
378
|
+
$$;
|
|
379
|
+
|
|
380
|
+
CREATE OR REPLACE TRIGGER trig_cerefox_documents_metadata_revision
|
|
381
|
+
BEFORE UPDATE ON cerefox_documents
|
|
382
|
+
FOR EACH ROW EXECUTE FUNCTION cerefox_bump_metadata_revision();
|
|
383
|
+
|
|
353
384
|
|
|
354
385
|
-- ── Live-database migration ───────────────────────────────────────────────────
|
|
355
386
|
-- When applying this schema to an existing database that still has the old
|
|
@@ -408,7 +439,7 @@ ON CONFLICT (key) DO NOTHING;
|
|
|
408
439
|
-- The review workflow (agent writes land pending_review, a person approves)
|
|
409
440
|
-- is OFF on a fresh install (#241): most stores have no reviewer, and a queue
|
|
410
441
|
-- nobody drains is noise. Migration 0031 seeds TRUE on stores that predate the
|
|
411
|
-
-- flag, so an upgrade never changes
|
|
442
|
+
-- flag, so an upgrade never changes behavior. Only this seed and that
|
|
412
443
|
-- migration ever write the value; the toggle itself is `cerefox config set`.
|
|
413
444
|
INSERT INTO cerefox_config (key, value)
|
|
414
445
|
VALUES ('review_workflow_enabled', 'false')
|
|
@@ -491,7 +522,7 @@ ALTER TABLE cerefox_document_relations ENABLE ROW LEVEL SECURITY;
|
|
|
491
522
|
-- Guarded: on the local (World B) stack this file deploys BEFORE roles.sql
|
|
492
523
|
-- creates service_role, so missing-role must be a no-op (roles.sql re-runs the
|
|
493
524
|
-- grants implicitly via its own PostgREST wiring; the next deploy picks them up).
|
|
494
|
-
-- DERIVED from the
|
|
525
|
+
-- DERIVED from the catalog, never listed. The list used to be a hand-written
|
|
495
526
|
-- ARRAY and it drifted: cerefox_document_relations was added above and never
|
|
496
527
|
-- added to the array, so a FRESH cloud deploy left that table with no Data API
|
|
497
528
|
-- grant. Upgrades hid it (migration 0014 grants it inline) and the self-hosted
|
|
@@ -19,7 +19,8 @@ import { callerIdentity } from "../../../_shared/mcp-tools/identity.ts";
|
|
|
19
19
|
* { document_id: string, version_id?: string | null }
|
|
20
20
|
*
|
|
21
21
|
* Response (200):
|
|
22
|
-
* { document_id, doc_title, full_content, chunk_count, total_chars, is_archived, version_id, content_hash
|
|
22
|
+
* { document_id, doc_title, full_content, chunk_count, total_chars, is_archived, version_id, content_hash,
|
|
23
|
+
* metadata, metadata_revision }
|
|
23
24
|
* Response (404):
|
|
24
25
|
* { error: "Document not found" }
|
|
25
26
|
* Response (400):
|
|
@@ -108,6 +109,8 @@ Deno.serve(async (req: Request): Promise<Response> => {
|
|
|
108
109
|
chunk_count?: number;
|
|
109
110
|
total_chars?: number;
|
|
110
111
|
content_hash?: string;
|
|
112
|
+
doc_metadata?: Record<string, unknown> | null;
|
|
113
|
+
metadata_revision?: number | null;
|
|
111
114
|
} | undefined;
|
|
112
115
|
|
|
113
116
|
if (!row) {
|
|
@@ -138,6 +141,10 @@ Deno.serve(async (req: Request): Promise<Response> => {
|
|
|
138
141
|
// Optimistic-concurrency token (iter-32): always the CURRENT hash —
|
|
139
142
|
// pass back as expected_content_hash when updating via ingest.
|
|
140
143
|
content_hash: row.content_hash ?? null,
|
|
144
|
+
// #333: the metadata and its own token — pass metadata_revision back as
|
|
145
|
+
// expected_metadata_revision when changing metadata via ingest.
|
|
146
|
+
metadata: row.doc_metadata ?? {},
|
|
147
|
+
metadata_revision: version_id === null ? (row.metadata_revision ?? null) : null,
|
|
141
148
|
}),
|
|
142
149
|
{ status: 200, headers: { ...CORS_HEADERS, "Content-Type": "application/json" } },
|
|
143
150
|
);
|
|
@@ -3,6 +3,7 @@ import { createClient } from "jsr:@supabase/supabase-js@2";
|
|
|
3
3
|
import { isVersionRequest, versionResponse } from "../../../_shared/ef-meta/index.ts";
|
|
4
4
|
import { efAuthGate } from "../../../_shared/ef-auth/index.ts";
|
|
5
5
|
import { callerIdentity } from "../../../_shared/mcp-tools/identity.ts";
|
|
6
|
+
import { duplicateContentDocumentId, legacyContentUniqueError } from "../../../_shared/mcp-tools/_utils.ts";
|
|
6
7
|
import { capEmbeddingInput } from "../../../_shared/embeddings/index.ts";
|
|
7
8
|
import {
|
|
8
9
|
ensureDocumentInProject,
|
|
@@ -61,24 +62,26 @@ interface IngestRequest {
|
|
|
61
62
|
expected_content_hash?: string;
|
|
62
63
|
// Explicitly skip the concurrency check (external source of truth).
|
|
63
64
|
last_write_wins?: boolean;
|
|
65
|
+
// #333: the metadata's token, checked when `metadata` is passed on an update
|
|
66
|
+
// (metadata merges; a null value removes a key).
|
|
67
|
+
expected_metadata_revision?: number;
|
|
64
68
|
}
|
|
65
69
|
|
|
66
70
|
// Map the RPC's expected/validation errors to proper HTTP responses:
|
|
67
71
|
// CEREFOX_CONFLICT → 409 (stale optimistic-concurrency token)
|
|
68
72
|
// CEREFOX_TOKEN_REQUIRED → 400 (missing expected_content_hash)
|
|
69
|
-
//
|
|
70
|
-
// holds identical content)
|
|
73
|
+
// CEREFOX_METADATA_CONFLICT → 409 (stale expected_metadata_revision, #333)
|
|
71
74
|
// Returns null for genuinely unexpected errors (→ 500 at the call site).
|
|
72
75
|
function concurrencyErrorResponse(
|
|
73
76
|
message: string,
|
|
74
77
|
headers: Record<string, string>,
|
|
75
78
|
): Response | null {
|
|
76
|
-
if (message.includes("
|
|
79
|
+
if (message.includes("CEREFOX_METADATA_CONFLICT")) {
|
|
77
80
|
return new Response(
|
|
78
81
|
JSON.stringify({
|
|
79
|
-
error: "
|
|
82
|
+
error: "metadata_conflict",
|
|
80
83
|
message:
|
|
81
|
-
"
|
|
84
|
+
"The document's metadata changed since it was read. Re-read it (getDocument), reapply your change, and retry with the new expected_metadata_revision.",
|
|
82
85
|
detail: message,
|
|
83
86
|
}),
|
|
84
87
|
{ status: 409, headers },
|
|
@@ -278,7 +281,7 @@ Deno.serve(async (req: Request) => {
|
|
|
278
281
|
// metadata: null = "not provided" — the RPC keeps existing metadata on
|
|
279
282
|
// update and uses {} on create (v0.11.1; a `= {}` default here used to wipe
|
|
280
283
|
// a document's tags on every content update that didn't re-pass them).
|
|
281
|
-
const { title, content, document_id = null, project_name, source = "agent", metadata = null, update_if_exists = false, author_type = "agent", expected_content_hash = null, last_write_wins = false } = body;
|
|
284
|
+
const { title, content, document_id = null, project_name, source = "agent", metadata = null, update_if_exists = false, author_type = "agent", expected_content_hash = null, last_write_wins = false, expected_metadata_revision = null } = body;
|
|
282
285
|
const callerName = callerIdentity(body as unknown as Record<string, unknown>);
|
|
283
286
|
const author = callerName ?? "agent";
|
|
284
287
|
|
|
@@ -436,6 +439,9 @@ Deno.serve(async (req: Request) => {
|
|
|
436
439
|
p_source_label: source,
|
|
437
440
|
p_expected_content_hash: expected_content_hash,
|
|
438
441
|
p_last_write_wins: last_write_wins,
|
|
442
|
+
// Only when set (#333 review): a null would name a signature a pre-0.21.0
|
|
443
|
+
// schema lacks, failing the whole update.
|
|
444
|
+
...(expected_metadata_revision != null ? { p_expected_metadata_revision: expected_metadata_revision } : {}),
|
|
439
445
|
p_content_format: CONTENT_FORMAT_BLIND_STITCH,
|
|
440
446
|
});
|
|
441
447
|
|
|
@@ -554,6 +560,9 @@ Deno.serve(async (req: Request) => {
|
|
|
554
560
|
p_source_label: source,
|
|
555
561
|
p_expected_content_hash: expected_content_hash,
|
|
556
562
|
p_last_write_wins: last_write_wins,
|
|
563
|
+
// Only when set (#333 review): a null would name a signature a pre-0.21.0
|
|
564
|
+
// schema lacks, failing the whole update.
|
|
565
|
+
...(expected_metadata_revision != null ? { p_expected_metadata_revision: expected_metadata_revision } : {}),
|
|
557
566
|
p_content_format: CONTENT_FORMAT_BLIND_STITCH,
|
|
558
567
|
});
|
|
559
568
|
|
|
@@ -600,10 +609,14 @@ Deno.serve(async (req: Request) => {
|
|
|
600
609
|
}
|
|
601
610
|
|
|
602
611
|
// ── Hash deduplication (normal create path) ────────────────────────────────
|
|
612
|
+
// Creates only, against LIVE documents (#332). Saves the embedding spend;
|
|
613
|
+
// the RPC repeats the check authoritatively.
|
|
603
614
|
const { data: hashMatch } = await supabase
|
|
604
615
|
.from("cerefox_documents")
|
|
605
616
|
.select("id, title")
|
|
606
617
|
.eq("content_hash", contentHash)
|
|
618
|
+
.is("deleted_at", null)
|
|
619
|
+
.order("created_at", { ascending: true })
|
|
607
620
|
.limit(1);
|
|
608
621
|
|
|
609
622
|
if (hashMatch?.length) {
|
|
@@ -665,7 +678,30 @@ Deno.serve(async (req: Request) => {
|
|
|
665
678
|
p_content_format: CONTENT_FORMAT_BLIND_STITCH,
|
|
666
679
|
});
|
|
667
680
|
|
|
681
|
+
// The RPC's duplicate check caught an identical create that landed after
|
|
682
|
+
// the pre-check above. Nothing was written; answer as the pre-check does.
|
|
683
|
+
const duplicateId = duplicateContentDocumentId(ingestErr?.message ?? "");
|
|
684
|
+
if (duplicateId) {
|
|
685
|
+
const { data: existing } = await supabase
|
|
686
|
+
.from("cerefox_documents")
|
|
687
|
+
.select("title")
|
|
688
|
+
.eq("id", duplicateId)
|
|
689
|
+
.maybeSingle();
|
|
690
|
+
return new Response(
|
|
691
|
+
JSON.stringify({
|
|
692
|
+
document_id: duplicateId,
|
|
693
|
+
title: existing?.title ?? title.trim(),
|
|
694
|
+
skipped: true,
|
|
695
|
+
message: "Document already exists (content hash match)",
|
|
696
|
+
}),
|
|
697
|
+
{ headers },
|
|
698
|
+
);
|
|
699
|
+
}
|
|
668
700
|
if (ingestErr || !ingestResult?.length) {
|
|
701
|
+
// Functions deployed ahead of the schema still meet the pre-0.20.0
|
|
702
|
+
// store-wide content constraint.
|
|
703
|
+
const legacy = legacyContentUniqueError(ingestErr?.message ?? "");
|
|
704
|
+
if (legacy) return new Response(JSON.stringify({ error: legacy.message }), { status: 409, headers });
|
|
669
705
|
return new Response(
|
|
670
706
|
JSON.stringify({ error: `Ingest RPC failed: ${ingestErr?.message ?? "no data returned"}` }),
|
|
671
707
|
{ status: 500, headers },
|
package/docs/api/openapi.json
CHANGED
|
@@ -206,6 +206,34 @@
|
|
|
206
206
|
"created_at": {
|
|
207
207
|
"default": "",
|
|
208
208
|
"type": "string"
|
|
209
|
+
},
|
|
210
|
+
"metadata_before": {
|
|
211
|
+
"anyOf": [
|
|
212
|
+
{
|
|
213
|
+
"type": "object",
|
|
214
|
+
"propertyNames": {
|
|
215
|
+
"type": "string"
|
|
216
|
+
},
|
|
217
|
+
"additionalProperties": {}
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
"type": "null"
|
|
221
|
+
}
|
|
222
|
+
]
|
|
223
|
+
},
|
|
224
|
+
"metadata_after": {
|
|
225
|
+
"anyOf": [
|
|
226
|
+
{
|
|
227
|
+
"type": "object",
|
|
228
|
+
"propertyNames": {
|
|
229
|
+
"type": "string"
|
|
230
|
+
},
|
|
231
|
+
"additionalProperties": {}
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
"type": "null"
|
|
235
|
+
}
|
|
236
|
+
]
|
|
209
237
|
}
|
|
210
238
|
},
|
|
211
239
|
"required": [
|
|
@@ -1498,6 +1526,18 @@
|
|
|
1498
1526
|
"null"
|
|
1499
1527
|
]
|
|
1500
1528
|
},
|
|
1529
|
+
"metadata_revision": {
|
|
1530
|
+
"anyOf": [
|
|
1531
|
+
{
|
|
1532
|
+
"type": "integer",
|
|
1533
|
+
"minimum": -9007199254740991,
|
|
1534
|
+
"maximum": 9007199254740991
|
|
1535
|
+
},
|
|
1536
|
+
{
|
|
1537
|
+
"type": "null"
|
|
1538
|
+
}
|
|
1539
|
+
]
|
|
1540
|
+
},
|
|
1501
1541
|
"versions": {
|
|
1502
1542
|
"default": [],
|
|
1503
1543
|
"type": "array",
|
|
@@ -2007,7 +2047,7 @@
|
|
|
2007
2047
|
}
|
|
2008
2048
|
},
|
|
2009
2049
|
"metadata": {
|
|
2010
|
-
"description": "Replaces the metadata. `{}` clears every key; omit to leave it unchanged.",
|
|
2050
|
+
"description": "Replaces the metadata (this route edits it as a whole form). `{}` clears every key; omit to leave it unchanged. Send expected_metadata_revision with it.",
|
|
2011
2051
|
"type": "object",
|
|
2012
2052
|
"propertyNames": {
|
|
2013
2053
|
"type": "string"
|
|
@@ -2021,6 +2061,19 @@
|
|
|
2021
2061
|
"null"
|
|
2022
2062
|
]
|
|
2023
2063
|
},
|
|
2064
|
+
"expected_metadata_revision": {
|
|
2065
|
+
"description": "The metadata_revision you read (GET /documents/{id}). Required with `metadata` for an identified caller; a stale one is a 409.",
|
|
2066
|
+
"anyOf": [
|
|
2067
|
+
{
|
|
2068
|
+
"type": "integer",
|
|
2069
|
+
"minimum": -9007199254740991,
|
|
2070
|
+
"maximum": 9007199254740991
|
|
2071
|
+
},
|
|
2072
|
+
{
|
|
2073
|
+
"type": "null"
|
|
2074
|
+
}
|
|
2075
|
+
]
|
|
2076
|
+
},
|
|
2024
2077
|
"last_write_wins": {
|
|
2025
2078
|
"description": "Skip the concurrency check. Only when an external source of truth makes a conflict meaningless.",
|
|
2026
2079
|
"type": "boolean"
|
|
@@ -3471,12 +3524,19 @@
|
|
|
3471
3524
|
"type": "string"
|
|
3472
3525
|
},
|
|
3473
3526
|
"metadata": {
|
|
3527
|
+
"description": "On an update it MERGES into the stored metadata: a null value removes a key, omitted keys are kept.",
|
|
3474
3528
|
"type": "object",
|
|
3475
3529
|
"propertyNames": {
|
|
3476
3530
|
"type": "string"
|
|
3477
3531
|
},
|
|
3478
3532
|
"additionalProperties": {}
|
|
3479
3533
|
},
|
|
3534
|
+
"expected_metadata_revision": {
|
|
3535
|
+
"description": "Optional, with metadata on an update: the metadata_revision you read. A stale one is a 409.",
|
|
3536
|
+
"type": "integer",
|
|
3537
|
+
"minimum": -9007199254740991,
|
|
3538
|
+
"maximum": 9007199254740991
|
|
3539
|
+
},
|
|
3480
3540
|
"source": {
|
|
3481
3541
|
"description": "Origin label. Defaults to \"paste\".",
|
|
3482
3542
|
"type": "string"
|
|
@@ -5731,7 +5791,7 @@
|
|
|
5731
5791
|
},
|
|
5732
5792
|
"/api/v1/version": {
|
|
5733
5793
|
"get": {
|
|
5734
|
-
"summary": "Server version and
|
|
5794
|
+
"summary": "Server version, environment label, and the newest release on npm as the server last checked it (`latest`, plus `update_command` when it is newer).",
|
|
5735
5795
|
"operationId": "get_version",
|
|
5736
5796
|
"responses": {
|
|
5737
5797
|
"200": {
|
|
@@ -5761,13 +5821,27 @@
|
|
|
5761
5821
|
"string",
|
|
5762
5822
|
"null"
|
|
5763
5823
|
]
|
|
5824
|
+
},
|
|
5825
|
+
"latest": {
|
|
5826
|
+
"type": [
|
|
5827
|
+
"string",
|
|
5828
|
+
"null"
|
|
5829
|
+
]
|
|
5830
|
+
},
|
|
5831
|
+
"update_command": {
|
|
5832
|
+
"type": [
|
|
5833
|
+
"string",
|
|
5834
|
+
"null"
|
|
5835
|
+
]
|
|
5764
5836
|
}
|
|
5765
5837
|
},
|
|
5766
5838
|
"required": [
|
|
5767
5839
|
"version",
|
|
5768
5840
|
"git_commit_short",
|
|
5769
5841
|
"build_date",
|
|
5770
|
-
"env_label"
|
|
5842
|
+
"env_label",
|
|
5843
|
+
"latest",
|
|
5844
|
+
"update_command"
|
|
5771
5845
|
],
|
|
5772
5846
|
"additionalProperties": false
|
|
5773
5847
|
}
|
|
@@ -60,8 +60,12 @@ Postgres RPCs
|
|
|
60
60
|
```
|
|
61
61
|
|
|
62
62
|
**ChatGPT Custom GPT Actions** call the eight primitive Edge Functions directly over HTTPS
|
|
63
|
-
using an OpenAPI schema. `cerefox-mcp` is not involved
|
|
64
|
-
|
|
63
|
+
using an OpenAPI schema. `cerefox-mcp` is not involved. OpenAI retires Custom GPTs on **December 11, 2026**, and custom actions do not carry over to its plugins, so this path ends
|
|
64
|
+
then. ChatGPT now supports remote MCP connectors (OAuth or no auth, no static bearer
|
|
65
|
+
token), so its replacement is expected to be a connector to `cerefox-mcp` over OAuth,
|
|
66
|
+
like cloud Claude; see
|
|
67
|
+
[`chatgpt-after-custom-gpts.md`](../research/chatgpt-after-custom-gpts.md) and
|
|
68
|
+
[#326](https://github.com/fstamatelopoulos/cerefox/issues/326).
|
|
65
69
|
|
|
66
70
|
**curl / scripts / custom HTTP clients** can also call the primitives directly using the
|
|
67
71
|
same Cerefox token as a Bearer token.
|
|
@@ -213,7 +217,7 @@ single container with an internally-held token.
|
|
|
213
217
|
| Claude Code / Cursor | HTTPS → `cerefox-mcp` | Cerefox access token (`cfx_pat_…`) | Advanced/fallback; prefer local MCP for daily use |
|
|
214
218
|
| Claude Desktop | HTTPS → `cerefox-mcp` (via `supergateway`) | Cerefox access token (`cfx_pat_…`) | Advanced/fallback; prefer local MCP for daily use |
|
|
215
219
|
| **Cloud Claude (claude.ai web + mobile)** | HTTPS → `cerefox-mcp` over **OAuth 2.1** | Owner-pinned OAuth access token (JWKS-verified) | Optional; memory in the browser + on the phone |
|
|
216
|
-
| ChatGPT Custom GPT | HTTPS → primitive Edge Functions | Cerefox access token (`cfx_pat_…`) | AI assistant via GPT Actions |
|
|
220
|
+
| ChatGPT Custom GPT | HTTPS → primitive Edge Functions | Cerefox access token (`cfx_pat_…`) | AI assistant via GPT Actions; **ends 2026-12-11** (Custom GPTs retire) |
|
|
217
221
|
| curl / HTTP scripts | HTTPS → primitive Edge Functions | Cerefox access token (`cfx_pat_…`) | Ad-hoc queries, automation |
|
|
218
222
|
| Web UI (`cerefox web`) | Supabase REST API | Secret key (or legacy service_role) | Web UI backend (TS Hono) |
|
|
219
223
|
| **HTTP client → `/api/v1`** | Plain HTTP to `cerefox web` / Cerefox Local | **None from loopback; API key otherwise** (v1.12.0) — `cerefox api-key generate` / `cerefox-local api-key` | A local program or bot harness that wants HTTP rather than MCP. Names itself with `X-Cerefox-Author` / `X-Cerefox-Requestor` (v1.11.0): a declared label for attribution, not a verified identity. See [`api.md`](api.md) |
|
package/docs/guides/api.md
CHANGED
|
@@ -179,7 +179,7 @@ default; Cerefox Local picks its own port and `cerefox-local status` prints it).
|
|
|
179
179
|
|
|
180
180
|
| Method + path | Purpose |
|
|
181
181
|
|---|---|
|
|
182
|
-
| `GET /version` | Server version and
|
|
182
|
+
| `GET /version` | Server version, environment label, and the newest release on npm as the server last checked it (`latest`, plus `update_command` when it is newer). |
|
|
183
183
|
| `GET /schema-version` | Deployed schema version. |
|
|
184
184
|
| `GET /openapi.json` | This API's OpenAPI 3.1 description, served by the version you are talking to. Every `/api/v1` response points at it with `Link: </api/v1/openapi.json>; rel="service-desc"` (RFC 8631). |
|
|
185
185
|
| `GET /search?q=…` | Hybrid search (FTS + semantic). `&review_status=approved\|pending_review` filters server-side; `400` while the review workflow is off. The response's `embedder` (`openai` or `local`) says which scale the scores are on. |
|
package/docs/guides/cli.md
CHANGED
|
@@ -278,7 +278,9 @@ cerefox document set-metadata [OPTIONS] DOCUMENT_ID
|
|
|
278
278
|
| `-s, --set <key=value...>` | Set a key. Repeatable. |
|
|
279
279
|
| `-r, --remove <key...>` | Remove a key. Repeatable. |
|
|
280
280
|
| `--json <object>` | A JSON object of keys to set; a `null` value removes that key. |
|
|
281
|
-
| `--replace` | Set the metadata to **exactly** what was given, discarding every key not listed. |
|
|
281
|
+
| `--replace` | Set the metadata to **exactly** what was given, discarding every key not listed. Needs `--expected-metadata-revision` or `--last-write-wins` (v1.19.0). |
|
|
282
|
+
| `--expected-metadata-revision <n>` | The `metadata_revision` you read (`document get` shows it). The write fails if the metadata changed since. Optional on a merge, required with `--replace`. |
|
|
283
|
+
| `--last-write-wins` | Replace without a revision, regardless of concurrent changes (recorded in the audit log). |
|
|
282
284
|
| `-a, --author <name>` | Caller identity (audit log). |
|
|
283
285
|
| `--author-type <type>` | `user` (default) or `agent`. |
|
|
284
286
|
| `--json-out` | Emit the result as JSON. |
|
|
@@ -773,11 +775,11 @@ These flat commands handle install, configuration, and health. Run any with `--h
|
|
|
773
775
|
| Command | Purpose |
|
|
774
776
|
|---|---|
|
|
775
777
|
| `cerefox init` | Interactive first-run setup; writes `~/.cerefox/.env`, offers `server deploy` + self-docs ingest. |
|
|
776
|
-
| `cerefox doctor` | Diagnose the install (credentials, DB reachability, schema version, whether the review workflow is on). |
|
|
778
|
+
| `cerefox doctor` | Diagnose the install (credentials, DB reachability, schema version, whether the review workflow is on, whether a newer release is out). |
|
|
777
779
|
| `cerefox status` | Show connection + schema status. |
|
|
778
780
|
| `cerefox configure-agent --tool <client>` | Write MCP client config (`claude-code`, `claude-desktop`, `cursor`, `codex`, `gemini`). |
|
|
779
781
|
| `cerefox token generate` / `rotate` / `list` | Manage the Cerefox access token (`cfx_pat_…`) — the Edge Function Bearer credential (remote MCP, GPT Actions, curl). See the [`cerefox token`](#cerefox-token-generate--cerefox-token-rotate--cerefox-token-list) section above. |
|
|
780
|
-
| `cerefox self-update` | Update the installed `@cerefox/memory` package. |
|
|
782
|
+
| `cerefox self-update` | Update the installed `@cerefox/memory` package. Other commands print a one-line notice when a newer release is out; see [configuration.md → Update check](configuration.md#update-check) to turn it off. |
|
|
781
783
|
| `cerefox completion` | Emit a shell completion script. |
|
|
782
784
|
| `cerefox backup create` / `cerefox backup restore` | File-system backup / restore of the knowledge base (see [`ops-scripts.md`](ops-scripts.md)). |
|
|
783
785
|
|
|
@@ -183,7 +183,7 @@ This handles intermittent OpenAI API errors (500s) that would otherwise cause se
|
|
|
183
183
|
| `CEREFOX_MIN_TERM_COVERAGE` | **Retired in v1.1.0 — no longer read.** | Now a store setting: `cerefox config set min_term_coverage <value>`, or the **Settings** page. A per-call override still works (`cerefox search --min-term-coverage`, the MCP `min_term_coverage` param). `cerefox doctor` and the Settings page report the variable if it is still set. |
|
|
184
184
|
| `CEREFOX_EMBED_MAX_INPUT_CHARS` | `20000` | Safety cap on the characters sent to the embedding model per input. The full chunk content is always stored and reconstructed untouched; only the embedding uses the (rare) truncated prefix, so an oversized chunk can never fail an ingest. |
|
|
185
185
|
| `CEREFOX_MODELS_DIR` | `~/.cerefox/models` (in-container: inside the data volume) | Where the local embedder caches downloaded model weights (Cerefox Local; `CEREFOX_EMBEDDER=local`). |
|
|
186
|
-
| `CEREFOX_ONNX_BATCH` | `
|
|
186
|
+
| `CEREFOX_ONNX_BATCH` | `1` | Texts per local-embedder inference call. Peak memory grows with this (each batch is padded to its longest chunk), and on CPU a larger batch is not faster, so leave it at 1. Was 4 before v1.19.0 (#335). Cerefox Local forwards it from its `.env` (from v1.19.0; after upgrading, run `cerefox-local init` once for a value set there to apply). |
|
|
187
187
|
|
|
188
188
|
### Metadata filter
|
|
189
189
|
|
|
@@ -344,6 +344,27 @@ there matters.
|
|
|
344
344
|
|
|
345
345
|
---
|
|
346
346
|
|
|
347
|
+
## Update check
|
|
348
|
+
|
|
349
|
+
Cerefox tells you when a newer release is on npm (v1.18.0, #323): one line after
|
|
350
|
+
a CLI command (at most once a day, only at a terminal), an `updates` row in
|
|
351
|
+
`cerefox doctor`, and a note in the web UI footer. Inside the Cerefox Local
|
|
352
|
+
container it points at `cerefox-local upgrade` instead of `cerefox self-update`.
|
|
353
|
+
|
|
354
|
+
The only network call is a GET of the `latest` release from the npm registry
|
|
355
|
+
(`registry.npmjs.org`), the same request `cerefox self-update` makes. It is made
|
|
356
|
+
by `cerefox web` (at startup, then daily), `cerefox mcp` (at startup, when the
|
|
357
|
+
last answer is over a day old), `cerefox doctor` and `cerefox self-update`.
|
|
358
|
+
Every other command only reads the saved answer, `update-check.json` in
|
|
359
|
+
`~/.cerefox` (or in `CEREFOX_CONFIG_DIR`, when set), so it never adds latency.
|
|
360
|
+
Offline, the check stays quiet.
|
|
361
|
+
|
|
362
|
+
| Variable | Default | Description |
|
|
363
|
+
|----------|---------|-------------|
|
|
364
|
+
| `CEREFOX_NO_UPDATE_CHECK` | _(unset)_ | Set to `1` to turn the check off entirely: no request to npm, and no notice anywhere. `NO_UPDATE_NOTIFIER` (the convention other CLIs use) and `CI` do the same. |
|
|
365
|
+
|
|
366
|
+
---
|
|
367
|
+
|
|
347
368
|
## Logging
|
|
348
369
|
|
|
349
370
|
| Variable | Default | Description |
|
|
@@ -422,7 +443,7 @@ cerefox server deploy --functions-only
|
|
|
422
443
|
|
|
423
444
|
Contributors with a repo clone can instead redeploy individual functions with `npx supabase functions deploy cerefox-search` (and `cerefox-ingest`), or through the Supabase Dashboard → Edge Functions → Deploy.
|
|
424
445
|
|
|
425
|
-
> **If you only use the local MCP server** (Claude
|
|
446
|
+
> **If you only use the local MCP server** (Claude Code, Claude Desktop, Codex, Cursor), Step 3 is
|
|
426
447
|
> optional — the Edge Functions are only used for GPT Actions and direct HTTP access.
|
|
427
448
|
|
|
428
449
|
> **Future improvement**: the Edge Functions will be updated to read model config from Supabase
|