Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions .bestpractices.json
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@
"build_floss_tools_justification": "pip, uv, hatchling, pytest, ruff and pyright are all FLOSS, and the build runs on ubuntu-latest and windows-latest GitHub runners.",

"test_status": "Met",
"test_justification": "An automated test suite of 6439 tests lives under tests_py/ (measured 2026-07-29 with 'pytest --collect-only -q'), covering core, handlers, infrastructure, integration, invariants and architecture: https://github.com/cdeust/Cortex/tree/main/tests_py",
"test_justification": "An automated test suite of 6526 tests lives under tests_py/ (measured 2026-07-29 with 'pytest --collect-only -q'), covering core, handlers, infrastructure, integration, invariants and architecture: https://github.com/cdeust/Cortex/tree/main/tests_py",

"test_invocation_status": "Met",
"test_invocation_justification": "The whole suite runs with a single 'pytest' command, documented in CONTRIBUTING.md under Testing along with per-layer subsets: https://github.com/cdeust/Cortex/blob/main/CONTRIBUTING.md",
Expand All @@ -114,7 +114,7 @@
"test_policy_justification": "CONTRIBUTING.md carries an explicit, mandatory testing policy: new functionality ships with tests in the automated suite, a bug fix carries a regression test that fails on the pre-fix code, and each failure path asserts its observable effect including the signal it emits \u2014 https://github.com/cdeust/Cortex/blob/main/CONTRIBUTING.md#the-testing-policy-mandatory. The per-tool checklist ('Add a unit test', 'Add an integration test if the tool touches the database') and the five-element mechanism checklist restate it per change type.",

"tests_are_added_status": "Met",
"tests_are_added_justification": "New functionality ships with its tests; the suite has grown to 6439 tests alongside the v4.x feature series, and CI runs it on every pull request.",
"tests_are_added_justification": "New functionality ships with its tests; the suite has grown to 6526 tests alongside the v4.x feature series, and CI runs it on every pull request.",

"tests_documented_added_status": "Met",
"tests_documented_added_justification": "The requirement is written into the documented instructions for change proposals: https://github.com/cdeust/Cortex/blob/main/CONTRIBUTING.md#the-testing-policy-mandatory states that every change adding or altering observable behaviour must arrive with tests in the same PR, the per-tool and per-mechanism checklists repeat it as a concrete step, and .github/PULL_REQUEST_TEMPLATE.md requires a Test plan section in every pull request.",
Expand Down Expand Up @@ -189,7 +189,7 @@
"static_analysis_often_justification": "CodeQL default setup analyses each push and pull request and additionally runs on a weekly schedule, so analysis happens per change rather than per release.",

"dynamic_analysis_status": "Unmet",
"dynamic_analysis_justification": "No dynamic analysis tool in the badge's sense (fuzzer, sanitizer, or scanner) is applied. The project runs a 6439-test suite with coverage on every change, but a test suite is not a dynamic analysis tool and is not claimed as one here.",
"dynamic_analysis_justification": "No dynamic analysis tool in the badge's sense (fuzzer, sanitizer, or scanner) is applied. The project runs a 6526-test suite with coverage on every change, but a test suite is not a dynamic analysis tool and is not claimed as one here.",

"dynamic_analysis_unsafe_status": "N/A",
"dynamic_analysis_unsafe_justification": "Cortex is written in Python, a memory-safe language, so the memory-safety tooling this criterion asks about (ASan, Valgrind) does not apply.",
Expand Down Expand Up @@ -297,13 +297,13 @@
"interfaces_current_justification": "The stack targets currently supported runtimes and APIs: Python 3.10 through 3.13, all four in the CI matrix, with pgvector 0.3+/PostgreSQL 17 and current major versions of FastMCP, Pydantic v2, numpy and sentence-transformers. Deprecated interfaces are removed rather than wrapped \u2014 the standing rule is one-shot migrations with no back-compat shims (CLAUDE.md), and CI runs on the newest released Python so a deprecation surfaces as a warning in the build rather than as a surprise at end of life.",

