mirror of
https://github.com/PerpetualSoftware/pad.git
synced 2026-09-11 13:28:57 +00:00
0693807906
Agent CLI writes were recorded as the human whose credentials they used. Three independent defects, each verified by reading the path AND by probing a live instance — the item deliberately held the mechanism open, so none of this is inherited. 1. THE HEADER WAS NEVER SENT. actorFromRequest sets actor="agent" on one signal: the X-Pad-Agent header. The only code that sets it took the value from `agent_name` in .pad.toml and nowhere else — no environment detection, no session detection. This repo's .pad.toml has only `workspace`, so the header has never been sent from here and every agent write has looked human. ResolveAgentName now resolves .pad.toml → $PAD_AGENT → detected runtime. 2. ITEM CREATE DISCARDED THE ACTOR. createItemChecked called actorFromRequest and kept only the source (`_, src :=`), never setting input.CreatedBy, so store.CreateItem fell through to its "user" default — even for an agent that DID send the header. Comments have always stamped it correctly; item creation silently did not, which made the skill's own contract false on its own terms. 3. SINGLE-ITEM PATCH NEVER STAMPED LastModifiedBy. Bulk ops do (handlers_items_bulk.go); the single-item path did not, so an item edited only by agents read as human-edited. Only entries VERIFIED against a live session belong in the runtime detection table, so it has exactly one: Claude Code exports CLAUDECODE=1 to child processes, confirmed by reading a pad subprocess's environment inside one. Guessing at Cursor/Windsurf/Aider variable names would put unverified claims in a shipped binary and misattribute silently when wrong; those set $PAD_AGENT until someone confirms a signature. WHAT THIS DOES NOT DO, stated in the code and the skill rather than left for someone to assume: the header is client-supplied and self-declared. An agent that omits it is indistinguishable from the human it borrows credentials from, and a human running `! pad ...` inside an agent's terminal inherits that environment and is attributed to the agent. This makes the trail HONEST, not VERIFIED — it is not a basis for machine-verifiable human-approval provenance, which needs a channel the agent cannot author at all. The incident behind this item is exactly that distinction: an agent's relay of a human's words was recorded indistinguishably from the human typing them. Contract corrected in both skill copies, since the item's first question was which of contract and behavior was wrong. It was the contract: it promised automatic agent attribution that only ever applied to workspaces that had opted in. Tests, each mutation-tested against its own defect reverted alone: - TestResolveAgentName — precedence plus the negative that makes it mean something: a plain human shell must still resolve to "". Fails 2/5 reverted. - TestItemAttribution_AgentVsHuman — agent and human legs for create, update and the create-stamp-survives-edit invariant. Fails on the create stamp reverted; fails 2/2 on the update stamp reverted. The update leg deliberately uses the OTHER writer: insertItemTx seeds last_modified_by FROM created_by, so a same-writer edit passes whether or not the PATCH stamps anything — the first version of this test did exactly that and passed its own counterfactual. Caught only because each fix was reverted separately. - TestItemAttribution_ExplicitBodyValueWins — an explicit body value still beats the header. End-to-end on a live instance through the real CLI, no .pad.toml opt-in: agent session → created_by/last_modified_by/comment all `agent`; same binary with CLAUDECODE stripped → all `user`. Claude-Session: https://claude.ai/code/session_01QGbUKZBAZoWdEgiTNWsXag