Skip to content

Rectify: Broker Provisioning Catch-All + Cook-Loop Opt-In Regression — Architectural Immunity (#4684) - #4696

Merged
Trecek merged 43 commits into
developfrom
impl-rectify-broker-provisioning-20260817-200349
Aug 20, 2026
Merged

Rectify: Broker Provisioning Catch-All + Cook-Loop Opt-In Regression — Architectural Immunity (#4684)#4696
Trecek merged 43 commits into
developfrom
impl-rectify-broker-provisioning-20260817-200349

Conversation

@Trecek

@Trecek Trecek commented Aug 19, 2026

Copy link
Copy Markdown
Collaborator

Summary

Implements the rectify plan for issue #4684 (broker provisioning catch-all swallow + cook-loop opt-in regression) via "architectural immunity by construction": typed exploration-broker failure codes, opt-in force_inactive_agent_teams on CmdSpec, config-field-has-consumer discipline, single-enforcement-point AST guard, capability-tied grant/revoke symmetry, broker auto-provisioning, skill preflight docs, blast-radius gating, CLI composition tests, live-gate hardening, a pluginless-explorer fallback specialist, and the KNOWN_UNAFFECTED_SKILL_IDS registry (13 numbered sub-plans, "Rectify 2.1"–"2.13").

An independent /audit-impl pass then found 4 real gaps against the plan's own text, all fixed here as 4 additional commits:

  • REQ-007: test_config_field_has_consumer.py was scoped to AgentBackendConfig only, contradicting the plan's "every dataclass" spec. Widened reflectively to all 31 dataclasses in config/_config_dataclasses.py; fixed two detector bugs the widening surfaced (ClassVar pseudo-fields, method-mediated consumption); two genuine pre-existing orphans found and tracked via Orphaned config fields: RunSkillConfig.natural_exit_grace_seconds, ProviderProfileDef.context_window have no production consumer #4693 rather than wired (out of Exploration broker provisioning fails silently — enable_exploration returns exploration_provisioning_failed and broker-only subagents spawn with zero tools #4684's scope).
  • REQ-017: 3 sibling functions in tools_exploration.py still had the original opaque catch-all — only enable_exploration was refactored. Refactored all 3 to typed failure codes.
  • REQ-019: tests/contracts/AGENTS.md was never updated to document the new discipline. Added.
  • REQ-020: the plan's own Step 2.4 explicitly warned that removing the inline policy call without an equivalent one on the fleet/campaign-orchestrator raw launch branch would silently drop enforcement. Investigated a direct fix and found it's substantially deeper than it looks — the real fix requires threading force_inactive_agent_teams through _run_interactive_session's signature and both its branches, which is outside this plan's actual scope (the cook-loop opt-in regression specifically). Documented explicitly in the plan's own §6 Out-of-Scope section instead of duplicating work.

⚠️ Expected merge conflicts

develop has moved significantly since this branch's fork point (5e3a29a86). Commit 0b1b412c0 (#4688, "Interactive Policy Checkpoint Immunity") independently re-solved much of the same architectural ground as this plan's Phases 2.2–2.4 and 2.7, via a different design (a single inline call inside assert_interactive_ordering gated on spec.force_inactive_agent_teams, vs. this plan's single-enforcement-point consolidation into backend.validate_interactive_invocation).

13 files are touched by both branches, including a direct conflict: this branch deletes tests/execution/test_launch_force_inactive_call_path.py (replaced by a reflective test), while #4688 extends the same file. Reconciliation is expected to require either (a) a manual rebase favoring #4688's already-tested implementation on the shared files, retaining only this branch's exploration-broker-specific work (Phases 2.1, 2.5, 2.6, 2.8–2.13, which #4688 doesn't touch), or (b) a fresh, smaller re-plan scoped to just that non-overlapping work.

Verification

task test-check: 38096 passed, 613 skipped, 27 xfailed, 0 failed (on this branch's own base, before reconciling with develop's current tip).

Implementation Plan

Plan file: .autoskillit/temp/rectify/rectify_broker_provisioning_and_cook_opt_in_immunity_2026-08-17_235000.md

🤖 Generated with Claude Code via AutoSkillit

Co-authored-by: Claude Sonnet 5 noreply@anthropic.com

@Trecek
Trecek force-pushed the impl-rectify-broker-provisioning-20260817-200349 branch 2 times, most recently from e38438f to af44b0e Compare August 19, 2026 17:14
Trecek and others added 28 commits August 19, 2026 14:05
Replace the single opaque `except Exception: return "exploration_provisioning_failed"`
catch-all in enable_exploration (and the three sibling catch-alls in
tools_exploration.py, plus tools_evidence_reader.py's two) with typed
branches keyed off the new ExplorationFailureCode(StrEnum) registry
(mirrors FleetErrorCode/FLEET_ERROR_CODES).

- Add ExplorationFailureCode enum (_type_enums.py) and
  EXPLORATION_FAILURE_CODES frozenset registry (_type_constants_registries.py),
  re-exported through core/__init__.pyi.
- Add five named exception classes nested on OwnerBoundExplorationContextStore
  (TrustedRootMismatch, ServiceNotConfigured, SnapshotStale, StoreClosed,
  CapacityExceeded) and convert bind_session_scoped's six raw raises to use
  them, preserving each existing message string and exception base type.
- enable_exploration now wraps its bind_session_scoped/enable_components
  calls to distinguish BindSessionScopedFailed/EnableComponentsFailed from
  the five named store exceptions, with a final catch-all producing
  UNEXPECTED_INTERNAL_ERROR instead of the old opaque code (preserving the
  "Never raises" contract).
- tools_evidence_reader.py's two catch-alls get their own
  evidence_reader_unexpected_internal_error code, distinct from the
  misleading "broker_unavailable" label that conflated any bug with an
  actually-down broker.
- New tests/server/test_enable_exploration_failure_codes.py: one test per
  precondition code (12 total), replacing the old catch-all-enshrining
  assertion in test_enable_exploration.py.
- New tests/contracts/test_exploration_failure_code_registry.py: AST scan
  rejecting any unregistered "code" string literal in tools_exploration.py.

Part of the #4684 broker-provisioning-catch-all-and-cook-loop-opt-in-
regression rectify plan (step 2.1 of 13).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…g-field-has-consumer (#4684)

Move the interactive content-policy checkpoint from an unconditional gate
inside assert_interactive_ordering/validate_interactive_invocation to an
opt-in flag carried on CmdSpec itself, mirroring the existing
force_inactive_agent_teams builder-kwarg precedent. Wires the previously-dead
AgentBackendConfig.force_claude_agent_teams_inactive config field into the
cook CLI so it finally has a production consumer.

- CmdSpec gains force_inactive_agent_teams: bool = False; all 16 constructor
  sites across claude.py, codex.py, _cmd_builder.py, launch_resolution.py,
  and _type_launch.py's launch-record (de)serializer now pass it explicitly.
- assert_interactive_ordering (_headless_helpers.py) and
  validate_interactive_invocation (claude.py) both short-circuit when
  spec.force_inactive_agent_teams is False — the policy no longer fires for
  every interactive launch regardless of caller intent.
- _session_cook.py resolves force_inactive from
  config.agent_backend.force_claude_agent_teams_inactive and threads it into
  both the probe-required and fallback build paths.
- prepare_interactive_launch (_session_launch.py) fixes two propagation
  gaps: (1) its first build_interactive_cmd call never forwarded
  project_root, so force_inactive_agent_teams=True unconditionally raised
  "requires project_root"; (2) the second (executable-bound) call cannot
  itself carry force_inactive_agent_teams=True (build_interactive_cmd's own
  guard forbids combining it with executable=), so the resulting spec never
  carried the caller's intent — fixed via dataclasses.replace() after the
  call returns.
- build_interactive_cmd's env-drift check ("interactive environment changed
  after executable binding") now excludes CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS
  from the comparison: when force_inactive_agent_teams=True neutralizes that
  var on the first (probe) call, the second (executable-bound) call cannot
  re-neutralize it (guarded), so a real ambient env value would otherwise
  always trip the drift check as a false positive. The var's own state is
  already validated by assert_agent_teams_inactive on whichever call
  requested force_inactive.
- New tests/contracts/test_cmd_spec_force_inactive_field.py: AST-walks every
  CmdSpec(...) site in src/autoskillit/, asserting each passes the kwarg.
- New tests/contracts/test_config_field_has_consumer.py: generalizes the
  inert-tracked:#NNNN discipline (tests/AGENTS.md) to config dataclass
  fields, scoped to AgentBackendConfig (the dataclass this plan wires).
- New tests/cli/test_cook_settings_local_agent_teams.py: end-to-end cook()
  against a populated .claude/settings.local.json without stubbing
  validate_interactive_invocation — the exact composition PR #4613 broke.
  This test class of failure (72 pre-existing failures across
  test_fleet_dispatch.py, test_session_launch.py, test_interactive_cold_
  launch_medium.py, test_reload_loop.py, test_terminal.py, and others) is
  now resolved now that the policy is opt-in instead of unconditional.

Part of the #4684 broker-provisioning-catch-all-and-cook-loop-opt-in-
regression rectify plan (steps 2.2+2.3 of 13).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…4684)

Removes the double-gate where both assert_interactive_ordering
(_headless_helpers.py) and backend.validate_interactive_invocation
(claude.py) independently called _interactive_invocation_environment_policy
— fixing one gate silently left the other still unconditionally firing.

- assert_interactive_ordering is now cmd-ordering-only; the content-policy
  check lives solely behind backend.validate_interactive_invocation, which
  is already the sole call site (defined in the same module as the policy
  function it wraps).
- The raw/non-managed branch of _run_interactive_session (used by ad-hoc
  fleet and campaign interactive sessions — cli/fleet/_fleet_session.py:89,199)
  never threads force_inactive_agent_teams (out of this plan's scope, same
  as _session_cook.py's wiring), so the policy check was always a no-op
  there; validate_interactive_invocation is deliberately NOT added to this
  branch, since for Codex it also enforces an unrelated, stricter contract
  (CODEX_HOME/SQLite-home env var matching) that only a managed session
  home satisfies — adding it would break real fleet/campaign Codex
  sessions, which never have one.
- New scripts/check_single_enforcement_point.py: a bespoke AST call-site
  equivalence-class resolver (import-alias resolution, pure-return-wrapper
  equivalence classes, backend-specific exemption list) — no external prior
  art in this codebase for this shape of check. Wired as a new pre-commit
  hook.
- New tests/contracts/test_single_enforcement_point.py: unit-verifies the
  resolver mechanism against synthetic fixtures (single/double call sites,
  import alias forms, wrapper equivalence classes, backend exemption
  accept/reject) plus an integration check against the real codebase.

Part of the #4684 broker-provisioning-catch-all-and-cook-loop-opt-in-
regression rectify plan (step 2.4 of 13).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…oration authority (#4684)

- Extract _ReopenedLaunchAuthority, _safe_submit_failure_reason, and
  _ExplorationLaunchAuthorityStore out of exploration_context.py into a new
  sibling module exploration_context_durable.py, to stay under the 1100-line
  ceiling while adding new durable-authority machinery. One-way dependency
  (durable -> exploration_context); no circular import.
- Add OwnerBoundExplorationContextStore.lease_for_capability() accessor so
  callers can resolve the in-memory lease minted for a just-bound capability.
- Add bind_session_scoped_durable() free function in the new module: binds
  the in-memory capability via bind_session_scoped(), then writes an
  HMAC-signed 0600 durable authority file recording the same capability,
  mirroring bind_launch's durability guarantee for the session-scoped path.
- Wire tools_exploration.py's enable_exploration to call
  bind_session_scoped_durable(...) with a per-session authority_home under
  ctx.temp_dir, instead of the bare in-memory bind_session_scoped().
- Fix grant/revoke asymmetry: enable_exploration granted FastMCP tag
  visibility via ctx.enable_components() but only ever revoked the in-memory
  lease via store.cleanup_session() on failure paths — never the tag itself.
  Add await ctx.disable_components(tags={"exploration"}) to the finally
  block so a later failure after a successful grant always revokes the tag.
- Register both durable authority writers (bind_launch,
  bind_session_scoped_durable) in DURABLE_ARTIFACT_WRITERS
  (core/types/_type_constants.py).
- Add tests/contracts/test_exploration_grant_revoke_symmetry.py: an AST
  check that every ctx.enable_components(...) call site in
  tools_exploration.py has a reachable ctx.disable_components(...) in the
  same function, plus a membership check that both durable writers are
  registered. Deliberately does NOT add pipeline/exploration_context*.py to
  test_durable_artifact_writers_guard.py's _SCOPED_MODULES — that guard's
  docstring explicitly rejects codebase-wide scope expansion beyond hook
  artifacts; the membership check above is the plan's actual specified home
  for this assertion.
- Update tests/server/test_enable_exploration.py and
  test_enable_exploration_failure_codes.py: ctx fixtures now set
  disable_components = AsyncMock() and assert it is awaited with
  tags={"exploration"} on both the cancellation path and the
  enable_components-failure path.
- Bump tests/arch/test_subpackage_isolation.py's pipeline file-count
  exemption from 18 to 19 for the new sibling module.

test_check: 37859 passed, 613 skipped, 27 xfailed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…4684)

- Add AgentBackendConfig.auto_provision_exploration: bool = False. When True
  and the boot-time/request-time session type is not in
  EXPLORER_INELIGIBLE_SESSION_TYPES, the exploration tag is pre-applied
  alongside the existing kitchen/plan-review reveals, mirroring
  force_claude_agent_teams_inactive's opt-in-on-the-artifact pattern.
- Wire the gate into all three reveal call sites, each using whichever
  visibility primitive it already uses:
  - open_kitchen's ctx.enable_components branch (notification-capable
    backends): await ctx.enable_components(tags={exploration}).
  - open_kitchen's _use_global_enable/mcp.enable branch (backends without
    tools/list_changed support, e.g. Codex): mcp.enable(tags={exploration}).
  - _pre_reveal_kitchen (boot-time pre-reveal for non-notification backends):
    _mcp.enable(tags={exploration}).
  The HMAC capability lease minted by enable_exploration remains the sole
  authorization boundary in all cases — this only auto-provisions the
  weaker, ergonomic visibility gate.
- Register auto_provision_exploration as a CONFIG_DEFAULT_INGREDIENTS entry
  (ingredient_defaults.py) with a defaults.yaml documented default and a
  lexicographically-inserted tests/contracts/config_key_ledger.txt entry
  (before agent_backend.backend).
- Add KitchenStatusResult.broker_authority: str (server/tools/_types.py) and
  its runtime construction in tools_status.py's kitchen_status: reports
  ineligible_session_type for ORCHESTRATOR/FLEET sessions, else
  no_session_bound — kitchen_status has no per-request session/token
  context to determine a specific bound capability, so this reports session-
  type eligibility (the diagnostic the issue's motivation actually asks
  for: learn *before* the downstream zero-tool subagent refusal, not just
  after). Add the field to the pretty_output_hook formatter
  (_fmt_status.py's _fmt_kitchen_status + _FMT_KITCHEN_STATUS_RENDERED).
- New tests/server/test_kitchen_status_broker_field.py (Step 1.4): the
  3-case precondition matrix from the plan (skill -> no_session_bound;
  orchestrator/fleet -> ineligible_session_type).
- New open_kitchen/_pre_reveal_kitchen auto-provision tests in
  test_tools_kitchen_visibility.py (both branches) and
  test_lifespan_skill_boot.py (boot-time pre-reveal, opted-in and
  opted-out cases) — not explicitly named in the plan's Step 1 list but
  added per AGENTS.md's 'add tests for new features' discipline; the
  boot-time tests isolate the new auto_provision_exploration flag from the
  pre-existing, separate 'exploration' feature gate (both must be on for
  the tag to actually surface — _pre_reveal_kitchen's unconditional
  feature-suppression pass would otherwise immediately undo the enable).
- Fix-forward from the first test_check round: _fmt_status.py's formatter
  coverage registry, tests/infra/test_schema_version_convention.py's
  hardcoded (file, line) JSON-write-site allowlist (four call sites shifted
  by my new lines), and tests/server/_helpers.py's _PATCHED_DEFAULTS fixture
  (needed the new ingredient key for parity with CONFIG_DEFAULT_INGREDIENTS).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…lders (#4684)

Replace tests/execution/test_launch_force_inactive_call_path.py's 5
hand-written build_*_cmd probes with a reflective discovery test in
test_launch_force_inactive_call_path_reflective.py. The hand-written list
silently passed when a sixth builder was added to ClaudeCodeBackend without
the force_inactive_agent_teams parameter — nothing enumerated the actual
builder surface, mirroring the reflective-enumeration pattern in
tests/arch/test_backend_protocol_completeness.py.

- BUILDER_METHOD_NAME_REGEX = r"^build_(?!inspector).*_cmd$" discovers
  build_food_truck_cmd, build_headless_cmd, build_interactive_cmd,
  build_resume_cmd, build_skill_session_cmd via dir(backend) — the same 5
  the old hand-written list covered. Naturally excludes build_inspector_cmd
  (not launch-wired, per plan 2.2) and build_cmd (the pipeline-internal
  skill-command dispatch entry point, which forwards the field through but
  does not expose it as a caller parameter) without a hardcoded name list —
  neither matches build_<non-empty-middle>_cmd.
- test_every_discovered_builder_accepts_force_inactive_agent_teams: the new
  signature-presence check the plan specifies (1.10).
- Preserved the original 5 probes' actual behavioral assertions (env var
  actually stripped when force=True, left alone when force=False) rather
  than discarding them for the plan's literal signature-only pseudocode —
  removing real behavioral coverage would be a regression. Keyed them into
  a _BEHAVIORAL_PROBES registry plus a completeness test
  (test_every_discovered_builder_has_a_behavioral_probe) so a newly
  discovered builder without a wired probe fails loudly instead of being
  silently skipped.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…4684)

Before this change, no SKILL.md or agent definition mentioned
enable_exploration anywhere — rg -rln "enable_exploration"
src/autoskillit/skills/ src/autoskillit/skills_extended/ src/autoskillit/agents/
returned zero files. A skill author who adds an
<!-- autoskillit:exploration-vector id="..." --> marker shipped with no
instruction that a preflight enable_exploration call is required.

- Add a '> **Preflight:**' blockquote sentence (mentioning enable_exploration
  within 200 characters of the anchor) to all 49 skills_extended/*/SKILL.md
  files currently carrying the exploration-vector marker: 13 arch-lens-*,
  18 exp-lens-*, 12 vis-lens-*, plus investigate, audit-docs,
  planner-analyze, planner-elaborate-phase, planner-extract-domain, scope.
  Inserted as the first paragraph after each file's H1 title heading.
- New tests/skills/test_exploration_vector_preflight.py: enumerates every
  SKILL.md under skills/ and skills_extended/ containing the marker (mirrors
  test_explorer_conformance_preamble.py's enumeration shape) and asserts
  the structured preflight block — anchored by literal '> **Preflight:**'
  (case-insensitive) with enable_exploration within 200 characters. A loose,
  unrelated mention elsewhere in the file does not satisfy the contract.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…4684)

Three consecutive rectify-adjacent changes to the same function/module
(PR #4503 -> #4512 -> #4613/#4684) shipped with no CI signal that the
underlying plan touched multiple import layers at once — PR #4613's plan
touched >=3 layers across 141 files with no gate to flag it.

- New tests/arch/test_rectify_blast_radius_guard.py: scans
  'git diff --name-only HEAD~1 -- .autoskillit/temp/rectify/*.md' (not a
  static directory listing — .autoskillit/temp/ is gitignored per
  .autoskillit/.gitignore:1, so a directory scan would pass vacuously in
  every fresh CI clone with zero chance of ever firing) for any rectify
  plan committed in the most recent commit, counts distinct IL-0..IL-3
  import-layer tokens referenced in it, and classifies: <=3 layers OK,
  4-5 layers emits a pytest warning (explicit reviewer check), >5 layers
  is a hard test failure (decomposition required). The counting/
  classification logic is pinned by direct unit tests independent of git
  state; the integration test passes vacuously in this worktree (the
  driving plan for this very implementation lives outside version control,
  per the same gitignore rule) — documented as the accepted, structural
  limitation of a git-diff-based scan versus a directory-listing scan that
  could never fire at all.
- Extend skills_extended/rectify/SKILL.md's plan-split rule: split per
  layer when >3 distinct import layers are touched, even under the
  pre-existing 500-line threshold; >5 layers must be decomposed before
  implementation begins. Line count and layer count are independent
  triggers.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
… namespace (#4684)

- tests/server/test_lifespan_skill_boot.py: rewrite both _pre_reveal_kitchen
  auto-provision tests (added in Rectify 2.6) to assert on the mocked
  autoskillit.server.mcp singleton's .enable call history instead of the
  real cumulative mcp.list_tools() output. submit_exploration_query et al.
  also carry the pre-existing 'kitchen' tag; FastMCP's last-match-wins
  transform stack means _mcp.enable(tags={'kitchen'}) alone can make them
  visible whenever nothing *later* in the sequence disables 'exploration'
  specifically (e.g. when the separate 'exploration' feature flag is on) —
  a real-list_tools assertion was therefore passing or failing for reasons
  unrelated to the auto_provision_exploration opt-in itself. The
  call-recording assertion (mirroring the pattern already used for
  open_kitchen's two branches in test_tools_kitchen_visibility.py) tests
  exactly what Rectify 2.6 added: whether the new code calls
  enable(tags={'exploration'}), independent of the tag-union interaction
  with 'kitchen'.
- src/autoskillit/skills_extended/rectify/SKILL.md: namespace the Rectify
  2.9 plan-split-rule addition's skill cross-reference
  (/implement-worktree-no-merge -> /autoskillit:implement-worktree-no-merge)
  per tests/workspace/test_skills.py's cross-reference namespacing contract.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ocal.json (#4684)

- Extend tests/cli/_cook_launch_helpers.py's arrange_cook with two new
  optional parameters, both additive/opt-in (existing call sites unchanged):
  - settings_content: dict | None — writes .claude/settings.local.json into
    the tmp_path project before cook runs. Refactored
    test_cook_settings_local_agent_teams.py (delivered in Rectify 2.2/2.3)
    to use it instead of its own duplicate write, removing the redundancy.
  - project_dir_override: Path | None — uses a caller-supplied project
    directory (e.g. the real repository root) instead of creating an empty
    tmp_path/project. Caller owns creating/populating that directory; the
    helper will not mkdir() or write settings_content into it, since doing
    so on a real, pre-existing directory would be destructive.
- New tests/contracts/test_cli_cook_validator_stub_guard.py: an AST guard
  that fails when a tests/cli/ test both invokes cli.cook(...) and
  monkeypatches the *real* ClaudeCodeBackend.validate_interactive_invocation
  (3-arg class-target or 2-arg dotted-path form) — the exact composition
  that hid PR #4613's regression. Deliberately narrower than 'any
  validate_interactive_invocation stub': stubbing the real CodexBackend (a
  distinct, legitimately no-op-for-this-policy implementation — Codex has
  no agent-teams concept, out of scope per the plan's Step 6) or defining a
  standalone fake backend double (neither is 'hiding' any real production
  behavior) does not flag. A fully generic rule would immediately
  false-positive on 4+ pre-existing, legitimate Codex-focused tests. Five
  synthetic self-tests exercise both the two violation shapes and the three
  allowed shapes; a sixth confirms zero real violations exist today.
- New tests/cli/test_cook_real_root_smoke.py (opt-in live gate, Step 1.13):
  gated by AUTOSKILLIT_COOK_REAL_ROOT_SMOKE=1, drives cli.cook() against
  this repository's own project root and its real .claude/settings.local.json
  (backed up and restored around each sub-case; restoration failure fails
  the test loudly rather than silently leaving the file test-modified).
  Asserts cli.cook() completes without error when force_inactive is False,
  and raises when CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1 is active and
  force_inactive is True — the exact composition PR #4613 broke. A single
  test function (not two) so 'exactly one non-skipped test' holds.
- New Taskfile.yml task test-smoke-cook-real-root, mirroring
  test-smoke-claude-explorer-live-gate's preconditions (claude installed +
  isolated auth present) and post-validation shape (JUnit non-skipped-count
  check), simplified to a stdout ValueError-absence check in place of the
  explorer gate's evidence-JSONL correlation check (this gate has no
  analogous evidence stream to validate).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
… env-parity registry (#4684)

Rectify 2.10's new Taskfile.yml task-smoke-cook-real-root sets
AUTOSKILLIT_COOK_REAL_ROOT_SMOKE in its env: block, which
test_every_taskfile_override_is_registered requires be registered in
TEST_HARNESS_ENV_OVERRIDES (or the non-parity allowlist) — the double-bind
pincer that prevents an unregistered Taskfile override from silently
masking a failure class. Registered with the same shape as the sibling
AUTOSKILLIT_CLAUDE_EXPLORER_LIVE_GATE/AUTOSKILLIT_WEB_AGENT_LIVE_GATE
entries: opt-in-only, no parity fixture needed since ordinary test tasks
never set it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…her extension (#4684)

Step 1.14 — tests/server/test_claude_explorer_live_gate_envelope_code.py
(new file, does not modify the existing live gate):
- The existing test_claude_explorer_live_gate.py verifies only that the
  parent Claude session replied LIVE_OK; it never verifies enable_exploration
  itself succeeded. A future broker regression that swallows a precondition
  failure into a typed code (or the unexpected_internal_error catch-all —
  the exact bug class #4684 fixes) could still produce a LIVE_OK reply via
  some other retry path, uncaught by the existing gate.
- Imports the existing gate's repository/plugin/MCP-config setup helpers by
  name rather than duplicating or refactoring them, so the
  already-verified live gate is untouched.
- Instruments tools_exploration._failure(code) — the single module-level
  helper every except-branch in enable_exploration (including the final
  catch-all) routes through — using the same 'reassign a module-level name,
  called via bare-name lookup from the target function's body' pattern the
  existing gate already uses successfully for
  consume_exploration_request_record. (Reassigning the already-@mcp.tool()
  -decorated enable_exploration function itself was considered and
  rejected: the running MCP framework captures a direct reference to it at
  registration time, not a re-lookupable module attribute, so a later
  reassignment would not actually intercept dispatched calls.) Recording
  every _failure(code) invocation is a more direct test of 'the happy path
  was taken, not the catch-all' than trying to capture the inline-built
  success envelope: zero recorded failure codes plus a LIVE_OK reply is
  exactly the property this gate needs to hold.

Step 1.3 — tests/hooks/test_exploration_request_identity_guard.py (extended,
not replaced):
- Add the four real production runtime tool names
  (mcp__plugin_autoskillit_autoskillit__*) to the existing parametrized
  matrix — every prior case used a synthesized name; none was the actual
  shape the plugin emits, despite the matcher regex being purpose-built for
  it. Verifies end-to-end through subprocess.run against the real shape.
- Add a new AUTOSKILLIT_AGENT_BACKEND x AUTOSKILLIT_HEADLESS env-var matrix
  test (3 x 4 = 12 combinations) pinning that both checks in the guard
  script use exact equality (== "codex", == "1"), not truthiness —
  "true" is a truthy string in many languages' conventions but must NOT
  trigger the skip since it doesn't equal "1". Extracted _run's subprocess
  invocation into a new _run_with_env(payload, env) so the matrix test can
  set exact string env values (including "") that _run's bool-only
  headless parameter can't express, without changing _run's own signature
  or any of its existing call sites.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
 Fix E / AC5)

Issue #4684 AC5 requires a fallback agent for when enable_exploration
returns ineligible_session_type or exploration_store_unavailable. The
codebase's agent discipline is specialist-only (agents/AGENTS.md;
docs/execution/explorer-agents.md mandates specialist terminal leaves;
tests/server/test_tools_agents.py:434-479 assert packless agents must not
appear in AGENT_PACK_REGISTRY) — registering general-purpose.md would
contradict it. Tool restrictions come from frontmatter (tools:), not prompt
content, so a restricted specialist is the architecturally consistent fix.

- New src/autoskillit/agents/pluginless-explorer.md: a third terminal-leaf
  explorer specialist, tools: [Read, Grep, Glob], packless (subagent_type-only
  per agents/AGENTS.md:56-59 — only step 1 of the Adding Agents procedure is
  required). Body mirrors semantic-code-navigator.md's shape (Role +
  Verdict section) adapted for the restricted tool surface; includes the
  '## Verdict' structured-output marker every agents/*.md file requires
  per test_tools_agents.py's generic frontmatter/structured-output/
  tool-body-reference checks (verified against all three directly).
  Deliberately has no codex: frontmatter block (Claude-only fallback) and
  is NOT added to BUNDLED_EXPLORER_ROLES — that set drives Codex's
  per-child terminal-binding machinery (explorer_projection.py,
  server/_explorer_projection.py's strict-equality discovery check) for
  the two broker-bound MCP-tool explorers; pluginless-explorer has an
  unrelated tool surface and is not part of that binding contract.
- agents/AGENTS.md: document the new specialist alongside the existing
  prose sentence for semantic-code-navigator/repository-impact-profiler
  (the same 'packless terminal-leaf explorer' documentation pattern, not
  the general packless-agents enumerated list).
- New tests/server/test_fallback_specialist_agent_registered.py (Step
  1.15): asserts the specialist exists with the correct restricted tool
  surface, that no general-purpose.md was created, that it stays out of
  BUNDLED_EXPLORER_ROLES, and reflective discovery via an agents/ directory
  walk (mirrors test_launch_force_inactive_call_path_reflective.py's
  pattern) finds it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…-explorer (#4684)

Rectify 2.12 registered a new agent (pluginless-explorer.md), discovered by
load_bundled_agent_definitions() alongside the other 22 bundled agents.
test_skill_child_roles_have_bounded_tools_and_usage_descriptions had a
hardcoded len(definitions) == 22 assertion; bump to 23. No
_SKILL_CHILD_ROLE_EXPECTATIONS entry is needed — pluginless-explorer is a
terminal explorer specialist (like semantic-code-navigator and
repository-impact-profiler, neither of which has an entry either), not a
skill-child-role in that dict's sense.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Issue #4684 AC6 requires 'No new failure mode is introduced for the
unaffected skills (regression test on each).' A hardcoded count assertion
breaks on any new SKILL.md addition; a KNOWN_UNAFFECTED_SKILL_IDS registry
follows the codebase's retirement-registry discipline instead (modelled on
RETIRED_SKILL_NAMES).

- Add KNOWN_UNAFFECTED_SKILL_IDS: frozenset[str] to
  core/types/_type_constants.py, immediately after RETIRED_SKILL_NAMES.
  Populated from a live walk of skills/ and skills_extended/ (92 skills) —
  a skill is UNAFFECTED iff its SKILL.md carries no
  '<!-- autoskillit:exploration-vector id="..." -->' marker. Deliberately
  excludes exp-lens-*/vis-lens-* (dormant markers, not yet wired via
  for_each) despite the source investigation's own narrative prose calling
  them 'Unaffected' — the registry's own contract test asserts registered
  skills carry zero markers, so a marker-bearing skill (dormant or not)
  cannot be included; a dormant marker could be wired up in a future PR
  without this registry's knowledge, which is exactly the drift the AC6
  regression test exists to catch.
  Plan location deviation: the plan specified
  src/autoskillit/skills/_unaffected_skill_registry.py, but skills/ is a
  namespace package holding only SKILL.md content today (no __init__.py,
  no other .py files) — adding importable code there means every import
  creates a skills/__pycache__/ directory, which
  scripts/check_doc_counts.py's naive 'every directory under skills/ is a
  skill' counter then miscounts as an extra skill (141 -> 142, reproduced
  locally and confirmed via a real pre-commit failure before relocating).
  core/types/_type_constants.py is the architecturally consistent home:
  it's where the actual RETIRED_SKILL_NAMES/AGENT_PACK_REGISTRY/
  DURABLE_ARTIFACT_WRITERS precedents this plan cites all already live.
  Exported via core/__init__.pyi (alphabetically positioned stub line).
- New tests/contracts/test_unaffected_skill_registry.py:
  _discover_unaffected_skills() applies the same negative predicate live
  against the current skills/ and skills_extended/ trees.
  test_unaffected_skill_set_is_stable catches both directions of drift
  (new UNAFFECTED skill not registered; registered skill no longer
  UNAFFECTED). test_no_new_broker_coupling_in_unaffected_skills is
  parametrized per registered skill and asserts zero exploration-vector
  markers AND zero enable_exploration references (the indirect-coupling
  failure mode AC6 guards against). A sanity test pins the predicate
  against one known-affected (arch-lens-c4-container) and one
  known-unaffected (open-kitchen) skill so a broken predicate can't pass
  vacuously.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ol counts (#4684)

- tests/arch/test_subpackage_structure.py::test_type_constants_split_completeness:
  167 -> 168 total __all__ symbols across the _type_constants* split modules,
  for Rectify 2.13's new KNOWN_UNAFFECTED_SKILL_IDS export.
- tests/workspace/test_agent_definition_rendering.py::
  test_all_builtin_only_agents_rendered_byte_identical: bundled_definitions
  22 -> 23 and originals (built-in-tool-only agents, no mcp__ tools) 19 -> 20
  for Rectify 2.12's pluginless-explorer.md (tools: [Read, Grep, Glob] — no
  mcp__ tools, so it counts as built-in-only). A sibling to the
  tests/core/test_agent_definition.py fix already applied after 2.12 — this
  one was missed in that pass since I moved straight to 2.13 without a fresh
  full test_check round in between; caught by this round's full run.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…every dataclass

Independent audit found the contract test scoped to AgentBackendConfig
only, contradicting the plan's explicit spec ('walk every field of every
@DataClass in config/_config_dataclasses.py'). Widen _ENFORCED_DATACLASSES
to reflectively enumerate every dataclass directly defined in
_config_dataclasses.py.

Widening surfaced two real methodological gaps in the detector itself:
- dataclasses.fields() vs raw __dataclass_fields__: the latter includes
  ClassVar pseudo-fields (RunSkillConfig._EXIT_GRACE_BUFFER_MS), producing
  a false violation. Switched to dataclasses.fields().
- Fields consumed only via a method defined on their own dataclass
  (GitHubConfig.allowed_labels, read inside check_label_allowed/
  check_labels_allowed, called from 6 real tools_* sites) were invisible
  to the direct '.field_name' grep. Added a two-hop detector: a method
  (excluding __post_init__, which is auto-invoked regardless of whether
  the field's value ever reaches real behavior) that reads self.field_name
  and is itself called externally counts as consumption.

Widening also surfaced two genuine pre-existing orphans, unrelated to
- RunSkillConfig.natural_exit_grace_seconds — validated in __post_init__
  but never threaded into execution/process/__init__.py's same-named
  parameter at either real call site.
- ProviderProfileDef.context_window — validated in __post_init__ but
  server/_guards.py's _profile_to_env (the sole translator of profile
  fields into effective behavior) never reads it.

Both annotated inert-tracked:#4693 (tracking issue opened for this
discovery) rather than wired here, to avoid scope creep into unrelated
config debt. The escape hatch is now also exercised against a real field
(natural_exit_grace_seconds), not only a synthetic fixture, per the plan's
own requirement.

