@cerefox/memory 1.1.0-beta.5 → 1.1.0-beta.7
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 +1 -1
- package/AGENT_QUICK_REFERENCE.md +13 -8
- package/dist/bin/cerefox.js +66 -33
- package/dist/frontend/assets/{index-Co28kX04.js → index-DD7DDGGU.js} +28 -28
- package/dist/frontend/assets/index-DD7DDGGU.js.map +1 -0
- package/dist/frontend/index.html +1 -1
- package/dist/server-assets/_shared/ef-meta/index.ts +2 -2
- package/dist/server-assets/_shared/mcp-tools/_utils.ts +35 -38
- package/dist/server-assets/_shared/mcp-tools/get-help-content.ts +3 -3
- package/dist/server-assets/db/migrations/0015_conflict_errcode_pt409.sql +51 -0
- package/dist/server-assets/db/migrations/0016_retention_config.sql +69 -0
- package/dist/server-assets/db/rpcs.sql +98 -9
- package/dist/server-assets/db/schema.sql +1 -1
- package/docs/guides/cli.md +40 -0
- package/docs/guides/configuration.md +6 -6
- package/docs/guides/upgrading.md +13 -0
- package/package.json +1 -1
- package/dist/frontend/assets/index-Co28kX04.js.map +0 -1
package/dist/frontend/index.html
CHANGED
|
@@ -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-
|
|
18
|
+
<script type="module" crossorigin src="/app/assets/index-DD7DDGGU.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.
|
|
21
|
+
export const EF_VERSION = "1.1.0-beta.7";
|
|
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.2";
|
|
|
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.
|
|
31
|
+
export const EF_LAST_CHANGED = "1.1.0-beta.7";
|
|
32
32
|
|
|
33
33
|
/**
|
|
34
34
|
* The 8 peer EFs the cerefox-mcp aggregator probes (excludes cerefox-mcp
|
|
@@ -86,33 +86,7 @@ function readUnitInterval(name: string): number | undefined {
|
|
|
86
86
|
export const DEFAULT_SEARCH_ALPHA = 0.7;
|
|
87
87
|
|
|
88
88
|
export function getSearchAlpha(): number {
|
|
89
|
-
return
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* The retrieval tunables a client should actually SEND to the RPCs (#133).
|
|
94
|
-
*
|
|
95
|
-
* Returns undefined when the operator has expressed no preference, so the
|
|
96
|
-
* parameter is omitted and the server resolves it: `cerefox_config` first,
|
|
97
|
-
* then the built-in default. That is what lets one `cerefox config set` govern
|
|
98
|
-
* every access path. When a value IS configured here it wins, preserving the
|
|
99
|
-
* chain: per-call argument > client env > deployment config > built-in.
|
|
100
|
-
*
|
|
101
|
-
* `min_search_score` has one subtlety: the local embedder needs a higher floor
|
|
102
|
-
* (nomic scores unrelated text ~0.4–0.55), so an explicitly local embedder
|
|
103
|
-
* counts as "configured" even when CEREFOX_MIN_SEARCH_SCORE is unset —
|
|
104
|
-
* otherwise omitting the parameter would silently apply the OpenAI-calibrated
|
|
105
|
-
* default to a local deployment.
|
|
106
|
-
*/
|
|
107
|
-
export function getConfiguredMinSearchScore(): number | undefined {
|
|
108
|
-
const explicit = readUnitInterval("CEREFOX_MIN_SEARCH_SCORE");
|
|
109
|
-
if (explicit !== undefined) return explicit;
|
|
110
|
-
if (readEnv("CEREFOX_EMBEDDER") === "local") return DEFAULT_MIN_SEARCH_SCORE_LOCAL;
|
|
111
|
-
return undefined;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
export function getConfiguredSearchAlpha(): number | undefined {
|
|
115
|
-
return readUnitInterval("CEREFOX_SEARCH_ALPHA");
|
|
89
|
+
return DEFAULT_SEARCH_ALPHA;
|
|
116
90
|
}
|
|
117
91
|
|
|
118
92
|
/**
|
|
@@ -127,24 +101,47 @@ export function getConfiguredSearchAlpha(): number | undefined {
|
|
|
127
101
|
* EF path doesn't use the host `.env` anyway).
|
|
128
102
|
*/
|
|
129
103
|
export function getMinSearchScore(): number {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
: DEFAULT_MIN_SEARCH_SCORE;
|
|
134
|
-
return readUnitInterval("CEREFOX_MIN_SEARCH_SCORE") ?? fallback;
|
|
104
|
+
return readEnv("CEREFOX_EMBEDDER") === "local"
|
|
105
|
+
? DEFAULT_MIN_SEARCH_SCORE_LOCAL
|
|
106
|
+
: DEFAULT_MIN_SEARCH_SCORE;
|
|
135
107
|
}
|
|
136
108
|
|
|
137
109
|
/**
|
|
138
|
-
*
|
|
139
|
-
*
|
|
140
|
-
*
|
|
141
|
-
*
|
|
142
|
-
*
|
|
110
|
+
* Retrieval tuning is server-side state, not a per-machine preference.
|
|
111
|
+
*
|
|
112
|
+
* These used to read CEREFOX_MIN_SEARCH_SCORE / CEREFOX_SEARCH_ALPHA /
|
|
113
|
+
* CEREFOX_MIN_TERM_COVERAGE so a client could override the store's setting.
|
|
114
|
+
* That was the wrong model: the right similarity floor depends on which
|
|
115
|
+
* embedder produced the vectors, and the embedder is a property of the STORE —
|
|
116
|
+
* every client querying one database must use the same one (`doctor` enforces
|
|
117
|
+
* exactly that). So there is no case where two clients should legitimately
|
|
118
|
+
* disagree, and an override only creates a way for search to behave differently
|
|
119
|
+
* depending on who asked.
|
|
120
|
+
*
|
|
121
|
+
* All three now return undefined: the parameter is omitted and the RPC resolves
|
|
122
|
+
* `cerefox_config`, then the built-in default. One `cerefox config set` — or the
|
|
123
|
+
* Settings page — governs every access path.
|
|
124
|
+
*
|
|
125
|
+
* Cerefox Local still needs its higher floor for the nomic embedder; it seeds
|
|
126
|
+
* `min_search_score` into its own `cerefox_config` at container init rather than
|
|
127
|
+
* carrying it in the environment.
|
|
128
|
+
*
|
|
129
|
+
* A per-call argument (`--min-score`, the MCP `min_score` param) still wins, as
|
|
130
|
+
* it always did. `cerefox doctor` reports the retired variables if still set.
|
|
143
131
|
*/
|
|
132
|
+
export function getConfiguredMinSearchScore(): number | undefined {
|
|
133
|
+
return undefined;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function getConfiguredSearchAlpha(): number | undefined {
|
|
137
|
+
return undefined;
|
|
138
|
+
}
|
|
139
|
+
|
|
144
140
|
export function getMinTermCoverage(): number | undefined {
|
|
145
|
-
return
|
|
141
|
+
return undefined;
|
|
146
142
|
}
|
|
147
143
|
|
|
144
|
+
|
|
148
145
|
export function applyByteBudget(
|
|
149
146
|
rows: unknown[],
|
|
150
147
|
maxBytes: number,
|
|
@@ -11,16 +11,16 @@
|
|
|
11
11
|
* docs/specs/polish-and-distribution-design.md §10d.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
-
export const HELP_FULL = "# Cerefox Knowledge Base -- Agent Quick Reference\n\nCerefox is a persistent, shared knowledge base. You have **14 MCP tools** (13 of them have CLI equivalents — `cerefox_get_help` is MCP-only). For the full guide, search Cerefox for \"How AI Agents Use Cerefox\" or call `cerefox_get_help` to retrieve this content over MCP.\n\n## Tools\n\n| Tool | Purpose | Key params |\n|------|---------|------------|\n| `cerefox_search` | Find documents (hybrid FTS + semantic) | `query` (required), `project_name`, `metadata_filter`, `requestor` |\n| `cerefox_ingest` | Save or update a document | `title`, `content` (required), `document_id` (update by ID), `expected_content_hash` (**required on content updates** — see rule 9), `last_write_wins`, `update_if_exists`, `project_name` (single, non-destructive add on update), `project_names` (list, destructive replace on update), `metadata` (omit on update to keep existing tags; `{}` clears), `author` |\n| `cerefox_get_document` | Get full document by ID (header includes `content_hash` — the update token) | `document_id` (required) |\n| `cerefox_list_versions` | Version history of a document | `document_id` (required) |\n| `cerefox_set_relation` | Link two documents (`source --rel_type--> target`) | `source_id`, `target_id`, `rel_type` (required), `metadata`, `author` |\n| `cerefox_delete_relation` | Remove a relation | `source_id`, `target_id`, `rel_type` |\n| `cerefox_get_relations` | All relations touching a document, both directions | `document_id` |\n| `cerefox_get_neighbors` | Walk the graph along ONE relation type | `document_id`, `rel_type` (required), `depth`, `from_time`, `to_time`, `limit` |\n| `cerefox_metadata_search` | Find or list docs by metadata, project, or time (no text query) | `metadata_filter`, `project_name` (list a project's docs), `updated_since`, `include_content` — **at least one** of metadata_filter/project_name/updated_since/created_since |\n| `cerefox_list_metadata_keys` | Discover available metadata keys | (none required) |\n| `cerefox_list_projects` | List all projects | (none required) |\n| `cerefox_set_document_projects` | Set doc's project memberships to exactly the given list (destructive replace; metadata-only, no content change) | `document_id`, `project_names` (required) |\n| `cerefox_get_audit_log` | Query write operation history | `document_id`, `author`, `operation`, `since` |\n| `cerefox_get_help` | Retrieve Cerefox conventions (this reference) over MCP. **Call this whenever uncertain.** | `topic` (optional, case-insensitive H2 substring match) |\n\n## Essential Rules\n\n1. **Search before ingesting** -- check if the document exists first.\n2. **Prefer ID-based updates** -- pass `document_id` from search results for deterministic updates. Falls back to title-matching with `update_if_exists: true`.\n3. **Set `author`/`requestor`** to your name on every call (e.g., \"Claude Code\", \"archiver\"). On MCP, pass as parameters. On CLI, pass `--author`/`--author-type`/`--requestor` flags, or rely on `CEREFOX_AUTHOR_NAME`/`CEREFOX_AUTHOR_TYPE`/`CEREFOX_REQUESTOR_NAME` env vars set in the user's `.env`.\n4. **Use `document_id` from search results** `[id: uuid]` for get_document and list_versions.\n5. **Add metadata** -- at minimum `type` (\"decision-log\", \"research\", \"design-doc\") and `status` (\"active\", \"draft\").\n6. **Write structured Markdown** with H1/H2/H3 headings for good chunking and search.\n7. **Deletes are soft (recoverable); purge is web-UI-only.** If you decide to delete, surface it to the user (`I soft-deleted X — recoverable from the Cerefox web UI trash`). You cannot un-do your own delete from agent code by design.\n8. **Cross-doc links inside content**: **always use `[Text](document-uuid)`.** UUIDs are the only fully reliable link form — stable across title changes, never ambiguous, no encoding gotchas. Every `cerefox_search` result shows `[id: <uuid>]` after the title; grab it and use it. Title-based linking (`[Text](<Title With Spaces>)`) is fragile (breaks on colons, parens, ampersands, brackets — silently navigates to wrong page) — **don't write title-based links**; do an extra search to get the UUID instead. Repo-path forms (`[Text](docs/path.md)`) exist for repo-ingested files; don't construct manually. See `AGENT_GUIDE.md → Writing linkable content` for the full rule.\n9. **Concurrency: content updates require `expected_content_hash`.** Pass the `content_hash` you read (shown by `cerefox_get_document`, `cerefox_search`, and `cerefox_metadata_search`) when updating a document. If it's stale you get a **conflict** — re-read the document, merge your changes into the latest content, retry with the new hash. **Never resolve a conflict by overwriting blindly** — the current content includes another writer's work. `last_write_wins: true` skips the check; use it ONLY when an external source of truth makes conflicts meaningless (file re-sync), never to silence a conflict.\n10. **Search: prefer a few distinctive terms; heed `below confidence`.** When nothing clears the relevance threshold, `cerefox_search` returns the closest candidates prefixed with a `below confidence` warning instead of an empty set — that flag means **weak signal, not absent knowledge**: check the candidates' scores and titles before concluding the KB lacks the content. A truly empty response means nothing even weakly related exists.\n11. **Relations express how documents relate; lifecycle tells you if knowledge is still good.** Use `cerefox_set_relation` when one document supersedes, contradicts, references, or continues another. `supersedes` marks the target **superseded**; `contradicts` marks **both** stale; `related_to`/`duplicates`/`contradicts` are symmetric (both directions written). Any other type string is accepted without special behaviour. When a search result or `cerefox_get_relations` shows a neighbour marked `[superseded]` or `[stale]`, say so rather than presenting it as current.\n12. **Project memberships — non-destructive by default**: on `cerefox_ingest` updates, **`project_name` (singular) is a non-destructive add** (ensures membership, preserves others). Use **`project_names` (list)** when you want to set the doc's full project set in one call (destructive replace). For metadata-only project changes without writing content, use **`cerefox_set_document_projects(document_id, project_names)`** — that tool is the destructive-replace contract made explicit. Never call `cerefox_set_document_projects` with a single name when you mean \"add\" — that would REMOVE the doc from all other projects. When in doubt, use `cerefox_ingest` with singular `project_name`.\n\n## Update Workflow (ID-based -- preferred)\n\n```\nsearch(\"topic\") -> find doc [id: abc123] -> get_document(abc123) -> note its content_hash -> modify ->\ningest(title=\"Same Title\", content=\"...\", document_id=\"abc123\",\n expected_content_hash=\"<the hash you read>\", author=\"my-agent\")\n```\n\nOn a **conflict** error: get_document again (fresh content + fresh hash) -> merge your changes -> retry with the new hash.\n\n## Update Workflow (title-based -- fallback)\n\n```\nsearch(\"topic\") -> find doc (note its hash) -> modify ->\ningest(title=\"Same Title\", content=\"...\", update_if_exists=true,\n expected_content_hash=\"<the hash you read>\", author=\"my-agent\")\n```\n\n## Catch-Up Workflow\n\n```\nmetadata_search(metadata_filter={\"type\": \"decision-log\"}, updated_since=\"2026-03-28T00:00:00Z\")\n```\n\n## CLI fallback (when MCP is unavailable)\n\nIf `cerefox_search` is not in your tool list, your user has likely installed the Cerefox CLI. The canonical invocation is plain **`cerefox <subcommand>`** (the TypeScript CLI, installed via `npm install -g @cerefox/memory`). It uses a resource-verb shape (`cerefox document get`, `cerefox project list`, …).\n\nSame operations, same conventions. Full reference: [`docs/guides/cli.md`](docs/guides/cli.md). CLI flag names match MCP parameter names exactly (e.g. `metadata_filter` ↔ `--metadata-filter`); common flags also have single-letter short forms (`-f`, `-p`, `-c`, `-m`, `-u`, `-a`, `-r`). Use the canonical long name (what `--help` shows) or its short form — there are no long-form aliases like `--filter` or `--count`.\n\n| MCP tool | CLI |\n|---|---|\n| `cerefox_search` | `cerefox search \"<q>\" --requestor \"<your-name>\"` |\n| `cerefox_ingest` (paste) | `printf '...' \\| cerefox document ingest --paste --title \"<t>\" --author \"<your-name>\" --author-type agent` |\n| `cerefox_ingest` (update by ID) | `printf '...' \\| cerefox document ingest --paste --title \"<t>\" --document-id \"<uuid>\" --expected-content-hash \"<hash>\" --author \"<your-name>\" --author-type agent` |\n| `cerefox_get_document` | `cerefox document get <id> --version-id <vid> --requestor \"<your-name>\"` |\n| `cerefox_list_versions` | `cerefox document version list <id> --requestor \"<your-name>\"` |\n| `cerefox_list_projects` | `cerefox project list --requestor \"<your-name>\"` |\n| `cerefox_list_metadata_keys` | `cerefox metadata keys` |\n| `cerefox_set_relation` | Link two documents (`source --rel_type--> target`) | `source_id`, `target_id`, `rel_type` (required), `metadata`, `author` |\n| `cerefox_delete_relation` | Remove a relation | `source_id`, `target_id`, `rel_type` |\n| `cerefox_get_relations` | All relations touching a document, both directions | `document_id` |\n| `cerefox_get_neighbors` | Walk the graph along ONE relation type | `document_id`, `rel_type` (required), `depth`, `from_time`, `to_time`, `limit` |\n| `cerefox_metadata_search` | `cerefox metadata search --metadata-filter '<json>' --requestor \"<your-name>\"` (list a project: `cerefox document list --project <name>`) |\n| `cerefox_set_document_projects` | `cerefox document set-projects <id> <name...> --author \"<your-name>\" --author-type agent` (or `--clear` to remove all) |\n| `cerefox_get_audit_log` | `cerefox audit list --requestor \"<your-name>\"` (add `--json` for scripted access) |\n| `cerefox_get_help` | `cerefox guides show agent-quick-reference` (or `cerefox guides list` for the full bundled-docs index) |\n\n**Set identity on every call**, exactly as you would on MCP:\n- Writes (`document ingest`, `document ingest-dir`): `--author \"<your-name>\" --author-type agent`\n- Reads: `--requestor \"<your-name>\"`\n\nOr have your user set `CEREFOX_AUTHOR_NAME` / `CEREFOX_AUTHOR_TYPE` / `CEREFOX_REQUESTOR_NAME` in their `.env` to apply defaults once.\n";
|
|
14
|
+
export const HELP_FULL = "# Cerefox Knowledge Base -- Agent Quick Reference\n\nCerefox is a persistent, shared knowledge base. You have **14 MCP tools** (13 of them have CLI equivalents — `cerefox_get_help` is MCP-only). For the full guide, search Cerefox for \"How AI Agents Use Cerefox\" or call `cerefox_get_help` to retrieve this content over MCP.\n\n## Tools\n\n| Tool | Purpose | Key params |\n|------|---------|------------|\n| `cerefox_search` | Find documents (hybrid FTS + semantic) | `query` (required), `project_name`, `metadata_filter`, `requestor` |\n| `cerefox_ingest` | Save or update a document | `title`, `content` (required), `document_id` (update by ID), `expected_content_hash` (**required on content updates** — see rule 9), `last_write_wins`, `update_if_exists`, `project_name` (single, non-destructive add on update), `project_names` (list, destructive replace on update), `metadata` (omit on update to keep existing tags; `{}` clears), `author` |\n| `cerefox_get_document` | Get full document by ID (header includes `content_hash` — the update token) | `document_id` (required) |\n| `cerefox_list_versions` | Version history of a document | `document_id` (required) |\n| `cerefox_set_relation` ⚑ | Link two documents (`source --rel_type--> target`) | `source_id`, `target_id`, `rel_type` (required), `metadata`, `author` |\n| `cerefox_delete_relation` ⚑ | Remove a relation | `source_id`, `target_id`, `rel_type` |\n| `cerefox_get_relations` ⚑ | All relations touching a document, both directions | `document_id` |\n| `cerefox_get_neighbors` ⚑ | Walk the graph along ONE relation type | `document_id`, `rel_type` (required), `depth`, `from_time`, `to_time`, `limit` |\n| `cerefox_metadata_search` | Find or list docs by metadata, project, or time (no text query) | `metadata_filter`, `project_name` (list a project's docs), `updated_since`, `include_content` — **at least one** of metadata_filter/project_name/updated_since/created_since |\n| `cerefox_list_metadata_keys` | Discover available metadata keys | (none required) |\n| `cerefox_list_projects` | List all projects | (none required) |\n| `cerefox_set_document_projects` | Set doc's project memberships to exactly the given list (destructive replace; metadata-only, no content change) | `document_id`, `project_names` (required) |\n| `cerefox_get_audit_log` | Query write operation history | `document_id`, `author`, `operation`, `since` |\n| `cerefox_get_help` | Retrieve Cerefox conventions (this reference) over MCP. **Call this whenever uncertain.** | `topic` (optional, case-insensitive H2 substring match) |\n\n⚑ **Opt-in — usually absent.** The four relation tools are hidden unless the\noperator enables them (`relations_enabled`). **Trust your own tool list**: if\nthey are not in it, the feature is switched off for this deployment. That is\nnormal, not an error, and not something to work around.\n\n## Essential Rules\n\n1. **Search before ingesting** -- check if the document exists first.\n2. **Prefer ID-based updates** -- pass `document_id` from search results for deterministic updates. Falls back to title-matching with `update_if_exists: true`.\n3. **Set `author`/`requestor`** to your name on every call (e.g., \"Claude Code\", \"archiver\"). On MCP, pass as parameters. On CLI, pass `--author`/`--author-type`/`--requestor` flags, or rely on `CEREFOX_AUTHOR_NAME`/`CEREFOX_AUTHOR_TYPE`/`CEREFOX_REQUESTOR_NAME` env vars set in the user's `.env`.\n4. **Use `document_id` from search results** `[id: uuid]` for get_document and list_versions.\n5. **Add metadata** -- at minimum `type` (\"decision-log\", \"research\", \"design-doc\") and `status` (\"active\", \"draft\").\n6. **Write structured Markdown** with H1/H2/H3 headings for good chunking and search.\n7. **Deletes are soft (recoverable); purge is web-UI-only.** If you decide to delete, surface it to the user (`I soft-deleted X — recoverable from the Cerefox web UI trash`). You cannot un-do your own delete from agent code by design.\n8. **Cross-doc links inside content**: **always use `[Text](document-uuid)`.** UUIDs are the only fully reliable link form — stable across title changes, never ambiguous, no encoding gotchas. Every `cerefox_search` result shows `[id: <uuid>]` after the title; grab it and use it. Title-based linking (`[Text](<Title With Spaces>)`) is fragile (breaks on colons, parens, ampersands, brackets — silently navigates to wrong page) — **don't write title-based links**; do an extra search to get the UUID instead. Repo-path forms (`[Text](docs/path.md)`) exist for repo-ingested files; don't construct manually. See `AGENT_GUIDE.md → Writing linkable content` for the full rule.\n9. **Concurrency: content updates require `expected_content_hash`.** Pass the `content_hash` you read (shown by `cerefox_get_document`, `cerefox_search`, and `cerefox_metadata_search`) when updating a document. If it's stale you get a **conflict** — re-read the document, merge your changes into the latest content, retry with the new hash. **Never resolve a conflict by overwriting blindly** — the current content includes another writer's work. `last_write_wins: true` skips the check; use it ONLY when an external source of truth makes conflicts meaningless (file re-sync), never to silence a conflict.\n10. **Search: prefer a few distinctive terms; heed `below confidence`.** When nothing clears the relevance threshold, `cerefox_search` returns the closest candidates prefixed with a `below confidence` warning instead of an empty set — that flag means **weak signal, not absent knowledge**: check the candidates' scores and titles before concluding the KB lacks the content. A truly empty response means nothing even weakly related exists.\n11. **Relations express how documents relate; lifecycle tells you if knowledge is still good.** Use `cerefox_set_relation` when one document supersedes, contradicts, references, or continues another. `supersedes` marks the target **superseded**; `contradicts` marks **both** stale; `related_to`/`duplicates`/`contradicts` are symmetric (both directions written). Any other type string is accepted without special behaviour. When a search result or `cerefox_get_relations` shows a neighbour marked `[superseded]` or `[stale]`, say so rather than presenting it as current.\n12. **Project memberships — non-destructive by default**: on `cerefox_ingest` updates, **`project_name` (singular) is a non-destructive add** (ensures membership, preserves others). Use **`project_names` (list)** when you want to set the doc's full project set in one call (destructive replace). For metadata-only project changes without writing content, use **`cerefox_set_document_projects(document_id, project_names)`** — that tool is the destructive-replace contract made explicit. Never call `cerefox_set_document_projects` with a single name when you mean \"add\" — that would REMOVE the doc from all other projects. When in doubt, use `cerefox_ingest` with singular `project_name`.\n\n## Update Workflow (ID-based -- preferred)\n\n```\nsearch(\"topic\") -> find doc [id: abc123] -> get_document(abc123) -> note its content_hash -> modify ->\ningest(title=\"Same Title\", content=\"...\", document_id=\"abc123\",\n expected_content_hash=\"<the hash you read>\", author=\"my-agent\")\n```\n\nOn a **conflict** error: get_document again (fresh content + fresh hash) -> merge your changes -> retry with the new hash.\n\n## Update Workflow (title-based -- fallback)\n\n```\nsearch(\"topic\") -> find doc (note its hash) -> modify ->\ningest(title=\"Same Title\", content=\"...\", update_if_exists=true,\n expected_content_hash=\"<the hash you read>\", author=\"my-agent\")\n```\n\n## Catch-Up Workflow\n\n```\nmetadata_search(metadata_filter={\"type\": \"decision-log\"}, updated_since=\"2026-03-28T00:00:00Z\")\n```\n\n## CLI fallback (when MCP is unavailable)\n\nIf `cerefox_search` is not in your tool list, your user has likely installed the Cerefox CLI. The canonical invocation is plain **`cerefox <subcommand>`** (the TypeScript CLI, installed via `npm install -g @cerefox/memory`). It uses a resource-verb shape (`cerefox document get`, `cerefox project list`, …).\n\nSame operations, same conventions. Full reference: [`docs/guides/cli.md`](docs/guides/cli.md). CLI flag names match MCP parameter names exactly (e.g. `metadata_filter` ↔ `--metadata-filter`); common flags also have single-letter short forms (`-f`, `-p`, `-c`, `-m`, `-u`, `-a`, `-r`). Use the canonical long name (what `--help` shows) or its short form — there are no long-form aliases like `--filter` or `--count`.\n\n| MCP tool | CLI |\n|---|---|\n| `cerefox_search` | `cerefox search \"<q>\" --requestor \"<your-name>\"` |\n| `cerefox_ingest` (paste) | `printf '...' \\| cerefox document ingest --paste --title \"<t>\" --author \"<your-name>\" --author-type agent` |\n| `cerefox_ingest` (update by ID) | `printf '...' \\| cerefox document ingest --paste --title \"<t>\" --document-id \"<uuid>\" --expected-content-hash \"<hash>\" --author \"<your-name>\" --author-type agent` |\n| `cerefox_get_document` | `cerefox document get <id> --version-id <vid> --requestor \"<your-name>\"` |\n| `cerefox_list_versions` | `cerefox document version list <id> --requestor \"<your-name>\"` |\n| `cerefox_list_projects` | `cerefox project list --requestor \"<your-name>\"` |\n| `cerefox_list_metadata_keys` | `cerefox metadata keys` |\n| `cerefox_set_relation` ⚑ | Link two documents (`source --rel_type--> target`) | `source_id`, `target_id`, `rel_type` (required), `metadata`, `author` |\n| `cerefox_delete_relation` ⚑ | Remove a relation | `source_id`, `target_id`, `rel_type` |\n| `cerefox_get_relations` ⚑ | All relations touching a document, both directions | `document_id` |\n| `cerefox_get_neighbors` ⚑ | Walk the graph along ONE relation type | `document_id`, `rel_type` (required), `depth`, `from_time`, `to_time`, `limit` |\n| `cerefox_metadata_search` | `cerefox metadata search --metadata-filter '<json>' --requestor \"<your-name>\"` (list a project: `cerefox document list --project <name>`) |\n| `cerefox_set_document_projects` | `cerefox document set-projects <id> <name...> --author \"<your-name>\" --author-type agent` (or `--clear` to remove all) |\n| `cerefox_get_audit_log` | `cerefox audit list --requestor \"<your-name>\"` (add `--json` for scripted access) |\n| `cerefox_get_help` | `cerefox guides show agent-quick-reference` (or `cerefox guides list` for the full bundled-docs index) |\n\n**Set identity on every call**, exactly as you would on MCP:\n- Writes (`document ingest`, `document ingest-dir`): `--author \"<your-name>\" --author-type agent`\n- Reads: `--requestor \"<your-name>\"`\n\nOr have your user set `CEREFOX_AUTHOR_NAME` / `CEREFOX_AUTHOR_TYPE` / `CEREFOX_REQUESTOR_NAME` in their `.env` to apply defaults once.\n";
|
|
15
15
|
|
|
16
16
|
/** Sections keyed by their H2 heading text (lower-cased for matching). */
|
|
17
17
|
export const HELP_SECTIONS: Record<string, string> = {
|
|
18
|
-
"Tools": "## Tools\n\n| Tool | Purpose | Key params |\n|------|---------|------------|\n| `cerefox_search` | Find documents (hybrid FTS + semantic) | `query` (required), `project_name`, `metadata_filter`, `requestor` |\n| `cerefox_ingest` | Save or update a document | `title`, `content` (required), `document_id` (update by ID), `expected_content_hash` (**required on content updates** — see rule 9), `last_write_wins`, `update_if_exists`, `project_name` (single, non-destructive add on update), `project_names` (list, destructive replace on update), `metadata` (omit on update to keep existing tags; `{}` clears), `author` |\n| `cerefox_get_document` | Get full document by ID (header includes `content_hash` — the update token) | `document_id` (required) |\n| `cerefox_list_versions` | Version history of a document | `document_id` (required) |\n| `cerefox_set_relation` | Link two documents (`source --rel_type--> target`) | `source_id`, `target_id`, `rel_type` (required), `metadata`, `author` |\n| `cerefox_delete_relation` | Remove a relation | `source_id`, `target_id`, `rel_type` |\n| `cerefox_get_relations` | All relations touching a document, both directions | `document_id` |\n| `cerefox_get_neighbors` | Walk the graph along ONE relation type | `document_id`, `rel_type` (required), `depth`, `from_time`, `to_time`, `limit` |\n| `cerefox_metadata_search` | Find or list docs by metadata, project, or time (no text query) | `metadata_filter`, `project_name` (list a project's docs), `updated_since`, `include_content` — **at least one** of metadata_filter/project_name/updated_since/created_since |\n| `cerefox_list_metadata_keys` | Discover available metadata keys | (none required) |\n| `cerefox_list_projects` | List all projects | (none required) |\n| `cerefox_set_document_projects` | Set doc's project memberships to exactly the given list (destructive replace; metadata-only, no content change) | `document_id`, `project_names` (required) |\n| `cerefox_get_audit_log` | Query write operation history | `document_id`, `author`, `operation`, `since` |\n| `cerefox_get_help` | Retrieve Cerefox conventions (this reference) over MCP. **Call this whenever uncertain.** | `topic` (optional, case-insensitive H2 substring match)
|
|
18
|
+
"Tools": "## Tools\n\n| Tool | Purpose | Key params |\n|------|---------|------------|\n| `cerefox_search` | Find documents (hybrid FTS + semantic) | `query` (required), `project_name`, `metadata_filter`, `requestor` |\n| `cerefox_ingest` | Save or update a document | `title`, `content` (required), `document_id` (update by ID), `expected_content_hash` (**required on content updates** — see rule 9), `last_write_wins`, `update_if_exists`, `project_name` (single, non-destructive add on update), `project_names` (list, destructive replace on update), `metadata` (omit on update to keep existing tags; `{}` clears), `author` |\n| `cerefox_get_document` | Get full document by ID (header includes `content_hash` — the update token) | `document_id` (required) |\n| `cerefox_list_versions` | Version history of a document | `document_id` (required) |\n| `cerefox_set_relation` ⚑ | Link two documents (`source --rel_type--> target`) | `source_id`, `target_id`, `rel_type` (required), `metadata`, `author` |\n| `cerefox_delete_relation` ⚑ | Remove a relation | `source_id`, `target_id`, `rel_type` |\n| `cerefox_get_relations` ⚑ | All relations touching a document, both directions | `document_id` |\n| `cerefox_get_neighbors` ⚑ | Walk the graph along ONE relation type | `document_id`, `rel_type` (required), `depth`, `from_time`, `to_time`, `limit` |\n| `cerefox_metadata_search` | Find or list docs by metadata, project, or time (no text query) | `metadata_filter`, `project_name` (list a project's docs), `updated_since`, `include_content` — **at least one** of metadata_filter/project_name/updated_since/created_since |\n| `cerefox_list_metadata_keys` | Discover available metadata keys | (none required) |\n| `cerefox_list_projects` | List all projects | (none required) |\n| `cerefox_set_document_projects` | Set doc's project memberships to exactly the given list (destructive replace; metadata-only, no content change) | `document_id`, `project_names` (required) |\n| `cerefox_get_audit_log` | Query write operation history | `document_id`, `author`, `operation`, `since` |\n| `cerefox_get_help` | Retrieve Cerefox conventions (this reference) over MCP. **Call this whenever uncertain.** | `topic` (optional, case-insensitive H2 substring match) |\n\n⚑ **Opt-in — usually absent.** The four relation tools are hidden unless the\noperator enables them (`relations_enabled`). **Trust your own tool list**: if\nthey are not in it, the feature is switched off for this deployment. That is\nnormal, not an error, and not something to work around.",
|
|
19
19
|
"Essential Rules": "## Essential Rules\n\n1. **Search before ingesting** -- check if the document exists first.\n2. **Prefer ID-based updates** -- pass `document_id` from search results for deterministic updates. Falls back to title-matching with `update_if_exists: true`.\n3. **Set `author`/`requestor`** to your name on every call (e.g., \"Claude Code\", \"archiver\"). On MCP, pass as parameters. On CLI, pass `--author`/`--author-type`/`--requestor` flags, or rely on `CEREFOX_AUTHOR_NAME`/`CEREFOX_AUTHOR_TYPE`/`CEREFOX_REQUESTOR_NAME` env vars set in the user's `.env`.\n4. **Use `document_id` from search results** `[id: uuid]` for get_document and list_versions.\n5. **Add metadata** -- at minimum `type` (\"decision-log\", \"research\", \"design-doc\") and `status` (\"active\", \"draft\").\n6. **Write structured Markdown** with H1/H2/H3 headings for good chunking and search.\n7. **Deletes are soft (recoverable); purge is web-UI-only.** If you decide to delete, surface it to the user (`I soft-deleted X — recoverable from the Cerefox web UI trash`). You cannot un-do your own delete from agent code by design.\n8. **Cross-doc links inside content**: **always use `[Text](document-uuid)`.** UUIDs are the only fully reliable link form — stable across title changes, never ambiguous, no encoding gotchas. Every `cerefox_search` result shows `[id: <uuid>]` after the title; grab it and use it. Title-based linking (`[Text](<Title With Spaces>)`) is fragile (breaks on colons, parens, ampersands, brackets — silently navigates to wrong page) — **don't write title-based links**; do an extra search to get the UUID instead. Repo-path forms (`[Text](docs/path.md)`) exist for repo-ingested files; don't construct manually. See `AGENT_GUIDE.md → Writing linkable content` for the full rule.\n9. **Concurrency: content updates require `expected_content_hash`.** Pass the `content_hash` you read (shown by `cerefox_get_document`, `cerefox_search`, and `cerefox_metadata_search`) when updating a document. If it's stale you get a **conflict** — re-read the document, merge your changes into the latest content, retry with the new hash. **Never resolve a conflict by overwriting blindly** — the current content includes another writer's work. `last_write_wins: true` skips the check; use it ONLY when an external source of truth makes conflicts meaningless (file re-sync), never to silence a conflict.\n10. **Search: prefer a few distinctive terms; heed `below confidence`.** When nothing clears the relevance threshold, `cerefox_search` returns the closest candidates prefixed with a `below confidence` warning instead of an empty set — that flag means **weak signal, not absent knowledge**: check the candidates' scores and titles before concluding the KB lacks the content. A truly empty response means nothing even weakly related exists.\n11. **Relations express how documents relate; lifecycle tells you if knowledge is still good.** Use `cerefox_set_relation` when one document supersedes, contradicts, references, or continues another. `supersedes` marks the target **superseded**; `contradicts` marks **both** stale; `related_to`/`duplicates`/`contradicts` are symmetric (both directions written). Any other type string is accepted without special behaviour. When a search result or `cerefox_get_relations` shows a neighbour marked `[superseded]` or `[stale]`, say so rather than presenting it as current.\n12. **Project memberships — non-destructive by default**: on `cerefox_ingest` updates, **`project_name` (singular) is a non-destructive add** (ensures membership, preserves others). Use **`project_names` (list)** when you want to set the doc's full project set in one call (destructive replace). For metadata-only project changes without writing content, use **`cerefox_set_document_projects(document_id, project_names)`** — that tool is the destructive-replace contract made explicit. Never call `cerefox_set_document_projects` with a single name when you mean \"add\" — that would REMOVE the doc from all other projects. When in doubt, use `cerefox_ingest` with singular `project_name`.",
|
|
20
20
|
"Update Workflow (ID-based -- preferred)": "## Update Workflow (ID-based -- preferred)\n\n```\nsearch(\"topic\") -> find doc [id: abc123] -> get_document(abc123) -> note its content_hash -> modify ->\ningest(title=\"Same Title\", content=\"...\", document_id=\"abc123\",\n expected_content_hash=\"<the hash you read>\", author=\"my-agent\")\n```\n\nOn a **conflict** error: get_document again (fresh content + fresh hash) -> merge your changes -> retry with the new hash.",
|
|
21
21
|
"Update Workflow (title-based -- fallback)": "## Update Workflow (title-based -- fallback)\n\n```\nsearch(\"topic\") -> find doc (note its hash) -> modify ->\ningest(title=\"Same Title\", content=\"...\", update_if_exists=true,\n expected_content_hash=\"<the hash you read>\", author=\"my-agent\")\n```",
|
|
22
22
|
"Catch-Up Workflow": "## Catch-Up Workflow\n\n```\nmetadata_search(metadata_filter={\"type\": \"decision-log\"}, updated_since=\"2026-03-28T00:00:00Z\")\n```",
|
|
23
|
-
"CLI fallback (when MCP is unavailable)": "## CLI fallback (when MCP is unavailable)\n\nIf `cerefox_search` is not in your tool list, your user has likely installed the Cerefox CLI. The canonical invocation is plain **`cerefox <subcommand>`** (the TypeScript CLI, installed via `npm install -g @cerefox/memory`). It uses a resource-verb shape (`cerefox document get`, `cerefox project list`, …).\n\nSame operations, same conventions. Full reference: [`docs/guides/cli.md`](docs/guides/cli.md). CLI flag names match MCP parameter names exactly (e.g. `metadata_filter` ↔ `--metadata-filter`); common flags also have single-letter short forms (`-f`, `-p`, `-c`, `-m`, `-u`, `-a`, `-r`). Use the canonical long name (what `--help` shows) or its short form — there are no long-form aliases like `--filter` or `--count`.\n\n| MCP tool | CLI |\n|---|---|\n| `cerefox_search` | `cerefox search \"<q>\" --requestor \"<your-name>\"` |\n| `cerefox_ingest` (paste) | `printf '...' \\| cerefox document ingest --paste --title \"<t>\" --author \"<your-name>\" --author-type agent` |\n| `cerefox_ingest` (update by ID) | `printf '...' \\| cerefox document ingest --paste --title \"<t>\" --document-id \"<uuid>\" --expected-content-hash \"<hash>\" --author \"<your-name>\" --author-type agent` |\n| `cerefox_get_document` | `cerefox document get <id> --version-id <vid> --requestor \"<your-name>\"` |\n| `cerefox_list_versions` | `cerefox document version list <id> --requestor \"<your-name>\"` |\n| `cerefox_list_projects` | `cerefox project list --requestor \"<your-name>\"` |\n| `cerefox_list_metadata_keys` | `cerefox metadata keys` |\n| `cerefox_set_relation` | Link two documents (`source --rel_type--> target`) | `source_id`, `target_id`, `rel_type` (required), `metadata`, `author` |\n| `cerefox_delete_relation` | Remove a relation | `source_id`, `target_id`, `rel_type` |\n| `cerefox_get_relations` | All relations touching a document, both directions | `document_id` |\n| `cerefox_get_neighbors` | Walk the graph along ONE relation type | `document_id`, `rel_type` (required), `depth`, `from_time`, `to_time`, `limit` |\n| `cerefox_metadata_search` | `cerefox metadata search --metadata-filter '<json>' --requestor \"<your-name>\"` (list a project: `cerefox document list --project <name>`) |\n| `cerefox_set_document_projects` | `cerefox document set-projects <id> <name...> --author \"<your-name>\" --author-type agent` (or `--clear` to remove all) |\n| `cerefox_get_audit_log` | `cerefox audit list --requestor \"<your-name>\"` (add `--json` for scripted access) |\n| `cerefox_get_help` | `cerefox guides show agent-quick-reference` (or `cerefox guides list` for the full bundled-docs index) |\n\n**Set identity on every call**, exactly as you would on MCP:\n- Writes (`document ingest`, `document ingest-dir`): `--author \"<your-name>\" --author-type agent`\n- Reads: `--requestor \"<your-name>\"`\n\nOr have your user set `CEREFOX_AUTHOR_NAME` / `CEREFOX_AUTHOR_TYPE` / `CEREFOX_REQUESTOR_NAME` in their `.env` to apply defaults once.",
|
|
23
|
+
"CLI fallback (when MCP is unavailable)": "## CLI fallback (when MCP is unavailable)\n\nIf `cerefox_search` is not in your tool list, your user has likely installed the Cerefox CLI. The canonical invocation is plain **`cerefox <subcommand>`** (the TypeScript CLI, installed via `npm install -g @cerefox/memory`). It uses a resource-verb shape (`cerefox document get`, `cerefox project list`, …).\n\nSame operations, same conventions. Full reference: [`docs/guides/cli.md`](docs/guides/cli.md). CLI flag names match MCP parameter names exactly (e.g. `metadata_filter` ↔ `--metadata-filter`); common flags also have single-letter short forms (`-f`, `-p`, `-c`, `-m`, `-u`, `-a`, `-r`). Use the canonical long name (what `--help` shows) or its short form — there are no long-form aliases like `--filter` or `--count`.\n\n| MCP tool | CLI |\n|---|---|\n| `cerefox_search` | `cerefox search \"<q>\" --requestor \"<your-name>\"` |\n| `cerefox_ingest` (paste) | `printf '...' \\| cerefox document ingest --paste --title \"<t>\" --author \"<your-name>\" --author-type agent` |\n| `cerefox_ingest` (update by ID) | `printf '...' \\| cerefox document ingest --paste --title \"<t>\" --document-id \"<uuid>\" --expected-content-hash \"<hash>\" --author \"<your-name>\" --author-type agent` |\n| `cerefox_get_document` | `cerefox document get <id> --version-id <vid> --requestor \"<your-name>\"` |\n| `cerefox_list_versions` | `cerefox document version list <id> --requestor \"<your-name>\"` |\n| `cerefox_list_projects` | `cerefox project list --requestor \"<your-name>\"` |\n| `cerefox_list_metadata_keys` | `cerefox metadata keys` |\n| `cerefox_set_relation` ⚑ | Link two documents (`source --rel_type--> target`) | `source_id`, `target_id`, `rel_type` (required), `metadata`, `author` |\n| `cerefox_delete_relation` ⚑ | Remove a relation | `source_id`, `target_id`, `rel_type` |\n| `cerefox_get_relations` ⚑ | All relations touching a document, both directions | `document_id` |\n| `cerefox_get_neighbors` ⚑ | Walk the graph along ONE relation type | `document_id`, `rel_type` (required), `depth`, `from_time`, `to_time`, `limit` |\n| `cerefox_metadata_search` | `cerefox metadata search --metadata-filter '<json>' --requestor \"<your-name>\"` (list a project: `cerefox document list --project <name>`) |\n| `cerefox_set_document_projects` | `cerefox document set-projects <id> <name...> --author \"<your-name>\" --author-type agent` (or `--clear` to remove all) |\n| `cerefox_get_audit_log` | `cerefox audit list --requestor \"<your-name>\"` (add `--json` for scripted access) |\n| `cerefox_get_help` | `cerefox guides show agent-quick-reference` (or `cerefox guides list` for the full bundled-docs index) |\n\n**Set identity on every call**, exactly as you would on MCP:\n- Writes (`document ingest`, `document ingest-dir`): `--author \"<your-name>\" --author-type agent`\n- Reads: `--requestor \"<your-name>\"`\n\nOr have your user set `CEREFOX_AUTHOR_NAME` / `CEREFOX_AUTHOR_TYPE` / `CEREFOX_REQUESTOR_NAME` in their `.env` to apply defaults once.",
|
|
24
24
|
};
|
|
25
25
|
|
|
26
26
|
export const HELP_SECTION_HEADINGS: string[] = ["Tools", "Essential Rules", "Update Workflow (ID-based -- preferred)", "Update Workflow (title-based -- fallback)", "Catch-Up Workflow", "CLI fallback (when MCP is unavailable)"];
|
|
@@ -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 $$;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
-- 0016_retention_config.sql — version retention becomes a property of the
|
|
2
|
+
-- store, not of whichever client happens to write.
|
|
3
|
+
--
|
|
4
|
+
-- `cerefox_snapshot_version` took `p_retention_hours` / `p_cleanup_enabled` as
|
|
5
|
+
-- parameters, and every client filled them from its own environment
|
|
6
|
+
-- (`CEREFOX_VERSION_RETENTION_HOURS`, `CEREFOX_VERSION_CLEANUP_ENABLED`). The
|
|
7
|
+
-- surviving version history therefore depended on **which client wrote last**:
|
|
8
|
+
-- an operator could set "keep everything" on their machine and still lose
|
|
9
|
+
-- versions the moment an agent running defaults saved a document. Retention
|
|
10
|
+
-- describes the data, so it belongs to the data.
|
|
11
|
+
--
|
|
12
|
+
-- Both parameters now default to NULL, meaning "read the store's policy":
|
|
13
|
+
--
|
|
14
|
+
-- version_retention_hours (default 48)
|
|
15
|
+
-- version_cleanup_enabled (default true)
|
|
16
|
+
--
|
|
17
|
+
-- Passing an explicit value still overrides, for deliberate one-off admin
|
|
18
|
+
-- operations — but no caller supplies one by accident any more.
|
|
19
|
+
--
|
|
20
|
+
-- Adds `cerefox_config_int` / `cerefox_config_bool`, the integer and boolean
|
|
21
|
+
-- companions to the existing `cerefox_config_float`. Same contract: fall back
|
|
22
|
+
-- to the caller's default when the key is unset or unparseable, so a malformed
|
|
23
|
+
-- config row can never break a write path.
|
|
24
|
+
--
|
|
25
|
+
-- Unchanged, and worth restating because it bounds the risk of a long window:
|
|
26
|
+
-- cleanup NEVER deletes the most recent version, and never deletes a version
|
|
27
|
+
-- marked `archived`. Turning cleanup off keeps everything forever; leaving it
|
|
28
|
+
-- on with a long window still guarantees at least the latest version survives.
|
|
29
|
+
--
|
|
30
|
+
-- The functions live in `rpcs.sql`, which `cerefox server deploy` re-applies.
|
|
31
|
+
-- This migration exists so the schema version moves and operators are told to
|
|
32
|
+
-- redeploy — the change is inert until the RPCs are replaced.
|
|
33
|
+
--
|
|
34
|
+
-- Idempotent: safe to re-run.
|
|
35
|
+
|
|
36
|
+
-- ── Fail-safe for EXISTING stores ────────────────────────────────────────────
|
|
37
|
+
--
|
|
38
|
+
-- Upgrading silently changes where retention comes from. An operator running
|
|
39
|
+
-- `CEREFOX_VERSION_CLEANUP_ENABLED=false` (or a long window) would, on their
|
|
40
|
+
-- very next save, fall back to the 48-hour default and lose the history they had
|
|
41
|
+
-- deliberately kept. The env var stops being read the moment the client updates,
|
|
42
|
+
-- which is before anyone reads a release note.
|
|
43
|
+
--
|
|
44
|
+
-- So this migration disables pruning on existing stores. Nothing is deleted;
|
|
45
|
+
-- cleanup simply does not run until the operator states a policy. Pruning is
|
|
46
|
+
-- irreversible and not-pruning is not, so the safe default during an unattended
|
|
47
|
+
-- upgrade is to do nothing. `cerefox doctor` and the Settings page both show the
|
|
48
|
+
-- value, and turning it back on is one command.
|
|
49
|
+
--
|
|
50
|
+
-- Only existing databases get this. A fresh deploy STAMPS migrations as applied
|
|
51
|
+
-- rather than running them (see `_shared/db-deploy`), so new installs keep the
|
|
52
|
+
-- ordinary bounded default (48h, cleanup on) — there is no history there to
|
|
53
|
+
-- lose, and unbounded version growth is a poor default to saddle them with.
|
|
54
|
+
--
|
|
55
|
+
-- ON CONFLICT DO NOTHING: if the operator has already chosen a policy, this must
|
|
56
|
+
-- never overwrite it, including on a re-run.
|
|
57
|
+
INSERT INTO cerefox_config (key, value)
|
|
58
|
+
VALUES ('version_cleanup_enabled', 'false')
|
|
59
|
+
ON CONFLICT (key) DO NOTHING;
|
|
60
|
+
|
|
61
|
+
DO $$
|
|
62
|
+
BEGIN
|
|
63
|
+
RAISE NOTICE
|
|
64
|
+
'Migration 0016: version retention now reads cerefox_config '
|
|
65
|
+
'(version_retention_hours, version_cleanup_enabled). The CEREFOX_VERSION_* '
|
|
66
|
+
'environment variables are no longer read. Version pruning has been DISABLED '
|
|
67
|
+
'on this store as an upgrade precaution — nothing was deleted. Set your policy '
|
|
68
|
+
'with `cerefox config set version_cleanup_enabled true` or the Settings page.';
|
|
69
|
+
END $$;
|
|
@@ -940,7 +940,10 @@ $$;
|
|
|
940
940
|
-- Parameters:
|
|
941
941
|
-- p_document_id : Document to snapshot
|
|
942
942
|
-- p_source : How the update was triggered ('file','paste','agent','manual')
|
|
943
|
-
-- p_retention_hours : Retention window in hours (default
|
|
943
|
+
-- p_retention_hours : Retention window in hours. NULL (default) reads
|
|
944
|
+
-- `version_retention_hours` from cerefox_config, else 48.
|
|
945
|
+
-- A non-NULL value overrides the store policy for this
|
|
946
|
+
-- call only.
|
|
944
947
|
--
|
|
945
948
|
-- Returns: (version_id, version_number, chunk_count, total_chars) of the new version
|
|
946
949
|
|
|
@@ -949,8 +952,11 @@ DROP FUNCTION IF EXISTS cerefox_snapshot_version(UUID, TEXT, INT, BOOLEAN);
|
|
|
949
952
|
CREATE FUNCTION cerefox_snapshot_version(
|
|
950
953
|
p_document_id UUID,
|
|
951
954
|
p_source TEXT DEFAULT 'manual',
|
|
952
|
-
|
|
953
|
-
|
|
955
|
+
-- NULL (the new default) means "use the store's policy from
|
|
956
|
+
-- cerefox_config". Passing a value still overrides, for deliberate one-off
|
|
957
|
+
-- admin operations — but callers no longer supply one by accident.
|
|
958
|
+
p_retention_hours INT DEFAULT NULL,
|
|
959
|
+
p_cleanup_enabled BOOLEAN DEFAULT NULL
|
|
954
960
|
)
|
|
955
961
|
RETURNS TABLE (
|
|
956
962
|
version_id UUID,
|
|
@@ -967,6 +973,18 @@ DECLARE
|
|
|
967
973
|
v_version_number INT;
|
|
968
974
|
v_chunk_count INT;
|
|
969
975
|
v_total_chars INT;
|
|
976
|
+
-- Resolve the retention policy from the STORE, not the caller.
|
|
977
|
+
--
|
|
978
|
+
-- These used to arrive as parameters filled from each client's own env, so
|
|
979
|
+
-- the surviving version history depended on which client wrote last: an
|
|
980
|
+
-- agent running defaults would prune versions that an operator had
|
|
981
|
+
-- configured to keep. Retention describes the data, so it belongs to the
|
|
982
|
+
-- data. Same COALESCE(param, config, default) shape the retrieval tunables
|
|
983
|
+
-- already use.
|
|
984
|
+
v_retention INT := COALESCE(p_retention_hours,
|
|
985
|
+
cerefox_config_int('version_retention_hours', 48));
|
|
986
|
+
v_cleanup BOOLEAN := COALESCE(p_cleanup_enabled,
|
|
987
|
+
cerefox_config_bool('version_cleanup_enabled', TRUE));
|
|
970
988
|
BEGIN
|
|
971
989
|
-- Count current chunks to record in the version metadata
|
|
972
990
|
SELECT COUNT(*), COALESCE(SUM(char_count), 0)
|
|
@@ -999,11 +1017,11 @@ BEGIN
|
|
|
999
1017
|
-- but always keep the most recently created version (the one we just made).
|
|
1000
1018
|
-- Skip archived versions (archived=true) -- they are protected from cleanup.
|
|
1001
1019
|
-- Skip cleanup entirely if p_cleanup_enabled is false (immutable mode).
|
|
1002
|
-
IF
|
|
1020
|
+
IF v_cleanup THEN
|
|
1003
1021
|
DELETE FROM cerefox_document_versions dv
|
|
1004
1022
|
WHERE dv.document_id = p_document_id
|
|
1005
1023
|
AND dv.archived IS NOT TRUE
|
|
1006
|
-
AND dv.created_at < NOW() - (
|
|
1024
|
+
AND dv.created_at < NOW() - (v_retention || ' hours')::INTERVAL
|
|
1007
1025
|
AND dv.id != (
|
|
1008
1026
|
SELECT id FROM cerefox_document_versions
|
|
1009
1027
|
WHERE document_id = p_document_id
|
|
@@ -1253,7 +1271,7 @@ $$;
|
|
|
1253
1271
|
-- p_expected_content_hash : optimistic-concurrency token (iter-32). On the UPDATE
|
|
1254
1272
|
-- path this must equal the document's current content_hash —
|
|
1255
1273
|
-- the caller proves they based their edit on the live version.
|
|
1256
|
-
-- Mismatch → CEREFOX_CONFLICT (SQLSTATE
|
|
1274
|
+
-- Mismatch → CEREFOX_CONFLICT (SQLSTATE PT409 → HTTP 409). Absent (NULL)
|
|
1257
1275
|
-- without p_last_write_wins → CEREFOX_TOKEN_REQUIRED (22023).
|
|
1258
1276
|
-- Ignored on the CREATE path.
|
|
1259
1277
|
-- p_last_write_wins : explicit opt-out of the concurrency check (filesystem-sync
|
|
@@ -1368,7 +1386,17 @@ BEGIN
|
|
|
1368
1386
|
-- choose last-write-wins. Message prefixes are machine-detectable:
|
|
1369
1387
|
-- transport handlers map them to agent-first retry instructions.
|
|
1370
1388
|
IF NOT p_last_write_wins THEN
|
|
1371
|
-
|
|
1389
|
+
-- A blank token is an ABSENT token, not a stale one.
|
|
1390
|
+
--
|
|
1391
|
+
-- '' is not NULL, so an empty string used to skip the
|
|
1392
|
+
-- TOKEN_REQUIRED branch and fall into the conflict branch below:
|
|
1393
|
+
-- it can never equal a real hash, so it failed deterministically
|
|
1394
|
+
-- and forever. That is precisely the shape that drove the retry
|
|
1395
|
+
-- storm — a permanent failure reported as a retryable one. Even
|
|
1396
|
+
-- with PT409 now closing the loop, classifying it as a conflict is
|
|
1397
|
+
-- wrong: nobody read '' from a document, so the caller has not
|
|
1398
|
+
-- followed the read-before-write contract, which is a 400.
|
|
1399
|
+
IF NULLIF(BTRIM(p_expected_content_hash), '') IS NULL THEN
|
|
1372
1400
|
RAISE EXCEPTION
|
|
1373
1401
|
'CEREFOX_TOKEN_REQUIRED: content updates require expected_content_hash (the content_hash you read) or last_write_wins=true. Current hash: %',
|
|
1374
1402
|
v_current_hash
|
|
@@ -1377,7 +1405,27 @@ BEGIN
|
|
|
1377
1405
|
RAISE EXCEPTION
|
|
1378
1406
|
'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
1407
|
v_doc_id, p_expected_content_hash, v_current_hash
|
|
1380
|
-
|
|
1408
|
+
-- PT409 → HTTP 409 Conflict (PostgREST's PTxxx convention).
|
|
1409
|
+
--
|
|
1410
|
+
-- This was '40001' (serialization_failure) until v1.1.0-beta.6,
|
|
1411
|
+
-- which was a category error with severe consequences. 40001 is
|
|
1412
|
+
-- the ONE PostgreSQL class that promises "this was transient,
|
|
1413
|
+
-- retry and it may succeed" — but a stale-token conflict is
|
|
1414
|
+
-- DETERMINISTIC: the same request fails identically forever.
|
|
1415
|
+
-- Retry-aware layers took the promise at face value and looped.
|
|
1416
|
+
--
|
|
1417
|
+
-- Measured on a real project: one HTTP request carrying a stale
|
|
1418
|
+
-- hash executed this function 68,825 times in 125s before the
|
|
1419
|
+
-- gateway returned 504 — and kept going after the client was
|
|
1420
|
+
-- gone, passing 153,000 executions before the backend was killed
|
|
1421
|
+
-- manually. A contributor hit the same loop for ~24h and 47
|
|
1422
|
+
-- MILLION calls, which is what depleted their Disk IO budget.
|
|
1423
|
+
-- The same probe raising PT409 executed exactly ONCE and
|
|
1424
|
+
-- returned 409 in 636ms.
|
|
1425
|
+
--
|
|
1426
|
+
-- Rule of thumb: never raise a permanent application error under
|
|
1427
|
+
-- a SQLSTATE whose contract says "retryable".
|
|
1428
|
+
USING ERRCODE = 'PT409';
|
|
1381
1429
|
END IF;
|
|
1382
1430
|
END IF;
|
|
1383
1431
|
|
|
@@ -2025,6 +2073,43 @@ BEGIN
|
|
|
2025
2073
|
END;
|
|
2026
2074
|
$$;
|
|
2027
2075
|
|
|
2076
|
+
-- Integer/boolean companions to cerefox_config_float. Same contract: fall back
|
|
2077
|
+
-- to the caller's default when the key is unset or unparseable, so a malformed
|
|
2078
|
+
-- row can never break a write path.
|
|
2079
|
+
CREATE OR REPLACE FUNCTION cerefox_config_int(p_key TEXT, p_fallback INT)
|
|
2080
|
+
RETURNS INT
|
|
2081
|
+
LANGUAGE plpgsql
|
|
2082
|
+
STABLE
|
|
2083
|
+
SECURITY DEFINER
|
|
2084
|
+
SET search_path = public, pg_catalog
|
|
2085
|
+
AS $$
|
|
2086
|
+
DECLARE
|
|
2087
|
+
v_raw TEXT;
|
|
2088
|
+
BEGIN
|
|
2089
|
+
SELECT value INTO v_raw FROM cerefox_config WHERE key = p_key;
|
|
2090
|
+
IF v_raw IS NULL OR btrim(v_raw) = '' THEN RETURN p_fallback; END IF;
|
|
2091
|
+
RETURN v_raw::INT;
|
|
2092
|
+
EXCEPTION WHEN others THEN
|
|
2093
|
+
RETURN p_fallback;
|
|
2094
|
+
END;
|
|
2095
|
+
$$;
|
|
2096
|
+
|
|
2097
|
+
CREATE OR REPLACE FUNCTION cerefox_config_bool(p_key TEXT, p_fallback BOOLEAN)
|
|
2098
|
+
RETURNS BOOLEAN
|
|
2099
|
+
LANGUAGE plpgsql
|
|
2100
|
+
STABLE
|
|
2101
|
+
SECURITY DEFINER
|
|
2102
|
+
SET search_path = public, pg_catalog
|
|
2103
|
+
AS $$
|
|
2104
|
+
DECLARE
|
|
2105
|
+
v_raw TEXT;
|
|
2106
|
+
BEGIN
|
|
2107
|
+
SELECT value INTO v_raw FROM cerefox_config WHERE key = p_key;
|
|
2108
|
+
IF v_raw IS NULL OR btrim(v_raw) = '' THEN RETURN p_fallback; END IF;
|
|
2109
|
+
RETURN lower(btrim(v_raw)) = 'true';
|
|
2110
|
+
END;
|
|
2111
|
+
$$;
|
|
2112
|
+
|
|
2028
2113
|
CREATE OR REPLACE FUNCTION cerefox_set_config(p_key TEXT, p_value TEXT)
|
|
2029
2114
|
RETURNS VOID
|
|
2030
2115
|
LANGUAGE plpgsql
|
|
@@ -2038,6 +2123,10 @@ DECLARE
|
|
|
2038
2123
|
v_allowed TEXT[] := ARRAY[
|
|
2039
2124
|
'usage_tracking_enabled', 'require_requestor_identity', 'requestor_identity_format',
|
|
2040
2125
|
'min_search_score', 'min_term_coverage', 'search_alpha',
|
|
2126
|
+
-- Version retention: a property of the STORE, not of whichever client
|
|
2127
|
+
-- happens to write. Previously passed per-call from client env, so the
|
|
2128
|
+
-- surviving history depended on who saved last.
|
|
2129
|
+
'version_retention_hours', 'version_cleanup_enabled',
|
|
2041
2130
|
-- Optional features, off by default (iteration 29).
|
|
2042
2131
|
'relations_enabled'
|
|
2043
2132
|
];
|
|
@@ -2234,7 +2323,7 @@ SET search_path = public, pg_catalog
|
|
|
2234
2323
|
AS $$
|
|
2235
2324
|
-- Keep in lockstep with the `@version:` marker in schema.sql (cut_release.ts
|
|
2236
2325
|
-- enforces it). Bump whenever schema.sql OR rpcs.sql changes.
|
|
2237
|
-
SELECT '0.10.
|
|
2326
|
+
SELECT '0.10.3'::TEXT;
|
|
2238
2327
|
$$;
|
|
2239
2328
|
|
|
2240
2329
|
-- ── 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.
|
|
8
|
+
-- @version: 0.10.3
|
|
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/docs/guides/cli.md
CHANGED
|
@@ -574,6 +574,46 @@ cerefox token list # show masked fingerprints of the accepted set
|
|
|
574
574
|
|
|
575
575
|
---
|
|
576
576
|
|
|
577
|
+
### `cerefox relation set` / `delete` / `list` / `neighbors`
|
|
578
|
+
|
|
579
|
+
> **Off by default.** The relation feature ships **dormant**: the four MCP
|
|
580
|
+
> relation tools are hidden from agents, and the CLI group is inert, until an
|
|
581
|
+
> operator opts in with `cerefox config set relations_enabled true` (or the
|
|
582
|
+
> **Settings** page in the web UI). The table and schema exist either way, so
|
|
583
|
+
> enabling and disabling are both non-destructive — turning it off hides the
|
|
584
|
+
> tools again without deleting a single edge.
|
|
585
|
+
|
|
586
|
+
**Purpose**: typed, directed links between documents — `source --rel_type--> target`.
|
|
587
|
+
`rel_type` is free text; a dictionary in SQL marks some types symmetric (setting
|
|
588
|
+
one direction implies the other, and deleting removes both).
|
|
589
|
+
|
|
590
|
+
**Synopsis**:
|
|
591
|
+
```
|
|
592
|
+
cerefox relation set SOURCE_ID REL_TYPE TARGET_ID
|
|
593
|
+
cerefox relation delete SOURCE_ID REL_TYPE TARGET_ID
|
|
594
|
+
cerefox relation list DOCUMENT_ID # every relation touching it, both directions
|
|
595
|
+
cerefox relation neighbors DOCUMENT_ID REL_TYPE # walk the graph along one type
|
|
596
|
+
```
|
|
597
|
+
|
|
598
|
+
**Notes**:
|
|
599
|
+
|
|
600
|
+
- `neighbors` walks to a bounded `--depth` and is cycle-safe: a document already
|
|
601
|
+
visited on the walk is not revisited, so a loop in the graph terminates rather
|
|
602
|
+
than recursing forever.
|
|
603
|
+
- Self-edges are rejected, as are edges to a document that does not exist.
|
|
604
|
+
- Deleting a document cascades to its edges; soft-deleting one hides them from
|
|
605
|
+
traversal without removing them, so a restore brings the graph back intact.
|
|
606
|
+
- Every write is recorded in the audit log (`relation-set`).
|
|
607
|
+
|
|
608
|
+
**Enabling it**:
|
|
609
|
+
```
|
|
610
|
+
cerefox config set relations_enabled true # tools appear in every agent's list
|
|
611
|
+
cerefox config set relations_enabled false # hidden again; no data removed
|
|
612
|
+
```
|
|
613
|
+
|
|
614
|
+
Agents see 10 tools with the flag off and 14 with it on. See
|
|
615
|
+
[`configuration.md`](configuration.md) for the full runtime-config surface.
|
|
616
|
+
|
|
577
617
|
### `cerefox config list` / `cerefox config get` / `cerefox config set`
|
|
578
618
|
|
|
579
619
|
**Purpose**: read/write runtime config in `cerefox_config` (e.g. `usage_tracking_enabled`, `require_requestor_identity`).
|
|
@@ -151,9 +151,9 @@ This handles intermittent OpenAI API errors (500s) that would otherwise cause se
|
|
|
151
151
|
| Variable | Default | Description |
|
|
152
152
|
|----------|---------|-------------|
|
|
153
153
|
| `CEREFOX_MAX_RESPONSE_BYTES` | `200000` | Maximum bytes in a single search response (local MCP path). See explanation below. |
|
|
154
|
-
| `CEREFOX_MIN_SEARCH_SCORE` |
|
|
155
|
-
| `CEREFOX_SEARCH_ALPHA` |
|
|
156
|
-
| `CEREFOX_MIN_TERM_COVERAGE` |
|
|
154
|
+
| `CEREFOX_MIN_SEARCH_SCORE` | **Retired in v1.1.0 — no longer read.** | Now a store setting: `cerefox config set min_search_score <value>`, or the **Settings** page. The right floor depends on which embedder produced the vectors, and the embedder belongs to the store — every client querying one database must use the same one. A per-call override still works (`cerefox search --min-score`, the MCP `min_score` param). `cerefox doctor` and the Settings page report the variable if it is still set. |
|
|
155
|
+
| `CEREFOX_SEARCH_ALPHA` | **Retired in v1.1.0 — no longer read.** | Now a store setting: `cerefox config set search_alpha <value>`, or the **Settings** page. A per-call override still works (`cerefox search --min-score`, the MCP `min_score` param). `cerefox doctor` and the Settings page report the variable if it is still set. |
|
|
156
|
+
| `CEREFOX_MIN_TERM_COVERAGE` | **Retired in v1.1.0 — no longer read.** | Now a store setting: `cerefox config set min_term_coverage <value>`, or the **Settings** page. A per-call override still works (`cerefox search --min-score`, the MCP `min_score` param). `cerefox doctor` and the Settings page report the variable if it is still set. |
|
|
157
157
|
| `CEREFOX_EMBED_MAX_INPUT_CHARS` | `20000` | Safety cap on the characters sent to the embedding model per input. The full chunk content is always stored and reconstructed untouched; only the embedding uses the (rare) truncated prefix, so an oversized chunk can never fail an ingest. |
|
|
158
158
|
| `CEREFOX_MODELS_DIR` | `~/.cerefox/models` (in-container: inside the data volume) | Where the local embedder caches downloaded model weights (Cerefox Local; `CEREFOX_EMBEDDER=local`). |
|
|
159
159
|
| `CEREFOX_ONNX_BATCH` | `4` | Texts per local-embedder inference call. Peak memory scales with this; the small default keeps ingest/reindex safe on small Docker VMs. |
|
|
@@ -240,8 +240,8 @@ Cerefox automatically archives previous document content whenever a document is
|
|
|
240
240
|
|
|
241
241
|
| Variable | Default | Description |
|
|
242
242
|
|----------|---------|-------------|
|
|
243
|
-
| `CEREFOX_VERSION_RETENTION_HOURS` |
|
|
244
|
-
| `CEREFOX_VERSION_CLEANUP_ENABLED` |
|
|
243
|
+
| `CEREFOX_VERSION_RETENTION_HOURS` | **Retired in v1.1.0 — no longer read.** | Version retention is now a property of the store: `cerefox config set version_retention_hours <hours>`, or the **Settings** page. It moved because it used to be passed per-call from each client's environment, so the surviving history depended on which client wrote last. `cerefox doctor` reports the variable if it is still set. |
|
|
244
|
+
| `CEREFOX_VERSION_CLEANUP_ENABLED` | **Retired in v1.1.0 — no longer read.** | Use `cerefox config set version_cleanup_enabled <true\|false>` (or **Settings**). Set to `false` to keep every version forever. Cleanup never deletes the most recent version, nor any version marked `archived`. |
|
|
245
245
|
|
|
246
246
|
**How versioning works:**
|
|
247
247
|
|
|
@@ -266,7 +266,7 @@ cerefox document get <document-id> --version-id <version-id>
|
|
|
266
266
|
|----------|---------|-------------|
|
|
267
267
|
| `CEREFOX_BACKUP_DIR` | `~/.cerefox/backups` | Local directory where file system backups are stored. Created automatically if it doesn't exist. **Use an absolute path** — a relative value (such as the pre-v0.3.0 `./backups`) resolves against the current working directory, so snapshots scatter depending on where you run the command; `backup create` warns when it sees one. Does **not** follow `CEREFOX_CONFIG_DIR`, so a second environment must set it explicitly. |
|
|
268
268
|
| `CEREFOX_ENV_LABEL` | _(unset)_ | Names a non-production environment (e.g. `staging`). Purely cosmetic and inert when unset. When set: the web UI shows a banner on every page, `doctor` shows `[LABEL]` on its title line, `backup create` puts the label in the snapshot filename and payload, and `backup restore` warns when a snapshot's environment differs from the target's. See [`staging-env.md`](staging-env.md). |
|
|
269
|
-
| `CEREFOX_VERSION_RETENTION_HOURS` |
|
|
269
|
+
| `CEREFOX_VERSION_RETENTION_HOURS` | **Retired in v1.1.0 — no longer read.** | Version retention is now a property of the store: `cerefox config set version_retention_hours <hours>`, or the **Settings** page. It moved because it used to be passed per-call from each client's environment, so the surviving history depended on which client wrote last. `cerefox doctor` reports the variable if it is still set. |
|
|
270
270
|
|
|
271
271
|
---
|
|
272
272
|
|