Skip to content

feat(guide): entry authorization replaces per-action consent [spec 03-03] - #137

Merged
wine-fall merged 9 commits into
mainfrom
zachg-0818--guide-auto-mode
Aug 18, 2026
Merged

feat(guide): entry authorization replaces per-action consent [spec 03-03]#137
wine-fall merged 9 commits into
mainfrom
zachg-0818--guide-auto-mode

Conversation

@wine-fall

Copy link
Copy Markdown
Owner

Stacked on #136 (quit correctness), which stacks on #134. Merge order: #134#136 → this.

Implements the 2026-08-18 consent revision of specs/spec03/03-03-guide-harness.md (§3, §5, §2 updated in this PR; recorded as a user decision).

What

Entry authorization replaces per-action consent: the offer card's y is the authorization, and the guide acts on it.

  • cliPermission rewritten: allows every tool call, never asks — the y/N wall (and the isReadOnlyCommand classifier that patched it, tests included) is deleted. Denies secret-bearing input with a reason the model can act on; a fired quit latch still denies.
  • The secret guard's real enforcement point is a PreToolUse hook (guideOptions): a smoke against the real SDK proved canUseTool is consulted only when the SDK's own policy would ask — a Read of .env sailed straight through the permission callback (a pre-existing hole in the per-action design too). The hook fires for every tool use; SECRET_BEARING also widens to secret-shaped names and environment dumps (*API_KEY, printenv, env), closing the review finding deferred from fix(guide): /quit cuts the session instead of waiting it out [spec 03-03] #136. murmur-owned tools are exempt — write_voice_config owns the keyboard secret channel by design.
  • Prompts rewritten (GUIDE_PERSONA + the task sections): routine steps — investigation, installs/upgrades on the user's own channel — run without asking, narrated as they go; the guide stops in natural language only at substantive forks (destructive/hard-to-reverse, a genuine choice, costs money). The never-take-a-credential-in-conversation rule is unchanged.
  • Offer card y row states the authorization: "I'll run the fixes, and check with you at real choices".
  • bypassPermissions stays banned (grep-guard kept); the surface stays bounded via tools.

Evidence

Real-SDK smoke (runGuide, Opus): node --version ran with zero questions reaching the host; a Read of .env was denied by the hook and the model quoted the credential reason back verbatim. Note: the SDK's safe-command classifier runs read-only commands without consulting canUseTool at all — visibility comes from the tool-activity stream (#134), which narrates every tool use into the dev log; the spec now records this seam fact.

Spec 03-03 §5.3 now owes a fresh by-ear pass under the new flow (the 2026-07/08 passes ran under per-action consent) — tracked as an issue.

Peer review: codex skipped — workspace spend cap; fell back to /code-review high (multi-agent): 8 findings, 6 applied, 2 dismissed. Applied: the api[_-]?key regex denied the walkthrough's own open .../app/api-keys step (URLs now stripped before the name test); the deny-list's bypass set (set/export -p//proc/self/environ/~/.murmur globs/bare $KEY expansions) now trips the guard; the false-positive set (/usr/bin/env as launcher, WebFetch pricing prompts, script shebangs) now passes; the quit latch reaches pullBed (stops at the next ref boundary); stale per-action-consent comments scrubbed across four files; date stamps dropped from code comments. Dismissed: the dev-log "command received" line states wire receipt — its purpose is quit-latency measurement, receipt ≠ handled; the SIGINT handover's escalation-counter reset is latent (the run still winds down via the quit check; the cost is one extra press only if voice.start() stalls).

AI coding brief

  • Original request: engine-line stacked PR B of two: guide auto-mode — entry authorization replaces per-action confirmation, the offer's y is the authorization; deny only secret-bearing paths; conversational stops at substantive forks; spec 03-03 §3/§5.1 revised as the 2026-08-18 user decision.
  • Manual interventions: none — executed as briefed; the PreToolUse-hook enforcement point was added beyond the brief after a real-SDK smoke showed canUseTool never sees the calls the SDK auto-allows.
  • Retro: the smoke-before-claiming-done discipline earned its keep — the permission-callback-only design (both old and new) was silently unenforceable for Read, and only a real SDK run showed it. Any consent-layer brief should demand a real-SDK probe of the enforcement point first.

🤖 Generated with Claude Code

wine-fall and others added 7 commits August 18, 2026 11:46
The guide's message loop dropped tool_use and tool_result blocks, so a
long repair (brew upgrade during setup) showed nothing between the
permission ask and the agent's next sentence. runGuideSession now
surfaces both through GuideRequest callbacks, and runSetup routes them
to the host: the command as it starts, the tail of its output when it
lands, errors labeled. A secret-bearing tool use the user approved by
hand keeps its output withheld — info lines mirror into the dev log.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Commands are the diagnostics trail (quit latency, menu picks); chat and
ask answers stay out — an answer may be a pasted secret.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…-03]