Refs #4684.
…_exploration.py siblings

Independent audit found submit_exploration_query/get_exploration_page/
resume_exploration_context still on their original opaque
'except Exception: return _failure(_FAILURE_BROKER_UNAVAILABLE)'
catch-all — only enable_exploration was refactored in Phase 2.1, despite
the plan explicitly listing all four functions ('refactor :407-478 and
the three sibling catch-alls at :299, :350, :397').

Their failure surface is narrower than enable_exploration's (no
bind_session_scoped/enable_components calls), so the typed-branch shape
differs: a new StoreUnavailable marker exception (mirroring
BindSessionScopedFailed/EnableComponentsFailed) replaces the bare
'raise RuntimeError("exploration context store is unavailable")' at the
two sites that raise it (submit_exploration_query,
_fetch_page_from_launch_environment, shared by the other two siblings).
Each function now distinguishes:
- StoreUnavailable -> BROKER_UNAVAILABLE (a named, specific condition —
  no exploration context store configured for this session)
- any other exception -> UNEXPECTED_INTERNAL_ERROR (the new generic
  fallback, same code enable_exploration already uses; logged via
  log.warning(exc_info=True), never re-raised, preserving the
  'Never raises' contract)

Both codes already existed in ExplorationFailureCode — no enum change
needed. Added regression tests for both branches across all 3 siblings.

