Files
lda-wf/docs/historical/superpowers/plans/2026-06-06-auth-admin-mutation-slice-4.md
T

928 lines
29 KiB
Markdown

# Auth Admin Mutation Slice 4 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:** Add local/dev auth record save and delete through neutral admin, JSON-RPC, and `wf admin auth` CLI without exposing secret payload values in responses.
**Architecture:** Extend the existing neutral `WorkflowAdminApi` auth surface with mutation methods, then implement those methods in the MCP-backed file-store provider. JSON-RPC and CLI call the neutral admin surface; do not add new `wf-mcp` tools or old broker-only product behavior. This is a local/dev file-store slice only: no OAuth, no production secret manager, no provider-specific auth variants, and no secret payload values in response bodies.
**Tech Stack:** Python 3.14, dataclasses, Pydantic v2, Typer, JSON-RPC HTTP transport, pytest, ruff, basedpyright.
---
## Boundaries
- Do not add new behavior to the legacy `wf-mcp` script.
- Do not expose auth payload values in list, inspect, save, or delete responses.
- Do not support inline auth records in `wf_config` or source registry documents.
- Do not add OAuth/browser login, encryption, or secret-manager integration.
- Do not change the on-disk auth JSON shape in this slice.
- Do use `wf_api.auth.AuthRecord` as the neutral input shape for save/upsert.
- Do keep the current compatibility `wf_mcp.auth.AuthRecord(connection_id, scheme, payload)` file adapter.
## Files
- Modify `src/wf_api/admin.py`
- Add mutation methods to `WorkflowAdminAuthProvider`.
- Add `WorkflowAdminApi.save_auth_record(...)`.
- Add `WorkflowAdminApi.delete_auth_record(...)`.
- Modify `src/wf_api/surface.py`
- Add mutation methods to `WorkflowAdminSurface`.
- Modify `src/wf_mcp/storage/store.py`
- Add `delete_auth(...)` and `delete_auth_record(...)` to `Store` / `FileStore`.
- Modify `src/wf_mcp/broker/service/auth_admin.py`
- Implement save/delete using `Store.save_auth_record` and `Store.delete_auth_record`.
- Modify `src/wf_transport_rpc_http/models.py`
- Add `SaveAuthParams` and `DeleteAuthParams`.
- Modify `src/wf_transport_rpc_http/methods_admin.py`
- Register `workflow.admin.auth.save` and `workflow.admin.auth.delete`.
- Modify `src/wf_transport_rpc_http/client_admin.py`
- Add `save_auth_record(...)` and `delete_auth_record(...)`.
- Modify `src/wf_cli/commands/auth_admin.py`
- Add `wf admin auth save`.
- Add `wf admin auth delete --confirm`.
- Modify docs:
- `docs/superpowers/specs/2026-06-06-auth-source-secrets-boundary.md`
- `docs/current_roadmap.md`
- `docs/wf_cli.md`
- Add/update tests:
- `tests/wf_api/test_admin_api.py`
- `tests/wf_mcp/service/test_auth_admin.py`
- `tests/wf_mcp/test_auth.py` or `tests/wf_mcp/test_store.py`
- `tests/wf_transport_rpc_http/test_admin_auth_rpc.py`
- `tests/wf_cli/test_auth_admin.py`
---
### Task 1: Neutral Admin Auth Mutation Surface
**Files:**
- Modify: `src/wf_api/admin.py`
- Modify: `src/wf_api/surface.py`
- Test: `tests/wf_api/test_admin_api.py`
- [ ] **Step 1: Add failing neutral API tests**
Append these tests to `tests/wf_api/test_admin_api.py`. If a local fake provider already exists in the file, extend it instead of duplicating the whole class.
```python
from wf_api.auth import AuthRecord
class MutableAuthProvider(AuthProvider):
def __init__(self) -> None:
self.records: dict[str, dict[str, Any]] = {}
def list_auth_records(self):
return list(self.records.values())
def inspect_auth_record(self, auth_ref: str):
try:
return self.records[auth_ref]
except KeyError as exc:
raise KeyError(auth_ref) from exc
def save_auth_record(self, record: AuthRecord):
self.records[record.id] = {
"id": record.id,
"scheme": record.scheme,
"metadata": dict(record.metadata),
"payload_keys": sorted(str(key) for key in record.payload),
}
return self.records[record.id]
def delete_auth_record(self, auth_ref: str):
if auth_ref not in self.records:
raise KeyError(auth_ref)
del self.records[auth_ref]
return {"deleted": True, "id": auth_ref}
def test_admin_saves_auth_record_without_payload_values() -> None:
provider = MutableAuthProvider()
api = _api(provider)
payload = asyncio.run(
api.save_auth_record(
auth_ref="drive.work",
scheme="bearer",
payload={"token": "secret"},
metadata={"owner": "test"},
)
)
assert payload == {
"id": "drive.work",
"scheme": "bearer",
"metadata": {"owner": "test"},
"payload_keys": ["token"],
}
assert "secret" not in str(payload)
def test_admin_deletes_auth_record() -> None:
provider = MutableAuthProvider()
api = _api(provider)
asyncio.run(
api.save_auth_record(
auth_ref="drive.work",
scheme="bearer",
payload={"token": "secret"},
)
)
payload = asyncio.run(api.delete_auth_record("drive.work"))
assert payload == {"deleted": True, "id": "drive.work"}
with pytest.raises(KeyError):
provider.inspect_auth_record("drive.work")
def test_admin_auth_mutations_report_unavailable_without_provider() -> None:
with pytest.raises(RuntimeError, match="auth admin is not available"):
asyncio.run(
_api().save_auth_record(
auth_ref="drive.work",
scheme="bearer",
payload={"token": "secret"},
)
)
with pytest.raises(RuntimeError, match="auth admin is not available"):
asyncio.run(_api().delete_auth_record("drive.work"))
```
- [ ] **Step 2: Run the failing tests**
Run:
```powershell
uv run pytest tests\wf_api\test_admin_api.py -q
```
Expected: fail because `WorkflowAdminApi.save_auth_record` and `delete_auth_record` do not exist.
- [ ] **Step 3: Implement neutral API methods**
In `src/wf_api/admin.py`, add:
```python
from wf_api.auth import AuthRecord
```
Extend `WorkflowAdminAuthProvider`:
```python
class WorkflowAdminAuthProvider(Protocol):
"""Provides auth inventory and local/dev auth mutation."""
def list_auth_records(self) -> Sequence[Mapping[str, Any] | object]: ...
def inspect_auth_record(self, auth_ref: str) -> Mapping[str, Any] | object: ...
def save_auth_record(self, record: AuthRecord) -> Mapping[str, Any] | object: ...
def delete_auth_record(self, auth_ref: str) -> Mapping[str, Any] | object: ...
```
Add methods to `WorkflowAdminApi`:
```python
async def save_auth_record(
self,
*,
auth_ref: str,
scheme: str,
payload: Mapping[str, object],
metadata: Mapping[str, object] | None = None,
) -> dict[str, Any]:
if self.auth is None:
raise RuntimeError("auth admin is not available for this target")
record = AuthRecord(
id=auth_ref,
scheme=scheme,
payload=dict(payload),
metadata=dict(metadata or {}),
)
return _payload(self.auth.save_auth_record(record))
async def delete_auth_record(self, auth_ref: str) -> dict[str, Any]:
if self.auth is None:
raise RuntimeError("auth admin is not available for this target")
return _payload(self.auth.delete_auth_record(auth_ref))
```
In `src/wf_api/surface.py`, add these methods to `WorkflowAdminSurface`:
```python
async def save_auth_record(
self,
*,
auth_ref: str,
scheme: str,
payload: Mapping[str, object],
metadata: Mapping[str, object] | None = None,
) -> dict[str, Any]: ...
async def delete_auth_record(self, auth_ref: str) -> dict[str, Any]: ...
```
If `Mapping` is not imported in `surface.py`, import it from `collections.abc`.
- [ ] **Step 4: Run tests**
Run:
```powershell
uv run pytest tests\wf_api\test_admin_api.py -q
uv run ruff check src\wf_api\admin.py src\wf_api\surface.py tests\wf_api\test_admin_api.py
uv run basedpyright --level error src\wf_api\admin.py src\wf_api\surface.py tests\wf_api\test_admin_api.py
```
Expected: pass.
---
### Task 2: MCP File Store Save/Delete Provider
**Files:**
- Modify: `src/wf_mcp/storage/store.py`
- Modify: `src/wf_mcp/broker/service/auth_admin.py`
- Test: `tests/wf_mcp/test_store.py`
- Test: `tests/wf_mcp/service/test_auth_admin.py`
- [ ] **Step 1: Add failing store tests**
Append to `tests/wf_mcp/test_store.py`:
```python
def test_file_store_deletes_auth_record(tmp_path) -> None:
store = FileStore(tmp_path / "store")
record = AuthRecord(
connection_id="drive.work",
scheme="bearer",
payload={"token": "secret"},
)
store.save_auth(record)
assert store.load_auth("drive.work") == record
assert store.delete_auth("drive.work") is True
assert store.load_auth("drive.work") is None
assert store.delete_auth("drive.work") is False
def test_file_store_deletes_neutral_auth_record(tmp_path) -> None:
store = FileStore(tmp_path / "store")
store.save_auth_record(
NeutralAuthRecord(
id="drive.work",
scheme="bearer",
payload={"token": "secret"},
)
)
assert store.delete_auth_record("drive.work") is True
assert store.load_auth_record("drive.work") is None
```
Add imports if needed:
```python
from wf_api.auth import AuthRecord as NeutralAuthRecord
```
- [ ] **Step 2: Add failing provider tests**
Append to `tests/wf_mcp/service/test_auth_admin.py`:
```python
from wf_api.auth import AuthRecord as NeutralAuthRecord
def test_auth_admin_provider_saves_auth_without_returning_payload(tmp_path) -> None:
store = FileStore(tmp_path / "store")
provider = McpAuthAdminProvider(store)
payload = provider.save_auth_record(
NeutralAuthRecord(
id="drive.work",
scheme="bearer",
payload={"token": "secret"},
metadata={"owner": "test"},
)
)
assert payload == {
"id": "drive.work",
"scheme": "bearer",
"metadata": {},
"payload_keys": ["token"],
}
assert "secret" not in str(payload)
assert store.load_auth("drive.work") == AuthRecord(
connection_id="drive.work",
scheme="bearer",
payload={"token": "secret"},
)
def test_auth_admin_provider_deletes_auth(tmp_path) -> None:
store = FileStore(tmp_path / "store")
provider = McpAuthAdminProvider(store)
store.save_auth(AuthRecord(connection_id="drive.work", scheme="bearer"))
payload = provider.delete_auth_record("drive.work")
assert payload == {"deleted": True, "id": "drive.work"}
assert store.load_auth("drive.work") is None
def test_auth_admin_provider_delete_unknown_auth_raises_key_error(tmp_path) -> None:
provider = McpAuthAdminProvider(FileStore(tmp_path / "store"))
with pytest.raises(KeyError, match="unknown auth record 'missing'"):
provider.delete_auth_record("missing")
```
- [ ] **Step 3: Run failing tests**
Run:
```powershell
uv run pytest tests\wf_mcp\test_store.py tests\wf_mcp\service\test_auth_admin.py -q
```
Expected: fail because delete/save provider mutation methods are missing.
- [ ] **Step 4: Implement store delete methods**
In `src/wf_mcp/storage/store.py`, extend `Store`:
```python
def delete_auth(self, connection_id: str) -> bool:
raise NotImplementedError
def delete_auth_record(self, auth_ref: str) -> bool:
raise NotImplementedError
```
In `FileStore`, add:
```python
def delete_auth(self, connection_id: str) -> bool:
path = self._auth_path(connection_id)
if not path.exists():
return False
path.unlink()
return True
def delete_auth_record(self, auth_ref: str) -> bool:
"""Delete neutral auth through the legacy MCP file shape."""
return self.delete_auth(auth_ref)
```
- [ ] **Step 5: Implement provider methods**
In `src/wf_mcp/broker/service/auth_admin.py`, import neutral auth:
```python
from wf_api.auth import AuthRecord as NeutralAuthRecord
```
Add methods to `McpAuthAdminProvider`:
```python
def save_auth_record(self, record: NeutralAuthRecord) -> dict[str, Any]:
self.store.save_auth_record(record)
return self.inspect_auth_record(record.id)
def delete_auth_record(self, auth_ref: str) -> dict[str, Any]:
deleted = self.store.delete_auth_record(auth_ref)
if not deleted:
raise KeyError(f"unknown auth record {auth_ref!r}")
return {"deleted": True, "id": auth_ref}
```
- [ ] **Step 6: Run tests**
Run:
```powershell
uv run pytest tests\wf_mcp\test_store.py tests\wf_mcp\service\test_auth_admin.py -q
uv run ruff check src\wf_mcp\storage\store.py src\wf_mcp\broker\service\auth_admin.py tests\wf_mcp\test_store.py tests\wf_mcp\service\test_auth_admin.py
uv run basedpyright --level error src\wf_mcp\storage\store.py src\wf_mcp\broker\service\auth_admin.py tests\wf_mcp\test_store.py tests\wf_mcp\service\test_auth_admin.py
```
Expected: pass.
---
### Task 3: JSON-RPC Auth Mutation Methods
**Files:**
- Modify: `src/wf_transport_rpc_http/models.py`
- Modify: `src/wf_transport_rpc_http/methods_admin.py`
- Modify: `src/wf_transport_rpc_http/client_admin.py`
- Test: `tests/wf_transport_rpc_http/test_admin_auth_rpc.py`
- [ ] **Step 1: Add failing RPC tests**
Append to `tests/wf_transport_rpc_http/test_admin_auth_rpc.py`:
```python
async def test_rpc_saves_auth_record_without_returning_payload(tmp_path) -> None:
store = FileStore(tmp_path / "store")
server = build_workflow_server_from_config(BrokerConfig(store_root=store.root))
async with _client(server) as client:
payload = await client.save_auth_record(
auth_ref="drive.work",
scheme="bearer",
payload={"token": "secret"},
metadata={"owner": "test"},
)
assert payload["id"] == "drive.work"
assert payload["scheme"] == "bearer"
assert payload["payload_keys"] == ["token"]
assert "secret" not in str(payload)
assert FileStore(store.root).load_auth("drive.work") == AuthRecord(
connection_id="drive.work",
scheme="bearer",
payload={"token": "secret"},
)
async def test_rpc_deletes_auth_record(tmp_path) -> None:
store = FileStore(tmp_path / "store")
store.save_auth(AuthRecord(connection_id="drive.work", scheme="bearer"))
server = build_workflow_server_from_config(BrokerConfig(store_root=store.root))
async with _client(server) as client:
payload = await client.delete_auth_record("drive.work")
assert payload == {"deleted": True, "id": "drive.work"}
assert FileStore(store.root).load_auth("drive.work") is None
```
Use the existing `_client(...)` helper in this test file. If it has a different name, adapt only the helper call.
- [ ] **Step 2: Run failing tests**
Run:
```powershell
uv run pytest tests\wf_transport_rpc_http\test_admin_auth_rpc.py -q
```
Expected: fail because RPC client/server mutation methods do not exist.
- [ ] **Step 3: Add RPC parameter models**
In `src/wf_transport_rpc_http/models.py`, add:
```python
class SaveAuthParams(RpcParamsModel):
auth_ref: str = Field(min_length=1)
scheme: str = Field(min_length=1)
payload: dict[str, Any] = Field(default_factory=dict)
metadata: dict[str, Any] = Field(default_factory=dict)
class DeleteAuthParams(RpcParamsModel):
auth_ref: str = Field(min_length=1)
```
If `Any` is not imported in that file, import it from `typing`.
- [ ] **Step 4: Register RPC methods**
In `src/wf_transport_rpc_http/methods_admin.py`, import the new params:
```python
from .models import AdminEmptyParams, DeleteAuthParams, InspectAuthParams, SaveAuthParams
```
Add methods after `workflow.admin.auth.inspect`:
```python
@entrypoint.method(
name="workflow.admin.auth.save",
errors=[WorkflowRpcError],
)
async def workflow_admin_auth_save(
params: SaveAuthParams = RpcParams(),
) -> dict[str, Any]:
try:
return await server.admin.save_auth_record(
auth_ref=params.auth_ref,
scheme=params.scheme,
payload=params.payload,
metadata=params.metadata,
)
except (
ValueError,
KeyError,
LookupError,
FileNotFoundError,
RuntimeError,
) as exc:
raise_workflow_rpc_error(exc)
@entrypoint.method(
name="workflow.admin.auth.delete",
errors=[WorkflowRpcError],
)
async def workflow_admin_auth_delete(
params: DeleteAuthParams = RpcParams(),
) -> dict[str, Any]:
try:
return await server.admin.delete_auth_record(params.auth_ref)
except (
ValueError,
KeyError,
LookupError,
FileNotFoundError,
RuntimeError,
) as exc:
raise_workflow_rpc_error(exc)
```
- [ ] **Step 5: Add RPC client methods**
In `src/wf_transport_rpc_http/client_admin.py`, add:
```python
async def save_auth_record(
self,
*,
auth_ref: str,
scheme: str,
payload: dict[str, Any],
metadata: dict[str, Any] | None = None,
) -> dict[str, Any]:
return await self._call(
"workflow.admin.auth.save",
{
"auth_ref": auth_ref,
"scheme": scheme,
"payload": payload,
"metadata": metadata or {},
},
)
async def delete_auth_record(self, auth_ref: str) -> dict[str, Any]:
return await self._call(
"workflow.admin.auth.delete",
{"auth_ref": auth_ref},
)
```
- [ ] **Step 6: Run tests**
Run:
```powershell
uv run pytest tests\wf_transport_rpc_http\test_admin_auth_rpc.py -q
uv run ruff check src\wf_transport_rpc_http\models.py src\wf_transport_rpc_http\methods_admin.py src\wf_transport_rpc_http\client_admin.py tests\wf_transport_rpc_http\test_admin_auth_rpc.py
uv run basedpyright --level error src\wf_transport_rpc_http\models.py src\wf_transport_rpc_http\methods_admin.py src\wf_transport_rpc_http\client_admin.py tests\wf_transport_rpc_http\test_admin_auth_rpc.py
```
Expected: pass.
---
### Task 4: CLI Auth Save/Delete
**Files:**
- Modify: `src/wf_cli/commands/auth_admin.py`
- Test: `tests/wf_cli/test_auth_admin.py`
- [ ] **Step 1: Add failing CLI tests**
Append to `tests/wf_cli/test_auth_admin.py`:
```python
def test_wf_admin_auth_save(monkeypatch) -> None:
mock_admin = MockAuthAdmin()
monkeypatch.setattr(
"wf_cli.commands.auth_admin.load_cli_context_from_typer",
lambda _ctx: SimpleNamespace(admin=mock_admin),
)
result = CliRunner().invoke(
app,
[
"admin",
"auth",
"save",
"drive.work",
"--scheme",
"bearer",
"--payload",
'{"token":"secret"}',
],
)
assert result.exit_code == 0
payload = json.loads(result.output)
assert payload["id"] == "drive.work"
assert payload["payload_keys"] == ["token"]
assert "secret" not in result.output
mock_admin.save_auth_record.assert_called_once_with(
auth_ref="drive.work",
scheme="bearer",
payload={"token": "secret"},
metadata=None,
)
def test_wf_admin_auth_save_reads_payload_file(tmp_path, monkeypatch) -> None:
mock_admin = MockAuthAdmin()
payload_file = tmp_path / "auth.json"
payload_file.write_text('{"token":"secret"}', encoding="utf-8")
monkeypatch.setattr(
"wf_cli.commands.auth_admin.load_cli_context_from_typer",
lambda _ctx: SimpleNamespace(admin=mock_admin),
)
result = CliRunner().invoke(
app,
[
"admin",
"auth",
"save",
"drive.work",
"--scheme",
"bearer",
"--payload-file",
str(payload_file),
],
)
assert result.exit_code == 0
mock_admin.save_auth_record.assert_called_once_with(
auth_ref="drive.work",
scheme="bearer",
payload={"token": "secret"},
metadata=None,
)
def test_wf_admin_auth_delete_requires_confirm(monkeypatch) -> None:
mock_admin = MockAuthAdmin()
monkeypatch.setattr(
"wf_cli.commands.auth_admin.load_cli_context_from_typer",
lambda _ctx: SimpleNamespace(admin=mock_admin),
)
result = CliRunner().invoke(app, ["admin", "auth", "delete", "drive.work"])
assert result.exit_code != 0
assert "--confirm" in result.output
mock_admin.delete_auth_record.assert_not_called()
def test_wf_admin_auth_delete(monkeypatch) -> None:
mock_admin = MockAuthAdmin()
monkeypatch.setattr(
"wf_cli.commands.auth_admin.load_cli_context_from_typer",
lambda _ctx: SimpleNamespace(admin=mock_admin),
)
result = CliRunner().invoke(
app,
["admin", "auth", "delete", "drive.work", "--confirm"],
)
assert result.exit_code == 0
assert json.loads(result.output) == {"deleted": True, "id": "drive.work"}
mock_admin.delete_auth_record.assert_called_once_with("drive.work")
```
If the test file uses a different fake than `MockAuthAdmin`, extend the existing fake with `save_auth_record = MagicMock(...)` and `delete_auth_record = MagicMock(...)`.
- [ ] **Step 2: Run failing CLI tests**
Run:
```powershell
uv run pytest tests\wf_cli\test_auth_admin.py -q
```
Expected: fail because commands are missing.
- [ ] **Step 3: Implement JSON input helper locally**
In `src/wf_cli/commands/auth_admin.py`, add imports:
```python
import json
from pathlib import Path
```
Add helpers:
```python
def _read_json_object(
inline: str | None,
file_path: str | None,
flag_names: str,
) -> dict[str, object]:
if inline and file_path:
raise typer.BadParameter(f"provide exactly one of {flag_names}")
if inline:
try:
value = json.loads(inline)
except json.JSONDecodeError as exc:
raise typer.BadParameter(f"invalid JSON: {exc}") from exc
if not isinstance(value, dict):
raise typer.BadParameter(f"{flag_names} must be a JSON object")
return dict(value)
if file_path:
try:
value = json.loads(Path(file_path).read_text(encoding="utf-8"))
except FileNotFoundError as exc:
raise typer.BadParameter(f"file not found: {file_path}") from exc
except json.JSONDecodeError as exc:
raise typer.BadParameter(f"invalid JSON in file: {exc}") from exc
if not isinstance(value, dict):
raise typer.BadParameter(f"{flag_names} must be a JSON object")
return dict(value)
raise typer.BadParameter(f"{flag_names} is required")
```
- [ ] **Step 4: Add CLI commands**
In `src/wf_cli/commands/auth_admin.py`, add:
```python
@app.command("save")
def save_auth_record(
ctx: typer.Context,
auth_ref: Annotated[str, typer.Argument(help="Auth record id/ref.")],
scheme: Annotated[str, typer.Option("--scheme", help="Auth scheme/kind.")],
payload_json: Annotated[
str | None,
typer.Option("--payload", help="Secret payload JSON object."),
] = None,
payload_file: Annotated[
str | None,
typer.Option("--payload-file", help="File containing secret payload JSON object."),
] = None,
metadata_json: Annotated[
str | None,
typer.Option("--metadata", help="Non-secret metadata JSON object."),
] = None,
metadata_file: Annotated[
str | None,
typer.Option("--metadata-file", help="File containing non-secret metadata JSON object."),
] = None,
) -> None:
"""Save or replace a local/dev auth record; response never includes payload values."""
payload = _read_json_object(payload_json, payload_file, "--payload/--payload-file")
metadata = (
_read_json_object(metadata_json, metadata_file, "--metadata/--metadata-file")
if metadata_json or metadata_file
else None
)
context = load_cli_context_from_typer(ctx)
result = run_cli_operation(
context,
context.admin.save_auth_record(
auth_ref=auth_ref,
scheme=scheme,
payload=payload,
metadata=metadata,
),
)
emit_json(result)
@app.command("delete")
def delete_auth_record(
ctx: typer.Context,
auth_ref: Annotated[str, typer.Argument(help="Auth record id/ref.")],
confirm: Annotated[
bool,
typer.Option("--confirm", help="Required to delete an auth record."),
] = False,
) -> None:
"""Delete a local/dev auth record."""
if not confirm:
raise typer.BadParameter("--confirm is required to delete an auth record")
context = load_cli_context_from_typer(ctx)
result = run_cli_operation(context, context.admin.delete_auth_record(auth_ref))
emit_json(result)
```
- [ ] **Step 5: Run CLI tests**
Run:
```powershell
uv run pytest tests\wf_cli\test_auth_admin.py -q
uv run ruff check src\wf_cli\commands\auth_admin.py tests\wf_cli\test_auth_admin.py
uv run basedpyright --level error src\wf_cli\commands\auth_admin.py tests\wf_cli\test_auth_admin.py
```
Expected: pass.
---
### Task 5: Docs and Final Verification
**Files:**
- Modify: `docs/superpowers/specs/2026-06-06-auth-source-secrets-boundary.md`
- Modify: `docs/current_roadmap.md`
- Modify: `docs/wf_cli.md`
- [ ] **Step 1: Update auth spec status**
In `docs/superpowers/specs/2026-06-06-auth-source-secrets-boundary.md`, update the `## Status` paragraph to include:
```markdown
Slice 4 adds local/dev file-backed auth save/delete through neutral admin,
JSON-RPC, and CLI. Responses still expose only ids, schemes, metadata, and
payload keys; secret payload values remain write-only. OAuth, production secret
managers, and provider-specific auth variants remain future work.
```
- [ ] **Step 2: Update roadmap**
In `docs/current_roadmap.md`, under the auth/source secrets boundary bullet, add:
```markdown
Fourth implementation slice complete: local/dev auth records can be saved and
deleted through neutral admin, JSON-RPC, and `wf admin auth`. This is still not
a production secret manager or OAuth flow; payload values are accepted only as
write inputs and never returned.
```
- [ ] **Step 3: Update CLI docs**
In `docs/wf_cli.md`, add an auth section near admin/source registry commands:
```markdown
### Local/dev auth records
Auth payload values are write-only. `list`, `inspect`, `save`, and `delete`
responses show ids, schemes, metadata, and payload keys only.
```powershell
wf admin auth save drive.work --scheme bearer --payload-file drive-auth.json
wf admin auth list
wf admin auth inspect drive.work
wf admin auth delete drive.work --confirm
```
Use source `auth_ref` values to point sources at these records. Do not commit
payload files containing real secrets.
```
```
- [ ] **Step 4: Run final focused suite**
Run:
```powershell
uv run pytest tests\wf_api\test_admin_api.py tests\wf_mcp\test_store.py tests\wf_mcp\service\test_auth_admin.py tests\wf_transport_rpc_http\test_admin_auth_rpc.py tests\wf_cli\test_auth_admin.py -q
uv run ruff check src\wf_api\admin.py src\wf_api\surface.py src\wf_mcp\storage\store.py src\wf_mcp\broker\service\auth_admin.py src\wf_transport_rpc_http\models.py src\wf_transport_rpc_http\methods_admin.py src\wf_transport_rpc_http\client_admin.py src\wf_cli\commands\auth_admin.py tests\wf_api\test_admin_api.py tests\wf_mcp\test_store.py tests\wf_mcp\service\test_auth_admin.py tests\wf_transport_rpc_http\test_admin_auth_rpc.py tests\wf_cli\test_auth_admin.py
uv run basedpyright --level error src\wf_api src\wf_mcp\storage\store.py src\wf_mcp\broker\service\auth_admin.py src\wf_transport_rpc_http src\wf_cli\commands\auth_admin.py tests\wf_api\test_admin_api.py tests\wf_mcp\test_store.py tests\wf_mcp\service\test_auth_admin.py tests\wf_transport_rpc_http\test_admin_auth_rpc.py tests\wf_cli\test_auth_admin.py
```
Expected: pass.
- [ ] **Step 5: Run broader smoke**
Run:
```powershell
uv run pytest tests\wf_mcp tests\wf_transport_rpc_http tests\wf_cli\test_auth_admin.py -q
```
Expected: pass with the existing skipped/xfail counts only.
- [ ] **Step 6: Review security invariant**
Run:
```powershell
rg -n "\"payload\"|secret|token" src\wf_api\admin.py src\wf_mcp\broker\service\auth_admin.py src\wf_transport_rpc_http\methods_admin.py src\wf_cli\commands\auth_admin.py tests\wf_api\test_admin_api.py tests\wf_mcp\service\test_auth_admin.py tests\wf_transport_rpc_http\test_admin_auth_rpc.py tests\wf_cli\test_auth_admin.py
```
Expected:
- Payload appears only as input construction or persisted store calls.
- Response assertions use `payload_keys`.
- Tests assert secret values are not present in output.
---
## Self-Review
- Spec coverage: this plan implements local/dev auth save/delete, keeps read responses secret-free, and leaves OAuth/secret managers/provider unions future.
- Placeholder scan: no TBD/TODO placeholders are present.
- Type consistency: public API method names are `save_auth_record` and `delete_auth_record` across `wf_api`, JSON-RPC client, and CLI. RPC method names are `workflow.admin.auth.save` and `workflow.admin.auth.delete`.
- Scope check: this plan does not touch legacy `wf-mcp` tools; it routes through neutral admin surfaces and JSON-RPC/CLI.