@cerefox/memory 0.7.2 → 0.8.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/dist/bin/cerefox.js +1357 -361
- package/dist/frontend/assets/{index-BzAPcCXA.js → index-CAp2_lFX.js} +2 -2
- package/dist/frontend/assets/index-CAp2_lFX.js.map +1 -0
- package/dist/frontend/index.html +1 -1
- package/dist/server-assets/_shared/ef-meta/index.ts +97 -0
- package/dist/server-assets/_shared/embeddings/index.ts +175 -0
- package/dist/server-assets/_shared/mcp-tools/_chunker.ts +187 -0
- package/dist/server-assets/_shared/mcp-tools/_projects.ts +121 -0
- package/dist/server-assets/_shared/mcp-tools/_utils.ts +73 -0
- package/dist/server-assets/_shared/mcp-tools/audit-log.ts +95 -0
- package/dist/server-assets/_shared/mcp-tools/get-document.ts +73 -0
- package/dist/server-assets/_shared/mcp-tools/get-help-content.ts +26 -0
- package/dist/server-assets/_shared/mcp-tools/get-help.ts +90 -0
- package/dist/server-assets/_shared/mcp-tools/index.ts +67 -0
- package/dist/server-assets/_shared/mcp-tools/ingest.ts +315 -0
- package/dist/server-assets/_shared/mcp-tools/list-metadata-keys.ts +55 -0
- package/dist/server-assets/_shared/mcp-tools/list-projects.ts +59 -0
- package/dist/server-assets/_shared/mcp-tools/list-versions.ts +72 -0
- package/dist/server-assets/_shared/mcp-tools/metadata-search.ts +154 -0
- package/dist/server-assets/_shared/mcp-tools/search.ts +193 -0
- package/dist/server-assets/_shared/mcp-tools/set-document-projects.ts +163 -0
- package/dist/server-assets/_shared/mcp-tools/types.ts +92 -0
- package/dist/server-assets/db/migrations/0003_add_document_versions.sql +91 -0
- package/dist/server-assets/db/migrations/0004_add_audit_log_review_status_archived.sql +71 -0
- package/dist/server-assets/db/migrations/0005_metadata_search.sql +628 -0
- package/dist/server-assets/db/migrations/0006_usage_log.sql +255 -0
- package/dist/server-assets/db/migrations/0007_usage_log_requestor.sql +178 -0
- package/dist/server-assets/db/migrations/0008_soft_delete.sql +130 -0
- package/dist/server-assets/db/migrations/0009_audit_log_restore_operation.sql +20 -0
- package/dist/server-assets/db/migrations/0010_requestor_enforcement_config.sql +12 -0
- package/dist/server-assets/db/migrations/0011_title_boosting.sql +48 -0
- package/dist/server-assets/db/rpcs.sql +1723 -0
- package/dist/server-assets/db/schema.sql +380 -0
- package/dist/server-assets/supabase/functions/cerefox-get-audit-log/index.ts +117 -0
- package/dist/server-assets/supabase/functions/cerefox-get-document/index.ts +138 -0
- package/dist/server-assets/supabase/functions/cerefox-ingest/index.ts +819 -0
- package/dist/server-assets/supabase/functions/cerefox-list-projects/index.ts +96 -0
- package/dist/server-assets/supabase/functions/cerefox-list-versions/index.ts +113 -0
- package/dist/server-assets/supabase/functions/cerefox-mcp/index.ts +294 -0
- package/dist/server-assets/supabase/functions/cerefox-mcp/shared.ts +42 -0
- package/dist/server-assets/supabase/functions/cerefox-metadata/index.ts +99 -0
- package/dist/server-assets/supabase/functions/cerefox-metadata-search/index.ts +146 -0
- package/dist/server-assets/supabase/functions/cerefox-search/index.ts +382 -0
- package/docs/guides/connect-agents.md +78 -3
- package/docs/guides/migration-v0.5.md +50 -0
- package/docs/guides/quickstart.md +6 -2
- package/package.json +3 -2
- package/dist/frontend/assets/index-BzAPcCXA.js.map +0 -1
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
-- Migration 0006: Usage tracking + config table (Iteration 16C)
|
|
2
|
+
--
|
|
3
|
+
-- New tables:
|
|
4
|
+
-- cerefox_config -- key-value config (e.g., usage_tracking_enabled)
|
|
5
|
+
-- cerefox_usage_log -- read operation log for analytics
|
|
6
|
+
--
|
|
7
|
+
-- New RPCs:
|
|
8
|
+
-- cerefox_get_config, cerefox_set_config
|
|
9
|
+
-- cerefox_log_usage
|
|
10
|
+
-- cerefox_list_usage_log
|
|
11
|
+
-- cerefox_usage_summary
|
|
12
|
+
|
|
13
|
+
-- ── 1. Config table ──────────────────────────────────────────────────────────
|
|
14
|
+
|
|
15
|
+
CREATE TABLE IF NOT EXISTS cerefox_config (
|
|
16
|
+
key TEXT PRIMARY KEY,
|
|
17
|
+
value TEXT NOT NULL
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
-- RLS: deny direct access; all access goes through SECURITY DEFINER RPCs
|
|
21
|
+
ALTER TABLE cerefox_config ENABLE ROW LEVEL SECURITY;
|
|
22
|
+
|
|
23
|
+
-- Seed default config
|
|
24
|
+
INSERT INTO cerefox_config (key, value)
|
|
25
|
+
VALUES ('usage_tracking_enabled', 'false')
|
|
26
|
+
ON CONFLICT (key) DO NOTHING;
|
|
27
|
+
|
|
28
|
+
-- ── 2. Usage log table ───────────────────────────────────────────────────────
|
|
29
|
+
|
|
30
|
+
CREATE TABLE IF NOT EXISTS cerefox_usage_log (
|
|
31
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
32
|
+
logged_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
33
|
+
operation TEXT NOT NULL,
|
|
34
|
+
access_path TEXT NOT NULL,
|
|
35
|
+
reader TEXT,
|
|
36
|
+
document_id UUID REFERENCES cerefox_documents(id) ON DELETE SET NULL,
|
|
37
|
+
project_id UUID REFERENCES cerefox_projects(id) ON DELETE SET NULL,
|
|
38
|
+
query_text TEXT,
|
|
39
|
+
result_count INT,
|
|
40
|
+
extra JSONB DEFAULT '{}'::JSONB
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
-- RLS: deny direct access
|
|
44
|
+
ALTER TABLE cerefox_usage_log ENABLE ROW LEVEL SECURITY;
|
|
45
|
+
|
|
46
|
+
-- Indexes
|
|
47
|
+
CREATE INDEX IF NOT EXISTS idx_usage_log_logged_at
|
|
48
|
+
ON cerefox_usage_log (logged_at DESC);
|
|
49
|
+
CREATE INDEX IF NOT EXISTS idx_usage_log_operation_logged_at
|
|
50
|
+
ON cerefox_usage_log (operation, logged_at DESC);
|
|
51
|
+
CREATE INDEX IF NOT EXISTS idx_usage_log_access_path
|
|
52
|
+
ON cerefox_usage_log (access_path);
|
|
53
|
+
CREATE INDEX IF NOT EXISTS idx_usage_log_reader
|
|
54
|
+
ON cerefox_usage_log (reader) WHERE reader IS NOT NULL;
|
|
55
|
+
CREATE INDEX IF NOT EXISTS idx_usage_log_document_id
|
|
56
|
+
ON cerefox_usage_log (document_id) WHERE document_id IS NOT NULL;
|
|
57
|
+
|
|
58
|
+
-- ── 3. Config RPCs ───────────────────────────────────────────────────────────
|
|
59
|
+
|
|
60
|
+
CREATE OR REPLACE FUNCTION cerefox_get_config(p_key TEXT)
|
|
61
|
+
RETURNS TEXT
|
|
62
|
+
LANGUAGE sql
|
|
63
|
+
SECURITY DEFINER
|
|
64
|
+
STABLE
|
|
65
|
+
SET search_path = public, pg_catalog
|
|
66
|
+
AS $$
|
|
67
|
+
SELECT value FROM cerefox_config WHERE key = p_key;
|
|
68
|
+
$$;
|
|
69
|
+
|
|
70
|
+
CREATE OR REPLACE FUNCTION cerefox_set_config(p_key TEXT, p_value TEXT)
|
|
71
|
+
RETURNS VOID
|
|
72
|
+
LANGUAGE plpgsql
|
|
73
|
+
SECURITY DEFINER
|
|
74
|
+
SET search_path = public, pg_catalog
|
|
75
|
+
AS $$
|
|
76
|
+
DECLARE
|
|
77
|
+
v_allowed TEXT[] := ARRAY['usage_tracking_enabled'];
|
|
78
|
+
BEGIN
|
|
79
|
+
IF NOT (p_key = ANY(v_allowed)) THEN
|
|
80
|
+
RAISE EXCEPTION 'Unknown config key: %. Allowed keys: %', p_key, v_allowed;
|
|
81
|
+
END IF;
|
|
82
|
+
|
|
83
|
+
INSERT INTO cerefox_config (key, value)
|
|
84
|
+
VALUES (p_key, p_value)
|
|
85
|
+
ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value;
|
|
86
|
+
END;
|
|
87
|
+
$$;
|
|
88
|
+
|
|
89
|
+
-- ── 4. Log usage RPC ─────────────────────────────────────────────────────────
|
|
90
|
+
-- Checks config first; no-op if tracking is disabled.
|
|
91
|
+
|
|
92
|
+
CREATE OR REPLACE FUNCTION cerefox_log_usage(
|
|
93
|
+
p_operation TEXT,
|
|
94
|
+
p_access_path TEXT,
|
|
95
|
+
p_reader TEXT DEFAULT NULL,
|
|
96
|
+
p_document_id UUID DEFAULT NULL,
|
|
97
|
+
p_project_id UUID DEFAULT NULL,
|
|
98
|
+
p_query_text TEXT DEFAULT NULL,
|
|
99
|
+
p_result_count INT DEFAULT NULL,
|
|
100
|
+
p_extra JSONB DEFAULT '{}'::JSONB
|
|
101
|
+
)
|
|
102
|
+
RETURNS VOID
|
|
103
|
+
LANGUAGE plpgsql
|
|
104
|
+
SECURITY DEFINER
|
|
105
|
+
SET search_path = public, pg_catalog
|
|
106
|
+
AS $$
|
|
107
|
+
DECLARE
|
|
108
|
+
v_enabled TEXT;
|
|
109
|
+
BEGIN
|
|
110
|
+
SELECT value INTO v_enabled FROM cerefox_config WHERE key = 'usage_tracking_enabled';
|
|
111
|
+
IF v_enabled IS NULL OR v_enabled != 'true' THEN
|
|
112
|
+
RETURN; -- tracking disabled; no-op
|
|
113
|
+
END IF;
|
|
114
|
+
|
|
115
|
+
INSERT INTO cerefox_usage_log (
|
|
116
|
+
operation, access_path, reader, document_id, project_id,
|
|
117
|
+
query_text, result_count, extra
|
|
118
|
+
) VALUES (
|
|
119
|
+
p_operation, p_access_path, p_reader, p_document_id, p_project_id,
|
|
120
|
+
p_query_text, p_result_count, p_extra
|
|
121
|
+
);
|
|
122
|
+
END;
|
|
123
|
+
$$;
|
|
124
|
+
|
|
125
|
+
-- ── 5. List usage log RPC ────────────────────────────────────────────────────
|
|
126
|
+
|
|
127
|
+
CREATE OR REPLACE FUNCTION cerefox_list_usage_log(
|
|
128
|
+
p_start TIMESTAMPTZ DEFAULT NULL,
|
|
129
|
+
p_end TIMESTAMPTZ DEFAULT NULL,
|
|
130
|
+
p_operation TEXT DEFAULT NULL,
|
|
131
|
+
p_access_path TEXT DEFAULT NULL,
|
|
132
|
+
p_reader TEXT DEFAULT NULL,
|
|
133
|
+
p_project_id UUID DEFAULT NULL,
|
|
134
|
+
p_limit INT DEFAULT 100
|
|
135
|
+
)
|
|
136
|
+
RETURNS TABLE (
|
|
137
|
+
id UUID,
|
|
138
|
+
logged_at TIMESTAMPTZ,
|
|
139
|
+
operation TEXT,
|
|
140
|
+
access_path TEXT,
|
|
141
|
+
reader TEXT,
|
|
142
|
+
document_id UUID,
|
|
143
|
+
doc_title TEXT,
|
|
144
|
+
project_id UUID,
|
|
145
|
+
query_text TEXT,
|
|
146
|
+
result_count INT,
|
|
147
|
+
extra JSONB
|
|
148
|
+
)
|
|
149
|
+
LANGUAGE sql
|
|
150
|
+
SECURITY DEFINER
|
|
151
|
+
STABLE
|
|
152
|
+
SET search_path = public, pg_catalog
|
|
153
|
+
AS $$
|
|
154
|
+
SELECT
|
|
155
|
+
ul.id,
|
|
156
|
+
ul.logged_at,
|
|
157
|
+
ul.operation,
|
|
158
|
+
ul.access_path,
|
|
159
|
+
ul.reader,
|
|
160
|
+
ul.document_id,
|
|
161
|
+
d.title AS doc_title,
|
|
162
|
+
ul.project_id,
|
|
163
|
+
ul.query_text,
|
|
164
|
+
ul.result_count,
|
|
165
|
+
ul.extra
|
|
166
|
+
FROM cerefox_usage_log ul
|
|
167
|
+
LEFT JOIN cerefox_documents d ON ul.document_id = d.id
|
|
168
|
+
WHERE (p_start IS NULL OR ul.logged_at >= p_start)
|
|
169
|
+
AND (p_end IS NULL OR ul.logged_at <= p_end)
|
|
170
|
+
AND (p_operation IS NULL OR ul.operation = p_operation)
|
|
171
|
+
AND (p_access_path IS NULL OR ul.access_path = p_access_path)
|
|
172
|
+
AND (p_reader IS NULL OR ul.reader = p_reader)
|
|
173
|
+
AND (p_project_id IS NULL OR ul.project_id = p_project_id)
|
|
174
|
+
ORDER BY ul.logged_at DESC
|
|
175
|
+
LIMIT p_limit;
|
|
176
|
+
$$;
|
|
177
|
+
|
|
178
|
+
-- ── 6. Usage summary RPC ─────────────────────────────────────────────────────
|
|
179
|
+
-- Returns a JSON object with aggregated stats for the analytics page.
|
|
180
|
+
|
|
181
|
+
CREATE OR REPLACE FUNCTION cerefox_usage_summary(
|
|
182
|
+
p_start TIMESTAMPTZ DEFAULT NULL,
|
|
183
|
+
p_end TIMESTAMPTZ DEFAULT NULL,
|
|
184
|
+
p_project_id UUID DEFAULT NULL,
|
|
185
|
+
p_access_path TEXT DEFAULT NULL
|
|
186
|
+
)
|
|
187
|
+
RETURNS JSON
|
|
188
|
+
LANGUAGE plpgsql
|
|
189
|
+
SECURITY DEFINER
|
|
190
|
+
STABLE
|
|
191
|
+
SET search_path = public, pg_catalog
|
|
192
|
+
AS $$
|
|
193
|
+
DECLARE
|
|
194
|
+
v_result JSON;
|
|
195
|
+
BEGIN
|
|
196
|
+
WITH filtered AS (
|
|
197
|
+
SELECT *
|
|
198
|
+
FROM cerefox_usage_log ul
|
|
199
|
+
WHERE (p_start IS NULL OR ul.logged_at >= p_start)
|
|
200
|
+
AND (p_end IS NULL OR ul.logged_at <= p_end)
|
|
201
|
+
AND (p_project_id IS NULL OR ul.project_id = p_project_id)
|
|
202
|
+
AND (p_access_path IS NULL OR ul.access_path = p_access_path)
|
|
203
|
+
),
|
|
204
|
+
ops_by_day AS (
|
|
205
|
+
SELECT
|
|
206
|
+
DATE(logged_at) AS day,
|
|
207
|
+
COUNT(*) AS count
|
|
208
|
+
FROM filtered
|
|
209
|
+
GROUP BY DATE(logged_at)
|
|
210
|
+
ORDER BY day
|
|
211
|
+
),
|
|
212
|
+
ops_by_operation AS (
|
|
213
|
+
SELECT operation, COUNT(*) AS count
|
|
214
|
+
FROM filtered
|
|
215
|
+
GROUP BY operation
|
|
216
|
+
ORDER BY count DESC
|
|
217
|
+
),
|
|
218
|
+
ops_by_access_path AS (
|
|
219
|
+
SELECT access_path, COUNT(*) AS count
|
|
220
|
+
FROM filtered
|
|
221
|
+
GROUP BY access_path
|
|
222
|
+
ORDER BY count DESC
|
|
223
|
+
),
|
|
224
|
+
top_documents AS (
|
|
225
|
+
SELECT
|
|
226
|
+
f.document_id,
|
|
227
|
+
d.title AS doc_title,
|
|
228
|
+
COUNT(*) AS count
|
|
229
|
+
FROM filtered f
|
|
230
|
+
JOIN cerefox_documents d ON f.document_id = d.id
|
|
231
|
+
WHERE f.document_id IS NOT NULL
|
|
232
|
+
GROUP BY f.document_id, d.title
|
|
233
|
+
ORDER BY count DESC
|
|
234
|
+
LIMIT 10
|
|
235
|
+
),
|
|
236
|
+
top_readers AS (
|
|
237
|
+
SELECT reader, COUNT(*) AS count
|
|
238
|
+
FROM filtered
|
|
239
|
+
WHERE reader IS NOT NULL
|
|
240
|
+
GROUP BY reader
|
|
241
|
+
ORDER BY count DESC
|
|
242
|
+
LIMIT 10
|
|
243
|
+
)
|
|
244
|
+
SELECT json_build_object(
|
|
245
|
+
'total_count', (SELECT COUNT(*) FROM filtered),
|
|
246
|
+
'ops_by_day', COALESCE((SELECT json_agg(json_build_object('day', day, 'count', count)) FROM ops_by_day), '[]'::JSON),
|
|
247
|
+
'ops_by_operation', COALESCE((SELECT json_agg(json_build_object('operation', operation, 'count', count)) FROM ops_by_operation), '[]'::JSON),
|
|
248
|
+
'ops_by_access_path', COALESCE((SELECT json_agg(json_build_object('access_path', access_path, 'count', count)) FROM ops_by_access_path), '[]'::JSON),
|
|
249
|
+
'top_documents', COALESCE((SELECT json_agg(json_build_object('document_id', document_id, 'doc_title', doc_title, 'count', count)) FROM top_documents), '[]'::JSON),
|
|
250
|
+
'top_readers', COALESCE((SELECT json_agg(json_build_object('reader', reader, 'count', count)) FROM top_readers), '[]'::JSON)
|
|
251
|
+
) INTO v_result;
|
|
252
|
+
|
|
253
|
+
RETURN v_result;
|
|
254
|
+
END;
|
|
255
|
+
$$;
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
-- Migration 0007: Rename reader to requestor in cerefox_usage_log
|
|
2
|
+
--
|
|
3
|
+
-- The usage log tracks ALL operations (both reads and writes), not just reads.
|
|
4
|
+
-- The "reader" column name implied read-only tracking. "requestor" is a neutral
|
|
5
|
+
-- term that covers both the agent/user performing a search (read) and the
|
|
6
|
+
-- agent/user performing an ingest (write).
|
|
7
|
+
|
|
8
|
+
-- 1. Rename the column
|
|
9
|
+
ALTER TABLE cerefox_usage_log RENAME COLUMN reader TO requestor;
|
|
10
|
+
|
|
11
|
+
-- 2. Rename the index (Postgres doesn't have RENAME INDEX, so drop and recreate)
|
|
12
|
+
DROP INDEX IF EXISTS idx_usage_log_reader;
|
|
13
|
+
CREATE INDEX IF NOT EXISTS idx_usage_log_requestor
|
|
14
|
+
ON cerefox_usage_log (requestor) WHERE requestor IS NOT NULL;
|
|
15
|
+
|
|
16
|
+
-- 3. Drop and recreate cerefox_log_usage with renamed parameter
|
|
17
|
+
DROP FUNCTION IF EXISTS cerefox_log_usage(TEXT, TEXT, TEXT, UUID, UUID, TEXT, INT, JSONB);
|
|
18
|
+
|
|
19
|
+
CREATE OR REPLACE FUNCTION cerefox_log_usage(
|
|
20
|
+
p_operation TEXT,
|
|
21
|
+
p_access_path TEXT,
|
|
22
|
+
p_requestor TEXT DEFAULT NULL,
|
|
23
|
+
p_document_id UUID DEFAULT NULL,
|
|
24
|
+
p_project_id UUID DEFAULT NULL,
|
|
25
|
+
p_query_text TEXT DEFAULT NULL,
|
|
26
|
+
p_result_count INT DEFAULT NULL,
|
|
27
|
+
p_extra JSONB DEFAULT '{}'::JSONB
|
|
28
|
+
)
|
|
29
|
+
RETURNS VOID
|
|
30
|
+
LANGUAGE plpgsql
|
|
31
|
+
SECURITY DEFINER
|
|
32
|
+
SET search_path = public, pg_catalog
|
|
33
|
+
AS $$
|
|
34
|
+
DECLARE
|
|
35
|
+
v_enabled TEXT;
|
|
36
|
+
BEGIN
|
|
37
|
+
SELECT value INTO v_enabled FROM cerefox_config WHERE key = 'usage_tracking_enabled';
|
|
38
|
+
IF v_enabled IS NULL OR v_enabled != 'true' THEN
|
|
39
|
+
RETURN;
|
|
40
|
+
END IF;
|
|
41
|
+
|
|
42
|
+
INSERT INTO cerefox_usage_log (
|
|
43
|
+
operation, access_path, requestor, document_id, project_id,
|
|
44
|
+
query_text, result_count, extra
|
|
45
|
+
) VALUES (
|
|
46
|
+
p_operation, p_access_path, p_requestor, p_document_id, p_project_id,
|
|
47
|
+
p_query_text, p_result_count, p_extra
|
|
48
|
+
);
|
|
49
|
+
END;
|
|
50
|
+
$$;
|
|
51
|
+
|
|
52
|
+
-- 4. Drop and recreate cerefox_list_usage_log with renamed column
|
|
53
|
+
DROP FUNCTION IF EXISTS cerefox_list_usage_log(TIMESTAMPTZ, TIMESTAMPTZ, TEXT, TEXT, TEXT, UUID, INT);
|
|
54
|
+
|
|
55
|
+
CREATE OR REPLACE FUNCTION cerefox_list_usage_log(
|
|
56
|
+
p_start TIMESTAMPTZ DEFAULT NULL,
|
|
57
|
+
p_end TIMESTAMPTZ DEFAULT NULL,
|
|
58
|
+
p_operation TEXT DEFAULT NULL,
|
|
59
|
+
p_access_path TEXT DEFAULT NULL,
|
|
60
|
+
p_requestor TEXT DEFAULT NULL,
|
|
61
|
+
p_project_id UUID DEFAULT NULL,
|
|
62
|
+
p_limit INT DEFAULT 100
|
|
63
|
+
)
|
|
64
|
+
RETURNS TABLE (
|
|
65
|
+
id UUID,
|
|
66
|
+
logged_at TIMESTAMPTZ,
|
|
67
|
+
operation TEXT,
|
|
68
|
+
access_path TEXT,
|
|
69
|
+
requestor TEXT,
|
|
70
|
+
document_id UUID,
|
|
71
|
+
doc_title TEXT,
|
|
72
|
+
project_id UUID,
|
|
73
|
+
query_text TEXT,
|
|
74
|
+
result_count INT,
|
|
75
|
+
extra JSONB
|
|
76
|
+
)
|
|
77
|
+
LANGUAGE sql
|
|
78
|
+
SECURITY DEFINER
|
|
79
|
+
STABLE
|
|
80
|
+
SET search_path = public, pg_catalog
|
|
81
|
+
AS $$
|
|
82
|
+
SELECT
|
|
83
|
+
ul.id,
|
|
84
|
+
ul.logged_at,
|
|
85
|
+
ul.operation,
|
|
86
|
+
ul.access_path,
|
|
87
|
+
ul.requestor,
|
|
88
|
+
ul.document_id,
|
|
89
|
+
d.title AS doc_title,
|
|
90
|
+
ul.project_id,
|
|
91
|
+
ul.query_text,
|
|
92
|
+
ul.result_count,
|
|
93
|
+
ul.extra
|
|
94
|
+
FROM cerefox_usage_log ul
|
|
95
|
+
LEFT JOIN cerefox_documents d ON ul.document_id = d.id
|
|
96
|
+
WHERE (p_start IS NULL OR ul.logged_at >= p_start)
|
|
97
|
+
AND (p_end IS NULL OR ul.logged_at <= p_end)
|
|
98
|
+
AND (p_operation IS NULL OR ul.operation = p_operation)
|
|
99
|
+
AND (p_access_path IS NULL OR ul.access_path = p_access_path)
|
|
100
|
+
AND (p_requestor IS NULL OR ul.requestor = p_requestor)
|
|
101
|
+
AND (p_project_id IS NULL OR ul.project_id = p_project_id)
|
|
102
|
+
ORDER BY ul.logged_at DESC
|
|
103
|
+
LIMIT p_limit;
|
|
104
|
+
$$;
|
|
105
|
+
|
|
106
|
+
-- 5. Drop and recreate cerefox_usage_summary with renamed field
|
|
107
|
+
DROP FUNCTION IF EXISTS cerefox_usage_summary(TIMESTAMPTZ, TIMESTAMPTZ, UUID, TEXT);
|
|
108
|
+
|
|
109
|
+
CREATE OR REPLACE FUNCTION cerefox_usage_summary(
|
|
110
|
+
p_start TIMESTAMPTZ DEFAULT NULL,
|
|
111
|
+
p_end TIMESTAMPTZ DEFAULT NULL,
|
|
112
|
+
p_project_id UUID DEFAULT NULL,
|
|
113
|
+
p_access_path TEXT DEFAULT NULL
|
|
114
|
+
)
|
|
115
|
+
RETURNS JSON
|
|
116
|
+
LANGUAGE plpgsql
|
|
117
|
+
SECURITY DEFINER
|
|
118
|
+
STABLE
|
|
119
|
+
SET search_path = public, pg_catalog
|
|
120
|
+
AS $$
|
|
121
|
+
DECLARE
|
|
122
|
+
v_result JSON;
|
|
123
|
+
BEGIN
|
|
124
|
+
WITH filtered AS (
|
|
125
|
+
SELECT *
|
|
126
|
+
FROM cerefox_usage_log ul
|
|
127
|
+
WHERE (p_start IS NULL OR ul.logged_at >= p_start)
|
|
128
|
+
AND (p_end IS NULL OR ul.logged_at <= p_end)
|
|
129
|
+
AND (p_project_id IS NULL OR ul.project_id = p_project_id)
|
|
130
|
+
AND (p_access_path IS NULL OR ul.access_path = p_access_path)
|
|
131
|
+
),
|
|
132
|
+
ops_by_day AS (
|
|
133
|
+
SELECT DATE(logged_at) AS day, COUNT(*) AS count
|
|
134
|
+
FROM filtered
|
|
135
|
+
GROUP BY DATE(logged_at)
|
|
136
|
+
ORDER BY day
|
|
137
|
+
),
|
|
138
|
+
ops_by_operation AS (
|
|
139
|
+
SELECT operation, COUNT(*) AS count
|
|
140
|
+
FROM filtered
|
|
141
|
+
GROUP BY operation
|
|
142
|
+
ORDER BY count DESC
|
|
143
|
+
),
|
|
144
|
+
ops_by_access_path AS (
|
|
145
|
+
SELECT access_path, COUNT(*) AS count
|
|
146
|
+
FROM filtered
|
|
147
|
+
GROUP BY access_path
|
|
148
|
+
ORDER BY count DESC
|
|
149
|
+
),
|
|
150
|
+
top_documents AS (
|
|
151
|
+
SELECT f.document_id, d.title AS doc_title, COUNT(*) AS count
|
|
152
|
+
FROM filtered f
|
|
153
|
+
JOIN cerefox_documents d ON f.document_id = d.id
|
|
154
|
+
WHERE f.document_id IS NOT NULL
|
|
155
|
+
GROUP BY f.document_id, d.title
|
|
156
|
+
ORDER BY count DESC
|
|
157
|
+
LIMIT 10
|
|
158
|
+
),
|
|
159
|
+
top_requestors AS (
|
|
160
|
+
SELECT requestor, COUNT(*) AS count
|
|
161
|
+
FROM filtered
|
|
162
|
+
WHERE requestor IS NOT NULL
|
|
163
|
+
GROUP BY requestor
|
|
164
|
+
ORDER BY count DESC
|
|
165
|
+
LIMIT 10
|
|
166
|
+
)
|
|
167
|
+
SELECT json_build_object(
|
|
168
|
+
'total_count', (SELECT COUNT(*) FROM filtered),
|
|
169
|
+
'ops_by_day', COALESCE((SELECT json_agg(json_build_object('day', day, 'count', count)) FROM ops_by_day), '[]'::JSON),
|
|
170
|
+
'ops_by_operation', COALESCE((SELECT json_agg(json_build_object('operation', operation, 'count', count)) FROM ops_by_operation), '[]'::JSON),
|
|
171
|
+
'ops_by_access_path', COALESCE((SELECT json_agg(json_build_object('access_path', access_path, 'count', count)) FROM ops_by_access_path), '[]'::JSON),
|
|
172
|
+
'top_documents', COALESCE((SELECT json_agg(json_build_object('document_id', document_id, 'doc_title', doc_title, 'count', count)) FROM top_documents), '[]'::JSON),
|
|
173
|
+
'top_requestors', COALESCE((SELECT json_agg(json_build_object('requestor', requestor, 'count', count)) FROM top_requestors), '[]'::JSON)
|
|
174
|
+
) INTO v_result;
|
|
175
|
+
|
|
176
|
+
RETURN v_result;
|
|
177
|
+
END;
|
|
178
|
+
$$;
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
-- Migration 0008: Soft delete for documents
|
|
2
|
+
--
|
|
3
|
+
-- Adds deleted_at column to cerefox_documents. "Delete" sets the timestamp
|
|
4
|
+
-- instead of cascade-deleting. Search indexes exclude soft-deleted docs.
|
|
5
|
+
-- A separate "purge" operation does the real cascade delete.
|
|
6
|
+
-- Recovery is just clearing the deleted_at timestamp.
|
|
7
|
+
|
|
8
|
+
-- 1. Add deleted_at column (nullable, default NULL = not deleted)
|
|
9
|
+
ALTER TABLE cerefox_documents ADD COLUMN IF NOT EXISTS deleted_at TIMESTAMPTZ DEFAULT NULL;
|
|
10
|
+
|
|
11
|
+
-- 1b. Add 'restore' to the audit log operation CHECK constraint
|
|
12
|
+
ALTER TABLE cerefox_audit_log DROP CONSTRAINT IF EXISTS cerefox_audit_log_operation_check;
|
|
13
|
+
ALTER TABLE cerefox_audit_log ADD CONSTRAINT cerefox_audit_log_operation_check CHECK (
|
|
14
|
+
operation IN ('create', 'update-content', 'update-metadata', 'delete',
|
|
15
|
+
'status-change', 'archive', 'unarchive', 'restore')
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
-- 2. Index for finding soft-deleted documents efficiently
|
|
19
|
+
CREATE INDEX IF NOT EXISTS idx_documents_deleted_at
|
|
20
|
+
ON cerefox_documents (deleted_at) WHERE deleted_at IS NOT NULL;
|
|
21
|
+
|
|
22
|
+
-- 3. Update cerefox_delete_document to soft-delete instead of cascade-delete.
|
|
23
|
+
-- The old RPC did: audit entry + CASCADE DELETE.
|
|
24
|
+
-- The new RPC does: audit entry + SET deleted_at = NOW().
|
|
25
|
+
-- Drop the old signature first (it takes different params across versions).
|
|
26
|
+
|
|
27
|
+
DROP FUNCTION IF EXISTS cerefox_delete_document(UUID);
|
|
28
|
+
|
|
29
|
+
CREATE OR REPLACE FUNCTION cerefox_delete_document(
|
|
30
|
+
p_document_id UUID
|
|
31
|
+
)
|
|
32
|
+
RETURNS VOID
|
|
33
|
+
LANGUAGE plpgsql
|
|
34
|
+
SECURITY DEFINER
|
|
35
|
+
SET search_path = public, pg_catalog
|
|
36
|
+
AS $$
|
|
37
|
+
DECLARE
|
|
38
|
+
v_title TEXT;
|
|
39
|
+
v_total_chars INT;
|
|
40
|
+
BEGIN
|
|
41
|
+
-- Get document info for audit entry
|
|
42
|
+
SELECT title, total_chars INTO v_title, v_total_chars
|
|
43
|
+
FROM cerefox_documents WHERE id = p_document_id;
|
|
44
|
+
|
|
45
|
+
IF v_title IS NULL THEN
|
|
46
|
+
RETURN; -- Document doesn't exist
|
|
47
|
+
END IF;
|
|
48
|
+
|
|
49
|
+
-- Soft delete: set deleted_at timestamp
|
|
50
|
+
UPDATE cerefox_documents SET deleted_at = NOW() WHERE id = p_document_id;
|
|
51
|
+
|
|
52
|
+
-- Audit entry
|
|
53
|
+
INSERT INTO cerefox_audit_log (
|
|
54
|
+
document_id, operation, author, author_type,
|
|
55
|
+
size_before, size_after, description
|
|
56
|
+
) VALUES (
|
|
57
|
+
p_document_id, 'delete', 'unknown', 'user',
|
|
58
|
+
v_total_chars, 0,
|
|
59
|
+
format('Soft-deleted document: %s (%s chars)', v_title, v_total_chars)
|
|
60
|
+
);
|
|
61
|
+
END;
|
|
62
|
+
$$;
|
|
63
|
+
|
|
64
|
+
-- 4. New RPC: restore a soft-deleted document
|
|
65
|
+
CREATE OR REPLACE FUNCTION cerefox_restore_document(
|
|
66
|
+
p_document_id UUID
|
|
67
|
+
)
|
|
68
|
+
RETURNS VOID
|
|
69
|
+
LANGUAGE plpgsql
|
|
70
|
+
SECURITY DEFINER
|
|
71
|
+
SET search_path = public, pg_catalog
|
|
72
|
+
AS $$
|
|
73
|
+
DECLARE
|
|
74
|
+
v_title TEXT;
|
|
75
|
+
v_total_chars INT;
|
|
76
|
+
BEGIN
|
|
77
|
+
SELECT title, total_chars INTO v_title, v_total_chars
|
|
78
|
+
FROM cerefox_documents WHERE id = p_document_id AND deleted_at IS NOT NULL;
|
|
79
|
+
|
|
80
|
+
IF v_title IS NULL THEN
|
|
81
|
+
RETURN; -- Not found or not deleted
|
|
82
|
+
END IF;
|
|
83
|
+
|
|
84
|
+
UPDATE cerefox_documents SET deleted_at = NULL WHERE id = p_document_id;
|
|
85
|
+
|
|
86
|
+
INSERT INTO cerefox_audit_log (
|
|
87
|
+
document_id, operation, author, author_type,
|
|
88
|
+
size_before, size_after, description
|
|
89
|
+
) VALUES (
|
|
90
|
+
p_document_id, 'restore', 'unknown', 'user',
|
|
91
|
+
0, v_total_chars,
|
|
92
|
+
format('Restored document: %s', v_title)
|
|
93
|
+
);
|
|
94
|
+
END;
|
|
95
|
+
$$;
|
|
96
|
+
|
|
97
|
+
-- 5. New RPC: permanently purge a soft-deleted document
|
|
98
|
+
CREATE OR REPLACE FUNCTION cerefox_purge_document(
|
|
99
|
+
p_document_id UUID
|
|
100
|
+
)
|
|
101
|
+
RETURNS VOID
|
|
102
|
+
LANGUAGE plpgsql
|
|
103
|
+
SECURITY DEFINER
|
|
104
|
+
SET search_path = public, pg_catalog
|
|
105
|
+
AS $$
|
|
106
|
+
DECLARE
|
|
107
|
+
v_title TEXT;
|
|
108
|
+
v_total_chars INT;
|
|
109
|
+
BEGIN
|
|
110
|
+
SELECT title, total_chars INTO v_title, v_total_chars
|
|
111
|
+
FROM cerefox_documents WHERE id = p_document_id AND deleted_at IS NOT NULL;
|
|
112
|
+
|
|
113
|
+
IF v_title IS NULL THEN
|
|
114
|
+
RETURN; -- Not found or not soft-deleted (can only purge soft-deleted docs)
|
|
115
|
+
END IF;
|
|
116
|
+
|
|
117
|
+
-- Audit entry BEFORE delete (FK will SET NULL on the audit entry's document_id)
|
|
118
|
+
INSERT INTO cerefox_audit_log (
|
|
119
|
+
document_id, operation, author, author_type,
|
|
120
|
+
size_before, size_after, description
|
|
121
|
+
) VALUES (
|
|
122
|
+
p_document_id, 'delete', 'unknown', 'user',
|
|
123
|
+
v_total_chars, 0,
|
|
124
|
+
format('Permanently deleted document: %s (%s chars)', v_title, v_total_chars)
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
-- Real cascade delete
|
|
128
|
+
DELETE FROM cerefox_documents WHERE id = p_document_id;
|
|
129
|
+
END;
|
|
130
|
+
$$;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
-- Migration 0009: Add 'restore' to audit log operation CHECK constraint
|
|
2
|
+
--
|
|
3
|
+
-- The soft delete feature (0008) used 'unarchive' for restore-from-trash
|
|
4
|
+
-- operations. This was semantically incorrect -- 'unarchive' is for version
|
|
5
|
+
-- archival toggling. This migration adds 'restore' as a proper operation type.
|
|
6
|
+
--
|
|
7
|
+
-- Safe to re-run: DROP CONSTRAINT IF EXISTS handles idempotency.
|
|
8
|
+
|
|
9
|
+
ALTER TABLE cerefox_audit_log DROP CONSTRAINT IF EXISTS cerefox_audit_log_operation_check;
|
|
10
|
+
ALTER TABLE cerefox_audit_log ADD CONSTRAINT cerefox_audit_log_operation_check CHECK (
|
|
11
|
+
operation IN ('create', 'update-content', 'update-metadata', 'delete',
|
|
12
|
+
'status-change', 'archive', 'unarchive', 'restore')
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
-- Also fix any existing audit entries that used 'unarchive' for restore-from-trash
|
|
16
|
+
-- (they have description starting with 'Restored document:')
|
|
17
|
+
UPDATE cerefox_audit_log
|
|
18
|
+
SET operation = 'restore'
|
|
19
|
+
WHERE operation = 'unarchive'
|
|
20
|
+
AND description LIKE 'Restored document:%';
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
-- Migration 0010: Seed requestor enforcement config defaults
|
|
2
|
+
--
|
|
3
|
+
-- Adds two new config keys for optional requestor identity enforcement
|
|
4
|
+
-- on MCP tool calls. Both default to "off" for backward compatibility.
|
|
5
|
+
|
|
6
|
+
INSERT INTO cerefox_config (key, value)
|
|
7
|
+
VALUES ('require_requestor_identity', 'false')
|
|
8
|
+
ON CONFLICT (key) DO NOTHING;
|
|
9
|
+
|
|
10
|
+
INSERT INTO cerefox_config (key, value)
|
|
11
|
+
VALUES ('requestor_identity_format', '^[a-zA-Z0-9_:.\- ]+$')
|
|
12
|
+
ON CONFLICT (key) DO NOTHING;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
-- Migration 0011: Title Boosting for FTS and Semantic Search
|
|
2
|
+
--
|
|
3
|
+
-- Changes the fts column on cerefox_chunks from GENERATED ALWAYS AS to a
|
|
4
|
+
-- regular tsvector column. This allows the document title (from
|
|
5
|
+
-- cerefox_documents) to be included at weight A in the FTS index, which
|
|
6
|
+
-- is impossible with a GENERATED column (cannot reference other tables).
|
|
7
|
+
--
|
|
8
|
+
-- After this migration, fts is set by the ingestion pipeline:
|
|
9
|
+
-- setweight(to_tsvector('english', doc_title), 'A') -- document title
|
|
10
|
+
-- || setweight(to_tsvector('english', chunk_title), 'A') -- heading title
|
|
11
|
+
-- || setweight(to_tsvector('english', content), 'B') -- body content
|
|
12
|
+
--
|
|
13
|
+
-- The cerefox_ingest_document RPC computes this automatically for all new
|
|
14
|
+
-- or updated documents. The cerefox_update_chunk_fts RPC handles the
|
|
15
|
+
-- title-change path (title changed but content unchanged).
|
|
16
|
+
--
|
|
17
|
+
-- Existing chunks retain their old tsvectors (computed without doc title).
|
|
18
|
+
-- New ingestion automatically uses the new formula. Running
|
|
19
|
+
-- scripts/reindex_all.py updates all existing chunks (optional but
|
|
20
|
+
-- recommended for complete title-boosted search coverage).
|
|
21
|
+
--
|
|
22
|
+
-- Applied by: uv run python scripts/db_migrate.py
|
|
23
|
+
|
|
24
|
+
-- Step 1: Drop the GENERATED expression on fts.
|
|
25
|
+
-- The column stays as a regular tsvector; existing values are preserved.
|
|
26
|
+
ALTER TABLE cerefox_chunks ALTER COLUMN fts DROP EXPRESSION;
|
|
27
|
+
|
|
28
|
+
-- Step 2: Add cerefox_update_chunk_fts RPC.
|
|
29
|
+
-- Called when a document title changes (content unchanged) to refresh the
|
|
30
|
+
-- FTS index for all current chunks without creating a version snapshot.
|
|
31
|
+
DROP FUNCTION IF EXISTS cerefox_update_chunk_fts(UUID, TEXT);
|
|
32
|
+
CREATE FUNCTION cerefox_update_chunk_fts(
|
|
33
|
+
p_document_id UUID,
|
|
34
|
+
p_new_title TEXT
|
|
35
|
+
)
|
|
36
|
+
RETURNS VOID
|
|
37
|
+
LANGUAGE sql
|
|
38
|
+
SECURITY DEFINER
|
|
39
|
+
SET search_path = public, pg_catalog
|
|
40
|
+
AS $$
|
|
41
|
+
UPDATE cerefox_chunks
|
|
42
|
+
SET fts =
|
|
43
|
+
setweight(to_tsvector('english', COALESCE(p_new_title, '')), 'A') ||
|
|
44
|
+
setweight(to_tsvector('english', COALESCE(title, '')), 'A') ||
|
|
45
|
+
setweight(to_tsvector('english', COALESCE(content, '')), 'B')
|
|
46
|
+
WHERE document_id = p_document_id
|
|
47
|
+
AND version_id IS NULL;
|
|
48
|
+
$$;
|