test(tools): all-tools integration round-trip canary (follow-up to #157)#158
Merged
Conversation
Closes the unit-test-coverage gap that let memory_check_contradictions
ship to Fly with a hidden bug. Pre-existing test_server.py mocks every
external dep (LLM, NLI, embedder, vecstore), which is right for
isolated-contract testing but leaves a gap: a tool's real call path
can raise an uncaught exception that no mocked test exercises.
tests/test_tools_integration.py iterates the entire registered MCP
tool manager and invokes each tool against a real seeded vault with
minimal-valid inputs. Three test classes:
1. test_every_tool_invokes_cleanly — no unhandled exception, return
shape matches MCP contract (str/dict/list). Coverage-check
assertions force every newly-registered tool to have a fixture
input AND every removed tool to have its fixture cleaned up.
2. test_no_tool_returns_opaque_error_string — outputs must not
contain "Error occurred during tool execution" or "Internal
server error" verbatim. Opaque envelopes come from the MCP
transport wrapping an escaped Python exception — never
acceptable as a tool's own output.
3. test_destructive_tools_respect_dry_run — locks the dry_run
contract on memory_check_contradictions + memory_sweep. Mutating
inputs are stubbed to "would-decay" labels via mocked NLI; the
test asserts pre-state == post-state.
Heavy paths (NLI classify, FastEmbed re-embed) are stubbed so the
suite stays under ~17s; real-model paths are validated by
scripts/calibrate_capture_threshold.py and the operator Layer-3
web test ritual (extended in follow-up PR #158).
Catches the failure class that bit memory_check_contradictions on
2026-05-22: the LLM-required path would have raised under [server]
extras even with the broad try/except, and this canary's assertion
"no unhandled exception" would have fired on PR #154 when the
salience-tier tools were added — forcing the fix before merge.
Note: this PR doesn't yet exercise the [server]-only install matrix
(that's PR #158). It DOES catch any tool that raises through its
wrapper regardless of extras — the universal subset of the gap.
Composes with feedback_no_silent_fails: every tool must catch
failures at its boundary and return a clean error string, never
let an exception escape.
Suite 847 → 850 passing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 tasks
cipher813
added a commit
that referenced
this pull request
May 22, 2026
…#159) Follow-up to PR #158 — closes the [server]-extras gap that the local integration canary can't see + extends the operator Layer-3 web test ritual to probe every MCP tool against the live Fly app. Two artifacts: 1. .github/workflows/ci-server-extras.yml — installs mnemon-memory[server] ONLY (the Fly Docker install) + pytest as a separate test runner. Runs the full suite under that minimal install. Includes a guard that asserts llama-cpp-python is NOT installed under [server] — so future PRs can't accidentally drag the LLM dep into the production path. This is the workflow that would have caught memory_check_contradictions's LLM hard-dependency on PR #154 when the salience-tier tools were first added; ci.yml passed because [dev] installs everything. 2. scripts/promote_stable.sh layer3 --exercise-all-tools — opt-in flag that, after the test Fly app is up but before downgrade, iterates every registered MCP tool against the remote and asserts each returns cleanly. Catches Fly-specific breakage (missing baked models, Anthropic MCP proxy timeouts, transport regressions) that the local Python-level canary in tests/test_tools_integration.py can't see. Tool list resolved dynamically from mcp._tool_manager._tools, so tools added in future PRs are exercised automatically — no per- release maintenance burden. Per-tool inputs mirror the integration- test fixture; destructive tools (memory_forget, memory_rebuild) skipped; mutating tools constrained to dry_run / round-trip. scripts/_layer3_remote_helper.py gains an exercise-all-tools subcommand wired through the FastMCP tool manager. Two regression- lock tests added to tests/test_promote_stable.sh harness (13 → 15 passing) covering helper dispatch + flag plumbing through the bash dispatcher (cmd_layer3 "$@" forwarding + EXERCISE_ALL_TOOLS=1 set). Full Python suite still 850 passing. Driver: Brian's 2026-05-22 ask after the memory_check_contradictions incident — "given the difficulty of checking each individual mnemon tool available, are we properly using unit tests to confirm that everything works as expected?" PR #158 addressed the Python-level canary; this PR addresses the deployment-environment + Fly-level canary. Together they form the test trio for catching the 2026-05-22 failure class on the next PR rather than on the next operator MCP call. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4 tasks
cipher813
added a commit
that referenced
this pull request
May 22, 2026
…160) Closes Brian's "coverage to ≥80% + add a badge" leg of the test-trio plan (PRs #158/#159/#160). Suite at 86.43% with the new omits + 4 added nli.py error-path tests. pyproject.toml gains [tool.coverage.run] + [tool.coverage.report] config with fail_under=80. ci.yml runs `pytest --cov` so a PR that drops coverage below the gate fails the build. Excluded modules are documented inline and all under-testable-by-design: - dashboard/* Streamlit UI; tested by running the app - __main__.py 4-line entry-point shim - upgrade.py Real Fly+AWS interactions; tested by Layer-3 - downgrade.py Same - llm.py Deprecated optional-LLM path; the deployed product is LLM-free by design (2026-05-22). NLI replaced the only production use of this module in mnemon.contradiction. README badge: shields.io static `coverage-86%-brightgreen`. Matches existing static-badge pattern (Status, Python, License, MCP). Manual update on each release; no SaaS / codecov dep. 4 new nli.py tests (suite 850 → 855): - _ensure_loaded raises NLIUnavailableError on HF download failure - _ensure_loaded raises on unexpected label set (model with different output classes fails fast, not silently mis-classifies) - prewarm() swallows unavailability per acceptable-secondary- observability category - classify_pair softmax + input-building exercised with stubbed session (lines 164-189 of nli.py) Composes with the test-trio: PR #158 — Python-level integration canary (every tool round-trip) PR #159 — [server]-extras CI matrix + layer3 --exercise-all-tools PR #160 — this PR: coverage gate + badge Together they catch the 2026-05-22 memory_check_contradictions failure class at PR review time (canary + extras matrix) and ensure overall coverage doesn't regress as the project grows. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
3 tasks
cipher813
added a commit
that referenced
this pull request
May 22, 2026
Rolls the test-trio into a soak-substrate release: - #158 every-MCP-tool integration canary - #159 [server]-extras CI matrix + layer3 --exercise-all-tools - #160 coverage gate at 80% + README badge (86% baseline) Composes with the prior 0.7.0rc2 features (NLI-based contradiction detection, dry_run flag). After PyPI publish + Fly redeploy, this is the version operators should run the Phase 1 standing-tier soak on per Brian's "all known bugs fixed before soak" standard. Suite 855 passing. mnemon --version returns 0.7.0rc3. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds
tests/test_tools_integration.py— every-MCP-tool round-trip canary that catches the failure class of the 2026-05-22memory_check_contradictionsincident on Fly. This was intended to ship inside PR #157 (per Brian's "add 1 to pr157" directive) but #157 squash-merged before I could push the additional commit, so it lands here as a follow-up.Brian's question that drove this: "given the difficulty of checking each individual mnemon tool available, are we properly using unit tests to confirm that everything works as expected without having to manually run each tool?"
Honest answer pre-PR: no. Unit tests in
test_server.pymock every external dep (LLM, NLI, embedder, vecstore), which is right for asserting per-tool behavior in isolation but leaves a gap: a tool's real call path can raise an uncaught exception (or hang) that no mocked test exercises — the exact failure class that produced the opaqueError occurred during tool executionenvelope frommemory_check_contradictions.What ships
Three test classes covering the gap:
test_every_tool_invokes_cleanly— iteratesmcp._tool_manager._tools, calls each tool with minimal-valid inputs against a seeded vault, asserts no unhandled exception + return shape matches MCP contract (str/dict/list). Coverage-check assertions force every newly-registered tool to have a fixture input AND every removed tool to have its fixture cleaned up (registered-vs-fixture diff).test_no_tool_returns_opaque_error_string— outputs must not containError occurred during tool executionorInternal server errorverbatim. Opaque envelopes come from the MCP transport wrapping an escaped Python exception — never acceptable as a tool's own output.test_destructive_tools_respect_dry_run— locks thedry_runcontract onmemory_check_contradictions+memory_sweep. Mutating inputs stubbed to "would-decay" labels via mocked NLI; pre-state == post-state.Heavy paths (NLI classify, FastEmbed re-embed) are stubbed so the suite stays under ~17s. Real-model paths validated separately by
scripts/calibrate_capture_threshold.pyand the operator Layer-3 web test ritual (which gets--exercise-all-toolsmode in PR #159, per Brian's follow-up plan).Composability
memory_check_contradictionson 2026-05-22 (the LLM-required path would have raised through the broad try/except, and "no unhandled exception" would have fired on PR feat(salience): Phase 1 — first-class standing tier (default-off, soak-gated) #154 when the salience-tier tools were added — forcing the fix before merge).feedback_no_silent_fails: every tool must catch failures at its boundary and return a clean error string, never let an exception escape.[server]-only install matrix (that's PR ci(server-extras): [server]-only matrix + layer3 --exercise-all-tools #159). It catches any tool that raises through its wrapper regardless of extras — the universal subset of the gap.Test plan
Two follow-ups already filed in this session per Brian's plan:
[server]-extras-only CI matrix + extended Layer-3 with--exercise-all-toolsAfter all three follow-ups merge, soak substrate is ready per Brian's "known bugs fixed before soak" standard.
🤖 Generated with Claude Code