#1712

Link layer: the downward `LinkCommand` port is unbuilt (roadmap item 1)

Openjozanekopened 1mo ago

Roadmap item 1 opens with:

Move the CB delegates, scheduling, duty cycle, and buffers behind LinkEvent/LinkCommand ports.

BLELinkEvent shipped in slice 5 (#1551). LinkCommand has no implementation — the name occurs twice in this doc (lines 34 and 136) and nowhere in the source. Item 1 reads as finished because (a), (b) and (c) are all marked done, but only the upward half of the port was built.

Where the two directions stand

Upward (bleQueue → engine): enumerable. BLELinkEvent is five cases in one file, every crossing goes through emitLinkEvent into one consumer (handleLinkEvent), and SimulatedMesh drives the identical enum a radio does.

Downward (engine → bleQueue): not enumerable. 14 readLinkState call sites, each passing an arbitrary closure over BLELinkStateStore, plus 26 direct dispatch sites under Services/BLE/ (15 bleQueue.async/asyncAfter, 11 bleQueue.sync). There is no type that says what the engine may ask of the radio — you have to read BLEService.swift, still 7,187 lines, to find out.

To be explicit, since it would be the obvious wrong fix: this is not a proposal to make the downward edge async. engine ──sync──▶ bleQueue is pinned by BLEQueueContractTests, and the onEngine trap exists to catch exactly the ABBA pairing with readLinkState that caused the July 9 field freeze. A LinkCommand port would keep that edge and its direction unchanged; the question is only whether the surface crossing it has a name and a finite case list.

Why it's worth doing now rather than later

Roadmap item 2 names SimulatedLinkLayer, and the doc states the gap it leaves open:

Fidelity boundary: no physical links, so fanout planning/backpressure is not exercised; protocol behavior is.

That gap is downstream of this one. SimulatedMesh can substitute for the radio on the receive path because that path narrowed to one typed choke point. It can't substitute on the send path, because there is no surface to substitute — the engine reaches into linkStateStore through 14 closures. BLEOutboundWriteBuffer and BLEFanoutSelector are referenced by their own unit tests and by nothing else in the suite — never exercised end to end, for that reason.

Three of the four open PRs touching this code (#1671 fragment ceiling, #1666 write-buffer offsets, #1705 reassembly attribution) are in the part of the stack that simulation currently can't reach.

Not proposing

  • The formal handle(event) -> [Effect] system. The doc rules it out with reasons I'm not trying to relitigate, and a command enum is not that — no effect interpreter, no change to who owns state.
  • Splitting the engine-domain feature handlers into more files.
  • Any wire, signing, or peerID binding change.
  • Converting readLinkState's queue semantics.

Rough shape

BLELinkCommand as the mirror of BLELinkEvent: the physical admissions the engine already performs — notify, write, the backpressure queues, discardPeripheralLinkPhysical, connect/cancel, scan and advertise control — as cases rather than closures, with readLinkState kept as the synchronous query join it is today. Behavior-identical, one commit per group of call sites, in the slice style of #1540/#1547.

The measurable outcome is that SimulatedLinkLayer becomes writable against a finite enum, which is what closes the fanout/backpressure fidelity gap.

Question

Is this already claimed or in progress?

If it is, I'd rather not duplicate it — roadmap item 2's SimulatedLinkLayer is additive, collides with no open PR, and I'm happy to take that instead, or first regardless.

If it's open, I'd start with one slice covering the peripheral-notify path only, so the shape is reviewable before anything else moves.

Asking before writing it rather than opening an architecture PR unannounced: the slice series so far has been maintainer-authored end to end, and a large unsolicited refactor is the least reviewable thing that could land in the current queue.

Activity

0

No activity yet.