Skip to content

fix(storage): no default PostgreSQL URL in code; auto without a URL is SQLite (#704, slice 2) - #865

Closed
cdeust wants to merge 14 commits into
mainfrom
fix/704-no-default-database-url
Closed

cdeust wants to merge 14 commits into
mainfrom
fix/704-no-default-database-url

Conversation

@cdeust

@cdeust cdeust commented Nov 4, 2026

Copy link
Copy Markdown
Owner

Summary

Library residual of #704, named out of scope by #804 (docs/verification/draft-relevance-20261027.md § 770): MemorySettings.DATABASE_URL still defaulted to postgresql://127.0.0.1:5432/cortex. Every library path that reads the setting without a named URL could therefore reach a local cortex database nobody configured:

  • _construct_store with the cli runtime mapped auto to postgresql and tried the default URL;
  • python -m mcp_server.migrate connected to it (RED reproduced: connection to server at "127.0.0.1", port 5432 failed);
  • agent_briefing_query._connect passed it to raw pg_connect when no backend was decided.

Root cause: the default URL in memory_config.py. It is now empty, and the callers follow an explicit contract:

  • auto with no named URL → SQLite in every runtime, with no connection attempted (_resolve_backend_url follows the same rule, so the cache key stays consistent);
  • an explicit postgresql backend with no URL → RuntimeError (no silent SQLite store, no guessed server; the old allow_fallback = not url branch disappears);
  • pg_store_ddl._get_database_url refuses an empty conninfo (ValueError), because libpq would then connect to its local defaults; cortex-migrate reports it on its exit-1 contract;
  • agent briefing: with no URL, only the shared store decides; for postgresql without a URL, nothing is opened.

PRIVACY.md is corrected: it promised a "default postgresql://127.0.0.1:5432/cortex". The marker path keeps ADR-1104's legacy URL in settle_backend, and the text says so.

Refs #704 (not Closes: see "Breaking changes / remaining work").

Type of change

  • Bug fix
  • Breaking change (a library caller relying on the implicit local URL must name one)

Test plan

  • New tests_py/infrastructure/test_no_default_database_url.py, 10 tests: 9 fail on origin/main (1de07f9) and 10 pass with the fix. Every _try_pg_verbose / pg_connect call fails the test, so any connection attempt is caught.
  • test_sqlite_backend.py::test_reachable_postgresql_is_preferred_over_sqlite asserted exactly the behaviour A profile with no configuration connects to a local PostgreSQL it was never configured for #704 rejects (a default URL that answers wins). It now names its URL through CORTEX_MEMORY_DATABASE_URL.
  • Targeted run: test_no_default_database_url, test_sqlite_backend, test_backend_resolution, hooks/test_entry_fresh_backend, test_shared_backend_bootstrap, hooks/test_agent_briefing*, test_migrate → 122 passed, 8 skipped (PostgreSQL absent).
  • Full pytest tests_py: 10904 passed, 624 skipped, 4 failed, none caused by this PR: 3 test_embedding_engine (torch not installable here, its download host is blocked) and test_wiki_coverage_dashboard::test_unreadable_page_is_skipped_not_fatal, which also fails on origin/main (the session runs as root, so "unreadable" files can be read).
  • ruff check / ruff format --check (0.17.3, from uv.lock) pass on the touched files (main has 15 E501/E741 errors elsewhere). scripts/check_craftsmanship.py: OK.

Audit notes

  • No independent review in this pass.
  • Not verified: the PostgreSQL-gated tests (no PostgreSQL here), Windows/macOS.

Breaking changes

  • Before: with no URL and no marker, a library caller (CLI auto, migrate, briefing) connected to postgresql://127.0.0.1:5432/cortex.
  • After: SQLite (auto), or an explicit error (postgresql backend, migrate). Migration: set DATABASE_URL or CORTEX_MEMORY_DATABASE_URL.
  • Remaining A profile with no configuration connects to a local PostgreSQL it was never configured for #704 slice, needs an owner decision: scripts/install-plugin.sh (lines 20–21, 87) still selects PostgreSQL at install time when it finds a reachable local cortex database ("never downgrade"). Removing that choice changes upgrades for existing PostgreSQL installs, so it is a product decision, not part of this PR.

Reviewer checklist

  • CHANGELOG.md updated (Unreleased / Fixed).
  • Documentation updated (PRIVACY.md).
  • No secrets / credentials / PII in the diff.
  • CI passes on the latest commit: not yet observed.

Generated by Claude Code

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 5, 2026 •

Copy link
Copy Markdown
Codex Review Summary: ✅ Completed

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-06T19:37:45.349139Z f27f1c0 New commits
🔒 Security Review ✅ Completed 2026-11-06T19:39:32.133027Z f27f1c0 New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@claude

claude Bot commented Nov 5, 2026 •

Copy link
Copy Markdown

Claude finished @cdeust's task in 2m 40s —— View job


Review 1 of 3 (ADR-1119): PR #865

  • Gather diff and context (git diff HEAD~1 HEAD, single commit 1ba96df)
  • Run five reviews (quality, security, performance, tests, docs)
  • Verify findings against the review contract
  • Post verdict

Blockers: none.

Checks behind that:

  • Store selection. _resolve_backend_url and _construct_store move CLI auto to PostgreSQL under the same rule (memory_store.py:112 and :141). The store cache key and the store that gets built therefore agree.
  • No empty URL reaches PostgreSQL. Every caller that now gets an empty URL either raises or uses SQLite: migrate exits 1, PgMemoryStore() raises ValueError, and the briefing hook returns None. None of them reaches pg_connect("").
  • CI with PostgreSQL forced on. scripts/ci_unit.sh sets CORTEX_MEMORY_STORE_BACKEND=postgresql for the whole run. I checked every test that unsets DATABASE_URL: each one also overrides the backend, strips CORTEX_* variables, or never builds a store. None hits the new RuntimeError.
  • Earlier CI run. Pipeline run 41898927272 passed every job, including unit (against a real PostgreSQL), e2e, and Windows/macOS. I could not confirm which commit it ran on, because gh is not permitted in this job.
  • Layering. No layering or craftsmanship rule is affected.

Notes, no action before merge

  • The new PRIVACY.md text (PRIVACY.md:60-62) and the CHANGELOG mention only the marker case for the legacy URL. settle_backend (backend_resolution.py:151-153) also fills in postgresql://localhost:5432/cortex when CORTEX_MEMORY_STORE_BACKEND=postgresql is set in the environment with no URL.
  • Some docstrings are now stale: migrate.py:27-28 still says "DATABASE_URL is optional", and memory_store.py:214-215 refers to a "DB-less inspection default" that no longer exists.
  • groomer_coordinator.py:60 hashes "" for auto with no URL, though that store is now SQLite. The key should come from SQLITE_FALLBACK_PATH. Windows on different stores sharing one coordinator key predates this PR.

Posted by updating this comment: gh pr comment is not permitted in this job.

Verdict: approve
· Branch

…s SQLite (#704)

MemorySettings.DATABASE_URL defaulted to postgresql://127.0.0.1:5432/cortex,
so library callers with no URL and no marker (CLI-runtime auto,
cortex-migrate, the agent-briefing raw connection) reached whatever local
cortex database answered. The default is now empty; auto without a named
URL is SQLite in every runtime; an explicit postgresql backend with no URL
raises; _get_database_url refuses an empty conninfo and cortex-migrate
reports it on its exit-1 contract.

Refs #704

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018kyZYtyFK1iv4S9wZDjGoP
@cdeust
cdeust force-pushed the fix/704-no-default-database-url branch from 1ba96df to 4c5a624 Compare November 5, 2026 12:36
@claude

claude Bot commented Nov 5, 2026 •

Copy link
Copy Markdown

Claude finished @cdeust's task in 3m 46s —— View job


Review of PR #865 (review 1 of 3)

  • Gather diff and context
  • Run five reviews (quality, security, performance, tests, docs)
  • Verify findings against the review contract (ADR-1119)
  • Post verdict (review comment)

1 Blocker, which goes against "no silent fallbacks":

  • The bug: python -m mcp_server.portability, and the __main__ of handlers/wiki_migrate.py, never call settle_backend. With a PostgreSQL marker and no DATABASE_URL, they now build an empty SQLite store and give no warning.
    • export writes 0 memories and exits 0.
    • import writes the memories into memory.db instead of the PostgreSQL store.
  • Why it worked before: the old default URL happened to point at the same server the marker names.

There are also 3 one-line Notes: the CHANGELOG and PRIVACY wording claims more than the code does, a branch in resolve_auto_backend can no longer run, and the migrate.py docstring is out of date. Nothing was changed or pushed.

Verdict: block

@claude

claude Bot commented Nov 5, 2026

Copy link
Copy Markdown

Review 1/3 — PR #865 (commit 4c5a624)

Five reviews ran: quality, security, performance, tests and docs. I checked each candidate finding against the code before listing it.

Blocker

B1. Standalone store CLIs now silently use an empty SQLite store when the marker says PostgreSQL (breaks the CLAUDE.md rule "no silent fallbacks")

  • Where: mcp_server/portability.py:39-44 and mcp_server/handlers/wiki_migrate.py:421/:434. The deciding code is mcp_server/infrastructure/memory_store.py:139-145, which falls through to _make_sqlite.
  • Who it hits: a user whose backend marker selects PostgreSQL (the ADR-1104 legacy server), with no DATABASE_URL in the shell, who runs python -m mcp_server.portability export|import from a terminal (runtime cli, see memory_config.py:27).
  • Why it happens: these __main__ entry points never call settle_backend (only mcp_server/__main__.py:26 and hooks/entry.py:68 do), so the marker is never read.
  • Before this PR: CLI auto became postgresql, and the old default URL happened to point at the same server the marker names.
  • Now: url == "", so auto stays auto and the store is SQLite, with no warning.
    • export writes 0 memories and exits 0.
    • import writes into memory.db instead of the user's PostgreSQL store.
  • Fix: call settle_backend(os.environ) at the start of each standalone __main__ that builds a store, as mcp_server/__main__.py does. Add a regression test: PostgreSQL marker, no URL, portability CLI.

Fix this →

Notes, no action before merge

  • CHANGELOG.md:55,62 and PRIVACY.md:59 claim too much: backend_resolution.py:151-153 still writes postgresql://localhost:5432/cortex for any selected PostgreSQL backend, including CORTEX_MEMORY_STORE_BACKEND=postgresql set in the environment. The new RuntimeError is reached only by library callers.
  • backend_resolution.py:175-180: the PostgreSQL branch of resolve_auto_backend can no longer be reached, because auto with no URL is always SQLite now. If it ever ran, it would export DATABASE_URL="".
  • mcp_server/migrate.py:27-28: the docstring still says "DATABASE_URL is optional", but without one the command now exits 1.

Verdict: block

…a URL is SQLite (#704)

fixup! fix(storage): no default PostgreSQL URL in code; auto without a URL is SQLite (#704)

Review B1 (PR #865, comment 6658250021): portability, migrate and wiki_migrate run as standalone commands and never read the backend marker. With no default URL, a PostgreSQL marker and no URL in the shell gave an empty SQLite store, silently. They now call backend_resolution.settle_for_command first, as mcp_server/__main__.py does. Also: migrate and memory_store docstrings, and the PRIVACY/CHANGELOG wording on which backends use the legacy URL.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018kyZYtyFK1iv4S9wZDjGoP

cdeust commented Nov 5, 2026

Copy link
Copy Markdown
Owner Author

Review on 4c5a624, answered in 749bf05 (fixup!).

B1, confirmed and fixed. Before this PR, mcp_server.portability and mcp_server.migrate reached the legacy server only because the default URL happened to name it; with a SQLite marker they already went to PostgreSQL. mcp_server/handlers/wiki_migrate.py's __main__ has the same gap. All three now call backend_resolution.settle_for_command(prog) before any store or URL is built: the same settle_backend(os.environ) as mcp_server/__main__.py, with an invalid marker or an unrunnable managed server ending the command in one Cortex not ready line, exit 1. Hooks already go through hooks/entry.py. New tests_py/infrastructure/test_standalone_commands_settle_backend.py: a PostgreSQL marker with no URL reaches postgresql://localhost:5432/cortex for portability and migrate; an invalid marker builds no store. 3 failures without the fix, 3 passes with it.

Notes:

  • Fixed: the PRIVACY.md and CHANGELOG wording now names both ways a PostgreSQL backend gets selected (the marker, or CORTEX_MEMORY_STORE_BACKEND=postgresql) and says the RuntimeError is reached only by library callers. Also fixed: the migrate.py and _database_url_is_explicit docstrings.
  • Left as is: the PostgreSQL branch of resolve_auto_backend (backend_resolution.py:175-180) is unreachable now. Deleting it changes how hooks resolve auto, which this PR does not need to touch.
  • Left as is: the groomer key for auto without a URL (review 1). It is unchanged in kind: one constant key before (hash of the default URL) and one now (hash of "").

Runs: tests_py/infrastructure, tests_py/hooks, test_migrate, test_wiki_migrate and test_main: 2275 passed, 313 skipped, 3 failed, all in test_embedding_engine (torch cannot be installed here, unrelated). ruff 0.17.3 and check_craftsmanship.py are clean. CI on 749bf05: not observed yet.


Generated by Claude Code

@claude

claude Bot commented Nov 5, 2026 •

Copy link
Copy Markdown

Claude finished @cdeust's task in 2m 17s —— View job


Review 3/3: PR #865 (fixup 749bf05, after 4c5a624)

  • Check that Blocker B1 from review 1 (4c5a624) is fixed
  • Review the commits pushed after 4c5a624 (quality, security, performance, tests, docs)
  • Post verdict

B1 is fixed. B1 was the standalone CLIs silently building an empty SQLite store when the marker selects PostgreSQL.

  • The fix: a new settle_for_command (backend_resolution.py:183-200) now runs settle_backend(os.environ) before any store is built in:
    • portability.main (portability.py:49-52)
    • migrate._run (migrate.py:39-42)
    • wiki_migrate's __main__ (handlers/wiki_migrate.py:457-460)
  • Error handling: it catches the same three exceptions as mcp_server/__main__.py:24-29 and prints one line.
  • Settings are not bound too early: no module calls get_memory_settings() at import time. So get_memory_settings() / get_shared_store cannot bind settings before the settle step, in any of the three commands.
  • New tests: test_standalone_commands_settle_backend.py covers portability and migrate. Reading the code, all three tests would fail without the fix. The marker monkeypatch takes effect, because BACKEND_MARKER_PATH is read when the function runs. The tests remove the CORTEX_MEMORY_STORE_BACKEND=postgresql that ci_unit.sh sets, so they work under CI.
  • The five reviews found no defects:
    • Layers and the craftsmanship gate are intact: wiki_migrate.py and migrate._run are already in the baseline.
    • The new top-level managed_pg import creates no import cycle and costs nothing extra, because backend_marker already imports it.
    • No credentials appear in the new stderr line.
    • The localhost:5432 probe on first launch is the same one the server already runs, and it runs once per process.

Blockers: none.

Notes, no action before merge

  • PRIVACY.md:60-64 describes the URL fallback only for a marker or CORTEX_MEMORY_STORE_BACKEND. It leaves out the managed marker (that uses the plugin's own server, not localhost), CORTEX_BACKEND, and the ADR-1104 first-launch TCP probe of localhost:5432 on a HOME with earlier state. That behaviour is older than this PR.
  • migrate.py:27-28: the Pre lists the marker but not an environment-selected PostgreSQL backend, which also yields postgresql://localhost:5432/cortex instead of exit 1.
  • settle_for_command lets the KeyError/ValueError from a corrupt managed-server status file (managed_pg.serves) through as a traceback. For cortex-migrate that breaks its "never raises" contract; the server behaves the same way.

Merge scope

All of the PR is accepted: commits 4c5a624 and 749bf05, all 13 files. No Blocker is open and nothing needs a refinement PR. Still open, as the PR body says, and outside this PR: the scripts/install-plugin.sh "never downgrade" PostgreSQL selection, which is an owner decision.

I posted this by updating this comment, because gh pr comment and direct python runs need approval in this job. So I did not run the tests here: everything above comes from reading the code.
· Branch

Verdict: approve

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 749bf05d70

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

# source: issue #704 -- no default URL: PostgreSQL is used only when an
# operator names one (DATABASE_URL / CORTEX_MEMORY_DATABASE_URL) or the
# backend marker selects it, never because a local default answers.
DATABASE_URL: str = ""

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Settle the backend in the remaining maintenance entry points

For an existing local PostgreSQL installation selected by backend.json, with neither URL variable exported, removing this default makes several standalone maintenance commands select SQLite instead of the configured PostgreSQL store. Repository-wide inspection shows that scripts/memory_staleness_revalidate.py:70–79, scripts/memory_reheat.py:47–51, and scripts/memory_dedup_exact.py:42–46 call get_shared_store() without resolving the marker. The staleness command can consequently report success after scanning an empty SQLite database, while the latter commands exit through require_postgres_store() despite PostgreSQL being configured. Apply settle_for_command() before settings/store construction in these remaining entry points, as this change already does for portability and wiki migration.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed and fixed in 25c27d2 (fixup!).

I swept every standalone entry point that builds the shared store, not only the three named. Nine scripts/ maintenance commands open it through get_shared_store(settings.DB_PATH, …) without reading the marker: backfill_wiki_titles_2026_07_14, backfill_write_class, memory_dedup_exact, memory_domain_backfill, memory_reheat, memory_staleness_revalidate, near_dup_calibrate, provenance_sweep and wiki_citation_seed. Each one now calls settle_for_command(<name>) first, as portability, migrate and wiki_migrate already do, and exits 1 on an invalid marker.

The benchmark harnesses that call PgMemoryStore() with no URL (entity_dedup, decay_sweep_runner) are not changed. They now raise the explicit ValueError instead of reaching a guessed server, and they run against an explicit database (benchmarks/reproduce.sh).

Tests (test_standalone_commands_settle_backend.py):

  • a structural check, parametrised over the nine scripts, that the settle call comes before store construction;
  • an end-to-end check that memory_staleness_revalidate.main with a PostgreSQL marker and no URL reaches postgresql://localhost:5432/cortex.

With the script changes reverted, 10 of these tests fail; with them, 13 pass. On the touched files ruff check and ruff format --check pass, and check_craftsmanship.py is OK: main in the backfill script went over 40 lines, so store opening moved to _open_store(). tests_py/scripts, handlers/consolidation and invariants: 1958 passed.


Generated by Claude Code

…a URL is SQLite (#704)

fixup! fix(storage): no default PostgreSQL URL in code; auto without a URL is SQLite (#704)

Codex review on 749bf05 (comment 4480636738): the nine scripts/ maintenance commands that open the shared store (memory_staleness_revalidate, memory_reheat, memory_dedup_exact, and six more) now read the backend marker first through settle_for_command, like portability and migrate. Without it, a PostgreSQL marker with no URL gave them an empty SQLite store.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018kyZYtyFK1iv4S9wZDjGoP
…a URL is SQLite (#704)

fixup! fix(storage): no default PostgreSQL URL in code; auto without a URL is SQLite (#704)

unit failed on 25c27d2 (run 42031311038, job 127258608806): test_migrate_reports_a_missing_url_on_its_exit_contract. migrate now settles the backend first. The fixture left CORTEX_MEMORY_STORE_BACKEND=postgresql in place: scripts/ci_unit.sh exports it, and tests_py/conftest.py replaces it with sqlite only when PostgreSQL is unreachable, which is why the test passed locally. settle_backend then supplied the legacy URL. The fixture now removes the backend variable, points the marker at an empty temporary methodology directory, and restores the environment whole. Reproduced locally by re-exporting the variable after conftest; passes with the fix.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018kyZYtyFK1iv4S9wZDjGoP

cdeust commented Nov 5, 2026

Copy link
Copy Markdown
Owner Author

unit failed on 25c27d2 because of this PR's own test. Fixed in cc3804d (fixup!).

Run 42031311038, job 127258608806: 1 failed, 11606 passed. merge failed only because unit did.

Diagnosis:

  • Since 749bf05, cortex-migrate settles the backend marker before resolving its URL.
  • The no_named_url fixture still left CORTEX_MEMORY_STORE_BACKEND=postgresql in place. scripts/ci_unit.sh exports it.
  • settle_backend therefore supplied the legacy postgresql://localhost:5432/cortex, and migrate reported "cannot connect" instead of "no PostgreSQL URL configured".
  • The test passed locally only because tests_py/conftest.py:188 replaces the backend with sqlite when PostgreSQL is unreachable.

Fix (test only): the fixture now removes CORTEX_MEMORY_STORE_BACKEND as well. It also points the marker at an empty temporary methodology directory and restores the whole environment, because settle_backend writes keys of its own.

Evidence:

  • Reproduced locally by re-exporting CORTEX_MEMORY_STORE_BACKEND=postgresql after conftest has loaded, through a small pytest plugin.
  • Old fixture: the same assertion fails with the same 127.0.0.1:5432 refused message as CI.
  • New fixture: test_no_default_database_url.py and test_standalone_commands_settle_backend.py give 23 passed.

Not verified: the full suite against a real PostgreSQL, which this session does not have. CI on cc3804d will show it.


Generated by Claude Code

…a URL is SQLite (#704)

fixup! fix(storage): no default PostgreSQL URL in code; auto without a URL is SQLite (#704)

Mutation gate (ADR-1122) on cc3804d, run 42034218395: kill the survivors in the functions this PR changes. Adds tests for settle_for_command, portability.main, migrate._run, _get_database_url and agent_briefing._connect. settle_for_command moves above the first function: when it was appended after resolve_auto_backend, the diff's trailing blank lines put that untouched function in scope.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018kyZYtyFK1iv4S9wZDjGoP
…a URL is SQLite (#704)

fixup! fix(storage): no default PostgreSQL URL in code; auto without a URL is SQLite (#704)

Mutation gate (ADR-1122) on cc3804d, run 42034218395: for memory_store.py the stats run died on tests_py/hooks/test_entry.py. The hook subprocess inherits MUTANT_UNDER_TEST and runs mutmut's trampoline in a temp cwd with no [tool.mutmut] config ("Could not figure out where the code to mutate is"). hermetic_hook_env now drops the variable, so the hook process runs the original code and the in-process tests judge the mutants. Any PR that touches a module these hook tests import hits the same failure; the change is also proposed to main separately.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018kyZYtyFK1iv4S9wZDjGoP

cdeust commented Nov 5, 2026

Copy link
Copy Markdown
Owner Author

unit was red on cc3804d because of the mutation gate (ADR-1122), not the suite. Answered in 4129b86 and f944b6f (fixup!).

Run 42034218395, job 127268126994: pytest passed 11607, then ci_mutation_diff.py reported 6 file(s), 6 failed. The gate came onto this branch with the rebase on main (#872, #883, #884). There were two different causes.

1. Surviving mutants in the functions this PR changes. Test gaps, fixed (4129b86):

  • New tests for settle_for_command (the three exceptions, the exact message, the return values), portability.main (export and import arguments, errors, stdout), migrate._run (invalid marker, the URL handed to PgMemoryStore), _get_database_url (strip, ${ placeholder, exact message) and agent_briefing._connect (sqlite with a URL, log line, autocommit=True).
  • settle_for_command moves above the first function of backend_resolution.py. Appended after resolve_auto_backend, the trailing blank lines of the diff counted as part of that function, so 35 mutants of a function this PR does not touch were in scope.
  • Gate re-run locally, without PostgreSQL: backend_resolution, pg_store_ddl and portability pass. All 9 agent_briefing._connect survivors from CI are killed, along with the 5 from migrate._run (2, 3, 4, 5, 25).
  • Some migrate._run mutants still survive locally, but they are not on CI's list: there, tests_py/test_migrate.py kills them against PostgreSQL.

2. memory_store.py, the stats run failed. Gate tooling problem, not this code (f944b6f):

  • Under mutmut, tests_py/hooks/test_entry.py starts the hook as a subprocess with cwd set to the temporary project.
  • That subprocess inherits MUTANT_UNDER_TEST, so mutmut's trampoline looks for [tool.mutmut] in the cwd. It aborts with Could not figure out where the code to mutate is, the hook prints "Cortex not ready", and the stats run stops (failed to collect stats).
  • tests_py/_hermetic_hook_env.py now drops MUTANT_UNDER_TEST. The hook process runs the original code, and the in-process tests still judge the mutants. A test in tests_py/test_hermetic_hook_env.py covers this.
  • Any PR that changes a module these hook tests import hits the same failure, so I am also proposing the change to main in a separate PR.

Local checks: full suite: 11024 passed, 4 failed, none caused by this PR (3 test_embedding_engine, because torch cannot be installed here; test_wiki_coverage_dashboard, already red on main when run as root). ruff and check_craftsmanship.py are clean.

Not verified: the mutation of memory_store.py past the stats step. It needs PostgreSQL, and initdb refuses to run as root in this session. CI on f944b6f will show it.


Generated by Claude Code

cdeust commented Nov 5, 2026

Copy link
Copy Markdown
Owner Author

The MUTANT_UNDER_TEST change to hermetic_hook_env (gate cause 2 above) is also proposed to main as #889. Other PRs touching a module that the hook tests import will need it too. If #889 merges first, the next rebase of this branch will drop the duplicate.


Generated by Claude Code

cdeust commented Nov 5, 2026

Copy link
Copy Markdown
Owner Author

unit on f944b6f (run 42042293324, attempt 1) failed before any test ran: could not download https://github.com/theseus-rs/postgresql-binaries/releases/download/18.6.1/...: HTTP Error 504: Gateway Time-out (scripts/launcher_pg_assets.py). This is an infrastructure failure, not this PR. merge failed only because unit did. I have re-run the failed jobs once (attempt 2).


Generated by Claude Code

…a URL is SQLite (#704)

fixup! fix(storage): no default PostgreSQL URL in code; auto without a URL is SQLite (#704)

unit on f944b6f (run 42042293324, attempt 2): 35 failed, 204 errors, most of them 'no PostgreSQL URL configured' or KeyError 'DATABASE_URL' in later PostgreSQL tests. The no_named_url fixture pops DATABASE_URL, a test then monkeypatch.setenv's it, and monkeypatch's own teardown (which runs after the fixture's full restore) deletes the key it recorded as absent. CI's DATABASE_URL was gone for the rest of the session. Both fixtures now call monkeypatch.undo() before the restore. Reproduced with a probe test after these files (failed before, passes after); a full before/after environment comparison now differs only in PYTEST_CURRENT_TEST. The tree_sitter_language_pack DownloadError failures in the same run are GitHub fetches, not this PR.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018kyZYtyFK1iv4S9wZDjGoP

cdeust commented Nov 5, 2026

Copy link
Copy Markdown
Owner Author

unit was red on f944b6f (run 42042293324, attempt 2) and the cause is in this PR. Fixed in 129beff (fixup!).

The run ended with 35 failed and 204 errors. The PostgreSQL tests that ran after the new files failed with ValueError: no PostgreSQL URL configured or KeyError: 'DATABASE_URL'.

Cause: the fixture removes DATABASE_URL from the environment, then the new briefing tests set it again with monkeypatch.setenv. monkeypatch therefore recorded the variable as absent. Its teardown runs after the fixture has restored the whole environment, so it deleted CI's DATABASE_URL for the rest of the session.

Fix: both fixtures (no_named_url, postgresql_marker) now call monkeypatch.undo() before restoring the environment.

Evidence:

  • A probe test run after these two files failed before the fix and passes after it.
  • A full comparison of the environment before and after these files, plus test_hermetic_hook_env.py, now differs only in PYTEST_CURRENT_TEST.

Not this PR: in the same run, the tree_sitter_language_pack.DownloadError: Failed to fetch manifest from https://github.com/... failures (test_ast_decorator_calls, test_ast_extractor_definitions) are GitHub downloads. They passed on cc3804d.


Generated by Claude Code

cdeust commented Nov 5, 2026

Copy link
Copy Markdown
Owner Author

CI autopilot: PR on hold. On 129beff, the mutation gate hangs on memory_store.py, and I cannot reproduce or fix that from this session.

Run 42044899519, job 127305210538:

  • Suite: 11626 passed, 26 skipped. The DATABASE_URL leak is fixed.
  • Mutation gate: agent_briefing_query.py and backend_resolution.py pass. For memory_store.py, the stats run now completes, so the MUTANT_UNDER_TEST fix (also test(hooks): hook subprocesses do not inherit the parent's MUTANT_UNDER_TEST (ADR-1122) #889) works.
  • The hang: after Running mutation testing (14:48:12Z), the counter stops at 141/262 🎉 126 🙁 15 ⏰ 0 and never moves again. GitHub cancels the job at the 6 h limit (20:26:29Z, The operation was canceled), with mutmut still running (orphaned pids 39634 and 126748). merge fails as a result.

What I know:

  1. One mutant makes a test hang and mutmut kills nothing: ⏰ 0, and no per-mutant timeout fired. The log does not name the mutant. mutmut mutates the whole file (262 mutants), not only the 3 functions in scope, so the hanging one may sit outside this PR's functions.
  2. 15 mutants had already survived at that point. I don't know how many are in scope (_resolve_backend_url, _construct_store, _database_url_is_explicit).
  3. I cannot reproduce here. The memory_store selection needs PostgreSQL, and initdb refuses to run as root in this session. Without PostgreSQL, the stats run stops on test_file_memory_priming.

What a human needs to decide or do:

  • Run scripts/mutation_check.sh for memory_store.py on a machine with PostgreSQL (the selection is in the log at line 4312), then mutmut results to name the hanging mutant and the 15 survivors.
  • Choose how the gate handles a mutant that never finishes. One option is an overall bound in scripts/mutation_check.sh (timeout around mutmut run, or mutmut's own --timeout), so the job reports a verdict instead of running into GitHub's 6 h limit.
  • Then decide whether this PR's memory_store survivors are killed here or registered as equivalents.

Everything else on this PR is green or verified. Commits pushed since the last review: 4129b86, f944b6f, 129beff.


Generated by Claude Code

@cdeust cdeust added the autopilot:hold CI autopilot stood down: a human decision is required label Nov 5, 2026 — with Claude
github-actions Bot pushed a commit that referenced this pull request Nov 5, 2026
…ER_TEST (ADR-1122) (#889)

Under the mutation gate, hook tests start the hook as a subprocess whose cwd
is a temporary project. The subprocess inherits MUTANT_UNDER_TEST, so
mutmut's trampoline looks for [tool.mutmut] in that cwd and aborts ("Could
not figure out where the code to mutate is"). The hook prints "Cortex not
ready", tests_py/hooks/test_entry.py fails, and the gate's stats run stops
for any PR that changes a module those tests import (seen on #865, run
42034218395, memory_store.py). hermetic_hook_env now drops the variable:
the hook process runs the original code, and the in-process tests judge
the mutants.


Claude-Session: https://claude.ai/code/session_018kyZYtyFK1iv4S9wZDjGoP

Co-authored-by: Claude <[email protected]>
github-actions Bot pushed a commit that referenced this pull request Nov 6, 2026
…e (ADR-1122) (#898)

* ci: run only the mutants of the changed functions in the mutation gate (ADR-1122)

mutation_check.sh ran `mutmut run` with no name, so mutmut ran every
mutant of each changed file and the gate judged the changed functions
afterwards. #865 (backend_resolution.py, 262 mutants) was cancelled at
the six-hour job limit after 141; #877 ran past two hours.

The script now passes one `<prefix>*` name per changed function
(scripts/ci_mutation_scope.py --globs). mutmut 3.8.1 asserts when no
mutant matches; after a failed run the gate passes only when mutmut's
stats file exists and its .meta files hold no mutant in scope, any other
failure fails the job. Without MUTATION_ONLY_PREFIXES, every mutant runs
as before.

Measured on #877's diff (local macOS arm64, PostgreSQL 18.6): 36 mutants
instead of 1,164, verdict in 21 min 15 s. No mutant hit mutmut's CPU
timeout, so no wall-clock cap is added.

* test(ci): keep the mutation scope test helper within four parameters

* ci: fail loudly when the glob helper fails, and require each target's meta for an empty scope

Review 1 of #898 notes:
- A failure of `ci_mutation_scope.py --globs` inside `< <(...)` did not
  stop the script: NAMES stayed empty and every mutant ran. The names are
  now read from an assignment, so `set -e` stops the gate.
- `--empty-scope` passed when the mutated file's `.meta` was missing. It
  now takes the mutated files and concludes only when each `.meta` exists;
  an empty file list is a usage error.
- The shell tests used rc=1 for the fake failures; they now use 3, so the
  script must forward mutmut's own exit code.
@cdeust cdeust removed the autopilot:hold CI autopilot stood down: a human decision is required label Nov 6, 2026

cdeust commented Nov 6, 2026

Copy link
Copy Markdown
Owner Author

Hold levé : f9f8074 (#898), fusionné sur main après le hold, fait muter par scripts/mutation_check.sh les seules fonctions modifiées, ce qui écarte les 262 mutants de tout memory_store.py en cause dans le blocage ; je rebase la branche sur origin/main.


Generated by Claude Code

cdeust commented Nov 6, 2026

Copy link
Copy Markdown
Owner Author

CI autopilot : rebase prêt localement, push refusé par cette session ; remise en hold.

  • Rebase de 129beff sur origin/main (499c69d2) avec autosquash : sans conflit, un seul commit 54d499b8. Le changement de tests_py/_hermetic_hook_env.py disparaît, car test(hooks): hook subprocesses do not inherit the parent's MUTANT_UNDER_TEST (ADR-1122) #889 (8bb0bd4) l'a déjà apporté sur main.
  • Vérifié localement sur le résultat : ruff check et ruff format --check passent sur les 20 fichiers Python de la PR. pytest sur test_no_default_database_url, test_sqlite_backend, test_standalone_commands_settle_backend, test_hermetic_hook_env et test_ci_mutation_scope donne 117 passés et 11 ignorés (pas de PostgreSQL ici).
  • git push --force-with-lease=fix/704-no-default-database-url:129beff… a été refusé par la politique de permission de la session automatique, avant d'atteindre GitHub. Je ne cherche pas à contourner ce refus.
  • Non vérifié : le gate de mutation avec ci: run only the mutants of the changed functions in the mutation gate (ADR-1122) #898 (périmètre réduit aux fonctions modifiées) sur memory_store.py, puisque PostgreSQL manque ici. scripts/check_craftsmanship.py signale aussi tests_py/infrastructure/test_standalone_commands_settle_backend.py (301 lignes, plafond 300), et cela dès 129beff : ce n'est pas le rebase qui l'introduit.

À faire par un humain : git fetch origin && git checkout fix/704-no-default-database-url && GIT_SEQUENCE_EDITOR=true git rebase -i --autosquash origin/main && git push --force-with-lease=fix/704-no-default-database-url:129beff23eb5be90187ea4a8ef1acd490d59a64b, ou autoriser ce push pour la routine. Le nouveau head lèvera ce hold.


Generated by Claude Code

@cdeust cdeust added the autopilot:hold CI autopilot stood down: a human decision is required label Nov 6, 2026 — with Claude
…(300-line cap)

They test backend_resolution.settle_for_command, not a standalone command; the
move brings test_standalone_commands_settle_backend.py from 301 to 263 lines.
@cdeust cdeust removed the autopilot:hold CI autopilot stood down: a human decision is required label Nov 6, 2026

cdeust commented Nov 6, 2026

Copy link
Copy Markdown
Owner Author

Hold autopilot:hold levé : le head 32ad0ff (poussé le 2026-11-06 à 10:38Z, après le hold de 01:27Z) déplace les tests settle_for_command et ramène test_standalone_commands_settle_backend.py sous le plafond de 300 lignes ; le check unit de ce head est encore en cours, l'autopilot y reviendra au prochain passage.


Generated by Claude Code

…a URL is SQLite (#704)

Check unit, run 42204224811 (32ad0ff): the ADR-1122 mutation gate left 122
survivors in the changed functions of memory_store.py (_construct_store 102,
_resolve_backend_url 16, _database_url_is_explicit 4). No test pinned the
selection's outcomes: which store, path and dimension, which URL is tried,
and the exact operator-facing error and warning texts.

test_construct_store_selection.py pins every branch with PostgreSQL and
SQLite replaced by recorders. Two code changes remove mutants no test can
kill: the strict-failure message no longer carries the dead
`url or '<unset>'` (url is non-empty there since #704 raises first), and
os.environ.get("DATABASE_URL") drops a "" default that `or` made
equivalent to None. The two remaining CORTEX_ALLOW_SQLITE_FALLBACK default
mutants are registered equivalents.

Local gate (mutation_check.sh, main's gate scripts, forkserver, only this
test file): 187 in-scope mutants, 185 killed, 2 registered equivalents.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018kyZYtrWzv6PGpJnXKSYPs

cdeust commented Nov 6, 2026

Copy link
Copy Markdown
Owner Author

CI autopilot : correctif du gate de mutation poussé en fixup! (8878f6d).

Diagnostic. Run 42204224811 sur 32ad0ff (ubuntu-26.04, Runner Provisioner 20261028.624) : unit finit sur « mutation: 6 file(s), 1 failed ». Le fichier en échec est memory_store.py, avec 122 survivants dans les trois fonctions que la PR modifie : _construct_store 102, _resolve_backend_url 16, _database_url_is_explicit 4. merge échoue parce que unit n'est pas vert. Aucun test ne fixait ce que produit la sélection du backend : le store retourné, son chemin et sa dimension, l'URL essayée, et le texte exact des erreurs et des avertissements. C'est un trou de tests réel, que la PR possède selon ADR-1122 §5. Ce n'est pas une panne d'infra.

Correctif (8878f6d, fixup! du commit #704, poussé sans rebase) :

  • tests_py/infrastructure/test_construct_store_selection.py : chaque branche de la sélection, avec PostgreSQL et SQLite remplacés par des enregistreurs.
  • memory_store.py : retrait de url or '<unset>', du code mort depuis que A profile with no configuration connects to a local PostgreSQL it was never configured for #704 lève une erreur avant quand l'URL est vide. Retrait aussi du défaut "" de os.environ.get("DATABASE_URL") : avec le or qui suit, ce défaut équivaut à None.
  • memory/mutation-equivalents.json : 2 équivalents par construction (_construct_store__mutmut_55 et _110). Remplacer le défaut "" de CORTEX_ALLOW_SQLITE_FALLBACK par "XXXX" ne change rien, car "xxxx" n'est pas une valeur d'opt-in.

Reçus locaux (Linux x86_64, Python 3.11, sans torch ni PostgreSQL) :

  • mutation_check.sh avec les scripts de gate de main (forkserver, MUTATION_ONLY_PREFIXES des 3 fonctions), restreint au seul nouveau fichier de test : 187 mutants dans le périmètre, 185 tués, 2 équivalents enregistrés acceptés, rc=0. Le premier passage sans les deux changements de code laissait 6 survivants.
  • 163 tests passent et 8 sont sautés (PostgreSQL absent) dans les fichiers de store et de backend.
  • Ruff est propre sur les fichiers touchés, et le craftsmanship gate est OK.

Non vérifié :

  • La CI de 8878f6d. Le gate CI lance tous les fichiers de test qui importent memory_store ; c'est un sur-ensemble du mien, il ne devrait donc tuer que davantage de mutants.
  • Les 5 autres fichiers du gate, qui étaient déjà verts.

La branche a 5 commits de retard sur main (#910, #911, #912, #891, #895). Le rebase avec autosquash est propre en local (diff identique), mais cette session ne peut pas faire de push --force-with-lease. La CI pull_request teste de toute façon le merge avec main.


Generated by Claude Code

…tabase-url

# Conflicts:
#	memory/mutation-equivalents.json
@cdeust cdeust added the claude-rereview ADR-1119: allows one more Claude review (a fourth) on this PR label Nov 6, 2026
…tabase-url

# Conflicts:
#	memory/mutation-equivalents.json
@claude

claude Bot commented Nov 6, 2026 •

Copy link
Copy Markdown

Claude finished @cdeust's task in 4m 32s —— View job


Review 4 (past the ADR-1119 cap of 3): PR #865, commits after 749bf05

B1 is still fixed. portability, migrate and wiki_migrate call settle_for_command before they build a store (backend_resolution.py:47-64).

Blocker

B2. The 25c27d2 sweep missed three commands. They still fall back silently to an empty SQLite store when the marker selects PostgreSQL (breaks the CLAUDE.md rule "no silent fallbacks", same failure as B1)

  • Where: scripts/groomer.py, scripts/cortex_ingest.py and scripts/dev_run_ingest.py. None of them calls settle_backend or settle_for_command. The sweep grepped scripts/ for get_shared_store, but these three reach the store through handlers:
    • groomer.py:58-59 → handlers/get_grooming_health.py:94 and handlers/curate_distill.py:136, both calling get_shared_store();
    • the ingest scripts → handlers/ingest_codebase.py:99-100.
  • Traced: the user has a PostgreSQL marker and no DATABASE_URL. This is the documented launchd job, scripts/com.cortex.scheduled-groomer.plist:48 (CORTEX_HEADLESS_AUTHORING=1 uv run python scripts/groomer.py --apply, no URL).
    • Nothing reads the marker, so STORE_BACKEND stays auto and DATABASE_URL is "" (memory_config.py:52).
    • _construct_store (memory_store.py:141, then :210) returns SQLite and prints no warning.
    • The groomer then judges grooming health and distils against an empty memory.db.
  • Before this PR: CLI auto became postgresql through the old default URL, which pointed at the marker's legacy server.
  • Fix: call settle_for_command("<name>") at the start of each main(), before the handler runs, the same way as the other nine scripts. Add these three to the parametrised structural test in test_standalone_commands_settle_backend.py. It would also be worth grepping scripts/ for handler imports that end in a store: the quality review flagged wiki_bulk_migrate.py, wiki_pilot_migration.py and wiki_backfill_ids.py as likely cases, but did not trace them.

Fix this →

Checked and fine:

  • Settings bind after the settle step. wire_composition_root and the module imports never call get_memory_settings() at import time.
  • SystemExit(1) inside asyncio.run exits with code 1.
  • The two A profile with no configuration connects to a local PostgreSQL it was never configured for #704 mutation equivalents (_55, _110) are real. "xxxx" is not an opt-in value.
  • url={url} is only reached with a non-empty URL, so dropping '<unset>' changes nothing.
  • Teardown order is right. The monkeypatch.undo() then environment restore order no longer leaks DATABASE_URL.
  • No new credential leak or per-call cost. settle_for_command runs once per process.

Notes, no action before merge

  • memory_store.py:172,192,197 print the raw URL, so a password in it is printed too. This is older than this PR; redact_url (used in doctor_mcp.py:418) could be applied there later.
  • PRIVACY.md:60-62 still leaves out the ADR-1104 first launch on a HOME with earlier state: it probes localhost:5432 and can choose PostgreSQL. This is unchanged since 749bf05 and was already noted in review 3.
  • The structural test in test_standalone_commands_settle_backend.py:111-113 compares where text first appears in the file, not the order of calls inside one function.

Merge scope

  • Accepted: every commit after 749bf05 (25c27d2, cc3804d, 4129b86, f944b6f, 129beff, 32ad0ff, 8878f6d) and all 25 files of the PR as they stand.
  • Where the open Blocker comes from: the default-URL removal in 4c5a624 (memory_config.py), together with the incomplete entry-point sweep in 25c27d2. The missing calls are in three files outside the current diff: scripts/groomer.py, scripts/cortex_ingest.py and scripts/dev_run_ingest.py.
  • No partial merge: merging the accepted part without them ships the regression. Each fix is about 4 lines per file and fits in one more fixup! on this PR.

Not verified:

  • I ran no tests. This job does not allow git fetch, gh or shell variables, so all of the above comes from reading the code at d034c79.
  • I posted this by updating this comment, because gh pr comment is not permitted in this job.
    · Branch

Verdict: block

…ore a handler opens the store (#704)

PR #865 review 4 (B2): scripts/groomer.py, scripts/cortex_ingest.py and
scripts/dev_run_ingest.py reach get_shared_store through handlers
(get_grooming_health, curate_distill, ingest_codebase) and never called
settle_for_command. With a PostgreSQL marker and no DATABASE_URL (the
documented launchd groomer job), they used an empty SQLite store, silently.
Each entry point now settles first and exits 1 when it cannot.

The earlier sweep relied on a list kept by hand. A new test scans scripts/
and requires settle_for_command in every script that imports a handler or
the store, with the two exemptions explained in the test.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018kyZYtyFK1iv4S9wZDjGoP

cdeust commented Nov 6, 2026

Copy link
Copy Markdown
Owner Author

Review 4 on d034c79, answered in 21fbc2f.

B2: confirmed and fixed. scripts/groomer.py, scripts/cortex_ingest.py and scripts/dev_run_ingest.py reach get_shared_store through handlers (get_grooming_health, curate_distill, ingest_codebase) and never settled the backend. Each one now calls settle_for_command("<name>") in its if __name__ == "__main__": block, before main(), and exits 1 when it cannot. I placed the call there rather than inside main() so that groomer.main does not grow.

Root cause, not just these three: the 25c27d2 sweep relied on a list kept by hand. The new tests_py/infrastructure/test_scripts_settle_backend.py scans scripts/*.py instead. It requires settle_for_command( in every script that imports from mcp_server.handlers or calls get_shared_store / MemoryStore(. Two scripts are exempt, each with its reason in the test:

  • collect_response_budget_payloads.py refuses to start unless DATABASE_URL names its own nonexistent socket.
  • wiki_bulk_migrate.py imports only handlers.wiki_rename, which has no get_shared_store and edits wiki files.

wiki_pilot_migration.py and wiki_backfill_ids.py, named as likely in the review, import no handler and no store.

Checks:

  • Without the script changes, 4 tests fail; with them, 25 pass.
  • 278 existing tests that mention these scripts pass.
  • ruff is clean, and check_craftsmanship.py is OK.
  • test_standalone_commands_settle_backend.py stays at 263 lines; the new file has 47.

Notes: the raw URL printed in memory_store.py:172,192,197 predates this PR, as the review says, and is left as is here.


Generated by Claude Code

github-actions Bot pushed a commit that referenced this pull request Nov 6, 2026
…122 item 6) (#917)

Owner decision 2026-11-06. mutmut names mutants by function, so the gate ran and
judged every mutant of each changed function: a one-line change owned the whole
function's survivors (#865: 102 in _construct_store; #878: 273 on the recall path).

scripts/ci_mutation_lines.py places each mutant on the source by diffing it
against mutmut's unmutated copy of the function, both indexed in
mutants/<path>.spans, and keeps it when it alters a line the PR changed (or the
anchor of a deletion and the next line). plan() gives each job its lines;
run_job passes them as MUTATION_ONLY_LINES; the --untested check and
mutation_equivalents.py judge only the kept mutants, so registry entries on
other lines are not stale. A mutant that cannot be placed stops the gate.

Checked against mutmut show on all 245 mutants of core/telemetry.py and all 35
of core/wiki_identity.py (methods included). End to end on a one-line change to
publish_record (PostgreSQL 18.6): 38 mutants ran, 20 survived on untouched
lines, the gate passes and reports them as not judged.

cdeust commented Nov 6, 2026

Copy link
Copy Markdown
Owner Author

CI autopilot : branche en retard de 2 commits sur main (eb5e9bd), non rebasée. Tous les checks sont verts sur le head actuel. Le rebase sur origin/main avec push --force-with-lease a été refusé par la politique de permissions de la session autopilot (pas par GitHub) ; je ne le contourne pas. À rebaser par un humain (ou laisser le gate de merge décider si le retard est acceptable).

Note : la branche contient des commits Merge remote-tracking branch 'origin/main' ; un rebase -i --autosquash direct entre en conflit sur CHANGELOG.md — un squash du tree fusionné sur origin/main donne un commit unique propre.


Generated by Claude Code

cdeust commented Nov 7, 2026

Copy link
Copy Markdown
Owner Author

CI autopilot : branche toujours en retard sur main (15 commits, main = 8b22316), non rebasée. Les checks sont tous verts sur le head f27f1c0.

J'ai préparé la mise à jour en local sans pouvoir la pousser : la session autopilot a refusé le git push --force-with-lease=fix/704-no-default-database-url:f27f1c0 (refus de la politique de permissions de la session, pas de GitHub). Je ne le contourne pas.

Pour la personne qui rebasera : un rebase -i --autosquash origin/main entre en conflit sur tests_py/_hermetic_hook_env.py (fixup f944b6f), parce que la branche contient des merges de main. Par contre, git merge-tree --write-tree origin/main origin/fix/704-no-default-database-url fusionne sans conflit (tree 7e8bda4e). Un commit unique sur origin/main qui porte ce tree garde exactement le diff de la PR (29 fichiers, +992/−38) :

git commit-tree 7e8bda4e1d2cc11e154e2fd410b34c28fd5b2088 -p origin/main -m "fix(storage): no default PostgreSQL URL in code; auto without a URL is SQLite (#704, slice 2)"

Vérifié en local sur ce commit : ruff check / ruff format --check sur les 26 fichiers Python de la PR OK, check_craftsmanship.py OK, les 6 fichiers de test de la PR passent (132 passés, 11 skips PostgreSQL), et tests_py/infrastructure/ + tests_py/handlers/ donnent 3286 passés et 3 échecs, tous dans test_embedding_engine.py (sentence_transformers absent ici : torch est bloqué par le proxy de la session, sans rapport avec la PR). Non vérifié : les tests PostgreSQL et la CI sur ce commit.


Generated by Claude Code

cdeust commented Nov 7, 2026

Copy link
Copy Markdown
Owner Author

CI autopilot : rebase sur main préparé mais pas poussé.

La branche a 15 commits de retard sur main (8b22316). Le rebase avec autosquash rencontre deux conflits mécaniques, tous deux résolus localement :

Après rebase : 3 commits, 150 tests de la PR et test_hermetic_hook_env.py passent (11 ignorés : PostgreSQL absent), porte craftsmanship OK.

Le push --force-with-lease=fix/704-no-default-database-url:f27f1c0 a été refusé par la politique de permissions de l'environnement de la routine, pas par GitHub. Un humain doit soit autoriser ce push, soit rebaser la branche lui-même. Le head de la PR reste f27f1c0, inchangé.


Generated by Claude Code

@cdeust

cdeust commented Nov 7, 2026

Copy link
Copy Markdown
Owner Author

Remplacée par #937 : même commit de tête (f27f1c09), ouverte pour une review supplémentaire exceptionnelle, décidée par le propriétaire le 2026-11-07. Les quatre reviews ADR-1119 de cette PR sont épuisées, et la quatrième bloquait sur B2, auquel 21fbc2f3 répond. La branche fix/704-no-default-database-url est conservée.

@cdeust cdeust closed this Nov 7, 2026
github-actions Bot pushed a commit that referenced this pull request Nov 7, 2026
…s SQLite (#704, slice 2) (#937)

* fix(storage): no default PostgreSQL URL in code; auto without a URL is SQLite (#704)

MemorySettings.DATABASE_URL defaulted to postgresql://127.0.0.1:5432/cortex,
so library callers with no URL and no marker (CLI-runtime auto,
cortex-migrate, the agent-briefing raw connection) reached whatever local
cortex database answered. The default is now empty; auto without a named
URL is SQLite in every runtime; an explicit postgresql backend with no URL
raises; _get_database_url refuses an empty conninfo and cortex-migrate
reports it on its exit-1 contract.

Refs #704

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018kyZYtyFK1iv4S9wZDjGoP

* fixup! fix(storage): no default PostgreSQL URL in code; auto without a URL is SQLite (#704)

fixup! fix(storage): no default PostgreSQL URL in code; auto without a URL is SQLite (#704)

Review B1 (PR #865, comment 6658250021): portability, migrate and wiki_migrate run as standalone commands and never read the backend marker. With no default URL, a PostgreSQL marker and no URL in the shell gave an empty SQLite store, silently. They now call backend_resolution.settle_for_command first, as mcp_server/__main__.py does. Also: migrate and memory_store docstrings, and the PRIVACY/CHANGELOG wording on which backends use the legacy URL.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018kyZYtyFK1iv4S9wZDjGoP

* fixup! fix(storage): no default PostgreSQL URL in code; auto without a URL is SQLite (#704)

fixup! fix(storage): no default PostgreSQL URL in code; auto without a URL is SQLite (#704)

Codex review on 749bf05 (comment 4480636738): the nine scripts/ maintenance commands that open the shared store (memory_staleness_revalidate, memory_reheat, memory_dedup_exact, and six more) now read the backend marker first through settle_for_command, like portability and migrate. Without it, a PostgreSQL marker with no URL gave them an empty SQLite store.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018kyZYtyFK1iv4S9wZDjGoP

* fixup! fix(storage): no default PostgreSQL URL in code; auto without a URL is SQLite (#704)

fixup! fix(storage): no default PostgreSQL URL in code; auto without a URL is SQLite (#704)

unit failed on 25c27d2 (run 42031311038, job 127258608806): test_migrate_reports_a_missing_url_on_its_exit_contract. migrate now settles the backend first. The fixture left CORTEX_MEMORY_STORE_BACKEND=postgresql in place: scripts/ci_unit.sh exports it, and tests_py/conftest.py replaces it with sqlite only when PostgreSQL is unreachable, which is why the test passed locally. settle_backend then supplied the legacy URL. The fixture now removes the backend variable, points the marker at an empty temporary methodology directory, and restores the environment whole. Reproduced locally by re-exporting the variable after conftest; passes with the fix.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018kyZYtyFK1iv4S9wZDjGoP

* fixup! fix(storage): no default PostgreSQL URL in code; auto without a URL is SQLite (#704)

fixup! fix(storage): no default PostgreSQL URL in code; auto without a URL is SQLite (#704)

Mutation gate (ADR-1122) on cc3804d, run 42034218395: kill the survivors in the functions this PR changes. Adds tests for settle_for_command, portability.main, migrate._run, _get_database_url and agent_briefing._connect. settle_for_command moves above the first function: when it was appended after resolve_auto_backend, the diff's trailing blank lines put that untouched function in scope.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018kyZYtyFK1iv4S9wZDjGoP

* fixup! fix(storage): no default PostgreSQL URL in code; auto without a URL is SQLite (#704)

fixup! fix(storage): no default PostgreSQL URL in code; auto without a URL is SQLite (#704)

Mutation gate (ADR-1122) on cc3804d, run 42034218395: for memory_store.py the stats run died on tests_py/hooks/test_entry.py. The hook subprocess inherits MUTANT_UNDER_TEST and runs mutmut's trampoline in a temp cwd with no [tool.mutmut] config ("Could not figure out where the code to mutate is"). hermetic_hook_env now drops the variable, so the hook process runs the original code and the in-process tests judge the mutants. Any PR that touches a module these hook tests import hits the same failure; the change is also proposed to main separately.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018kyZYtyFK1iv4S9wZDjGoP

* fixup! fix(storage): no default PostgreSQL URL in code; auto without a URL is SQLite (#704)

fixup! fix(storage): no default PostgreSQL URL in code; auto without a URL is SQLite (#704)

unit on f944b6f (run 42042293324, attempt 2): 35 failed, 204 errors, most of them 'no PostgreSQL URL configured' or KeyError 'DATABASE_URL' in later PostgreSQL tests. The no_named_url fixture pops DATABASE_URL, a test then monkeypatch.setenv's it, and monkeypatch's own teardown (which runs after the fixture's full restore) deletes the key it recorded as absent. CI's DATABASE_URL was gone for the rest of the session. Both fixtures now call monkeypatch.undo() before the restore. Reproduced with a probe test after these files (failed before, passes after); a full before/after environment comparison now differs only in PYTEST_CURRENT_TEST. The tree_sitter_language_pack DownloadError failures in the same run are GitHub fetches, not this PR.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018kyZYtyFK1iv4S9wZDjGoP

* test: move the settle_for_command tests next to backend_resolution's (300-line cap)

They test backend_resolution.settle_for_command, not a standalone command; the
move brings test_standalone_commands_settle_backend.py from 301 to 263 lines.

* fixup! fix(storage): no default PostgreSQL URL in code; auto without a URL is SQLite (#704)

Check unit, run 42204224811 (32ad0ff): the ADR-1122 mutation gate left 122
survivors in the changed functions of memory_store.py (_construct_store 102,
_resolve_backend_url 16, _database_url_is_explicit 4). No test pinned the
selection's outcomes: which store, path and dimension, which URL is tried,
and the exact operator-facing error and warning texts.

test_construct_store_selection.py pins every branch with PostgreSQL and
SQLite replaced by recorders. Two code changes remove mutants no test can
kill: the strict-failure message no longer carries the dead
`url or '<unset>'` (url is non-empty there since #704 raises first), and
os.environ.get("DATABASE_URL") drops a "" default that `or` made
equivalent to None. The two remaining CORTEX_ALLOW_SQLITE_FALLBACK default
mutants are registered equivalents.

Local gate (mutation_check.sh, main's gate scripts, forkserver, only this
test file): 187 in-scope mutants, 185 killed, 2 registered equivalents.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018kyZYtrWzv6PGpJnXKSYPs

* fix(scripts): groomer and ingest commands read the backend marker before a handler opens the store (#704)

PR #865 review 4 (B2): scripts/groomer.py, scripts/cortex_ingest.py and
scripts/dev_run_ingest.py reach get_shared_store through handlers
(get_grooming_health, curate_distill, ingest_codebase) and never called
settle_for_command. With a PostgreSQL marker and no DATABASE_URL (the
documented launchd groomer job), they used an empty SQLite store, silently.
Each entry point now settles first and exits 1 when it cannot.

The earlier sweep relied on a list kept by hand. A new test scans scripts/
and requires settle_for_command in every script that imports a handler or
the store, with the two exemptions explained in the test.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018kyZYtyFK1iv4S9wZDjGoP

* fixup! fix(storage): no default PostgreSQL URL in code; auto without a URL is SQLite (#704)

Review 1 of #937, blocker: PRIVACY.md said that with neither DATABASE_URL nor a
marker Cortex opens no PostgreSQL connection. That holds on a fresh install
only: on a HOME with earlier Cortex state and no marker, settle_backend ->
persist_first_decision -> decide probes localhost:5432 once and selects
PostgreSQL when something answers or the answer is unclear, then writes the
marker (ADR-1104). The text now says both.

---------

Co-authored-by: Claude <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

claude-rereview ADR-1119: allows one more Claude review (a fourth) on this PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants