CI & Build / Python lint (push) Successful in 4s
CI & Build / Plugin hooks (push) Successful in 10s
CI & Build / TypeScript typecheck (push) Successful in 53s
CI & Build / integration (push) Successful in 55s
CI & Build / Python tests (push) Successful in 1m40s
CI & Build / Build & push image (push) Successful in 25s
rules_due_for_verification (the rule staleness sweep) and rule_history (what a rule used to say) are pure reads, and a read-scoped API key was refused both: neither is in _READ_ONLY_TOOLS, and the completeness test that should have caught it only looked at tools whose NAMES start like a read (get_, list_, search…). Neither does. - Both join _READ_ONLY_TOOLS; the comment that pointed at this issue now says why they sat unlisted. - _WRITE_TOOLS declares every writing tool by name. Nothing reads it at runtime — default-deny already refuses an unlisted tool — it exists so the classification is total. - test_every_registered_tool_is_classified_exactly_once takes its candidates from what build_mcp_server() actually mounts, requires each in exactly one of the three sets, and still flags a classified name that is no tool. The decision stays explicit; only the candidate set widened. - test_the_completeness_check_can_fail drops a real tool from its set and asserts it is noticed (rule 167). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01821k5B3Ysecp9fNYs92Kuy