"automated_integration_testing_status": "Met",
"automated_integration_testing_justification": "The full suite runs on every push and pull request to main via .github/workflows/ci.yml and reports success or failure per job: 6439 tests on Python 3.10-3.13 against PostgreSQL + pgvector, the same suite against the SQLite backend, the suite on Windows, and a Docker smoke job that boots the bare container and exercises the DB-less contract. tests_py/integration/ holds the database-backed integration tests specifically.",
"automated_integration_testing_justification": "The full suite runs on every push and pull request to main via .github/workflows/ci.yml and reports success or failure per job: 6526 tests on Python 3.10-3.13 against PostgreSQL + pgvector, the same suite against the SQLite backend, the suite on Windows, and a Docker smoke job that boots the bare container and exercises the DB-less contract. tests_py/integration/ holds the database-backed integration tests specifically.",

"regression_tests_added50_status": "Met",
"regression_tests_added50_justification": "Measured on 2026-07-27 over the merged pull requests titled as fixes in the preceding six months (2026-01-27 onward): 23 of 30 \u2014 76.7% \u2014 changed the test tree in the same PR, against the 50% this criterion asks for. (The measure counts a PR as carrying tests when it touches tests_py/ or tests_js/, which is a proxy for 'added a regression test'; the policy behind it is written down at https://github.com/cdeust/Cortex/blob/main/CONTRIBUTING.md#the-testing-policy-mandatory \u2014 a bug fix carries a regression test that fails on the pre-fix code.)",

"test_statement_coverage80_status": "Met",
"test_statement_coverage80_justification": "Statement coverage is 82.02% (30,229 of 36,854 statements in mcp_server), measured by coverage.py 7.15.2 in the CI coverage job, run 30316539703 (2026-07-28) \u2014 above the 80% this criterion requires. The gap was closed by https://github.com/cdeust/Cortex/issues/196: the four zero-importer modules were wired-and-tested or removed with proof, and contract tests now cover the previously-uncovered context-assembly, hook, and wiki modules. The figure cannot silently fall back: the same CI coverage job enforces --cov-fail-under=82 as a floor, seeded at the achieved number.",
"test_statement_coverage80_justification": "Statement coverage is 82.02% (30,229 of 36,854 statements in mcp_server), measured by coverage.py 7.15.2 in the CI coverage job, run 30316526703 (2026-07-28) \u2014 above the 80% this criterion requires. The gap was closed by https://github.com/cdeust/Cortex/issues/196: the four zero-importer modules were wired-and-tested or removed with proof, and contract tests now cover the previously-uncovered context-assembly, hook, and wiki modules. The figure cannot silently fall back: the same CI coverage job enforces --cov-fail-under=82 as a floor, seeded at the achieved number.",