Refs #4684.
…discipline in tests/contracts/AGENTS.md

Independent audit found this file byte-identical to develop despite the
plan explicitly listing it under Phase 2.3's file list ('new section
documenting the discipline'). Added a section mirroring the sibling
run_skill Parameter-Role Ledgers doc in tests/AGENTS.md, documenting the
liveness predicate (direct reader, indirect method-reader, or
inert-tracked:#NNNN), why it re-derives on every run instead of using a
frozen ledger, and the two pre-existing orphans REQ-007's widening
surfaced.

Refs #4684.
…cement gap as explicit out-of-scope

Independent audit confirmed the exact trap the plan's own Step 2.4 warned
against: assert_interactive_ordering's inline policy call was removed
without an equivalent call reaching the raw/non-managed
_run_interactive_session branch (the sole launch path for ad-hoc
fleet/campaign interactive sessions), leaving only an explanatory code
comment rather than the plan's specified remediation.

Investigated both remediation options the audit offered:
(a) add a Claude-backend-guarded enforcement call here, or
(b) document the gap explicitly in the plan's Out-of-Scope section.

Chose (b), after confirming (a) is far larger and more redundant than it
first appears: _run_interactive_session has NO force_inactive_agent_teams
parameter at all in this plan's scope — neither branch ever threads it,
so a guarded call alone would still be a no-op; the real fix requires
threading the flag through _run_interactive_session's signature and both
branches, plus _launch_cook_session and its callers. develop's tip
already ships exactly this (0b1b412 / #4688, 'Interactive Policy
Checkpoint Immunity'), independently, via a differently-shaped
architecture (a single inline call inside assert_interactive_ordering,
gated on spec.force_inactive_agent_teams, rather than this plan's Step
2.4 single-enforcement-point consolidation into
backend.validate_interactive_invocation). Hand-duplicating that fix here
would produce two competing, partial implementations of the same
corridor that must be reconciled against develop anyway.

Updated the plan's own §6 Out-of-Scope section (main repo, not this
worktree — plans live outside the implementation worktree) with the
full investigation trail, and tightened the code comment here to
reference it. The Codex-incompatibility rationale for not calling the
generic validate_interactive_invocation Protocol method is unchanged and
still applies regardless of which remediation path closes this gap.

Refs #4684, #4688.
…efactors

Post-rebase reconciliation of semantic conflicts that survived the textual
merge (no conflict markers, but wrong against the new integration base):

- _type_launch.py: drop the duplicate "force_inactive_agent_teams" key from
  the launch-record command payload. Both sides added it independently and
  git merged them cleanly into one dict; ruff F601 caught the duplicate.
  develop's placement (after process_idle_timeout_ms) is kept.

- test_subpackage_structure.py: bump the hardcoded _type_constants symbol
  count 168 -> 169. develop added CLAUDE_CODE_MCP_TOOL_IDLE_TIMEOUT_ENV_VAR
  (+1) while this branch added EXPLORATION_FAILURE_CODES and
  KNOWN_UNAFFECTED_SKILL_IDS (+2); neither side's count absorbed the other's.

- test_cook_settings_local_agent_teams.py / test_cook_real_root_smoke.py:
  rebase the expected outcome onto #4688's remediation strategy. develop
  now neutralizes a conflicting env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS
  settings entry before the inactivity assertion rather than refusing the
  launch, so cook no longer raises for that case. Both tests keep their
  purpose — the real, unstubbed policy running against a real settings
  file — and now assert the file is actually rewritten and the launch env
  is agent-teams-free, plus that the opt-in leaves the repository untouched
  when off.

- tests/contracts/AGENTS.md: follow develop's rename of
  force_claude_agent_teams_inactive -> force_inactive_agent_teams.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…al AST-parse assertions it never used

test_escape_hatch_and_reader_detection_on_synthetic_fields claimed to exercise
_field_has_consumer/_field_is_inert_tracked against synthetic fields but only
ever asserted _INERT_TRACKED_RE against a hand-typed comment_block string; the
synthetic_source/ast.parse/isinstance lines exercised nothing the assertions
depended on. Real escape-hatch/consumer-detection coverage already lives in
test_natural_exit_grace_seconds_is_inert_tracked_against_a_real_field and
test_allowed_labels_is_consumed_indirectly_via_a_dataclass_method.
…ec call-site scan

_cmd_spec_call_sites() smuggled file provenance through node._autoskillit_file
with a silent "?" fallback if ever missing. Return (Path, ast.Call) tuples
instead, matching the established multi-file ast.walk idiom already used by
test_maintenance_install_argv_contract.py and test_no_backend_name_bypass.py.
…tive launch branch

The 24-line comment claimed #4688 (commit 0b1b412) "already shipped on
develop... threading the flag through both branches on its own
single-inline-call architecture." Verified via git show that commit never
touched this file; its actual change removed an inline env-policy call
elsewhere, converging on the single-enforcement-point design this branch
already uses — the opposite of what the comment described. Replaced with a
concise, durably-true summary and dropped the plan-section/date citations.
…ng-fragmentation leak

_safe_submit_failure_reason replaced sensitive_values in declaration order.
authority_path frequently contains cwd/repository_root as a literal
substring, and shorter fields (session_id) can be substrings of longer ones
(source_identity). Redacting a shorter value first fragments a later,
longer value's match, causing that replacement to silently no-op and
leaving cleartext fragments next to a "[redacted]" token. Sorting by
descending length before redacting eliminates the fragmentation window.
… invariant

assert is stripped under python -O/PYTHONOPTIMIZE, and lease is None is
reachable in a genuine race (a concurrent bind_session_scoped for the same
session_id can evict this thread's just-minted capability via
_discard_session_locked before lease_for_capability runs). Stripping the
assert would let a None lease flow into lease.snapshot_digest/expires_at
below as an unguarded AttributeError. Matches this module's existing
plain-builtin-exception idiom (cf. ValueError in
_ExplorationLaunchAuthorityStore.write).
bind_session_scoped_durable mints and registers a lease in-memory via
store.bind_session_scoped, then writes the durable authority record. If the
write raises (ValueError on a bad authority_home, or OSError from the file
write/chmod), the lease was already committed with no compensating
cleanup_session call on this path, orphaning it until TTL expiry. Wrap the
write in try/except and call store.cleanup_session(session_id) before
re-raising, mirroring the compensating-cleanup pattern already used at the
tools_exploration.py call site for the enable_components failure case.
…ndependently

Three related findings on the same finally block: (1) an unguarded
cleanup_session/disable_components failure could mask an in-flight
exception; (2) cleanup_session ran before disable_components with no
isolation, so a cleanup_session raise skipped disable_components entirely,
defeating the "partial-success enable_components never leaves the tag
visible without a live lease" invariant the block exists to guarantee;
(3) disable_components had no defensive wrapping while enable_components
above it does. Wrapping each call in its own try/except-and-log makes both
attempted unconditionally and resolves all three.
Trecek and others added 15 commits August 19, 2026 14:07
…e failure codes

_FAILURE_INVALID_REQUEST, _FAILURE_CONTEXT_UNAVAILABLE, and
_FAILURE_BROKER_UNAVAILABLE are module-level-aliased because each is
referenced from multiple functions; UNEXPECTED_INTERNAL_ERROR is referenced
from all 4 mcp.tool() handlers yet was the one holdout referenced directly,
breaking the file's own "alias iff referenced from >1 call site" convention.
Single-use codes are left unaliased, unchanged.
…cate

The auto-provision eligibility check
(ctx.config.agent_backend.auto_provision_exploration and session_type not in
EXPLORER_INELIGIBLE_SESSION_TYPES) was duplicated verbatim in
_session_boots.py's _pre_reveal_kitchen and _open_kitchen.py's open_kitchen,
both introduced in the same commit. Extract a pure predicate function next
to EXPLORER_INELIGIBLE_SESSION_TYPES in pipeline/exploration_context.py,
re-export it from pipeline/__init__.py, and use it at both call sites.
…mismatch

bind_session_scoped raised TrustedRootMismatch for a malformed
source_identity as well as for an actual repository_root mismatch, so both
distinct precondition failures produced the same trusted_root_mismatch
diagnostic. Add a dedicated InvalidSourceIdentity(ValueError) exception and
INVALID_SOURCE_IDENTITY failure code, and wire it through
enable_exploration's re-raise tuple and except clause following the same
one-code-per-precondition pattern already used for the other 5 exceptions.
EXPLORATION_FAILURE_CODES is auto-derived as frozenset(ExplorationFailureCode),
so the new code self-registers with no separate registry file to update.
The rebase onto upstream/develop (which now includes #4665's
_command_classification.py decomposition and #4694's ambient-env
ground-truth surface) shifted line numbers referenced by two
line-number-keyed registries in tests/_ambient_env_surface.py, and
made one explicit env-scrub call redundant:

- FORWARDING_SITES: execution/backends/codex.py sites shifted
  514->516, 648->651, 767->771, 839->844.
- DYNAMIC_READ_EXEMPTIONS: server/_lifespan/_session_boots.py
  455->468; server/tools/tools_evidence_reader.py 170->174.
- tests/cli/test_cook_real_root_smoke.py:107's explicit
  monkeypatch.delenv("CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS") is now
  redundant with #4694's autouse _scrub_ambient_env fixture (the var
  has disposition="scrub" in AMBIENT_ENV_DISPOSITIONS); removed per
  tests/arch/test_no_adhoc_env_workarounds.py's guard.

