Class-closing half of #10371, split out by lane (the six repairs are the services-lane card; this guard is devx gate work). The guard was already ruled FOR by the domain:devx PM on the #9371 thread — this card is the workpiece, not a re-ask.
Why a guard and not six fixes
Plugin (packages/core/src/types.ts:170) declares init(), start?(ctx), destroy?() — and NOT stop(). The trap is the asymmetry: start?() IS on the interface and fires, so a start/stop pair reads symmetric in review while only one half is wired. This survived review in two packages and was only ever found by merge-queue evictions (#9371: two green PRs evicted). Fix six plugins and the seventh arrives spelled shutdown() or close(); the guard makes the class unreachable.
Spec of the check
Ordering
No hard dependency on the six repairs landing first, but the guard will red on all six until they do — either land after the services-lane repairs (#10371), or land with the six known instances baselined and the baseline burned down by that card. Dev picks; say which in the PR.
Refs: #10371 (repairs, same class) · #9371 / PR #10375 (root cause + ruling) · #10374 (upstream amplifier).
Triage: pm:queue, domain:devx, Task — gate addition with a named population and a ruled mandate.
Class-closing half of #10371, split out by lane (the six repairs are the services-lane card; this guard is devx gate work). The guard was already ruled FOR by the
domain:devxPM on the #9371 thread — this card is the workpiece, not a re-ask.Why a guard and not six fixes
Plugin(packages/core/src/types.ts:170) declaresinit(),start?(ctx),destroy?()— and NOTstop(). The trap is the asymmetry:start?()IS on the interface and fires, so astart/stoppair reads symmetric in review while only one half is wired. This survived review in two packages and was only ever found by merge-queue evictions (#9371: two green PRs evicted). Fix six plugins and the seventh arrives spelledshutdown()orclose(); the guard makes the class unreachable.Spec of the check
Pluginimplementations acrosspackages/**(the six known instances are enumerated in plugin-reports: the timers started by ReportsPlugin are released from stop(), which the kernel never calls #10371).stop/shutdown/close— exact roster is the dev's first design decision, document it in the gate header) and does not implementdestroy().MessagingServicePluginat main before PR fix(service-messaging): stop the delivery dispatchers on the kernel's own teardown hook (#9371) #10375 must red — a real fixture, not a synthetic one.stop()callingdestroy()or vice versa, the flaky: a vitest worker teardown race (EnvironmentTeardownError: Closing rpc while onUserConsoleLog was pending) fails app-showcase with 334/334 tests passing #9371 repair shape) must stay green.Ordering
No hard dependency on the six repairs landing first, but the guard will red on all six until they do — either land after the services-lane repairs (#10371), or land with the six known instances baselined and the baseline burned down by that card. Dev picks; say which in the PR.
Refs: #10371 (repairs, same class) · #9371 / PR #10375 (root cause + ruling) · #10374 (upstream amplifier).
Triage:
pm:queue,domain:devx, Task — gate addition with a named population and a ruled mandate.