@cerefox/memory 1.12.1 → 1.13.1
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 +26 -26
- package/AGENT_QUICK_REFERENCE.md +9 -9
- package/dist/bin/cerefox.js +7031 -7433
- 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/audit-log.ts +10 -8
- package/dist/server-assets/_shared/mcp-tools/delete-document.ts +4 -10
- package/dist/server-assets/_shared/mcp-tools/feature-flags.ts +42 -23
- package/dist/server-assets/_shared/mcp-tools/get-document.ts +3 -6
- package/dist/server-assets/_shared/mcp-tools/get-help-content.ts +3 -3
- package/dist/server-assets/_shared/mcp-tools/get-help.ts +3 -6
- package/dist/server-assets/_shared/mcp-tools/identity.ts +48 -0
- package/dist/server-assets/_shared/mcp-tools/ingest.ts +5 -10
- package/dist/server-assets/_shared/mcp-tools/list-metadata-keys.ts +3 -6
- package/dist/server-assets/_shared/mcp-tools/list-projects.ts +3 -6
- package/dist/server-assets/_shared/mcp-tools/list-versions.ts +3 -6
- package/dist/server-assets/_shared/mcp-tools/metadata-search.ts +9 -7
- package/dist/server-assets/_shared/mcp-tools/partial-edits.ts +7 -12
- package/dist/server-assets/_shared/mcp-tools/relations.ts +11 -12
- package/dist/server-assets/_shared/mcp-tools/restore-document.ts +4 -10
- package/dist/server-assets/_shared/mcp-tools/search.ts +3 -6
- package/dist/server-assets/_shared/mcp-tools/set-document-metadata.ts +4 -10
- package/dist/server-assets/_shared/mcp-tools/set-document-projects.ts +3 -6
- package/dist/server-assets/db/migrations/0031_review_workflow_toggle.sql +74 -0
- package/dist/server-assets/db/rpcs.sql +53 -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-mcp/index.ts +6 -6
- 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 +21 -14
- package/docs/guides/configuration.md +75 -18
- package/docs/guides/connect-agents.md +10 -6
- package/docs/guides/upgrading.md +32 -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
|
@@ -8,6 +8,7 @@ import type { MCPSupabaseClient } from "./types.ts";
|
|
|
8
8
|
|
|
9
9
|
import { logUsage } from "./_utils.ts";
|
|
10
10
|
import { McpInvalidParams, type ToolContext, type ToolDefinition } from "./types.ts";
|
|
11
|
+
import { AUTHOR_PARAM_READ, callerIdentity } from "./identity.ts";
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* Version timestamps carried only a DATE (`slice(0, 10)`), which is
|
|
@@ -46,7 +47,7 @@ async function handler(
|
|
|
46
47
|
logUsage(supabase, {
|
|
47
48
|
operation: "list_versions",
|
|
48
49
|
accessPath: ctx.accessPath,
|
|
49
|
-
requestor: args
|
|
50
|
+
requestor: callerIdentity(args),
|
|
50
51
|
document_id,
|
|
51
52
|
result_count: versions.length,
|
|
52
53
|
});
|
|
@@ -79,11 +80,7 @@ export const listVersionsTool: ToolDefinition = {
|
|
|
79
80
|
type: "string",
|
|
80
81
|
description: "UUID of the document whose version history to list",
|
|
81
82
|
},
|
|
82
|
-
|
|
83
|
-
type: "string",
|
|
84
|
-
description:
|
|
85
|
-
'Name of the agent or user making this request. Recorded in the usage log. Defaults to "mcp-agent" if not provided. May be enforced via server config.',
|
|
86
|
-
},
|
|
83
|
+
author: AUTHOR_PARAM_READ,
|
|
87
84
|
},
|
|
88
85
|
},
|
|
89
86
|
handler,
|
|
@@ -9,7 +9,9 @@ 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";
|
|
14
|
+
import { AUTHOR_PARAM_READ, callerIdentity } from "./identity.ts";
|
|
13
15
|
|
|
14
16
|
async function handler(
|
|
15
17
|
supabase: MCPSupabaseClient,
|
|
@@ -85,7 +87,7 @@ async function handler(
|
|
|
85
87
|
logUsage(supabase, {
|
|
86
88
|
operation: "metadata_search",
|
|
87
89
|
accessPath: ctx.accessPath,
|
|
88
|
-
requestor: args
|
|
90
|
+
requestor: callerIdentity(args),
|
|
89
91
|
query_text: JSON.stringify(metadata_filter ?? {}),
|
|
90
92
|
project_id: projectId,
|
|
91
93
|
result_count: rows.length,
|
|
@@ -93,6 +95,10 @@ async function handler(
|
|
|
93
95
|
|
|
94
96
|
if (rows.length === 0) return "No documents match the given criteria.";
|
|
95
97
|
|
|
98
|
+
// The review status is a column of a feature that may be off (#241); when
|
|
99
|
+
// it is, an agent should not see "approved" and wonder what it means.
|
|
100
|
+
const showReview = await reviewWorkflowEnabled(supabase);
|
|
101
|
+
|
|
96
102
|
// Note: when include_content is true the RPC already respects p_max_bytes
|
|
97
103
|
// server-side. The applyByteBudget helper is retained here only for
|
|
98
104
|
// parity with the EF implementation and as a defensive trim — see the
|
|
@@ -109,7 +115,7 @@ async function handler(
|
|
|
109
115
|
const hash = row.content_hash ? `\nhash: ${row.content_hash}` : "";
|
|
110
116
|
const header =
|
|
111
117
|
`## ${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}`;
|
|
118
|
+
`${meta}${projects} | ${row.total_chars} chars | ${showReview ? `${row.review_status} | ` : ""}updated ${row.updated_at?.slice(0, 10) ?? "?"}${hash}`;
|
|
113
119
|
|
|
114
120
|
if (include_content && row.content) {
|
|
115
121
|
return `${header}\n\n${row.content}`;
|
|
@@ -159,11 +165,7 @@ export const metadataSearchTool: ToolDefinition = {
|
|
|
159
165
|
description:
|
|
160
166
|
"Soft cap on total response bytes when include_content is true. Defaults to server maximum (200000).",
|
|
161
167
|
},
|
|
162
|
-
|
|
163
|
-
type: "string",
|
|
164
|
-
description:
|
|
165
|
-
'Name of the agent or user making this request. Recorded in the usage log. Defaults to "mcp-agent" if not provided. May be enforced via server config.',
|
|
166
|
-
},
|
|
168
|
+
author: AUTHOR_PARAM_READ,
|
|
167
169
|
},
|
|
168
170
|
},
|
|
169
171
|
handler,
|
|
@@ -38,6 +38,7 @@ import {
|
|
|
38
38
|
import { activeEmbedderName, embedBatch, resolveEmbedderKind } from "../embeddings/index.ts";
|
|
39
39
|
import { extractConflictHashes, isMissingFunctionError, logUsage } from "./_utils.ts";
|
|
40
40
|
import { McpInvalidParams, type MCPSupabaseClient, type ToolContext, type ToolDefinition } from "./types.ts";
|
|
41
|
+
import { AUTHOR_PARAM_WRITE, DEFAULT_IDENTITY, callerIdentity } from "./identity.ts";
|
|
41
42
|
|
|
42
43
|
/**
|
|
43
44
|
* Who to record as the author. Derived from the access path rather than taken
|
|
@@ -307,8 +308,8 @@ async function applyAndWrite(
|
|
|
307
308
|
p_source: null,
|
|
308
309
|
p_content_hash: newHash,
|
|
309
310
|
p_metadata: null, // null = keep existing metadata
|
|
310
|
-
//
|
|
311
|
-
|
|
311
|
+
// review_status: decided by the RPC from p_author_type + the store's
|
|
312
|
+
// review_workflow_enabled flag (#241), not here.
|
|
312
313
|
p_chunks: chunkData,
|
|
313
314
|
p_author: requestor,
|
|
314
315
|
// Who actually made the write, not which module executed it. The CLI is a
|
|
@@ -440,7 +441,7 @@ async function insertHandler(
|
|
|
440
441
|
documentId,
|
|
441
442
|
operations,
|
|
442
443
|
expectedHash,
|
|
443
|
-
requestor: (args
|
|
444
|
+
requestor: callerIdentity(args) ?? defaultRequestor(ctx),
|
|
444
445
|
toolLabel: "insert",
|
|
445
446
|
authorType: resolveAuthorType(ctx, args),
|
|
446
447
|
});
|
|
@@ -493,10 +494,7 @@ export const insertTool: ToolDefinition = {
|
|
|
493
494
|
description:
|
|
494
495
|
"content_hash of the version you are basing this on. Required — no last-write-wins.",
|
|
495
496
|
},
|
|
496
|
-
|
|
497
|
-
type: "string",
|
|
498
|
-
description: 'Agent or user making this request. Recorded in the usage log. Defaults to "mcp-agent".',
|
|
499
|
-
},
|
|
497
|
+
author: AUTHOR_PARAM_WRITE,
|
|
500
498
|
author_type: {
|
|
501
499
|
type: "string",
|
|
502
500
|
enum: ["user", "agent"],
|
|
@@ -538,7 +536,7 @@ async function editHandler(
|
|
|
538
536
|
documentId,
|
|
539
537
|
operations,
|
|
540
538
|
expectedHash,
|
|
541
|
-
requestor: (args
|
|
539
|
+
requestor: callerIdentity(args) ?? defaultRequestor(ctx),
|
|
542
540
|
toolLabel: "edit",
|
|
543
541
|
authorType: resolveAuthorType(ctx, args),
|
|
544
542
|
});
|
|
@@ -618,10 +616,7 @@ export const editTool: ToolDefinition = {
|
|
|
618
616
|
description:
|
|
619
617
|
"content_hash of the version you are basing these edits on. Required — no last-write-wins.",
|
|
620
618
|
},
|
|
621
|
-
|
|
622
|
-
type: "string",
|
|
623
|
-
description: 'Agent or user making this request. Recorded in the usage log. Defaults to "mcp-agent".',
|
|
624
|
-
},
|
|
619
|
+
author: AUTHOR_PARAM_WRITE,
|
|
625
620
|
author_type: {
|
|
626
621
|
type: "string",
|
|
627
622
|
enum: ["user", "agent"],
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
import { logUsage } from "./_utils.ts";
|
|
16
16
|
import { McpInvalidParams, type ToolContext, type ToolDefinition } from "./types.ts";
|
|
17
17
|
import type { MCPSupabaseClient } from "./types.ts";
|
|
18
|
+
import { AUTHOR_PARAM_READ, AUTHOR_PARAM_WRITE, DEFAULT_IDENTITY, callerIdentity } from "./identity.ts";
|
|
18
19
|
|
|
19
20
|
/** Relation types that carry behaviour; any other string is accepted too. */
|
|
20
21
|
const KNOWN_TYPES =
|
|
@@ -44,7 +45,7 @@ async function setHandler(
|
|
|
44
45
|
p_source_id: source,
|
|
45
46
|
p_target_id: target,
|
|
46
47
|
p_rel_type: relType,
|
|
47
|
-
p_author: (args
|
|
48
|
+
p_author: callerIdentity(args) ?? DEFAULT_IDENTITY,
|
|
48
49
|
p_author_type: "agent",
|
|
49
50
|
p_metadata: (args.metadata as Record<string, unknown> | undefined) ?? {},
|
|
50
51
|
});
|
|
@@ -57,7 +58,7 @@ async function setHandler(
|
|
|
57
58
|
logUsage(supabase, {
|
|
58
59
|
operation: "set_relation",
|
|
59
60
|
accessPath: ctx.accessPath,
|
|
60
|
-
requestor: args
|
|
61
|
+
requestor: callerIdentity(args),
|
|
61
62
|
document_id: source,
|
|
62
63
|
});
|
|
63
64
|
|
|
@@ -103,8 +104,7 @@ export const setRelationTool: ToolDefinition = {
|
|
|
103
104
|
type: "object",
|
|
104
105
|
description: "Optional JSON context for the edge (note, confidence, …)",
|
|
105
106
|
},
|
|
106
|
-
author:
|
|
107
|
-
requestor: { type: "string", description: "Name of the agent making this request" },
|
|
107
|
+
author: AUTHOR_PARAM_WRITE,
|
|
108
108
|
},
|
|
109
109
|
},
|
|
110
110
|
handler: setHandler,
|
|
@@ -126,7 +126,7 @@ async function deleteHandler(
|
|
|
126
126
|
p_source_id: source,
|
|
127
127
|
p_target_id: target,
|
|
128
128
|
p_rel_type: relType,
|
|
129
|
-
p_author: (args
|
|
129
|
+
p_author: callerIdentity(args) ?? DEFAULT_IDENTITY,
|
|
130
130
|
p_author_type: "agent",
|
|
131
131
|
});
|
|
132
132
|
if (error) throw new Error(`RPC error: ${error.message}`);
|
|
@@ -135,7 +135,7 @@ async function deleteHandler(
|
|
|
135
135
|
logUsage(supabase, {
|
|
136
136
|
operation: "delete_relation",
|
|
137
137
|
accessPath: ctx.accessPath,
|
|
138
|
-
requestor: args
|
|
138
|
+
requestor: callerIdentity(args),
|
|
139
139
|
document_id: source,
|
|
140
140
|
});
|
|
141
141
|
|
|
@@ -166,8 +166,7 @@ export const deleteRelationTool: ToolDefinition = {
|
|
|
166
166
|
source_id: { type: "string", description: "UUID of the source document" },
|
|
167
167
|
target_id: { type: "string", description: "UUID of the target document" },
|
|
168
168
|
rel_type: { type: "string", description: "Relation type to remove" },
|
|
169
|
-
author:
|
|
170
|
-
requestor: { type: "string", description: "Name of the agent making this request" },
|
|
169
|
+
author: AUTHOR_PARAM_WRITE,
|
|
171
170
|
},
|
|
172
171
|
},
|
|
173
172
|
handler: deleteHandler,
|
|
@@ -197,7 +196,7 @@ async function getRelationsHandler(
|
|
|
197
196
|
logUsage(supabase, {
|
|
198
197
|
operation: "get_relations",
|
|
199
198
|
accessPath: ctx.accessPath,
|
|
200
|
-
requestor: args
|
|
199
|
+
requestor: callerIdentity(args),
|
|
201
200
|
document_id: docId,
|
|
202
201
|
result_count: rows.length,
|
|
203
202
|
});
|
|
@@ -231,7 +230,7 @@ export const getRelationsTool: ToolDefinition = {
|
|
|
231
230
|
required: ["document_id"],
|
|
232
231
|
properties: {
|
|
233
232
|
document_id: { type: "string", description: "UUID of the document" },
|
|
234
|
-
|
|
233
|
+
author: AUTHOR_PARAM_READ,
|
|
235
234
|
},
|
|
236
235
|
},
|
|
237
236
|
handler: getRelationsHandler,
|
|
@@ -268,7 +267,7 @@ async function getNeighborsHandler(
|
|
|
268
267
|
logUsage(supabase, {
|
|
269
268
|
operation: "get_neighbors",
|
|
270
269
|
accessPath: ctx.accessPath,
|
|
271
|
-
requestor: args
|
|
270
|
+
requestor: callerIdentity(args),
|
|
272
271
|
document_id: docId,
|
|
273
272
|
result_count: rows.length,
|
|
274
273
|
});
|
|
@@ -307,7 +306,7 @@ export const getNeighborsTool: ToolDefinition = {
|
|
|
307
306
|
from_time: { type: "string", description: "ISO-8601: only neighbours created on/after" },
|
|
308
307
|
to_time: { type: "string", description: "ISO-8601: only neighbours created on/before" },
|
|
309
308
|
limit: { type: "integer", description: "Max documents to return (default 50, max 200)" },
|
|
310
|
-
|
|
309
|
+
author: AUTHOR_PARAM_READ,
|
|
311
310
|
},
|
|
312
311
|
},
|
|
313
312
|
handler: getNeighborsHandler,
|
|
@@ -21,6 +21,7 @@ import type { MCPSupabaseClient } from "./types.ts";
|
|
|
21
21
|
|
|
22
22
|
import { isDocumentNotFoundError, isMissingFunctionError, logUsage } from "./_utils.ts";
|
|
23
23
|
import { McpInvalidParams, type ToolContext, type ToolDefinition } from "./types.ts";
|
|
24
|
+
import { AUTHOR_PARAM_WRITE, DEFAULT_IDENTITY, callerIdentity } from "./identity.ts";
|
|
24
25
|
|
|
25
26
|
async function handler(
|
|
26
27
|
supabase: MCPSupabaseClient,
|
|
@@ -32,7 +33,7 @@ async function handler(
|
|
|
32
33
|
|
|
33
34
|
if (!document_id) throw new McpInvalidParams("document_id is required");
|
|
34
35
|
|
|
35
|
-
const author = (args
|
|
36
|
+
const author = callerIdentity(args);
|
|
36
37
|
// Derived from the transport, never taken from the caller: an agent must not
|
|
37
38
|
// be able to record itself as a user. Matches the delete handler.
|
|
38
39
|
const authorType = ctx.accessPath === "cli" ? "user" : "agent";
|
|
@@ -81,7 +82,7 @@ async function handler(
|
|
|
81
82
|
logUsage(supabase, {
|
|
82
83
|
operation: "restore",
|
|
83
84
|
accessPath: ctx.accessPath,
|
|
84
|
-
requestor:
|
|
85
|
+
requestor: author,
|
|
85
86
|
document_id,
|
|
86
87
|
result_count: 1,
|
|
87
88
|
});
|
|
@@ -116,14 +117,7 @@ export const restoreDocumentTool: ToolDefinition = {
|
|
|
116
117
|
description:
|
|
117
118
|
"Why this document is being restored. Recorded in the audit-log entry. Short and specific beats long.",
|
|
118
119
|
},
|
|
119
|
-
author:
|
|
120
|
-
type: "string",
|
|
121
|
-
description: "Who is making this change. Recorded in the audit log.",
|
|
122
|
-
},
|
|
123
|
-
requestor: {
|
|
124
|
-
type: "string",
|
|
125
|
-
description: "Name of the agent or user making this request. Recorded in the usage log.",
|
|
126
|
-
},
|
|
120
|
+
author: AUTHOR_PARAM_WRITE,
|
|
127
121
|
},
|
|
128
122
|
},
|
|
129
123
|
handler,
|
|
@@ -22,6 +22,7 @@ import { applyByteBudget, getConfiguredMinSearchScore, getConfiguredSearchAlpha,
|
|
|
22
22
|
getMaxResponseBytes, getMinTermCoverage, logUsage } from "./_utils.ts";
|
|
23
23
|
import { lookupProjectId } from "./_projects.ts";
|
|
24
24
|
import { McpInvalidParams, type ToolContext, type ToolDefinition } from "./types.ts";
|
|
25
|
+
import { AUTHOR_PARAM_READ, callerIdentity } from "./identity.ts";
|
|
25
26
|
|
|
26
27
|
async function handler(
|
|
27
28
|
supabase: MCPSupabaseClient,
|
|
@@ -135,7 +136,7 @@ async function handler(
|
|
|
135
136
|
logUsage(supabase, {
|
|
136
137
|
operation: "search",
|
|
137
138
|
accessPath: ctx.accessPath,
|
|
138
|
-
requestor: args
|
|
139
|
+
requestor: callerIdentity(args),
|
|
139
140
|
query_text: query,
|
|
140
141
|
project_id: projectId,
|
|
141
142
|
result_count: accepted.length,
|
|
@@ -244,11 +245,7 @@ export const searchTool: ToolDefinition = {
|
|
|
244
245
|
description:
|
|
245
246
|
"Optional response size budget in bytes. Results are dropped whole until the budget is satisfied; a truncated flag is set when results are dropped. Defaults to the server maximum (200000). Pass a smaller value if your context window is limited. Values above the server maximum are silently capped.",
|
|
246
247
|
},
|
|
247
|
-
|
|
248
|
-
type: "string",
|
|
249
|
-
description:
|
|
250
|
-
'Name of the agent or user making this request (e.g., "Claude Code", "archiver"). Recorded in the usage log for attribution. Defaults to "mcp-agent" if not provided. May be enforced via server config.',
|
|
251
|
-
},
|
|
248
|
+
author: AUTHOR_PARAM_READ,
|
|
252
249
|
},
|
|
253
250
|
},
|
|
254
251
|
handler,
|
|
@@ -21,6 +21,7 @@ import type { MCPSupabaseClient } from "./types.ts";
|
|
|
21
21
|
|
|
22
22
|
import { logUsage } from "./_utils.ts";
|
|
23
23
|
import { McpInvalidParams, type ToolContext, type ToolDefinition } from "./types.ts";
|
|
24
|
+
import { AUTHOR_PARAM_WRITE, DEFAULT_IDENTITY, callerIdentity } from "./identity.ts";
|
|
24
25
|
|
|
25
26
|
async function handler(
|
|
26
27
|
supabase: MCPSupabaseClient,
|
|
@@ -48,7 +49,7 @@ async function handler(
|
|
|
48
49
|
);
|
|
49
50
|
}
|
|
50
51
|
|
|
51
|
-
const author = (args
|
|
52
|
+
const author = callerIdentity(args);
|
|
52
53
|
// Derived from the transport, never taken from the caller: an agent must not
|
|
53
54
|
// be able to record itself as a user. Matches the partial-edit handlers.
|
|
54
55
|
const authorType = ctx.accessPath === "cli" ? "user" : "agent";
|
|
@@ -71,7 +72,7 @@ async function handler(
|
|
|
71
72
|
logUsage(supabase, {
|
|
72
73
|
operation: "update_metadata",
|
|
73
74
|
accessPath: ctx.accessPath,
|
|
74
|
-
requestor:
|
|
75
|
+
requestor: author,
|
|
75
76
|
document_id,
|
|
76
77
|
result_count: 1,
|
|
77
78
|
});
|
|
@@ -119,14 +120,7 @@ export const setDocumentMetadataTool: ToolDefinition = {
|
|
|
119
120
|
description:
|
|
120
121
|
"Set the metadata to EXACTLY this object, discarding any key not listed. Defaults to false (merge). Use only when you mean to reset a document's tags wholesale.",
|
|
121
122
|
},
|
|
122
|
-
author:
|
|
123
|
-
type: "string",
|
|
124
|
-
description: "Who is making this change. Recorded in the audit log.",
|
|
125
|
-
},
|
|
126
|
-
requestor: {
|
|
127
|
-
type: "string",
|
|
128
|
-
description: "Name of the agent or user making this request. Recorded in the usage log.",
|
|
129
|
-
},
|
|
123
|
+
author: AUTHOR_PARAM_WRITE,
|
|
130
124
|
},
|
|
131
125
|
},
|
|
132
126
|
handler,
|
|
@@ -13,6 +13,7 @@ import type { MCPSupabaseClient } from "./types.ts";
|
|
|
13
13
|
|
|
14
14
|
import { replaceDocumentProjects } from "./_projects.ts";
|
|
15
15
|
import { McpInvalidParams, type ToolContext, type ToolDefinition } from "./types.ts";
|
|
16
|
+
import { AUTHOR_PARAM_WRITE, DEFAULT_IDENTITY, callerIdentity } from "./identity.ts";
|
|
16
17
|
|
|
17
18
|
async function handler(
|
|
18
19
|
supabase: MCPSupabaseClient,
|
|
@@ -21,7 +22,7 @@ async function handler(
|
|
|
21
22
|
): Promise<string> {
|
|
22
23
|
const document_id = (args.document_id as string | undefined)?.trim();
|
|
23
24
|
const project_names_raw = args.project_names;
|
|
24
|
-
const author = (args
|
|
25
|
+
const author = callerIdentity(args) ?? DEFAULT_IDENTITY;
|
|
25
26
|
|
|
26
27
|
if (!document_id) {
|
|
27
28
|
throw new McpInvalidParams(
|
|
@@ -96,11 +97,7 @@ export const setDocumentProjectsTool: ToolDefinition = {
|
|
|
96
97
|
description:
|
|
97
98
|
"Explicit list of project names. Each created if absent. Order is preserved. Empty list = remove from all projects.",
|
|
98
99
|
},
|
|
99
|
-
author:
|
|
100
|
-
type: "string",
|
|
101
|
-
description:
|
|
102
|
-
'Agent or tool name recorded in the audit log. Defaults to "mcp-agent". May be enforced via server config.',
|
|
103
|
-
},
|
|
100
|
+
author: AUTHOR_PARAM_WRITE,
|
|
104
101
|
},
|
|
105
102
|
},
|
|
106
103
|
handler,
|
|
@@ -0,0 +1,74 @@
|
|
|
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. That RPC lives in rpcs.sql, which
|
|
11
|
+
-- `cerefox server deploy` re-applies. (0.16.0 also had the RPC read this flag
|
|
12
|
+
-- on write and store 'approved' for everyone while off; 0.16.1 removed that —
|
|
13
|
+
-- the flag governs visibility only, the stored value follows author_type.)
|
|
14
|
+
--
|
|
15
|
+
-- #240: cerefox_hybrid_search / cerefox_search_docs gain p_review_status so a
|
|
16
|
+
-- filtered search is applied before the limit, not after. A new argument is a
|
|
17
|
+
-- new overload; the old ones must go or PostgREST calls become ambiguous
|
|
18
|
+
-- (PGRST203). Same DROPs sit at the top of rpcs.sql for the fresh path.
|
|
19
|
+
--
|
|
20
|
+
-- Idempotent: safe to re-run.
|
|
21
|
+
|
|
22
|
+
INSERT INTO cerefox_config (key, value)
|
|
23
|
+
VALUES ('review_workflow_enabled', 'true')
|
|
24
|
+
ON CONFLICT (key) DO NOTHING;
|
|
25
|
+
|
|
26
|
+
DROP FUNCTION IF EXISTS cerefox_hybrid_search(TEXT, VECTOR(768), INT, FLOAT, BOOLEAN, UUID, FLOAT, JSONB, FLOAT);
|
|
27
|
+
DROP FUNCTION IF EXISTS cerefox_search_docs(TEXT, VECTOR(768), INT, FLOAT, UUID, FLOAT, INT, INT, JSONB, FLOAT);
|
|
28
|
+
|
|
29
|
+
-- The allow-list in cerefox_set_config grows by one key. Same signature, so
|
|
30
|
+
-- OR REPLACE is enough (no overload to drop). Carried here as well as in
|
|
31
|
+
-- rpcs.sql so `db_migrate` alone leaves the key settable; the unit test
|
|
32
|
+
-- `rpc-guard-invariants` pins the two lists to each other.
|
|
33
|
+
CREATE OR REPLACE FUNCTION cerefox_set_config(
|
|
34
|
+
p_key TEXT,
|
|
35
|
+
p_value TEXT,
|
|
36
|
+
p_author TEXT DEFAULT 'unknown',
|
|
37
|
+
p_author_type TEXT DEFAULT 'user'
|
|
38
|
+
)
|
|
39
|
+
RETURNS VOID
|
|
40
|
+
LANGUAGE plpgsql
|
|
41
|
+
SECURITY DEFINER
|
|
42
|
+
SET search_path = public, pg_catalog
|
|
43
|
+
AS $$
|
|
44
|
+
DECLARE
|
|
45
|
+
v_allowed TEXT[] := ARRAY[
|
|
46
|
+
'usage_tracking_enabled', 'require_requestor_identity', 'requestor_identity_format',
|
|
47
|
+
'min_search_score', 'min_term_coverage', 'search_alpha',
|
|
48
|
+
'version_retention_hours', 'version_cleanup_enabled',
|
|
49
|
+
'relations_enabled',
|
|
50
|
+
'review_workflow_enabled',
|
|
51
|
+
'document_size_warning_chars'
|
|
52
|
+
];
|
|
53
|
+
v_old TEXT;
|
|
54
|
+
BEGIN
|
|
55
|
+
IF NOT (p_key = ANY(v_allowed)) THEN
|
|
56
|
+
RAISE EXCEPTION 'Unknown config key: %. Allowed keys: %', p_key, v_allowed;
|
|
57
|
+
END IF;
|
|
58
|
+
|
|
59
|
+
SELECT value INTO v_old FROM cerefox_config WHERE key = p_key;
|
|
60
|
+
|
|
61
|
+
INSERT INTO cerefox_config (key, value)
|
|
62
|
+
VALUES (p_key, p_value)
|
|
63
|
+
ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value;
|
|
64
|
+
|
|
65
|
+
PERFORM cerefox_create_audit_entry(
|
|
66
|
+
p_operation := 'config-change',
|
|
67
|
+
p_author := p_author,
|
|
68
|
+
p_author_type := p_author_type,
|
|
69
|
+
p_description := 'config: ' || p_key || ': '
|
|
70
|
+
|| COALESCE('''' || v_old || '''', '(unset)')
|
|
71
|
+
|| ' → ''' || p_value || ''''
|
|
72
|
+
);
|
|
73
|
+
END;
|
|
74
|
+
$$;
|
|
@@ -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,12 @@ $$;
|
|
|
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 alone (agent →
|
|
1399
|
+
-- pending_review, user → approved), so every transport
|
|
1400
|
+
-- behaves the same. The `review_workflow_enabled` flag
|
|
1401
|
+
-- only governs what surfaces show (0.16.1).
|
|
1402
|
+
-- Kept in the signature so no caller breaks.
|
|
1383
1403
|
-- p_chunks : JSONB array of chunk objects, each with:
|
|
1384
1404
|
-- chunk_index, heading_path, heading_level, title,
|
|
1385
1405
|
-- content, char_count, embedding (float[]), embedder (text)
|
|
@@ -1564,9 +1584,20 @@ BEGIN
|
|
|
1564
1584
|
USING ERRCODE = '22023'; -- deterministic; never a retryable SQLSTATE
|
|
1565
1585
|
END IF;
|
|
1566
1586
|
|
|
1567
|
-
--
|
|
1568
|
-
|
|
1569
|
-
|
|
1587
|
+
-- Review status is decided HERE, not by the caller (#241), from the
|
|
1588
|
+
-- author type alone: an agent write is 'pending_review', a person's is
|
|
1589
|
+
-- 'approved'. The `review_workflow_enabled` flag is deliberately NOT
|
|
1590
|
+
-- consulted (0.16.1): it governs what the surfaces show and enforce, not
|
|
1591
|
+
-- what is stored, so a store that turns the workflow off and later back on
|
|
1592
|
+
-- sees exactly the statuses it would have had all along. 0.16.0 wrote
|
|
1593
|
+
-- 'approved' for every author while the flag was off; that made a stored
|
|
1594
|
+
-- 'approved' mean two different things depending on when it was written.
|
|
1595
|
+
-- p_review_status is not consulted either — six clients used to compute
|
|
1596
|
+
-- it and they could not have agreed on a store-level policy.
|
|
1597
|
+
v_status := CASE
|
|
1598
|
+
WHEN p_author_type = 'agent' THEN 'pending_review'
|
|
1599
|
+
ELSE 'approved'
|
|
1600
|
+
END;
|
|
1570
1601
|
|
|
1571
1602
|
-- Count chunks and total chars from the input
|
|
1572
1603
|
v_chunk_count := jsonb_array_length(p_chunks);
|
|
@@ -2704,6 +2735,9 @@ DECLARE
|
|
|
2704
2735
|
'version_retention_hours', 'version_cleanup_enabled',
|
|
2705
2736
|
-- Optional features, off by default (iteration 29).
|
|
2706
2737
|
'relations_enabled',
|
|
2738
|
+
-- #241: the review workflow. Off on fresh installs, on for stores that
|
|
2739
|
+
-- predate the flag. Read by cerefox_ingest_document on every write.
|
|
2740
|
+
'review_workflow_enabled',
|
|
2707
2741
|
-- Iteration 33: flag writes that push a document past this many chars
|
|
2708
2742
|
-- (0 = off). Partial edits make writes cheap, so an insert-only agent
|
|
2709
2743
|
-- never assembles the document and never sees it grow past its split
|
|
@@ -3057,6 +3091,17 @@ SET search_path = public, pg_catalog
|
|
|
3057
3091
|
AS $$
|
|
3058
3092
|
-- Keep in lockstep with the `@version:` marker in schema.sql (cut_release.ts
|
|
3059
3093
|
-- enforces it). Bump whenever schema.sql OR rpcs.sql changes.
|
|
3094
|
+
-- 0.16.1 (v1.13.1): cerefox_ingest_document no longer consults
|
|
3095
|
+
-- `review_workflow_enabled` — review_status follows author_type whatever
|
|
3096
|
+
-- the flag says; the flag governs visibility/enforcement only. RPC-only,
|
|
3097
|
+
-- no migration (server deploy re-applies rpcs.sql).
|
|
3098
|
+
-- 0.16.0 (#241, #240): `review_workflow_enabled` config key (seeded false
|
|
3099
|
+
-- on fresh installs, true by migration 0031 on existing ones);
|
|
3100
|
+
-- cerefox_ingest_document decides review_status itself from author_type
|
|
3101
|
+
-- + the flag, p_review_status is ignored; p_review_status filter on
|
|
3102
|
+
-- cerefox_hybrid_search / cerefox_search_docs, applied before the limit.
|
|
3103
|
+
-- 0.15.0 (iteration 39): cerefox_rename_document.
|
|
3104
|
+
-- 0.14.0 (#147/#219): store-level writes audited in-RPC.
|
|
3060
3105
|
-- 0.13.0 (#216): archived chunks carry no search artifacts —
|
|
3061
3106
|
-- cerefox_snapshot_version nulls embedding_primary/embedding_upgrade/fts
|
|
3062
3107
|
-- at archive time; embedding_primary becomes nullable; migration 0027
|
|
@@ -3079,7 +3124,7 @@ AS $$
|
|
|
3079
3124
|
-- 0.11.0 supersedes 0.10.6 (v1.2.1, #191): this branch carries that fix plus
|
|
3080
3125
|
-- the partial-edit surface, and both migrations (0019, 0020) are in the
|
|
3081
3126
|
-- sequence, so a store deploying this gets everything from both lines.
|
|
3082
|
-
SELECT '0.
|
|
3127
|
+
SELECT '0.16.1'::TEXT;
|
|
3083
3128
|
$$;
|
|
3084
3129
|
|
|
3085
3130
|
-- ── 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.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 —
|
|
@@ -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, from author_type alone. The store-level
|
|
60
|
+
-- `review_workflow_enabled` flag (#241) governs whether any surface shows
|
|
61
|
+
-- or enforces the column; it never changes what is stored (0.16.1).
|
|
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 ────────────────────────────────────────────────────────────────
|