Repository navigation
fix(profiles): a rescan never lowers a domain's accumulated session evidence - #697
Conversation
|
ZETETIC-REVIEW: REQUEST_CHANGES Scope: code, tests, text. Worktree .claude/worktrees/review-697 (detached at the head). Load was 2-3 during the runs; only targeted tests. BLOCKING
NON-BLOCKING
VERIFIED (command/source)
NOT VERIFIED
|
…vidence rebuild_profiles replaced every scanned domain's stored profile with one computed from the transcripts currently on disk. Old transcripts are deleted by the host's default retention, so that is a sliding window, while the stored profile accumulates one session per record_session_end. A forced rebuild turned the cortex domain from 32 recorded sessions into 8 on the owner's machine. A domain whose scan sees fewer sessions than the stored profile records is now kept untouched and reported; a scan that sees at least as many rebuilds as before. replace_accumulated_profiles is the explicit, destructive override (force only bypasses the freshness check). The result carries domainOutcomes per scanned domain with stored, scanned and resulting counts. A non-integer stored sessionCount stops the rebuild instead of reading as 0. profile_assembler.py shrinks under the 300-line cap (helpers moved to profile_domain_stats.py and profile_domain_grouping.py) and its baseline entry is pruned. The record_session_end tool description no longer calls rebuild_profiles a from-scratch rescan. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DXMXvHLm94B6pA9FkWWmzu Signed-off-by: cdeust <cdeust@icloud.com>
…ted session evidence Registers the decision behind the rebuild_profiles change in this branch. It amends ADR-0429 and ADR-0227. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DXMXvHLm94B6pA9FkWWmzu Signed-off-by: cdeust <cdeust@icloud.com>
…pin kept bridges The policy module cites ADR-1099. rebuild_profiles now carries the DESTRUCTIVE tool annotation because replace_accumulated_profiles can destroy accumulated state. A new test pins that a kept domain's connectionBridges are not recomputed and fails when the guard is removed. The profile_builder docstring no longer claims a re-export that does not exist. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DXMXvHLm94B6pA9FkWWmzu Signed-off-by: cdeust <cdeust@icloud.com>
f64ab9a to
2b243a9
Compare
…e registered tool The accumulation tests call the handler directly and the schema parity test only compares parameter sets, so a wrapper that kept the parameter and forwarded a constant passed the whole handlers directory. The wiring bug found earlier in this change was exactly that class. Two tests now call rebuild_profiles through mcp.call_tool and read the stored profile back: true replaces the accumulated profile, force=true alone keeps it. Forwarding a hard-coded False fails the first; forwarding a hard-coded True fails the second. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DXMXvHLm94B6pA9FkWWmzu Signed-off-by: cdeust <cdeust@icloud.com>
|
ZETETIC-REVIEW: APPROVE Scope: code, tests, text. Delta 2b243a9..8d6cf03. Worktree review-697c (detached, arm64 python, removed afterwards). uptime 2.1 before runs; targeted tests only; ps shows no pytest left. BLOCKING: none. The earlier required finding (no test through the registered wrapper) is closed. VERIFIED
NON-BLOCKING (nit)
NOT VERIFIED |
rebuild_profiles no longer lowers a domain's accumulated session evidence: a domain whose scan sees fewer sessions than the stored profile records is kept and reported, and a deliberate replacement needs the explicit parameter replace_accumulated_profiles.
Symptom
Measured on the owner's machine on 2026-10-10: a forced rebuild turned the cortex domain from 32 recorded sessions (an April backup of the same key) into 8, the number of transcripts still on disk. Old transcripts are deleted by default retention, so the disk is a sliding window while the profile accumulates one session per record_session_end. The tool description said "rebuild from scratch" and force only bypasses the one-hour freshness check, so nothing told a caller that it replaces.
Root cause
mcp_server/core/profile_assembler.py, build_domain_profiles, old line 295:
profiles["domains"][domain_id] = _build_single_domain(domain_id, data)replaced the stored profile for every domain with at least one transcript, with sessionCount = len(convs) of the scan.Fix
The two checks the issue asks for
Precision on kept domains and out of scope
save_profiles rewrites every domain file of the store with its content and bumps updatedAt, so a kept domain's file is rewritten with identical content; only its in-memory profile is guaranteed untouched. The whole-store read-modify-write race with a concurrent record_session_end exists on main too and is out of scope here; it should be a follow-up issue (owner to file).
Decision record
ADR-1099 is in this PR (wiki page, docs mirror, manifest); the policy module cites it. rebuild_profiles now carries the DESTRUCTIVE tool annotation from _tool_meta.py because replace_accumulated_profiles can destroy accumulated state. A new test pins that a kept domain's connectionBridges are not recomputed; with the guard removed it fails (raw output:
assert ['fresh-bridge'] == ['stored'])._check_skip (issue 685)
Not touched. The fix does not need it; the freshness check keeps its current behaviour.
Failing before, passing after
Before, on main with the new tests (raw output in the first lines):
After, on the final head 8d6cf03, full suite
pytest -q -p no:cacheprovider(raw output in /Users/cdeust/.claude/fleet/work/Cortex/A2-full-pytest.txt):Other gates on 8d6cf03, all pass:
uvx ruff@0.16.6 check,uvx ruff@0.16.6 format --check,python scripts/check_craftsmanship.py --base origin/main(prints OK),python scripts/check_project_wiki.py,python scripts/check_no_deps_invariant.py,pyright mcp_server/(0 errors, environment from uv.lock with the typecheck group and the otel extra, arm64 Python).Registry value flow (review round 2): TestRegisteredToolPath calls
rebuild_profilesthroughmcp.call_tooland reads the stored profile back. Mutant that forwards a hard-coded False in tool_registry_core.py:test_true_reaches_the_handler_and_replacesfails (1 failed, 25 passed, A-mutant-registry-false.txt). Mutant that forwards a hard-coded True:test_force_alone_keeps_the_accumulated_profilefails (A-mutant-registry-true.txt). Both pass on the real wrapper.Completion Ledger
All handler tests use a temporary directory for the scanner root and the methodology store; nothing reads or writes the real home directory.
Decision points for an ADR
(Registered as ADR-1099.)
Not verified
Windows. The load average on the machine was above 100 while the suite ran, so only the pass/fail result is meaningful, not the duration.
Fixes #686
🤖 Generated with Claude Code
https://claude.ai/code/session_01DXMXvHLm94B6pA9FkWWmzu