@cerefox/memory 1.9.1 → 1.10.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.
@@ -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.9.1";
7441
+ var PKG_VERSION = "1.10.0";
7442
7442
  var init_meta = () => {};
7443
7443
 
7444
7444
  // ../../_shared/config/paths.ts
@@ -23530,9 +23530,9 @@ function isAuditCheckError(message) {
23530
23530
  function isDuplicateKeyError(message) {
23531
23531
  return /duplicate key|unique constraint|23505/i.test(message);
23532
23532
  }
23533
- function storeWriteRemediation(message, fnName) {
23533
+ function storeWriteRemediation(message, fnName, requiredSchema = "0.14.0") {
23534
23534
  if (isMissingFunctionError(message, fnName)) {
23535
- return "The deployed server predates schema 0.14.0 — or PostgREST's schema cache " + "is stale right after a deploy. If you just deployed, retry in a few " + "seconds; otherwise run `cerefox server deploy`.";
23535
+ return `The deployed server predates schema ${requiredSchema} — or PostgREST's schema cache ` + "is stale right after a deploy. If you just deployed, retry in a few " + "seconds; otherwise run `cerefox server deploy`.";
23536
23536
  }
23537
23537
  if (isAuditCheckError(message)) {
23538
23538
  return "The server's audit-log constraint predates migration 0028 (partial " + "deploy). Run `cerefox server deploy` to apply pending migrations, then retry.";
@@ -25598,6 +25598,221 @@ var init_src = __esm(() => {
25598
25598
  src_default = Postgres;
25599
25599
  });
25600
25600
 
25601
+ // ../../_shared/mcp-tools/_document-meta.ts
25602
+ function stableStringify(value) {
25603
+ if (Array.isArray(value))
25604
+ return `[${value.map(stableStringify).join(",")}]`;
25605
+ if (value !== null && typeof value === "object") {
25606
+ const keys = Object.keys(value).sort();
25607
+ return `{${keys.map((k) => `${JSON.stringify(k)}:${stableStringify(value[k])}`).join(",")}}`;
25608
+ }
25609
+ return JSON.stringify(value);
25610
+ }
25611
+ function normalizeMetadata(value) {
25612
+ const out = {};
25613
+ for (const [k, v] of Object.entries(value)) {
25614
+ if (v !== null)
25615
+ out[k] = v;
25616
+ }
25617
+ return out;
25618
+ }
25619
+ async function changeDocumentTitle(supabase, documentId, newTitle, who) {
25620
+ const trimmed = (newTitle ?? "").trim();
25621
+ if (!trimmed)
25622
+ throw new FacetValidationError("Title cannot be empty.");
25623
+ const { data, error } = await supabase.rpc("cerefox_rename_document", {
25624
+ p_document_id: documentId,
25625
+ p_new_title: trimmed,
25626
+ p_author: who.author,
25627
+ p_author_type: who.authorType
25628
+ });
25629
+ if (error) {
25630
+ const msg = error.message ?? String(error);
25631
+ if (/not found/i.test(msg))
25632
+ throw new FacetNotFoundError(msg);
25633
+ if (/cannot be empty/i.test(msg))
25634
+ throw new FacetValidationError(msg);
25635
+ const remediation = storeWriteRemediation(msg, "cerefox_rename_document", "0.15.0");
25636
+ if (remediation)
25637
+ throw new Error(`Title update failed: ${remediation}`);
25638
+ throw new Error(`Title update failed: ${msg}`);
25639
+ }
25640
+ const row = data?.[0];
25641
+ return { changed: row?.renamed ?? false, title: row?.new_title ?? trimmed };
25642
+ }
25643
+ async function applyMembershipReplace(supabase, opts) {
25644
+ const { data: current, error: curErr } = await supabase.from("cerefox_document_projects").select("project_id").eq("document_id", opts.documentId);
25645
+ if (curErr)
25646
+ throw new Error(`Membership read failed: ${curErr.message}`);
25647
+ const currentSet = new Set((current ?? []).map((r) => r.project_id));
25648
+ if (opts.projectIds.length === currentSet.size && opts.projectIds.every((id) => currentSet.has(id))) {
25649
+ return { changed: false };
25650
+ }
25651
+ const { error: delErr } = await supabase.from("cerefox_document_projects").delete().eq("document_id", opts.documentId);
25652
+ if (delErr)
25653
+ throw new Error(`Membership replace failed: ${delErr.message}`);
25654
+ if (opts.projectIds.length > 0) {
25655
+ const rows = opts.projectIds.map((pid) => ({
25656
+ document_id: opts.documentId,
25657
+ project_id: pid
25658
+ }));
25659
+ const { error: insErr } = await supabase.from("cerefox_document_projects").insert(rows);
25660
+ if (insErr)
25661
+ throw new Error(`Membership replace failed: ${insErr.message}`);
25662
+ }
25663
+ const { error: auditErr } = await supabase.rpc("cerefox_create_audit_entry", {
25664
+ p_document_id: opts.documentId,
25665
+ p_version_id: null,
25666
+ p_operation: "update-metadata",
25667
+ p_author: opts.author,
25668
+ p_author_type: opts.authorType,
25669
+ p_size_before: null,
25670
+ p_size_after: null,
25671
+ p_description: opts.projectNames.length > 0 ? `Set document projects to [${opts.projectNames.join(", ")}]` : "Cleared all project memberships"
25672
+ });
25673
+ if (auditErr)
25674
+ console.warn("applyMembershipReplace: audit entry failed", auditErr.message);
25675
+ logUsage(supabase, {
25676
+ operation: "set-document-projects",
25677
+ accessPath: opts.accessPath,
25678
+ requestor: opts.author,
25679
+ document_id: opts.documentId,
25680
+ result_count: opts.projectIds.length
25681
+ });
25682
+ return { changed: true };
25683
+ }
25684
+ async function assertDocumentLive(supabase, documentId) {
25685
+ const { data, error } = await supabase.from("cerefox_documents").select("id").eq("id", documentId).is("deleted_at", null).limit(1);
25686
+ if (error)
25687
+ throw new Error(`Document read failed: ${error.message}`);
25688
+ if (!data?.length) {
25689
+ throw new FacetNotFoundError(`Document not found (or in the trash): ${documentId}`);
25690
+ }
25691
+ }
25692
+ async function setDocumentProjectsByIds(supabase, opts) {
25693
+ const wanted = [...new Set(opts.projectIds)];
25694
+ const { data: current, error: curErr } = await supabase.from("cerefox_document_projects").select("project_id").eq("document_id", opts.documentId);
25695
+ if (curErr)
25696
+ throw new Error(`Membership read failed: ${curErr.message}`);
25697
+ const currentSet = new Set((current ?? []).map((r2) => r2.project_id));
25698
+ if (wanted.length === currentSet.size && wanted.every((id) => currentSet.has(id))) {
25699
+ return { changed: false, names: [] };
25700
+ }
25701
+ await assertDocumentLive(supabase, opts.documentId);
25702
+ let names = [];
25703
+ if (wanted.length > 0) {
25704
+ const { data: found, error: valErr } = await supabase.from("cerefox_projects").select("id, name").in("id", wanted);
25705
+ if (valErr)
25706
+ throw new Error(`Project validation failed: ${valErr.message}`);
25707
+ const byId = new Map((found ?? []).map((r2) => [r2.id, r2.name]));
25708
+ const missing = wanted.filter((id) => !byId.has(id));
25709
+ if (missing.length > 0) {
25710
+ throw new FacetValidationError(`Unknown project id(s): ${missing.join(", ")} — memberships left unchanged.`);
25711
+ }
25712
+ names = wanted.map((id) => byId.get(id));
25713
+ }
25714
+ const r = await applyMembershipReplace(supabase, {
25715
+ documentId: opts.documentId,
25716
+ projectIds: wanted,
25717
+ projectNames: names,
25718
+ accessPath: opts.accessPath,
25719
+ author: opts.author,
25720
+ authorType: opts.authorType
25721
+ });
25722
+ return { changed: r.changed, names };
25723
+ }
25724
+ async function updateDocumentFacets(supabase, opts) {
25725
+ const result = {
25726
+ titleChanged: false,
25727
+ metadataChanged: false,
25728
+ projectsChanged: false
25729
+ };
25730
+ const who = { author: opts.author, authorType: opts.authorType };
25731
+ const step = async (fn) => {
25732
+ try {
25733
+ return await fn();
25734
+ } catch (err) {
25735
+ throw err instanceof FacetUpdateError ? err : new FacetUpdateError(result, err instanceof Error ? err : new Error(String(err)));
25736
+ }
25737
+ };
25738
+ if (opts.metadata !== undefined) {
25739
+ await step(async () => {
25740
+ const { data: doc, error } = await supabase.from("cerefox_documents").select("metadata").eq("id", opts.documentId).is("deleted_at", null).limit(1);
25741
+ if (error)
25742
+ throw new Error(`Metadata read failed: ${error.message}`);
25743
+ if (!doc?.length) {
25744
+ throw new FacetNotFoundError(`Document not found (or in the trash): ${opts.documentId}`);
25745
+ }
25746
+ const stored = doc[0].metadata ?? {};
25747
+ const wanted = normalizeMetadata(opts.metadata);
25748
+ if (stableStringify(stored) !== stableStringify(wanted)) {
25749
+ const { error: rpcErr } = await supabase.rpc("cerefox_set_document_metadata", {
25750
+ p_document_id: opts.documentId,
25751
+ p_metadata: wanted,
25752
+ p_replace: true,
25753
+ p_author: opts.author,
25754
+ p_author_type: opts.authorType
25755
+ });
25756
+ if (rpcErr) {
25757
+ const msg = rpcErr.message ?? String(rpcErr);
25758
+ if (/not found/i.test(msg))
25759
+ throw new FacetNotFoundError(msg);
25760
+ throw new Error(`Metadata update failed: ${msg}`);
25761
+ }
25762
+ result.metadataChanged = true;
25763
+ }
25764
+ });
25765
+ }
25766
+ if (opts.projectIds !== undefined) {
25767
+ await step(async () => {
25768
+ const r = await setDocumentProjectsByIds(supabase, {
25769
+ documentId: opts.documentId,
25770
+ projectIds: opts.projectIds,
25771
+ accessPath: opts.accessPath,
25772
+ ...who
25773
+ });
25774
+ result.projectsChanged = r.changed;
25775
+ });
25776
+ }
25777
+ if (opts.title !== undefined) {
25778
+ await step(async () => {
25779
+ const r = await changeDocumentTitle(supabase, opts.documentId, opts.title, who);
25780
+ result.titleChanged = r.changed;
25781
+ });
25782
+ }
25783
+ return result;
25784
+ }
25785
+ var FacetNotFoundError, FacetValidationError, FacetUpdateError;
25786
+ var init__document_meta = __esm(() => {
25787
+ init__utils();
25788
+ FacetNotFoundError = class FacetNotFoundError extends Error {
25789
+ constructor(message) {
25790
+ super(message);
25791
+ this.name = "FacetNotFoundError";
25792
+ }
25793
+ };
25794
+ FacetValidationError = class FacetValidationError extends Error {
25795
+ constructor(message) {
25796
+ super(message);
25797
+ this.name = "FacetValidationError";
25798
+ }
25799
+ };
25800
+ FacetUpdateError = class FacetUpdateError extends Error {
25801
+ applied;
25802
+ constructor(applied, cause) {
25803
+ const done = [
25804
+ applied.metadataChanged ? "metadata" : null,
25805
+ applied.projectsChanged ? "projects" : null,
25806
+ applied.titleChanged ? "title" : null
25807
+ ].filter(Boolean);
25808
+ super(done.length > 0 ? `${cause.message} (already applied before the failure: ${done.join(", ")})` : cause.message);
25809
+ this.name = "FacetUpdateError";
25810
+ this.applied = applied;
25811
+ this.cause = cause;
25812
+ }
25813
+ };
25814
+ });
25815
+
25601
25816
  // ../../_shared/mcp-tools/_projects.ts
25602
25817
  async function resolveOrCreateProject(supabase, projectName, audit) {
25603
25818
  const { data, error } = await supabase.rpc("cerefox_create_project", {
@@ -25670,31 +25885,13 @@ async function replaceDocumentProjects(supabase, opts) {
25670
25885
  throw new Error(`Could not resolve project(s): ${failed.join(", ")} — memberships left unchanged.`);
25671
25886
  }
25672
25887
  const projectIds = resolved.map((r) => r.projectId);
25673
- await supabase.from("cerefox_document_projects").delete().eq("document_id", documentId);
25674
- if (projectIds.length > 0) {
25675
- const rows = projectIds.map((pid) => ({ document_id: documentId, project_id: pid }));
25676
- await supabase.from("cerefox_document_projects").insert(rows);
25677
- }
25678
- try {
25679
- await supabase.rpc("cerefox_create_audit_entry", {
25680
- p_document_id: documentId,
25681
- p_version_id: null,
25682
- p_operation: "update-metadata",
25683
- p_author: author,
25684
- p_author_type: authorType,
25685
- p_size_before: null,
25686
- p_size_after: null,
25687
- p_description: cleanNames.length > 0 ? `Set document projects to [${cleanNames.join(", ")}]` : "Cleared all project memberships"
25688
- });
25689
- } catch (err) {
25690
- console.warn("replaceDocumentProjects: audit entry failed", err);
25691
- }
25692
- logUsage(supabase, {
25693
- operation: "set-document-projects",
25888
+ await applyMembershipReplace(supabase, {
25889
+ documentId,
25890
+ projectIds,
25891
+ projectNames: cleanNames,
25694
25892
  accessPath,
25695
- requestor: author,
25696
- document_id: documentId,
25697
- result_count: projectIds.length
25893
+ author,
25894
+ authorType
25698
25895
  });
25699
25896
  return { documentTitle: doc[0].title, cleanNames, projectIds };
25700
25897
  }
@@ -25705,6 +25902,7 @@ async function lookupProjectId(supabase, projectName) {
25705
25902
  return data[0].id;
25706
25903
  }
25707
25904
  var init__projects = __esm(() => {
25905
+ init__document_meta();
25708
25906
  init__utils();
25709
25907
  });
25710
25908
 
@@ -25802,7 +26000,7 @@ var init_bundled_docs = __esm(() => {
25802
26000
  });
25803
26001
 
25804
26002
  // ../../_shared/ef-meta/index.ts
25805
- var EF_VERSION = "1.9.1", CEREFOX_VERSION = "1.9.1", EF_LAST_CHANGED = "1.9.0";
26003
+ var EF_VERSION = "1.10.0", CEREFOX_VERSION = "1.10.0", EF_LAST_CHANGED = "1.10.0";
25806
26004
  var init_ef_meta = () => {};
25807
26005
 
25808
26006
  // ../../_shared/compatibility/index.ts
@@ -73413,6 +73611,7 @@ Proceed with deployment to Supabase?`, true);
73413
73611
  println(c.green(`
73414
73612
  ✓ Server deploy complete.`));
73415
73613
  println(c.dim("Verify with: cerefox doctor"));
73614
+ println(c.dim("Refresh the bundled guides: cerefox guides ingest"));
73416
73615
  }
73417
73616
  function registerDeployServer(program2) {
73418
73617
  program2.command("deploy-server").description("Deploy/update the Cerefox server side (schema + RPCs + Edge Functions) on Supabase.").option("--dry-run", "Print the plan + pre-flight without deploying.").option("--schema-only", "Deploy/update only the schema + RPCs (skip Edge Functions).").option("--functions-only", "Deploy only the Edge Functions (skip the schema/RPCs).").option("--project-ref <ref>", "Supabase project ref for Edge Function deploys (default: derived from CEREFOX_SUPABASE_URL).").option("--yes", "Non-interactive (skip the deployment confirmation).").action(action10);
@@ -73420,6 +73619,7 @@ function registerDeployServer(program2) {
73420
73619
 
73421
73620
  // src/cli/commands/document-edit.ts
73422
73621
  init_cli_core();
73622
+ init__document_meta();
73423
73623
  init_client();
73424
73624
  function parseMetaPair(pair) {
73425
73625
  const eq = pair.indexOf("=");
@@ -73483,27 +73683,17 @@ async function action11(documentId, options) {
73483
73683
  throw systemError(`Metadata update failed: ${metaErr.message}`);
73484
73684
  }
73485
73685
  }
73486
- if (hasTitle) {
73487
- const { error: updErr } = await client.raw.from("cerefox_documents").update({ title: newTitle, updated_at: new Date().toISOString() }).eq("id", documentId);
73488
- if (updErr)
73489
- throw systemError(`Update failed: ${updErr.message}`);
73490
- }
73491
- if (titleChanged) {
73492
- const { error: ftsErr } = await client.raw.rpc("cerefox_update_chunk_fts", {
73493
- p_document_id: documentId,
73494
- p_new_title: newTitle
73495
- });
73496
- if (ftsErr)
73497
- throw systemError(`Title updated but FTS refresh failed: ${ftsErr.message}`);
73498
- }
73499
- if (titleChanged) {
73500
- await client.raw.rpc("cerefox_create_audit_entry", {
73501
- p_document_id: documentId,
73502
- p_operation: "update-metadata",
73503
- p_author: author,
73504
- p_author_type: authorType,
73505
- p_description: "Edited title"
73506
- });
73686
+ if (hasTitle && titleChanged) {
73687
+ try {
73688
+ await changeDocumentTitle(client.raw, documentId, newTitle, { author, authorType });
73689
+ } catch (err) {
73690
+ const msg = err instanceof Error ? err.message : String(err);
73691
+ if (err instanceof FacetNotFoundError)
73692
+ throw notFound(msg);
73693
+ if (err instanceof FacetValidationError)
73694
+ throw userError(msg);
73695
+ throw systemError(msg);
73696
+ }
73507
73697
  }
73508
73698
  println(c.green(`✓ Edited "${newTitle}" (id: ${documentId}).`));
73509
73699
  if (titleChanged) {
@@ -80660,14 +80850,10 @@ async function action33(options) {
80660
80850
  println("");
80661
80851
  println(c.green(`✓ Upgraded to ${target}.`));
80662
80852
  println("");
80663
- println(c.dim("Refreshing bundled-docs ingest (using the new version)…"));
80664
- const sync = spawnSync5(process.execPath, [process.argv[1], "guides", "ingest"], {
80665
- stdio: "inherit"
80666
- });
80667
- if (sync.status !== 0) {
80668
- println(cErr.yellow("⚠ ") + "Could not refresh self-docs (see output above).");
80669
- println(c.dim(" Run `cerefox guides ingest` manually after a successful `cerefox init`."));
80670
- }
80853
+ println("Next steps:");
80854
+ println(" 1. " + c.bold("cerefox server deploy") + " apply this release's schema/RPC/EF updates");
80855
+ println(" 2. " + c.bold("cerefox guides ingest") + " re-sync the bundled guides into your KB");
80856
+ println(c.dim(" (order matters: the new client may require the new schema — run the deploy first)"));
80671
80857
  }
80672
80858
  function registerSelfUpdate(program2) {
80673
80859
  const desc = "Upgrade Cerefox in place. Alias: `cerefox upgrade`.";
@@ -85301,6 +85487,7 @@ function registerDocumentReadRoutes(app, ctx) {
85301
85487
  });
85302
85488
  }
85303
85489
  // src/web/routes/documents-write.ts
85490
+ init__document_meta();
85304
85491
  init__utils();
85305
85492
  async function createAuditEntry(ctx, args) {
85306
85493
  try {
@@ -85316,16 +85503,6 @@ async function createAuditEntry(ctx, args) {
85316
85503
  });
85317
85504
  } catch {}
85318
85505
  }
85319
- async function assignDocumentProjects(ctx, documentId, projectIds) {
85320
- await ctx.supabase.from("cerefox_document_projects").delete().eq("document_id", documentId);
85321
- if (projectIds.length > 0) {
85322
- const rows = projectIds.map((pid) => ({
85323
- document_id: documentId,
85324
- project_id: pid
85325
- }));
85326
- await ctx.supabase.from("cerefox_document_projects").insert(rows);
85327
- }
85328
- }
85329
85506
  async function getCurrentDoc(ctx, documentId) {
85330
85507
  const { data } = await ctx.supabase.from("cerefox_documents").select("*").eq("id", documentId).maybeSingle();
85331
85508
  return data ?? null;
@@ -85378,7 +85555,7 @@ function registerDocumentWriteRoutes(app, ctx) {
85378
85555
  title: title || doc2.title,
85379
85556
  source: "manual",
85380
85557
  projectIds: Array.isArray(body.project_ids) ? body.project_ids : undefined,
85381
- metadata: Object.keys(metadata).length > 0 ? metadata : undefined,
85558
+ metadata: body.metadata != null ? metadata : undefined,
85382
85559
  author: "web-ui",
85383
85560
  authorType: "user",
85384
85561
  expectedContentHash: typeof body.expected_content_hash === "string" ? body.expected_content_hash : null
@@ -85419,33 +85596,28 @@ function registerDocumentWriteRoutes(app, ctx) {
85419
85596
  return c2.json({ success: false, error: msg }, 500);
85420
85597
  }
85421
85598
  }
85422
- const updates = {};
85423
- if (title && title !== doc2.title) {
85424
- updates.title = title;
85425
- }
85426
- if (Object.keys(metadata).length > 0) {
85427
- updates.metadata = metadata;
85428
- }
85429
- if (Object.keys(updates).length > 0) {
85430
- updates.updated_at = new Date().toISOString();
85431
- const { error: error3 } = await ctx.supabase.from("cerefox_documents").update(updates).eq("id", documentId);
85432
- if (error3)
85433
- return c2.json({ success: false, error: error3.message }, 500);
85434
- }
85435
- const projectsTouched = Array.isArray(body.project_ids);
85436
- if (projectsTouched) {
85437
- await assignDocumentProjects(ctx, documentId, projectIds);
85438
- }
85439
- const anythingChanged = "title" in updates || "metadata" in updates || projectsTouched;
85440
- if (anythingChanged) {
85441
- await createAuditEntry(ctx, {
85442
- operation: "update-metadata",
85443
- author: "web-ui",
85599
+ try {
85600
+ const facets = await updateDocumentFacets(ctx.supabase, {
85444
85601
  documentId,
85445
- description: `Updated via web UI (title=${"title" in updates}, metadata=${"metadata" in updates}, projects=${projectsTouched})`
85602
+ title: body.title !== undefined && title !== doc2.title ? title : undefined,
85603
+ metadata: body.metadata != null ? metadata : undefined,
85604
+ projectIds: Array.isArray(body.project_ids) ? projectIds : undefined,
85605
+ author: "web-ui",
85606
+ authorType: "user",
85607
+ accessPath: "webapp"
85446
85608
  });
85609
+ return c2.json({ success: true, reindexed: false, ...facets });
85610
+ } catch (err) {
85611
+ const cause = err instanceof FacetUpdateError ? err.cause : err;
85612
+ const msg = err instanceof Error ? err.message : String(err);
85613
+ if (cause instanceof FacetNotFoundError) {
85614
+ return c2.json({ success: false, error: msg, detail: msg }, 404);
85615
+ }
85616
+ if (cause instanceof FacetValidationError) {
85617
+ return c2.json({ success: false, error: msg, detail: msg }, 400);
85618
+ }
85619
+ return c2.json({ success: false, error: msg, detail: msg }, 500);
85447
85620
  }
85448
- return c2.json({ success: true, reindexed: false });
85449
85621
  });
85450
85622
  app.delete("/api/v1/documents/:document_id", async (c2) => {
85451
85623
  const documentId = c2.req.param("document_id");