Verified: all shifted line numbers checked against actual on-disk
positions; tests/contracts/test_ambient_env_surface.py (26),
tests/arch/test_no_adhoc_env_workarounds.py (3), and
tests/cli/test_cook_real_root_smoke.py (1 skipped, opt-in gated) all
pass locally. pre-commit run --all-files clean.

The remaining CI failure (test_sigterm_writes_scenario_json, rc=-15)
is byte-identical to develop and untouched by this branch's history
— passes locally in isolation; treated as pre-existing CI flakiness,
not a regression from this PR.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…4699)

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
kitchen_status's broker_authority literal, two agent doc descriptions,
and the parametrized test all hand-typed "ineligible_session_type"
(word order reversed) instead of the registered
ExplorationFailureCode.SESSION_TYPE_INELIGIBLE = "session_type_ineligible"
value that enable_exploration actually returns for this condition.
tools_status.py now references the enum's .value directly so the two
vocabularies cannot drift again.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
When module == SRC_ROOT.name (a bare `import autoskillit` with no
submodule), the function fell through to "" + ".py" = ".py", which
never matches any real key in the parsed relpath dict (the root
package file is keyed "__init__.py"). Return "__init__.py" directly
for this case instead.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ontext_durable

- Module docstring claimed "zero imports from exploration_context"; the
  module actually has one TYPE_CHECKING-only annotation import. Reworded
  to state zero *runtime* imports, which is the actually-true claim.
