Skip to content

feat(workbench): approval-gated assistant writes + framed bridge client (0394 phase 2) - #656

Merged
crs48 merged 4 commits into
mainfrom
claude/0394-phase2-approvals
Jul 29, 2026
Merged

feat(workbench): approval-gated assistant writes + framed bridge client (0394 phase 2)#656
crs48 merged 4 commits into
mainfrom
claude/0394-phase2-approvals

Conversation

@crs48

@crs48 crs48 commented Jul 29, 2026

Copy link
Copy Markdown
Owner

Exploration 0394's deliberately-deferred phase 2, built with the design pass it asked for — and the unblocking of 0406's last implementation item.

The in-chat approval ceremony (ai-chat-ceremony.ts): write tools ride the existing risk-tiered AgentAuditRecorder (0337). Reads bypass (no audit-node flood from searches); low-risk writes execute with an audit trail; medium parks on the one-time code — APPROVE <code> typed in chat rides the real nonce machinery (SHA-256 match, TTL, single-use); high/critical refuse the chat path structurally (no nonce exists) and need the deliberate in-app approval, stamped with the operator's DID, with the change reviewed before the button enables. Denial and expiry resolve the model's tool call honestly — the turn never hangs. 9 ceremony tests + 5 gating tests.

Doubly-gated write tools (ai-chat-write-tools.ts): the tier must be reliable (writeModeFor = agentic) AND the operator must flip the new "Allow edits" toggle (default off). The badge escalates honestly: reads workspace → searches workspace → edits with approval.

The framed bridge client (ai-bridge-frames.ts): the panel's bridge tier now speaks /v1/agent/stream — the endpoint #623 shipped and nothing consumed. Tool activity, per-turn cost, and permission asks arrive structured instead of flattened away.

Desktop MCP ceremony: agent-mcp-server.ts now passes agentAudit, closing a real hole — a bridged agent could previously run xnet_apply_page_markdown with no gate but the system prompt.

Verified live on the desktop: with writes armed, a chat turn through the bridged Claude Code created a real Page node in the desktop SQLite store (Ceremony Test 0394), and the panel showed "Last turn · $0.1581 · 495 output tokens" — cost frames only exist on the framed wire, so that line is proof of the new transport. With writes off, the agent still refuses (verified in the same session).

Doc movement: 0394 → 9/10 impl, 3/7 val. 0406 → 23/23 impl, 9/10 val (only the cold-open item remains, in flight separately).

🤖 Generated with Claude Code

xNet Test added 3 commits July 28, 2026 21:01
… phase 2)

Signed-off-by: xNet Test <test@xnet.dev>
Signed-off-by: xNet Test <test@xnet.dev>
@crs48
crs48 temporarily deployed to pr-656 July 29, 2026 04:23 — with GitHub Actions Inactive
github-actions Bot added a commit that referenced this pull request Jul 29, 2026
@github-actions

github-actions Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Preview removed for PR #656.

github-actions Bot added a commit that referenced this pull request Jul 29, 2026
@crs48
crs48 temporarily deployed to pr-656 July 29, 2026 04:33 — with GitHub Actions Inactive
github-actions Bot added a commit that referenced this pull request Jul 29, 2026
github-actions Bot added a commit that referenced this pull request Jul 29, 2026
@crs48
crs48 merged commit 81134ce into main Jul 29, 2026
19 checks passed
@crs48
crs48 deleted the claude/0394-phase2-approvals branch July 29, 2026 04:42
github-actions Bot added a commit that referenced this pull request Jul 29, 2026
crs48 added a commit that referenced this pull request Jul 29, 2026
Every box in exploration 0406 is checked — 23/23 implementation, 10/10
validation — so the filename flips to `[x]`.

The arc, for the record: #641/#642/#644 (port + core extraction), #648
(AI panel on desktop), #650/#651 (chrome behind injection points, first
Electron mount), #652 (desktop surfaces in the shell), #653 (flag
removed, bespoke shell deleted), #655 (cold-open back under the 0.52s
baseline), #656 (approval-gated writes — the last blocked item). The
desktop and web apps now render one shell from one module, verified live
at every step.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
crs48 pushed a commit that referenced this pull request Jul 30, 2026
…shim check

