@cerefox/memory 1.1.0-beta.4 → 1.1.0-beta.6

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-BLqrpbNz.js"></script>
18
+ <script type="module" crossorigin src="/app/assets/index-Co28kX04.js"></script>
19
19
  <link rel="stylesheet" crossorigin href="/app/assets/index-C1JXZA9m.css">
20
20
  </head>
21
21
  <body>
@@ -0,0 +1,51 @@
1
+ -- 0015_conflict_errcode_pt409.sql — stop a permanent conflict masquerading as
2
+ -- a retryable one.
3
+ --
4
+ -- `cerefox_ingest_document` raised CEREFOX_CONFLICT under SQLSTATE '40001'
5
+ -- (serialization_failure). In PostgreSQL, 40001 is the one class that promises
6
+ -- "this failure was transient — retry the transaction and it may succeed", and
7
+ -- PostgREST maps it to a retryable HTTP status. But an optimistic-concurrency
8
+ -- conflict is DETERMINISTIC: the same request, carrying the same stale token,
9
+ -- fails identically forever. Retry-aware infrastructure took the promise at
10
+ -- face value and looped with no exit condition.
11
+ --
12
+ -- Measured on a live project before the fix:
13
+ --
14
+ -- * ONE HTTP request with a stale token executed the function 68,825 times
15
+ -- in 125 seconds, then returned 504 Gateway Timeout.
16
+ -- * The loop OUTLIVED the client: it kept running after the 504, passing
17
+ -- 153,000 executions before the backend was terminated by hand.
18
+ -- * A contributor hit the same loop for roughly a day: ~47 MILLION calls,
19
+ -- which exhausted their project's Disk IO budget and required killing a
20
+ -- hung connection to stop.
21
+ -- * The identical probe raising PT409 executed exactly ONCE and returned
22
+ -- 409 Conflict in 636 ms.
23
+ --
24
+ -- Two changes, both in `cerefox_ingest_document`:
25
+ --
26
+ -- 1. Conflicts now raise SQLSTATE 'PT409'. PostgREST's PTxxx convention maps
27
+ -- it to HTTP 409 Conflict, which nothing retries.
28
+ -- 2. A blank (empty or whitespace) expected_content_hash is treated as
29
+ -- ABSENT rather than stale, so it raises CEREFOX_TOKEN_REQUIRED (400)
30
+ -- instead of a conflict. '' is not NULL, so it used to slip past the
31
+ -- absent-token branch into the conflict branch — and could never match a
32
+ -- real hash, making it a permanent failure. That is the exact shape that
33
+ -- triggered the incident.
34
+ --
35
+ -- Client detection is unaffected: every transport matches on the
36
+ -- `CEREFOX_CONFLICT:` / `CEREFOX_TOKEN_REQUIRED:` message prefix, never on the
37
+ -- SQLSTATE.
38
+ --
39
+ -- This migration only re-applies `rpcs.sql`, which `cerefox server deploy`
40
+ -- does anyway. It exists so the schema version moves and operators are told to
41
+ -- redeploy — the fix is inert until the RPC is replaced.
42
+ --
43
+ -- Idempotent: safe to re-run.
44
+
45
+ DO $$
46
+ BEGIN
47
+ RAISE NOTICE
48
+ 'Migration 0015: CEREFOX_CONFLICT now raises PT409 (HTTP 409) instead of 40001. '
49
+ 'The change lives in rpcs.sql, which is re-applied by `cerefox server deploy`. '
50
+ 'Until that runs, stale-token conflicts remain retryable by infrastructure.';
51
+ END $$;
@@ -1253,7 +1253,7 @@ $$;
1253
1253
  -- p_expected_content_hash : optimistic-concurrency token (iter-32). On the UPDATE
1254
1254
  -- path this must equal the document's current content_hash —
1255
1255
  -- the caller proves they based their edit on the live version.
1256
- -- Mismatch → CEREFOX_CONFLICT (SQLSTATE 40001). Absent (NULL)
1256
+ -- Mismatch → CEREFOX_CONFLICT (SQLSTATE PT409 → HTTP 409). Absent (NULL)
1257
1257
  -- without p_last_write_wins → CEREFOX_TOKEN_REQUIRED (22023).
1258
1258
  -- Ignored on the CREATE path.