- bind_session_scoped_durable's docstring claimed authority_home is
  resolved via a server/_factory.py session_authority_home accessor that
  does not exist anywhere in the codebase (confirmed by full-repo grep).
  Reworded to describe what the sole caller actually does.
- _is_capability_shape duplicates
  OwnerBoundExplorationContextStore._is_capability_shape in
  exploration_context.py (intentional, to avoid an import cycle — same
  rationale already documented for the neighboring _MAX_CAPABILITY_LENGTH
  constant). Added the same duplication-acknowledgment comment here so
  both copies are flagged for coordinated updates.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…able_exploration

Both wrap arbitrary underlying failures from store.bind_session_scoped
and ctx.enable_components (raise ... from exc), but their except clauses
returned an opaque failure code with no server-side trace, unlike the
final except Exception branch which already logs with exc_info=True.
For a genuine unexpected failure inside either call, nothing was
recorded server-side.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…rride

arrange_cook()'s settings_content write was nested inside the branch
that only runs when project_dir_override is None. Passing both together
silently dropped settings_content with no error, even though the
caller's intent was clearly to seed a settings file — a footgun for
future callers extending the real-root live gate. Raise ValueError
instead of silently no-op'ing.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
_cmd_spec_call_sites() only matched bare-name CmdSpec(...) calls
(ast.Name), silently skipping a qualified/attribute-path construction
site (e.g. module.CmdSpec(...)), which would never be flagged for
omitting force_inactive_agent_teams= — defeating the contract's stated
guarantee. Mirrors the dual ast.Name/ast.Attribute handling already
used by _stubs_real_claude_backend_validator in
test_cli_cook_validator_stub_guard.py for the analogous problem.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
… test

