@cerefox/memory 1.1.0-beta.5 → 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.
@@ -7438,7 +7438,7 @@ var exports_meta = {};
7438
7438
  __export(exports_meta, {
7439
7439
  PKG_VERSION: () => PKG_VERSION
7440
7440
  });
7441
- var PKG_VERSION = "1.1.0-beta.5";
7441
+ var PKG_VERSION = "1.1.0-beta.6";
7442
7442
  var init_meta = () => {};
7443
7443
 
7444
7444
  // ../../_shared/config/paths.ts
@@ -78094,7 +78094,7 @@ async function action27(options) {
78094
78094
  println(c.bold(`Reindexing ${chunks.length} chunk(s) ${reindexAll ? "(--all)" : "(stale only)"}${dryRun ? " — DRY RUN" : ""}`));
78095
78095
  warnLargeBulkWrite({
78096
78096
  count: chunks.length,
78097
- threshold: 1000,
78097
+ threshold: 5000,
78098
78098
  unit: "chunk",
78099
78099
  batchHint: "reindex in stages with --document-id"
78100
78100
  });
@@ -78189,7 +78189,7 @@ async function action28(options) {
78189
78189
  }
78190
78190
  warnLargeBulkWrite({
78191
78191
  count: targets.length,
78192
- threshold: 200,
78192
+ threshold: 1000,
78193
78193
  unit: "document",
78194
78194
  batchHint: "run it in batches with --limit 100"
78195
78195
  });
@@ -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 —
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cerefox/memory",
3
- "version": "1.1.0-beta.5",
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",