@cerefox/memory 1.1.0-beta.1 → 1.1.0-beta.2

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.
@@ -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-BMFGsK0D.js"></script>
18
+ <script type="module" crossorigin src="/app/assets/index-CMNl_LF9.js"></script>
19
19
  <link rel="stylesheet" crossorigin href="/app/assets/index-C1JXZA9m.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.1.0-beta.1";
21
+ export const EF_VERSION = "1.1.0-beta.2";
22
22
 
23
23
  /**
24
24
  * The most recent version whose EF-side SOURCE actually changed (#127).
@@ -28,7 +28,7 @@ export const EF_VERSION = "1.1.0-beta.1";
28
28
  * `cut_release.ts` ONLY when EF source changed since the last tag; doctor
29
29
  * uses it to stay silent on label-only drift.
30
30
  */
31
- export const EF_LAST_CHANGED = "1.1.0-beta.1";
31
+ export const EF_LAST_CHANGED = "1.1.0-beta.2";
32
32
 
33
33
  /**
34
34
  * The 8 peer EFs the cerefox-mcp aggregator probes (excludes cerefox-mcp
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Optional-feature gating for the MCP tool surface.
3
+ *
4
+ * Document relations (iteration 29) ship **dormant**: the table sits empty, the
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.
9
+ *
10
+ * So exposure is gated on a deployment-wide flag (`relations_enabled` in
11
+ * `cerefox_config`, default **false**), read through the same RPC as every
12
+ * other setting. Turning it on is one command:
13
+ *
14
+ * cerefox config set relations_enabled true
15
+ *
16
+ * Failure mode is deliberately closed: if the config read fails (older schema,
17
+ * transient error), the feature stays hidden rather than appearing
18
+ * intermittently.
19
+ */
20
+
21
+ import type { MCPSupabaseClient } from "./types.ts";
22
+
23
+ /** Tools gated behind `relations_enabled`. */
24
+ export const RELATION_TOOL_NAMES: ReadonlySet<string> = new Set([
25
+ "cerefox_set_relation",
26
+ "cerefox_delete_relation",
27
+ "cerefox_get_relations",
28
+ "cerefox_get_neighbors",
29
+ ]);
30
+
31
+ /**
32
+ * Cached per process. `tools/list` happens once per session, but `tools/call`
33
+ * checks too (a long-lived session could hold a stale list), and a round trip
34
+ * per call is not worth paying.
35
+ */
36
+ const CACHE_TTL_MS = 60_000;
37
+ let cached: { value: boolean; at: number } | null = null;
38
+
39
+ /** Test seam: drop the cache so a flag change is picked up immediately. */
40
+ export function resetFeatureFlagCache(): void {
41
+ cached = null;
42
+ }
43
+
44
+ export async function relationsEnabled(supabase: MCPSupabaseClient): Promise<boolean> {
45
+ if (cached && Date.now() - cached.at < CACHE_TTL_MS) return cached.value;
46
+ try {
47
+ const { data, error } = await supabase.rpc("cerefox_get_config", {
48
+ p_key: "relations_enabled",
49
+ });
50
+ if (error) throw new Error(error.message);
51
+ const value = String(data ?? "").trim().toLowerCase() === "true";
52
+ cached = { value, at: Date.now() };
53
+ return value;
54
+ } catch {
55
+ // Fail closed, and don't cache a failure — a transient error shouldn't
56
+ // hide the feature for a full TTL once it is genuinely enabled.
57
+ return false;
58
+ }
59
+ }
60
+
61
+ /** Message shown when a gated tool is called while the feature is off. */
62
+ export function disabledToolMessage(name: string): string {
63
+ return (
64
+ `${name} is part of the document-relations feature, which is off by default. ` +
65
+ `Enable it with: cerefox config set relations_enabled true ` +
66
+ `(deployment-wide; every access path picks it up).`
67
+ );
68
+ }
@@ -12,6 +12,11 @@
12
12
  */
13
13
 
14
14
  import { auditLogTool } from "./audit-log.ts";