_field_source_comment()/_field_is_inert_tracked() looked up a field's
preceding doc-comment by field_name alone, scanning the whole file and
returning the FIRST 4-space-indented match — unlike
_has_indirect_method_reader()/_class_node(), which correctly scope to
the dataclass under test. If two enforced dataclasses shared a field
name and only the earlier-in-file one carried an inert-tracked:#NNNN
annotation, the later class's genuinely orphaned field would be
misattributed that annotation and silently pass the contract. Scope
both functions to cls's ast.ClassDef line range via the existing
_class_node() helper.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ceholder

Defined but never consulted by the scan predicate (test_every_code_
literal_is_a_registered_exploration_failure_code() does not reference
it). Its own comment admitted it exists purely for a hypothetical
future envelope shape — exactly the "declared but unread" anti-pattern
this same PR's test_config_field_has_consumer.py argues is dangerous
because it makes reviewers believe a gate exists when none does.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ration

The notification-backend auto_provision_exploration path has both an
opt-in and opt-out test, but the _use_global_enable branch (mcp.enable
path) only had an opt-in test — an asymmetric coverage gap in the very
feature (#4684 Fix D) this PR adds. Mirrors
test_open_kitchen_skips_exploration_for_notification_backend_without_opt_in.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…y edit

Adding ExplorationFailureCode to tools_status.py's import block (prior
commit) shifted the file's grandfathered json.dumps(mcp_data) write
site from line 579 to line 580. Update the line-number-pinned allowlist
entry in test_current_json_write_sites_match_allowlist to match.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@Trecek
Trecek force-pushed the impl-rectify-broker-provisioning-20260817-200349 branch from 10e93ec to e5a52a9 Compare August 20, 2026 00:35
@Trecek
Trecek added this pull request to the merge queue Aug 20, 2026
Merged via the queue into develop with commit 25836a0 Aug 20, 2026
4 checks passed
@Trecek
Trecek deleted the impl-rectify-broker-provisioning-20260817-200349 branch August 20, 2026 00:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant