# Workflow Console Capability Playground 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 a safe, schema-generated capability playground to Discover so an operator can inspect and directly call one capability without first creating a workflow.
**Architecture:** Extend the existing authored Effect RPC boundary for `workflow.capabilities.call`, expose it through a configured browser policy, and invoke it through the console's existing write executor. Resolve self-contained local JSON Schema references before field normalization, keep direct-call state in a focused controller, and sanitize/bound protocol evidence before it reaches application state.
**Tech Stack:** TypeScript 6, React 19, Effect RPC, Hono, Valibot, Vitest, Testing Library, Vite, pnpm.
## Global Constraints
- Direct capability calls are immediate operations, not workflow runs: do not create graph, state, route, run, or trace records in the UI.
- Never call on selection, tab change, reconnect, form change, or automatic retry.
- Require explicit side-effect acknowledgement before every selected capability can be called.
- Use generated JSON Schema through `runtimeSchemasFor`; do not handwrite transport payload/result schemas.
- Support only local JSON Pointer references beginning with `#/`; reject external references, cycles, and structural `$ref` siblings that require schema intersection.
- Browser capability calls are enabled automatically only when `WEB_HOST` is loopback. Non-loopback hosts require `WEB_ENABLE_CAPABILITY_CALLS=1`.
- Bound evidence to 100 records, depth 8, 100 collection entries, 4096 characters per string, and 32 KiB serialized per request or response.
- Redact keys case-insensitively for `authorization`, `cookie`, `set-cookie`, `token`, `access_token`, `refresh_token`, `secret`, `password`, `api_key`, and `api-key`.
- Preserve the existing package boundaries: RPC transport in `web/packages/rpc`, browser policy in `web/apps/server`, and product projection/UI in `web/apps/console`.
- Do not introduce MCP Inspector, n8n, another state store, or another transport as a dependency.
- Add comments or docstrings at security boundaries and non-obvious truncation/reference-resolution logic.
## Reference Patterns, Not Dependencies
- [MCP Inspector](https://github.com/modelcontextprotocol/inspector) demonstrates the useful product loop: inspect a tool contract, generate parameter inputs, execute explicitly, and show request/result/error history. Keep our implementation inside the existing workflow API and console architecture.
- [MCP Inspector documentation](https://modelcontextprotocol.io/docs/2026-07-28/tools/inspector) treats the inspector as a development surface rather than a workflow runtime. Preserve that same distinction.
- [n8n execution-data guidance](https://github.com/n8n-io/n8n-docs/blob/main/docs/deploy/host-n8n/configure-n8n/scaling/manage-execution-data.md) warns that retained execution data grows without pruning. Apply that lesson to the console evidence ledger with deterministic bounds and redaction.
---
### Task 1: Resolve Self-Contained Local Schema References
**Files:**
- Create: `web/apps/console/src/workspace/schema-form/schema-reference.ts`
- Create: `web/apps/console/src/workspace/schema-form/schema-reference.test.ts`
- Modify: `web/apps/console/src/workspace/schema-form/schema-field.ts`
- Modify: `web/apps/console/src/workspace/schema-form/schema-field.test.ts`
- Modify: `web/apps/console/src/workspace/schema-form/SchemaForm.tsx`
- Modify: `web/apps/console/src/workspace/schema-form/SchemaForm.test.tsx`
- Modify: `web/apps/console/src/workspace/schema-form/SchemaFieldControl.tsx`
**Interfaces:**
- Produces: `resolveLocalSchemaNode(rootSchema: unknown, schemaNode: unknown): SchemaReferenceResolution`.
- Produces: `SchemaReferenceResolution = { readonly ok: true; readonly schema: unknown } | { readonly ok: false; readonly reason: string }`.
- Extends: `SchemaFormProps` with `readonly showSourceControls?: boolean`, defaulting to `true`.
- Preserves: `normalizeSchema(schema: unknown): SchemaField` and all existing authoring callers.
- [ ] **Step 1: Add failing resolver tests**
Test direct and nested `#/$defs/...` references, `#/definitions/...`, escaped JSON Pointer tokens (`~0`, `~1`), annotation siblings (`title`, `description`, `default`), unresolved pointers, external references, cycles, and structural siblings such as `properties` beside `$ref`.
```ts
expect(resolveLocalSchemaNode(root, root.properties?.ref)).toEqual({
ok: true,
schema: {
type: "object",
title: "Resource",
properties: {
logical_source: { type: "string" },
uri: { type: "string" },
},
required: ["logical_source", "uri"],
},
});
expect(resolveLocalSchemaNode(root, { $ref: "https://example.test/schema" })).toEqual({
ok: false,
reason: "External schema references are not supported.",
});
```
- [ ] **Step 2: Verify the resolver tests fail**
Run: `pnpm --dir web --filter @lda/console test -- src/workspace/schema-form/schema-reference.test.ts`
Expected: FAIL because `schema-reference.ts` does not exist.
- [ ] **Step 3: Implement the local-reference resolver**
Implement an internal recursive resolver that carries the root document and an active-reference set. Decode pointer tokens with `~1 -> /` and `~0 -> ~`; only traverse own object properties and non-negative array indices. Merge only annotation siblings `title`, `description`, and `default` over the resolved schema.
```ts
export type SchemaReferenceResolution =
| { readonly ok: true; readonly schema: unknown }
| { readonly ok: false; readonly reason: string };
export const resolveLocalSchemaNode = (
rootSchema: unknown,
schemaNode: unknown,
): SchemaReferenceResolution => resolveNode(rootSchema, schemaNode, new Set());
```
Return a stable failure reason for malformed pointers, missing targets, cycles, external references, and structural siblings. Do not mutate the input schema.
- [ ] **Step 4: Integrate resolution into field normalization**
Change internal normalization to retain `rootSchema` across recursive object and array fields. Resolve each node before reading `type`, `properties`, or `items`. On failure, return the existing JSON fallback field with the resolver's reason.
Add a realistic `wf.source.read_resource` schema test whose `ref` property points to `#/$defs/SourceResourceRef`; assert generated child fields for `logical_source`, `uri`, and defaulted `kind`.
- [ ] **Step 5: Add and test source-control suppression**
Thread `showSourceControls` from `SchemaForm` into `SchemaFieldControl`. When false, render literal inputs only and do not render path-source selectors. Keep the default true so draft authoring behavior is unchanged.
```tsx
```
- [ ] **Step 6: Run focused schema-form verification**
Run: `pnpm --dir web --filter @lda/console test -- src/workspace/schema-form/schema-reference.test.ts src/workspace/schema-form/schema-field.test.ts src/workspace/schema-form/SchemaForm.test.tsx`
Expected: PASS.
- [ ] **Step 7: Commit Task 1**
```powershell
git add web/apps/console/src/workspace/schema-form
git commit -m "feat: resolve local capability schema references"
```
---
### Task 2: Author the Capability Call RPC
**Files:**
- Modify: `web/packages/rpc/src/rpcs.ts`
- Modify: `web/packages/rpc/src/index.ts`
- Modify: `web/packages/rpc/src/method-registry.ts`
- Modify: `web/packages/rpc/src/service.ts`
- Modify: `web/packages/rpc/src/service.test.ts`
- Modify: `web/packages/rpc/src/json-schema/rpc-parity.test.ts`
- Modify: registry tests adjacent to `method-registry.ts` if present
**Interfaces:**
- Produces: `WorkflowCapabilitiesCallPayloadSchema`, `WorkflowCapabilitiesCallResultSchema`, and `WorkflowCapabilitiesCall`.
- Extends: `OperationName` with the already-generated `"workflow.capabilities.call"` operation.
- Produces interpreted camel-case result:
```ts
type CapabilityCallInterpreted = {
readonly qualifiedName: string;
readonly sourceId: string;
readonly kind: "node_spec" | "wrapper_artifact";
readonly deploymentId: string | null;
readonly outcome: string;
readonly output: Record | null;
readonly diagnostics: ReadonlyArray<{
readonly boundSource: string | null;
readonly code: string;
readonly logicalRef: string;
readonly message: string;
readonly repairHint: string | null;
readonly severity: string;
}>;
};
```
- [ ] **Step 1: Add failing RPC parity and service tests**
Pin that the authored RPC group contains `workflow.capabilities.call`, malformed payloads fail decoding before fetch, and a successful exchange dispatches the exact JSON-RPC method with `qualified_name`, `payload`, and optional `deployment_id`.
- [ ] **Step 2: Verify RPC tests fail**
Run: `pnpm --dir web --filter @lda/workflow-rpc test -- src/json-schema/rpc-parity.test.ts src/service.test.ts`
Expected: FAIL because the generated inventory contains the operation but `WorkflowRpcs` does not.
- [ ] **Step 3: Define the RPC from generated runtime schemas**
```ts
const capabilityCallSchemas = runtimeSchemasFor("workflow.capabilities.call");
export const WorkflowCapabilitiesCallPayloadSchema = capabilityCallSchemas.payload;
export const WorkflowCapabilitiesCallResultSchema = capabilityCallSchemas.success;
export const WorkflowCapabilitiesCall = Rpc.make("workflow.capabilities.call", {
payload: WorkflowCapabilitiesCallPayloadSchema,
success: WorkflowCapabilitiesCallResultSchema,
error: Schema.Never,
});
```
Add it to `WorkflowRpcs` and export it through `index.ts`.
- [ ] **Step 4: Add registry metadata and interpretation**
Register the operation with `idempotency: "write"`. Build the equivalent CLI as `uv run wf cap call --input ` and append `--deployment ` only when present. Use the existing shell-argument helper for both the JSON payload and identifiers. Decode the result through `WorkflowCapabilitiesCallResultSchema`; do not cast raw objects.
- [ ] **Step 5: Dispatch the operation in the service switch**
Decode with `WorkflowCapabilitiesCallPayloadSchema`, then call:
```ts
return yield* client.workflow["capabilities.call"](payload);
```
Keep the exhaustive `unreachableOperation` guard intact.
- [ ] **Step 6: Run RPC package verification**
Run: `pnpm --dir web --filter @lda/workflow-rpc test`
Run: `pnpm --dir web --filter @lda/workflow-rpc typecheck`
Expected: PASS.
- [ ] **Step 7: Commit Task 2**
```powershell
git add web/packages/rpc/src
git commit -m "feat: expose capability calls through workflow rpc"
```
---
### Task 3: Configure the Browser Operation Security Boundary
**Files:**
- Modify: `web/apps/server/src/browser-operation-policy.ts`
- Modify: `web/apps/server/src/browser-operation-policy.test.ts`
- Modify: `web/apps/server/src/app.ts`
- Modify: `web/apps/server/src/app.test.ts`
- Modify: `web/apps/server/src/index.ts`
- Modify: `web/apps/console/src/connection/contracts.ts`
- Modify: `web/apps/console/src/connection/contracts.test.ts`
**Interfaces:**
- Produces: `BrowserOperationDecision = "allowed" | "disabled" | "unknown"`.
- Produces: `createBrowserOperationPolicy(options: { readonly enableCapabilityCalls: boolean }): { readonly classify(operation: string): BrowserOperationDecision }`.
- Produces: `capabilityCallsEnabledForHost(hostname: string, override: string | undefined): boolean`.
- Extends: `createApp` dependencies with an injected browser operation policy.
- Adds: browser error code `operation_disabled`.
- [ ] **Step 1: Add failing policy tests**
Assert that existing operations remain allowed, unknown operations remain unknown, and `workflow.capabilities.call` is disabled or allowed according to configuration. Cover `127.0.0.1`, `localhost`, `::1`, `0.0.0.0`, LAN addresses, and explicit `WEB_ENABLE_CAPABILITY_CALLS=1`.
- [ ] **Step 2: Verify policy tests fail**
Run: `pnpm --dir web --filter @lda/web-server test -- src/browser-operation-policy.test.ts src/app.test.ts`
Expected: FAIL because policy classification is currently boolean and static.
- [ ] **Step 3: Implement policy classification**
Keep the existing authored allowlist. Treat capability call as a known conditional operation rather than adding it unconditionally.
```ts
const conditionalOperations = new Set([
"workflow.capabilities.call",
]);
```
`classify()` must return `unknown` for generated-but-unexposed operations such as admin methods.
- [ ] **Step 4: Return a distinct disabled response**
In `/api/rpc`, return HTTP 403 and:
```json
{
"ok": false,
"error": {
"code": "operation_disabled",
"message": "workflow.capabilities.call is disabled for this console server"
},
"exchange": { "request": null, "response": null }
}
```
Keep unknown operations on their existing 400 response. Add the new code to the Valibot browser contract.
- [ ] **Step 5: Wire startup configuration**
Build the policy once in `index.ts`. Loopback hosts enable direct calls by default; non-loopback hosts require the exact string `"1"`. Log a startup warning when the override enables calls on a non-loopback host. Do not claim authentication or TLS.
- [ ] **Step 6: Run server and connection-contract tests**
Run: `pnpm --dir web --filter @lda/web-server test`
Run: `pnpm --dir web --filter @lda/console test -- src/connection/contracts.test.ts`
Expected: PASS.
- [ ] **Step 7: Commit Task 3**
```powershell
git add web/apps/server/src web/apps/console/src/connection
git commit -m "feat: guard browser capability calls by host policy"
```
---
### Task 4: Bound and Redact Console Evidence
**Files:**
- Create: `web/apps/console/src/workspace/domain/evidence-policy.ts`
- Create: `web/apps/console/src/workspace/domain/evidence-policy.test.ts`
- Modify: `web/apps/console/src/app/state.ts`
- Modify: `web/apps/console/src/app/state.test.ts`
- Modify: `web/apps/console/src/workspace/domain/executor-protocol.ts`
- Modify: `web/apps/console/src/workspace/domain/read-executor.test.ts`
- Modify: `web/apps/console/src/workspace/domain/write-executor.test.ts`
- Modify: `web/apps/console/src/workspace/ConsoleWorkspace.tsx`
- Update: tests and fixtures constructing `EvidenceRecord`
**Interfaces:**
- Extends: `EvidenceRecord` with `readonly target: string`.
- Produces: `sanitizeEvidenceValue(value: unknown): unknown`.
- Produces: `sanitizeEvidenceRecord(record: EvidenceRecord): EvidenceRecord`.
- Produces: `retainEvidence(records: readonly EvidenceRecord[], record: EvidenceRecord): readonly EvidenceRecord[]`.
- [ ] **Step 1: Add failing evidence-policy tests**
Cover case-insensitive redaction, depth truncation, string truncation, array/object entry truncation, 32 KiB aggregate request/response budgets, preservation of ordinary scalar data, and retention of only the newest 100 records.
```ts
expect(sanitizeEvidenceValue({ Authorization: "Bearer secret", value: "ok" })).toEqual({
Authorization: "[redacted]",
value: "ok",
});
```
- [ ] **Step 2: Verify evidence tests fail**
Run: `pnpm --dir web --filter @lda/console test -- src/workspace/domain/evidence-policy.test.ts src/app/state.test.ts`
Expected: FAIL because evidence is currently unbounded and unsanitized.
- [ ] **Step 3: Implement the pure sanitizer**
Use a recursive projector with an explicit byte budget. Redact matching keys before traversing their values. Use stable markers such as `[redacted]`, `[truncated: depth limit]`, and `[truncated: evidence limit]`. Never mutate source objects.
Document why the aggregate budget is applied independently to request and response.
- [ ] **Step 4: Apply policy before application state**
Replace `appendEvidence` with `retainEvidence`, sanitizing the incoming record and slicing to the newest 100. This makes the reducer the single retention boundary for live console evidence.
- [ ] **Step 5: Record the target on every executor path**
Add `target: options.target` in `createConsoleExecutor` records, including invocation failure, protocol failure, mismatch, and success. Add the connected target to the health evidence constructed in `ConsoleWorkspace`.
- [ ] **Step 6: Update fixtures and run focused tests**
Run: `pnpm --dir web --filter @lda/console test -- src/workspace/domain/evidence-policy.test.ts src/workspace/domain/read-executor.test.ts src/workspace/domain/write-executor.test.ts src/app/state.test.ts`
Expected: PASS.
- [ ] **Step 7: Commit Task 4**
```powershell
git add web/apps/console/src/app web/apps/console/src/workspace web/apps/console/src/components web/apps/console/src/presentation
git commit -m "feat: bound and redact console operation evidence"
```
---
### Task 5: Add the Capability Call Domain Boundary
**Files:**
- Modify: `web/apps/console/src/workspace/domain/capability-models.ts`
- Modify: `web/apps/console/src/workspace/domain/capability-models.test.ts`
- Create: `web/apps/console/src/workspace/domain/capability-call-client.ts`
- Create: `web/apps/console/src/workspace/domain/capability-call-client.test.ts`
- Create: `web/apps/console/src/workspace/routes/useCapabilityPlayground.ts`
- Create: `web/apps/console/src/workspace/routes/useCapabilityPlayground.test.tsx`
**Interfaces:**
- Produces: `CapabilityCallResult` and `decodeCapabilityCallResult(value: unknown): CapabilityCallResult`.
- Produces: `callCapability(executor, request): Promise`.
- Produces controller fields `phase`, `result`, `message`, `acknowledged`, `deploymentId`, `setAcknowledged`, `setDeploymentId`, `call`, and `reset`.
```ts
export type CapabilityCallRequest = {
readonly qualifiedName: string;
readonly payload: Record;
readonly deploymentId?: string;
};
```
- [ ] **Step 1: Add failing decoder/client tests**
Pin successful output, `runtime_error` as a decoded completed result, dependency diagnostics, malformed results, snake-case request payload, and omission of blank deployment IDs.
- [ ] **Step 2: Verify domain tests fail**
Run: `pnpm --dir web --filter @lda/console test -- src/workspace/domain/capability-models.test.ts src/workspace/domain/capability-call-client.test.ts`
Expected: FAIL because the call model/client do not exist.
- [ ] **Step 3: Implement the Valibot decoder and client**
Reuse the existing `decode()` helper. Model the exact interpreted camel-case shape emitted by Task 2. The client must use `ConsoleExecutor.run("workflow.capabilities.call", ...)`; it must not call `fetch` or `callOperation` directly.
- [ ] **Step 4: Add failing controller tests**
Cover disconnected, idle, calling, result, and error phases; acknowledgement and deployment reset when capability or target changes; double-submit suppression; and stale completion suppression after selection changes.
- [ ] **Step 5: Implement the controller**
Use request identity or an incrementing generation ref so an earlier promise cannot overwrite a newer selection. Keep `runtime_error` in `phase: "result"`; only transport/protocol/decode failures use `phase: "error"`.
- [ ] **Step 6: Run focused domain/controller tests**
Run: `pnpm --dir web --filter @lda/console test -- src/workspace/domain/capability-models.test.ts src/workspace/domain/capability-call-client.test.ts src/workspace/routes/useCapabilityPlayground.test.tsx`
Expected: PASS.
- [ ] **Step 7: Commit Task 5**
```powershell
git add web/apps/console/src/workspace/domain web/apps/console/src/workspace/routes/useCapabilityPlayground.ts web/apps/console/src/workspace/routes/useCapabilityPlayground.test.tsx
git commit -m "feat: add capability playground controller"
```
---
### Task 6: Build the Discover-Route Playground
**Files:**
- Create: `web/apps/console/src/workspace/routes/CapabilityPlayground.tsx`
- Create: `web/apps/console/src/workspace/routes/CapabilityPlayground.test.tsx`
- Modify: `web/apps/console/src/workspace/routes/DiscoverRoute.tsx`
- Modify: `web/apps/console/src/workspace/routes/DiscoverRoute.test.tsx`
- Modify: `web/apps/console/src/styles/workspace.css`
- Move: `web/apps/console/src/workspace/authoring/format-bounded-json.ts` to `web/apps/console/src/workspace/domain/format-bounded-json.ts`
- Move: `web/apps/console/src/workspace/authoring/format-bounded-json.test.ts` to `web/apps/console/src/workspace/domain/format-bounded-json.test.ts`
- Modify: imports of `formatBoundedJson` in workspace authoring/routes
**Interfaces:**
- Consumes: Tasks 1, 4, and 5.
- Produces: `CapabilityPlayground({ capability, target, executor }: CapabilityPlaygroundProps)`.
- Preserves: existing capability search, inspect, pagination, and Add to draft behavior.
- [ ] **Step 1: Move the bounded JSON helper without behavior changes**
Move the generic formatter to `workspace/domain`, update all imports, and run its existing test before adding UI behavior.
Run: `pnpm --dir web --filter @lda/console test -- src/workspace/domain/format-bounded-json.test.ts`
Expected: PASS.
- [ ] **Step 2: Add failing playground component tests**
Test Contract/Try capability views, generated literal-only form, immediate-execution warning, acknowledgement gate, wrapper-only deployment input, in-flight state, successful outcome/output/diagnostics, `runtime_error`, and rejected operation alert.
Assert that changing capability resets acknowledgement and result. Assert no call occurs on render, tab selection, or form change.
- [ ] **Step 3: Verify UI tests fail**
Run: `pnpm --dir web --filter @lda/console test -- src/workspace/routes/CapabilityPlayground.test.tsx src/workspace/routes/DiscoverRoute.test.tsx`
Expected: FAIL because the component is absent.
- [ ] **Step 4: Implement the detail tabs and call form**
Keep the capability list unchanged. Replace the detail body's always-expanded raw schemas with tabs:
- **Contract:** current facts, input/output schemas, wrapper hints, Add to draft.
- **Try capability:** `SchemaForm` with `showSourceControls={false}`, acknowledgement checkbox, optional wrapper deployment ID, and `Call capability` button.
The submit handler must reject serialization issues and non-object root values locally. It passes only literal `result.value` to the controller.
- [ ] **Step 5: Implement the result receipt**
Show capability name, outcome, duration/provenance supplied by evidence where available, diagnostics as a readable list, and output through `formatBoundedJson`. Label `runtime_error` as a completed capability outcome and explain that no workflow run or trace was created.
Use existing workspace visual language and Lucide icons. Do not clone MCP Inspector or n8n styling.
- [ ] **Step 6: Integrate with DiscoverRoute**
Obtain `writeExecutor` and `connectedTarget` from the workspace context, create the controller for the selected capability, and preserve `CreateDraftDialog`. Ensure the conditional operation-disabled response appears inline instead of disconnecting the console.
- [ ] **Step 7: Run focused UI tests and React Doctor**
Run: `pnpm --dir web --filter @lda/console test -- src/workspace/routes/CapabilityPlayground.test.tsx src/workspace/routes/DiscoverRoute.test.tsx`
Run: `pnpm --dir web --filter @lda/console exec react-doctor --verbose`
Expected: tests PASS; no new React Doctor errors.
- [ ] **Step 8: Commit Task 6**
```powershell
git add web/apps/console/src/workspace web/apps/console/src/styles/workspace.css
git commit -m "feat: add capability playground to discover"
```
---
### Task 7: Document, Smoke-Test, and Close the Slice
**Files:**
- Modify: `web/README.md`
- Modify: `docs/current_roadmap.md`
- Modify: `docs/project_map.md` if it enumerates console workspace modules
- Archived at: `docs/historical/superpowers/plans/2026-08-11-workflow-console-capability-playground.md`
**Interfaces:**
- Documents: loopback defaults, non-loopback opt-in, side-effect warning, direct-call semantics, evidence limits, and known platform-context limitation.
- Verifies: the complete vertical slice against a running workflow RPC server.
- [x] **Step 1: Add user-facing runbook documentation**
Document:
```powershell
uv run wf-rpc-server --config wf.config.json --host 127.0.0.1 --port 8765
pnpm --dir web dev
```
Explain that `wf.std.concat` is a suitable direct-call smoke test. Explain that `wf.source.read_resource` should render a proper form after local `$ref` support, but a node-spec direct call can still return `runtime_error` when it requires workflow platform context. State that this is truthful behavior, not a failed form implementation.
Document `WEB_ENABLE_CAPABILITY_CALLS=1` as an explicit non-loopback risk acceptance, not authentication.
- [x] **Step 2: Run scoped package verification**
Run: `pnpm --dir web --filter @lda/workflow-rpc test`
Run: `pnpm --dir web --filter @lda/web-server test`
Run: `pnpm --dir web --filter @lda/console test`
Expected: PASS.
- [x] **Step 3: Run full web verification**
Run: `pnpm --dir web test`
Run: `pnpm --dir web typecheck`
Run: `pnpm --dir web build`
Run: `git diff --check`
Expected: all tests and typechecks pass; build succeeds except any already-documented chunk-size warning; no whitespace errors.
- [x] **Step 4: Perform browser smoke verification**
At `/console/discover`:
1. Connect to `http://127.0.0.1:8765/rpc`.
2. Select `wf.std.concat`; confirm Contract shows schemas and Try capability shows generated fields.
3. Confirm no request occurs before acknowledgement and submit.
4. Submit `items: ["hello", "world"]`, `separator: " "`; verify output and operation evidence.
5. Select `wf.source.read_resource`; verify `logical_source`, `uri`, and defaulted `kind` fields render instead of unresolved `$ref` JSON.
6. Call it without platform context and verify a completed `runtime_error` receipt rather than a transport alert.
7. Verify the evidence ledger shows the target and redacts a synthetic sensitive key in a test fixture; do not send real credentials.
8. Run the web server with a non-loopback host and no override; verify the Try surface receives `operation_disabled` without exposing the call.
- [x] **Step 5: Record review status**
The controller owns the formal per-task and final reviews. Per the Task 7
brief, no review agent was spawned; self-review found no Critical or Important
findings and the narrowly necessary evidence-target display correction was
covered by a focused test.
- [x] **Step 6: Update roadmap and archive the completed plan**
Mark Workflow Console Slice 4 completed in `docs/current_roadmap.md` and link this historical plan after verification succeeds.
- [x] **Step 7: Commit Task 7**
```powershell
git add web/README.md docs/current_roadmap.md docs/project_map.md docs/superpowers docs/historical
git commit -m "docs: complete capability playground slice"
```
## Plan Self-Review
- **Spec coverage:** Tasks 1–7 cover local `$ref`, authored RPC parity, browser policy, evidence safety, decoding/controller state, Discover UI, documentation, live smoke, and completion gates.
- **Placeholder scan:** No `TBD`, `TODO`, unspecified error handling, or unnamed test steps remain.
- **Type consistency:** `CapabilityCallRequest`, `CapabilityCallResult`, `SchemaReferenceResolution`, `BrowserOperationDecision`, and evidence-policy function names are defined before downstream use.
- **Scope control:** Composite mixed array/object source bindings remain Workflow Console Slice 5; this plan does not fake `items.0` support or alter Python workflow semantics.