feat(doctor): implement check_mandate_backend (#235)#296
Merged
Conversation
Closes the documented-but-unimplemented doctor check that the #124 MandateBackend arc declared in CLAUDE.md but never shipped in atomic_agents/doctor.py. The new scope-scoped Protocol-coherence check mirrors check_policy_backend and check_persona_backend with five documented divergences from the closest sister (check_policy_backend): - No cascade kwarg. Mandate's two-tier scope (project-level <scope_root>/mandates.md vs per-agent <scope_root>/<agent>/mandates.md) is handled inside the backend via the scope: str parameter; the doctor only probes project scope. Policy's cascade-aware scope warning at #236 is policy-specific. - Probe scope is "project:doctor" rather than the empty string. Empirically, _parse_scope("") rejects empty input; the project: kind discards the name component in _mandates_path so any non-empty "project:<name>" reads <scope_root>/mandates.md regardless of name. - Probe calls list_mandates() in addition to capabilities(). Policy's Protocol has no list method so it only probes capabilities; Mandate has list_mandates() so the probe surfaces a real mandate_count in the detail dict matching check_persona_backend's shape. - Single except Exception block in the filesystem branch. Sister checks (persona, tool-registry, profile) all use a single typed-or-generic except. The dual MandateError / Exception split surfaced in Round 1 adversarial review as Bucket-C noise with no safety gain (filesystem backends do not embed URL credentials in their exception messages). - _redact_for_error_message imported from mandate.backend for the unknown-id FAIL credential-redaction path, mirroring persona's import shape. PASS / WARN / FAIL ladder: - PASS when ATOMIC_AGENTS_MANDATE_BACKEND is unset or "filesystem" and the default backend constructs + capabilities() + list_mandates() return cleanly with mandates.md present. Detail carries the full capability snapshot plus mandate_count + mandates_md_exists + resolved_path. - WARN when construction succeeds but mandates.md is absent at scope_root — no operator-granted authorities at this scope. Informational so single-agent home users don't see a noisy FAIL. - FAIL when ATOMIC_AGENTS_MANDATE_BACKEND is set to an id not in list_mandate_backends(). The echoed env value is redacted at :// via mandate.backend._redact_for_error_message so an operator who accidentally pasted a credential-bearing URL into the id env var doesn't leak through doctor output. - FAIL when the registered backend's factory raises during construction (credentials dropped from the surfaced exception text; the verbatim exception is not included in either message or fix_hint). - WARN when construction succeeds but capabilities() or list_mandates() raises — backend reachable but probe surface degraded. Matches the sister WARN-on-unreachable-probe pattern. Adds 5 tests at tests/test_doctor_check_mandate_backend.py: - test_check_mandate_backend_passes_with_no_mandates_md (WARN-no-md) - test_check_mandate_backend_passes_with_mandates_md (PASS path) - test_check_mandate_backend_fails_on_unknown_env_var - test_check_mandate_backend_fails_on_unknown_env_var_url_credential_redaction - test_check_mandate_backend_capability_snapshot_in_detail Wires check_mandate_backend(resolved_root) into run_doctor at the position matching spec/27 catalogue order (between check_tool_registry_backend and check_policy_backend). Adds "mandate-backend" to the agent-name-None SKIP enumeration so the new scope-scoped check runs in --scope-only mode alongside its sisters. Test suite: 2686 → 2691 passing + 48 skipped. Zero regressions on the 166 existing AtomicAgent(...) construction sites. ruff check + ruff format --check both clean on atomic_agents/doctor.py. Methodology: pre-impl prep dispatched one Sonnet implementation agent with verified MandateCapabilities field names + closest-analog cite + test pattern reference. Step 11 Opus adversarial 2 rounds: Round 1 caught 1 P1 (spec/27 missing WARN-on-md-absent bullet) + 2 P2 (dual except redundant + docstring cite to wrong spec section); all folded. Round 2 verified the 3 fixes correct + caught 1 P2 (CHANGELOG missing entry, addressed in the sibling commit). Round 2 closing rec: "fix and ship; no Round 3 required" matching the project's 2-round convergence pattern. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…aimer + CHANGELOG arc-close (#235) The disclaimer at docs/spec/27-doctor.md:386-393 was added in PR #295 (consolidated doc-debt sweep) pointing at this issue because check_mandate_backend was documented in the spec but missing from atomic_agents/doctor.py. With the impl now landed in the sibling commit, the disclaimer is removed; spec/27 describes shipped behavior again. Also adds a third WARN ladder bullet to the spec/27 mandate-backend entry documenting the WARN-on-mandates.md-absent path. Round 1 Opus adversarial caught the gap: the impl emits this WARN but spec/27 only documented the capabilities()-probe-failure WARN. Spec/27's PASS / WARN / FAIL ladder now matches the impl 1:1 across all 6 paths (2 PASS + 2 FAIL + 2 WARN), mirroring policy-backend's analogous bullet for the policy.md absent case. CHANGELOG [Unreleased] gains an Added bullet covering the impl, the spec/27 disclaimer removal, the test additions, and the methodology trail (Step 11 Opus adversarial 2 rounds converging at "fix and ship"). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
dep0we
added a commit
that referenced
this pull request
Jun 1, 2026
…RON RULE regression suite (#304) * feat(corpus): #65 PR 3 of 4. env-var resolution + CLI env-var swap corpus/__init__.py: add sqlite branch to get_default_corpus_backend so ATOMIC_AGENTS_CORPUS_BACKEND=sqlite resolves to SQLiteCorpusBackend with default db at <agent_root>/.corpus.db, agent_scope=<agent_root.name>. Mirrors profile/__init__.py:227-235 precedent. Empty-string env var normalizes to filesystem. Wraps sqlite construction in (OSError, PermissionError) for clean operator-facing error. cli.py: replace hardcoded FilesystemCorpusBackend(agent_root) in _cmd_corpus with get_default_corpus_backend(agent_root) so CLI honors ATOMIC_AGENTS_CORPUS_BACKEND env var (closes silent CLI-vs-runtime drift). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * feat(corpus): #65 PR 3 of 4. agent.py wiring (flag + delegate + OSError catch) Constructor adds corpus_backend kwarg + class-level annotation. Resolves via get_default_corpus_backend(self.agent_root) when not supplied. Mirrors PersonaBackend D-ER-2 explicit-only threading: _corpus_backend_was_explicit flag saved to self, consumed at delegate() for conditional kwarg insertion. _load_indexes() at agent.py:2933-2985 routes wiki/INDEX.md read through CorpusBackend Protocol when configured. Legacy direct-read fallback now catches OSError and returns empty string with a logged warning marker wiki_index_unreadable, matching FilesystemCorpusBackend.render_index_summary behavior at corpus/filesystem.py:701-702. Brings both code paths into behavioral agreement so the IRON RULE byte-identity assertion holds. NOTE: legacy direct-read previously propagated OSError. This is an intentional behavior change. Operators with a wiki/INDEX.md that becomes briefly unreadable now see a logged warning and an empty wiki section rather than a hard crash at agent construction. delegate() at agent.py:4628-4629 threads corpus_backend ONLY when the operator supplied it explicitly. Default-resolved backends do not leak the coordinator's content_root to delegates (corpus is per-agent semantic context, not fleet-scoped). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * feat(corpus): #65 PR 3 of 4. bundle.py 3-level threading + byte-identity helper render_bundle, _render_sections, and _render_memory_breakpoint all gain corpus_backend: CorpusBackend | None = None parameter, threaded through all three call levels. When corpus_backend is None at any caller, the fallback path uses the legacy direct file read. New private helper _render_wiki_index_section(label, path, content) produces the bundle section in the canonical ## {label}\n\`{path}\`\n\n{content} format used by _render_file_section. Both the corpus_backend Protocol path AND the legacy fallback path call this helper with the same logical wiki path so byte-identical output is guaranteed regardless of which path produced the content. Closes the IRON RULE assertion 4 risk. Both branches apply .strip() to match _render_file_section's _safe_read_text(...).strip() behavior. Skip the section when content is empty (no file or empty file). _source_paths at bundle.py:266 gets a TODO(v1.1) comment noting the deferred Protocol routing (filesystem-only function; SQLite has no equivalent path to track). Follow-up issue filed at PR 4. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * feat(corpus): #65 PR 3 of 4. per-runner corpus_backend kwargs OutcomeRunner (outcome.py:255) and EvalRunner (eval.py:363) each accept corpus_backend: CorpusBackend | None = None and thread it to their internal AtomicAgent construction site. Mirrors the per-runner kwarg shape locked at #63 PR 2 (AgentProfileBackend) and #62 PR 2 (PersonaBackend). DreamRunner accepts the kwarg for API parity but does NOT thread it to any internal AtomicAgent construction site (none exists in v1). Stored as self._corpus_backend with a comment matching the existing DreamRunner pattern for the other 4 backend kwargs (_policy_backend, _persona_backend), documenting the future-state threading site. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * feat(corpus): #65 PR 3 of 4. doctor.check_corpus_backend PASS/WARN/FAIL ladder 12th check_*_backend implementation in doctor.py. Mirrors check_mandate_backend shape (the most recently merged precedent from #296). PASS: backend constructs successfully + stats("wiki") and stats("raw") both return without raising. Capability snapshot in detail dict (backend_id, supports_full_text_search, supports_semantic_search, supports_versioning, embedding_provider, wiki_page_count, raw_page_count). WARN: 1. supports_full_text_search=False AND wiki_page_count > 1000 OR raw_page_count > 1000 (the page-count cliff WARN per /plan-eng-review 2026-05-29 finding P1). Hint names ATOMIC_AGENTS_CORPUS_BACKEND=sqlite as the remedy. WARN: 2. ATOMIC_AGENTS_CORPUS_BACKEND_URL set but ATOMIC_AGENTS_CORPUS_BACKEND not. URL silently ignored otherwise; surface this misconfiguration explicitly. FAIL: backend cannot be constructed, OR stats() raises. URL credential redaction via existing _redact_for_error_message helper used by the other doctor checks. Probes BOTH wiki and raw corpora (the page-count cliff WARN fires if either exceeds the threshold). Registered in run_all_checks dispatch. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * test(corpus): #65 PR 3 of 4. IRON RULE regression suite + wiring + doctor 35 net new tests + 2 augmented existing integration tests. NEW tests/test_corpus_composition.py (4 tests): _corpus_backend_was_explicit flag tracking + delegate explicit-only threading. NEW tests/test_corpus_migration_regression.py (5 tests): the IRON RULE suite. Assertions 1-4 verify byte-identity between corpus_backend=None fallback path and corpus_backend=FilesystemCorpusBackend Protocol path at both agent.py:_load_indexes and bundle.py:_render_memory_breakpoint call sites. Assertion 5 (full pre-#65 suite passes unchanged) is a CI criterion documented in the PR body. Plus 1 OSError catch test exercising the new legacy-path soft-degrade behavior. NEW tests/test_corpus_wiring.py (13 tests): env var resolution (filesystem default, sqlite default, URL override, empty-URL fallback, empty-backend-treated-as-unset, whitespace padding, filesystem URL, agent_root empty name guard) + per-runner kwarg storage (Outcome, Eval, Dream) + OutcomeRunner threading + CLI env-var activation. NEW tests/test_corpus_doctor.py (11 tests): PASS/WARN/FAIL ladder discrimination across capability conditions + page-count cliff WARN on both wiki and raw corpora + URL-without-backend WARN + construction-fail FAIL + unwritable-path FAIL + capability snapshot completeness + URL credential redaction + run_all_checks integration. AUGMENTED tests/test_agent_cascade_integration.py: _build_full_cascade_layout fixture writes real wiki/INDEX.md content + assert wiki section header + body content + section ordering after memory INDEX. Closes silent-corruption risk class flagged by /plan-subagent S4 (9 wiki-touching tests created empty wiki dirs with ZERO INDEX content assertions). AUGMENTED tests/test_cascade_bundle.py: end-to-end render_bundle threading test asserting byte-identity between Protocol path and fallback path + _source_paths v1.1 deferral guard test (pins the deferral decision mechanically; a future premature Protocol-routing of _source_paths would fail this test). Total suite: 2853 -> 2888 passing + 48 skipped. Zero regressions to the pre-#65 surface. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(corpus): #65 PR 3 of 4. Round 1 adversarial review fixes + CHANGELOG Round 1 adversarial review (Claude subagent + pre-landing review) caught 8 high-confidence findings + 2 INFORMATIONAL pre-landing findings. All addressed in this commit. Round 2 + Round 3 to follow under /ship. corpus/__init__.py SQLite branch: - URL-encode agent_root.name via quote_plus so names containing URL metacharacters (spaces, +, &, ?, =) do not silently corrupt agent_scope or raise ValueError. A name like "my+agent" decoded as "my agent" via parse_qsl, causing cross-scope contamination with a real agent named "my agent". - Widen the construction try/except from (OSError, PermissionError) to Exception. Re-raise as CorpusBackendNotRegistered with the URL remedy. Covers ValueError (malformed URL, invalid charset) and sqlite3.OperationalError (db locked at cold start, WAL transition failure on NFS) that previously escaped as raw library exceptions. PermissionError is redundant (subclass of OSError) so it drops. corpus/filesystem.py render_index_summary: - Add UnicodeDecodeError to the except clause. Pre-PR-3 bundle.py used _safe_read_text which catches UnicodeDecodeError; the Protocol path did not. A wiki/INDEX.md with non-UTF-8 bytes (Latin-1, BOM, mixed encodings) would crash agent construction via the Protocol path where the legacy bundle path gracefully degraded. Now at parity. agent.py _load_indexes: - Add broad try/except around the Protocol path call to corpus_backend.render_index_summary. Soft-degrade to empty string with a logged wiki_index_unreadable warning so any custom-backend exception (sqlite3.OperationalError, CorpusError, KeyError) does not crash agent construction. Matches the legacy direct-read soft degrade behavior. - Update comment on the legacy direct-read else-branch: noting it is unreachable in production after Stream B's default-resolution at __init__ (self.corpus_backend is always non-None). Retained as a safety net for future refactors that remove the auto-resolve. Exercised by tests in test_corpus_migration_regression.py that force corpus_backend=None post-construction. doctor.py check_corpus_backend: - Rewrite the URL-without-backend WARN message. Pre-fix message said "the URL is being ignored" which was factually wrong: when backend unset and URL set, get_default_corpus_backend normalizes to filesystem and routes the URL through make_filesystem_corpus_backend_from_url. The URL is USED. New message describes the implicit-default state and recommends explicit binding. - Update the docstring at line 2438-2447 to match (drop the misleading "or resolves to filesystem" qualifier). - Replace bare assert wiki_stats is not None / assert raw_stats is not None at line 2583-2584 with a defensive conditional that returns CheckResult(status=FAIL) on the logically unreachable None state. Preserves the always-returns-CheckResult contract under python -O optimized builds. tests/test_corpus_doctor.py: - Update test_check_corpus_backend_warns_url_without_backend_id to assert on stable substrings rather than the verbatim previous message text. Matches the new WARN wording. CHANGELOG.md: - Add two PR 3 entries to [Unreleased] section: main PR 3 wiring entry + Round 1 adversarial fix entry, plus two Changed entries documenting the agent.py legacy-path OSError catch behavior change and the cli.py CLI env-var honoring behavior change. Full pytest suite: 2853 -> 2888 passing, 48 skipped, zero regressions. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(corpus): #65 PR 3 of 4. Round 2 adversarial review fixes Round 2 hunted in the Round 1 fix commit (ad13220) per CLAUDE.md rule 11 ("each fix changes the diff and exposes new edges"). Caught 3 MEDIUM + 4 LOW findings introduced by Round 1. F3, F4, F5 applied as FIXABLE + F6 closed as a coverage gap. F1, F2, F7 defended as trade-off calls. R2-F3 (FIXABLE, was MEDIUM): - corpus/filesystem.py render_index_summary returned "" on UnicodeDecodeError, silently losing wiki body content where the pre-PR-3 bundle.py _safe_read_text preserved partial content. Round 1 CHANGELOG claimed "matches the pre-#65 behavior" but the code did NOT. - Rewrite to match _safe_read_text exactly: re-read with errors="replace" + prepend the same warning comment shape used by _safe_read_text. The Protocol and legacy paths now produce truly symmetric output for the Unicode case. - Split the except clause into separate UnicodeDecodeError and OSError branches because they have different soft-degrade behavior (UnicodeDecodeError has partial content available, OSError does not). R2-F4 (FIXABLE, was LOW): - doctor.py:2476-2481 comment still said the URL was "silently ignored" after Round 1 fixed that. Updated to describe the post-fix behavior accurately: URL is honored via the filesystem factory; binding is implicit; the WARN surfaces the implicit-default state. R2-F5 (FIXABLE, was LOW): - doctor.py:2588-2598 defensive-conditional FAIL detail dict carried only backend_id, dropping the capability snapshot fields already available in caps. Operators debugging the (logically-unreachable) None state had no context. - Expand the dict to include supports_full_text_search, supports_semantic_search, supports_versioning, embedding_provider. R2-F6 (INVESTIGATE -> closed via new test): - No test exercised the Protocol-path except Exception branch added in Round 1; only the legacy-path OSError catch had a test. - Add test_agent_load_indexes_protocol_path_exception_soft_degrades to tests/test_corpus_migration_regression.py. Uses a _RaisingCorpusBackend stub whose render_index_summary raises sqlite3.OperationalError. Verifies _wiki_index_text == "" + log marker + backend-class-name in the warning message. Defended: - F1 (broad except in corpus/__init__.py misdirects with sqlite-URL hint on non-storage errors): cause type is included in error message so developers can debug; the production stability trade-off is the right default. - F2 (Protocol-path broad except silently degrades on programmer errors like AttributeError): same trade-off; the logged wiki_index_unreadable warning is observable; strict-fail behavior is a follow-up env var (ATOMIC_AGENTS_CORPUS_STRICT) for a future PR. - F7 (sqlite-specific URL remedy in error message): scoped correctly inside the sqlite branch only. CHANGELOG updated with the Round 2 fix bullet under [Unreleased]. Test suite: 2888 -> 2889 passing + 48 skipped, zero regressions. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * docs(corpus): #65 PR 3 of 4. Round 3 R3-F1 documenting comment Round 3 R3-F1 (LOW, 7/10): the legacy direct-read else-branch in _load_indexes catches OSError but not UnicodeDecodeError. The branch is unreachable in production after PR 3 default-resolution, so adding a catch would be dead code. Document the gap in the comment instead so a future contributor reactivating the branch knows to mirror the Protocol path's partial-content soft-degrade. Round 3 R3-F2 (LOW, 8/10) and R3-F3 (LOW, 6/10) are coverage gaps accepted as follow-up backlog candidates (UnicodeDecodeError branch in render_index_summary lacks a direct test; defensive-FAIL detail dict path remains logically unreachable). Neither blocks PR 3 merge. Round 3 convergence shape: 3 LOW + zero CRITICAL/HIGH/MEDIUM. Matches PR 2 of #65 precedent (PR 2 converged at Round 3 with 5 LOW = zero higher tiers). PR 3 ready for merge per CLAUDE.md rule 11 ("2-3 rounds is sufficient for most diffs"). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * docs(corpus): #65 PR 3 of 4. spec/34 PR 3 status + spec/27 corpus-backend stub spec/34 inline note: PR 3 wiring IMPLEMENTED. Does NOT drop the RFC banner or finalize the N-MUST Implementer Contract -- both PR 4 work. spec/27 corpus-backend entry: 12th check_*_backend doctor entry. PASS/ WARN/FAIL ladder, capability snapshot, page-count cliff WARN at ~1000 pages for filesystem backends without supports_full_text_search. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Dan Powers <dep0we@gmail.com> Co-authored-by: Claude Opus 4.7 <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
Closes #235. Implements
doctor.check_mandate_backend— the documented-but-missing doctor coherence check that the #124 MandateBackend arc declared in CLAUDE.md but never shipped inatomic_agents/doctor.py. With this PR, CLAUDE.md's MandateBackend lock-paragraph claim ofdoctor.check_mandate_backendis true at runtime.Two bisectable commits:
7e3def7—feat(doctor): 245-linecheck_mandate_backendimplementation + 5 tests +run_doctororchestrator wiring + agent-name-None SKIP enumeration update.4ddb83b—docs(spec/27): removes the "Implementation status (2026-05-28)" disclaimer added in PR docs: consolidated doc-debt sweep — spec/27 catalogue + 6-spec marker scrub + Protocol exception catalog #295 (it pointed at this issue; with the impl shipped, spec/27 describes shipped behavior again); adds a third WARN ladder bullet documenting the WARN-on-mandates.md-absent path that Round 1 adversarial caught as missing; CHANGELOG[Unreleased]Added bullet.What landed
check_mandate_backend(scope_root: Path) -> CheckResultmirrorscheck_policy_backend/check_persona_backendwith 5 documented divergences:cascadekwarg (Mandate's two-tier scope is handled inside the backend viascope: str)."project:doctor"not""(empirically:_parse_scope("")rejects; project kind discards name component per_mandates_path).list_mandates()in addition tocapabilities()so the detail dict carries realmandate_countmatching persona-backend's shape.except Exceptionblock in the filesystem branch (Round 1 collapsed the dual MandateError + Exception fork because filesystem exceptions do not embed credentials)._redact_for_error_messageimported frommandate.backendfor the unknown-id FAIL credential-redaction path mirroring persona's import shape.PASS / WARN / FAIL ladder (2 PASS + 2 FAIL + 2 WARN, matching spec/27 1:1):
filesystemwith validmandates.md→ capability snapshot +mandate_count.filesystemwithmandates.mdabsent → "no operator-granted authorities at this scope" (informational; single-agent home users do not see a noisy FAIL)._redact_for_error_message).capabilities()/list_mandates()probe raises.mandates.mdabsent WARN reachable through the non-filesystem branch.Test Coverage
5 new tests at
tests/test_doctor_check_mandate_backend.pycovering: WARN-no-mandates-md, PASS-with-mandates-md, FAIL-unknown-env-var (bare), FAIL-URL-credential-redaction, capability-snapshot-in-detail-dict invariant.Test suite: 2686 → 2691 passing + 48 skipped, zero regressions across all four pytest runs (pre-impl, post-impl, post-Round-1 fixes, post-CHANGELOG).
ruff check atomic_agents/doctor.py+ruff format --check atomic_agents/doctor.py: both clean.Methodology trail
Pre-impl prep + dispatch: read
check_policy_backendend-to-end as the canonical analog, verifiedMandateCapabilitiesfield names from source (mandate/types.py:316-337), verifiedMandateBackendProtocol method signatures (mandate/backend.py:135,388,354,379), then briefed one Sonnet implementation agent with the closest-analog cite + verified field/method names + test pattern. The Sonnet agent surfaced 5 documented divergences from the brief during implementation (notably the"project:doctor"probe scope choice after empirically discovering_parse_scope("")rejects empty input).Step 11 Opus adversarial Round 1: 0 P0 + 1 P1 + 2 P2.
except MandateError+except Exceptionblock in the filesystem branch was Bucket-C noise. Sister checks (persona, tool-registry, profile) all use a single typed-or-generic except. Filesystem backends don't embed credentials in exception messages, so the discrimination gained nothing safety-wise. Collapsed into singleexcept Exceptionand removed the now-unusedMandateErrorimport.mandate/filesystem.py::_mandates_path. Swapped the cite to the file::function form so a future-Claude lands in the implementing function in one hop.Step 11 Opus adversarial Round 2: 0 P0 + 0 P1 + 1 P2.
[Unreleased]was missing an Added entry. Per CLAUDE.md memoryfeedback_changelog_doc_shipping("every shipped change goes in the CHANGELOG, docs included"), this PR ships a new public-API surface (atomic_agents.doctor.check_mandate_backend) — operators runningatomic-agents doctorwill see a new check fire that didn't fire in the prior release. Added the bullet in thedocs(spec/27)sibling commit.Round 2 closing rec: "fix and ship; no Round 3 required" matching the project's 2-round convergence pattern (PR 2 + PR 3 + PR 4 of [backend] PersonaBackend — load IDENTITY/SOUL/USER from a registry instead of fixed markdown files #62 + PR docs: consolidated doc-debt sweep — spec/27 catalogue + 6-spec marker scrub + Protocol exception catalog #295 all converged at Round 2).
Codex skipped per standing project rule.
Plan Completion
check_mandate_backendinatomic_agents/doctor.pycheck_mandate_backendintorun_doctororchestrator"mandate-backend"to the agent-name-None SKIP enumeration[Unreleased]Added bullet (Round 2 P2 fold)Test plan
uv run pytest -q: 2691 passing + 48 skipped, zero regressionsruff check+ruff format --checkclean onatomic_agents/doctor.pyfrom atomic_agents.doctor import check_mandate_backendsucceedsgrep -c "Implementation status" docs/spec/27-doctor.mdreturns 0check_mandate_backendexists indoctor.py(verifies the fix to the issue title)doctor.check_mandate_backendcoherence check is now true at runtime🤖 Generated with Claude Code