teamsiso/docs/superpowers/plans/2026-05-07-teamsiso-phase-b-1-pipeline-orchestration.md
Zac Gaetano f1513ddaf5
Some checks failed
CI / build-and-test (push) Failing after 25s
docs: add Phase B-1 pipeline-orchestration plan
2026-05-07 15:22:56 +00:00

7.5 KiB

TeamsISO Phase B-1 — Pipeline Orchestration Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task.

Goal: Implement the engine-side pipeline orchestration on top of the INdiInterop test seam from Phase A — NdiReceiver, NdiSender, ExponentialBackoff, NdiRuntimeProbe, IsoPipeline (lifecycle + restart loop), and IsoController (top-level engine API). All testable on Linux against FakeNdiInterop. Phase B-2 (real Windows P/Invoke for INdiInterop + libyuv IFrameScaler + integration tests) follows.

Architecture: Pure orchestration. Each IsoPipeline wires one NdiReceiver → existing FrameProcessor → one NdiSender via two bounded channels. The pipeline owns a restart loop driven by ExponentialBackoff. IsoController is the top of the engine — holds the pipeline dictionary, the ParticipantTracker, the ConfigStore, and exposes the contract the WPF host (Phase C) will bind to.

Tech Stack: .NET 8, xUnit, FluentAssertions. No new external dependencies.

Source spec: docs/superpowers/specs/2026-05-07-teamsiso-v1-design.md


File structure additions

src/TeamsISO.Engine/
├── Pipeline/
│   ├── NdiReceiver.cs              (NEW)
│   ├── NdiSender.cs                (NEW)
│   ├── ExponentialBackoff.cs       (NEW)
│   ├── IsoPipeline.cs              (NEW)
│   └── IsoPipelineConfig.cs        (NEW)
├── Interop/
│   └── NdiRuntimeProbe.cs          (NEW)
└── Controller/
    ├── IIsoController.cs           (NEW)
    └── IsoController.cs            (NEW)

src/tests/TeamsISO.Engine.Tests/
├── Pipeline/NdiReceiverTests.cs    (NEW)
├── Pipeline/NdiSenderTests.cs      (NEW)
├── Pipeline/ExponentialBackoffTests.cs (NEW)
├── Pipeline/IsoPipelineTests.cs    (NEW)
├── Interop/NdiRuntimeProbeTests.cs (NEW)
└── Controller/IsoControllerTests.cs (NEW)

Task 1: NdiReceiver

Receiver that wraps INdiInterop.CaptureFrame and pushes results into a ChannelWriter<RawFrame>. Exposes a CaptureOnce test seam mirroring FrameProcessor.ProcessOnceAsync. RunAsync is the production loop with LongRunning thread semantics.

TDD assertions:

  • CaptureOnce writes a captured frame to the output channel; counter increments.
  • CaptureOnce does nothing on null capture (timeout); counter does not change.
  • RunAsync honors cancellation and disposes the receiver handle on exit.

Commit: feat(pipeline): add NdiReceiver with channel-based output


Task 2: NdiSender

Sender that pulls from a ChannelReader<ProcessedFrame> and forwards to INdiInterop.SendFrame. SendNextAsync returns true if a frame was sent; false if the channel completed. RunAsync loops until cancellation.

TDD assertions:

  • SendNextAsync forwards a frame to the interop and increments the sent counter.
  • Returns false when channel completes.
  • RunAsync honors cancellation and disposes the sender handle.

Commit: feat(pipeline): add NdiSender with channel-based input


Task 3: ExponentialBackoff

Pure policy type. Given an attempt count, returns the next delay (1, 2, 4, 8, 16 s, capped at 30 s) and decides whether to give up after N consecutive failures (default 5).

TDD assertions:

  • Sequence at attempts 1..5 is 1, 2, 4, 8, 16 seconds.
  • ShouldGiveUp returns true after the 5th attempt.
  • Cap: at attempt 7 the delay is 30 s, not 64.

Commit: feat(pipeline): add ExponentialBackoff policy


Task 4: NdiRuntimeProbe

