HOMELAB-1727: fix(pi-friction): the /friction command stamps the human actor #63
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "plane/HOMELAB-1727-friction-actor"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Part of HOMELAB-1727 (LD4.10), the 4.6 handoff item: "
/frictionstampsactor.kind: "agent", so R-28/30 cannot distinguish the operator's filing from an agent's".pi-extensionshas noepic/LD4branch — the other half of this ticket lands inHomelab/infra-core#600(targetingepic/LD4) — so this one PR targetsmainfor the TS fix alone.The defect
draftForhard-codedactor: { kind: "agent", id: facts.sessionId, sandboxEnv: facts.sandboxEnv }, so the operator's own/frictionline looked exactly like an agent session's filing. Two consumers care:ld.friction/v1line onwhere.surface: skillas a correction source, alongside the in-session operator turn.The fix
draftFortakes the actor kind:actorsessionId+sandboxEnv)frictiontool/frictioncommand{ kind: "human", id: "operator" }stampDraftderivessandboxEnv: "unbound"for the human actor (the operator is not inside a sandbox) — the same value the Go decode path derives, so unbound work stays visible rather than absent (P-ROUT-006). No schema change:actor.kindalready declareshuman, and the TS validator only requires a sandbox binding foragent.Verification
npx tsc --noEmitclean;npx vitest run→ 99 tests passing.kind: "agent", i.e. the defect as correct).friction.DecodeLine) is in infra-core #600, so both sides of the wire are asserted.The 4.6 handoff defect: `draftFor` hard-coded `actor.kind: "agent"`, so the operator's `/friction` line was indistinguishable from an agent session's — R-28/R-30 could not tell the operator's filing from an agent's, and R-26's episode leg (LD4.10) cannot read a human-actor line as a correction source. `draftFor` now takes the actor kind: the automatic hook and the model-callable tool stay the agent (session id + sandbox binding), and the command path stamps `{kind: "human", id: "operator"}`. A human actor carries no sandbox binding; stampDraft derives `sandboxEnv: "unbound"`, which is the same value the Go decode path stamps, so unbound work stays visible (P-ROUT-006). The Go half of the same defect (friction.Line accepting an operator line with no binding) is asserted in infra-core PR #600 — one fixture on each side of the wire.Landing review — PASS (no blocker findings)
Reviewed head
ec02adeagainst primary sources only (diff,types.ts/index.tsat head, the Go wire contract oninfra-core@epic/LD4, and an independent re-run).Verdict: PASS — mergeable on green CI.
Correctness vs the acceptance criterion (actor attribution)
The defect is real and the fix matches it. At head the three surfaces stamp:
tool_execution_endsessionId+sandboxEnv)autoCaptureDraft,capture.ts:133(type is agent-only)frictiontool (model-callable)draftFor(..., kind="agent" default)/frictioncommand{kind:"human", id:"operator"}draftFor(..., "human")The tool and auto-hook are correctly left as agent; only the human path changed.
OPERATOR_ACTOR_ID = "operator"matches the schema's declared id (Actor.iddoc, validator message) — no new vocabulary invented.Wire compatibility (checked, not assumed)
{kind:"human", id:"operator", sandboxEnv:"unbound"}decodes on the Go side:ActorKinddeclaresActorHuman = "human"andLine.Stamp()fillsSandboxUnboundfor any actor with an emptySandboxEnv, with a comment explicitly pinning byte-for-byte agreement with this TS capture surface. Schema deltas are zero:ACTOR_KINDSalready includeshuman, and the validator only requiressandboxEnvforkind: "agent". Consistent with #600's half.Test adequacy / independent verification
Not taken on trust — reproduced at head in a detached worktree:
npx tsc --noEmitclean,npx vitest run→ 99 passed (5 files). The command test asserts the full actor object against the spool bytes (behavior, not implementation) and previously asserted the defect (kind: "agent") as correct; the validator'sunboundstamping for a human actor is covered intypes.test.ts. Coverage of both the changed path and its derivation is present.Scope
Diff is confined to
pi-friction/{README.md,index.ts,tests/extension.test.ts}— the PR's own area.pi-otel-telemetryandpi-swarm-suiteare untouched (verified: no hunks, no shared consumers in-repo; R-28/R-30 live on the Go side). The command's JSON form carries noactorfield inParsedCommand, so the surface-derives-actor property holds with no dead plumbing.Non-blocking note (does not gate merge)
The new comment in
index.ts(echoed in the PR body) saysstampDraftderivessandboxEnv: "unbound". In TS,stampDraftpassesactorthrough unchanged — the fill happens invalidateLine's output normalization (types.ts:502-507). The behavior and the asserted spool bytes are correct; only the attribution in the prose is imprecise. Suggest a follow-up wording tweak (or fold into a later pi-friction touch), not a re-spin.Merge on green CI.