Skip to content

feat(sidecar): taint escalates to HIC approval (v0.5.1 HGW-W1) - #79

Merged
SaulBuilds merged 3 commits into
mainfrom
hgw/w1-taint-hic
Oct 15, 2026
Merged

SaulBuilds merged 3 commits into
mainfrom
hgw/w1-taint-hic

Conversation

@SaulBuilds

@SaulBuilds SaulBuilds commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

HGW Week 1, runtime, 1 of 5. Stack: this PR (base main) → #80 → #81 → #82 → #83. Each PR is green on its own (fmt on touched files, clippy -D warnings on agent-sidecar / agent-loop / agent-shell, cargo test --workspace --locked); merge in order. Refs #74 (HGW-W1). Owner decision 2026-10-15, HGW (2). Needs the owner's security sign-off (W1 adds a privileged path, per the sprint file).

What changes

Before: any shell_run output (even an approved node -v) tainted the session, and every later command and sidecar-hosted write was declined for the rest of the conversation ("…which this tool's host cannot ask for"). Nothing could clear it.

Now, in a session opened with hicAware: true:

  • A tainted, effectful, sidecar-hosted call (fs_*, file_write, sheet_write, the toolchain tools, capsules) waits on a new per-session approval card. The card shows the tool, the exact canonical arguments (subject), a one-line summary, hicReason and every taint source. An allow runs that one call with those arguments.
  • shell_run still asks after taint, on its own card, which now also carries hicReason and taintSources.
  • An approval releases one action. The session stays tainted, so the next action asks again. Nothing is approved automatically.
  • Explicit clear ([v0.5.1 HGW-W1] Taint escalates to an HIC approval #74): POST /sessions/:id/taint/clear {note} calls TaintState::clear_by_member. The clear is recorded first (taint.clear). If no records log is configured, or the record fails, nothing is cleared (503). A scheduled (unattended) session is never cleared (409).
  • A declined card tells the model to ask the member instead of retrying.
  • Without hicAware (headless or scheduled), nobody can be asked. The call is declined with citrate_agent_loop::cannot_ask_reason, which names the taint source.
  • Every decision is recorded write-ahead (sidecar.tool_call), like the shell and MCP cards.

Interface for the core lane

Route Body / response
GET /sessions/:id/approvals/pending {pending: [{id: "act-<n>", callId, tool, hic: "required", subject, summary, hicReason, taintSources, expiresInSecs}]}
POST /sessions/:id/approvals/:aid/decide {allow, subject}. The subject must equal the card's; otherwise 409. A missing session is 404.
POST /sessions/:id/taint/clear {note}: 200 {ok, cleared: {source, reason}, sources}; 400 for an empty note; 409 when unattended or not tainted; 503 when no records log is configured
GET /sessions/:id/shell/pending unchanged, plus optional hicReason and taintSources
  • Core must open sessions with hicAware: true and poll /approvals/pending next to /shell/pending (sidecarProvider.ts). The card renders summary, subject, hicReason and taintSources.
  • Core shows "clear taint" only as an explicit member card and needs CITRATE_HERMES_RECORDS_DIR set.

Tests (red first)

  • agent-loop taint_tests: +1 (the headless refusal names its source)
  • action_approvals unit: +3
  • action_approvals_tests: +8. Covers AT-1.1 (≥3 consecutive shell_runs plus fs_write after taint), one approval per action, exact subject, the headless decline, routes and bearer, records, the explicit clear, and ask-not-retry.
  • shell_run_tests: +2 (one test rewritten for the new semantics)
  • files_tests: the taint case is rewritten to the card flow (a decline writes nothing)
  • Workspace: 2241 → 2255 passed, 0 failed (baseline 5b337ff).

Not here

The ADR-HGW-1 write-up, which the sprint file says lands first (Mac lane, core repo).

🤖 Generated with Claude Code

https://claude.ai/code/session_018wWZZ8GRVU9USh3kKQHsFe

SaulBuilds and others added 2 commits October 15, 2026 11:34
Owner decision 2026-10-15 (HGW (2)). Before this change any shell_run
output, even an approved `node -v`, tainted the session, and every later
command and sidecar-hosted write was declined for the rest of the
conversation ("which this tool's host cannot ask for"), with no way to
clear it.

Now, in a session opened with hicAware: true:
- a tainted effectful sidecar call (fs_*, file_write, sheet_write,
  toolchain tools, capsules) waits on a new per-session action card
  (GET /sessions/:id/actions/pending, POST .../actions/decide
  {id, allow, subject}); the card shows the tool, the exact canonical
  arguments, a summary, the hicReason and every taint source;
- shell_run keeps its own card and still asks after taint; the card
  now carries hicReason and taintSources;
- an approval releases that one call; the session stays tainted, so
  the next action asks again (never global, never automatic);
- decisions are recorded (kind sidecar.tool_call, write-ahead).

Without hicAware (headless, scheduled) nobody can be asked: the call is
declined with citrate_agent_loop::cannot_ask_reason, which names the
taint source and says why.

Tests: agent-loop taint_tests (+1), action_approvals unit (+3),
action_approvals_tests session/route tests (+6), shell_run_tests (+2,
one rewritten), files_tests taint case rewritten to the card flow.
Workspace: 2241 -> 2253 passed, 0 failed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018wWZZ8GRVU9USh3kKQHsFe
…t-retry (HGW-1)

Aligns the HGW-1 interface with the HGW-W1 issue (#74):
- routes are GET /sessions/:id/approvals/pending and
  POST /sessions/:id/approvals/:aid/decide {allow, subject};
- POST /sessions/:id/taint/clear {note}: the member clears the taint
  from an explicit card (TaintState::clear_by_member). Recorded
  write-ahead (kind taint.clear); with no records log, or when the
  record cannot be written, nothing is cleared (503). Scheduled
  (unattended) runs are never cleared (409); empty note 400; not
  tainted 409. Approving one action still never clears the taint;
- a declined card tells the model to ask the member instead of
  retrying (shell_run and approval cards).
Test support opens one undo store per manager (a store is locked while
open).

Tests: action_approvals_tests (+2). Workspace: 2253 -> 2255 passed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018wWZZ8GRVU9USh3kKQHsFe

@BerryManifold BerryManifold left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed per-action taint escalation, exact-subject matching, bearer-protected decision routes, headless refusal, stop/timeout handling, and recorded member taint clearing. The 11 action-approval unit/session tests passed locally on the #81 stack head. No blocking finding in this PR. This approval does not cover later git/auto-approval changes; #85 must land to resolve the existing audit failures.

@Matr0xshka Matr0xshka left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed per-action taint escalation, exact-subject matching, bearer-protected decision routes, headless refusal, stop/timeout handling, and recorded member taint clearing. The 11 action-approval unit/session tests passed locally on the #81 stack head. No blocking finding in this PR. This approval does not cover later git/auto-approval changes; #85 must land to resolve the existing audit failures.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018wWZZ8GRVU9USh3kKQHsFe
@SaulBuilds
SaulBuilds merged commit 4dc75ec into main Oct 15, 2026
10 checks passed
@SaulBuilds
SaulBuilds deleted the hgw/w1-taint-hic branch October 15, 2026 23:12
SaulBuilds added a commit that referenced this pull request Oct 16, 2026
The squash of #79 on main is identical to hgw/w1-taint-hic@00264e5, which this branch already contains, so the resolution keeps this branch's tree.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018wWZZ8GRVU9USh3kKQHsFe
SaulBuilds added a commit that referenced this pull request Oct 16, 2026
main is the squash of #79, identical to hgw/w1-taint-hic@00264e5, which this branch already contains; the resolution keeps this branch's tree. Lands #80-#84 as one squash (owner decision 2026-10-15).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018wWZZ8GRVU9USh3kKQHsFe
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.

3 participants