15
+ import {
16
+ disabledToolMessage,
17
+ relationsEnabled,
18
+ RELATION_TOOL_NAMES,
19
+ } from "./feature-flags.ts";
15
20
  import {
16
21
  deleteRelationTool,
17
22
  getNeighborsTool,
@@ -27,7 +32,7 @@ import { listVersionsTool } from "./list-versions.ts";
27
32
  import { metadataSearchTool } from "./metadata-search.ts";
28
33
  import { searchTool } from "./search.ts";
29
34
  import { setDocumentProjectsTool } from "./set-document-projects.ts";
30
- import type { ToolDefinition } from "./types.ts";
35
+ import { McpInvalidParams, type MCPSupabaseClient, type ToolDefinition } from "./types.ts";
31
36
 
32
37
  /** All Cerefox MCP tools, in canonical order (matches AGENT_QUICK_REFERENCE.md). */
33
38
  export const ALL_TOOLS: ToolDefinition[] = [
@@ -48,6 +53,31 @@ export const ALL_TOOLS: ToolDefinition[] = [
48
53
  getHelpTool,
49
54
  ];
50
55
 
56
+ /**
57
+ * The tools an agent should SEE, given deployment config. Optional features are
58
+ * hidden until enabled (see feature-flags.ts) — a tool an agent can see is a
59
+ * tool an agent may use, so "dormant" has to mean invisible, not just unused.
60
+ */
61
+ export async function listEnabledTools(
62
+ supabase: MCPSupabaseClient,
63
+ ): Promise<ToolDefinition[]> {
64
+ const relations = await relationsEnabled(supabase);
65
+ return ALL_TOOLS.filter((t) => relations || !RELATION_TOOL_NAMES.has(t.name));
66
+ }
67
+
68
+ /**
69
+ * Guard for the call path: a session that listed tools before the flag changed
70
+ * (or a hand-written client) can still name a gated tool.
71
+ */
72
+ export async function assertToolEnabled(
73
+ supabase: MCPSupabaseClient,
74
+ name: string,
75
+ ): Promise<void> {
76
+ if (!RELATION_TOOL_NAMES.has(name)) return;
77
+ if (await relationsEnabled(supabase)) return;
78
+ throw new McpInvalidParams(disabledToolMessage(name));
79
+ }
80
+
51
81
  /** Build a name → definition map for fast dispatch. */
52
82
  export const TOOLS_BY_NAME: Record<string, ToolDefinition> = Object.fromEntries(
53
83
  ALL_TOOLS.map((t) => [t.name, t]),
@@ -57,3 +57,8 @@ ALTER TABLE cerefox_audit_log ADD CONSTRAINT cerefox_audit_log_operation_check C
57
57
  'status-change', 'archive', 'unarchive', 'restore',
58
58
  'relation-set', 'relation-delete')
59
59
  );
60
+
61
+ -- Relations ship dormant: the MCP tools stay hidden until a deployment opts in.
62
+ INSERT INTO cerefox_config (key, value)
63
+ VALUES ('relations_enabled', 'false')
64
+ ON CONFLICT (key) DO NOTHING;
@@ -2037,7 +2037,9 @@ DECLARE
2037
2037
  -- because they all resolve through these RPCs.
2038
2038
  v_allowed TEXT[] := ARRAY[
2039
2039
  'usage_tracking_enabled', 'require_requestor_identity', 'requestor_identity_format',
2040
- 'min_search_score', 'min_term_coverage', 'search_alpha'
2040
+ 'min_search_score', 'min_term_coverage', 'search_alpha',
2041
+ -- Optional features, off by default (iteration 29).
2042
+ 'relations_enabled'
2041
2043
  ];
2042
2044
  BEGIN
2043
2045
  IF NOT (p_key = ANY(v_allowed)) THEN
@@ -2232,7 +2234,7 @@ SET search_path = public, pg_catalog
2232
2234
  AS $$
2233
2235
  -- Keep in lockstep with the `@version:` marker in schema.sql (cut_release.ts
2234
2236
  -- enforces it). Bump whenever schema.sql OR rpcs.sql changes.
2235
- SELECT '0.10.0'::TEXT;
2237
+ SELECT '0.10.1'::TEXT;
2236
2238
  $$;
2237
2239
 
2238
2240
  -- ── cerefox_content_format_stats ─────────────────────────────────────────────
@@ -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.10.0
8
+ -- @version: 0.10.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 —
@@ -368,6 +368,11 @@ ON CONFLICT (key) DO NOTHING;
368
368
  INSERT INTO cerefox_config (key, value)
369
369
  VALUES ('requestor_identity_format', '^[a-zA-Z0-9_:.\- ]+$')
370
370
  ON CONFLICT (key) DO NOTHING;
371
+ -- Document relations ship dormant: the tools stay hidden from agents until a
372
+ -- deployment opts in (iteration 29).
373
+ INSERT INTO cerefox_config (key, value)
374
+ VALUES ('relations_enabled', 'false')
375
+ ON CONFLICT (key) DO NOTHING;
371
376
 
372
377
 
373
378
  -- ── Usage log ────────────────────────────────────────────────────────────────
@@ -34,12 +34,15 @@ import {
34
34
  unauthorizedChallenge,
35
35
  } from "./oauth.ts";
36
36
  import type { AuthResult, McpAuthenticator } from "../../../_shared/mcp-auth/index.ts";
37
+ import type { MCPSupabaseClient } from "../../../_shared/mcp-tools/types.ts";
37
38
  import { checkAccessToken, parseAccessTokens } from "../../../_shared/ef-auth/index.ts";
38
39
  import {
39
40
  ALL_TOOLS,
40
41
  McpInvalidParams,
41
42
  TOOLS_BY_NAME,
42
43
  type ToolContext,
44
+ assertToolEnabled,
45
+ listEnabledTools,
43
46
  } from "../../../_shared/mcp-tools/index.ts";
44
47
  import {
45
48
  type AggregatedVersions,
@@ -57,11 +60,16 @@ const SERVER_VERSION = "0.4.0";
57
60
 
58
61
  // ── Tool list (derived from _shared/mcp-tools/) ─────────────────────────────
59
62
 
60
- const TOOLS = ALL_TOOLS.map((t) => ({
61
- name: t.name,
62
- description: t.description,
63
- inputSchema: t.inputSchema,
64
- }));
63
+ // Built per request rather than at module load: the tool surface depends on
64
+ // deployment config (optional features are hidden until enabled), and an
65
+ // isolate can outlive a config change.
66
+ async function buildToolList(supabase: MCPSupabaseClient) {
67
+ return (await listEnabledTools(supabase)).map((t) => ({
68
+ name: t.name,
69
+ description: t.description,
70
+ inputSchema: t.inputSchema,
71
+ }));
72
+ }
65
73
 
66
74
  // ── Method handlers ──────────────────────────────────────────────────────────
67
75
 
@@ -77,8 +85,8 @@ function handleInitialize(id: unknown): Response {
77
85
  });
78
86
  }
