Skip to content

fix(serve): wire per-session capability scopes, skill/memory tools, and policy fail-closed#6140

Merged
bug-ops merged 2 commits into
mainfrom
fix/6045-serve-scope-denials-sentinel
Jul 12, 2026
Merged

fix(serve): wire per-session capability scopes, skill/memory tools, and policy fail-closed#6140
bug-ops merged 2 commits into
mainfrom
fix/6045-serve-scope-denials-sentinel

Conversation

@bug-ops

@bug-ops bug-ops commented Jul 12, 2026

Copy link
Copy Markdown
Owner

Summary

Closes three follow-up gaps in Zeph's src/serve/* subsystem (/sessions* HTTP+SSE API), all identified during adversarial critic review of recent PRs (#5951/#5958/#5975, #5973/#5977/#5886):

  • ScopedToolExecutor ([security.capability_scopes]) was built once, eagerly, and shared across every concurrent /sessions* agent, so it never received a per-session signal-queue wire-up — OutOfScope capability-scope denials never reached that session's TrajectorySentinel risk-escalation tracking, unlike ACP and daemon. It is now wrapped fresh per session, mirroring ACP's per-connection wiring order.
  • /sessions* agents never had skill_loader, invoke_skill (SkillInvokeExecutor), memory, or overflow tool executors wired in at all, unlike CLI/TUI/ACP/daemon. These are now composed into serve's per-session tool chain, inside the existing trust/policy/adversarial gate stack.
  • A [tools.policy]/[tools.authorization] compile failure silently fell back to disabled policy enforcement for serve too (the same fail-open CLI/ACP/daemon intentionally keep) — but serve is an HTTP-facing entrypoint with potentially remote/less-trusted callers who have no visibility into an operator-console-only tracing::error! line. Serve now aborts startup fail-closed on a real compile failure while staying fail-open when [tools.policy] is legitimately unconfigured.

A follow-up finding from the adversarial critic (startup capability-scope validation compiling against a different, narrower tool registry than the per-session wrap — a default-config-reachable false-positive startup abort) was fixed in the same PR by unifying both call sites through one compose_session_tool_tree helper, so the two registries can no longer drift apart.

Closes #6045
Closes #6046
Closes #6008

Test plan

  • cargo +nightly fmt --check
  • cargo clippy --profile ci --workspace --all-targets --features "desktop,ide,server,chat,pdf,scheduler,testing" -- -D warnings
  • cargo nextest run --config-file .github/nextest.toml --workspace --features "desktop,ide,server,chat,pdf,scheduler" --lib --bins — 13081/13081 passed
  • Rustdoc gate (RUSTFLAGS="-D warnings" RUSTDOCFLAGS="--deny rustdoc::broken_intra_doc_links") — clean
  • Adversarial critic review (2 rounds — found + fixed a registry-drift issue between startup validation and per-session scoping)
  • Code review — approved
  • .local/testing/playbooks/serve.md and .local/testing/coverage-status.md updated with live-testing scenarios for all three fixes

@github-actions github-actions Bot added documentation Improvements or additions to documentation bug Something isn't working size/XL Extra large PR (500+ lines) labels Jul 12, 2026
@bug-ops
bug-ops force-pushed the fix/6045-serve-scope-denials-sentinel branch from 60bcdfa to a202fd1 Compare July 12, 2026 15:04
@bug-ops
bug-ops enabled auto-merge (squash) July 12, 2026 15:04
bug-ops added 2 commits July 12, 2026 17:25
…nd policy fail-closed

ScopedToolExecutor now wraps fresh per session instead of being built
once and shared, so OutOfScope capability-scope denials reach that
session's TrajectorySentinel signal queue like ACP and daemon already
do. Startup validation and the per-session wrap now share a single
compose_session_tool_tree helper covering the same tool surface,
closing a false-positive startup abort for scopes that reference
skill_loader/invoke_skill/memory/overflow tools.

Those four tool executors are also now wired into /sessions* agents,
matching the CLI/TUI/ACP/daemon tool surface (MCP tools, the scheduler
executor, and hot-reload broadcast forwarding remain a separately
tracked gap).

A [tools.policy]/[tools.authorization] compile failure now aborts
serve-sessions startup instead of silently disabling policy
enforcement, since serve is HTTP-facing with potentially
remote/less-trusted callers. CLI/TUI/ACP/daemon keep their existing
fail-open behavior unchanged.

Closes #6045
Closes #6046
Closes #6008
…eature bundles

PolicyGatePieces::policy_configured (#6008) is only read from src/serve/deps.rs,
which is gated behind #[cfg(feature = "session")]. Feature bundles that don't
pull in session (ide, chat, bench) never compile that reader, so the field was
flagged dead_code there, and CI's per-bundle Bundle Check/MSRV jobs deny
warnings — failing the build. desktop/server both pull in session transitively,
which is why the combined local check suite never caught this.
@bug-ops
bug-ops force-pushed the fix/6045-serve-scope-denials-sentinel branch from 54f8e64 to 5596822 Compare July 12, 2026 15:27
@bug-ops
bug-ops merged commit 35b3d4c into main Jul 12, 2026
43 checks passed
@bug-ops
bug-ops deleted the fix/6045-serve-scope-denials-sentinel branch July 12, 2026 15:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working documentation Improvements or additions to documentation size/XL Extra large PR (500+ lines)

Projects

None yet

1 participant