From 6d6abfb857652349acb4ea214b5eb9559f5b35c8 Mon Sep 17 00:00:00 2001 From: Thor Whalen <1906276+thorwhalen@users.noreply.github.com> Date: Mon, 6 Jul 2026 09:48:31 +0200 Subject: [PATCH 1/2] docs(assistant): complete + harden the confirmation gate (HITL) pattern MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The last "operate my app" piece. Design settled with the user: middleware + convention, NOT CommandRecord fields (getRisk(id) is an external map, so the closed record stays closed). Like macros/recordSequence, the ~40-line gate ships as a PATTERN, not a package (hard-don't #2 — no god-package-of-one, no natural package home). Rewrote docs/hand-written-assistant-runtime.md Piece 1 from a sketch to a complete, SECURE implementation: - createApprovalStore — one-use tokens bound to the exact {command, params}. - confirmationGate — a dispatch wrapper returning a confirmation_required errors-as-data proposal for risky assistant calls. - Security fix that motivated the hardening: the token is minted by the RUNTIME after a human approves and NEVER returned to the model. The prior sketch's `!ctx?.approvedToken` truthiness check (any token passes) plus a hinted token-in-proposal would let the model self-approve by lifting the token from the tool result. The proposal now carries only {command, params, preview}. acture-ai-assistant skill §2 updated (trust boundary, one-use call-bound tokens, convention-not-fields / pattern-not-package). Roadmap v1.18 entry. Docs/skill only — no package, no changeset, no version bump. Claude-Session: https://claude.ai/code/session_016teVFousRsWNhiiPNpG1p9 --- .claude/skills/acture-ai-assistant/SKILL.md | 9 ++++++++- docs/hand-written-assistant-runtime.md | Bin 11785 -> 14243 bytes docs/roadmap.md | 15 ++++++++++++++- 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/.claude/skills/acture-ai-assistant/SKILL.md b/.claude/skills/acture-ai-assistant/SKILL.md index 0c83c32..31f3b90 100644 --- a/.claude/skills/acture-ai-assistant/SKILL.md +++ b/.claude/skills/acture-ai-assistant/SKILL.md @@ -32,7 +32,14 @@ No surveyed framework ships a first-class typed channel for exposing an app's *c ### 2. Human-in-the-loop confirmation — at the dispatch boundary -The industry converged on one beat: model proposes → runtime pauses with serializable state → UI renders the proposal → user approves/denies/edits → resume (research-11 §6). For a command-dispatch app the gate belongs at the **dispatch boundary, driven by declarative command metadata** — *the model proposes; the registry disposes*. A `requiresConfirmation`/`sideEffect: 'destructive'` command dispatched from the assistant channel without an approval token does not execute — it returns a **proposal as errors-as-data** (`code: 'confirmation_required'`, `{command, params, preview}`) which the chat UI renders as an approve/deny/edit card; on approve, re-dispatch with a **one-use token bound to the exact `{command, params}` hash**. Reproducible core: `docs/hand-written-assistant-runtime.md` Piece 1. This keeps confirmation caller-independent, declarative, and schema-validated regardless of surface — and `preview` can call a *view* to show *what will change*. +The industry converged on one beat: model proposes → runtime pauses with serializable state → UI renders the proposal → user approves/denies/edits → resume (research-11 §6). For a command-dispatch app the gate belongs at the **dispatch boundary, driven by a declarative risk convention** — *the model proposes; the registry disposes*. A `requiresConfirmation`/`sideEffect: 'destructive'` command dispatched from the assistant channel without a valid token does not execute — it returns a **proposal as errors-as-data** (`code: 'confirmation_required'`, `{command, params, preview}`) which the chat UI renders as an approve/deny/edit card. **The complete, secure pattern is `docs/hand-written-assistant-runtime.md` Piece 1** — build it from there. + +Two load-bearing points that pattern gets right and a naive gate gets wrong: + +- **The token is minted by the RUNTIME after a human approves, and never returned to the model.** If the gate put a usable token in the `confirmation_required` proposal, the model — which reads that tool result — would lift it and **self-approve**, defeating HITL. The proposal carries only `{command, params, preview}`; the runtime mints the one-use token out-of-band on human approval and re-dispatches. +- **Tokens are one-use and bound to the exact `{command, params}`** (a `createApprovalStore` with `approve`/`consume`), so an approval can't be replayed against different args. + +**Design settled: middleware + convention, not `CommandRecord` fields.** `getRisk(id)` is an external map, so the closed record is untouched (the fields alternative opens the guarded surface — needs the named-need test). And like macros, the ~40-line gate ships as a **pattern**, not a package (hard-don't #2; no natural package home). This keeps confirmation caller-independent, declarative, and schema-validated regardless of surface — and `preview` can call a *view* to show *what will change*. ### 3. The dispatch chain is a macro — undo / replay / test fixtures diff --git a/docs/hand-written-assistant-runtime.md b/docs/hand-written-assistant-runtime.md index 4abd7b8c476ddef5576d014c0adf91b0ba974d6b..eb446571868c2ca2dd9c5d3587bd041ffa0699e3 100644 GIT binary patch delta 3564 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 diff --git a/docs/roadmap.md b/docs/roadmap.md index 2004805..802d3a4 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -4,7 +4,7 @@ The live forward-planning surface. `docs/v1_plan.md` and `docs/implementation_pl **How work proceeds:** phases are over. Work is small, tracked increments. Each picks one or two items from "Next" or "Deferred", ships them, updates this file, and replaces `docs/next_session.md` with the following handoff. -Last updated: **2026-07-06** (v1.17 — acture-hotkeys end-user keymap customization, the second v1.14-deferred accelerator). +Last updated: **2026-07-06** (v1.18 — confirmation gate (HITL) shipped as a pattern; the "operate my app" story is now complete). --- @@ -160,6 +160,19 @@ The second v1.14-deferred accelerator. Lets a **user** (not just the developer) --- +### v1.18 — confirmation gate (HITL for destructive AI dispatch) — complete (this increment) + +The last "operate my app" piece: a human-in-the-loop gate for destructive/irreversible AI dispatch (research-11 §6). **Design settled with the user: middleware + convention, NOT `CommandRecord` fields** — `getRisk(id)` is an external map, so the closed record stays closed (the fields alternative was declined to avoid opening the guarded surface). And like macros / `recordSequence`, the ~40-line gate ships as a **pattern**, not a package (hard-don't #2 — no god-package-of-one, no natural existing package home); the delivery surface is the reference doc + skill. + +- **`docs/hand-written-assistant-runtime.md` Piece 1** rewritten from a sketch to a complete, **secure** implementation: `createApprovalStore` (one-use tokens bound to the exact `{command, params}`), `confirmationGate` (a dispatch wrapper that returns a `confirmation_required` errors-as-data proposal for risky assistant calls), and the runtime re-dispatch flow. +- **The security fix that motivated hardening it:** the token is minted by the **runtime after a human approves, and never returned to the model** — the prior sketch's `!ctx?.approvedToken` truthiness check (any token passes) plus a hinted token-in-proposal would let the model **self-approve** by lifting the token from the tool result. The proposal now carries only `{command, params, preview}`; tokens are one-use and call-bound. +- **`acture-ai-assistant` skill §2** updated: the trust-boundary rule (token minted post-human-approval, never to the model), the one-use call-bound tokens, and the settled convention-not-fields / pattern-not-package decision. +- Docs/skill only — no package, no changeset, no version bump (pattern delivery). + +**With this, the "operate my app" story is complete:** write side (tools/MCP) + read side (resources + getState) + HITL confirmation + macro capture + the runtime bridge — all shipped, as packages where a package earned it and as patterns/skills elsewhere. + +--- + ## Next **The autonomous v1.12 + v1.13 chain is complete.** The remaining post-v1 items need user direction; the rewritten `docs/next_session.md` surfaces them. The candidates are: **`acture-state-jotai`** (atom-tree ↔ flat-state bridge is non-trivial per research-3; the adapter may not implement `PatchCapableAdapter` cleanly), **`acture-state-valtio`** (proxy-to-patch translation is non-trivial). (**`acture-sandbox`** is no longer a candidate — its isolation-only seam shipped in the extension-system increment; see below.) Pull-forward decisions are the user's; surface options with honest trade-offs when scheduled. From 8831c0927a7ce30cc5c3f3795bea19567817324b Mon Sep 17 00:00:00 2001 From: Thor Whalen <1906276+thorwhalen@users.noreply.github.com> Date: Mon, 6 Jul 2026 09:50:10 +0200 Subject: [PATCH 2/2] =?UTF-8?q?docs:=20refresh=20next=5Fsession=20handoff?= =?UTF-8?q?=20for=20the=20completed=20v1.14=E2=80=93v1.18=20chain?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Claude-Session: https://claude.ai/code/session_016teVFousRsWNhiiPNpG1p9 --- docs/next_session.md | 145 +++++++++++++++++++++---------------------- 1 file changed, 70 insertions(+), 75 deletions(-) diff --git a/docs/next_session.md b/docs/next_session.md index 0df27b1..2ca3b78 100644 --- a/docs/next_session.md +++ b/docs/next_session.md @@ -1,78 +1,73 @@ -# Next Session — pick the next post-v1 increment (user-steered) - -**Your role this session:** *one* of two things. - -1. **If the user says "ship X":** read the X-specific brief below and run a single-increment session (Step 1 design decision → build → test → changeset → roadmap update → reflection → commit → push → merge Version PR → verify publish → pull bump). Mirror the v1.12 + v1.13 workflow exactly. -2. **If the user hasn't picked yet:** surface the three remaining post-v1 options below via `AskUserQuestion`. **Do not pick autonomously.** v1.12 + v1.13 were a pre-committed chain; everything past v1.13 is a fresh decision the user owns. - -The autonomous-chain mode from the previous handoff is **off** by default. If the user says "do v1.14 + v1.15 autonomously" or similar, re-enter that mode with the same "truly stuck" criteria the v1.12 handoff defined. - -## Outcomes of the previous chain - -- **v1.12 — `acture-test-property@1.1.0`** — fast-check arbitraries over the command registry; random `CommandSequence`s replayed via `acture-e2e-playwright`'s `replaySequence`; invariants asserted end-of-sequence. Counter-examples carry the shrunk sequence on `PropertyTestFailure.sequence` (replayable verbatim). +29 tests. In-package Zod→arbitrary mapper covering the JSON-Schema-representable subset (the spec-listed `@fast-check/zod` package does not exist on npm). Consumer skill: `acture-test-property`. Reference: `docs/hand-written-test-property.md`. Full write-up: `docs/v1_12-reflection.md`. -- **v1.13 — `acture` on PyPI** — Python companion graduated from name-reservation placeholder to a real, thin MCP-client facade. `ActureClient(Mapping[str, Command])`, `Command`, `ActureError`, `stdio_transport` / `http_transport`. ~300 LoC. One dependency (`mcp >= 1.10`). +23 Python tests via the SDK's in-memory transport. Cross-language semver = lockstep (existing `sync-python-version.mjs`), deliberately not decoupled in this increment. Consumer skill: `acture-python`. Reference: `docs/hand-written-python-client.md`. Full write-up: `docs/v1_13-reflection.md`. -- **Status now:** 19 npm packages + 1 PyPI package; **489 npm package tests + 41 example tests + 23 Python tests** green. 26 skills, 7 reproducibility / recipe docs. Pending changesets: none — chain end is fully published. - -## The three remaining post-v1 candidates - -Pick one (or none). Each has honest trade-offs — they are NOT equally cheap, and none of them is the obvious next move. - -### Option A — `acture-state-jotai` (additional `StateAdapter` reference) - -Adds a third reference state adapter. Jotai is **atom-tree**, not flat-state — every piece of state is its own primitive atom. The `StateAdapter` interface assumes flat state (`getState(): S`, `setState((s) => …)`). - -- **Effort:** the atom-tree-to-flat bridge has to either (a) flatten the atom tree into a single derived atom (loses Jotai's atomic-update benefit), or (b) introduce an "atom selector function" the adapter user supplies. Research-3 §3 flagged this; the implementation friction is real. -- **`PatchCapableAdapter`:** **may not implement cleanly.** Jotai mutations don't naturally produce Immer-style patches; the adapter would need to wrap every `set` and synthesize a patch from the before/after diff — expensive at large state. -- **Trade-off vs. alternatives:** Jotai is genuinely popular; users who already chose Jotai will want it. But the adapter would either compromise Jotai's strengths (option a) or push complexity onto the user (option b). -- **Recommended user-side decision:** "do we have a real Jotai consumer asking for this?" If no, defer. - -### Option B — `acture-state-valtio` (additional `StateAdapter` reference) - -Same shape as A, different substrate. Valtio is **proxy-based** — mutations look like direct property assignment, and the proxy synthesizes update notifications. - -- **Effort:** proxy-to-patch translation is non-trivial. Valtio's `subscribe` fires after the mutation has already happened; to emit Immer-style patches the adapter would have to do a structural diff against a captured pre-state snapshot, like the Jotai option (b) above. -- **`PatchCapableAdapter`:** same constraint as A — synthesizable but expensive at large state. -- **Trade-off vs. alternatives:** Valtio is less popular than Jotai but has a dedicated user base. The proxy approach is incompatible with the JSON-serializable state constraint (`acture-greenfield-state-model`'s four hard constraints) only at the structural-clone boundary; not a blocker, but a thing to document. -- **Recommended user-side decision:** "do we have a real Valtio consumer?" If no, defer. - -### Option C — `acture-sandbox` ✅ shipped (the extension-system increment) - -**Done.** The gating design is filed as research-9 (`docs/research/acture_research_9 -- Extensions and Plugin Systems.md`, the §7 brief); the maintainer overrode its honest no-named-user NO-GO to build the **isolation-only seam**. Shipped: the `acture-extensions` skill, `docs/hand-written-sandbox.md` (the ~15-line core-only host/loader pattern), and the `acture-sandbox` package — which ships *only* the `ExtensionRunner` port + an in-process transport. - -- **What's settled:** the trust-tier model (research-9 Axis A), effects-as-data, the host/loader as a pattern (the `CommandRecord` IS the manifest), and isolation as the one rung that earns a package. -- **What stays deferred** (the genuine untrusted-author work): the real isolating transports (Worker / iframe / QuickJS-WASM / `isolated-vm`), CPU/memory quotas, and the capability/manifest/entitlement/marketplace layers — gated on a real untrusted-author user (research-9 §0). - -### A fourth option — none of the above - -Either the user has a different priority (a backlog item, a bug, a docs gap, a research question), or the suite genuinely is at a stable point and the next increment is "wait for real user feedback." Stable-and-waiting is a valid outcome of finishing v1; don't pull-forward speculative work just to keep shipping. - -## How to surface the choice - -When the user opens a session with "what's next" / "pick the next increment" / similar, ask via `AskUserQuestion`: - -- **Header:** `Next increment` -- **Question:** "Two post-v1 state-adapter options remain. Which (if any) should we schedule next?" -- **Options:** - 1. **`acture-state-jotai`** — Jotai adapter. Real implementation friction per research-3; needs a concrete consumer before pulling forward. - 2. **`acture-state-valtio`** — Valtio adapter. Same friction class as Jotai; same gate. - 3. (Other — let the user type a backlog item or a fresh ask.) - - (`acture-sandbox` is no longer an option — its isolation-only seam shipped in the extension-system increment. The next sandbox work is gated on a real untrusted-author user.) - -If the user picks 1 or 2, run a normal increment (Step 1 = `AskUserQuestion` on adapter-specific design choices — the atom-tree-vs-flat-state choice for jotai, the diff-vs-proxy-subscribe choice for valtio). If the user picks 3, **do not write package code** — propose a research-7 prompt first. - -## Standing constraints (unchanged from v1.12) - -- **`docs/positioning.md` is canonical.** Section 1 now names three delivery surfaces (skills/patterns, npm packages, PyPI). Each new package documents its hand-written equivalent in `docs/hand-written-*.md`. -- **`docs/redesign_takeaways.md` §6.** The rule of three is for acture *users*. For maintainer decisions, the principles are YAGNI / wait for a concrete named need, hard-don't #2 (no god-package), and the dev-tool-first principle. No callers-counting gate. -- **Hard-don'ts bind** (`acture-hard-donts` skill). For an adapter increment, #1 (no inner-platform DSL), #2 (no god-package), #3 (translate, don't decide) are the load-bearing ones. - -## Publishing state at chain end - -19 npm packages live on npm; 1 PyPI package live on PyPI (the real `acture` client, replacing the placeholder). No pending changesets. Release workflow has worked cleanly for v1.7 through v1.13; reuse the pattern. The "Publish Python stub to PyPI" job is now mis-named — it publishes the real client. Renaming is a cosmetic future PR. +# Next Session — user-steered + +**Your role:** the v1.14–v1.18 consumer-gap chain is complete. From here the user +steers. Do **not** pick new package work autonomously — surface options with honest +trade-offs. Mirror the v1.15–v1.17 single-increment workflow (Step 1 design decision +→ build → test → changeset → roadmap → reflection → commit → PR → merge Version PR → +verify publish) when the user picks a package increment. + +## What just shipped (v1.14 → v1.18) + +The three consumer surfaces the user was about to implement — **command palette**, +**keyboard-shortcut customization**, and an **app-operating AI assistant** — were +gap-filled end to end. Command palette was already covered; the other two are now +complete. + +- **v1.14 — research + skills + reference docs** (PR #41): `research_10` (end-user + keybinding customization) + `research_11` (AI assistant operating an app, + Vancouver-cited); new `acture-ai-assistant` skill; extended `acture-hotkeys` + skill; 3 `hand-written-*` refs (`keymap-override`, `view-registry`, + `assistant-runtime`). Adversarially reviewed; 8 correctness fixes applied. +- **peer-range republish** (PR #42): `acture-palette-react` / `acture-hotkeys` / + `acture-state-zustand` **1.0.1** — fixed the ERESOLVE exact-pin on `acture`. +- **v1.15 — MCP resources** (PR #44): `acture-mcp-server@1.2.0` — the AI read side + (`createMcpServer({ views })` → `resources/list`/`read`/`subscribe`). +- **v1.16 — getState tool** (PR #46): `acture-mcp-server@1.3.0` — the portable + read-side hedge for tools-only hosts (serves reelee-web's direct-Anthropic flow). +- **v1.17 — keymap customization** (PR #48): `acture-hotkeys@1.1.0` — end-user + remapping (`bindHotkeys({ keymap })` + `resolveKeys`/`detectConflicts`/capture & + display primitives). +- **v1.18 — confirmation gate** (PR #50): HITL for destructive AI dispatch, shipped + as a **pattern** (middleware+convention, not `CommandRecord` fields; hard-don't + #2). Complete secure impl in `docs/hand-written-assistant-runtime.md` Piece 1. + +**The "operate my app" story is complete:** write side (tools/MCP) + read side +(resources + getState) + HITL confirmation + macro capture + runtime bridge — as +packages where a package earned it, as patterns/skills elsewhere. + +**Named consumer:** `reelee-web` (its `src/ai/acture-tools.ts` bridges the registry +to Anthropic tools and classifies destructive commands) grounds all of the above. + +## Candidates next (user's call) + +1. **Wire the shipped pieces into `reelee-web`** — palette, hotkeys + keymap + customization, MCP resources + getState, and the confirmation-gate pattern. This + is the natural "use what we built" move; the named consumer is ready. +2. **`acture-state-jotai` / `acture-state-valtio`** — the remaining post-v1 state + adapters. Still gated on a real Jotai/Valtio consumer (research-3 friction; may + not implement `PatchCapableAdapter` cleanly). Defer unless a consumer surfaces. +3. **Stable-and-waiting** — a valid outcome. Don't pull-forward speculative work. + +If the confirmation gate ever needs to be an *importable* helper (not a copied +pattern) because a second project reuses it, revisit the pattern-vs-package call — +but honor hard-don't #2 (no god-package-of-one; find an existing home first). + +## Standing constraints (unchanged) + +- **`docs/positioning.md` is canonical** — dev-tool-first; each package documents its + hand-written equivalent in `docs/hand-written-*.md`. +- **`docs/redesign_takeaways.md` §6** — rule of three is for acture *users*; for + maintainer decisions: YAGNI / named need, hard-don't #2 (no god-package), + dev-tool-first. +- **Hard-don'ts bind** (`acture-hard-donts`). The closed `CommandRecord` stays + closed unless a change passes the named-need test (the confirmation gate + deliberately used convention over fields to honor this). +- Release workflow (changesets → Version PR → publish) has run cleanly for + #42/#44/#46/#48; reuse the pattern. ## When unsure -Re-read `docs/positioning.md`, `docs/redesign_takeaways.md` §6, `docs/roadmap.md`, and the v1.11 / v1.12 / v1.13 reflections. If a change is irreversible, append to `docs/escalations.md` and ask the user. - -**The autonomous chain is over.** From here, the user steers. +Re-read `docs/positioning.md`, `docs/redesign_takeaways.md` §6, `docs/roadmap.md` +(the v1.14–v1.18 entries), and the `acture-ai-assistant` / `acture-hotkeys` / +`acture-mcp` skills. If a change is irreversible, append to `docs/escalations.md` +and ask the user.