@cerefox/memory 1.7.1 → 1.9.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 +13 -0
- package/AGENT_QUICK_REFERENCE.md +1 -1
- package/README.md +4 -0
- package/dist/bin/cerefox.js +325 -99
- package/dist/frontend/assets/index-DWT7wZMR.js +121 -0
- package/dist/frontend/assets/index-DWT7wZMR.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/_projects.ts +76 -51
- package/dist/server-assets/_shared/mcp-tools/_utils.ts +45 -0
- package/dist/server-assets/_shared/mcp-tools/audit-log.ts +3 -5
- package/dist/server-assets/_shared/mcp-tools/audit-ops.ts +46 -0
- package/dist/server-assets/_shared/mcp-tools/get-help-content.ts +1 -1
- package/dist/server-assets/_shared/mcp-tools/ingest.ts +6 -6
- package/dist/server-assets/db/migrations/0025_drop_orphaned_overloads.sql +2 -1
- package/dist/server-assets/db/migrations/0027_drop_archived_search_artifacts.sql +172 -0
- package/dist/server-assets/db/migrations/0028_audit_store_level_writes.sql +349 -0
- package/dist/server-assets/db/rpcs.sql +270 -64
- package/dist/server-assets/db/schema.sql +18 -4
- package/dist/server-assets/supabase/functions/cerefox-ingest/index.ts +14 -97
- package/docs/guides/cli.md +13 -2
- package/docs/guides/connect-agents.md +4 -19
- package/docs/guides/ops-scripts.md +4 -4
- package/docs/guides/upgrading.md +14 -1
- package/package.json +2 -2
- package/dist/frontend/assets/index-D8E0mTnp.js +0 -121
- package/dist/frontend/assets/index-D8E0mTnp.js.map +0 -1
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
-- Migration 0028 — store-level writes join the audit trail (schema 0.14.0)
|
|
2
|
+
--
|
|
3
|
+
-- WHY (canonical rationale — other docs reference this header):
|
|
4
|
+
--
|
|
5
|
+
-- The audit log covered every *document* write but nothing store-level, so the
|
|
6
|
+
-- two questions that motivated this release were unanswerable from the trail:
|
|
7
|
+
-- "who changed this config value, and when?" (cerefox_config has no
|
|
8
|
+
-- timestamps) and "when was this project created/renamed/removed?" (#147).
|
|
9
|
+
-- Both are governance decisions of exactly the kind an append-only trail
|
|
10
|
+
-- exists to answer.
|
|
11
|
+
--
|
|
12
|
+
-- This migration:
|
|
13
|
+
-- 1. Extends the cerefox_audit_log.operation allow-list with the four
|
|
14
|
+
-- store-level operations: 'config-change', 'project-create',
|
|
15
|
+
-- 'project-edit', 'project-delete'. These entries carry document_id NULL
|
|
16
|
+
-- — the same shape as rows a purge cascade orphans, so every existing
|
|
17
|
+
-- reader (CLI, web, EF) already tolerates them.
|
|
18
|
+
-- 2. Replaces cerefox_set_config with the 4-arg auditing version and DROPs
|
|
19
|
+
-- the old 2-arg signature (CREATE OR REPLACE never removes a grown-out
|
|
20
|
+
-- overload; a survivor makes named PostgREST calls ambiguous — PGRST203,
|
|
21
|
+
-- the v1.7.0 purge/restore lesson). The full new body ships INSIDE this
|
|
22
|
+
-- migration so neither `db_migrate.ts` alone nor a deploy that fails
|
|
23
|
+
-- between the migration step and the RPC refresh can leave the old,
|
|
24
|
+
-- non-auditing function behind while 0028 is stamped applied (same
|
|
25
|
+
-- repair-path closure as 0027).
|
|
26
|
+
-- 3. Drops the dead V1 RPC cerefox_save_note: a Python-era tool with zero
|
|
27
|
+
-- callers since the TS rewrite — no CLI command, MCP tool, Edge
|
|
28
|
+
-- Function, web route, or SQL function references it. (Its sibling
|
|
29
|
+
-- cerefox_context_expand looked identical but is load-bearing:
|
|
30
|
+
-- cerefox_search_docs calls it for small-to-big retrieval, so it stays.)
|
|
31
|
+
--
|
|
32
|
+
-- Project create/edit/delete write through three new RPCs
|
|
33
|
+
-- (cerefox_create_project / cerefox_update_project / cerefox_delete_project)
|
|
34
|
+
-- that audit IN-TRANSACTION, exactly like cerefox_set_config — the
|
|
35
|
+
-- single-implementation principle applies the moment a write carries a side
|
|
36
|
+
-- effect (#219; an earlier draft audited client-side at every call site and
|
|
37
|
+
-- review caught two forgotten paths before it ever shipped). This migration
|
|
38
|
+
-- carries their bodies too (repair-path closure), plus their ACL lockdown.
|
|
39
|
+
--
|
|
40
|
+
-- Re-runnable: every statement is guarded or idempotent.
|
|
41
|
+
|
|
42
|
+
-- 1. Operation allow-list. Constraint swap is atomic within the migration's
|
|
43
|
+
-- transaction; the list must stay in lockstep with schema.sql.
|
|
44
|
+
ALTER TABLE cerefox_audit_log
|
|
45
|
+
DROP CONSTRAINT IF EXISTS cerefox_audit_log_operation_check;
|
|
46
|
+
ALTER TABLE cerefox_audit_log
|
|
47
|
+
ADD CONSTRAINT cerefox_audit_log_operation_check CHECK (
|
|
48
|
+
operation IN ('create', 'update-content', 'update-metadata', 'delete',
|
|
49
|
+
'status-change', 'archive', 'unarchive', 'restore',
|
|
50
|
+
'relation-set', 'relation-delete',
|
|
51
|
+
'insert', 'replace-section', 'delete-section',
|
|
52
|
+
'rename-section',
|
|
53
|
+
'config-change', 'project-create', 'project-edit',
|
|
54
|
+
'project-delete')
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
-- 2. Auditing cerefox_set_config (and the old signature's removal).
|
|
58
|
+
DROP FUNCTION IF EXISTS cerefox_set_config(TEXT, TEXT);
|
|
59
|
+
|
|
60
|
+
CREATE OR REPLACE FUNCTION cerefox_set_config(
|
|
61
|
+
p_key TEXT,
|
|
62
|
+
p_value TEXT,
|
|
63
|
+
p_author TEXT DEFAULT 'unknown',
|
|
64
|
+
p_author_type TEXT DEFAULT 'user'
|
|
65
|
+
)
|
|
66
|
+
RETURNS VOID
|
|
67
|
+
LANGUAGE plpgsql
|
|
68
|
+
SECURITY DEFINER
|
|
69
|
+
SET search_path = public, pg_catalog
|
|
70
|
+
AS $$
|
|
71
|
+
DECLARE
|
|
72
|
+
v_allowed TEXT[] := ARRAY[
|
|
73
|
+
'usage_tracking_enabled', 'require_requestor_identity', 'requestor_identity_format',
|
|
74
|
+
'min_search_score', 'min_term_coverage', 'search_alpha',
|
|
75
|
+
'version_retention_hours', 'version_cleanup_enabled',
|
|
76
|
+
'relations_enabled',
|
|
77
|
+
'document_size_warning_chars'
|
|
78
|
+
];
|
|
79
|
+
v_old TEXT;
|
|
80
|
+
BEGIN
|
|
81
|
+
IF NOT (p_key = ANY(v_allowed)) THEN
|
|
82
|
+
RAISE EXCEPTION 'Unknown config key: %. Allowed keys: %', p_key, v_allowed;
|
|
83
|
+
END IF;
|
|
84
|
+
|
|
85
|
+
SELECT value INTO v_old FROM cerefox_config WHERE key = p_key;
|
|
86
|
+
|
|
87
|
+
INSERT INTO cerefox_config (key, value)
|
|
88
|
+
VALUES (p_key, p_value)
|
|
89
|
+
ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value;
|
|
90
|
+
|
|
91
|
+
PERFORM cerefox_create_audit_entry(
|
|
92
|
+
p_operation := 'config-change',
|
|
93
|
+
p_author := p_author,
|
|
94
|
+
p_author_type := p_author_type,
|
|
95
|
+
p_description := 'config: ' || p_key || ': '
|
|
96
|
+
|| COALESCE('''' || v_old || '''', '(unset)')
|
|
97
|
+
|| ' → ''' || p_value || ''''
|
|
98
|
+
);
|
|
99
|
+
END;
|
|
100
|
+
$$;
|
|
101
|
+
|
|
102
|
+
-- 2b. Lock the new signature down. A freshly CREATEd function gets Postgres'
|
|
103
|
+
-- default EXECUTE-to-PUBLIC, and the repo's blanket REVOKE/GRANT loop
|
|
104
|
+
-- lives at the bottom of rpcs.sql — which `db_migrate.ts` alone never
|
|
105
|
+
-- applies, and which a deploy failing between the migration step and the
|
|
106
|
+
-- RPC refresh also misses. Without this, the anon/publishable key could
|
|
107
|
+
-- call the one function that WRITES store governance. Same guarded shape
|
|
108
|
+
-- as the rpcs.sql block (safe on non-Supabase Postgres).
|
|
109
|
+
DO $$
|
|
110
|
+
DECLARE
|
|
111
|
+
r TEXT;
|
|
112
|
+
BEGIN
|
|
113
|
+
REVOKE EXECUTE ON FUNCTION cerefox_set_config(TEXT, TEXT, TEXT, TEXT) FROM PUBLIC;
|
|
114
|
+
FOREACH r IN ARRAY ARRAY['anon', 'authenticated'] LOOP
|
|
115
|
+
IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = r) THEN
|
|
116
|
+
EXECUTE format('REVOKE EXECUTE ON FUNCTION cerefox_set_config(TEXT, TEXT, TEXT, TEXT) FROM %I', r);
|
|
117
|
+
END IF;
|
|
118
|
+
END LOOP;
|
|
119
|
+
IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'service_role') THEN
|
|
120
|
+
GRANT EXECUTE ON FUNCTION cerefox_set_config(TEXT, TEXT, TEXT, TEXT) TO service_role;
|
|
121
|
+
END IF;
|
|
122
|
+
END $$;
|
|
123
|
+
|
|
124
|
+
-- 2c. Project write RPCs (bodies identical to rpcs.sql — extracted, not
|
|
125
|
+
-- retyped; an invariant test compares them byte-for-byte) and their
|
|
126
|
+
-- lockdown, same rationale as 2b.
|
|
127
|
+
DROP FUNCTION IF EXISTS cerefox_create_project(TEXT, TEXT, TEXT, TEXT, TEXT);
|
|
128
|
+
|
|
129
|
+
CREATE FUNCTION cerefox_create_project(
|
|
130
|
+
p_name TEXT,
|
|
131
|
+
p_description TEXT DEFAULT '',
|
|
132
|
+
p_author TEXT DEFAULT 'unknown',
|
|
133
|
+
p_author_type TEXT DEFAULT 'user',
|
|
134
|
+
-- 'error' → explicit creation (CLI/web): duplicate name raises.
|
|
135
|
+
-- 'return' → get-or-create (implicit creation during document
|
|
136
|
+
-- assignment): an existing project is returned untouched and
|
|
137
|
+
-- NOT audited — only an actual create writes an entry.
|
|
138
|
+
p_if_exists TEXT DEFAULT 'error'
|
|
139
|
+
)
|
|
140
|
+
RETURNS TABLE (
|
|
141
|
+
project_id UUID,
|
|
142
|
+
project_name TEXT,
|
|
143
|
+
project_description TEXT,
|
|
144
|
+
created BOOLEAN,
|
|
145
|
+
created_at TIMESTAMPTZ,
|
|
146
|
+
updated_at TIMESTAMPTZ
|
|
147
|
+
)
|
|
148
|
+
LANGUAGE plpgsql
|
|
149
|
+
SECURITY DEFINER
|
|
150
|
+
SET search_path = public, pg_catalog
|
|
151
|
+
AS $$
|
|
152
|
+
DECLARE
|
|
153
|
+
v_row cerefox_projects%ROWTYPE;
|
|
154
|
+
BEGIN
|
|
155
|
+
IF NULLIF(BTRIM(p_name), '') IS NULL THEN
|
|
156
|
+
RAISE EXCEPTION 'Project name is required' USING ERRCODE = '22023';
|
|
157
|
+
END IF;
|
|
158
|
+
IF p_if_exists NOT IN ('error', 'return') THEN
|
|
159
|
+
RAISE EXCEPTION 'p_if_exists must be ''error'' or ''return''' USING ERRCODE = '22023';
|
|
160
|
+
END IF;
|
|
161
|
+
|
|
162
|
+
IF p_if_exists = 'return' THEN
|
|
163
|
+
-- Case-insensitive, matching the name resolution the assignment
|
|
164
|
+
-- paths have always used. (A case-colliding pair created directly is
|
|
165
|
+
-- pre-existing behavior: the unique constraint is exact-match; this
|
|
166
|
+
-- resolver returns the first match.)
|
|
167
|
+
SELECT p.* INTO v_row
|
|
168
|
+
FROM cerefox_projects p WHERE lower(p.name) = lower(BTRIM(p_name)) LIMIT 1;
|
|
169
|
+
IF v_row.id IS NOT NULL THEN
|
|
170
|
+
RETURN QUERY SELECT v_row.id, v_row.name, v_row.description, FALSE,
|
|
171
|
+
v_row.created_at, v_row.updated_at;
|
|
172
|
+
RETURN;
|
|
173
|
+
END IF;
|
|
174
|
+
END IF;
|
|
175
|
+
|
|
176
|
+
BEGIN
|
|
177
|
+
INSERT INTO cerefox_projects (name, description)
|
|
178
|
+
VALUES (BTRIM(p_name), COALESCE(p_description, ''))
|
|
179
|
+
RETURNING * INTO v_row;
|
|
180
|
+
EXCEPTION WHEN unique_violation THEN
|
|
181
|
+
-- TOCTOU (round 4): a concurrent create won the race between the
|
|
182
|
+
-- resolve above and this insert. In 'return' mode that is exactly
|
|
183
|
+
-- the get-or-create contract — hand back the winner, no audit entry
|
|
184
|
+
-- (this call created nothing). In 'error' mode a duplicate is the
|
|
185
|
+
-- caller's error, exactly as if there had been no race.
|
|
186
|
+
IF p_if_exists = 'return' THEN
|
|
187
|
+
SELECT p.* INTO v_row
|
|
188
|
+
FROM cerefox_projects p WHERE lower(p.name) = lower(BTRIM(p_name)) LIMIT 1;
|
|
189
|
+
IF v_row.id IS NOT NULL THEN
|
|
190
|
+
RETURN QUERY SELECT v_row.id, v_row.name, v_row.description, FALSE,
|
|
191
|
+
v_row.created_at, v_row.updated_at;
|
|
192
|
+
RETURN;
|
|
193
|
+
END IF;
|
|
194
|
+
END IF;
|
|
195
|
+
RAISE;
|
|
196
|
+
END;
|
|
197
|
+
|
|
198
|
+
PERFORM cerefox_create_audit_entry(
|
|
199
|
+
p_operation := 'project-create',
|
|
200
|
+
p_author := p_author,
|
|
201
|
+
p_author_type := p_author_type,
|
|
202
|
+
p_description := 'Project ''' || v_row.name || ''' created'
|
|
203
|
+
|| CASE WHEN p_if_exists = 'return'
|
|
204
|
+
THEN ' implicitly (document assignment)' ELSE '' END
|
|
205
|
+
);
|
|
206
|
+
RETURN QUERY SELECT v_row.id, v_row.name, v_row.description, TRUE,
|
|
207
|
+
v_row.created_at, v_row.updated_at;
|
|
208
|
+
END;
|
|
209
|
+
$$;
|
|
210
|
+
|
|
211
|
+
DROP FUNCTION IF EXISTS cerefox_update_project(UUID, TEXT, TEXT, TEXT, TEXT);
|
|
212
|
+
|
|
213
|
+
CREATE FUNCTION cerefox_update_project(
|
|
214
|
+
p_project_id UUID,
|
|
215
|
+
-- NULL = keep the current value (the #191/#204 "NULL means not provided"
|
|
216
|
+
-- convention). An explicit empty name is rejected.
|
|
217
|
+
p_name TEXT DEFAULT NULL,
|
|
218
|
+
p_description TEXT DEFAULT NULL,
|
|
219
|
+
p_author TEXT DEFAULT 'unknown',
|
|
220
|
+
p_author_type TEXT DEFAULT 'user'
|
|
221
|
+
)
|
|
222
|
+
RETURNS TABLE (
|
|
223
|
+
project_id UUID,
|
|
224
|
+
project_name TEXT,
|
|
225
|
+
project_description TEXT,
|
|
226
|
+
created_at TIMESTAMPTZ,
|
|
227
|
+
updated_at TIMESTAMPTZ
|
|
228
|
+
)
|
|
229
|
+
LANGUAGE plpgsql
|
|
230
|
+
SECURITY DEFINER
|
|
231
|
+
SET search_path = public, pg_catalog
|
|
232
|
+
AS $$
|
|
233
|
+
DECLARE
|
|
234
|
+
v_old cerefox_projects%ROWTYPE;
|
|
235
|
+
v_new cerefox_projects%ROWTYPE;
|
|
236
|
+
v_changes TEXT[] := '{}';
|
|
237
|
+
BEGIN
|
|
238
|
+
IF p_name IS NULL AND p_description IS NULL THEN
|
|
239
|
+
RAISE EXCEPTION 'Nothing to update: pass p_name and/or p_description' USING ERRCODE = '22023';
|
|
240
|
+
END IF;
|
|
241
|
+
IF p_name IS NOT NULL AND NULLIF(BTRIM(p_name), '') IS NULL THEN
|
|
242
|
+
RAISE EXCEPTION 'Project name cannot be empty' USING ERRCODE = '22023';
|
|
243
|
+
END IF;
|
|
244
|
+
|
|
245
|
+
SELECT p.* INTO v_old FROM cerefox_projects p WHERE p.id = p_project_id FOR UPDATE;
|
|
246
|
+
IF NOT FOUND THEN
|
|
247
|
+
RAISE EXCEPTION 'Project not found: %', p_project_id USING ERRCODE = '22023';
|
|
248
|
+
END IF;
|
|
249
|
+
|
|
250
|
+
UPDATE cerefox_projects SET
|
|
251
|
+
name = COALESCE(BTRIM(p_name), name),
|
|
252
|
+
description = COALESCE(p_description, description),
|
|
253
|
+
updated_at = NOW()
|
|
254
|
+
WHERE id = p_project_id
|
|
255
|
+
RETURNING * INTO v_new;
|
|
256
|
+
|
|
257
|
+
-- The trail records what actually changed, not which arguments arrived.
|
|
258
|
+
IF v_new.name <> v_old.name THEN
|
|
259
|
+
v_changes := v_changes || ('renamed ''' || v_old.name || ''' → ''' || v_new.name || '''');
|
|
260
|
+
END IF;
|
|
261
|
+
IF COALESCE(v_new.description, '') <> COALESCE(v_old.description, '') THEN
|
|
262
|
+
v_changes := v_changes || 'description changed';
|
|
263
|
+
END IF;
|
|
264
|
+
|
|
265
|
+
PERFORM cerefox_create_audit_entry(
|
|
266
|
+
p_operation := 'project-edit',
|
|
267
|
+
p_author := p_author,
|
|
268
|
+
p_author_type := p_author_type,
|
|
269
|
+
p_description := 'Project ''' || v_new.name || ''' edited ('
|
|
270
|
+
|| COALESCE(NULLIF(array_to_string(v_changes, '; '), ''), 'no-op') || ')'
|
|
271
|
+
);
|
|
272
|
+
RETURN QUERY SELECT v_new.id, v_new.name, v_new.description,
|
|
273
|
+
v_new.created_at, v_new.updated_at;
|
|
274
|
+
END;
|
|
275
|
+
$$;
|
|
276
|
+
|
|
277
|
+
CREATE OR REPLACE FUNCTION cerefox_delete_project(
|
|
278
|
+
p_project_id UUID,
|
|
279
|
+
p_author TEXT DEFAULT 'unknown',
|
|
280
|
+
p_author_type TEXT DEFAULT 'user'
|
|
281
|
+
)
|
|
282
|
+
RETURNS TABLE (deleted BOOLEAN, project_name TEXT)
|
|
283
|
+
LANGUAGE plpgsql
|
|
284
|
+
SECURITY DEFINER
|
|
285
|
+
SET search_path = public, pg_catalog
|
|
286
|
+
AS $$
|
|
287
|
+
DECLARE
|
|
288
|
+
v_name TEXT;
|
|
289
|
+
v_links INT;
|
|
290
|
+
BEGIN
|
|
291
|
+
-- Lock + read name and link count in one pass: the memberships CASCADE
|
|
292
|
+
-- with the row, so the count must be read pre-DELETE — but the zero-row
|
|
293
|
+
-- path (repeat delete) pays for nothing (round 4).
|
|
294
|
+
SELECT p.name, (SELECT COUNT(*) FROM cerefox_document_projects dp
|
|
295
|
+
WHERE dp.project_id = p.id)
|
|
296
|
+
INTO v_name, v_links
|
|
297
|
+
FROM cerefox_projects p WHERE p.id = p_project_id FOR UPDATE;
|
|
298
|
+
|
|
299
|
+
IF v_name IS NULL THEN
|
|
300
|
+
-- Zero rows: nothing happened, so nothing is audited — the trail
|
|
301
|
+
-- must never assert an event that did not occur. Callers decide
|
|
302
|
+
-- whether "already gone" is an error (CLI) or a 404 (web).
|
|
303
|
+
RETURN QUERY SELECT FALSE, NULL::TEXT;
|
|
304
|
+
RETURN;
|
|
305
|
+
END IF;
|
|
306
|
+
|
|
307
|
+
DELETE FROM cerefox_projects WHERE id = p_project_id;
|
|
308
|
+
|
|
309
|
+
PERFORM cerefox_create_audit_entry(
|
|
310
|
+
p_operation := 'project-delete',
|
|
311
|
+
p_author := p_author,
|
|
312
|
+
p_author_type := p_author_type,
|
|
313
|
+
p_description := 'Project ''' || v_name || ''' deleted ('
|
|
314
|
+
|| v_links || ' document link(s) removed)'
|
|
315
|
+
);
|
|
316
|
+
RETURN QUERY SELECT TRUE, v_name;
|
|
317
|
+
END;
|
|
318
|
+
$$;
|
|
319
|
+
|
|
320
|
+
DO $$
|
|
321
|
+
DECLARE
|
|
322
|
+
fn TEXT;
|
|
323
|
+
r TEXT;
|
|
324
|
+
BEGIN
|
|
325
|
+
FOREACH fn IN ARRAY ARRAY[
|
|
326
|
+
'cerefox_create_project(TEXT, TEXT, TEXT, TEXT, TEXT)',
|
|
327
|
+
'cerefox_update_project(UUID, TEXT, TEXT, TEXT, TEXT)',
|
|
328
|
+
'cerefox_delete_project(UUID, TEXT, TEXT)'
|
|
329
|
+
] LOOP
|
|
330
|
+
EXECUTE format('REVOKE EXECUTE ON FUNCTION %s FROM PUBLIC', fn);
|
|
331
|
+
FOREACH r IN ARRAY ARRAY['anon', 'authenticated'] LOOP
|
|
332
|
+
IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = r) THEN
|
|
333
|
+
EXECUTE format('REVOKE EXECUTE ON FUNCTION %s FROM %I', fn, r);
|
|
334
|
+
END IF;
|
|
335
|
+
END LOOP;
|
|
336
|
+
IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'service_role') THEN
|
|
337
|
+
EXECUTE format('GRANT EXECUTE ON FUNCTION %s TO service_role', fn);
|
|
338
|
+
END IF;
|
|
339
|
+
END LOOP;
|
|
340
|
+
END $$;
|
|
341
|
+
|
|
342
|
+
-- 3. Dead V1 RPC.
|
|
343
|
+
DROP FUNCTION IF EXISTS cerefox_save_note(TEXT, TEXT, TEXT, UUID, JSONB);
|
|
344
|
+
|
|
345
|
+
DO $$
|
|
346
|
+
BEGIN
|
|
347
|
+
RAISE NOTICE
|
|
348
|
+
'Migration 0028: audit trail now covers store-level writes (config-change, project-create/edit/delete). Dropped the retired V1 RPC cerefox_save_note.';
|
|
349
|
+
END $$;
|