fix(client,host): late duplicate-provider conflicts roll back wholesale and fail loud

Holes declared after two flow providers activated left the loser throwing
out of the slot flush with partial occupancy. deferRegistration gains an
onFailure channel (late failures unsubscribe, then hand over instead of
throwing through the flush); the native flow's pair rolls back wholesale
and re-raises on the global channel the boot's fail-loud handler owns.
Duplicate rows of the SAME package stay silently idempotent (the component
identity guard skips an occupied hole).
This commit is contained in:
creatixchu
2026-07-29 03:06:34 +08:00
parent a9b8fa2585
commit 9f37ca2709
4 changed files with 76 additions and 4 deletions

View File

@@ -24,6 +24,27 @@ describe('deferRegistration', () => {
expect(core.entries(HOLE)).toHaveLength(0)
})
it('hands a late registration failure to onFailure after unsubscribing itself', async () => {
const core = new SlotCore()
const component = (): null => null
const foreign = (): null => null
const failures: unknown[] = []
// Nothing is declared yet: the deferral just subscribes and waits.
const register = vi.fn(() => core.register({ name: HOLE } as never, component as never))
deferRegistration(core, HOLE, component, register, (error) => { failures.push(error) })
// The declaration lands with a foreign occupant racing in first: the
// deferral's flush-time attempt fails, unsubscribes itself, and reports
// through onFailure instead of throwing out of the flush.
core.register({ name: 'root', children: { [HOLE]: { kind: 'single', scope: 'root' } } } as never, (() => null) as never)
const disposeForeign = core.register({ name: HOLE } as never, foreign as never)
await Promise.resolve()
expect(failures.map(String).join('')).toContain('already has a registration')
// Unsubscribed: freeing the hole must not resurrect the loser.
disposeForeign()
await Promise.resolve()
expect(core.entries(HOLE)).toHaveLength(0)
})
it('drops its subscription when the immediate registration throws', async () => {
const core = declared()
const foreign = (): null => null