"test_policy_mandated_status": "Met",
"test_policy_mandated_justification": "https://github.com/cdeust/Cortex/blob/main/CONTRIBUTING.md#the-testing-policy-mandatory states it as policy, not preference: every change that adds or alters externally observable behaviour must arrive with tests in the same pull request, new functionality ships with tests in the automated suite, a bug fix carries a regression test that fails on the pre-fix code, and each failure path asserts its observable effect including the signal it emits. The consequence is stated \u2014 such a PR is sent back rather than merged with a follow-up promise.",
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ adheres to [Semantic Versioning](https://semver.org/).
- **Coverage-guided fuzzing** (closes the Scorecard Fuzzing alert). Two harnesses in `fuzz/` over pure parsers that read untrusted text (§13.1 D2 — LLM-generated content is untrusted): the hand-rolled YAML frontmatter parser and the wiki source-path canonicaliser. Wired to **ClusterFuzzLite** (`.clusterfuzzlite/`, `.github/workflows/fuzz.yml`) — a 120s batch on PRs that blocks, and a longer scheduled run that does not, because a fuzzer left running will eventually find something and holding the merge queue hostage to an unrelated input makes the check ignored within a week. Writing the path harness **found a live bug**: `normalize_source_path` stripped `./` in a loop and then `/` exactly once, so removing the slashes could expose a `./` the loop had already walked past — `.//./x` came out as `./x`, still carrying the prefix the function exists to remove, and not idempotent. `extract_document_paths` dedupes on that result, so one document reachable by two spellings counted as two. Fixed by iterating to a fixed point; the four reproducers are committed as corpus inputs and **fail on the pre-fix code**. `fuzz/replay_corpus.py` runs every corpus input through its harness with no atheris, so the properties execute in the ordinary `pytest` suite on every platform — atheris publishes manylinux x86_64 wheels for cpython 3.12–3.14 and nothing else, and a property only one CI job can run is one that rots.

### Fixed
- **A `created_at` that states a timezone was stored as the wrong instant** (#252). `normalize_date_to_iso` had no timezone policy on any of its paths, so three defects stacked: (1) the "already ISO" guard was the substring test `"T" in raw`, and every US zone abbreviation contains a T — `8 May 2023 13:56 EST` was returned unparsed; (2) the built-in fast path matches the date at the START of the string and discards the rest, so `8 May 2023 13:56 +02:00` became midnight, dropping both the time and the offset; (3) on the dateutil path an abbreviation it cannot resolve is dropped with a warning nobody sees, leaving a naive datetime that PostgreSQL's `timestamptz` cast and `compute_recency_boost` both read as UTC. The instant was up to a day off and nothing was emitted. A stated zone is now honoured or the value is refused: `mcp_server/core/temporal_timezones.py` supplies dateutil a `tzinfos` resolver over the **RFC 5322 §4.3 obs-zone table** (the normative answer to "which EST?" — cross-checked against CPython's `email._parseaddr._timezones`), and any abbreviation outside it is refused with a warning naming the input, the abbreviation and the fix, rather than defaulted. Parsing no longer depends on the host's local zone name, and no `warnings.catch_warnings()` — process-global and not thread-safe — is taken on a store write path. `normalize_date_to_iso` moves out of `core/temporal.py` into `core/temporal_normalize.py`: storage normalization must not lose precision, retrieval scoring may, and they are now separate modules (both stores import from the new path). The refusal also covers the degraded path — a string that states a zone is never salvaged to a naive date, whatever made the parse fail.
- **`python-dateutil` is now a declared dependency** (#252). `normalize_date_to_iso` has always parsed free-form dates with a time of day through it — the LoCoMo shape `1:56 pm on 8 May, 2023` its own docstring cites — but it was never declared and arrived only by transitive luck. It was absent from every `requirements/ci-*.txt`, so **every CI test job ran with that parser missing**: the fallback was dead code in CI, and any install resolving without it kept dates it could not read. Declared, locked and hash-pinned into the 9 exported requirement sets, so the write path behaves identically on every install and both backends.
- **Both stores skipped the normalization entirely for the dates that needed it most** (#252, the same substring defect one layer up). `PgMemoryStore._build_insert_params` and `SqliteMemoryStore._insert_memory_rows` each guarded the call with `"T" not in raw_created` as a cheap "is it already ISO?" test — so `8 May 2023 13:56 EST` (and every `PST`/`CST`/`MST` string) went to the database untouched, whichever way `normalize_date_to_iso` behaved. The guard is gone from both: deciding what is already ISO belongs to the function that owns it, which returns a real ISO datetime unchanged. Asserted on the stored row, not just on the parser, and asserted equal across the two backends.
- **Plugin installs bootstrapped a dependency set this repo no longer resolves** (found while wiring the above). `scripts/launcher_deps.py` carries the pin table the launcher pip-installs before the plugin's own dependencies exist, every row commented `# source: uv.lock` — and nothing verified it. 7 of the 9 rows had drifted: `fastmcp 3.2.4` (locked 3.4.5), `pydantic 2.13.3` (2.13.4), `pydantic-settings 2.14.0` (2.14.2), `psycopg 3.3.3` (3.3.4), `psycopg_pool 3.3.0` (3.3.1), `pgvector 0.4.2` (0.5.0 — the version whose psycopg loader change `pg_store._vector_to_bytes` is written for), `sentence-transformers 5.4.1` (5.6.1), plus a `numpy` marker split the two-branch constant no longer covered (2.4.4 is not in the lock at all). Realigned, and a test now asserts every pin is a version `uv.lock` records, with a negative control. Two prose copies of the same numbers — `docs/deployment-scenarios.md`'s container versions and a `pyproject.toml` comment — now point at the lock and its hashed exports instead of restating them.
- **`docker/Dockerfile` could not build at all**: it copied `/usr/local/lib/python3.12/site-packages` from the builder against a `python:3.14` base — a path absent from both stages since the base image moved off 3.12. Invisible because **no CI job built this image**; it now installs into a version-free venv (the rule the root Dockerfile already documents, from the incident where a literal `python3.13` path broke on every base bump). Both `docker/Dockerfile` and `.devcontainer/Dockerfile` gain build jobs in `ci.yml`, so the next such breakage is visible.
- **`scripts/setup.sh` reported success over any install failure**: the dependency install ended in `2>/dev/null`, which is where a resolution failure, a hash mismatch and a network error all appear, and the script printed "Python packages installed" regardless. Its exit status is now checked. Its hand-written package list — a duplicate of `pyproject.toml` — had already drifted from it, asking for `sentence-transformers>=2.2.0` against a real floor of `>=3.0.0`, and is replaced by the generated hashed file.
- **`.gitattributes` marks `fuzz/corpus/**` as binary**: end-of-line normalisation would have rewritten the CRLF corpus seed to LF on checkout, silently deleting the case that seed exists to cover.
Expand Down
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ coding write gates, causal graphs, and intent-aware retrieval.

- Install (dev): `uv pip install -e ".[dev]"` — SQLite backend: `".[dev,sqlite]"`
- Environment preflight: `python -m mcp_server.doctor` (backend-aware check list, fix message per check)
- Tests: `pytest` (full suite, 6439 tests) · `pytest tests_py/core/` (one layer) · `pytest --cov=mcp_server --cov-report=term-missing`
- Tests: `pytest` (full suite, 6526 tests) · `pytest tests_py/core/` (one layer) · `pytest --cov=mcp_server --cov-report=term-missing`
- Lint BEFORE every commit: `ruff check && ruff format --check` — the CI enforces **both**; passing only `ruff check` is not enough.
- Release gate benchmarks (isolated, ephemeral container — the only source
of truth for pre-tag/floor decisions): `benchmarks/reproduce.sh`. Do NOT
Expand Down
4 changes: 2 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ bash scripts/setup.sh # macOS / Linux
# Verify everything is wired
uvx --python 3.13 --from "hypermnesia-mcp[postgresql]" cortex-doctor

# Run tests (6439 tests under tests_py/)
# Run tests (6526 tests under tests_py/)
pytest

# Run a benchmark
Expand Down Expand Up @@ -134,7 +134,7 @@ The full standard lives in
## Testing

```bash
pytest # full suite (6439 tests)
pytest # full suite (6526 tests)
pytest tests_py/core # core (pure business logic) only
pytest tests_py/integration # PostgreSQL-backed integration
pytest tests_py/benchmarks -k locomo # subset
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
<a href="https://github.com/cdeust/Cortex/actions/workflows/ci.yml"><img src="https://github.com/cdeust/Cortex/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
<a href="LICENSE"><img src="assets/badge-license.svg" alt="License: MIT"></a>
<img src="assets/badge-python.svg" alt="Python 3.10+">
<img src="assets/badge-tests.svg" alt="6439 tests passing">
<img src="assets/badge-tests.svg" alt="6526 tests passing">
<img src="assets/badge-references.svg" alt="97 referenced papers">
<img src="assets/badge-version.svg" alt="Version 4.16.0">
<a href="https://www.bestpractices.dev/projects/13836"><img src="https://www.bestpractices.dev/projects/13836/badge" alt="OpenSSF Best Practices"></a>
Expand Down Expand Up @@ -526,7 +526,7 @@ Cortex is **local-first**: your memories, conversations, and profiles stay on yo
## Development

```bash
pytest # 6439 tests
pytest # 6526 tests
ruff check . # Lint
ruff format --check . # Format
python scripts/check_doc_claims.py # advertised counts must match the repo
Expand Down
8 changes: 4 additions & 4 deletions assets/badge-tests.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions docs/ASSURANCE-CASE.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ decoratively:

Standing analysis: CodeQL default setup (Python, JavaScript/TypeScript,
Actions) on every push and pull request plus weekly, currently 0 open alerts;
OpenSSF Scorecard via `.github/workflows/scorecard.yml`; and 6439 tests run on
OpenSSF Scorecard via `.github/workflows/scorecard.yml`; and 6526 tests run on
four Python versions, two backends and Windows.

## 6. What this assurance case does NOT claim
Expand All @@ -142,7 +142,7 @@ four Python versions, two backends and Windows.
source can be traced and purged.
- **The test suite is not yet strong enough to carry this argument alone.**
Statement coverage is 82.02% (measured in the CI coverage job, run
30316539703, 2026-07-28) and is held by a `--cov-fail-under=82` floor in
30316526703, 2026-07-28) and is held by a `--cov-fail-under=82` floor in
that job, but coverage proves execution, not detection: mutation testing
is wired for one module rather than the load-bearing set (the changed
code of #196 was mutation-triaged to zero non-equivalent survivors;
Expand Down
Loading