Branded Types & the Wire-Boundary Caveat
Branded types (AbsPath, InnerPath, Pid, FdNumber, ExitCode) are
declared in packages/core/src/kernel/types.ts. Each carries a readonly __brand: '…' phantom tag so that plain strings or numbers cannot satisfy the
type — callers must pass values minted by the designated constructor helpers
(absPath(), innerPath(), etc.).
InnerPath is an in-process brand
This note spells out one property that the rest of this codebase treats as implicit:
- (a)
InnerPathis an in-process brand. It has no wire representation, no 9P encoding, no on-disk form. It exists exclusively to witness — at the TypeScript level — that a path has been resolved throughNamespace.resolve()and is ready to be handed to aFileservermethod. - (b) The
AbsPath → InnerPathmint happens exclusively inNamespace.resolve()today. No other code mints anInnerPath, and no test or production site should reach forinnerPath(someString)as a shortcut. Tests that need a fileserver-relative path go through the namespace (or, when that is genuinely not an option, useinnerPath()with an inline justifying comment — this is the rare escape hatch). - (c) A future 9P-over-wire mount MUST mint
InnerPathonTwalksuccess at the wire adapter — not by casting strings at theFileserverboundary. Concretely: the adapter receives the peer’sTwalkresponse, walks the qid/name list into a local string, and at that boundary — where the wire has already proven the path is valid — mints theInnerPath. Casting at theFileserverboundary in the adapter would silently launder unverified strings into branded ones, defeating the whole point of the brand.
Why this note exists
The brand is the type-system’s witness of namespace resolution. Every production caller is protected by it. Tests and spike code have repeatedly wanted to skip resolution (it’s verbose, and branded-path APIs feel ceremonial in a test). The correct response has always been to make minting cheap — not to relax the brand.
When we eventually add a 9P mount adapter, the temptation will be to accept
strings from the wire and as InnerPath them at the Fileserver method
call. That would be wrong. The point of the brand is to remember where
validation happened; a wire adapter that received a valid walk result is a
valid minting site. A blanket as InnerPath at the method call is not.
Enforcement in test code
In test code, only absPath() / innerPath() are permitted to introduce
brand witnesses — enforced by lint on test/** and ports/*/test/** (T3).
The companion rule to the wire-boundary note above: tests may not invent
branded paths by casting through any, and the mint helpers are the single
sanctioned entry point for the rare case where a test genuinely needs a
literal.
See Also
packages/core/src/kernel/types.ts— brand declarations and constructorsdocs/architecture/fileservers/namespaces.md— howNamespace.resolve()producesInnerPathvalues todaydocs/product/tickets/debt-as-any-audit.md— the umbrella that motivated this note (track T8 “prevention”)