docs: add Phase B-1 pipeline-orchestration plan
Some checks failed
CI / build-and-test (push) Failing after 25s
Some checks failed
CI / build-and-test (push) Failing after 25s
This commit is contained in:
parent
38f7db888e
commit
f1513ddaf5
1 changed files with 167 additions and 0 deletions
|
|
@ -0,0 +1,167 @@
|
|||
# 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: `Idle` → `Receiving` (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.
|
||||
Loading…
Reference in a new issue