1259
1259
  -- p_last_write_wins : explicit opt-out of the concurrency check (filesystem-sync
@@ -1368,7 +1368,17 @@ BEGIN
1368
1368
  -- choose last-write-wins. Message prefixes are machine-detectable:
1369
1369
  -- transport handlers map them to agent-first retry instructions.
1370
1370
  IF NOT p_last_write_wins THEN
1371
- IF p_expected_content_hash IS NULL THEN
1371
+ -- A blank token is an ABSENT token, not a stale one.
1372
+ --
1373
+ -- '' is not NULL, so an empty string used to skip the
1374
+ -- TOKEN_REQUIRED branch and fall into the conflict branch below:
1375
+ -- it can never equal a real hash, so it failed deterministically
1376
+ -- and forever. That is precisely the shape that drove the retry
1377
+ -- storm — a permanent failure reported as a retryable one. Even
1378
+ -- with PT409 now closing the loop, classifying it as a conflict is
1379
+ -- wrong: nobody read '' from a document, so the caller has not
1380
+ -- followed the read-before-write contract, which is a 400.
1381
+ IF NULLIF(BTRIM(p_expected_content_hash), '') IS NULL THEN
1372
1382
  RAISE EXCEPTION
1373
1383
  'CEREFOX_TOKEN_REQUIRED: content updates require expected_content_hash (the content_hash you read) or last_write_wins=true. Current hash: %',
1374
1384
  v_current_hash
@@ -1377,7 +1387,27 @@ BEGIN
1377
1387
  RAISE EXCEPTION
1378
1388
  'CEREFOX_CONFLICT: document % changed since it was read (expected hash %, current hash %). Re-read the document, merge your changes, and retry with the new hash.',
1379
1389
  v_doc_id, p_expected_content_hash, v_current_hash
1380
- USING ERRCODE = '40001'; -- serialization_failure
1390
+ -- PT409 → HTTP 409 Conflict (PostgREST's PTxxx convention).
1391
+ --
1392
+ -- This was '40001' (serialization_failure) until v1.1.0-beta.6,
1393
+ -- which was a category error with severe consequences. 40001 is
1394
+ -- the ONE PostgreSQL class that promises "this was transient,
1395
+ -- retry and it may succeed" — but a stale-token conflict is
1396
+ -- DETERMINISTIC: the same request fails identically forever.
1397
+ -- Retry-aware layers took the promise at face value and looped.
1398
+ --
1399
+ -- Measured on a real project: one HTTP request carrying a stale
1400
+ -- hash executed this function 68,825 times in 125s before the
1401
+ -- gateway returned 504 — and kept going after the client was
1402
+ -- gone, passing 153,000 executions before the backend was killed
1403
+ -- manually. A contributor hit the same loop for ~24h and 47
1404
+ -- MILLION calls, which is what depleted their Disk IO budget.
1405
+ -- The same probe raising PT409 executed exactly ONCE and
1406
+ -- returned 409 in 636ms.
1407
+ --
1408
+ -- Rule of thumb: never raise a permanent application error under
1409
+ -- a SQLSTATE whose contract says "retryable".
1410
+ USING ERRCODE = 'PT409';
1381
1411
  END IF;
1382
1412
  END IF;
1383
1413
 
@@ -2234,7 +2264,7 @@ SET search_path = public, pg_catalog
2234
2264
  AS $$
2235
2265
  -- Keep in lockstep with the `@version:` marker in schema.sql (cut_release.ts
2236
2266
  -- enforces it). Bump whenever schema.sql OR rpcs.sql changes.
2237
- SELECT '0.10.1'::TEXT;
2267
+ SELECT '0.10.2'::TEXT;
2238
2268
  $$;
2239
2269
 
2240
2270
  -- ── 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.1
8
+ -- @version: 0.10.2
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 —
@@ -367,8 +367,12 @@ enable it.
367
367
 
368
368
  ### How it works
369
369
 
370
- A `cerefox_config` table in Postgres stores runtime configuration as key-value pairs. The only
371
- key currently in use is `usage_tracking_enabled`. Every usage logging call goes through the
370
+ A `cerefox_config` table in Postgres stores runtime configuration as key-value
371
+ pairs. The allow-list lives in the `cerefox_set_config` RPC; run `cerefox config
372
+ list` (or open **Settings** in the web UI) for the current set — today that is
373
+ usage tracking, the two requestor-identity keys, three retrieval tunables, and
374
+ `relations_enabled`. Usage logging is the illustrative case below: every logging
375
+ call goes through the
372
376
  `cerefox_log_usage` RPC, which checks this config value first:
373
377
 
374
378
  - If `usage_tracking_enabled` is `"true"` -- the RPC inserts a row into `cerefox_usage_log`
@@ -398,7 +402,30 @@ cerefox config set usage_tracking_enabled false
398
402
  cerefox config get usage_tracking_enabled
399
403
  ```
400
404
 
401
- The canonical path is the CLI (`cerefox config set <key> <value>` / `cerefox config get <key>`) above; it works regardless of whether the web server is running. The web UI's JSON API exposes the same config under `/api/v1/config/<key>` if you need programmatic access while `cerefox web` is running.
405
+ **Via the web UI:** `cerefox web` → **Settings**. Every runtime key is listed
406
+ with its description, current value and default, grouped into Retrieval,
407
+ Governance and Features.
408
+
409
+ Two things the page does deliberately:
410
+
411
+ - **Keys that change what agents see require confirmation.** Turning on
412
+ `relations_enabled` adds four tools to every connected agent's tool list, and
413
+ `require_requestor_identity` starts rejecting agents that don't identify
414
+ themselves. Neither is a bare toggle — you get a dialog naming the
415
+ consequence first.
416
+ - **Local overrides are shown, read-only.** If the server has
417
+ `CEREFOX_MIN_SEARCH_SCORE` (or the `..._TERM_COVERAGE` / `..._SEARCH_ALPHA`
418
+ equivalents) in its environment, that value beats the stored one *on that
419
+ machine*, and the row says so. Without this the page would report a value the
420
+ server isn't using. The page never edits `.env` — that file holds your
421
+ service-role key, OpenAI key and database password, and the server only reads
422
+ it at boot.
423
+
424
+ The CLI (`cerefox config set <key> <value>` / `cerefox config get <key>`) remains
425
+ the canonical path and works whether or not the web server is running. Both go
426
+ through the same `cerefox_set_config` RPC, so they cannot disagree.
427
+ `/api/v1/config` (list) and `/api/v1/config/<key>` (read/write) expose the same
428
+ data for programmatic access while `cerefox web` runs.
402
429
 
403
430
  ### What gets logged
404
431
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cerefox/memory",
3
- "version": "1.1.0-beta.4",
3
+ "version": "1.1.0-beta.6",
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",