@cerefox/memory 1.12.1 → 1.13.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 +2 -2
- package/dist/bin/cerefox.js +6947 -7329
- package/dist/frontend/assets/index-InRztcXr.js +121 -0
- package/dist/frontend/assets/index-InRztcXr.js.map +1 -0
- package/dist/frontend/index.html +1 -1
- package/dist/server-assets/_shared/ef-meta/index.ts +3 -3
- package/dist/server-assets/_shared/mcp-tools/feature-flags.ts +41 -23
- package/dist/server-assets/_shared/mcp-tools/ingest.ts +2 -4
- package/dist/server-assets/_shared/mcp-tools/metadata-search.ts +6 -1
- package/dist/server-assets/_shared/mcp-tools/partial-edits.ts +2 -2
- package/dist/server-assets/db/migrations/0031_review_workflow_toggle.sql +72 -0
- package/dist/server-assets/db/rpcs.sql +46 -8
- package/dist/server-assets/db/schema.sql +13 -2
- package/dist/server-assets/supabase/functions/cerefox-ingest/index.ts +2 -4
- package/dist/server-assets/supabase/functions/cerefox-metadata-search/index.ts +12 -2
- package/docs/guides/access-paths.md +1 -1
- package/docs/guides/cli.md +11 -4
- package/docs/guides/configuration.md +56 -8
- package/docs/guides/connect-agents.md +9 -5
- package/docs/guides/upgrading.md +21 -1
- package/package.json +1 -1
- package/dist/frontend/assets/index-P1F2Ldl9.js +0 -121
- package/dist/frontend/assets/index-P1F2Ldl9.js.map +0 -1
package/dist/frontend/index.html
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
href="https://fonts.googleapis.com/css2?family=Geist:wght@300;400;500;600;700&display=swap"
|
|
16
16
|
/>
|
|
17
17
|
<title>Cerefox</title>
|
|
18
|
-
<script type="module" crossorigin src="/app/assets/index-
|
|
18
|
+
<script type="module" crossorigin src="/app/assets/index-InRztcXr.js"></script>
|
|
19
19
|
<link rel="stylesheet" crossorigin href="/app/assets/index-Dm_zCch4.css">
|
|
20
20
|
</head>
|
|
21
21
|
<body>
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
* doesn't touch `supabase/functions/` leaves it alone).
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
|
-
export const EF_VERSION = "1.
|
|
21
|
+
export const EF_VERSION = "1.13.0";
|
|
22
22
|
|
|
23
23
|
/**
|
|
24
24
|
* The Cerefox RELEASE version — what `cerefox --version` reports and what npm
|
|
@@ -36,7 +36,7 @@ export const EF_VERSION = "1.12.1";
|
|
|
36
36
|
* is imported by the Deno Edge Functions, which cannot reach into the npm
|
|
37
37
|
* package.
|
|
38
38
|
*/
|
|
39
|
-
export const CEREFOX_VERSION = "1.
|
|
39
|
+
export const CEREFOX_VERSION = "1.13.0";
|
|
40
40
|
|
|
41
41
|
/**
|
|
42
42
|
* The most recent version whose EF-side SOURCE actually changed (#127).
|
|
@@ -46,7 +46,7 @@ export const CEREFOX_VERSION = "1.12.1";
|
|
|
46
46
|
* `cut_release.ts` ONLY when EF source changed since the last tag; doctor
|
|
47
47
|
* uses it to stay silent on label-only drift.
|
|
48
48
|
*/
|
|
49
|
-
export const EF_LAST_CHANGED = "1.
|
|
49
|
+
export const EF_LAST_CHANGED = "1.13.0";
|
|
50
50
|
|
|
51
51
|
/**
|
|
52
52
|
* The 8 peer EFs the cerefox-mcp aggregator probes (excludes cerefox-mcp
|
|
@@ -1,17 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Optional-feature gating
|
|
2
|
+
* Optional-feature gating, read from `cerefox_config`.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* `lifecycle_status` column defaults to `'active'`, search is untouched — but
|
|
6
|
-
* the four relation tools would still appear in every agent's tool list, and an
|
|
7
|
-
* agent that sees a tool may decide to use it. For a feature we intend to
|
|
8
|
-
* evolve through experimentation, that is not "optional" enough.
|
|
4
|
+
* Two features are governed by store-level boolean flags:
|
|
9
5
|
*
|
|
10
|
-
*
|
|
11
|
-
* `
|
|
12
|
-
*
|
|
6
|
+
* - **Document relations** (iteration 29) ship **dormant**: the table sits
|
|
7
|
+
* empty, the `lifecycle_status` column defaults to `'active'`, search is
|
|
8
|
+
* untouched — but the four relation tools would still appear in every
|
|
9
|
+
* agent's tool list, and an agent that sees a tool may decide to use it.
|
|
10
|
+
* For a feature we intend to evolve through experimentation, that is not
|
|
11
|
+
* "optional" enough. Gated on `relations_enabled` (default **false**).
|
|
13
12
|
*
|
|
14
|
-
*
|
|
13
|
+
* - **The review workflow** (#241): agent writes land `pending_review` and a
|
|
14
|
+
* person approves them. Gated on `review_workflow_enabled` (**false** on a
|
|
15
|
+
* fresh install, **true** on a store that predates the flag). The write-side
|
|
16
|
+
* decision lives in `cerefox_ingest_document`; this reader is for the
|
|
17
|
+
* presentation side — every surface that would show a `review_status` asks
|
|
18
|
+
* here first and omits it when the workflow is off.
|
|
19
|
+
*
|
|
20
|
+
* Both are read through the same RPC as every other setting, so turning one
|
|
21
|
+
* on is one command: `cerefox config set <key> true`.
|
|
15
22
|
*
|
|
16
23
|
* Failure mode is deliberately closed: if the config read fails (older schema,
|
|
17
24
|
* transient error), the feature stays hidden rather than appearing
|
|
@@ -29,27 +36,29 @@ export const RELATION_TOOL_NAMES: ReadonlySet<string> = new Set([
|
|
|
29
36
|
]);
|
|
30
37
|
|
|
31
38
|
/**
|
|
32
|
-
* Cached per process. `tools/list` happens once per session, but
|
|
33
|
-
* checks too (a long-lived session could hold a stale list), and
|
|
34
|
-
* per call is not worth paying.
|
|
39
|
+
* Cached per process, per key. `tools/list` happens once per session, but
|
|
40
|
+
* `tools/call` checks too (a long-lived session could hold a stale list), and
|
|
41
|
+
* a round trip per call is not worth paying. The web server busts the cache
|
|
42
|
+
* when it writes config itself; a flip made from another process shows up
|
|
43
|
+
* within the TTL.
|
|
35
44
|
*/
|
|
36
|
-
const CACHE_TTL_MS =
|
|
37
|
-
|
|
45
|
+
const CACHE_TTL_MS = 15_000;
|
|
46
|
+
const cache = new Map<string, { value: boolean; at: number }>();
|
|
38
47
|
|
|
39
|
-
/** Test seam: drop the cache so a flag change
|
|
48
|
+
/** Test seam (and the web config route's): drop the cache so a flag change
|
|
49
|
+
* is picked up immediately. */
|
|
40
50
|
export function resetFeatureFlagCache(): void {
|
|
41
|
-
|
|
51
|
+
cache.clear();
|
|
42
52
|
}
|
|
43
53
|
|
|
44
|
-
|
|
45
|
-
|
|
54
|
+
async function readBoolFlag(supabase: MCPSupabaseClient, key: string): Promise<boolean> {
|
|
55
|
+
const hit = cache.get(key);
|
|
56
|
+
if (hit && Date.now() - hit.at < CACHE_TTL_MS) return hit.value;
|
|
46
57
|
try {
|
|
47
|
-
const { data, error } = await supabase.rpc("cerefox_get_config", {
|
|
48
|
-
p_key: "relations_enabled",
|
|
49
|
-
});
|
|
58
|
+
const { data, error } = await supabase.rpc("cerefox_get_config", { p_key: key });
|
|
50
59
|
if (error) throw new Error(error.message);
|
|
51
60
|
const value = String(data ?? "").trim().toLowerCase() === "true";
|
|
52
|
-
|
|
61
|
+
cache.set(key, { value, at: Date.now() });
|
|
53
62
|
return value;
|
|
54
63
|
} catch {
|
|
55
64
|
// Fail closed, and don't cache a failure — a transient error shouldn't
|
|
@@ -58,6 +67,15 @@ export async function relationsEnabled(supabase: MCPSupabaseClient): Promise<boo
|
|
|
58
67
|
}
|
|
59
68
|
}
|
|
60
69
|
|
|
70
|
+
export async function relationsEnabled(supabase: MCPSupabaseClient): Promise<boolean> {
|
|
71
|
+
return readBoolFlag(supabase, "relations_enabled");
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Whether `review_status` is a thing on this store (#241). */
|
|
75
|
+
export async function reviewWorkflowEnabled(supabase: MCPSupabaseClient): Promise<boolean> {
|
|
76
|
+
return readBoolFlag(supabase, "review_workflow_enabled");
|
|
77
|
+
}
|
|
78
|
+
|
|
61
79
|
/** Message shown when a gated tool is called while the feature is off. */
|
|
62
80
|
export function disabledToolMessage(name: string): string {
|
|
63
81
|
return (
|
|
@@ -136,7 +136,8 @@ async function handler(
|
|
|
136
136
|
}
|
|
137
137
|
|
|
138
138
|
const contentHash = await sha256hex(normalizeContent(content));
|
|
139
|
-
|
|
139
|
+
// review_status is decided by cerefox_ingest_document from author_type and
|
|
140
|
+
// the store's review_workflow_enabled flag (#241); clients no longer send it.
|
|
140
141
|
|
|
141
142
|
// ── ID-based update path ─────────────────────────────────────────────────
|
|
142
143
|
if (document_id) {
|
|
@@ -202,7 +203,6 @@ async function handler(
|
|
|
202
203
|
p_source: source,
|
|
203
204
|
p_content_hash: contentHash,
|
|
204
205
|
p_metadata: metadata,
|
|
205
|
-
p_review_status: reviewStatus,
|
|
206
206
|
p_chunks: chunkData,
|
|
207
207
|
p_author: author,
|
|
208
208
|
p_author_type: author_type,
|
|
@@ -306,7 +306,6 @@ async function handler(
|
|
|
306
306
|
p_source: source,
|
|
307
307
|
p_content_hash: contentHash,
|
|
308
308
|
p_metadata: metadata,
|
|
309
|
-
p_review_status: reviewStatus,
|
|
310
309
|
p_chunks: chunkData,
|
|
311
310
|
p_author: author,
|
|
312
311
|
p_author_type: author_type,
|
|
@@ -384,7 +383,6 @@ async function handler(
|
|
|
384
383
|
p_source: source,
|
|
385
384
|
p_content_hash: contentHash,
|
|
386
385
|
p_metadata: metadata,
|
|
387
|
-
p_review_status: reviewStatus,
|
|
388
386
|
p_chunks: chunkData,
|
|
389
387
|
p_author: author,
|
|
390
388
|
p_author_type: author_type,
|
|
@@ -9,6 +9,7 @@ import type { MCPSupabaseClient } from "./types.ts";
|
|
|
9
9
|
|
|
10
10
|
import { applyByteBudget, getMaxResponseBytes, logUsage } from "./_utils.ts";
|
|
11
11
|
import { lookupProjectId } from "./_projects.ts";
|
|
12
|
+
import { reviewWorkflowEnabled } from "./feature-flags.ts";
|
|
12
13
|
import { McpInvalidParams, type ToolContext, type ToolDefinition } from "./types.ts";
|
|
13
14
|
|
|
14
15
|
async function handler(
|
|
@@ -93,6 +94,10 @@ async function handler(
|
|
|
93
94
|
|
|
94
95
|
if (rows.length === 0) return "No documents match the given criteria.";
|
|
95
96
|
|
|
97
|
+
// The review status is a column of a feature that may be off (#241); when
|
|
98
|
+
// it is, an agent should not see "approved" and wonder what it means.
|
|
99
|
+
const showReview = await reviewWorkflowEnabled(supabase);
|
|
100
|
+
|
|
96
101
|
// Note: when include_content is true the RPC already respects p_max_bytes
|
|
97
102
|
// server-side. The applyByteBudget helper is retained here only for
|
|
98
103
|
// parity with the EF implementation and as a defensive trim — see the
|
|
@@ -109,7 +114,7 @@ async function handler(
|
|
|
109
114
|
const hash = row.content_hash ? `\nhash: ${row.content_hash}` : "";
|
|
110
115
|
const header =
|
|
111
116
|
`## ${row.title} [id: ${row.document_id}]\n` +
|
|
112
|
-
`${meta}${projects} | ${row.total_chars} chars | ${row.review_status} | updated ${row.updated_at?.slice(0, 10) ?? "?"}${hash}`;
|
|
117
|
+
`${meta}${projects} | ${row.total_chars} chars | ${showReview ? `${row.review_status} | ` : ""}updated ${row.updated_at?.slice(0, 10) ?? "?"}${hash}`;
|
|
113
118
|
|
|
114
119
|
if (include_content && row.content) {
|
|
115
120
|
return `${header}\n\n${row.content}`;
|
|
@@ -307,8 +307,8 @@ async function applyAndWrite(
|
|
|
307
307
|
p_source: null,
|
|
308
308
|
p_content_hash: newHash,
|
|
309
309
|
p_metadata: null, // null = keep existing metadata
|
|
310
|
-
//
|
|
311
|
-
|
|
310
|
+
// review_status: decided by the RPC from p_author_type + the store's
|
|
311
|
+
// review_workflow_enabled flag (#241), not here.
|
|
312
312
|
p_chunks: chunkData,
|
|
313
313
|
p_author: requestor,
|
|
314
314
|
// Who actually made the write, not which module executed it. The CLI is a
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
-- 0031_review_workflow_toggle.sql — make the review workflow optional (#241,
|
|
2
|
+
-- schema 0.16.0, iteration 44).
|
|
3
|
+
--
|
|
4
|
+
-- New config key `review_workflow_enabled`. Fresh installs get 'false' from
|
|
5
|
+
-- schema.sql; this migration runs only on stores that predate the flag and
|
|
6
|
+
-- seeds 'true' there, so upgrading never changes what a store does. Neither
|
|
7
|
+
-- write ever overrides a value an operator has set (ON CONFLICT DO NOTHING).
|
|
8
|
+
--
|
|
9
|
+
-- The decision "agent write → pending_review" moves out of the six client call
|
|
10
|
+
-- sites and into cerefox_ingest_document, which reads this flag. That RPC
|
|
11
|
+
-- lives in rpcs.sql, which `cerefox server deploy` re-applies.
|
|
12
|
+
--
|
|
13
|
+
-- #240: cerefox_hybrid_search / cerefox_search_docs gain p_review_status so a
|
|
14
|
+
-- filtered search is applied before the limit, not after. A new argument is a
|
|
15
|
+
-- new overload; the old ones must go or PostgREST calls become ambiguous
|
|
16
|
+
-- (PGRST203). Same DROPs sit at the top of rpcs.sql for the fresh path.
|
|
17
|
+
--
|
|
18
|
+
-- Idempotent: safe to re-run.
|
|
19
|
+
|
|
20
|
+
INSERT INTO cerefox_config (key, value)
|
|
21
|
+
VALUES ('review_workflow_enabled', 'true')
|
|
22
|
+
ON CONFLICT (key) DO NOTHING;
|
|
23
|
+
|
|
24
|
+
DROP FUNCTION IF EXISTS cerefox_hybrid_search(TEXT, VECTOR(768), INT, FLOAT, BOOLEAN, UUID, FLOAT, JSONB, FLOAT);
|
|
25
|
+
DROP FUNCTION IF EXISTS cerefox_search_docs(TEXT, VECTOR(768), INT, FLOAT, UUID, FLOAT, INT, INT, JSONB, FLOAT);
|
|
26
|
+
|
|
27
|
+
-- The allow-list in cerefox_set_config grows by one key. Same signature, so
|
|
28
|
+
-- OR REPLACE is enough (no overload to drop). Carried here as well as in
|
|
29
|
+
-- rpcs.sql so `db_migrate` alone leaves the key settable; the unit test
|
|
30
|
+
-- `rpc-guard-invariants` pins the two lists to each other.
|
|
31
|
+
CREATE OR REPLACE FUNCTION cerefox_set_config(
|
|
32
|
+
p_key TEXT,
|
|
33
|
+
p_value TEXT,
|
|
34
|
+
p_author TEXT DEFAULT 'unknown',
|
|
35
|
+
p_author_type TEXT DEFAULT 'user'
|
|
36
|
+
)
|
|
37
|
+
RETURNS VOID
|
|
38
|
+
LANGUAGE plpgsql
|
|
39
|
+
SECURITY DEFINER
|
|
40
|
+
SET search_path = public, pg_catalog
|
|
41
|
+
AS $$
|
|
42
|
+
DECLARE
|
|
43
|
+
v_allowed TEXT[] := ARRAY[
|
|
44
|
+
'usage_tracking_enabled', 'require_requestor_identity', 'requestor_identity_format',
|
|
45
|
+
'min_search_score', 'min_term_coverage', 'search_alpha',
|
|
46
|
+
'version_retention_hours', 'version_cleanup_enabled',
|
|
47
|
+
'relations_enabled',
|
|
48
|
+
'review_workflow_enabled',
|
|
49
|
+
'document_size_warning_chars'
|
|
50
|
+
];
|
|
51
|
+
v_old TEXT;
|
|
52
|
+
BEGIN
|
|
53
|
+
IF NOT (p_key = ANY(v_allowed)) THEN
|
|
54
|
+
RAISE EXCEPTION 'Unknown config key: %. Allowed keys: %', p_key, v_allowed;
|
|
55
|
+
END IF;
|
|
56
|
+
|
|
57
|
+
SELECT value INTO v_old FROM cerefox_config WHERE key = p_key;
|
|
58
|
+
|
|
59
|
+
INSERT INTO cerefox_config (key, value)
|
|
60
|
+
VALUES (p_key, p_value)
|
|
61
|
+
ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value;
|
|
62
|
+
|
|
63
|
+
PERFORM cerefox_create_audit_entry(
|
|
64
|
+
p_operation := 'config-change',
|
|
65
|
+
p_author := p_author,
|
|
66
|
+
p_author_type := p_author_type,
|
|
67
|
+
p_description := 'config: ' || p_key || ': '
|
|
68
|
+
|| COALESCE('''' || v_old || '''', '(unset)')
|
|
69
|
+
|| ' → ''' || p_value || ''''
|
|
70
|
+
);
|
|
71
|
+
END;
|
|
72
|
+
$$;
|
|
@@ -76,6 +76,11 @@ DROP FUNCTION IF EXISTS cerefox_hybrid_search(TEXT, VECTOR(768), INT, FLOAT, BOO
|
|
|
76
76
|
DROP FUNCTION IF EXISTS cerefox_fts_search(TEXT, INT, UUID, JSONB);
|
|
77
77
|
DROP FUNCTION IF EXISTS cerefox_search_docs(TEXT, VECTOR(768), INT, FLOAT, UUID, FLOAT, INT, INT, JSONB);
|
|
78
78
|
|
|
79
|
+
-- Iteration 44 (0.16.0, #240): p_review_status added to the two search RPCs
|
|
80
|
+
-- so a review-status filter applies before the limit. Old overloads out.
|
|
81
|
+
DROP FUNCTION IF EXISTS cerefox_hybrid_search(TEXT, VECTOR(768), INT, FLOAT, BOOLEAN, UUID, FLOAT, JSONB, FLOAT);
|
|
82
|
+
DROP FUNCTION IF EXISTS cerefox_search_docs(TEXT, VECTOR(768), INT, FLOAT, UUID, FLOAT, INT, INT, JSONB, FLOAT);
|
|
83
|
+
|
|
79
84
|
-- ── Shared return type note ────────────────────────────────────────────────────
|
|
80
85
|
-- All chunk-level search RPCs return the same shape for consistency:
|
|
81
86
|
-- chunk_id, document_id, chunk_index, title, content, heading_path,
|
|
@@ -113,7 +118,11 @@ CREATE OR REPLACE FUNCTION cerefox_hybrid_search(
|
|
|
113
118
|
-- meant 100% of terms — the pass this gate generalizes. Chunks below the
|
|
114
119
|
-- bar can still pass via the vector threshold, else they are
|
|
115
120
|
-- below-confidence material. 0 restores the pre-gate OR behavior.
|
|
116
|
-
p_min_term_coverage FLOAT DEFAULT NULL
|
|
121
|
+
p_min_term_coverage FLOAT DEFAULT NULL,
|
|
122
|
+
-- #240: 'approved' | 'pending_review' restricts the candidate pool BEFORE
|
|
123
|
+
-- the limit, so a filtered page is a full page. NULL = no filter. Applied
|
|
124
|
+
-- here, in the CTEs, rather than by the caller on the returned page.
|
|
125
|
+
p_review_status TEXT DEFAULT NULL
|
|
117
126
|
)
|
|
118
127
|
RETURNS TABLE (
|
|
119
128
|
chunk_id UUID,
|
|
@@ -204,6 +213,7 @@ BEGIN
|
|
|
204
213
|
WHERE dp.document_id = d.id AND dp.project_id = p_project_id
|
|
205
214
|
))
|
|
206
215
|
AND (p_metadata_filter IS NULL OR d.metadata @> p_metadata_filter)
|
|
216
|
+
AND (p_review_status IS NULL OR d.review_status = p_review_status)
|
|
207
217
|
) INTO and_matches;
|
|
208
218
|
END IF;
|
|
209
219
|
|
|
@@ -239,6 +249,7 @@ BEGIN
|
|
|
239
249
|
WHERE dp.document_id = d.id AND dp.project_id = p_project_id
|
|
240
250
|
))
|
|
241
251
|
AND (p_metadata_filter IS NULL OR d.metadata @> p_metadata_filter)
|
|
252
|
+
AND (p_review_status IS NULL OR d.review_status = p_review_status)
|
|
242
253
|
ORDER BY fts_score DESC
|
|
243
254
|
LIMIT candidate_count
|
|
244
255
|
),
|
|
@@ -260,6 +271,7 @@ BEGIN
|
|
|
260
271
|
WHERE dp.document_id = d.id AND dp.project_id = p_project_id
|
|
261
272
|
))
|
|
262
273
|
AND (p_metadata_filter IS NULL OR d.metadata @> p_metadata_filter)
|
|
274
|
+
AND (p_review_status IS NULL OR d.review_status = p_review_status)
|
|
263
275
|
ORDER BY
|
|
264
276
|
CASE
|
|
265
277
|
WHEN p_use_upgrade AND c.embedding_upgrade IS NOT NULL
|
|
@@ -724,7 +736,9 @@ CREATE OR REPLACE FUNCTION cerefox_search_docs(
|
|
|
724
736
|
p_metadata_filter JSONB DEFAULT NULL,
|
|
725
737
|
-- NULL flows through to cerefox_hybrid_search, which resolves the
|
|
726
738
|
-- caller > cerefox_config > built-in chain in one place (#133).
|
|
727
|
-
p_min_term_coverage FLOAT DEFAULT NULL
|
|
739
|
+
p_min_term_coverage FLOAT DEFAULT NULL,
|
|
740
|
+
-- #240: optional review-status filter, applied inside cerefox_hybrid_search.
|
|
741
|
+
p_review_status TEXT DEFAULT NULL
|
|
728
742
|
)
|
|
729
743
|
RETURNS TABLE (
|
|
730
744
|
document_id UUID,
|
|
@@ -765,7 +779,8 @@ AS $$
|
|
|
765
779
|
p_project_id := p_project_id,
|
|
766
780
|
p_min_score := p_min_score,
|
|
767
781
|
p_metadata_filter := p_metadata_filter,
|
|
768
|
-
p_min_term_coverage := p_min_term_coverage
|
|
782
|
+
p_min_term_coverage := p_min_term_coverage,
|
|
783
|
+
p_review_status := p_review_status
|
|
769
784
|
)
|
|
770
785
|
),
|
|
771
786
|
best_per_doc AS (
|
|
@@ -1379,7 +1394,11 @@ $$;
|
|
|
1379
1394
|
-- p_metadata : JSONB metadata. NULL = "not provided" → create uses '{}',
|
|
1380
1395
|
-- update keeps the existing metadata (v0.11.1). Pass '{}'
|
|
1381
1396
|
-- explicitly to clear all metadata.
|
|
1382
|
-
-- p_review_status :
|
|
1397
|
+
-- p_review_status : ACCEPTED AND IGNORED since 0.16.0 (#241). The status is
|
|
1398
|
+
-- decided here from p_author_type and the store's
|
|
1399
|
+
-- `review_workflow_enabled` flag, so every transport
|
|
1400
|
+
-- behaves the same and a toggle needs no client change.
|
|
1401
|
+
-- Kept in the signature so no caller breaks.
|
|
1383
1402
|
-- p_chunks : JSONB array of chunk objects, each with:
|
|
1384
1403
|
-- chunk_index, heading_path, heading_level, title,
|
|
1385
1404
|
-- content, char_count, embedding (float[]), embedder (text)
|
|
@@ -1564,9 +1583,18 @@ BEGIN
|
|
|
1564
1583
|
USING ERRCODE = '22023'; -- deterministic; never a retryable SQLSTATE
|
|
1565
1584
|
END IF;
|
|
1566
1585
|
|
|
1567
|
-
--
|
|
1568
|
-
|
|
1569
|
-
|
|
1586
|
+
-- Review status is decided HERE, not by the caller (#241). With the
|
|
1587
|
+
-- workflow on, an agent write is queued for a person to look at; with it
|
|
1588
|
+
-- off, every write lands approved and no surface shows the column. The
|
|
1589
|
+
-- fallback FALSE matches the fresh-install seed; migration 0031 seeds TRUE
|
|
1590
|
+
-- on stores that predate the flag, so it only applies if the row is gone.
|
|
1591
|
+
-- p_review_status is deliberately not consulted — six clients used to
|
|
1592
|
+
-- compute it and they could not have agreed on a store-level policy.
|
|
1593
|
+
v_status := CASE
|
|
1594
|
+
WHEN NOT cerefox_config_bool('review_workflow_enabled', FALSE) THEN 'approved'
|
|
1595
|
+
WHEN p_author_type = 'agent' THEN 'pending_review'
|
|
1596
|
+
ELSE 'approved'
|
|
1597
|
+
END;
|
|
1570
1598
|
|
|
1571
1599
|
-- Count chunks and total chars from the input
|
|
1572
1600
|
v_chunk_count := jsonb_array_length(p_chunks);
|
|
@@ -2704,6 +2732,9 @@ DECLARE
|
|
|
2704
2732
|
'version_retention_hours', 'version_cleanup_enabled',
|
|
2705
2733
|
-- Optional features, off by default (iteration 29).
|
|
2706
2734
|
'relations_enabled',
|
|
2735
|
+
-- #241: the review workflow. Off on fresh installs, on for stores that
|
|
2736
|
+
-- predate the flag. Read by cerefox_ingest_document on every write.
|
|
2737
|
+
'review_workflow_enabled',
|
|
2707
2738
|
-- Iteration 33: flag writes that push a document past this many chars
|
|
2708
2739
|
-- (0 = off). Partial edits make writes cheap, so an insert-only agent
|
|
2709
2740
|
-- never assembles the document and never sees it grow past its split
|
|
@@ -3057,6 +3088,13 @@ SET search_path = public, pg_catalog
|
|
|
3057
3088
|
AS $$
|
|
3058
3089
|
-- Keep in lockstep with the `@version:` marker in schema.sql (cut_release.ts
|
|
3059
3090
|
-- enforces it). Bump whenever schema.sql OR rpcs.sql changes.
|
|
3091
|
+
-- 0.16.0 (#241, #240): `review_workflow_enabled` config key (seeded false
|
|
3092
|
+
-- on fresh installs, true by migration 0031 on existing ones);
|
|
3093
|
+
-- cerefox_ingest_document decides review_status itself from author_type
|
|
3094
|
+
-- + the flag, p_review_status is ignored; p_review_status filter on
|
|
3095
|
+
-- cerefox_hybrid_search / cerefox_search_docs, applied before the limit.
|
|
3096
|
+
-- 0.15.0 (iteration 39): cerefox_rename_document.
|
|
3097
|
+
-- 0.14.0 (#147/#219): store-level writes audited in-RPC.
|
|
3060
3098
|
-- 0.13.0 (#216): archived chunks carry no search artifacts —
|
|
3061
3099
|
-- cerefox_snapshot_version nulls embedding_primary/embedding_upgrade/fts
|
|
3062
3100
|
-- at archive time; embedding_primary becomes nullable; migration 0027
|
|
@@ -3079,7 +3117,7 @@ AS $$
|
|
|
3079
3117
|
-- 0.11.0 supersedes 0.10.6 (v1.2.1, #191): this branch carries that fix plus
|
|
3080
3118
|
-- the partial-edit surface, and both migrations (0019, 0020) are in the
|
|
3081
3119
|
-- sequence, so a store deploying this gets everything from both lines.
|
|
3082
|
-
SELECT '0.
|
|
3120
|
+
SELECT '0.16.0'::TEXT;
|
|
3083
3121
|
$$;
|
|
3084
3122
|
|
|
3085
3123
|
-- ── cerefox_find_dead_links ──────────────────────────────────────────────────
|
|
@@ -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.16.0
|
|
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 —
|
|
@@ -55,7 +55,10 @@ CREATE TABLE IF NOT EXISTS cerefox_documents (
|
|
|
55
55
|
total_chars INT NOT NULL DEFAULT 0,
|
|
56
56
|
-- review_status: human governance flag. 'approved' = validated by human,
|
|
57
57
|
-- 'pending_review' = modified by agent, not yet reviewed.
|
|
58
|
-
-- Content is searchable in both states.
|
|
58
|
+
-- Content is searchable in both states. Written ONLY by
|
|
59
|
+
-- cerefox_ingest_document, which consults `review_workflow_enabled`
|
|
60
|
+
-- (#241): with the workflow off every write lands 'approved' and every
|
|
61
|
+
-- surface hides the column; existing values are left as they are.
|
|
59
62
|
review_status TEXT NOT NULL DEFAULT 'approved',
|
|
60
63
|
-- lifecycle_status: where this document stands relative to the graph —
|
|
61
64
|
-- 'active' | 'superseded' | 'stale' | 'archived'. Distinct from
|
|
@@ -402,6 +405,14 @@ ON CONFLICT (key) DO NOTHING;
|
|
|
402
405
|
INSERT INTO cerefox_config (key, value)
|
|
403
406
|
VALUES ('relations_enabled', 'false')
|
|
404
407
|
ON CONFLICT (key) DO NOTHING;
|
|
408
|
+
-- The review workflow (agent writes land pending_review, a person approves)
|
|
409
|
+
-- is OFF on a fresh install (#241): most stores have no reviewer, and a queue
|
|
410
|
+
-- nobody drains is noise. Migration 0031 seeds TRUE on stores that predate the
|
|
411
|
+
-- flag, so an upgrade never changes behaviour. Only this seed and that
|
|
412
|
+
-- migration ever write the value; the toggle itself is `cerefox config set`.
|
|
413
|
+
INSERT INTO cerefox_config (key, value)
|
|
414
|
+
VALUES ('review_workflow_enabled', 'false')
|
|
415
|
+
ON CONFLICT (key) DO NOTHING;
|
|
405
416
|
|
|
406
417
|
|
|
407
418
|
-- ── Usage log ────────────────────────────────────────────────────────────────
|
|
@@ -344,7 +344,8 @@ Deno.serve(async (req: Request) => {
|
|
|
344
344
|
|
|
345
345
|
const contentHash = await sha256hex(normalizeContent(content));
|
|
346
346
|
const headers = { "Content-Type": "application/json", "Access-Control-Allow-Origin": "*" };
|
|
347
|
-
|
|
347
|
+
// review_status is decided by cerefox_ingest_document from author_type and
|
|
348
|
+
// the store's review_workflow_enabled flag (#241); the EF no longer sends it.
|
|
348
349
|
|
|
349
350
|
// ── ID-based update path ────────────────────────────────────────────────────
|
|
350
351
|
// When document_id is provided, update that exact document regardless of
|
|
@@ -424,7 +425,6 @@ Deno.serve(async (req: Request) => {
|
|
|
424
425
|
p_source: source,
|
|
425
426
|
p_content_hash: contentHash,
|
|
426
427
|
p_metadata: metadata,
|
|
427
|
-
p_review_status: reviewStatus,
|
|
428
428
|
p_chunks: chunkData,
|
|
429
429
|
p_author: author,
|
|
430
430
|
p_author_type: author_type,
|
|
@@ -543,7 +543,6 @@ Deno.serve(async (req: Request) => {
|
|
|
543
543
|
p_source: source,
|
|
544
544
|
p_content_hash: contentHash,
|
|
545
545
|
p_metadata: metadata,
|
|
546
|
-
p_review_status: reviewStatus,
|
|
547
546
|
p_chunks: chunkData,
|
|
548
547
|
p_author: author,
|
|
549
548
|
p_author_type: author_type,
|
|
@@ -655,7 +654,6 @@ Deno.serve(async (req: Request) => {
|
|
|
655
654
|
p_source: source,
|
|
656
655
|
p_content_hash: contentHash,
|
|
657
656
|
p_metadata: metadata,
|
|
658
|
-
p_review_status: reviewStatus,
|
|
659
657
|
p_chunks: chunkData,
|
|
660
658
|
p_author: author,
|
|
661
659
|
p_author_type: author_type,
|
|
@@ -2,6 +2,7 @@ import "jsr:@supabase/functions-js/edge-runtime.d.ts";
|
|
|
2
2
|
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
|
+
import { reviewWorkflowEnabled } from "../../../_shared/mcp-tools/feature-flags.ts";
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* cerefox-metadata-search -- Supabase Edge Function
|
|
@@ -27,7 +28,9 @@ import { efAuthGate } from "../../../_shared/ef-auth/index.ts";
|
|
|
27
28
|
* include_content boolean optional Include full text (default: false)
|
|
28
29
|
* max_bytes number optional Byte budget when include_content=true
|
|
29
30
|
*
|
|
30
|
-
* Response (200): Array of matching documents
|
|
31
|
+
* Response (200): Array of matching documents. `review_status` is present
|
|
32
|
+
* only while the review workflow is on (#241); with the flag
|
|
33
|
+
* off the key is absent, as on every other surface.
|
|
31
34
|
* Response (400): { error: "..." }
|
|
32
35
|
*/
|
|
33
36
|
|
|
@@ -160,7 +163,14 @@ Deno.serve(async (req: Request): Promise<Response> => {
|
|
|
160
163
|
p_project_id: project_id,
|
|
161
164
|
})).catch(() => {});
|
|
162
165
|
|
|
163
|
-
|
|
166
|
+
// Presentation only: the same shared reader every other surface uses.
|
|
167
|
+
const rows = (data ?? []) as Array<Record<string, unknown>>;
|
|
168
|
+
const showReview = await reviewWorkflowEnabled(supabase);
|
|
169
|
+
const out = showReview
|
|
170
|
+
? rows
|
|
171
|
+
: rows.map(({ review_status: _hidden, ...rest }) => rest);
|
|
172
|
+
|
|
173
|
+
return new Response(JSON.stringify(out), {
|
|
164
174
|
status: 200,
|
|
165
175
|
headers: { ...CORS_HEADERS, "Content-Type": "application/json" },
|
|
166
176
|
});
|
|
@@ -265,7 +265,7 @@ before "completing" the parity table by adding purge to agent-facing access path
|
|
|
265
265
|
| Tier | Operations | Reversible? | Where exposed |
|
|
266
266
|
|---|---|---|---|
|
|
267
267
|
| 1. Reads + soft mutations | search, get, list-*, ingest (create/update), metadata-search, get-audit-log | n/a (reads) / yes (versioned) | All paths — MCP, Edge Functions, CLI, web UI |
|
|
268
|
-
| 2. Soft-destructive + recovery | `delete_document` (soft delete to trash), `restore_document` (un-trash), `set_review_status` | yes — delete is restorable; restore recovers | CLI (`cerefox document delete` / `restore`), web UI, and — since v1.7.0 (#208, #210) — MCP (`cerefox_delete_document`, which requires the caller's read-hash, and `cerefox_restore_document`). **Not** the primitive GPT-Actions Edge Functions (deliberately deferred). |
|
|
268
|
+
| 2. Soft-destructive + recovery | `delete_document` (soft delete to trash), `restore_document` (un-trash), `set_review_status` (web only; a `404` while `review_workflow_enabled` is off) | yes — delete is restorable; restore recovers | CLI (`cerefox document delete` / `restore`), web UI, and — since v1.7.0 (#208, #210) — MCP (`cerefox_delete_document`, which requires the caller's read-hash, and `cerefox_restore_document`). **Not** the primitive GPT-Actions Edge Functions (deliberately deferred). |
|
|
269
269
|
| 3. **Hard-destructive** | `purge_document` (permanent), `set_version_archived` (toggle version retention) | no (purge) | **Web UI only** |
|
|
270
270
|
|
|
271
271
|
### Why purge is web-UI-only
|
package/docs/guides/cli.md
CHANGED
|
@@ -50,7 +50,7 @@ cerefox document ingest --paste --title "<title>" [OPTIONS] # stdin
|
|
|
50
50
|
| `--last-write-wins` | — | flag | off | Skip the concurrency check and overwrite regardless of concurrent changes. For re-sync flows where an external source of truth makes conflicts meaningless. Recorded in the audit log. |
|
|
51
51
|
| `--source` | — | str | `paste` / `file` | Source label recorded on the document. |
|
|
52
52
|
| `--author` | — | str | `CEREFOX_AUTHOR_NAME` or `unknown` | Audit-log author identity. |
|
|
53
|
-
| `--author-type` | — | `user`\|`agent` | `CEREFOX_AUTHOR_TYPE` or `user` | Caller type. Agent writes
|
|
53
|
+
| `--author-type` | — | `user`\|`agent` | `CEREFOX_AUTHOR_TYPE` or `user` | Caller type. Agent writes land `pending_review` while the review workflow is on (`review_workflow_enabled`); `approved` otherwise. |
|
|
54
54
|
|
|
55
55
|
**Examples**:
|
|
56
56
|
```bash
|
|
@@ -216,7 +216,7 @@ cerefox document list [OPTIONS]
|
|
|
216
216
|
| `--deleted` | flag | off | List soft-deleted (trashed) documents instead of active ones, newest-deleted first. Pair the ids with `cerefox document restore` / `cerefox document delete`. |
|
|
217
217
|
| `--json` | flag | off | Machine-readable JSON output. |
|
|
218
218
|
|
|
219
|
-
**Output**: tabular `id | title | source | status | updated_at` listing (or `deleted_at` with `--deleted`).
|
|
219
|
+
**Output**: tabular `id | title | source | status | updated_at` listing (or `deleted_at` with `--deleted`). The `status` column (and the `review_status` key in `--json`) is present only while the review workflow is on — see `review_workflow_enabled` in [configuration.md](configuration.md#review-workflow).
|
|
220
220
|
|
|
221
221
|
**MCP equivalent**: scope-by-project / metadata / time listing maps to [`cerefox_metadata_search`](../../AGENT_GUIDE.md) — e.g. `cerefox_metadata_search(project_name="research")` lists that project's documents (the `metadata_filter` may be empty when another scope is supplied). The `--deleted` (trash) view and unscoped whole-KB listing remain CLI-only.
|
|
222
222
|
|
|
@@ -506,6 +506,8 @@ cerefox metadata search --metadata-filter '{"type":"decision-log"}' --updated-si
|
|
|
506
506
|
cerefox metadata search --metadata-filter '{"status":"active"}' --project-name "research" --include-content
|
|
507
507
|
```
|
|
508
508
|
|
|
509
|
+
**Output**: a `## title [id: …]` block per document with its metadata, projects, size, review status and update date (plus content with `--include-content`), or JSON with `--json`. As with `document list`, the review status (and the `review_status` key in `--json`) is present only while the review workflow is on — see `review_workflow_enabled` in [configuration.md](configuration.md#review-workflow).
|
|
510
|
+
|
|
509
511
|
**MCP equivalent**: [`cerefox_metadata_search`](../../AGENT_GUIDE.md).
|
|
510
512
|
|
|
511
513
|
---
|
|
@@ -702,11 +704,16 @@ Agents see 15 tools with the flag off and 19 with it on. See
|
|
|
702
704
|
|
|
703
705
|
**Synopsis**:
|
|
704
706
|
```
|
|
705
|
-
cerefox config list
|
|
707
|
+
cerefox config list [--json] # every settable key, grouped, with kind + default (from the shared catalog)
|
|
706
708
|
cerefox config get KEY
|
|
707
709
|
cerefox config set KEY VALUE [--author NAME] [--author-type user|agent]
|
|
708
710
|
```
|
|
709
711
|
|
|
712
|
+
`config list` is derived from the same catalog the web Settings page renders
|
|
713
|
+
(v1.13.0, #239 — the earlier hand-written list had drifted and hid three
|
|
714
|
+
working keys). `--json` returns `{ keys: string[], catalog: [{ key, kind,
|
|
715
|
+
default, group, description }] }`; `keys` keeps its pre-1.13 shape.
|
|
716
|
+
|
|
710
717
|
Since v1.9.0 every `config set` is recorded in the audit log by the server
|
|
711
718
|
itself (`config-change`, with the old → new value), in the same transaction
|
|
712
719
|
as the write — pass `--author` (or set `CEREFOX_AUTHOR_NAME`) so the entry
|
|
@@ -766,7 +773,7 @@ These flat commands handle install, configuration, and health. Run any with `--h
|
|
|
766
773
|
| Command | Purpose |
|
|
767
774
|
|---|---|
|
|
768
775
|
| `cerefox init` | Interactive first-run setup; writes `~/.cerefox/.env`, offers `server deploy` + self-docs ingest. |
|
|
769
|
-
| `cerefox doctor` | Diagnose the install (credentials, DB reachability, schema version). |
|
|
776
|
+
| `cerefox doctor` | Diagnose the install (credentials, DB reachability, schema version, whether the review workflow is on). |
|
|
770
777
|
| `cerefox status` | Show connection + schema status. |
|
|
771
778
|
| `cerefox configure-agent --tool <client>` | Write MCP client config (`claude-code`, `claude-desktop`, `cursor`, `codex`, `gemini`). |
|
|
772
779
|
| `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. |
|