Found while implementing #13598 (PR #15215), which narrowed packages/runtime/src/domains/packages.ts from twelve untyped deps.resolveService(context, 'protocol') handles to one. Filed rather than folded in: #13598's dispatch scoped its file surface to packages.ts and named domains/meta.ts explicitly as "same family, a different file, and NOT in scope by default".
Measured on 34a1740a (PR #15215's head), after that change lands
Instrument, per non-test file under packages/runtime/src:
grep -c "resolveService([^,]*, 'protocol'" and grep -c "protocol as any\|protocol: any"
| file |
resolveService(…, 'protocol') sites |
any casts on the handle |
domains/meta.ts |
9 |
4 |
domains/mcp.ts |
1 |
1 |
domains/ui.ts |
1 |
0 |
domains/packages.ts (after #13598) |
1 |
0 |
The packages.ts row is the control: it is the same instrument on the file that was just fixed, and its single remaining site is the one inside the narrowing helper. Without that row the other three counts say nothing about whether the pattern is repairable.
ui.ts is the cheapest of the three and shows the shape plainly: the handle is untyped, so protocol.getUiView({ object: objectName, type }) compiles against nothing even though getUiView and GetUiViewRequest are both declared on the spec's MetadataProtocol. mcp.ts already declares type McpMergedMetadataRead = Pick(MetadataProtocol, 'getMetaItems') for its merged-read seam but resolves its second handle (domains/mcp.ts:609) as a bare any.
Why it may be worth a card
- Same seam, same reason:
'protocol' is deliberately unmapped in ServiceSlotContracts, so resolveService answers any for that name and nothing downstream compiles against a contract.
- The consumer-side fix now EXISTS in-tree and is proven:
PackagesDomainProtocol plus a one-line resolveProtocol helper in domains/packages.ts, with a pin (packages-protocol-handle-typing.test.ts) whose @ts-expect-error directives go unused — TS2578 — if the seam regresses. A follow-up is a template application, not a design question.
- ⛔ The runtime feature-detection probes must survive any such change, for the documented reason: a host may occupy the slot with a partial object. Every member of the handle type is optional precisely so the type agrees with the probes instead of replacing them.
- ⛔ Not a
packages/spec change. Mapping 'protocol' in ServiceSlotContracts would assert that a filled slot IS a MetadataProtocol, whose members are mostly required — which is what the probes exist to deny — and it would have to answer for the verbs no contract declares.
Observation-grade: no behavioural defect measured, declared-vs-served unaffected. meta.ts in particular builds its literals field by field and has its own pins, so the win there is narrower than it was in packages.ts; sizing and priority are triage's call.
Found while implementing #13598 (PR #15215), which narrowed
packages/runtime/src/domains/packages.tsfrom twelve untypeddeps.resolveService(context, 'protocol')handles to one. Filed rather than folded in: #13598's dispatch scoped its file surface topackages.tsand nameddomains/meta.tsexplicitly as "same family, a different file, and NOT in scope by default".Measured on
34a1740a(PR #15215's head), after that change landsInstrument, per non-test file under
packages/runtime/src:resolveService(…, 'protocol')sitesanycasts on the handledomains/meta.tsdomains/mcp.tsdomains/ui.tsdomains/packages.ts(after #13598)The
packages.tsrow is the control: it is the same instrument on the file that was just fixed, and its single remaining site is the one inside the narrowing helper. Without that row the other three counts say nothing about whether the pattern is repairable.ui.tsis the cheapest of the three and shows the shape plainly: the handle is untyped, soprotocol.getUiView({ object: objectName, type })compiles against nothing even thoughgetUiViewandGetUiViewRequestare both declared on the spec'sMetadataProtocol.mcp.tsalready declarestype McpMergedMetadataRead = Pick(MetadataProtocol, 'getMetaItems')for its merged-read seam but resolves its second handle (domains/mcp.ts:609) as a bareany.Why it may be worth a card
'protocol'is deliberately unmapped inServiceSlotContracts, soresolveServiceanswersanyfor that name and nothing downstream compiles against a contract.PackagesDomainProtocolplus a one-lineresolveProtocolhelper indomains/packages.ts, with a pin (packages-protocol-handle-typing.test.ts) whose@ts-expect-errordirectives go unused — TS2578 — if the seam regresses. A follow-up is a template application, not a design question.packages/specchange. Mapping'protocol'inServiceSlotContractswould assert that a filled slot IS aMetadataProtocol, whose members are mostly required — which is what the probes exist to deny — and it would have to answer for the verbs no contract declares.Observation-grade: no behavioural defect measured, declared-vs-served unaffected.
meta.tsin particular builds its literals field by field and has its own pins, so the win there is narrower than it was inpackages.ts; sizing and priority are triage's call.