The visual-capture `capture` job has failed its Build Storybook step on
every PR since #656: `views/ai-chat-write-tools.ts` imports `writeModeFor`
from `@xnetjs/plugins`, and the storybook browser shim never got a matching
export.

`pnpm check:storybook-shim` stayed green through all of it. Its walker only
knew the aliases in `.storybook/workspace-aliases.ts`, and `@xnetjs/workbench`
has no entry there, so it dropped the specifier and never reached the
importing module — while rollup resolved it through the workspace link and
failed. A gate that cannot fail on a real breakage teaches everyone to ignore
red.

The walker now resolves `@xnetjs/*` the way the build does: aliases first,
then the package's own `exports`/`main`, mapping `dist` entries back to the
source they are built from. A specifier it cannot resolve is a hard failure
rather than a silent drop, so the graph cannot quietly shrink again. It also
seeds the preview and manager entries, not just stories.

Coverage goes from 873 to 1177 modules, which turned up a second real break
(`AgentAuditRecorder`, also a value import) plus ten type-only names the shim
should mirror.

Verified with `pnpm build:stories`, the same command the capture job runs.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: xNet Test <test@xnet.dev>
crs48 added a commit that referenced this pull request Jul 30, 2026
…shim check (#661)

## What broke

The `capture` job in `.github/workflows/visual-capture.yml` has failed
its **Build Storybook** step on every PR since #656. Pre-existing on
`main` (verified at `bcec607b1`), unrelated to the PR that surfaced it
(#658).

```
"writeModeFor" is not exported by ".storybook/shims/xnet-plugins-browser.ts",
  imported by "packages/workbench/src/views/ai-chat-write-tools.ts".
```

`.storybook/workspace-aliases.ts:29` aliases `@xnetjs/plugins` to the
browser shim, and 0394 phase-2 (#656) added a `writeModeFor` import the
shim never got a matching export for.

## Why the gate didn't catch it

`pnpm check:storybook-shim` stayed green the whole time — "873 modules
walked, 114 shim exports".

Its walker only knew the aliases parsed out of `workspace-aliases.ts`.
**`@xnetjs/workbench` has no entry there**, so the walker silently
dropped the specifier and never reached the importing module — while
rollup resolved it through the workspace link and kept bundling.
Everything past that point went unchecked.

Per AGENTS.md: *"A gate that cannot go green teaches everyone to ignore
red."* The inverse is worse — a gate that cannot go **red** on a live
breakage.

## What changed

**`.storybook/shims/xnet-plugins-browser.ts`** — adds `writeModeFor` /
`WriteMode`, matching `packages/plugins/src/ai/connectors/types.ts:101`,
plus the names the widened walk turned up.

**`scripts/check-storybook-shim.mjs`** — resolves `@xnetjs/*` the way
the build does rather than only via aliases:

- aliases first (Vite applies them first), then the package's own
`exports`/`main`;
- `dist` entries map back to the source they're built from, so the walk
doesn't stop at an artifact that may not be present;
- an `@xnetjs/*` specifier it *cannot* resolve is now a **hard
failure**, not a silent drop — the graph can't quietly shrink again;
- seeds `.storybook/preview.tsx` and `manager.tsx`, which the build
compiles too.

Coverage: **873 → 1177 modules walked**. That turned up a **second real
break** — `AgentAuditRecorder` in `views/ai-chat-ceremony.ts:32`, also a
value import — plus ten type-only names the shim should mirror anyway.

### On "walk the full module graph instead"

Rejected as framed. The story graph *was* nearly the whole build graph;
the defect was that the **resolver was weaker than Vite's**, not that
the seed set was too small. Matching resolution (plus the two extra
entry seeds) closes the class without needing to run rollup to find out.

## Verification

| Check | Result |
| --- | --- |
| `pnpm build:stories` (the capture job's exact command, after `turbo
run build --filter='./packages/*'`) | ✅ `Storybook build completed
successfully` |
| `pnpm check:storybook-shim` | ✅ 1177 modules walked, 128 shim exports
|
| Regression proof — remove `writeModeFor` from the shim again | ✅ now
**fails** with `· writeModeFor (…/ai-chat-write-tools.ts)`; the old
walker passed |
| Pre-push full suite | ✅ 11331 passed, 4 skipped |

Labelled `skip-changelog`: dev tooling, nothing a user notices.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant