Give workspace names, herdr ids and commands their own opaque types #429
Labels
No labels
agent-ready
bug
contract
documentation
duplicate
enhancement
good first issue
help wanted
invalid
needs-human
needs-input
question
wontfix
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
iterative-works/iw-cli#429
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
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?
Problem
The terminal workspace port addresses everything with bare
String. Threedifferent things share that one type, and the compiler cannot tell them apart.
<project>-<key>, e.g.iw-cli-IW-422. tmuxaccepts this directly as a session name.
workspace by id only and does not accept a label, which is why
HerdrLookupexists: it turns a checkout path or a label into an id.Two consequences, both already met in IW-422:
RunInWorkspace.apply(workspace: String, command: String)takes two Strings.Transposing them compiles cleanly and fails only at runtime — it would run the
workspace name as a command, inside a workspace named after a command.
issue documented this trap and worked around it in
HerdrLookup; the typesystem does not enforce it.
Proposal
Introduce opaque types in
core/model/and thread them through the port.The design content is not the typing — it is deciding where each type starts and
stops. Specifically: at which boundary a
WorkspaceNamebecomes aHerdrWorkspaceId, whetherHerdrWorkspaceIdbelongs inmodel/or beside theherdr adapter, and where the raw
Stringis recovered foros.proc.Estimated blast radius (measured 2026-08-21)
core/model/TerminalWorkspaceOpssignaturesLiveTmuxOps,LiveHerdrOps,UnavailableWorkspaceOpsnamereferencesFakeTerminalWorkspaceOpsTmux.scalaargv builders.valueat the process boundaryHerdr.scalaRoughly half a day. The work is mechanical and compiler-driven — it does not
compile until every boundary is converted, so nothing is silent.
Origin
Raised while triaging LESSONS.md for IW-422, from the entry on narrowing
SessionHooksto the capability it calls. Naming the capability(
RunInWorkspace, commit69b2c64) restored the parameter roles in the type butleft both parameters as
String. This issue closes the remaining gap.IW-476 phase 2 created a new instance of this fault, found independently by the composition and scala3 reviewers.
The new call site.
TerminalWorkspaceOps.createwidened from(name, workDir)to(name: String, label: String, workDir: os.Path)(core/commands/CommandEnv.scala:403-407). Two same-typed, adjacent parameters with non-interchangeable meanings now sit next to each other, socreate(label, name, workDir)compiles silently.It fails differently per backend, which makes it worse than the RunInWorkspace case.
LiveTmuxOpsreadsnameand discardslabel;LiveHerdrOpsreadslabeland discardsname. A transposed call therefore misbehaves in one way under tmux and another under herdr, and every automated tier runs against tmux only.A gap in this issue's current proposal. The three opaque types listed here —
WorkspaceName,HerdrWorkspaceId,Command— have no counterpart for the display label.WorktreePath.sessionNameaddresses the workspace;WorktreePath.workspaceLabelonly displays it. Both are strings derived from the sameWorktreePath. Distinguishing them needs either a fourth opaque type or, as the reviewers suggested, oneWorkspaceIdentity(name, label)value produced byWorktreePath, narrowingcreateto(identity, workDir).The pattern is spreading.
Start.scala:250-259'screateSessionAndRunHooksnow takes five positional parameters. Three are derivable from aWorktreePaththe caller already holds, and a fourth (key) is one of its fields. PassingworktreePathinstead of unpacking it would shrink the signature and remove the same transposition class.What IW-476 did instead. A partial fix only: the three
createcall sites (Start.scala:259,Open.scala:119,CaskServer.scala:1283) pass named arguments. That removes the transposition risk where it exists today without changing the port signature the frozen phase context specified. The type-level fix is left to this issue.Recorded as IW-476 LESSONS.md entry 14.
Intake: agent-ready
Workflow: waterfall
Reason: all gates passed
Analysis: project-management/issues/IW-429/analysis.md on branch IW-429
Implement: needs-human
Reason: batch-implement exit 1 — the kanon plugin command did not compile against this branch's iw-cli core, so no phase ran.
ForgeCatchUp.scala:143expectsEither[String, …]fromfetchPullRequestForBranch, and core now returnsEither[TrackerError, …](since #778/#780). There is no batch-implement.log; these are the lines of the command output:Release: the implement loop failed this issue for a reason outside it. The kanon plugin did not compile against iw-cli main, because the tracker port now returns TrackerError (iw-cli #778). The plugin fix is dev-docs #484, and iw-cli 0.22.1 carries the core change. The issue goes back to the implement loop.