Reads the runtime version via INdiInterop.GetRuntimeVersion(), compares to an expected value (passed in by the engine for now; a real comparison against the SDK headers is Phase B-2). Returns either Match or Mismatch with both versions populated. The IsoController will surface EngineAlert.NdiRuntimeMismatch from a mismatch.

TDD assertions:

  • Match when versions equal.
  • Mismatch carries detected and expected.

Commit: feat(interop): add NdiRuntimeProbe with version-mismatch result


Task 5: IsoPipeline core lifecycle

Owns one NdiReceiver, one FrameProcessor, one NdiSender, and the two channels between them. StartAsync creates the channels, instantiates the receiver/processor/sender, kicks off the three loops on long-running tasks. StopAsync cancels the token, awaits the loops, and disposes everything.

IsoState transitions: IdleReceiving (after start) → Sending (after first send) → NoSignal (handled by FrameProcessor's slate path and exposed via Stats). On exception the loop transitions to Error.

The restart loop is in Task 6.

TDD assertions:

  • Start transitions Idle → Receiving.
  • Stop transitions back to Idle and disposes interop handles.
  • Receiver/sender handles are created on Start, disposed on Stop.

Commit: feat(pipeline): add IsoPipeline core lifecycle


Task 6: IsoPipeline restart loop

Wraps the running pipeline in a supervisory loop that catches unhandled exceptions, applies ExponentialBackoff, and either restarts or transitions to Error after exhausting retries. State observable updates accordingly.

TDD assertions (using a fault-injecting INdiInterop):

  • Pipeline that fails once, then runs cleanly, restarts and ends up Sending.
  • Pipeline that fails 5+ consecutive times transitions to Error and stays there.
  • Backoff delays are honored (using a fake delay primitive for fast tests).

Commit: feat(pipeline): add IsoPipeline restart supervisor with backoff


Task 7: IIsoController interface + IsoController implementation

The top-of-engine API the WPF host will bind to in Phase C.

Surface:

  • IObservable<IReadOnlyList<Participant>> Participants { get; }
  • IObservable<EngineAlert> Alerts { get; }
  • IsoHealthStats GetStats(Guid participantId)
  • Task EnableIsoAsync(Guid participantId, string? customName, CancellationToken ct)
  • Task DisableIsoAsync(Guid participantId, CancellationToken ct)
  • Task SetGlobalSettingsAsync(FrameProcessingSettings settings, CancellationToken ct)

Implementation owns: ParticipantTracker, NdiDiscoveryService, dictionary of IsoPipeline, the ConfigStore, the runtime probe.

TDD assertions:

  • EnableIsoAsync creates and starts a pipeline; DisableIsoAsync stops and removes it.
  • SetGlobalSettingsAsync persists via ConfigStore and applies to existing pipelines.
  • Discovery events flow through to the participants observable.
  • NdiRuntimeProbe mismatch surfaces an alert.

Commit: feat(controller): add IIsoController and IsoController implementation


Task 8: Wrap-up & milestone tag

  • Run full test suite, confirm all green.
  • Confirm coverage threshold still ≥80%.
  • Update docs/superpowers/plans/_NEXT.md to describe Phase B-2 (Windows-only).
  • Tag phase-b-1-complete.

Commit: chore: phase-b-1 milestone wrap-up Tag: phase-b-1-complete


Self-review

Spec coverage: Spec §4 components NdiReceiver, NdiSender, IsoPipeline, IsoController — Tasks 1, 2, 5, 6, 7. Spec §6 error handling restart/backoff — Task 6. Spec §6 NDI runtime mismatch — Task 4 + Task 7. ConfigStore integration in IsoController — Task 7.

Phase B-2 (deferred): Real NdiInteropPInvoke shim, real LibYuvFrameScaler, console smoke runner, integration tests against NDI Test Pattern source. All require Windows + NDI runtime so they live in their own plan.

Type consistency: All new types reference Phase A types unchanged. INdiInterop surface is sufficient — no additions needed.

No issues to fix. Ready to execute.