553 lines
17 KiB
Markdown
553 lines
17 KiB
Markdown
# Auth Diagnostics Slice 2 Implementation Plan
|
|
|
|
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
|
|
**Goal:** Surface missing auth references as explicit diagnostics in live source checks and source-registry apply summaries.
|
|
|
|
**Architecture:** Keep auth diagnostics MCP-provider-specific for now because current source/runtime auth resolution is implemented in `wf_mcp`. Add a small helper in `wf_mcp.auth` that inspects a `ConnectionConfig` and an auth lookup function, then reuse it from `UpstreamTransportService.deployment_diagnostics()` and `SourceRegistryAdminProvider.apply_registry_changes()`. Do not add auth admin CRUD, do not change auth storage, and do not make `wf_api` understand MCP credential payloads.
|
|
|
|
**Tech Stack:** Python 3.14, dataclasses, pytest, ruff, basedpyright.
|
|
|
|
---
|
|
|
|
## Scope
|
|
|
|
Implement only:
|
|
|
|
- `auth_not_found` diagnostic helper for connections with string `metadata["auth_ref"]`.
|
|
- live source diagnostics before upstream liveness probe.
|
|
- source registry apply summary field: `auth_diagnostics`.
|
|
- docs status update.
|
|
|
|
Do not implement:
|
|
|
|
- auth admin list/save/delete commands
|
|
- deployment static validation changes
|
|
- OAuth/secret-manager behavior
|
|
- source registry mutation rejection on missing auth
|
|
- any neutral `wf_api` diagnostic model changes
|
|
|
|
## Files
|
|
|
|
- Modify: `src/wf_mcp/auth.py`
|
|
- add `auth_ref_for_connection`
|
|
- add `auth_missing_diagnostic`
|
|
- add `connection_auth_diagnostic`
|
|
- Modify: `src/wf_mcp/broker/service/upstream_transport.py`
|
|
- use helper in `deployment_diagnostics`
|
|
- Modify: `src/wf_mcp/broker/service/source_registry_admin.py`
|
|
- accept optional auth loader
|
|
- return `auth_diagnostics` from `apply_registry_changes`
|
|
- Modify: `src/wf_mcp/broker/server.py`
|
|
- wire source-registry admin provider to upstream auth loader if needed
|
|
- Test: `tests/wf_mcp/test_auth.py`
|
|
- helper tests
|
|
- Test: `tests/wf_mcp/service/test_upstream_transport.py`
|
|
- live diagnostic for missing `auth_ref`
|
|
- Test: `tests/wf_mcp/service/test_source_registry_admin.py`
|
|
- apply summary includes auth diagnostics
|
|
- Docs: `docs/current_roadmap.md`
|
|
- Docs: `docs/superpowers/specs/2026-06-06-auth-source-secrets-boundary.md`
|
|
|
|
## Task 1: MCP auth diagnostic helper
|
|
|
|
**Files:**
|
|
- Modify: `src/wf_mcp/auth.py`
|
|
- Modify: `tests/wf_mcp/test_auth.py`
|
|
|
|
- [ ] **Step 1: Add helper tests**
|
|
|
|
Append to `tests/wf_mcp/test_auth.py`:
|
|
|
|
```python
|
|
from wf_artifacts import DiagnosticSeverity
|
|
from wf_mcp.auth import (
|
|
auth_ref_for_connection,
|
|
connection_auth_diagnostic,
|
|
)
|
|
from wf_mcp.models import ConnectionConfig
|
|
|
|
|
|
def test_auth_ref_for_connection_returns_string_only() -> None:
|
|
assert (
|
|
auth_ref_for_connection(
|
|
ConnectionConfig(
|
|
id="github.work",
|
|
server="github",
|
|
account="work",
|
|
metadata={"auth_ref": "github.creds"},
|
|
)
|
|
)
|
|
== "github.creds"
|
|
)
|
|
assert (
|
|
auth_ref_for_connection(
|
|
ConnectionConfig(
|
|
id="github.work",
|
|
server="github",
|
|
account="work",
|
|
metadata={"auth_ref": 123},
|
|
)
|
|
)
|
|
is None
|
|
)
|
|
|
|
|
|
def test_connection_auth_diagnostic_reports_missing_auth_ref() -> None:
|
|
connection = ConnectionConfig(
|
|
id="github.work",
|
|
server="github",
|
|
account="work",
|
|
metadata={"auth_ref": "github.creds"},
|
|
)
|
|
|
|
diagnostic = connection_auth_diagnostic(
|
|
connection,
|
|
load_auth=lambda auth_ref: None,
|
|
logical_ref="github",
|
|
)
|
|
|
|
assert diagnostic is not None
|
|
assert diagnostic.severity == DiagnosticSeverity.ERROR
|
|
assert diagnostic.code == "auth_not_found"
|
|
assert diagnostic.logical_ref == "github"
|
|
assert diagnostic.bound_source == "github.work"
|
|
assert "github.creds" in diagnostic.message
|
|
assert "Add an auth record" in diagnostic.repair_hint
|
|
|
|
|
|
def test_connection_auth_diagnostic_ignores_absent_or_present_auth_ref() -> None:
|
|
no_ref = ConnectionConfig(id="github.work", server="github", account="work")
|
|
with_ref = ConnectionConfig(
|
|
id="github.work",
|
|
server="github",
|
|
account="work",
|
|
metadata={"auth_ref": "github.creds"},
|
|
)
|
|
auth = McpAuthRecord(
|
|
connection_id="github.creds",
|
|
scheme="bearer",
|
|
payload={"token": "secret"},
|
|
)
|
|
|
|
assert (
|
|
connection_auth_diagnostic(
|
|
no_ref,
|
|
load_auth=lambda auth_ref: None,
|
|
logical_ref="github",
|
|
)
|
|
is None
|
|
)
|
|
assert (
|
|
connection_auth_diagnostic(
|
|
with_ref,
|
|
load_auth=lambda auth_ref: auth,
|
|
logical_ref="github",
|
|
)
|
|
is None
|
|
)
|
|
```
|
|
|
|
- [ ] **Step 2: Run tests to verify failure**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
uv run pytest tests/wf_mcp/test_auth.py -q
|
|
```
|
|
|
|
Expected: fails because `auth_ref_for_connection` and `connection_auth_diagnostic` do not exist.
|
|
|
|
- [ ] **Step 3: Implement helper functions**
|
|
|
|
Modify `src/wf_mcp/auth.py`.
|
|
|
|
Add imports:
|
|
|
|
```python
|
|
from collections.abc import Callable
|
|
|
|
from wf_artifacts import DependencyDiagnostic, DiagnosticSeverity
|
|
|
|
from .models import ConnectionConfig
|
|
```
|
|
|
|
Add functions before `__all__`:
|
|
|
|
```python
|
|
def auth_ref_for_connection(connection: ConnectionConfig) -> str | None:
|
|
"""Return the explicit auth ref for one source connection, if present."""
|
|
|
|
auth_ref = connection.metadata.get("auth_ref")
|
|
return auth_ref if isinstance(auth_ref, str) else None
|
|
|
|
|
|
def auth_missing_diagnostic(
|
|
*,
|
|
auth_ref: str,
|
|
source_id: str,
|
|
logical_ref: str | None = None,
|
|
) -> DependencyDiagnostic:
|
|
"""Build a stable diagnostic without including secret payload data."""
|
|
|
|
return DependencyDiagnostic(
|
|
severity=DiagnosticSeverity.ERROR,
|
|
code="auth_not_found",
|
|
logical_ref=logical_ref,
|
|
bound_source=source_id,
|
|
message=(
|
|
f"Source {source_id!r} references auth record {auth_ref!r}, "
|
|
"but no auth record was found."
|
|
),
|
|
repair_hint=(
|
|
"Add an auth record for this auth_ref, update the source auth_ref, "
|
|
"or bind the deployment to a source that does not require it."
|
|
),
|
|
)
|
|
|
|
|
|
def connection_auth_diagnostic(
|
|
connection: ConnectionConfig,
|
|
*,
|
|
load_auth: Callable[[str], McpAuthRecord | None],
|
|
logical_ref: str | None = None,
|
|
) -> DependencyDiagnostic | None:
|
|
"""Return an auth diagnostic for explicit auth_ref misses.
|
|
|
|
Connections without explicit auth_ref keep legacy no-auth behavior. This
|
|
makes the new auth boundary observable without treating every unauthenticated
|
|
MCP source as an error.
|
|
"""
|
|
|
|
auth_ref = auth_ref_for_connection(connection)
|
|
if auth_ref is None:
|
|
return None
|
|
if load_auth(auth_ref) is not None:
|
|
return None
|
|
return auth_missing_diagnostic(
|
|
auth_ref=auth_ref,
|
|
source_id=connection.id,
|
|
logical_ref=logical_ref,
|
|
)
|
|
```
|
|
|
|
Add to `__all__`:
|
|
|
|
```python
|
|
"auth_missing_diagnostic",
|
|
"auth_ref_for_connection",
|
|
"connection_auth_diagnostic",
|
|
```
|
|
|
|
- [ ] **Step 4: Run focused tests**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
uv run pytest tests/wf_mcp/test_auth.py -q
|
|
uv run ruff check src/wf_mcp/auth.py tests/wf_mcp/test_auth.py
|
|
uv run basedpyright --level error src/wf_mcp/auth.py tests/wf_mcp/test_auth.py
|
|
```
|
|
|
|
Expected: all pass.
|
|
|
|
## Task 2: Live source auth diagnostics
|
|
|
|
**Files:**
|
|
- Modify: `src/wf_mcp/broker/service/upstream_transport.py`
|
|
- Modify: `tests/wf_mcp/service/test_upstream_transport.py`
|
|
|
|
- [ ] **Step 1: Add live diagnostic test**
|
|
|
|
Append to `tests/wf_mcp/service/test_upstream_transport.py`:
|
|
|
|
```python
|
|
async def test_upstream_transport_live_diagnostics_report_missing_auth_ref(
|
|
tmp_path: Path,
|
|
) -> None:
|
|
events: list[McpEvent] = []
|
|
store = FileStore(tmp_path)
|
|
connections = ConnectionRegistry()
|
|
connection = ConnectionConfig(
|
|
id="github.work",
|
|
server="demo",
|
|
account="work",
|
|
metadata={"auth_ref": "github.creds"},
|
|
)
|
|
connections.register(connection)
|
|
transport = UpstreamTransportService(store=store, event_sink=events.append)
|
|
transport.register_adapter("demo", FakeAdapter())
|
|
source_catalog = SourceCatalogService(
|
|
store=store,
|
|
connection_lookup=connections.get,
|
|
connection_list_enabled=connections.list_enabled,
|
|
connection_list_all=connections.list_all,
|
|
tool_executor_for=transport.tool_executor_for,
|
|
load_auth=transport.load_connection_auth,
|
|
emit_event=events.append,
|
|
)
|
|
source_catalog.register_capability_source(
|
|
CapabilitySource(
|
|
id="github.work",
|
|
kind="connection",
|
|
permissions=SourcePermissions(calls_upstream=True),
|
|
capabilities=CapabilityBuckets(),
|
|
)
|
|
)
|
|
artifact = echo_artifact()
|
|
deployment = WorkflowDeployment(
|
|
id="echo.personal",
|
|
artifact_id="echo",
|
|
artifact_version=1,
|
|
bindings=[{"logical_source": "demo", "concrete_source": "github.work"}],
|
|
)
|
|
|
|
diagnostics = await transport.deployment_diagnostics(
|
|
deployment=deployment,
|
|
artifacts=[artifact],
|
|
source_catalog=source_catalog,
|
|
)
|
|
|
|
assert diagnostics[0].code == "auth_not_found"
|
|
assert diagnostics[0].bound_source == "github.work"
|
|
assert "github.creds" in diagnostics[0].message
|
|
```
|
|
|
|
- [ ] **Step 2: Run test to verify failure**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
uv run pytest tests/wf_mcp/service/test_upstream_transport.py::test_upstream_transport_live_diagnostics_report_missing_auth_ref -q
|
|
```
|
|
|
|
Expected: fails because live diagnostics currently probe with `auth=None` instead of reporting `auth_not_found`.
|
|
|
|
- [ ] **Step 3: Add diagnostic before liveness probe**
|
|
|
|
Modify `src/wf_mcp/broker/service/upstream_transport.py`.
|
|
|
|
Add import:
|
|
|
|
```python
|
|
from wf_mcp.auth import connection_auth_diagnostic
|
|
```
|
|
|
|
Inside `deployment_diagnostics`, after `connection = source_catalog.connection_lookup(source_id)` succeeds and before `adapter = require_adapter(...)`, add:
|
|
|
|
```python
|
|
auth_diagnostic = connection_auth_diagnostic(
|
|
connection,
|
|
load_auth=self.load_auth,
|
|
logical_ref=logical_ref,
|
|
)
|
|
if auth_diagnostic is not None:
|
|
diagnostics.append(auth_diagnostic)
|
|
continue
|
|
```
|
|
|
|
Use `self.load_auth` here intentionally: it loads by auth ref. Do not use
|
|
`load_connection_auth`, because that would convert a missing explicit auth ref
|
|
into `None` and lose the diagnostic reason.
|
|
|
|
- [ ] **Step 4: Run focused tests**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
uv run pytest tests/wf_mcp/service/test_upstream_transport.py -q
|
|
uv run ruff check src/wf_mcp/broker/service/upstream_transport.py tests/wf_mcp/service/test_upstream_transport.py
|
|
uv run basedpyright --level error src/wf_mcp/broker/service/upstream_transport.py tests/wf_mcp/service/test_upstream_transport.py
|
|
```
|
|
|
|
Expected: all pass.
|
|
|
|
## Task 3: Source registry apply auth diagnostics
|
|
|
|
**Files:**
|
|
- Modify: `src/wf_mcp/broker/service/source_registry_admin.py`
|
|
- Modify: `src/wf_mcp/broker/server.py`
|
|
- Modify: `tests/wf_mcp/service/test_source_registry_admin.py`
|
|
|
|
- [ ] **Step 1: Add apply summary test**
|
|
|
|
Append to `tests/wf_mcp/service/test_source_registry_admin.py`:
|
|
|
|
```python
|
|
def test_source_registry_apply_reports_missing_auth_ref(tmp_path: Path) -> None:
|
|
entry = McpSourceRegistryEntry(
|
|
id="github.work",
|
|
provider="github",
|
|
account="work",
|
|
auth_ref="github.creds",
|
|
transport=StdioSourceTransport(command="npx"),
|
|
)
|
|
provider, connection_service, _source_catalog = _apply_provider(
|
|
tmp_path,
|
|
registry_sources=[entry],
|
|
)
|
|
provider.load_auth = lambda auth_ref: None
|
|
|
|
payload = provider.apply_registry_changes()
|
|
|
|
assert payload["applied"] is True
|
|
assert payload["registered"] == ["github.work"]
|
|
assert connection_service.get("github.work").metadata["auth_ref"] == "github.creds"
|
|
assert payload["auth_diagnostics"] == [
|
|
{
|
|
"severity": "error",
|
|
"code": "auth_not_found",
|
|
"logical_ref": None,
|
|
"bound_source": "github.work",
|
|
"message": (
|
|
"Source 'github.work' references auth record 'github.creds', "
|
|
"but no auth record was found."
|
|
),
|
|
"repair_hint": (
|
|
"Add an auth record for this auth_ref, update the source auth_ref, "
|
|
"or bind the deployment to a source that does not require it."
|
|
),
|
|
}
|
|
]
|
|
```
|
|
|
|
If `DependencyDiagnostic.model_dump(mode="json")` uses enum values differently
|
|
in this repo, assert individual fields instead:
|
|
|
|
```python
|
|
diagnostic = payload["auth_diagnostics"][0]
|
|
assert diagnostic["code"] == "auth_not_found"
|
|
assert diagnostic["bound_source"] == "github.work"
|
|
assert "github.creds" in diagnostic["message"]
|
|
```
|
|
|
|
- [ ] **Step 2: Run test to verify failure**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
uv run pytest tests/wf_mcp/service/test_source_registry_admin.py::test_source_registry_apply_reports_missing_auth_ref -q
|
|
```
|
|
|
|
Expected: fails because `SourceRegistryAdminProvider` has no `load_auth` field and apply summary has no `auth_diagnostics`.
|
|
|
|
- [ ] **Step 3: Add auth loader to provider**
|
|
|
|
Modify `src/wf_mcp/broker/service/source_registry_admin.py`.
|
|
|
|
Add imports:
|
|
|
|
```python
|
|
from ...auth import connection_auth_diagnostic
|
|
from ...models import AuthRecord
|
|
```
|
|
|
|
Add field to `SourceRegistryAdminProvider`:
|
|
|
|
```python
|
|
load_auth: Callable[[str], AuthRecord | None] | None = None
|
|
```
|
|
|
|
- [ ] **Step 4: Add diagnostics to apply summary**
|
|
|
|
In `apply_registry_changes`, after computing `after`, add:
|
|
|
|
```python
|
|
auth_diagnostics = []
|
|
if self.load_auth is not None:
|
|
for source_id in sorted(after):
|
|
diagnostic = connection_auth_diagnostic(
|
|
after[source_id],
|
|
load_auth=self.load_auth,
|
|
)
|
|
if diagnostic is not None:
|
|
auth_diagnostics.append(diagnostic.model_dump(mode="json"))
|
|
```
|
|
|
|
Then add to the returned dict:
|
|
|
|
```python
|
|
"auth_diagnostics": auth_diagnostics,
|
|
```
|
|
|
|
Do not reject apply when auth is missing. Apply reconciles desired source state;
|
|
auth diagnostics tell the operator why later live calls may fail.
|
|
|
|
- [ ] **Step 5: Wire runtime provider construction**
|
|
|
|
Modify `src/wf_mcp/broker/server.py`.
|
|
|
|
Find where `SourceRegistryAdminProvider(...)` is constructed for MCP-backed
|
|
`WorkflowServer`. Add:
|
|
|
|
```python
|
|
load_auth=service.upstream.load_auth,
|
|
```
|
|
|
|
If the file constructs the provider through a helper, pass the loader through
|
|
that helper. Do not change local/static server behavior; local/static still has
|
|
no source registry admin provider.
|
|
|
|
- [ ] **Step 6: Run focused tests**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
uv run pytest tests/wf_mcp/service/test_source_registry_admin.py tests/wf_transport_rpc_http/test_mcp_backed_server_rpc.py -q
|
|
uv run ruff check src/wf_mcp/broker/service/source_registry_admin.py src/wf_mcp/broker/server.py tests/wf_mcp/service/test_source_registry_admin.py
|
|
uv run basedpyright --level error src/wf_mcp/broker/service/source_registry_admin.py src/wf_mcp/broker/server.py tests/wf_mcp/service/test_source_registry_admin.py
|
|
```
|
|
|
|
Expected: all pass.
|
|
|
|
## Task 4: Docs and verification
|
|
|
|
**Files:**
|
|
- Modify: `docs/current_roadmap.md`
|
|
- Modify: `docs/superpowers/specs/2026-06-06-auth-source-secrets-boundary.md`
|
|
|
|
- [ ] **Step 1: Update spec status**
|
|
|
|
In `docs/superpowers/specs/2026-06-06-auth-source-secrets-boundary.md`, update
|
|
the `## Status` section to:
|
|
|
|
```markdown
|
|
Slice 1 implements the neutral auth record/store protocol and MCP compatibility
|
|
bridge. Slice 2 surfaces missing explicit auth refs through live source
|
|
diagnostics and source registry apply summaries. Auth admin surfaces and
|
|
provider-specific auth unions are future slices.
|
|
```
|
|
|
|
- [ ] **Step 2: Update roadmap**
|
|
|
|
In `docs/current_roadmap.md`, under the auth/source secrets boundary bullet,
|
|
append:
|
|
|
|
```markdown
|
|
Second implementation slice complete: missing explicit auth refs now surface
|
|
as `auth_not_found` diagnostics in live source checks and source registry
|
|
apply summaries.
|
|
```
|
|
|
|
- [ ] **Step 3: Run final verification**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
uv run pytest tests/wf_mcp/test_auth.py tests/wf_mcp/service/test_upstream_transport.py tests/wf_mcp/service/test_source_registry_admin.py tests/wf_transport_rpc_http/test_mcp_backed_server_rpc.py -q
|
|
uv run ruff check src/wf_mcp/auth.py src/wf_mcp/broker/service/upstream_transport.py src/wf_mcp/broker/service/source_registry_admin.py src/wf_mcp/broker/server.py tests/wf_mcp/test_auth.py tests/wf_mcp/service/test_upstream_transport.py tests/wf_mcp/service/test_source_registry_admin.py
|
|
uv run basedpyright --level error src/wf_mcp/auth.py src/wf_mcp/broker/service/upstream_transport.py src/wf_mcp/broker/service/source_registry_admin.py src/wf_mcp/broker/server.py tests/wf_mcp/test_auth.py tests/wf_mcp/service/test_upstream_transport.py tests/wf_mcp/service/test_source_registry_admin.py
|
|
```
|
|
|
|
Expected: all pass.
|
|
|
|
- [ ] **Step 4: Final report**
|
|
|
|
Report:
|
|
|
|
- files changed
|
|
- verification output
|
|
- final shape of `auth_diagnostics`
|
|
- any deviations from the plan
|
|
|