79
87
 
80
- function handleToolsList(id: unknown): Response {
81
- return jsonResponse({ jsonrpc: "2.0", id, result: { tools: TOOLS } });
88
+ async function handleToolsList(id: unknown, supabase: MCPSupabaseClient): Promise<Response> {
89
+ return jsonResponse({ jsonrpc: "2.0", id, result: { tools: await buildToolList(supabase) } });
82
90
  }
83
91
 
84
92
  async function handleToolsCall(
@@ -105,6 +113,14 @@ async function handleToolsCall(
105
113
  // deno-lint-ignore no-explicit-any
106
114
  const supabase: any = makeSupabaseClient();
107
115
 
116
+ // Optional features: a session that listed tools before the flag changed can
117
+ // still name a gated tool.
118
+ try {
119
+ await assertToolEnabled(supabase, toolName);
120
+ } catch (err) {
121
+ return errorResponse(id, -32602, err instanceof Error ? err.message : String(err));
122
+ }
123
+
108
124
  try {
109
125
  const { data: requireConfig } = await supabase.rpc("cerefox_get_config", {
110
126
  p_key: "require_requestor_identity",
@@ -336,7 +352,8 @@ Deno.serve(async (req: Request): Promise<Response> => {
336
352
  case "ping":
337
353
  return jsonResponse({ jsonrpc: "2.0", id, result: {} });
338
354
  case "tools/list":
339
- return handleToolsList(id);
355
+ // deno-lint-ignore no-explicit-any
356
+ return await handleToolsList(id, makeSupabaseClient() as any);
340
357
  case "tools/call":
341
358
  return await handleToolsCall(
342
359
  id,
@@ -117,6 +117,12 @@ This handles intermittent OpenAI API errors (500s) that would otherwise cause se
117
117
 
118
118
  ## Retrieval
119
119
 
120
+ > **Optional features.** `relations_enabled` (default `false`) controls whether
121
+ > the document-relation tools are exposed to agents. The feature ships
122
+ > **dormant**: the table stays empty, `lifecycle_status` defaults to `active`,
123
+ > search is untouched, and the tools do not appear in any agent's tool list
124
+ > until you opt in with `cerefox config set relations_enabled true`.
125
+
120
126
  > **Deployment-wide defaults (v1.1.0+).** `min_search_score`,
121
127
  > `min_term_coverage`, and `search_alpha` can also be set **once, in the
122
128
  > database**, and every access path obeys — CLI, local and remote MCP, Edge
@@ -40,7 +40,14 @@ in — even after the current version has moved to format 2.
40
40
  - A document **moves to format 2 automatically the next time it is edited/saved**
41
41
  (it gets re-chunked by the new chunker).
42
42
  - If you want to convert everything now rather than on next edit, run
43
- `cerefox server reindex` (re-chunks + re-embeds the whole knowledge base).
43
+ `cerefox server migrate-format`. It re-ingests each legacy document through
44
+ the normal pipeline (re-chunk + re-embed + stamp the current format), which
45
+ costs embedding spend — so it is opt-in, supports `--dry-run` and `--limit`,
46
+ and skips any document that changes mid-run rather than overwriting it.
47
+
48
+ > **Not `cerefox server reindex`.** Reindex refreshes *embeddings* on the
49
+ > existing chunk rows; it never re-chunks, so it cannot advance the stored
50
+ > format. Earlier versions of this guide said otherwise (#164).
44
51
 
45
52
  `cerefox doctor` reports how many documents still use the legacy format — purely
46
53
  informational, never a failure. A fresh install shows zero.
@@ -21,7 +21,22 @@ This guide walks you from a blank Supabase project to a fully deployed Cerefox s
21
21
  1. Go to [app.supabase.com](https://app.supabase.com) and sign in
22
22
  2. Click **New project**
23
23
  3. Choose a name (e.g. `cerefox`), set a strong database password, pick a region close to you
24
- 4. Click **Create new project** and wait ~2 minutes for it to provision
24
+ 4. Review the security options offered at creation (defaults in brackets):
25
+ - **Enable Data API** *[on]* — **keep it on.** The CLI, web UI, and local MCP all reach
26
+ Supabase through PostgREST; without it nothing works.
27
+ - **Automatically expose new tables** *[on]* — **turn it off.** Cerefox grants its tables
28
+ to `service_role` explicitly (migration `0013`), so it does not rely on implicit
29
+ exposure, and leaving it on means any future table is exposed to the Data API roles by
30
+ default. Supabase recommends disabling it too.
31
+ - **Enable automatic RLS** *[off]* — optional. Cerefox's schema already runs
32
+ `ENABLE ROW LEVEL SECURITY` on every table it creates, so this only covers tables
33
+ Cerefox doesn't own. Harmless either way; every Cerefox access path authenticates as
34
+ `service_role`, which bypasses RLS by design.
35
+ 5. Click **Create new project** and wait ~2 minutes for it to provision
36
+
37
+ > **Note on the database password**: it is used *only* by `cerefox server deploy` (a direct
38
+ > Postgres connection for DDL). Everyday CLI, web, and MCP traffic uses the secret key
39
+ > instead. Store it in a password manager — Supabase will not show it again.
25
40
 
26
41
  ---
27
42
 
@@ -56,14 +71,24 @@ Either way: keep this key secret — it bypasses Row Level Security and grants f
56
71
 
57
72
  This is used by `cerefox server deploy` (and the contributor scripts `bun scripts/db_deploy.ts` / `bun scripts/db_migrate.ts`). See the **[Connection pooling in 2026](#connection-pooling-2026)** reference section near the end of this guide for context. The short version:
58
73
 
59
- 1. Open **Project Settings → Database → Connection pooling** (not the "Connect" dialog — that one usually omits the Session Pooler in the new UI).
74
+ 1. From the **project overview**, click the **Copy** button beside the project URL, then
75
+ **Get Connected** in the dropdown. Under **Direct Connection Pooling**, choose
76
+ **Session pooler**. *(Verified against a project created 2026-08-05. Supabase moves this
77
+ regularly — it used to live under Project Settings → Database → Connection pooling, which
78
+ may still work.)*
60
79
  2. Copy the **Session Pooler** URI (host ends in `.pooler.supabase.com`, port `5432`).
61
80
  3. Confirm the username has the form `postgres.<project-ref>` — without that suffix you'll get "Tenant or user not found".
62
81
  4. Append `?sslmode=require` to enforce TLS explicitly.
63
82
 
83
+ The result looks like this — note both the ref-suffixed username **and** the pooler host:
84
+
85
+ ```
86
+ postgresql://postgres.abcdefghijklmnop:<password>@aws-0-<region>.pooler.supabase.com:5432/postgres?sslmode=require
87
+ ```
88
+
64
89
  If you only see Direct Connection and Transaction Pooler in your dashboard, take the Transaction Pooler URI and change `:6543` → `:5432`. That gives you the Session Pooler. **Do not use port 6543** — Transaction Pooler does not support DDL and the schema deploy will fail mid-schema.
65
90
 
66
- The Direct Connection (`db.<project-ref>.supabase.co:5432`) is IPv6-only on the free tier and unusable on most home/office networks. The dashboard now warns about this directly.
91
+ > **Don't take the Direct connection string** (`postgresql://postgres:<password>@db.<ref>.supabase.co:5432/postgres`). It is the most prominent option in the dialog, but it is IPv6-only on the free tier, so it times out on most home and office networks. The dashboard warns about this directly.
67
92
 
68
93
  ---
69
94
 
@@ -380,7 +405,13 @@ Not yet. The Supabase docs explicitly state: *"You can still use old anon and se
380
405
 
381
406
  ## Connection pooling in 2026 <a id="connection-pooling-2026"></a>
382
407
 
383
- Supabase's "Connect" dialog was redesigned in 2026 and the **Session Pooler** is no longer a first-class tab in many projects. The other two surfaces (Direct Connection and Transaction Pooler) are present but neither works for Cerefox's deployment scripts. Here's the full picture.
408
+ Supabase's "Connect" dialog has been redesigned repeatedly through 2026, and where the
409
+ **Session Pooler** lives has moved with it. As of **2026-08-05** it is inside the Connect
410
+ dialog: project overview → **Copy** (beside the project URL) → **Get Connected** →
411
+ **Direct Connection Pooling** → **Session pooler**. Earlier in the year it was only under
412
+ Project Settings → Database → Connection pooling. Check both if one comes up empty — and the
413
+ shape of the URI is the reliable signal, not the menu path: **ref-suffixed username plus a
414
+ `.pooler.supabase.com` host on port 5432**. Here's the full picture.
384
415
 
385
416
  ### The three Postgres connection types
386
417
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cerefox/memory",
3
- "version": "1.1.0-beta.1",
3
+ "version": "1.1.0-beta.2",
4
4
  "description": "Cerefox — user-owned shared memory for AI agents. CLI + stdio MCP server + web UI + ingestion for a knowledge base on your own Supabase project (or fully self-hosted with Cerefox Local).",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://github.com/fstamatelopoulos/cerefox",