Give workspace names, herdr ids and commands their own opaque types #429

Open
opened 2026-08-21 13:06:44 +00:00 by mph · 0 comments
Owner

Problem

The terminal workspace port addresses everything with bare String. Three
different things share that one type, and the compiler cannot tell them apart.

  1. The workspace name — iw's <project>-<key>, e.g. iw-cli-IW-422. tmux
    accepts this directly as a session name.
  2. The herdr workspace id — what herdr actually accepts. herdr addresses a
    workspace by id only and does not accept a label, which is why
    HerdrLookup exists: it turns a checkout path or a label into an id.
  3. The command to run inside a workspace.

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.
  • Passing a label where herdr wants an id is a silent type-correct mistake. The
    issue documented this trap and worked around it in HerdrLookup; the type
    system does not enforce it.

Proposal

Introduce opaque types in core/model/ and thread them through the port.

opaque type WorkspaceName = String
opaque type HerdrWorkspaceId = String
opaque type Command = String

The design content is not the typing — it is deciding where each type starts and
stops. Specifically: at which boundary a WorkspaceName becomes a
HerdrWorkspaceId, whether HerdrWorkspaceId belongs in model/ or beside the
herdr adapter, and where the raw String is recovered for os.proc.

Estimated blast radius (measured 2026-08-21)

Where Sites
New opaque types in core/model/ ~15 lines
TerminalWorkspaceOps signatures 12 methods
LiveTmuxOps, LiveHerdrOps, UnavailableWorkspaceOps ~62 name references
FakeTerminalWorkspaceOps ~39
Tmux.scala argv builders ~23, mostly .value at the process boundary
Herdr.scala ~3
Test files 13

Roughly 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
SessionHooks to the capability it calls. Naming the capability
(RunInWorkspace, commit 69b2c64) restored the parameter roles in the type but
left both parameters as String. This issue closes the remaining gap.

## Problem The terminal workspace port addresses everything with bare `String`. Three different things share that one type, and the compiler cannot tell them apart. 1. **The workspace name** — iw's `<project>-<key>`, e.g. `iw-cli-IW-422`. tmux accepts this directly as a session name. 2. **The herdr workspace id** — what herdr actually accepts. herdr addresses a workspace **by id only and does not accept a label**, which is why `HerdrLookup` exists: it turns a checkout path or a label into an id. 3. **The command** to run inside a workspace. 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. - Passing a label where herdr wants an id is a silent type-correct mistake. The issue documented this trap and worked around it in `HerdrLookup`; the type system does not enforce it. ## Proposal Introduce opaque types in `core/model/` and thread them through the port. ```scala opaque type WorkspaceName = String opaque type HerdrWorkspaceId = String opaque type Command = String ``` The design content is not the typing — it is deciding where each type starts and stops. Specifically: at which boundary a `WorkspaceName` becomes a `HerdrWorkspaceId`, whether `HerdrWorkspaceId` belongs in `model/` or beside the herdr adapter, and where the raw `String` is recovered for `os.proc`. ## Estimated blast radius (measured 2026-08-21) | Where | Sites | |---|---| | New opaque types in `core/model/` | ~15 lines | | `TerminalWorkspaceOps` signatures | 12 methods | | `LiveTmuxOps`, `LiveHerdrOps`, `UnavailableWorkspaceOps` | ~62 `name` references | | `FakeTerminalWorkspaceOps` | ~39 | | `Tmux.scala` argv builders | ~23, mostly `.value` at the process boundary | | `Herdr.scala` | ~3 | | Test files | 13 | Roughly 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 `SessionHooks` to the capability it calls. Naming the capability (`RunInWorkspace`, commit 69b2c64) restored the parameter roles in the type but left both parameters as `String`. This issue closes the remaining gap.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
iterative-works/iw-cli#429
No description provided.