A typed /quit (or Ctrl-C) mid-onboarding now ends the run in under a
second instead of waiting out the guide's turn in flight:

- GuideRequest gains an interrupt signal; runGuideSession races every
  message against it and closes the SDK subprocess on fire (and now
  restores the for-await guarantee: the iterator is closed however the
  loop exits, a thrown handler included).
- runSetup skips the re-probe and the closing verdict when the user is
  leaving; cliPermission/cliConversation stop asking once the latch has
  fired.
- lineReader's latch is required, and a read resolved through EOF or the
  latch marks itself settled so its stale peekLine callback can no
  longer swallow a later line (the second /quit a stuck user types).
- first-run binds the fallback latch it actually checks, so a latch-less
  /quit writes no persona marker.
- One escalating SIGINT handler covers onboarding through the Director
  handover (and the explicit make setup entries), so a plain-mode
  Ctrl-C is never a bare process death mid-question.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The mirror keyed on a leading slash, so an ask answer that is an
absolute path (or chat opening with '/') was persisted — the very lines
the adjacent rule keeps out of the log. Membership in the COMMANDS
grammar is the test now.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…-03]

User decision recorded in spec 03-03 §3: the offer card's y IS the
authorization, and the checkpoints that remain are conversational.

- cliPermission allows every tool call and never asks; the y/N wall and
  the isReadOnlyCommand classifier that patched it are deleted, tests
  included. The persona and task prompts act on the authorization and
  stop, in natural language, only at substantive forks (destructive, a
  genuine choice, costs money).
- The secret guard's real enforcement point is a PreToolUse hook in
  guideOptions: a real-SDK smoke proved canUseTool is consulted only
  when the SDK's own policy would ask — a Read of .env sailed straight
  through the permission callback (a hole in the per-action design
  too). The hook fires for every tool use; the guard covers credential
  paths, secret-shaped Bash names (URLs stripped: opening a provider's
  api-keys page stays legal), environment dumps, and parameter
  expansion. A tripwire, not a sandbox; murmur-owned tools are exempt.
- The offer card's y row states what it buys; bypassPermissions stays
  banned (grep-guard kept).
- Spec 03-03 §1/§2/§3/§5/§6 revised; §5.3 owes a fresh by-ear pass
  under the new flow.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A Ctrl-C during a cold-cache pull printed "stopping..." and then waited
out the whole manifest. pullBed now consults shouldStop between refs,
and a quit that landed before the pull skips it entirely; the in-flight
download still runs out (ytdlpDownload has no abort seam — the known
cancellable-task gap).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Issue #102 measured ~6 burned rounds against fish.audio's
fetcher-hostile pages. The voice walkthrough now makes at most two
WebFetch attempts and then degrades honestly — says it could not
verify and hands over the link. §7.2 carries the cap.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
wine-fall and others added 2 commits August 18, 2026 16:29
@wine-fall
wine-fall merged commit 7a676d2 into main Aug 18, 2026
3 checks passed
@wine-fall
wine-fall deleted the zachg-0818--guide-auto-mode branch August 18, 2026 09:27
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