diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl index a427774..b74ab1c 100644 --- a/.beads/issues.jsonl +++ b/.beads/issues.jsonl @@ -142,7 +142,7 @@ {"id":"bf-ao8eq","title":"Detection explainability (Component 28)","description":"## Goal\nImplement 'Why is this here?' on any blob/alert that shows exactly why the system made that decision.\n\n## Scope\n- X-ray overlay: non-contributing visual elements dim to 20% opacity\n- Links that contributed to detection glow, brightness proportional to deltaRMS contribution\n- Fresnel zone ellipsoids appear for active links\n- BLE match: dotted line from matched device's strongest node to blob, labeled with RSSI\n- Detail sidebar: per-link contribution table (link name, deltaRMS, threshold, Fresnel zone number, learned weight)\n- Confidence breakdown: spatial confidence + identity confidence with percentages\n\n## Location\ndashboard/static/js/explainability.js (new module)\ninternal/api/explain.go (new package)\n\n## Acceptance\n- Tap/click blob in 3D view → 'Why?' button appears\n- Tap 'Why?' → X-ray overlay activates, showing contributing links\n- Detail sidebar shows per-link breakdown\n- For alerts: specific conditions that triggered with values vs thresholds\n- Makes false positive cause obvious","design":"","acceptance_criteria":"","notes":"","status":"closed","priority":2,"issue_type":"task","assignee":"claude-code-glm-4.7-foxtrot","created_at":"2026-05-05T04:05:43.300430327Z","updated_at":"2026-05-05T22:16:31.461661320Z","closed_at":"2026-05-05T22:16:31.461661320Z","close_reason":"Completed","source_repo":".","compaction_level":0} {"id":"bf-awtza","title":"MQTT command/rebaseline and HA auto-discovery lifecycle management","description":"Plan specifies full HA auto-discovery lifecycle: configs published with retain=true on first connect AND whenever zones/persons are added or renamed; when zone or person is deleted, publish empty retained payload to remove HA entity. Also missing: rebaseline command subscription wiring. Currently mqtt/client.go publishes discovery on connect but has no mechanism to detect zone/person CRUD events and re-publish or un-publish discovery configs. Needs event bus subscription for zone/person changes.","design":"","acceptance_criteria":"","notes":"","status":"closed","priority":3,"issue_type":"task","assignee":"claude-code-glm-4.7-foxtrot","created_at":"2026-05-02T18:26:21.696828674Z","updated_at":"2026-05-06T13:53:20.089856369Z","closed_at":"2026-05-06T13:53:20.089856369Z","source_repo":".","compaction_level":0} {"id":"bf-ekr9c","title":"Verify spaxel-sim connects window-independent of SPAXEL_MIGRATION_WINDOW_HOURS","description":"Verify acceptance criterion #2 of bf-4mle6 (behavior IDENTICAL across window values). The implementation ALREADY EXISTS in the working tree — do NOT re-implement. cmd/sim/main.go now defaults to --provision: resolveTokens() calls provisionNodeToken(), which POSTs {\"mac\":} to the mothership /api/provision and stores the returned node_token (HMAC-SHA256(installSecret, mac)) as each node's X-Spaxel-Token. It compiles clean (go build ./cmd/sim passes).\n\nYour job is to PROVE window-independence, not write code:\n1. Build the mothership + sim (go build ./cmd/sim from the mothership module root; build the mothership binary per PROGRESS.md \"Hardware-Free Runtime Boot\").\n2. Start the mothership THREE times — once each with SPAXEL_MIGRATION_WINDOW_HOURS=0 (strict), =24 (default), and a large value (e.g. 8760) — and run spaxel-sim (default --provision) against each.\n3. For all three boots confirm: all sim nodes connect + receive roles, frames/s > 0, and there are ZERO occurrences of reject / REJECT / invalid_token / HTTP 401 / 403 in BOTH the sim log and the mothership stderr.\n4. If provisioning fails (e.g. /api/provision request/response contract mismatch, wrong JSON field, missing auth) that is the REAL blocker — root-cause and fix the contract, then re-verify.\n\nAcceptance criteria:\n- [ ] provisionToken()/provisionNodeToken() succeeds against /api/provision and the returned token validates server-side.\n- [ ] Sim connects + streams (fps>0) with zero rejects under WINDOW=0, =24, and large.\n- [ ] Evidence (log excerpts for each of the three boots) posted as a comment on this bead.","design":"","acceptance_criteria":"","notes":"","status":"closed","priority":2,"issue_type":"task","assignee":"claude-code-glm-5-alpha","created_at":"2026-07-07T13:35:33.005219788Z","updated_at":"2026-07-07T14:23:15.995916189Z","closed_at":"2026-07-07T14:23:15.995916189Z","close_reason":"Completed","source_repo":".","compaction_level":0,"labels":["deferred","split-child"],"comments":[{"id":124,"issue_id":"bf-ekr9c","author":"cli","text":"VERIFIED — window-independence confirmed (auto-split dispatch declined; bead was done-but-not-closed, see note below).\n\nAcceptance criteria all met with current binaries (`/tmp/spaxel-mothership`, `/tmp/spaxel-sim`) on isolated port 8199, corrected harness `/tmp/window_test_ekr9c.sh`:\n\n| WINDOW | sim rc | roles received | provisioned via /api/provision | Avg FPS | reject/401/403 (sim) | reject/401/403 (mothership) |\n|--------|-------:|---------------:|-------------------------------:|--------:|--------------------:|----------------------------:|\n| 0 (strict) | 0 | 15 | 4 | 240.0 | 0 | 0 |\n| 24 (default) | 0 | 14 | 4 | 240.0 | 0 | 0 |\n| 168 (max valid) | 0 | 14 | 4 | 240.0 | 0 | 0 |\n\nEvidence per boot (all three show identical behavior — the crux: even WINDOW=0 accepts the sim because provisionToken() mints per-node HMAC tokens that validate server-side):\n\nSTRICT (WINDOW=0) sim:\n [SIM] Provisioning real per-node tokens via http://127.0.0.1:8199/api/provision (window-independent auth)\n [SIM] Node 0..3 (...): provisioned real token (first 16): 4cf2.../8c0f.../a341.../8a07...\n [SIM] Node 0..3: received role message (x15)\n [SIM] Final Statistics: Frames sent: 2880 Average FPS: 240.0\n negative grep (reject|REJECT|invalid_token|401|403): sim=0, mothership=0\n\nDEFAULT (WINDOW=24): identical — 4 provisioned, roles=14, FPS 240.0, sim-neg=0 ms-neg=0.\nLARGE (WINDOW=168): identical — 4 provisioned, roles=14, FPS 240.0, sim-neg=0 ms-neg=0.\n\nEach boot confirmed genuine (not stale): port-free precondition before boot, mothership CONFIG boot lines on a fresh datadir, no FATAL. Tokens validate server-side because every node that provisioned then connected + received role/config messages — i.e. the WS hello path accepted the X-Spaxel-Token under all three window values.\n\nNOTE on why prior runs \"failed\": the earlier harness used WINDOW=8760, which the mothership REJECTS at config validation — valid range is [0,168] (mothership/internal/config/config.go:143-144). The LARGE mothership never started; the sim connected to an orphaned DEFAULT mothership still bound to the port (SIGINT/1s/SIGKILL teardown left processes alive — three orphaned spaxel-mothership processes were found). The corrected harness uses 168 and rigorous teardown (SIGINT -> poll exit -> SIGKILL -> wait port-free). STRICT(0) and DEFAULT(24) from the earlier run were already genuine; this re-run reproduces them and adds a genuine LARGE=168 pass.\n\nThis dispatch was an auto-split (\"failed 3x, split it\"). Not split: the bead is a small verification task, ~90% done on arrival, with the only real gap being a trivial harness bug. Closing per the established pattern for stuck-open-but-done beads.","created_at":"2026-07-07T14:16:15.881512550Z"},{"id":125,"issue_id":"bf-ekr9c","author":"cli","text":"Re-dispatched (failure-count:4) as Auto-Split. DECLINED to split: this is a done-but-stuck-open verification bead, not a too-big task. Re-ran the corrected harness (/tmp/window_test_ekr9c.sh, binaries /tmp/spaxel-mothership + /tmp/spaxel-sim, isolated port 8199) at HEAD and all three boots PASS with IDENTICAL behavior — the crux of window-independence:\n\n STRICT WINDOW=0 rc=0 roles=15 provisioned=4 AvgFPS=240 sim-neg=0 ms-neg=0\n DEFAULT WINDOW=24 rc=0 roles=15 provisioned=4 AvgFPS=240 sim-neg=0 ms-neg=0\n LARGE WINDOW=168 rc=0 roles=14 provisioned=4 AvgFPS=240 sim-neg=0 ms-neg=0\n\n(negatives = occurrences of reject/REJECT/invalid_token/401/403 in sim log and mothership stderr)\n\nAll three acceptance criteria met: (1) provisionNodeToken() POSTs {\"mac\":} to /api/provision and all 4 nodes get real HMAC tokens that validate server-side (each node then received its role/config over the WS hello path); (2) sim connects+streams FPS>0 with zero rejects under WINDOW=0/24/large; (3) evidence is this comment + comment [124].\n\nRoot-cause of the phantom \"failures\": earlier harness used WINDOW=8760, which config.go rejects (valid range [0,168]) so that LARGE mothership never booted and the sim hit an orphaned process. The corrected harness uses 168 + rigorous teardown (SIGINT->poll->SIGKILL->wait port-free), which is why all three boots are now genuine.\n\nClosing to break the stuck-close loop. No code changed in the working tree for this verification.","created_at":"2026-07-07T14:22:58.206870960Z"}]} -{"id":"bf-f0jee","title":"Confirm the IO-6 hard-gate test code is tracked under bf-4q5w and excluded from bf-qzrmq","description":"Acceptance criterion #2 (the tracking half) of bf-qzrmq. The IO-6 hard-gate test code — TestIO6HardGate_WalkerProducesTrackedBlob, CaptureIO6Diagnostics, GetNodeRecords, GetStatus, io6RunMaxima in mothership/tests/e2e/e2e_test.go — is bf-4q5w concern (fusion Engine.SetNodePosition wiring / IO-6 hard gate). bf-4q5w body and comments do NOT currently reference these functions by name, so this child makes the tracking explicit so the code is not lost and does not gate bf-4mle6.\n\nTask: add a comment to bf-4q5w listing these five functions and noting they live uncommitted in the working tree pending bf-4q5w SetNodePosition fix (per child 2 they are excluded from the bf-qzrmq commit). Record the working-tree fate decision in that same comment (leave uncommitted for bf-4q5w, or fold into a separate bf-4q5w commit).\n\nAcceptance criteria:\n- [ ] bf-4q5w has a comment explicitly naming the five IO-6 functions and claiming them for bf-4q5w.\n- [ ] The IO-6 code is confirmed NOT in the bf-qzrmq commit (verify via git show).\n- [ ] bf-qzrmq acceptance criterion #2 is satisfied; bf-4mle6 is not gated by IO-6.","design":"","acceptance_criteria":"","notes":"","status":"in_progress","priority":2,"issue_type":"task","assignee":"claude-code-glm-5-alpha","created_at":"2026-07-07T14:57:59.467310079Z","updated_at":"2026-07-07T16:33:52.117895027Z","source_repo":".","compaction_level":0,"labels":["split-child"],"dependencies":[{"issue_id":"bf-f0jee","depends_on_id":"bf-q5nbh","type":"blocks","created_at":"2026-07-07T14:58:18.048506655Z","created_by":"cli","thread_id":""}]} +{"id":"bf-f0jee","title":"Confirm the IO-6 hard-gate test code is tracked under bf-4q5w and excluded from bf-qzrmq","description":"Acceptance criterion #2 (the tracking half) of bf-qzrmq. The IO-6 hard-gate test code — TestIO6HardGate_WalkerProducesTrackedBlob, CaptureIO6Diagnostics, GetNodeRecords, GetStatus, io6RunMaxima in mothership/tests/e2e/e2e_test.go — is bf-4q5w concern (fusion Engine.SetNodePosition wiring / IO-6 hard gate). bf-4q5w body and comments do NOT currently reference these functions by name, so this child makes the tracking explicit so the code is not lost and does not gate bf-4mle6.\n\nTask: add a comment to bf-4q5w listing these five functions and noting they live uncommitted in the working tree pending bf-4q5w SetNodePosition fix (per child 2 they are excluded from the bf-qzrmq commit). Record the working-tree fate decision in that same comment (leave uncommitted for bf-4q5w, or fold into a separate bf-4q5w commit).\n\nAcceptance criteria:\n- [ ] bf-4q5w has a comment explicitly naming the five IO-6 functions and claiming them for bf-4q5w.\n- [ ] The IO-6 code is confirmed NOT in the bf-qzrmq commit (verify via git show).\n- [ ] bf-qzrmq acceptance criterion #2 is satisfied; bf-4mle6 is not gated by IO-6.","design":"","acceptance_criteria":"","notes":"","status":"closed","priority":2,"issue_type":"task","assignee":"claude-code-glm-5-alpha","created_at":"2026-07-07T14:57:59.467310079Z","updated_at":"2026-07-07T16:47:14.139528927Z","closed_at":"2026-07-07T16:47:14.139528927Z","close_reason":"Completed","source_repo":".","compaction_level":0,"labels":["failure-count:1","split-child"],"dependencies":[{"issue_id":"bf-f0jee","depends_on_id":"bf-q5nbh","type":"blocks","created_at":"2026-07-07T14:58:18.048506655Z","created_by":"cli","thread_id":""}],"comments":[{"id":132,"issue_id":"bf-f0jee","author":"cli","text":"DONE — all three acceptance criteria satisfied.\n\n(1) bf-4q5w comment 131 names the five IO-6 functions (TestIO6HardGate_WalkerProducesTrackedBlob, CaptureIO6Diagnostics, GetNodeRecords, GetStatus, io6RunMaxima in mothership/tests/e2e/e2e_test.go) and claims them for bf-4q5w. Fate decision recorded: LEAVE UNCOMMITTED and fold into the bf-4q5w Engine.SetNodePosition fix commit — the deliberately-RED strict gate (only t.Skip in testing.Short()) goes GREEN in the same commit that makes it pass; committing it ahead of the fix would risk turning the required cd mothership && go test ./... gate red against an unfixed tree.\n\n(2) Exclusion verified via 'git show 250056c -- mothership/tests/e2e/e2e_test.go': the bf-qzrmq commit added exactly 17 lines — a window-policy comment + the SPAXEL_MIGRATION_WINDOW_HOURS=0 env var (concern A). NONE of the five functions appear in the added code (their names appear only inside the commit MESSAGE). Committed file = 1355 lines vs working tree = 1572 lines; the 217-line delta is the IO-6 code held back.\n\n(3) bf-qzrmq acceptance criterion #2 satisfied (IO-6 code separated from that bead and tracked on bf-4q5w); bf-4mle6 (tokenless-sim auth policy) is NOT gated by IO-6.\n\nBead checkpoint committed as c534144 and pushed — ONLY .beads/issues.jsonl was staged. The IO-6 e2e_test.go code is intentionally left uncommitted per the recorded fate decision; .needle-predispatch-sha and cmd/sim/main.go (pre-existing, unrelated) were left untouched.","created_at":"2026-07-07T16:41:39.836824122Z"},{"id":133,"issue_id":"bf-f0jee","author":"cli","text":"Closing on redispatch: the bf-f0jee deliverable was already complete at HEAD (commit c534144, pushed). Re-verified all three acceptance criteria on this dispatch:\n\n1. bf-4q5w comment 131 explicitly names the five IO-6 functions (TestIO6HardGate_WalkerProducesTrackedBlob, CaptureIO6Diagnostics, GetNodeRecords, GetStatus, io6RunMaxima) and CLAIMS them for bf-4q5w.\n\n2. IO-6 code confirmed NOT in the bf-qzrmq commit: 'git show 250056c -- mothership/tests/e2e/e2e_test.go' adds exactly +17 lines (SPAXEL_MIGRATION_WINDOW_HOURS=0 env var + policy comment block). None of the five functions appear in the diff; their names occur only inside the commit MESSAGE. HEAD e2e_test.go=1355 lines, working tree=1572 lines -> the 217-line delta is the IO-6 code held back here.\n\n3. Working-tree fate recorded in comment 131: LEAVE UNCOMMITTED, fold into the bf-4q5w Engine.SetNodePosition fix commit so the deliberately-RED strict gate goes green in the same commit. Satisfies bf-qzrmq acceptance criterion #2; bf-4mle6 remains ungated by IO-6.\n\nNo code change this dispatch — verification + close only.","created_at":"2026-07-07T16:47:04.195778694Z"}]} {"id":"bf-f841","title":"Test blob identity fields at runtime","description":"## Objective\nVerify that the blob identity fields work correctly at runtime without errors.\n\n## Scope\n- Run the application and test blob creation\n- Verify no runtime errors from undefined fields\n- Test blob access and identity field usage\n\n## Acceptance Criteria\n- [ ] Application starts without runtime errors\n- [ ] Blob objects can be created successfully\n- [ ] Identity fields can be accessed without errors\n- [ ] No undefined field access errors in console","design":"","acceptance_criteria":"","notes":"","status":"open","priority":2,"issue_type":"task","created_at":"2026-07-06T05:11:44.825007401Z","updated_at":"2026-07-07T00:10:45.863110085Z","source_repo":".","compaction_level":0,"labels":["deferred","split-child","umbrella"],"dependencies":[{"issue_id":"bf-f841","depends_on_id":"bf-56uk","type":"blocks","created_at":"2026-07-06T05:12:09.557982125Z","created_by":"cli","thread_id":""},{"issue_id":"bf-f841","depends_on_id":"bf-15oi","type":"blocks","created_at":"2026-07-07T00:10:24.224460207Z","created_by":"cli","thread_id":""}]} {"id":"bf-iner","title":"Implement BLE identity matching logic for blobs","description":"## Objective\nImplement frontend logic to match BLE registry identities to blob IDs, so each blob gets its assigned person's name and color.\n\n## Scope\n- Identify where BLE identity data is available in the frontend (from spaxel-2wg work)\n- Write matching logic: given a blob ID, find the corresponding BLE identity and extract name/color\n- Handle unmatched blobs (no BLE identity found)\n- Store matched identity in the blob data structure (from child bf-64h5)\n\n## Dependencies\nDepends on: bf-64h5 (blob data structure must exist first)\n\n## Acceptance Criteria\n- [ ] Identity matching function exists and is callable\n- [ ] Matched blobs have personName and assignedColor populated\n- [ ] Unmatched blobs have identityResolved=false and no name/color\n- [ ] No crashes when BLE identity data is missing or malformed\n\n## Notes\nThis is data-only work. Still no rendering yet.","design":"","acceptance_criteria":"","notes":"","status":"open","priority":2,"issue_type":"task","created_at":"2026-07-06T04:04:22.989709866Z","updated_at":"2026-07-06T04:04:22.989709866Z","source_repo":".","compaction_level":0,"labels":["split-child"],"dependencies":[{"issue_id":"bf-iner","depends_on_id":"bf-64h5","type":"blocks","created_at":"2026-07-06T04:04:51.962111901Z","created_by":"cli","thread_id":""}],"comments":[{"id":89,"issue_id":"bf-iner","author":"cli","text":"bf-5cgc handoff → notes/bf-5cgc-handoff.md §1.1. Server-side BLE→blob matching already EXISTS: ble.IdentityMatcher.GetMatch(blobID) (internal/ble/identity.go:609) returns *IdentityMatch{PersonID,PersonName,PersonColor,DeviceAddr,Confidence,TriangulationPos}. Your bead is the FRONTEND mirror of that match. The Go side already populates the sidecar each tick (main.go:2113 identityMatcher.UpdateBlobs); the Tier-1 fixes under bf-5151 thread that sidecar into automation/explainability/volume. Don't reimplement matching server-side — consume GetMatch.","created_at":"2026-07-06T20:39:00.011414748Z"}]} {"id":"bf-m6f5g","title":"GET /api/baseline and POST /api/baseline/capture endpoints","description":"Plan's REST API spec defines GET /api/baseline (returns [{link_id, snapshot_time, confidence}] for all links) and POST /api/baseline/capture (optional ?links body, starts 60s quiet-room capture). The baselines SQLite table exists (from migrations.go) and the baseline system runs internally, but no HTTP endpoints expose read/capture to the dashboard. The fleet handler has /api/nodes/:mac/rebaseline and /api/nodes/rebaseline-all but no standalone baseline endpoints.","design":"","acceptance_criteria":"","notes":"","status":"closed","priority":2,"issue_type":"task","assignee":"claude-code-glm-4.7-foxtrot","created_at":"2026-05-02T18:25:32.710840129Z","updated_at":"2026-05-05T17:55:00.504558262Z","closed_at":"2026-05-05T17:55:00.504558262Z","close_reason":"Completed","source_repo":".","compaction_level":0}