29 KiB
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-mcpscript. - Do not expose auth payload values in list, inspect, save, or delete responses.
- Do not support inline auth records in
wf_configor 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.AuthRecordas 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(...).
- Add mutation methods to
- Modify
src/wf_api/surface.py- Add mutation methods to
WorkflowAdminSurface.
- Add mutation methods to
- Modify
src/wf_mcp/storage/store.py- Add
delete_auth(...)anddelete_auth_record(...)toStore/FileStore.
- Add
- Modify
src/wf_mcp/broker/service/auth_admin.py- Implement save/delete using
Store.save_auth_recordandStore.delete_auth_record.
- Implement save/delete using
- Modify
src/wf_transport_rpc_http/models.py- Add
SaveAuthParamsandDeleteAuthParams.
- Add
- Modify
src/wf_transport_rpc_http/methods_admin.py- Register
workflow.admin.auth.saveandworkflow.admin.auth.delete.
- Register
- Modify
src/wf_transport_rpc_http/client_admin.py- Add
save_auth_record(...)anddelete_auth_record(...).
- Add
- Modify
src/wf_cli/commands/auth_admin.py- Add
wf admin auth save. - Add
wf admin auth delete --confirm.
- Add
- Modify docs:
docs/superpowers/specs/2026-06-06-auth-source-secrets-boundary.mddocs/current_roadmap.mddocs/wf_cli.md
- Add/update tests:
tests/wf_api/test_admin_api.pytests/wf_mcp/service/test_auth_admin.pytests/wf_mcp/test_auth.pyortests/wf_mcp/test_store.pytests/wf_transport_rpc_http/test_admin_auth_rpc.pytests/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.
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:
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:
from wf_api.auth import AuthRecord
Extend WorkflowAdminAuthProvider:
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:
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:
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:
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:
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:
from wf_api.auth import AuthRecord as NeutralAuthRecord
- Step 2: Add failing provider tests
Append to tests/wf_mcp/service/test_auth_admin.py:
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:
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:
def delete_auth(self, connection_id: str) -> bool:
raise NotImplementedError
def delete_auth_record(self, auth_ref: str) -> bool:
raise NotImplementedError
In FileStore, add:
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:
from wf_api.auth import AuthRecord as NeutralAuthRecord
Add methods to McpAuthAdminProvider:
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:
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:
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:
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:
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:
from .models import AdminEmptyParams, DeleteAuthParams, InspectAuthParams, SaveAuthParams
Add methods after workflow.admin.auth.inspect:
@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:
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:
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:
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:
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:
import json
from pathlib import Path
Add helpers:
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:
@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:
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:
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:
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:
### 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:
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:
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:
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_recordanddelete_auth_recordacrosswf_api, JSON-RPC client, and CLI. RPC method names areworkflow.admin.auth.saveandworkflow.admin.auth.delete. - Scope check: this plan does not touch legacy
wf-mcptools; it routes through neutral admin surfaces and JSON-RPC/CLI.