docs(plan): add T-304 delivery plan
This commit is contained in:
@@ -0,0 +1,381 @@
|
||||
# T-304 Go 实时演示、报警和打包 Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** Build a Windows Go V2 application that safely reads one local RTSP stream, performs ONNX Pose and fall-event processing off the UI thread, visibly alerts once per confirmed event, persists evidence, and can be assembled into a self-contained release folder.
|
||||
|
||||
**Architecture:** `internal/config` resolves a credential-free JSON configuration and the RTSP URL only from an environment variable. `internal/source` owns FFprobe/FFmpeg subprocesses, reconnection and a one-slot latest-frame channel. `internal/monitor` owns ONNX Pose and the existing fall engine; it publishes completed render frames and events. `internal/render` and `internal/alert` draw/persist evidence on worker goroutines. `internal/ui` is a Walk-only observer and command surface; it receives already-computed state via `Synchronize` and never opens a stream or runs inference.
|
||||
|
||||
**Tech Stack:** Go 1.24, `github.com/yalue/onnxruntime_go` v1.31.0, ONNX Runtime CPU DLL, FFmpeg/FFprobe, `github.com/lxn/walk`, Windows `MessageBeep`, standard-library JSON/PNG/SHA-256, existing `internal/pose` and `internal/fall`.
|
||||
|
||||
---
|
||||
|
||||
## Scope and acceptance boundary
|
||||
|
||||
- One Windows camera stream only. The public configuration stores an environment-variable name, never an RTSP URL, user name or password.
|
||||
- The target scene remains a fixed, downward-viewing hall/corridor with most of a body visible. `demo/1.mp4` remains a documented out-of-scope false-negative risk sample, not a T-304 success test.
|
||||
- The user manually closed T-303. This plan must not describe that as a new positive-video or event-latency validation result.
|
||||
- Release dependencies are copied from explicit operator-supplied paths and their hashes are written into the generated release manifest. No DLL, FFmpeg binary, ONNX file, customer video, credential or event artifact is committed.
|
||||
|
||||
## File map
|
||||
|
||||
| Path | Responsibility |
|
||||
| --- | --- |
|
||||
| `v2/config.example.json` | Public, credential-free V2 runtime shape and default V1-equivalent event values. |
|
||||
| `v2/internal/config/config.go` | Parse/validate JSON, resolve RTSP URL from environment, calculate a non-secret configuration version, and validate hashes/paths. |
|
||||
| `v2/internal/source/stream.go` | Probe source metadata, launch/relaunch FFmpeg, expose status and a latest-frame channel, and hide subprocess details that could contain a URL. |
|
||||
| `v2/internal/monitor/monitor.go` | Compose source, reusable ONNX Runtime, Pose parse and the existing fall engine; publish display-ready results. |
|
||||
| `v2/internal/render/overlay.go` | Convert BGR to RGBA and draw box, 17-point skeleton, state/ID/time and red confirmed border. |
|
||||
| `v2/internal/alert/dispatcher.go` | One-shot PNG + JSONL evidence writer, `MessageBeep` adapter and UI popup payload. |
|
||||
| `v2/internal/ui/window.go` | Light Windows Walk main window, Monitor/Settings tabs and UI-thread-safe update methods. |
|
||||
| `v2/cmd/silver-pose/main.go` | CLI lifecycle, dependency setup and app/UI composition. |
|
||||
| `v2/scripts/build-v2-release.ps1` | Build an isolated release directory, copy explicit runtime dependencies and emit their versions/hashes. |
|
||||
| `docs/v2-demo-runbook.md` | Preflight, local secret setup, start/stop, normal/fall/connection-error demo sequence, evidence review and scope statement. |
|
||||
|
||||
### Task 1: Safe V2 configuration and dependency manifests
|
||||
|
||||
**Files:**
|
||||
- Create: `v2/config.example.json`
|
||||
- Create: `v2/internal/config/config.go`
|
||||
- Create: `v2/internal/config/config_test.go`
|
||||
- Create: `v2/assets/runtime-manifest.example.json`
|
||||
- Modify: `docs/api.md`
|
||||
|
||||
- [ ] **Step 1: Write failing configuration tests**
|
||||
|
||||
```go
|
||||
func TestLoadResolvesOnlyNamedEnvironmentURL(t *testing.T) {
|
||||
t.Setenv("SILVER_POSE_RTSP_URL", "rtsp://operator:secret@192.0.2.9/Streaming/Channels/101")
|
||||
cfg, err := Load(writeConfig(t, `{"source":{"id":"lobby","rtsp_url_env":"SILVER_POSE_RTSP_URL"},"model":{"onnx":"assets/best.onnx","sha256":"`+strings.Repeat("a", 64)+`","ort_dll":"runtime/onnxruntime.dll"},"tools":{"ffmpeg":"runtime/ffmpeg.exe","ffprobe":"runtime/ffprobe.exe"},"event":`+defaultEventJSON+`,"artifacts":{"event_dir":"../artifacts/events"}}`))
|
||||
if err != nil || cfg.Source.URL == "" || strings.Contains(cfg.RuntimeConfigVersion, "secret") { t.Fatalf("unexpected config: %#v, %v", cfg, err) }
|
||||
}
|
||||
|
||||
func TestLoadRejectsEmbeddedRTSPURL(t *testing.T) {
|
||||
_, err := Load(writeConfig(t, `{"source":{"id":"lobby","url":"rtsp://secret"}}`))
|
||||
if err == nil || !strings.Contains(err.Error(), "rtsp_url_env") { t.Fatal(err) }
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run the focused test and verify the missing package fails**
|
||||
|
||||
Run: `Set-Location v2; go test ./internal/config -run TestLoad -v`
|
||||
Expected: FAIL because `internal/config` does not exist.
|
||||
|
||||
- [ ] **Step 3: Implement the minimal public contract**
|
||||
|
||||
```go
|
||||
type Config struct {
|
||||
Source SourceConfig; Model ModelConfig; Tools ToolConfig
|
||||
Event fall.EngineConfig; Artifacts ArtifactConfig; RuntimeConfigVersion string
|
||||
}
|
||||
type SourceConfig struct { ID string; URL string; Transport string; Timeout time.Duration; LowLatency bool }
|
||||
|
||||
func Load(path string) (Config, error) {
|
||||
// Decode into a private raw type, reject raw source.url/host/user/password,
|
||||
// resolve only os.LookupEnv(raw.Source.RTSPURLEnv), and hash canonical
|
||||
// non-secret source/model/event values as cfg-<sha256>.
|
||||
}
|
||||
```
|
||||
|
||||
Validate all required files and 64-hex model hash before monitoring starts. Return generic path/configuration errors; do not include the resolved URL in errors. Document the V2 field meanings and unchanged `FallEvent` fields in `docs/api.md`.
|
||||
|
||||
- [ ] **Step 4: Re-run focused and package tests**
|
||||
|
||||
Run: `Set-Location v2; go test ./internal/config -v`
|
||||
Expected: PASS, including missing environment, embedded URL, invalid hash and version-redaction cases.
|
||||
|
||||
- [ ] **Step 5: Commit the configuration slice**
|
||||
|
||||
```powershell
|
||||
git add v2/config.example.json v2/internal/config v2/assets/runtime-manifest.example.json docs/api.md
|
||||
git commit -m "feat(v2): add safe runtime configuration"
|
||||
```
|
||||
|
||||
### Task 2: FFprobe/FFmpeg source worker with reconnect and latest-frame semantics
|
||||
|
||||
**Files:**
|
||||
- Create: `v2/internal/source/stream.go`
|
||||
- Create: `v2/internal/source/stream_test.go`
|
||||
- Modify: `docs/04-architecture.md`
|
||||
|
||||
- [ ] **Step 1: Write deterministic source tests before subprocess integration**
|
||||
|
||||
```go
|
||||
func TestLatestFrameDropsStaleFrame(t *testing.T) {
|
||||
out := make(chan Frame, 1)
|
||||
publishLatest(out, Frame{Sequence: 1})
|
||||
publishLatest(out, Frame{Sequence: 2})
|
||||
if got := <-out; got.Sequence != 2 { t.Fatalf("got sequence %d", got.Sequence) }
|
||||
}
|
||||
|
||||
func TestBackoffIsBoundedAndCancellationStopsRetry(t *testing.T) {
|
||||
if got := retryDelay(0); got != time.Second { t.Fatal(got) }
|
||||
if got := retryDelay(99); got != 8*time.Second { t.Fatal(got) }
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Verify the tests fail**
|
||||
|
||||
Run: `Set-Location v2; go test ./internal/source -run 'Test(Latest|Backoff)' -v`
|
||||
Expected: FAIL because `internal/source` does not exist.
|
||||
|
||||
- [ ] **Step 3: Implement probe and worker ownership**
|
||||
|
||||
```go
|
||||
type Status string
|
||||
const (Connecting Status = "CONNECTING"; Connected Status = "CONNECTED"; Retrying Status = "RETRYING"; Stopped Status = "STOPPED"; Failed Status = "FAILED")
|
||||
type Frame struct { BGR []byte; Width, Height int; Timestamp time.Duration; Sequence uint64 }
|
||||
type Stream interface { Frames() <-chan Frame; Statuses() <-chan Update; Close() error }
|
||||
|
||||
func Start(ctx context.Context, cfg Config, commands CommandFactory) (Stream, error) {
|
||||
// ffprobe JSON obtains width/height/FPS once per connection; ffmpeg emits
|
||||
// bgr24 rawvideo. ReadFull exactly W*H*3 bytes, time-stamp with time.Since
|
||||
// (monotonic), publish only the newest frame, and retry 1/2/4/8 seconds.
|
||||
}
|
||||
```
|
||||
|
||||
Use `-rtsp_transport`, `-rw_timeout`, low-latency FFmpeg input flags when configured, and `-f rawvideo -pix_fmt bgr24 -` output. Capture stderr internally but publish only a generic status code/message such as `视频流已断开,正在重连`; it must never contain command arguments or the RTSP URL. Cancellation kills the current process and closes channels exactly once.
|
||||
|
||||
- [ ] **Step 4: Run source tests and a safe invalid-source smoke**
|
||||
|
||||
Run: `Set-Location v2; go test ./internal/source -v`
|
||||
Expected: PASS.
|
||||
Run: `go run ./cmd/silver-pose --config config.example.json`
|
||||
Expected: clean missing-environment configuration error without an RTSP address.
|
||||
|
||||
- [ ] **Step 5: Commit the source slice**
|
||||
|
||||
```powershell
|
||||
git add v2/internal/source docs/04-architecture.md
|
||||
git commit -m "feat(v2): add reconnecting FFmpeg stream source"
|
||||
```
|
||||
|
||||
### Task 3: Display overlay, event artifacts and exactly-once alert boundary
|
||||
|
||||
**Files:**
|
||||
- Create: `v2/internal/render/overlay.go`
|
||||
- Create: `v2/internal/render/overlay_test.go`
|
||||
- Create: `v2/internal/alert/dispatcher.go`
|
||||
- Create: `v2/internal/alert/dispatcher_test.go`
|
||||
- Modify: `docs/api.md`
|
||||
|
||||
- [ ] **Step 1: Write failing rendering and alert tests**
|
||||
|
||||
```go
|
||||
func TestRenderUsesRedBorderOnlyForConfirmed(t *testing.T) {
|
||||
normal := Render(grayBGR(), 2, 2, fall.FrameResult{})
|
||||
confirmed := Render(grayBGR(), 2, 2, fall.FrameResult{People: []fall.PersonAnalysis{{State: fall.Confirmed}}})
|
||||
if normal.RGBAAt(0, 0) == critical || confirmed.RGBAAt(0, 0) != critical { t.Fatal("critical border semantics broken") }
|
||||
}
|
||||
|
||||
func TestDispatchWritesOnePNGAndRedactedJSONL(t *testing.T) {
|
||||
d := NewDispatcher(t.TempDir(), "lobby", silentNotifier{})
|
||||
event := fall.Event{EventID:"FALL-run-000001", TrackID:"P-0001", ConfigVersion:"cfg-safe", State:fall.Confirmed}
|
||||
if got := d.Dispatch(event, image.NewRGBA(image.Rect(0,0,2,2))); !got.Written { t.Fatal(got) }
|
||||
if got := d.Dispatch(event, image.NewRGBA(image.Rect(0,0,2,2))); got.Written { t.Fatal("duplicate") }
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Verify tests fail**
|
||||
|
||||
Run: `Set-Location v2; go test ./internal/render ./internal/alert -v`
|
||||
Expected: FAIL because both packages are absent.
|
||||
|
||||
- [ ] **Step 3: Implement deterministic evidence outputs**
|
||||
|
||||
```go
|
||||
func Render(bgr []byte, width, height int, result fall.FrameResult) *image.RGBA
|
||||
type Record struct { Event fall.Event; ScreenshotPath, LogPath string; CreatedAtUTC time.Time; Written bool }
|
||||
func (d *Dispatcher) Dispatch(event fall.Event, image *image.RGBA) (Record, error)
|
||||
```
|
||||
|
||||
Draw a 17-point COCO skeleton, box and ASCII `track/state/time` label in the image. Normal/SUSPECT/RETRYING use green/amber/slate respectively; only `CONFIRMED` draws critical red skeleton and a six-pixel image border. Create `YYYYMMDD/FALL-*.png` and append one UTF-8 JSONL line with the V1-compatible public event fields plus source ID, UTC time and relative screenshot path. The dispatcher holds a seen-event-ID set before writing so retried UI messages cannot overwrite an image or replay an alert. Its notifier interface only reports a one-shot notification request; it does not own UI controls.
|
||||
|
||||
- [ ] **Step 4: Run focused tests**
|
||||
|
||||
Run: `Set-Location v2; go test ./internal/render ./internal/alert -v`
|
||||
Expected: PASS, including red-only-confirmed, PNG decodability, one JSONL line and no `rtsp`/credential substring.
|
||||
|
||||
- [ ] **Step 5: Commit the rendering and alert slice**
|
||||
|
||||
```powershell
|
||||
git add v2/internal/render v2/internal/alert docs/api.md
|
||||
git commit -m "feat(v2): add annotated evidence and alert dispatch"
|
||||
```
|
||||
|
||||
### Task 4: Background monitor composition and latest completed UI updates
|
||||
|
||||
**Files:**
|
||||
- Create: `v2/internal/monitor/monitor.go`
|
||||
- Create: `v2/internal/monitor/monitor_test.go`
|
||||
- Modify: `v2/internal/spike/ort.go` only if an interface adapter is needed
|
||||
|
||||
- [ ] **Step 1: Write failing orchestration tests using fakes**
|
||||
|
||||
```go
|
||||
func TestMonitorPublishesLatestCompletedFrameAndOneAlert(t *testing.T) {
|
||||
stream := newFakeStream(frameA, frameB)
|
||||
runtime := fakeRuntime{output: poseOutputForPersistentFall()}
|
||||
monitor := New(stream, runtime, fixedEngine(t), newFakeDispatcher())
|
||||
go monitor.Run(context.Background())
|
||||
got := eventually(t, monitor.Updates())
|
||||
if got.Image == nil || got.SourceStatus != source.Connected { t.Fatalf("bad update: %#v", got) }
|
||||
}
|
||||
|
||||
func TestMonitorDoesNotCreateFallEvidenceOnSourceFailure(t *testing.T) {
|
||||
monitor := New(newStatusOnlyStream(source.Retrying), fakeRuntime{}, fixedEngine(t), newFakeDispatcher())
|
||||
go monitor.Run(context.Background())
|
||||
if got := eventually(t, monitor.Updates()); len(got.Events) != 0 || got.SourceStatus != source.Retrying { t.Fatalf("bad update: %#v", got) }
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Verify the monitor tests fail**
|
||||
|
||||
Run: `Set-Location v2; go test ./internal/monitor -v`
|
||||
Expected: FAIL because `internal/monitor` does not exist.
|
||||
|
||||
- [ ] **Step 3: Implement the composition boundary**
|
||||
|
||||
```go
|
||||
type PoseRuntime interface { Run([]float32) ([]float32, error); Close() }
|
||||
type Update struct { Image *image.RGBA; Result fall.FrameResult; SourceStatus source.Status; SourceMessage string; Events []alert.Record }
|
||||
type Monitor struct { /* stream, pose runtime, engine, renderer, dispatcher, one-slot updates */ }
|
||||
|
||||
func (m *Monitor) Run(ctx context.Context) { /* source frame -> pose -> fall -> render -> dispatch -> publishLatest */ }
|
||||
```
|
||||
|
||||
Create/open one ONNX Runtime instance before consuming frames and close it only when monitoring stops. Treat Pose/preprocess errors as non-fall status updates and continue/reconnect according to source state; do not terminate the Walk message loop. Only completed rendered frames enter the one-slot `Updates` channel, so a slow UI never queues stale video frames.
|
||||
|
||||
- [ ] **Step 4: Run monitor, fall and full Go tests**
|
||||
|
||||
Run: `Set-Location v2; $env:CGO_ENABLED='1'; go test ./internal/monitor ./internal/fall ./...`
|
||||
Expected: PASS.
|
||||
|
||||
- [ ] **Step 5: Commit the monitor slice**
|
||||
|
||||
```powershell
|
||||
git add v2/internal/monitor v2/internal/spike/ort.go
|
||||
git commit -m "feat(v2): compose realtime monitor pipeline"
|
||||
```
|
||||
|
||||
### Task 5: Implement the accessible light Walk operator window
|
||||
|
||||
**Files:**
|
||||
- Create: `v2/internal/ui/window.go`
|
||||
- Create: `v2/internal/ui/window_test.go`
|
||||
- Modify: `v2/internal/ui/spec.go`
|
||||
- Modify: `v2/cmd/ui-spike/main.go`
|
||||
|
||||
- [ ] **Step 1: Write UI state-model tests without launching a native window**
|
||||
|
||||
```go
|
||||
func TestViewModelUsesCriticalOnlyForConfirmed(t *testing.T) {
|
||||
if NewViewModel().Apply(fall.Normal, source.Connected).Critical { t.Fatal("normal is critical") }
|
||||
if !NewViewModel().Apply(fall.Confirmed, source.Connected).Critical { t.Fatal("confirmed must be critical") }
|
||||
}
|
||||
|
||||
func TestSettingsNeverExposeResolvedRTSPURL(t *testing.T) {
|
||||
vm := NewViewModel()
|
||||
vm.SetSourceReady("SILVER_POSE_RTSP_URL", true)
|
||||
if strings.Contains(vm.SettingsSummary, "rtsp://") { t.Fatal(vm.SettingsSummary) }
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Verify the pure UI tests fail**
|
||||
|
||||
Run: `Set-Location v2; go test ./internal/ui -v`
|
||||
Expected: FAIL until the view-model additions are created.
|
||||
|
||||
- [ ] **Step 3: Implement the UI model and window**
|
||||
|
||||
```go
|
||||
type ViewModel struct { ConnectionText, StateText, EventText, SettingsSummary string; Critical bool }
|
||||
type Window struct { /* MainWindow, ImageView, labels, tabs, current bitmap and onStart/onStop callbacks */ }
|
||||
func NewWindow(config config.Config, commands Commands) (*Window, error)
|
||||
func (w *Window) Present(update monitor.Update)
|
||||
```
|
||||
|
||||
Use a 1120×720 default / 960×640 minimum window with a standard top `TabWidget` named “监控” and “设置”. Monitor tab: `ImageViewModeZoom`, source/status/event cards, a clearly labeled Start/Stop button and recent-event summary. Settings tab: read-only source environment-variable readiness, model hash prefix, runtime dependency readiness and the effective event values; do not present editable credentials or a resolved URL in T-304. Apply semantic brushes from the documented light palette (`#EAF1F8` base, white surfaces, blue focus/action); state text and labels always accompany color. Use standard controls so Tab/Shift+Tab/Enter/Space/Escape behavior is inherited. `Present` must use `MainWindow.Synchronize`, dispose the previous bitmap after `ImageView.SetImage`, and show exactly one modal `walk.MsgBox` with “我已知晓” semantics per dispatcher record. Call `win.MessageBeep` when the record reaches the UI; no normal, suspect or reconnect path may beep or show a critical dialog.
|
||||
|
||||
- [ ] **Step 4: Build the UI and perform the manual keyboard/sizing smoke**
|
||||
|
||||
Run: `Set-Location v2; $env:CGO_ENABLED='0'; go test ./internal/ui; go build ./cmd/ui-spike`
|
||||
Expected: PASS.
|
||||
Run: `go run ./cmd/ui-spike`
|
||||
Expected: light two-tab shell; verify focus traversal, Enter/Space activation, Escape closes the alert dialog, and no red element appears before a confirmed update.
|
||||
|
||||
- [ ] **Step 5: Commit the UI slice**
|
||||
|
||||
```powershell
|
||||
git add v2/internal/ui v2/cmd/ui-spike
|
||||
git commit -m "feat(v2): implement operator monitoring window"
|
||||
```
|
||||
|
||||
### Task 6: Production command, release script and operator runbook
|
||||
|
||||
**Files:**
|
||||
- Create: `v2/cmd/silver-pose/main.go`
|
||||
- Create: `v2/scripts/build-v2-release.ps1`
|
||||
- Create: `docs/v2-demo-runbook.md`
|
||||
- Modify: `docs/03-tech-stack.md`
|
||||
- Modify: `docs/04-architecture.md`
|
||||
- Modify: `docs/current-state.md`
|
||||
- Modify: `docs/06-tasks.md`
|
||||
- Modify: `progress.md`
|
||||
|
||||
- [ ] **Step 1: Write the command preflight test**
|
||||
|
||||
```go
|
||||
func TestValidateStartupRejectsMissingRuntimeWithoutLeakingSource(t *testing.T) {
|
||||
err := validateStartup(config.Config{Source: config.SourceConfig{URL: "rtsp://secret@example"}})
|
||||
if err == nil || strings.Contains(err.Error(), "secret") || strings.Contains(err.Error(), "rtsp://") { t.Fatal(err) }
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Verify it fails before adding the command package**
|
||||
|
||||
Run: `Set-Location v2; go test ./cmd/silver-pose -run TestValidateStartup -v`
|
||||
Expected: FAIL because the command package is absent.
|
||||
|
||||
- [ ] **Step 3: Implement preflight, lifecycle and release assembly**
|
||||
|
||||
```go
|
||||
func main() {
|
||||
cfg := mustLoadConfig(flagConfig)
|
||||
mustValidateStartup(cfg)
|
||||
window := mustNewWindow(cfg, commands)
|
||||
go consumeMonitorUpdates(window, monitor)
|
||||
window.Run()
|
||||
}
|
||||
```
|
||||
|
||||
`build-v2-release.ps1` must require `-OnnxPath`, `-OrtDllPath`, `-FfmpegPath`, `-FfprobePath` and `-OutputDirectory`; it must reject a non-empty target directory, build `silver-pose.exe` with CGO/MinGW, copy the four explicit binaries plus `config.example.json`, write `runtime-manifest.json` with SHA-256 and tool version output, and fail when a required input is missing. The runbook must document a local ignored `config.local.json`, setting the RTSP environment variable outside the repository, normal/reconnect/confirmed demonstrations, where event evidence is written, a 1–3 second stated target, and the fixed-view scope limitation.
|
||||
|
||||
- [ ] **Step 4: Run automated and release-folder smoke verification**
|
||||
|
||||
Run: `Set-Location v2; $env:CGO_ENABLED='1'; go test ./...; go build ./cmd/silver-pose`
|
||||
Expected: PASS.
|
||||
Run: `./scripts/build-v2-release.ps1 -OnnxPath <locked-onnx> -OrtDllPath <release-dll> -FfmpegPath <ffmpeg.exe> -FfprobePath <ffprobe.exe> -OutputDirectory <empty-temporary-directory>`
|
||||
Expected: `silver-pose.exe`, all runtime files, public config and hash manifest present; no RTSP URL, video or event artifact appears in the folder.
|
||||
|
||||
- [ ] **Step 5: Run the task acceptance sequence and update facts**
|
||||
|
||||
Run: `./init.ps1`
|
||||
Expected: V1 baseline passes.
|
||||
Run: `Set-Location v2; $env:CGO_ENABLED='1'; go test ./...; go build ./cmd/silver-pose`
|
||||
Expected: PASS.
|
||||
Run: launch the release executable with a local configuration and approved live RTSP stream.
|
||||
Expected: live frame and skeleton/ID; disconnect causes reconnect text without fall alert; one approved fall produces red overlay, one sound, one acknowledgment popup, one PNG and one JSONL line; normal activity does not create a confirmed event.
|
||||
|
||||
- [ ] **Step 6: Commit delivery evidence**
|
||||
|
||||
```powershell
|
||||
git add v2/cmd/silver-pose v2/scripts docs/03-tech-stack.md docs/04-architecture.md docs/current-state.md docs/06-tasks.md docs/v2-demo-runbook.md progress.md
|
||||
git commit -m "feat(v2): deliver realtime demo and release tooling"
|
||||
```
|
||||
|
||||
## Plan self-review
|
||||
|
||||
- Coverage: Tasks 1–2 implement secure RTSP configuration and reconnect; Tasks 3–4 add Pose/event-to-evidence flow; Task 5 provides the light, accessible operator UI and one-shot alert; Task 6 builds the release directory, documents operation and verifies the complete T-304 acceptance path.
|
||||
- Boundary checks: release binaries and local credentials/videos remain untracked; alerts are event-ID idempotent; red is exclusive to `CONFIRMED`; source failures never enter the fall engine; UI thread only observes completed updates.
|
||||
- Deliberate gap: actual camera/fall acceptance requires the local camera, credentials and safe on-site action. The application and release can be built/tested without those assets, but T-304 must remain `DOING` until the runbook's final live sequence is observed and recorded.
|
||||
Reference in New Issue
Block a user