From 1679f00640e7ec027b598a0b3a3bc56b3fa4f651 Mon Sep 17 00:00:00 2001 From: jedarden Date: Tue, 7 Jul 2026 07:51:10 -0400 Subject: [PATCH] =?UTF-8?q?docs(bf-34lwt):=20re-dispatch=20verification=20?= =?UTF-8?q?=E2=80=94=206=20facts=20re-confirmed=20at=20HEAD=209df77e3,=20c?= =?UTF-8?q?lose?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Re-dispatch of an already-complete investigation. The authoritative write-up (docs/notes/token-reject-root-cause.md, 5c6a692) and bf-4iewr comments [110]/[111] are accurate; re-verified all six citations against current source with zero line drift and posted verification+correction comment [118] on bf-4iewr (corrects comment [115]'s scenario.go claim — both sim copies set the X-Spaxel-Token header). Closed bf-34lwt; checkpointing the comment+close to JSONL so it stops re-dispatching. Co-Authored-By: Claude --- .beads/issues.jsonl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl index 056d6d9..4bb4bf3 100644 --- a/.beads/issues.jsonl +++ b/.beads/issues.jsonl @@ -51,7 +51,7 @@ {"id":"bf-2y6u","title":"Component 5: Self-healing fleet — before/after coverage overlay in dashboard","description":"The fleet manager self-healing logic (fleet/manager.go selfHeal()) correctly re-optimizes roles and pushes new config to nodes when a node goes offline. However the dashboard visualization described in plan.md Component 12 is partially missing.\n\nWhat the plan requires:\n- Dashboard shows a before/after coverage comparison overlay when self-healing fires: Node kitchen-ceiling went offline. Coverage in kitchen corridor degraded from excellent to fair. 4 links lost, 8 remaining.\n- When node reconnects: Node kitchen-ceiling back online. Full coverage restored.\n- Affected areas highlighted in red in the 3D view (not just the node changing to red status dot)\n\nWhat exists:\n- Node status color change (green/yellow/red) on disconnect\n- selfHeal() re-optimizes roles and pushes config\n- Timeline events for node online/offline\n\nWhat is missing:\n- Before/after GDOP comparison when self-heal fires: compute coverage with N nodes vs N-1 nodes, diff\n- Overlay in 3D view highlighting zones with degraded coverage (ground plane zones turn amber/red)\n- WebSocket message type for coverage-degradation event with affected-zones payload\n- Frontend handling in viz3d.js to show the coverage degradation overlay and dismiss after recovery\n\nThis is distinct from the GDOP overlay in setup mode — it fires automatically on node loss, not on manual placement.","design":"","acceptance_criteria":"","notes":"","status":"closed","priority":3,"issue_type":"task","assignee":"claude-code-glm-4.7-alpha","created_at":"2026-05-26T04:04:17.405979993Z","updated_at":"2026-07-06T06:29:20.716432012Z","closed_at":"2026-07-06T06:29:20.716432012Z","close_reason":"Completed","source_repo":".","compaction_level":0} {"id":"bf-31bp","title":"Firmware host-based unit tests: nvs/csi/serial_prov via idf.py test --target linux","description":"Testing Strategy requires 3 firmware modules tested via idf.py test --target linux (no hardware needed): (1) nvs schema migration test, (2) csi binary frame serialization roundtrip, (3) serial_prov JSON parser fuzz. No bead (open or closed) covers this. Implement the host test targets in the ESP-IDF component CMakeLists and wire them into CI.","design":"","acceptance_criteria":"","notes":"","status":"closed","priority":1,"issue_type":"task","assignee":"claude-code-glm-5-alpha","created_at":"2026-05-26T22:14:42.778447327Z","updated_at":"2026-07-03T17:30:32.207344691Z","closed_at":"2026-07-03T17:30:32.207344691Z","close_reason":"Completed","source_repo":".","compaction_level":0,"comments":[{"id":43,"issue_id":"bf-31bp","author":"cli","text":"Host-test intent satisfied by the committed gcc harness under firmware/test/ (build-out of decision spike bf-21t), NOT idf.py test --target linux. The idf.py --target linux mechanism named in this bead was explicitly REJECTED (decision record docs/notes/firmware-host-test-approach.md): firmware/main builds as ONE idf_component_register whose REQUIRES names esp_wifi/bt/driver -- three components with no host build -- so the whole component is unhostable; even nvs_migration.c (hostable in isolation) is blocked because main is one component. Building the idf.py path would require risky #ifdef-guarding of production firmware and create a second test mechanism (the decision record warns against exactly this). The gcc harness delivers exactly what the Testing Strategy requires, hardware-free: (1) nvs schema migration test_nvs_migration.c (fresh-install v1, no-downgrade guard, forward-migration dispatch, v1->v2 rename+default -- 8 tests); (2) csi binary frame serialization roundtrip test_csi_frame.c (24-byte header roundtrip, little-endian timestamp, signed-RSSI reinterpretation, I/Q payload, n_sub=0 probe, ingestion-side validation -- 7 tests); (3) serial_prov JSON parser fuzz test_serial_prov.c (bounded recursive-descent decoder fuzzed across random byte streams + deep-nesting cap, valid/invalid/minimal payloads -- 12 tests incl. fuzz passes). Result: 29 passed, 0 failed of 29. CI gate: Dockerfile 'RUN make -C test test' (plain gcc, no IDF toolchain; runs before the ESP-IDF build, propagates non-zero exit). Cleanup this bead: gitignored firmware/test_apps/ -- leftover CMake/ninja build output from an abandoned idf.py linux experiment (no source files; root-owned Docker artifacts so left in place, but now gitignored so it cannot be committed). Verification: make -C firmware/test test -> 29/29; cd mothership && go test ./... -> PASS; go vet ./... -> clean. Pushed 4b0eaba..fbb86fb.","created_at":"2026-07-03T17:29:35.954043239Z"}]} {"id":"bf-33za","title":"Component 33: Interactive Onboarding — Teach by Doing (5-step CSI physics wizard)","description":"The existing onboard.js has a basic 3-phase calibration wizard (walk/still/walk_through) but is missing the full Component 33 spec from plan.md.\n\nWhat exists: 3-phase calibration (walk 30s, still 10s, walk-through 15s), live CSI waveform canvas, post-calibration card.\n\nWhat is missing — 5 teaching steps with specific narration:\n\nStep 1 (walk 30s): Copy reads I am listening to your WiFi signal. Walk across the room / See that? Your body just changed the WiFi signal — that is how I detect you. Amplitude spikes highlighted in real-time.\n\nStep 2 (still 10s): A green baseline line fades in on chart. Copy reads This is your rooms baseline — the signal when nothing is moving. Any change from this means someone is here. Baseline captured automatically here, not via separate calibrate trigger.\n\nStep 3 (walk through detection zone 15s): Fresnel zone ellipsoid between node and router lights up as translucent green in 3D scene. Copy reads Walk between your node and the router — through the green zone / That is the Fresnel zone — I am most sensitive along this path. Zone pulses brighter as user crosses.\n\nStep 4 (let me find you 15s): Copy reads Walk somewhere and stop. I will try to locate you. Humanoid figure appears at estimated position with dotted accuracy radius. Copy reads Found you! I estimate you are about here. My accuracy is plus-or-minus 1 meter with this setup.\n\nStep 5 (place your node — interactive): Coverage painting activates on ground plane inside wizard. Copy reads Now drag your node to where it actually is in the room. Watch the green coverage change. After placement shows coverage score percentage. Copy reads Nice! Your coverage score is N percent.\n\nKey constraint: zero jargon — CSI, Fresnel, deltaRMS must never appear in wizard text.\n\nImplementation: Steps 3-4 require viz3d.js integration; step 5 requires placement.js GDOP overlay inside wizard; step 4 polls GET /api/blobs for humanoid placement.","design":"","acceptance_criteria":"","notes":"","status":"closed","priority":2,"issue_type":"task","assignee":"claude-code-glm-4.7-alpha","created_at":"2026-05-26T04:03:50.425961066Z","updated_at":"2026-07-06T03:01:11.271309877Z","closed_at":"2026-07-06T03:01:11.271309877Z","close_reason":"Completed","source_repo":".","compaction_level":0} -{"id":"bf-34lwt","title":"Investigate and document the REJECT/token root cause in the WS hello path","description":"First link of the bf-4iewr split. Write the definitive root-cause for the tokenless-sim REJECT failure mode so the remaining children have a precise target. The bead is currently PASSING-BY-ACCIDENT (no reject in a default boot) — this child explains exactly why, and exactly when it breaks.\n\nConfirm and document each of the following against the current source (cite file:line):\n\n## Scope\n1. Validator is wired UNCONDITIONALLY: `cmd/mothership/main.go:4494` calls `ingestSrv.SetTokenValidator(provSrv.ValidateToken)` — there is no config/build gate, so \"validator configured?\" is always YES.\n2. The validator reads `hello.Token` only: `internal/ingestion/server.go:513` (`tokenOK := hello.Token != \"\" && validator(hello.MAC, hello.Token)`).\n3. The sim sends its provisioned token ONLY as the `X-Spaxel-Token` HTTP header (`cmd/sim/main.go:633`, also `cmd/sim/scenario.go:318`) and does NOT include a `token` field in the hello JSON body (hello map at cmd/sim/main.go ~640).\n4. The mothership NEVER reads that header — `grep -rn 'X-Spaxel-Token\\|Header.Get' cmd/mothership/ internal/` returns no WS-path hit. Consequence: `hello.Token` is always \"\" for sim nodes → `tokenOK` always false → the \"supply valid tokens\" option in the parent bead is currently NON-FUNCTIONAL.\n5. The only thing accepting sim nodes is the migration window: `internal/ingestion/server.go:515`. MigrationWindowHours DEFAULTS TO 24 (`internal/config/config.go:139`), so a fresh boot opens a 24h window → sim nodes accepted as Unpaired, no reject (matches PROGRESS.md bf-3hji).\n6. Therefore REJECT fires ONLY when the window is closed: `SPAXEL_MIGRATION_WINDOW_HOURS=0` (deadline never set -> IsZero() -> reject branch), OR uptime > 24h. In those cases every sim node logs \"missing token\" -> sendReject(conn, \"invalid_token\") -> disconnect.\n\n## Acceptance Criteria\n- [ ] A written root-cause finding posted as a comment on bf-4iewr, confirming points 1-6 with file:line citations.\n- [ ] Explicit statement that current \"no reject\" is a 24h-window MASK, not a real fix.\n- [ ] Explicit statement that the token-supply path is currently dead (header ignored, hello.Token empty).","design":"","acceptance_criteria":"","notes":"","status":"open","priority":2,"issue_type":"task","created_at":"2026-07-07T09:48:38.581052724Z","updated_at":"2026-07-07T10:25:45.731335737Z","source_repo":".","compaction_level":0,"labels":["deferred","split-child","umbrella"],"dependencies":[{"issue_id":"bf-34lwt","depends_on_id":"bf-4th1x","type":"blocks","created_at":"2026-07-07T10:24:42.546648134Z","created_by":"cli","thread_id":""}]} +{"id":"bf-34lwt","title":"Investigate and document the REJECT/token root cause in the WS hello path","description":"First link of the bf-4iewr split. Write the definitive root-cause for the tokenless-sim REJECT failure mode so the remaining children have a precise target. The bead is currently PASSING-BY-ACCIDENT (no reject in a default boot) — this child explains exactly why, and exactly when it breaks.\n\nConfirm and document each of the following against the current source (cite file:line):\n\n## Scope\n1. Validator is wired UNCONDITIONALLY: `cmd/mothership/main.go:4494` calls `ingestSrv.SetTokenValidator(provSrv.ValidateToken)` — there is no config/build gate, so \"validator configured?\" is always YES.\n2. The validator reads `hello.Token` only: `internal/ingestion/server.go:513` (`tokenOK := hello.Token != \"\" && validator(hello.MAC, hello.Token)`).\n3. The sim sends its provisioned token ONLY as the `X-Spaxel-Token` HTTP header (`cmd/sim/main.go:633`, also `cmd/sim/scenario.go:318`) and does NOT include a `token` field in the hello JSON body (hello map at cmd/sim/main.go ~640).\n4. The mothership NEVER reads that header — `grep -rn 'X-Spaxel-Token\\|Header.Get' cmd/mothership/ internal/` returns no WS-path hit. Consequence: `hello.Token` is always \"\" for sim nodes → `tokenOK` always false → the \"supply valid tokens\" option in the parent bead is currently NON-FUNCTIONAL.\n5. The only thing accepting sim nodes is the migration window: `internal/ingestion/server.go:515`. MigrationWindowHours DEFAULTS TO 24 (`internal/config/config.go:139`), so a fresh boot opens a 24h window → sim nodes accepted as Unpaired, no reject (matches PROGRESS.md bf-3hji).\n6. Therefore REJECT fires ONLY when the window is closed: `SPAXEL_MIGRATION_WINDOW_HOURS=0` (deadline never set -> IsZero() -> reject branch), OR uptime > 24h. In those cases every sim node logs \"missing token\" -> sendReject(conn, \"invalid_token\") -> disconnect.\n\n## Acceptance Criteria\n- [ ] A written root-cause finding posted as a comment on bf-4iewr, confirming points 1-6 with file:line citations.\n- [ ] Explicit statement that current \"no reject\" is a 24h-window MASK, not a real fix.\n- [ ] Explicit statement that the token-supply path is currently dead (header ignored, hello.Token empty).","design":"","acceptance_criteria":"","notes":"","status":"closed","priority":2,"issue_type":"task","assignee":"claude-code-glm-5-alpha","created_at":"2026-07-07T09:48:38.581052724Z","updated_at":"2026-07-07T11:49:23.731951460Z","closed_at":"2026-07-07T11:49:23.731951460Z","close_reason":"Completed","source_repo":".","compaction_level":0,"labels":["deferred","split-child","umbrella"],"dependencies":[{"issue_id":"bf-34lwt","depends_on_id":"bf-4th1x","type":"blocks","created_at":"2026-07-07T10:24:42.546648134Z","created_by":"cli","thread_id":""}]} {"id":"bf-3a2py","title":"Acceptance scenario integration tests (AS-1 through AS-6)","description":"## Goal\nImplement the 6 acceptance scenarios from plan §Acceptance Scenarios as verifiable integration tests in test/acceptance/.\n\n## Scenarios to implement\n\nAS-1: First-time setup in under 5 minutes\n - Start fresh mothership container, open /api/auth/setup, set PIN\n - Run spaxel-sim --nodes 1 (simulates provisioned node)\n - Assert: node appears in /api/nodes within 30s\n\nAS-2: Person detected while walking\n - spaxel-sim --nodes 2 --walkers 1 --duration 60s\n - Poll /api/blobs every second\n - Assert: blob count > 0 for >80% of the run\n\nAS-3: Fall alert fires correctly\n - spaxel-sim with a walker that drops Z rapidly (spike downward velocity, then stays at Z<0.5)\n - Assert: events table contains fall_alert within 15s of trigger\n - Assert: webhook endpoint (test HTTP server) receives POST\n\nAS-4: BLE identity resolves to person name\n - Register a BLE device as 'Alice' via POST /api/ble/devices\n - spaxel-sim --ble (sends BLE reports for that address alongside blobs)\n - Assert: /api/blobs returns at least one blob with person='Alice'\n\nAS-5: OTA update succeeds / rollback on bad firmware\n - Already partially covered by existing OTA rollback integration test\n - Extend to verify the VERIFIED badge path (valid firmware + node reconnects with new version)\n\nAS-6: Replay shows recorded history\n - Run 60s of sim data\n - POST /api/replay/start with a 30s window\n - Assert: replay blobs are returned via WebSocket with replay:true flag\n\n## Location\ntest/acceptance/*.go — one file per scenario, parallel to test/integration/\n\n## Acceptance\nAll 6 scenarios pass in the Argo CI workflow using spaxel-sim as the test harness","design":"","acceptance_criteria":"","notes":"","status":"closed","priority":2,"issue_type":"test","assignee":"claude-code-glm-4.7-golf","created_at":"2026-05-02T12:09:24.898852471Z","updated_at":"2026-05-05T11:39:18.097311991Z","closed_at":"2026-05-05T11:39:18.097311991Z","close_reason":"Implemented all 6 acceptance scenarios as verifiable integration tests in test/acceptance/\n\nAS-1: First-time setup in under 5 minutes\n- Fresh mothership container starts successfully\n- PIN setup works via /api/auth/setup\n- spaxel-sim --nodes 1 connects and appears in /api/nodes within 30s\n\nAS-2: Person detected while walking\n- spaxel-sim --nodes 2 --walkers 1 runs for 60 seconds\n- GET /api/blobs returns at least 1 blob during walk\n- Detection ratio > 80% of run duration\n\nAS-3: Fall alert fires correctly\n- spaxel-sim with fall scenario triggers rapid Z descent\n- Fall alert appears in /api/events within 30 seconds\n- Webhook endpoint receives POST with alert payload\n\nAS-4: BLE identity resolves to person name\n- BLE device registered via POST /api/ble/devices\n- spaxel-sim --ble sends BLE advertisements\n- Blob appears with person='Alice' within 15 seconds\n\nAS-5: OTA update succeeds / rollback on bad firmware\n- spaxel-sim --scenario ota simulates successful OTA\n- Node firmware version increments after update\n- Rollback scenario triggers rollback on boot failure\n\nAS-6: Replay shows recorded history\n- 60s of sim data generates CSI buffer\n- POST /api/replay/start creates replay session\n- Replay blobs returned with replay:true flag\n- Seek functionality works within 1 second target\n\nAll tests use spaxel-sim as the test harness for simulating CSI data without hardware.","source_repo":".","compaction_level":0} {"id":"bf-3aij","title":"Define blob object literal search pattern","description":"## Objective\nDefine the search pattern for identifying blob-shaped object literals.\n\n## Scope\n- Define what constitutes a 'blob-shaped' object literal\n- Identify key characteristics: id, type, data fields\n- Create grep/search patterns for finding these literals\n\n## Acceptance Criteria\n- [ ] Blob pattern definition documented\n- [ ] Search patterns created for grep/ripgrep\n- [ ] Example matches documented\n- [ ] Search patterns saved to notes/bf-26ta-pattern.md","design":"","acceptance_criteria":"","notes":"","status":"closed","priority":2,"issue_type":"task","assignee":"claude-code-glm-4.7-alpha","created_at":"2026-07-06T06:01:42.216826835Z","updated_at":"2026-07-06T06:06:06.848567110Z","closed_at":"2026-07-06T06:06:06.848567110Z","close_reason":"Completed","source_repo":".","compaction_level":0,"labels":["split-child"]} {"id":"bf-3d55l","title":"Fuzz tests: binary frame parser and JSON protocol","description":"## Goal\nAdd property-based/fuzz tests for the two highest-impact input parsing surfaces, per plan §Testing Strategy → Property-Based / Fuzz Tests.\n\n## Targets\n\n1. FuzzParseBinaryFrame — internal/ingestion/frame_fuzz_test.go\n Seed corpus: valid frame, truncated header, n_sub mismatch, channel=0, n_sub>128\n Property: never panic; drop/parse/error all OK\n\n2. FuzzParseJSONFrame — internal/ingestion/json_fuzz_test.go\n Seed corpus: hello, health, ble, motion_hint, ota_status, unknown type\n Property: never panic; unknown types return typed error\n\n3. Phase sanitization property test — internal/pipeline/phase/phase_property_test.go\n For all valid int8 I/Q pairs, Sanitize output never contains NaN or Inf\n\n## Acceptance\n- go test -fuzz=FuzzParseBinaryFrame ./internal/ingestion/ -fuzztime=60s: no panic found\n- go test -fuzz=FuzzParseJSONFrame ./internal/ingestion/ -fuzztime=60s: no panic found\n- Phase sanitization property test passes for all int8 I/Q corner cases","design":"","acceptance_criteria":"","notes":"","status":"closed","priority":2,"issue_type":"test","assignee":"claude-code-glm-4.7-golf","created_at":"2026-05-02T12:08:47.919183889Z","updated_at":"2026-05-04T10:01:38.755721488Z","closed_at":"2026-05-04T10:01:38.755721488Z","close_reason":"Completed","source_repo":".","compaction_level":0} @@ -84,7 +84,7 @@ {"id":"bf-4bhd","title":"Find all blob creation code paths","description":"## Objective\nIdentify all locations in the codebase where blob objects are created or constructed.\n\n## Scope\n- Search for blob creation patterns (new Blob, object literals with blob shape, blob factories)\n- Document all blob creation sites\n- Identify which files need updates\n\n## Acceptance Criteria\n- [ ] All blob creation sites are identified and listed\n- [ ] Each site is documented with file path and line number\n- [ ] Creation pattern is noted (constructor, literal, factory, etc.)\n- [ ] Report is ready for the next bead to use","design":"","acceptance_criteria":"","notes":"","status":"closed","priority":2,"issue_type":"task","assignee":"claude-code-glm-5-alpha","created_at":"2026-07-06T05:11:28.984747608Z","updated_at":"2026-07-06T20:51:27.149808381Z","closed_at":"2026-07-06T20:51:27.149808381Z","close_reason":"Completed","source_repo":".","compaction_level":0,"labels":["deferred","umbrella"],"dependencies":[{"issue_id":"bf-4bhd","depends_on_id":"bf-3ldj","type":"blocks","created_at":"2026-07-06T05:32:05.819292341Z","created_by":"cli","thread_id":""},{"issue_id":"bf-4bhd","depends_on_id":"bf-1q3m","type":"blocks","created_at":"2026-07-06T15:04:21.606416708Z","created_by":"cli","thread_id":""},{"issue_id":"bf-4bhd","depends_on_id":"bf-5cgc","type":"blocks","created_at":"2026-07-06T20:13:54.885895615Z","created_by":"cli","thread_id":""}],"comments":[{"id":90,"issue_id":"bf-4bhd","author":"cli","text":"bf-5cgc handoff complete → notes/bf-5cgc-handoff.md. Your acceptance criterion 'Report is ready for the next bead to use' is now satisfied: the re-verified inventory lives in notes/bf-1q3m-consolidated.md (this file supersedes notes/bf-4bhd.md per bf-1q3m §4.1), and the site→bead handoff is formalized in notes/bf-5cgc-handoff.md with concrete owners (bf-5151 primary) + the GetMatch population pattern. bf-4bhd may be closed on this basis.","created_at":"2026-07-06T20:39:00.020455831Z"},{"id":92,"issue_id":"bf-4bhd","author":"cli","text":"Closed via auto re-dispatch. Blob-creation inventory was already delivered by the closed chain (bf-1q3m -> bf-1m2x -> bf-5cgc); this pass re-verifies currency. Empirical greps at reachable commit 21c702f (= 63f0c87 tree before the dispatcher rewrite) return exactly the documented counts: 5 primary literals, 15 non-test projection literals, 15 blob types, 2 JS production sites. Zero Go/JS/firmware source drift across f144aad..HEAD. New finding (provenance): prior notes cite unreachable dangling SHAs (1a26c12 is bf-1bmg not bf-1q3m; 0ae0c03 is amended-away bf-1q3m, real is f144aad) because history was rewritten; inventory unaffected, mapped to real SHAs. Deliverable: notes/bf-4bhd-reverification.md (commit a1e76e0). Source of truth: notes/bf-1q3m-consolidated.md; handoff: notes/bf-5cgc-handoff.md. All four acceptance criteria satisfied.","created_at":"2026-07-06T20:51:18.758949061Z"}]} {"id":"bf-4eal","title":"Update ardenone-cluster deployment to spaxel:0.1.354 after CI","description":"After spaxel-build-manual-bj7l2 (on iad-ci) succeeds and pushes ronaldraygun/spaxel:0.1.354:\n\n1. In ~/declarative-config, open k8s/ardenone-cluster/spaxel/deployment.yml\n2. Change image tag from 0.1.352 to 0.1.354\n3. Commit and push declarative-config\n4. ArgoCD auto-syncs the Deployment (Recreate strategy, ~30s downtime)\n5. Verify: kubectl --server=http://traefik-ardenone-cluster:8001 get pods -n spaxel\n6. Hit spaxel.ardenone.com/healthz to confirm it returns 200\n\nThis gets the 4MB firmware into the running mothership so re-flashing the ESP32 will work.","design":"","acceptance_criteria":"","notes":"","status":"closed","priority":0,"issue_type":"task","assignee":"claude-code-glm-5-alpha","created_at":"2026-06-04T03:15:07.139700779Z","updated_at":"2026-07-02T19:37:09.459815093Z","closed_at":"2026-07-02T19:37:09.459815093Z","close_reason":"Not executed: stale/obsolete — live cluster already at 0.1.358 (newer than target 0.1.354), healthy; deploying 0.1.354 would be a downtime downgrade. See comment 18.","source_repo":".","compaction_level":0,"comments":[{"id":16,"issue_id":"bf-4eal","author":"cli","text":"NOT DEPLOYED — preconditions unmet & premise stale (agent check 2026-07-02T19:03:12Z).\n\n1) Gating CI build missing: workflow 'spaxel-build-manual-bj7l2' is NotFound on iad-ci (zero spaxel workflows present). The task's hard precondition ('After spaxel-build-manual-bj7l2 succeeds and pushes ronaldraygun/spaxel:0.1.354') has NOT occurred.\n\n2) Premise is stale: task says 'change tag from 0.1.352 to 0.1.354', but declarative-config k8s/ardenone-cluster/spaxel/deployment.yml is ALREADY at 0.1.358. Git history: 0.1.352 -> (pre-staged 0.1.354 @951d408) -> 0.1.356 -> 0.1.358.\n\n3) Deploying 0.1.354 would be a DOWNGRADE (0.1.358 -> 0.1.354) with ~30s Recreate downtime. Live pod spaxel-d76b49496-7l4mj is running 0.1.358, healthy: healthz HTTP 200, uptime ~7.9d, db ok. The stated goal (4MB firmware in the running mothership) is already satisfied by the newer 0.1.358.\n\nPer bead rules, not closing. Recommend: close as obsolete, or re-target to the current VERSION (0.1.357) if a redeploy is actually wanted. No changes made to spaxel or declarative-config.","created_at":"2026-07-02T19:03:12.407349955Z"},{"id":17,"issue_id":"bf-4eal","author":"cli","text":"OBSOLETE — do NOT deploy 0.1.354 (would be a regression). Verified 2026-07-02: production ardenone-cluster is ALREADY running ronaldraygun/spaxel:0.1.358 (1/1 Running, 26d uptime), which is 4 patch versions AHEAD of the task target. The task's stated goal (ship the 4MB firmware / fix the 16MB crash loop) is already met: that fix is commit 301a588, which was VERSION 0.1.354 and is included in the deployed 0.1.358 and all versions since. Additional blockers: (1) prerequisite CI build spaxel-build-manual-bj7l2 does not exist on iad-ci; (2) the bead's own Rules forbid working outside /home/coding/spaxel/ but the task requires editing ~/declarative-config; (3) repo HEAD is 0.1.357 and production 0.1.358 — 0.1.354 was only a transient retry version. Recommend cancelling/re-scoping this bead rather than retrying. No code changes made.","created_at":"2026-07-02T19:25:11.305734462Z"},{"id":18,"issue_id":"bf-4eal","author":"cli","text":"NOT EXECUTED — stale/obsolete, intentionally skipped to avoid a harmful production downgrade. Verified at close time: Live ardenone-cluster Deployment ALREADY running docker.io/ronaldraygun/spaxel:0.1.358 (confirmed via Deployment image AND mothership /healthz: status=ok version=0.1.358 db=ok uptime~686000s). 0.1.358 is healthy (~8d uptime, no load shedding) — no reason to roll back. Target 0.1.354 is OLDER than running 0.1.358; deploying it = downgrade with ~30s downtime (Recreate) losing fixes in 0.1.355-0.1.358 (firmware 16MB crash-loop 301a588, dashboard /fleet 500 9ac200b, provision re-broadcast 2483d36). Precondition CI run spaxel-build-manual-bj7l2 does not exist in iad-ci argo-workflows. spaxel repo VERSION=0.1.357; bead premise (0.1.352 to 0.1.354) is outdated. Bead goal (4MB firmware in mothership for ESP32 re-flash) ALREADY satisfied — 0.1.358 is newer than the firmware-fix version. No action taken; declarative-config untouched, nothing pushed. If a newer-than-0.1.358 deploy is desired, create a fresh bead.","created_at":"2026-07-02T19:36:08.475523656Z"}]} {"id":"bf-4gbf","title":"Re-flash ESP32 via Web Serial after 4MB firmware is deployed","description":"Prerequisites: bf-4eal (deployment to 0.1.354) is complete.\n\nSteps:\n1. Open Chrome or Edge (Web Serial requires Chromium-based browser)\n2. Navigate to spaxel.ardenone.com (auth required — use existing credentials)\n3. Go to the provision / onboard page\n4. Connect the ESP32-S3 dev board via USB-C\n5. Click Flash/Provision — esptool.js will download firmware from mothership\n and write it to the device\n6. Monitor the serial output: should boot, NOT crash with flash size mismatch\n7. Expected: captive portal or WiFi connection attempt (node is unprovisioned)\n\nIf the device still shows the 16MB crash, check /api/firmware/manifest returns\na firmware binary whose image header byte[3] bits 4-7 = 0x4 (4MB), not 0x6 (16MB).\n\nThis is the validation step that confirms the 4MB fix works end-to-end.","design":"","acceptance_criteria":"","notes":"","status":"pending","priority":0,"issue_type":"task","assignee":"","created_at":"2026-06-04T03:15:17.801283106Z","updated_at":"2026-07-02T23:12:44.266969513Z","source_repo":".","compaction_level":0,"comments":[{"id":19,"issue_id":"bf-4gbf","author":"cli","text":"SOFTWARE PRE-CHECK VERIFIED (agent, no hardware): The deployed firmware binary is confirmed 4MB, not 16MB. Pulled ronaldraygun/spaxel:0.1.358 (matches running instance version per /healthz) and extracted /firmware/spaxel-firmware.bin (1.62MB merged binary). APP image header @0x10000 byte[3]=0x2F -> high nibble 0x2 = 4MB (esptool encoding). Bootloader header @0x0 byte[3]=0x2F = 4MB. An old buggy 16MB build would be 0x4F (nibble 0x4). Fix source confirmed in HEAD: commit d837598 (sdkconfig 16MB->4MB, partitions.csv 4MB redesign, onboard.js flashSize 'detect'->'4MB'); Dockerfile builds with idf.py 4MB config + esptool merge_bin --flash_size 4MB. HEADS-UP on this bead's stated nibbles: it says 'bits4-7=0x4(4MB),0x6(16MB)' but that does NOT match esptool's standard mapping (0x2=4MB,0x4=16MB,0x6=64MB). Don't check for literal 0x4 - the deployed 4MB binary is 0x2. REMAINING (needs human + ESP32-S3 + Chrome/Edge): the actual Web Serial flash + serial boot observation (steps 1-7). Agent cannot perform physical USB flashing, so bead NOT closed.","created_at":"2026-07-02T19:46:10.194617767Z"},{"id":20,"issue_id":"bf-4gbf","author":"cli","text":"RE-VERIFIED (independent, this dispatch 2026-07-02) — leaving OPEN.\n\n(1) DEPLOYED FIRMWARE BYTE-VERIFIED 4MB (independent of prior comment #19). Pulled ronaldraygun/spaxel:0.1.358 (exact version running per /healthz: status=ok, uptime~686819s/~7.95d, db=ok, shedding_level=0). Extracted /firmware/spaxel-firmware.bin (1,696,480 B / ~1.62MB, built 2026-06-27). ESP image headers:\n - Bootloader @0x0: E9 04 02 2F -> magic 0xE9 OK; byte[3]=0x2F -> flash-size nibble(bits4-7)=0x2 = 4MB\n - App @0x10000: E9 05 02 2F -> byte[3]=0x2F -> 4MB\n A buggy 16MB build would be 0x4F (nibble 0x4). Deployed binary is genuinely 4MB. CAVEAT (same as #19): the bead's stated nibbles '0x4=4MB, 0x6=16MB' do NOT match esptool's standard map (0x2=4MB, 0x4=16MB, 0x6=64MB); under the real map the deployed 4MB binary is nibble 0x2.\n\n(2) SOURCE-OF-TRUTH corroborates: firmware/sdkconfig CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y (16MB not set); partitions.csv = 4MB layout (factory 0x1F0000 + ota_0 0x1F0000 + otadata 0x3F0000); fix commit 301a588 in HEAD; deployed 0.1.358 newer than fix version 0.1.354.\n\n(3) PHYSICAL STEP STILL UNREACHABLE (re-confirmed, permanent): no /dev/ttyUSB* or /dev/ttyACM*; no serial/ESP device in lsusb; esptool not installed; no Chromium; and Web Serial is inherently a human-granted interactive browser permission over a physically-USB-connected device. No agent environment can do steps 1-7.\n\nDECISION: OPEN. All agent-verifiable criteria (the 4MB-header precondition) are satisfied and byte-confirmed, but the bead's actual purpose -- end-to-end validation by physically flashing an ESP32-S3 and observing boot -- has NOT been done by any agent or human. bf-4gbf gates the physical sub-chain (next: bf-2po1 provision the node, also hardware-only); closing on a software-only check would falsely signal 'device flashed & ready' and unblock hardware-dependent beads for a device that does not exist here. Needs a human + USB-C + ESP32-S3 + Chrome/Edge. No code changes; fix already deployed in 0.1.358.","created_at":"2026-07-02T19:54:37.887118038Z"},{"id":21,"issue_id":"bf-4gbf","author":"cli","text":"RE-VERIFIED (3rd dispatch, 2026-07-02) — leaving OPEN. Software precondition fully satisfied; physical step permanently unreachable here.\n\n(1) HARDWARE/BROWSER CONSTRAINT (re-confirmed): no /dev/ttyUSB* or /dev/ttyACM*; no serial/ESP device (lsusb none); esptool/esptool.py not installed; no Chromium/Chrome/Edge on PATH. Web Serial is an interactive, human-granted browser permission over a physically USB-connected device — no agent shell can perform steps 1-7.\n\n(2) NO DEVICE ATTACHED: live https://spaxel.ardenone.com/healthz = {status:ok, version:0.1.358, uptime_s:691176 (~8d), nodes_online:0, db:ok, shedding_level:0}. nodes_online:0 means no ESP32 is connected to the mothership — nothing to flash/observe.\n\n(3) ENTIRE 4MB CHAIN VERIFIED (independent of prior comments): firmware/sdkconfig has CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y (16MB not set); partitions.csv is the 4MB layout (factory 0x1F0000 + ota_0 0x1F0000 + otadata 0x3F0000); deployed 0.1.358 firmware binary was byte-verified 4MB by prior dispatches (bootloader+app header byte[3]=0x2F -> nibble 0x2 = 4MB per esptool map; a 16MB build would be 0x4F); AND the dashboard Web Serial write path now hard-codes 4MB — dashboard/js/onboard.js:679-692 loader.writeFlash({... flashSize:'4MB', flashMode:'dio', flashFreq:'80m' ...}). The /api/firmware/manifest handler (cmd/mothership/main.go:4231) serves the expected esp-web-tools manifest (behind Google OAuth, matching 'auth required').\n\n(4) BEAD NIBBLE NOTE (carried forward): the task's stated mapping 'byte[3] bits4-7 = 0x4(4MB)/0x6(16MB)' does NOT match esptool's standard map (0x2=4MB, 0x4=16MB, 0x6=64MB). Don't check for literal 0x4 — the real 4MB value is nibble 0x2.\n\nDECISION: OPEN. This is a hardware-validation bead; closing on software-only evidence would falsely signal 'device flashed & ready' and unblock bf-2po1 (node provisioning, also hardware-only) for a device that is not present. Needs a human + USB-C ESP32-S3 + Chrome/Edge. No code changes this session; 4MB fix already shipped in 0.1.358.","created_at":"2026-07-02T21:04:18.395853935Z"},{"id":22,"issue_id":"bf-4gbf","author":"cli","text":"Autonomous agent (bf-4gbf auto-dispatch) could NOT complete this bead — it is a physical hardware validation requiring a human.\n\nWhat this step requires (none achievable by a remote software agent in a terminal):\n- Chrome/Edge Web Serial session (needs a local browser + locally-USB-connected ESP32-S3)\n- Physical ESP32-S3 dev board connected via USB-C\n- Human to click Flash/Provision and read the device serial console\nConfirmed NOT available on the agent host: no /dev/ttyUSB* or /dev/ttyACM* (no board attached), no ESP-IDF toolchain (idf.py / $IDF_PATH absent), and the live mothership's /api/firmware/manifest + /firmware/ are behind the Google OAuth proxy (307), so the deployed binary header can't be inspected remotely either.\n\nSoftware-side prerequisite VERIFIED (the 4MB fix is in place end-to-end in source + deployed image):\n- firmware/sdkconfig: CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y, FLASHSIZE='4MB' (was 16MB before)\n- firmware/sdkconfig.defaults: CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y (comment: 'fits in 4MB flash')\n- firmware/partitions.csv fits in 4MB (factory 0x10000 1.9MB + ota_0 0x200000 1.9MB + otadata 0x3F0000)\n- Dockerfile busts Kaniko cache + forces sdkconfig regen (commit 301a588 'fix 16MB crash loop'), so deployed 0.1.357 was rebuilt from the corrected 4MB config\n- Live https://spaxel.ardenone.com/healthz returns HTTP 200\n- NOTE: stale local firmware/build/app-flash_args still says '--flash_size 16MB' but that is a gitignored pre-fix build artifact (Apr 14, pre-301a588), NOT authoritative for what CI deployed.\n\nNo code changes were needed — the fix already shipped via prerequisite bead bf-4eal. Bead left OPEN for a human with hardware to perform the actual Web Serial flash + serial-boot observation. Per dispatch rules, an un-completable bead is not closed.","created_at":"2026-07-02T21:24:09.647935343Z"},{"id":23,"issue_id":"bf-4gbf","author":"cli","text":"Agent pass (claude-code-glm-5-alpha, 2026-07-02): cannot auto-complete — this is a physical hardware validation that needs a human to connect the ESP32-S3 dev board over USB-C and click through the Web Serial / esptool.js provisioning flow in Chrome/Edge at spaxel.ardenone.com, then observe live serial output. Leaving IN_PROGRESS for a human with hardware. Prerequisites verified at source: (1) bf-4eal (deploy 0.1.354) is CLOSED; current VERSION is 0.1.357. (2) 4MB flash fix confirmed in firmware sdkconfig.defaults (CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y / ='4MB') and committed sdkconfig, plus partitions.csv is laid out for 4MB (factory 0x10000+0x1F0000, ota_0 0x200000+0x1F0000, otadata 0x3F0000). Fix commit: 301a588 'fix(firmware): bust Kaniko cache + force sdkconfig regen to fix 16MB crash loop'. No local spaxel.bin built (app binary is built in CI/Kaniko), so byte[3] header nibble of the deployed image can't be inspected from this checkout — confirm at runtime via /api/firmware manifest when flashing. Expected good result on re-flash: node boots cleanly (no 'flash size mismatch' / SPI-size crash) and either shows the captive portal or attempts WiFi as an unprovisioned node.","created_at":"2026-07-02T21:52:41.276935808Z"},{"id":24,"issue_id":"bf-4gbf","author":"cli","text":"Agent verification (bf-4gbf, 2026-07-02): Cannot complete the physical flash step — this is a human-in-the-loop hardware validation.\n\nConfirmed the software/firmware chain is correct end-to-end:\n- 4MB fix at source: firmware/sdkconfig.defaults has CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y / =\"4MB\"\n- Prereq bf-4eal (deploy 0.1.354) is CLOSED; running mothership is now at 0.1.358\n- Deployed mothership healthy: https://spaxel.ardenone.com/healthz -> status:ok, version 0.1.358, db ok\n- /api/firmware and /api/firmware/manifest are behind Google OAuth (can't introspect served binary from a CLI without browser creds)\n\nCannot be done by a coding agent (requires all four):\n- A physical ESP32-S3 dev board (none available)\n- USB-C physical connection\n- Interactive Chromium browser to drive Web Serial (needs user gesture + port picker; no headless path)\n- spaxel.ardenone.com Google OAuth session\n\nLeaving bead OPEN per 'do not close if you cannot complete' rule. Needs a human with the board to: open Chrome/Edge -> spaxel.ardenone.com -> onboard -> connect ESP32-S3 via USB-C -> Flash/Provision -> confirm boot (no flash-size-mismatch crash) -> expect captive portal / WiFi connection attempt.","created_at":"2026-07-02T22:09:20.701829249Z"},{"id":25,"issue_id":"bf-4gbf","author":"cli","text":"Software-side validation (agent, 2026-07-02T22:14:07Z):\n\nCONFIRMED FIX IS DEPLOYED:\n- Source: firmware/sdkconfig.defaults = CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y; generated firmware/sdkconfig also 4MB; partitions.csv fits 4MB.\n- Cache-bust in place (commit 301a588): Kaniko FIRMWARE_CACHE_BUST ARG + 'rm -f sdkconfig sdkconfig.old' so CI regenerates from the 4MB defaults (the root cause of the original 16MB loop).\n- Deployed mothership at spaxel.ardenone.com is healthy, version 0.1.358 (uptime ~8d) -> well past the 0.1.354 fix, so the 4MB image is live.\n- Note: stale local firmware/build/bootloader.bin (dated 2026-04-14, pre-fix) still shows a 16MB header -- it predates the fix and is NOT what shipped.\n\nBLOCKED ON PHYSICAL HARDWARE (needs a human):\nThe actual acceptance of this bead -- open a Chromium browser, OAuth into spaxel.ardenone.com, connect the ESP32-S3 via USB-C, click Flash/Provision (esptool.js), and monitor serial output for a clean boot (no 'Detected size smaller than image header' crash) followed by captive portal / WiFi attempt -- requires a physical board + browser that this coding agent has no access to.\n- I also cannot fetch the deployed /firmware/spaxel-firmware.bin to read byte[3] header bits: the entire site (not just /api/*) is behind the Google OAuth proxy (every non-/healthz path returns 307 to accounts.google.com), and I have no user session.\n- Per esptool's image-header table, byte[3] bits 4-7 = 0x2 means 4MB and 0x4 means 16MB (the task's '0x4=4MB, 0x6=16MB' looks off vs esptool encoding -- whoever does the flash should compare against 0x2 for 4MB).\n\nLeaving OPEN -- the end-to-end physical flash + serial validation remains for a human with the ESP32 board.","created_at":"2026-07-02T22:14:07.684170447Z"},{"id":26,"issue_id":"bf-4gbf","author":"cli","text":"Agent verification (cannot close — needs physical hardware):\n\nBLOCKED ON: human + physical ESP32-S3 board. This Hetzner box has NO USB-serial device (no /dev/ttyUSB* or /dev/ttyACM*), no USB-serial/ESP-JTAG bridge in lsusb, and no esptool on PATH. Web Serial also requires a GUI Chromium browser, which doesn't exist here. An agent cannot perform the physical USB-C flash or observe real boot/serial output.\n\nPREREQUISITES CONFIRMED READY (everything agent/CI-doable is done):\n- bf-4eal (deployment): CLOSED. Live mothership healthy — https://spaxel.ardenone.com/healthz => status ok, version 0.1.358, nodes_online 0.\n- Source 4MB fix in place: firmware/sdkconfig + sdkconfig.defaults both set CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y; partitions.csv is a 4MB layout (factory 0x10000 size 0x1F0000, ota_0 0x200000 size 0x1F0000, otadata 0x3F0000). Commit 301a588 'fix 16MB crash loop'.\n\nACTION FOR A HUMAN: connect an ESP32-S3 via USB-C, open Chrome/Edge, go to spaxel.ardenone.com provision page, Flash/Provision, confirm it boots (no 16MB flash-size-mismatch crash) into captive portal / WiFi-connect attempt. Then close this bead.","created_at":"2026-07-02T22:18:11.875420762Z"},{"id":27,"issue_id":"bf-4gbf","author":"cli","text":"Software/deployment chain verified — only the physical flash remains (requires a human + board):\n\n[PRE-FLIGHT CHECKS — all green]\n- 4MB flash fix at source: firmware/sdkconfig.defaults has CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y (+ CONFIG_ESPTOOLPY_FLASHSIZE='4MB'); partitions.csv sized for 4MB (factory 0x1F0000 + ota_0 0x1F0000 + otadata).\n- Prerequisite bf-4eal (deploy 0.1.354) is closed; repo VERSION=0.1.357.\n- Deployed mothership is live+healthy: GET https://spaxel.ardenone.com/healthz -> {status:ok, version:0.1.358, db:ok}. The 4MB firmware is in the running mothership, so a Web Serial flash will pull the correct binary.\n\n[BLOCKER — not agent-completable]\nThis bead's actual validation (open Chrome/Edge Web Serial, plug ESP32-S3 via USB-C, click Flash/Provision, watch serial output for boot-vs-flash-size-mismatch) requires physical hardware + a human. This server has no ESP32 attached and no browser/Web Serial. Leaving OPEN per the bead retry contract; needs a human with the dev board to perform the flash and confirm boot.\n\n[Encoding note for whoever flashes] The diagnostic hint ('byte[3] bits 4-7 = 0x4 = 4MB') does not match esptool's standard image-header encoding: high nibble 0x2 = 4MB, 0x4 = 16MB. Since FLASHSIZE_4MB is set at source, a freshly built/served binary should read high nibble 0x2 (e.g. byte[3]=0x2x). Treat a 0x4 high nibble as still-16MB, not 4MB.","created_at":"2026-07-02T22:36:42.216785943Z"},{"id":28,"issue_id":"bf-4gbf","author":"cli","text":"Autonomous-agent re-attempt (2026-07-02). Cannot complete this step — it requires a physical ESP32-S3 + USB-C and a Chromium browser driving the Web Serial API, neither of which is available on this Hetzner box (no /dev/ttyUSB* or /dev/ttyACM*, no USB-serial bridge in lsusb, no chromium/chrome/edge in PATH). Per the task rule I am leaving this open for a human (jedarden) rather than closing it.","created_at":"2026-07-02T22:39:07.125408227Z"},{"id":29,"issue_id":"bf-4gbf","author":"cli","text":"Source-level verification (2026-07-02): 4MB fix is confirmed at source. firmware/sdkconfig:482 = CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y; firmware/sdkconfig.defaults:10-11 = 4MB; firmware/partitions.csv redesigned for 4MB (factory 0x10000 size 0x1F0000 + single ota_0 0x200000, no 16MB layout; old 16MB config kept as firmware/sdkconfig.old for reference); dashboard/js/onboard.js:681 passes flashSize:'4MB' to esptool. Deploy prerequisite bf-4eal is satisfied: VERSION=0.1.357, well past the 0.1.354 this task references. Do NOT rebuild firmware locally to proxy-verify — it pulls a ~2GB ESP-IDF image and a prior run timed out (~7min) with no gain; source evidence is sufficient.","created_at":"2026-07-02T22:39:22.316277106Z"},{"id":30,"issue_id":"bf-4gbf","author":"cli","text":"IMPORTANT for whoever does the physical flash: the task's stated header test is INVERTED vs the real esptool spec. The task says 'byte[3] bits 4-7 = 0x4 (4MB), not 0x6 (16MB)'. Correct esptool encoding: 4MB -> image-header byte[3] high-nibble 0x2 (full byte 0x20); 16MB -> high-nibble 0x4 (full byte 0x40). So a correctly-built 4MB image is EXPECTED to read 0x2 in that nibble, not 0x4. Verify with: esptool.py --port /dev/ttyUSB0 image_info spaxel-*.bin and check 'Flash size: 4MB', or hexdump byte[3] of the .bin and confirm high nibble is 0x2. If the device still shows a flash-size mismatch crash after flashing, that is the real thing to investigate.","created_at":"2026-07-02T22:39:22.337314455Z"},{"id":31,"issue_id":"bf-4gbf","author":"cli","text":"BLOCKED - requires physical hardware + human, not auto-completable on this server (re-verified 2026-07-02):\n\n- No ESP32 attached: /dev/ttyUSB* and /dev/ttyACM* absent; no USB-serial bridge (CP210x/CH340/FTDI/Espressif) in lsusb.\n- No Chromium/Edge in PATH; Web Serial API cannot be driven headless from the server.\n- spaxel.ardenone.com dashboard is OAuth-gated (307 -> Google accounts), not headless-accessible.\n\n4MB fix IS confirmed correct at source: firmware/sdkconfig AND sdkconfig.defaults have CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y (16MB unset); dashboard/js/onboard.js:681 passes flashSize:'4MB'. Deploy is past the 0.1.354 the task references (repo at 0.1.357), so the bf-4eal prereq is satisfied.\n\nHEADER-CHECK CORRECTION (task spec is inverted): per vendored dashboard/js/esptool-bundle.js flash-size table (1MB:0,2MB:16,4MB:32,8MB:48,16MB:64), a valid 4MB image has header byte[3] high-nibble 0x2 (byte 0x20); 16MB is high-nibble 0x4 (byte 0x40). Expect 0x2 for 4MB, NOT 0x4 - the task's 4MB=0x4 is actually the 16MB marker.\n\nLeft open for a human (jedarden) with a Chromium browser + ESP32-S3 via USB-C. Do NOT build firmware locally to proxy-verify: it pulls a ~2GB ESP-IDF Docker image and times out (~7min, prior run killed). Source evidence is sufficient; the physical boot/captive-portal observation is the only remaining step.","created_at":"2026-07-02T22:45:19.450038533Z"},{"id":32,"issue_id":"bf-4gbf","author":"cli","text":"Verification pass (autonomous agent, 2026-07-02): 4MB fix is correct at source level and the deploy prerequisite bf-4eal is closed (4MB image is in the running mothership, past the 0.1.354 the task references). Confirmed: firmware/sdkconfig has CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y (lines 482/488); firmware/partitions.csv redesigned for 4MB (factory 0x1F0000 + single ota_0 at 0x200000, no 16MB layout); dashboard/js/onboard.js:681 passes flashSize:'4MB' to esptool; the vendored dashboard/js/esptool-bundle.js FLASH_SIZES map sends '4MB'->32 (0x20). Fix landed in commit d837598. PHYSICAL STEP NOT RUN: this Hetzner box has no ESP32-S3 attached (/dev/ttyUSB* and /dev/ttyACM* absent; no CP210x/CH340/FTDI/Espressif USB-serial bridge in lsusb) and no Chromium/Edge in PATH for Web Serial, and spaxel.ardenone.com is OAuth-gated. Needs a human with a Chromium browser + a physical ESP32-S3 board over USB-C. HEAD NOTE for whoever does the physical check: the bead's stated header test is inverted vs the real esptool spec. esptool encodes 4MB as image-header byte[3] high-nibble 0x2 (full byte 0x20) and 16MB as 0x4 (0x40) -- i.e. 0x4 = 16MB, NOT 4MB. So a correct 4MB image reads 0x2 in byte[3], not 0x4. Per task rule (cannot complete -> do not close), leaving OPEN.","created_at":"2026-07-02T22:48:00.612275608Z"},{"id":33,"issue_id":"bf-4gbf","author":"cli","text":"Verification check (autonomous agent on Hetzner box) — NOT closing; this is a physical-hardware validation step that can't be done here.\n\nState confirmed:\n- 4MB fix already correct at source: firmware/sdkconfig CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y (+ FLASHSIZE=\"4MB\"); firmware/partitions.csv redesigned for 4MB (factory 0x1F0000 + single ota_0 0x1F0000, no 16MB layout); dashboard/js/onboard.js:681 passes flashSize:'4MB'.\n- Prerequisite bf-4eal closed; deployed instance live at spaxel.ardenone.com — /healthz returns 200, version 0.1.358 (past the 0.1.354 the task references). So the 4MB firmware is deployed.\n\nWhy this box can't do the physical flash step:\n- No ESP32 attached (no /dev/ttyUSB* or /dev/ttyACM*; no CP210x/CH340/FTDI/Espressif USB-serial bridge in lsusb).\n- No Chromium-based browser in PATH — Web Serial API needs Chrome/Edge.\n- /api/firmware/manifest is OAuth-gated (307 -> accounts.google.com), unreachable headlessly.\n\nHeader-byte note for whoever does the physical check: the task's stated test (byte[3] high-nibble = 0x4 for 4MB, 0x6 for 16MB) is inverted vs the esptool spec. Correct: 4MB -> high-nibble 0x2 (byte 0x20); 16MB -> 0x4 (0x40). So a good 4MB image reads 0x2, not 0x4.\n\nLeaving open for a human with a Chromium browser + physical ESP32-S3 via USB-C.","created_at":"2026-07-02T22:51:34.310104175Z"},{"id":34,"issue_id":"bf-4gbf","author":"cli","text":"Re-attempted by agent (claude-code-glm-5-alpha), still blocked: requires a human + physical ESP32-S3. No ESP32 attached (no /dev/ttyUSB*/ttyACM*, no USB-serial bridge in lsusb), no Chromium browser in PATH, and spaxel.ardenone.com is OAuth-gated — Web Serial can't be driven from this server. Source-level 4MB fix is confirmed in place: sdkconfig FLASHSIZE_4MB=y, partitions.csv redesigned for 4MB (factory 0x1F0000 + single ota_0), onboard.js:681 passes flashSize:'4MB', fix commit d837598. Prereq bf-4eal closed. NOTE for the physical check: the task's header test is inverted vs esptool spec — a 4MB image header byte[3] high-nibble reads 0x2 (byte 0x20), NOT 0x4; 16MB reads 0x4. Leaving open per task rules. See memory: hw-validation-beads-not-agent-completable.","created_at":"2026-07-02T22:57:23.907813868Z"},{"id":35,"issue_id":"bf-4gbf","author":"cli","text":"BLOCKER — requires a human + physical ESP32-S3 board; this server-based coding agent cannot perform it. Not closing per task rule.\n\nSoftware preconditions VERIFIED (no need to re-check):\n- 4MB fix at source: firmware/sdkconfig.defaults has CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y / '4MB'; firmware/partitions.csv is a 4MB layout (factory ~1.9MB + ota_0 ~1.9MB + otadata). The 16MB flash-size-mismatch crash root cause is resolved in the build.\n- Prereq bf-4eal (deploy 0.1.354) is CLOSED. Deployed mothership healthy at v0.1.358: https://spaxel.ardenone.com/healthz -> 200 {status:ok, version:0.1.358}. 4MB firmware is live.\n- Firmware serving endpoints present in code: GET /api/firmware (manifest) + GET /firmware/ (binary), no-auth at app layer.\n\nCould NOT verify headlessly: /api/firmware is fronted by Google OAuth (auth.ardenone.com) at the reverse proxy, so the firmware binary image-header byte[3] flash-size check cannot run from this server without a browser session. A human session passes through.\n\nStill needed (HUMAN ONLY):\n1. Chrome/Edge -> spaxel.ardenone.com -> provision/onboard page.\n2. Plug ESP32-S3 dev board via USB-C.\n3. Click Flash/Provision (esptool.js downloads 4MB firmware + writes).\n4. Watch serial: must boot cleanly, NOT crash with flash-size mismatch; expect captive portal / WiFi connection attempt (node unprovisioned).","created_at":"2026-07-02T23:12:34.936822318Z"}]} -{"id":"bf-4iewr","title":"Resolve the REJECT/token path so tokenless sim nodes are accepted","description":"Second step of the chain (depends on bf-3ud7e build). With the binary built, ensure the hardware-free mothership (bf-3zll) does NOT reject tokenless spaxel-sim nodes.\n\nSource of truth: mothership/internal/ingestion/server.go ~lines 506-525. If a token validator is configured (SetTokenValidator) AND the migration window is closed (SetMigrationDeadline zero or past), every tokenless sim node is rejected via sendReject with reason 'invalid_token' and disconnected — this is the 'REJECT'/401/403 failure mode.\n\n## Scope\n- Determine whether the local/test mothership wires a token validator and what the migration deadline is.\n- If a validator is active for the sim run, disable it (unset validator), open the migration window, or have the sim supply valid tokens — whichever is correct for the hardware-free build.\n- Verify by running the sim (1-2 nodes) and grepping mothership stderr.\n\n## Acceptance Criteria\n- [ ] Root cause of any REJECT identified (validator configured? migration window closed?).\n- [ ] Mothership configured so tokenless sim nodes connect without rejection.\n- [ ] When the sim connects, mothership stderr shows zero occurrences of 'rejected' / 'REJECT' / 'invalid_token' / HTTP 401 / 403.","design":"","acceptance_criteria":"","notes":"","status":"open","priority":2,"issue_type":"task","created_at":"2026-07-07T05:19:51.181529765Z","updated_at":"2026-07-07T09:52:00.017666181Z","source_repo":".","compaction_level":0,"labels":["deferred","split-child","umbrella"],"dependencies":[{"issue_id":"bf-4iewr","depends_on_id":"bf-3ud7e","type":"blocks","created_at":"2026-07-07T05:20:08.389583352Z","created_by":"cli","thread_id":""},{"issue_id":"bf-4iewr","depends_on_id":"bf-601bz","type":"blocks","created_at":"2026-07-07T09:50:16.400577464Z","created_by":"cli","thread_id":""}],"comments":[{"id":110,"issue_id":"bf-4iewr","author":"cli","text":"[bf-34lwt root-cause] TOKENLESS-SIM REJECT PATH — definitive finding (citations are full repo-relative paths; the task body's cmd/* maps to mothership/cmd/*).\n\nTL;DR: current 'no reject' is a 24h MIGRATION-WINDOW MASK, not a fix. The validator is wired unconditionally, but the sim presents its token only as an HTTP header the mothership never reads, so hello.Token is always empty and tokenOK is always false. The only thing keeping sim nodes connected is the default 24h window; the moment it closes, every sim node is sendReject-ed (invalid_token) and disconnected.\n\n1) Validator wired UNCONDITIONALLY: mothership/cmd/mothership/main.go:4494 ingestSrv.SetTokenValidator(provSrv.ValidateToken) — no config/build gate. Only the *deadline* is gated (main.go:4495 if cfg.MigrationWindowHours > 0). So s.tokenValidator is always non-nil. => 'validator configured?' is always YES.\n\n2) Validator reads hello.Token only: mothership/internal/ingestion/server.go:513 tokenOK := hello.Token != \"\" && validator(hello.MAC, hello.Token). validator = ValidateToken(mac,token) (SetTokenValidator at server.go:321-327). hello.Token is JSON 'token,omitempty' (message.go:22).\n\n3) Sim sends token ONLY as X-Spaxel-Token HTTP header, NOT in hello body: mothership/cmd/sim/main.go:633 + mothership/cmd/sim/scenario.go:318 headers.Set(\"X-Spaxel-Token\", token). Hello body map (main.go:652-665 / scenario.go:327-340) has type/mac/firmware_version/capabilities/chip/flash_mb/uptime_ms/wifi_rssi/ip/pos_* — NO 'token' key => hello.Token deserializes to \"\".\n\n4) Mothership NEVER reads that header => token-supply path is DEAD. grep 'X-Spaxel-Token' mothership/ returns ONLY sim files (cmd/sim/*); zero hits in cmd/mothership or internal. The WS upgrade handler reads nothing off the request: server.go:469 s.upgrader.Upgrade(w, r, nil) inside HandleNodeWS (server.go:455) — no r.Header.Get. (All other Header.Get/Set under internal/ are outbound webhook/notify or tests.) Consequence: hello.Token == \"\" for every sim node => tokenOK always false, regardless of the provisioned token. The parent's 'have the sim supply valid tokens' option is currently NON-FUNCTIONAL.\n\n5) Only the migration window accepts sim nodes: server.go:515 if !deadline.IsZero() && time.Now().Before(deadline) => nc.Unpaired = true. MigrationWindowHours DEFAULTS to 24 (config.go:139). Fresh boot sets deadline=now+24h (main.go:4496) => window open => sim nodes accepted as Unpaired, no reject. Matches PROGRESS.md bf-3hji.\n\n6) REJECT fires ONLY when window closed. Two cases: (a) SPAXEL_MIGRATION_WINDOW_HOURS=0 => main.go:4495 false => SetMigrationDeadline never called => deadline IsZero() => server.go:515 !IsZero() false => reject branch; (b) uptime > 24h => Now().Before(deadline) false => reject branch. Either way hello.Token==\"\" => server.go:521 'missing token' => server.go:525 sendReject(conn,\"invalid_token\") => server.go:526 conn.Close() (sendReject at server.go:841-846).\n\nEXPLICIT STATEMENTS (acceptance criteria):\n- Current 'no reject' is a 24h-window MASK, not a real fix: it vanishes under SPAXEL_MIGRATION_WINDOW_HOURS=0 or after 24h uptime.\n- The token-supply path is currently DEAD: header ignored, hello.Token empty, tokenOK always false.\n\nFull write-up + fix options (A keep-mask / B make-token-real via header-read-in-HandleNodeWS-or-body-token / C gate-the-validator): docs/notes/token-reject-root-cause.md (committed with bf-34lwt). The remaining bf-4iewr children should pick exactly one of A/B/C; the two invariants for the final state: 'no reject' must not depend on the 24h mask, and the chosen token contract must be honored end-to-end by both sim and mothership.","created_at":"2026-07-07T09:59:00.629760880Z"},{"id":111,"issue_id":"bf-4iewr","author":"cli","text":"ROOT CAUSE (bf-34lwt, first link of this split) — confirmed against current source. Full write-up: docs/notes/token-reject-root-cause.md (commit 5c6a692).\n\nAll module paths are under mothership/; the task body dropped that prefix.\n\n1. VALIDATOR WIRED UNCONDITIONALLY: mothership/cmd/mothership/main.go:4494 ingestSrv.SetTokenValidator(provSrv.ValidateToken) — no config/build gate, so 'validator configured?' is always YES. Only the deadline is gated (main.go:4495 if cfg.MigrationWindowHours > 0).\n\n2. VALIDATOR READS hello.Token ONLY: mothership/internal/ingestion/server.go:513 tokenOK := hello.Token != \"\" && validator(hello.MAC, hello.Token). ValidateToken (mothership/internal/provisioning/server.go:135) does an HMAC constant-time compare on the token STRING — it never touches any HTTP header. hello.Token maps to JSON field token,omitempty (mothership/internal/ingestion/message.go:22).\n\n3. SIM SENDS TOKEN ONLY AS X-Spaxel-Token HEADER, never in the hello body. Both sim copies: mothership/cmd/sim/main.go:633 and mothership/cmd/sim/scenario.go:318 set headers.Set(\"X-Spaxel-Token\", token) on the WS dial; standalone module cmd/sim/main.go:313 does the same. None of the hello bodies include a 'token' key (mothership/cmd/sim/main.go:652-665; cmd/sim/main.go:348-359; scenario.go:331-341), so hello.Token deserializes to \"\" from either binary.\n\n4. MOTHERSHIP NEVER READS THE HEADER -> TOKEN-SUPPLY PATH IS DEAD. grep 'X-Spaxel-Token' over mothership/ returns only sim files; the WS upgrade handler upgrades UNCONDITIONALLY at server.go:469 s.upgrader.Upgrade(w, r, nil) with no r.Header.Get(...) and no HTTP-401 path (HandleNodeWS, server.go:455). So hello.Token==\"\" -> server.go:513 short-circuits to tokenOK=false regardless of the provisioned token. The 'have the sim supply valid tokens' option in THIS bead is therefore currently NON-FUNCTIONAL — the token goes to a header nobody reads.\n\n5. THE ONLY THING ACCEPTING SIM NODES IS THE MIGRATION WINDOW. server.go:514-519: if !tokenOK, accept as Unpaired only when !deadline.IsZero() && Now().Before(deadline). MigrationWindowHours DEFAULTS TO 24 (mothership/internal/config/config.go:139), so a fresh boot sets deadline=now+24h (main.go:4496) -> every tokenless sim node accepted as Unpaired, no reject (matches PROGRESS.md bf-3hji). Corroborated by the e2e harness itself (mothership/tests/e2e/e2e_test.go:86-94), which pins SPAXEL_MIGRATION_WINDOW_HOURS=24 and documents exactly this.\n\n6. REJECT FIRES ONLY WHEN THE WINDOW IS CLOSED (server.go:519-528): (a) SPAXEL_MIGRATION_WINDOW_HOURS=0 -> SetMigrationDeadline never called (main.go:4495 guard) -> deadline.IsZero() true -> reject; (b) uptime > 24h -> Now().Before(deadline) false -> reject. In both, hello.Token==\"\" logs 'Node rejected: missing token' (server.go:520-521), sendReject(conn,\"invalid_token\") (server.go:525, def 841-846), conn.Close() (526).\n\nBONUS: there is NO HTTP-401 path at all — the upgrade always succeeds (101). So AS-7 quality gate #7 ('node without token must get HTTP 401 and be rejected') is currently UNSATISFIABLE under default config; it's part of the same dead-path/accident.\n\nSUMMARY (for this bead's acceptance criteria):\n- 'No reject' is a 24h-window MASK, not a real fix — it vanishes under SPAXEL_MIGRATION_WINDOW_HOURS=0 or after 24h uptime.\n- The token-supply path is currently DEAD: header ignored (server.go:469), hello.Token empty (sim hello bodies), so tokenOK is always false (server.go:513).\n- Three independent fixes for the sibling children: (A) keep the mask but make it deliberate for sim/test; (B) make the token path real (read X-Spaxel-Token in HandleNodeWS, or have the sim put token in the hello body); (C) gate the validator off for the hardware-free build. See the doc for the trade-offs.","created_at":"2026-07-07T10:20:40.536429191Z"},{"id":115,"issue_id":"bf-4iewr","author":"cli","text":"## Root cause: tokenless-sim REJECT in the WS hello path (bf-34lwt synthesis)\n\nSynthesis of the three bf-34lwt children (bf-5ig3e, bf-29wyl, bf-2hdbg). All six facts re-confirmed against current source (repo-relative paths):\n\n**1. Validator is wired UNCONDITIONALLY** — `mothership/cmd/mothership/main.go:4494`\n`ingestSrv.SetTokenValidator(provSrv.ValidateToken)` runs at every boot with NO env/build guard, so \"is a validator configured?\" is always YES. (The migration deadline at lines 4495-4498 is wired separately, only when `MigrationWindowHours > 0`.)\n\n**2. Validator reads `hello.Token` ONLY** — `mothership/internal/ingestion/server.go:513`\n`tokenOK := hello.Token != \"\" && validator(hello.MAC, hello.Token)`. Accept/reject hinges entirely on the JSON body's `token` field; no HTTP header participates in the check.\n\n**3. Sim sends its token ONLY as the `X-Spaxel-Token` header** — `cmd/sim/main.go:313`\n`reqHeader.Set(\"X-Spaxel-Token\", token)`. The hello map (`cmd/sim/main.go:348-359`) carries type/mac/firmware_version/capabilities/chip/flash_mb/uptime_ms/pos_{x,y,z} and NO `token` field. `cmd/sim/scenario.go` sets no header or body token either.\n\n**4. Mothership NEVER reads that header** — `grep -rn 'X-Spaxel-Token' mothership/cmd/mothership/ mothership/internal/` returns zero hits; every `Header.Get` under `mothership/internal/` is an outbound notification client (webhook/ntfy/pushover/mqtt), none on the WS node path. Consequence: for sim nodes `hello.Token` is always `\"\"` -> `tokenOK` is always false.\n\n**5. The migration window — not tokens — is the only thing accepting sim nodes** — `mothership/internal/config/config.go:139` (`cfg.MigrationWindowHours = 24`, the default), applied at `mothership/cmd/mothership/main.go:4495-4498` to set a deadline 24h out. The acceptance branch is `mothership/internal/ingestion/server.go:515`: `if !deadline.IsZero() && time.Now().Before(deadline)` -> node admitted and flagged `Unpaired` (matches PROGRESS.md bf-3hji).\n\n**6. REJECT fires ONLY when the window is closed** — `mothership/internal/ingestion/server.go:519-527`, the else branch: logs \"missing token\"/\"invalid token\" -> `sendReject(conn, \"invalid_token\")` -> `conn.Close()`. Two triggers: (a) `SPAXEL_MIGRATION_WINDOW_HOURS=0` -> the `main.go:4495` gate is skipped -> deadline stays zero-value -> `deadline.IsZero()` true -> reject; (b) uptime > 24h -> `time.Now().After(deadline)` -> reject.\n\n## Two required takeaways\n\n- **\"No reject\" is a 24h-window MASK, not a real fix.** A default-boot mothership rejects nothing only because the 24h migration window is open. The instant it closes (`SPAXEL_MIGRATION_WINDOW_HOURS=0`, or any process alive >24h), every tokenless sim node hits the `invalid_token` reject branch. The hardware-free build is green by accident of uptime, not by design.\n\n- **The token-supply path is currently DEAD.** The parent bead's \"supply valid tokens\" option is non-functional today: the sim's token lives only in the unread `X-Spaxel-Token` header, while the validator checks only the always-empty `hello.Token` body field — so even a correctly-provisioned token cannot make `tokenOK` true.\n\n## Precise target for the remaining bf-4iewr children\n\nRepair the dead token-supply path (point 4): either (a) populate `hello.Token` in the sim's hello body, or (b) have the mothership read `X-Spaxel-Token` on the WS upgrade. Opening/lengthening the migration window (points 5-6) only hides the bug and must not be treated as the fix.","created_at":"2026-07-07T11:31:28.801356242Z"}]} +{"id":"bf-4iewr","title":"Resolve the REJECT/token path so tokenless sim nodes are accepted","description":"Second step of the chain (depends on bf-3ud7e build). With the binary built, ensure the hardware-free mothership (bf-3zll) does NOT reject tokenless spaxel-sim nodes.\n\nSource of truth: mothership/internal/ingestion/server.go ~lines 506-525. If a token validator is configured (SetTokenValidator) AND the migration window is closed (SetMigrationDeadline zero or past), every tokenless sim node is rejected via sendReject with reason 'invalid_token' and disconnected — this is the 'REJECT'/401/403 failure mode.\n\n## Scope\n- Determine whether the local/test mothership wires a token validator and what the migration deadline is.\n- If a validator is active for the sim run, disable it (unset validator), open the migration window, or have the sim supply valid tokens — whichever is correct for the hardware-free build.\n- Verify by running the sim (1-2 nodes) and grepping mothership stderr.\n\n## Acceptance Criteria\n- [ ] Root cause of any REJECT identified (validator configured? migration window closed?).\n- [ ] Mothership configured so tokenless sim nodes connect without rejection.\n- [ ] When the sim connects, mothership stderr shows zero occurrences of 'rejected' / 'REJECT' / 'invalid_token' / HTTP 401 / 403.","design":"","acceptance_criteria":"","notes":"","status":"open","priority":2,"issue_type":"task","created_at":"2026-07-07T05:19:51.181529765Z","updated_at":"2026-07-07T09:52:00.017666181Z","source_repo":".","compaction_level":0,"labels":["deferred","split-child","umbrella"],"dependencies":[{"issue_id":"bf-4iewr","depends_on_id":"bf-3ud7e","type":"blocks","created_at":"2026-07-07T05:20:08.389583352Z","created_by":"cli","thread_id":""},{"issue_id":"bf-4iewr","depends_on_id":"bf-601bz","type":"blocks","created_at":"2026-07-07T09:50:16.400577464Z","created_by":"cli","thread_id":""}],"comments":[{"id":110,"issue_id":"bf-4iewr","author":"cli","text":"[bf-34lwt root-cause] TOKENLESS-SIM REJECT PATH — definitive finding (citations are full repo-relative paths; the task body's cmd/* maps to mothership/cmd/*).\n\nTL;DR: current 'no reject' is a 24h MIGRATION-WINDOW MASK, not a fix. The validator is wired unconditionally, but the sim presents its token only as an HTTP header the mothership never reads, so hello.Token is always empty and tokenOK is always false. The only thing keeping sim nodes connected is the default 24h window; the moment it closes, every sim node is sendReject-ed (invalid_token) and disconnected.\n\n1) Validator wired UNCONDITIONALLY: mothership/cmd/mothership/main.go:4494 ingestSrv.SetTokenValidator(provSrv.ValidateToken) — no config/build gate. Only the *deadline* is gated (main.go:4495 if cfg.MigrationWindowHours > 0). So s.tokenValidator is always non-nil. => 'validator configured?' is always YES.\n\n2) Validator reads hello.Token only: mothership/internal/ingestion/server.go:513 tokenOK := hello.Token != \"\" && validator(hello.MAC, hello.Token). validator = ValidateToken(mac,token) (SetTokenValidator at server.go:321-327). hello.Token is JSON 'token,omitempty' (message.go:22).\n\n3) Sim sends token ONLY as X-Spaxel-Token HTTP header, NOT in hello body: mothership/cmd/sim/main.go:633 + mothership/cmd/sim/scenario.go:318 headers.Set(\"X-Spaxel-Token\", token). Hello body map (main.go:652-665 / scenario.go:327-340) has type/mac/firmware_version/capabilities/chip/flash_mb/uptime_ms/wifi_rssi/ip/pos_* — NO 'token' key => hello.Token deserializes to \"\".\n\n4) Mothership NEVER reads that header => token-supply path is DEAD. grep 'X-Spaxel-Token' mothership/ returns ONLY sim files (cmd/sim/*); zero hits in cmd/mothership or internal. The WS upgrade handler reads nothing off the request: server.go:469 s.upgrader.Upgrade(w, r, nil) inside HandleNodeWS (server.go:455) — no r.Header.Get. (All other Header.Get/Set under internal/ are outbound webhook/notify or tests.) Consequence: hello.Token == \"\" for every sim node => tokenOK always false, regardless of the provisioned token. The parent's 'have the sim supply valid tokens' option is currently NON-FUNCTIONAL.\n\n5) Only the migration window accepts sim nodes: server.go:515 if !deadline.IsZero() && time.Now().Before(deadline) => nc.Unpaired = true. MigrationWindowHours DEFAULTS to 24 (config.go:139). Fresh boot sets deadline=now+24h (main.go:4496) => window open => sim nodes accepted as Unpaired, no reject. Matches PROGRESS.md bf-3hji.\n\n6) REJECT fires ONLY when window closed. Two cases: (a) SPAXEL_MIGRATION_WINDOW_HOURS=0 => main.go:4495 false => SetMigrationDeadline never called => deadline IsZero() => server.go:515 !IsZero() false => reject branch; (b) uptime > 24h => Now().Before(deadline) false => reject branch. Either way hello.Token==\"\" => server.go:521 'missing token' => server.go:525 sendReject(conn,\"invalid_token\") => server.go:526 conn.Close() (sendReject at server.go:841-846).\n\nEXPLICIT STATEMENTS (acceptance criteria):\n- Current 'no reject' is a 24h-window MASK, not a real fix: it vanishes under SPAXEL_MIGRATION_WINDOW_HOURS=0 or after 24h uptime.\n- The token-supply path is currently DEAD: header ignored, hello.Token empty, tokenOK always false.\n\nFull write-up + fix options (A keep-mask / B make-token-real via header-read-in-HandleNodeWS-or-body-token / C gate-the-validator): docs/notes/token-reject-root-cause.md (committed with bf-34lwt). The remaining bf-4iewr children should pick exactly one of A/B/C; the two invariants for the final state: 'no reject' must not depend on the 24h mask, and the chosen token contract must be honored end-to-end by both sim and mothership.","created_at":"2026-07-07T09:59:00.629760880Z"},{"id":111,"issue_id":"bf-4iewr","author":"cli","text":"ROOT CAUSE (bf-34lwt, first link of this split) — confirmed against current source. Full write-up: docs/notes/token-reject-root-cause.md (commit 5c6a692).\n\nAll module paths are under mothership/; the task body dropped that prefix.\n\n1. VALIDATOR WIRED UNCONDITIONALLY: mothership/cmd/mothership/main.go:4494 ingestSrv.SetTokenValidator(provSrv.ValidateToken) — no config/build gate, so 'validator configured?' is always YES. Only the deadline is gated (main.go:4495 if cfg.MigrationWindowHours > 0).\n\n2. VALIDATOR READS hello.Token ONLY: mothership/internal/ingestion/server.go:513 tokenOK := hello.Token != \"\" && validator(hello.MAC, hello.Token). ValidateToken (mothership/internal/provisioning/server.go:135) does an HMAC constant-time compare on the token STRING — it never touches any HTTP header. hello.Token maps to JSON field token,omitempty (mothership/internal/ingestion/message.go:22).\n\n3. SIM SENDS TOKEN ONLY AS X-Spaxel-Token HEADER, never in the hello body. Both sim copies: mothership/cmd/sim/main.go:633 and mothership/cmd/sim/scenario.go:318 set headers.Set(\"X-Spaxel-Token\", token) on the WS dial; standalone module cmd/sim/main.go:313 does the same. None of the hello bodies include a 'token' key (mothership/cmd/sim/main.go:652-665; cmd/sim/main.go:348-359; scenario.go:331-341), so hello.Token deserializes to \"\" from either binary.\n\n4. MOTHERSHIP NEVER READS THE HEADER -> TOKEN-SUPPLY PATH IS DEAD. grep 'X-Spaxel-Token' over mothership/ returns only sim files; the WS upgrade handler upgrades UNCONDITIONALLY at server.go:469 s.upgrader.Upgrade(w, r, nil) with no r.Header.Get(...) and no HTTP-401 path (HandleNodeWS, server.go:455). So hello.Token==\"\" -> server.go:513 short-circuits to tokenOK=false regardless of the provisioned token. The 'have the sim supply valid tokens' option in THIS bead is therefore currently NON-FUNCTIONAL — the token goes to a header nobody reads.\n\n5. THE ONLY THING ACCEPTING SIM NODES IS THE MIGRATION WINDOW. server.go:514-519: if !tokenOK, accept as Unpaired only when !deadline.IsZero() && Now().Before(deadline). MigrationWindowHours DEFAULTS TO 24 (mothership/internal/config/config.go:139), so a fresh boot sets deadline=now+24h (main.go:4496) -> every tokenless sim node accepted as Unpaired, no reject (matches PROGRESS.md bf-3hji). Corroborated by the e2e harness itself (mothership/tests/e2e/e2e_test.go:86-94), which pins SPAXEL_MIGRATION_WINDOW_HOURS=24 and documents exactly this.\n\n6. REJECT FIRES ONLY WHEN THE WINDOW IS CLOSED (server.go:519-528): (a) SPAXEL_MIGRATION_WINDOW_HOURS=0 -> SetMigrationDeadline never called (main.go:4495 guard) -> deadline.IsZero() true -> reject; (b) uptime > 24h -> Now().Before(deadline) false -> reject. In both, hello.Token==\"\" logs 'Node rejected: missing token' (server.go:520-521), sendReject(conn,\"invalid_token\") (server.go:525, def 841-846), conn.Close() (526).\n\nBONUS: there is NO HTTP-401 path at all — the upgrade always succeeds (101). So AS-7 quality gate #7 ('node without token must get HTTP 401 and be rejected') is currently UNSATISFIABLE under default config; it's part of the same dead-path/accident.\n\nSUMMARY (for this bead's acceptance criteria):\n- 'No reject' is a 24h-window MASK, not a real fix — it vanishes under SPAXEL_MIGRATION_WINDOW_HOURS=0 or after 24h uptime.\n- The token-supply path is currently DEAD: header ignored (server.go:469), hello.Token empty (sim hello bodies), so tokenOK is always false (server.go:513).\n- Three independent fixes for the sibling children: (A) keep the mask but make it deliberate for sim/test; (B) make the token path real (read X-Spaxel-Token in HandleNodeWS, or have the sim put token in the hello body); (C) gate the validator off for the hardware-free build. See the doc for the trade-offs.","created_at":"2026-07-07T10:20:40.536429191Z"},{"id":115,"issue_id":"bf-4iewr","author":"cli","text":"## Root cause: tokenless-sim REJECT in the WS hello path (bf-34lwt synthesis)\n\nSynthesis of the three bf-34lwt children (bf-5ig3e, bf-29wyl, bf-2hdbg). All six facts re-confirmed against current source (repo-relative paths):\n\n**1. Validator is wired UNCONDITIONALLY** — `mothership/cmd/mothership/main.go:4494`\n`ingestSrv.SetTokenValidator(provSrv.ValidateToken)` runs at every boot with NO env/build guard, so \"is a validator configured?\" is always YES. (The migration deadline at lines 4495-4498 is wired separately, only when `MigrationWindowHours > 0`.)\n\n**2. Validator reads `hello.Token` ONLY** — `mothership/internal/ingestion/server.go:513`\n`tokenOK := hello.Token != \"\" && validator(hello.MAC, hello.Token)`. Accept/reject hinges entirely on the JSON body's `token` field; no HTTP header participates in the check.\n\n**3. Sim sends its token ONLY as the `X-Spaxel-Token` header** — `cmd/sim/main.go:313`\n`reqHeader.Set(\"X-Spaxel-Token\", token)`. The hello map (`cmd/sim/main.go:348-359`) carries type/mac/firmware_version/capabilities/chip/flash_mb/uptime_ms/pos_{x,y,z} and NO `token` field. `cmd/sim/scenario.go` sets no header or body token either.\n\n**4. Mothership NEVER reads that header** — `grep -rn 'X-Spaxel-Token' mothership/cmd/mothership/ mothership/internal/` returns zero hits; every `Header.Get` under `mothership/internal/` is an outbound notification client (webhook/ntfy/pushover/mqtt), none on the WS node path. Consequence: for sim nodes `hello.Token` is always `\"\"` -> `tokenOK` is always false.\n\n**5. The migration window — not tokens — is the only thing accepting sim nodes** — `mothership/internal/config/config.go:139` (`cfg.MigrationWindowHours = 24`, the default), applied at `mothership/cmd/mothership/main.go:4495-4498` to set a deadline 24h out. The acceptance branch is `mothership/internal/ingestion/server.go:515`: `if !deadline.IsZero() && time.Now().Before(deadline)` -> node admitted and flagged `Unpaired` (matches PROGRESS.md bf-3hji).\n\n**6. REJECT fires ONLY when the window is closed** — `mothership/internal/ingestion/server.go:519-527`, the else branch: logs \"missing token\"/\"invalid token\" -> `sendReject(conn, \"invalid_token\")` -> `conn.Close()`. Two triggers: (a) `SPAXEL_MIGRATION_WINDOW_HOURS=0` -> the `main.go:4495` gate is skipped -> deadline stays zero-value -> `deadline.IsZero()` true -> reject; (b) uptime > 24h -> `time.Now().After(deadline)` -> reject.\n\n## Two required takeaways\n\n- **\"No reject\" is a 24h-window MASK, not a real fix.** A default-boot mothership rejects nothing only because the 24h migration window is open. The instant it closes (`SPAXEL_MIGRATION_WINDOW_HOURS=0`, or any process alive >24h), every tokenless sim node hits the `invalid_token` reject branch. The hardware-free build is green by accident of uptime, not by design.\n\n- **The token-supply path is currently DEAD.** The parent bead's \"supply valid tokens\" option is non-functional today: the sim's token lives only in the unread `X-Spaxel-Token` header, while the validator checks only the always-empty `hello.Token` body field — so even a correctly-provisioned token cannot make `tokenOK` true.\n\n## Precise target for the remaining bf-4iewr children\n\nRepair the dead token-supply path (point 4): either (a) populate `hello.Token` in the sim's hello body, or (b) have the mothership read `X-Spaxel-Token` on the WS upgrade. Opening/lengthening the migration window (points 5-6) only hides the bug and must not be treated as the fix.","created_at":"2026-07-07T11:31:28.801356242Z"},{"id":118,"issue_id":"bf-4iewr","author":"cli","text":"[bf-34lwt re-dispatch VERIFICATION @ HEAD 9df77e3] — this is a re-dispatch of an already-complete investigation. The authoritative write-up (docs/notes/token-reject-root-cause.md, commit 5c6a692) and comments [110]/[111] are accurate. I re-verified all 6 points against the current source: ZERO line drift. Citations confirmed verbatim:\n\n1. Validator wired UNCONDITIONALLY — mothership/cmd/mothership/main.go:4494 `ingestSrv.SetTokenValidator(provSrv.ValidateToken)`. No config/build gate; only the deadline is gated (main.go:4495 `if cfg.MigrationWindowHours > 0`).\n2. Validator reads hello.Token ONLY — mothership/internal/ingestion/server.go:513 `tokenOK := hello.Token != \"\" && validator(hello.MAC, hello.Token)`. hello.Token = JSON `token,omitempty` (mothership/internal/ingestion/message.go:22).\n3. Sim sends token ONLY as X-Spaxel-Token header, never in hello body — THREE confirmed set sites: cmd/sim/main.go:313, mothership/cmd/sim/main.go:633, mothership/cmd/sim/scenario.go:318. `grep '\"token\"'` over all four hello bodies returns only the --token flag defs, never a JSON key => hello.Token deserializes to \"\".\n4. Mothership NEVER reads that header => token-supply path DEAD. `grep -rn 'X-Spaxel-Token' mothership/` returns only the two sim files; HandleNodeWS (server.go:455) upgrades at server.go:469 `s.upgrader.Upgrade(w, r, nil)` with no r.Header.Get. => tokenOK always false.\n5. Only the migration window accepts sim nodes — server.go:515 `if !deadline.IsZero() && time.Now().Before(deadline)` => Unpaired. MigrationWindowHours DEFAULTS to 24 (mothership/internal/config/config.go:139).\n6. REJECT fires ONLY when window closed — SPAXEL_MIGRATION_WINDOW_HOURS=0 (deadline never set => IsZero() => reject) OR uptime > 24h (Now().After(deadline) => reject); both log 'missing token' (server.go:520-521) then sendReject(conn,\"invalid_token\") (server.go:525, def 841-846).\n\nCORRECTION to comment [115] point 3: it claims \"cmd/sim/scenario.go sets no header or body token either\" — that path does not exist (the root cmd/sim/ module has only main.go). The scenario code that sets the header is mothership/cmd/sim/scenario.go:318, which DOES set `headers.Set(\"X-Spaxel-Token\", token)`. Both sim copies set the header; the accurate list is the three sites above (comments [110]/[111] and the doc already have this right).\n\nREQUIRED EXPLICIT STATEMENTS (acceptance criteria — re-affirmed):\n- Current \"no reject\" is a 24h-window MASK, not a real fix: it vanishes under SPAXEL_MIGRATION_WINDOW_HOURS=0 or after 24h uptime.\n- The token-supply path is currently DEAD: header ignored (server.go:469), hello.Token empty (sim hello bodies), tokenOK always false (server.go:513).\n\nAuthoritative write-up + the three fix options (A/B/C): docs/notes/token-reject-root-cause.md.","created_at":"2026-07-07T11:49:03.817378590Z"}]} {"id":"bf-4ly4","title":"Search JavaScript files for blob-shaped object literals","description":"## Objective\nSearch all JavaScript files for blob-shaped object literals.\n\n## Scope\n- Search only .js and .jsx files\n- Use the pattern defined in the parent bead\n- Identify all locations with blob structure\n\n## Acceptance Criteria\n- [ ] All JavaScript files searched\n- [ ] Blob literal locations identified with file paths and line numbers\n- [ ] Results saved to notes/bf-26ta-javascript-results.md\n- [ ] Each match includes context snippet showing the blob structure","design":"","acceptance_criteria":"","notes":"","status":"closed","priority":2,"issue_type":"task","assignee":"claude-code-glm-4.7-alpha","created_at":"2026-07-06T06:02:03.135320465Z","updated_at":"2026-07-06T06:18:55.359674524Z","closed_at":"2026-07-06T06:18:55.359674524Z","close_reason":"Completed","source_repo":".","compaction_level":0,"labels":["split-child"],"dependencies":[{"issue_id":"bf-4ly4","depends_on_id":"bf-1rzd","type":"blocks","created_at":"2026-07-06T06:02:07.318031216Z","created_by":"cli","thread_id":""}]} {"id":"bf-4mle6","title":"Make tokenless-sim acceptance durable for the hardware-free build","description":"Third link of the bf-4iewr split (depends on the token-bridge fix). Today the hardware-free sim is accepted only because MigrationWindowHours defaults to 24h — a value that silently EXPIRES (uptime > 24h) and that any `SPAXEL_MIGRATION_WINDOW_HOURS=0` boot disables. Make the HW-free build's auth policy explicit and durable so the sim never regresses.\n\n## Scope\nDecide and implement the supported policy for the hardware-free build (justify the choice; update PROGRESS.md \"Hardware-Free Simulator -> CSI Streaming\" section):\n- (A) Sim ALWAYS supplies a valid token (now functional via the previous child) -> window-independent acceptance. Verify provisionToken() succeeds against /api/provision and the token validates; OR\n- (B) E2E harness / documented boot sets the migration window EXPLICITLY and asserts it is open (no reliance on the implicit 24h default); OR\n- (C) A build/test gate that leaves the validator unwired for the sim path.\n\nEnsure the e2e harness (`mothership/tests/e2e/e2e_test.go`, TestHarness.Start) and the PROGRESS.md human-run commands reflect the chosen policy.\n\n## Acceptance Criteria\n- [ ] A documented, reproducible auth policy for the HW-free build in PROGRESS.md.\n- [ ] Behavior is IDENTICAL whether SPAXEL_MIGRATION_WINDOW_HOURS is 0, default (24), or large — sim nodes connect and stream in all three (i.e., no implicit dependence on the 24h default).\n- [ ] E2E harness no longer silently relies on the implicit 24h window.","design":"","acceptance_criteria":"","notes":"","status":"open","priority":2,"issue_type":"task","created_at":"2026-07-07T09:49:42.516790738Z","updated_at":"2026-07-07T09:49:42.516790738Z","source_repo":".","compaction_level":0,"labels":["split-child"],"dependencies":[{"issue_id":"bf-4mle6","depends_on_id":"bf-1o7qi","type":"blocks","created_at":"2026-07-07T09:50:16.344782807Z","created_by":"cli","thread_id":""}]} {"id":"bf-4oiz","title":"Verify and fix registry bridge position handling","description":"Ensure that positions from the simulator are correctly received and processed by internal/simulator/registry_bridge.go.\n\nScope:\n- Verify that registry_bridge.go receives position data from simulator\n- Check that positions are correctly formatted for registry consumption\n- Fix any missing or incorrect wiring\n\nAcceptance criteria:\n- registry_bridge.go receives position data from simulator\n- Positions are correctly formatted for registry\n- Any missing wiring is added\n- Positions flow from simulator to registry_bridge.go correctly","design":"","acceptance_criteria":"","notes":"","status":"closed","priority":2,"issue_type":"task","assignee":"claude-code-glm-5-alpha","created_at":"2026-07-06T04:56:17.285977814Z","updated_at":"2026-07-06T16:23:13.948046549Z","closed_at":"2026-07-06T16:23:13.948046549Z","close_reason":"Completed","source_repo":".","compaction_level":0,"labels":["deferred","split-child","umbrella"],"dependencies":[{"issue_id":"bf-4oiz","depends_on_id":"bf-3ifj","type":"blocks","created_at":"2026-07-06T04:56:20.008223894Z","created_by":"cli","thread_id":""},{"issue_id":"bf-4oiz","depends_on_id":"bf-5dpu","type":"blocks","created_at":"2026-07-06T05:24:19.551325547Z","created_by":"cli","thread_id":""},{"issue_id":"bf-4oiz","depends_on_id":"bf-69ym","type":"blocks","created_at":"2026-07-06T13:19:39.307840995Z","created_by":"cli","thread_id":""}],"comments":[{"id":69,"issue_id":"bf-4oiz","author":"cli","text":"Verified the registry-bridge position flow end-to-end for close-out. The compile blocker flagged by bf-4pqj (*fleet.Registry not satisfying simulator.RegistryNodeAdapter) was resolved by bf-5lii's fleetRegistryAdapter + immediate SyncToRegistry on AddNode/UpdateNode; bf-69ym added two real-SQLite-registry e2e tests. Re-verified: go build ./cmd/mothership OK, go vet ./... clean, go test ./... all green (simulator + api fresh). The two virtual-node position mutation sites (simulator.go CreateVirtualNode/UpdateNodePosition) are the only writers and both trigger immediate sync; 30s ticker stays as safety net. Acceptance criteria met. Doc-only close-out commit fd7918f.","created_at":"2026-07-06T16:22:53.997318825Z"}]} @@ -92,7 +92,7 @@ {"id":"bf-4q5w","title":"Wire fusion engine SetNodePosition — nodes never positioned, sim/IO-6 produces 0 blobs","description":"Found during investigation of bf-3v39 (presence-detection verification).\n\nSYMPTOM: E2E tests (tests/e2e TestFullE2EIntegration, TestDetectionEvents) and the IO-6 hard-gate scenario produce ZERO detection events / blobs even with 4 nodes + 2 walkers running spaxel-sim. The fusion loop runs at 10Hz but never emits a peak.\n\nROOT CAUSE: internal/fusion/fusion.go Engine.SetNodePosition(mac,x,y,z) is defined but NEVER CALLED in the non-test wiring (grep shows only the definition + unrelated SetPositionProvider helpers). Nodes default to pos=(0,0,1) in the DB (internal/db/migrations.go:143-145). With all nodes co-located, the Fresnel zone excess path length |P-T|+|P-R|-|T-R| collapses to ~0 everywhere, so the accumulation grid has no meaningful peaks and confidence is undefined -> no blobs.\n\nWHY IT MATTERS: This breaks the IO-6 release hard-gate ('walker produces a tracked blob') and means the eventual physical presence-detection validation (bf-3v39) will also show no blobs regardless of hardware, until node positions feed the fusion engine.\n\nLIKELY FIX: In cmd/mothership/main.go, call fusion Engine.SetNodePosition for each node (a) at startup from the DB nodes table, and (b) whenever a node's position changes (PATCH /api/nodes/:mac position handler) or a node connects/registers. The spaxel-sim default node placement (createVirtualNodes) and/or a default zone may also need to seed positions so the simulator path exercises real geometry. Verify by re-running TestFullE2EIntegration and asserting len(blobs) > 0 during the run (the test currently ducks this with 'we verify the API responds correctly rather than asserting a minimum count').","design":"","acceptance_criteria":"","notes":"","status":"open","priority":1,"issue_type":"task","created_at":"2026-07-03T00:11:33.217563362Z","updated_at":"2026-07-03T19:23:52.843063709Z","source_repo":".","compaction_level":0,"labels":["bug","deferred","fusion","localization","umbrella"],"dependencies":[{"issue_id":"bf-4q5w","depends_on_id":"bf-5jeo","type":"blocks","created_at":"2026-07-03T19:23:17.400573758Z","created_by":"cli","thread_id":""}]} {"id":"bf-4qto","title":"Add default values for blob identity fields","description":"## Objective\nEnsure all blob identity fields have appropriate default or undefined values across all contexts.\n\n## Scope\n- Review all updated blob creation code\n- Add proper undefined or default values where needed\n- Ensure consistency in default value handling\n\n## Acceptance Criteria\n- [ ] All personName fields set to undefined or appropriate default\n- [ ] All assignedColor fields set to undefined or appropriate default \n- [ ] All identityResolved fields set to undefined or appropriate default\n- [ ] Default handling is consistent across codebase","design":"","acceptance_criteria":"","notes":"","status":"closed","priority":2,"issue_type":"task","assignee":"claude-code-glm-5-alpha","created_at":"2026-07-06T05:11:37.452698658Z","updated_at":"2026-07-06T23:16:24.939887312Z","closed_at":"2026-07-06T23:16:24.939887312Z","close_reason":"Completed","source_repo":".","compaction_level":0,"labels":["deferred","split-child"],"dependencies":[{"issue_id":"bf-4qto","depends_on_id":"bf-5151","type":"blocks","created_at":"2026-07-06T05:11:57.963969215Z","created_by":"cli","thread_id":""}],"comments":[{"id":99,"issue_id":"bf-4qto","author":"cli","text":"Closed: propagated the three canonical identity fields (PersonName, AssignedColor, IdentityResolved) through the three projection sites in the fusion loop that were silently dropping them (explainability.BlobSnapshot, automation.TrackedBlob, volume.BlobPos in cmd/mothership/main.go). api.Track (tracks.go) and dashboard.blobJSON (hub.go) already propagated them, so this makes default-handling consistent across all five projection contexts. Fields stay at Go zero values (empty strings, nil *bool) which omitempty serializes as omitted = undefined in JS — the consistent default the acceptance criteria require. Also committed the four missing companion serialization suites (tracker.Blob, tracking.Blob, volume.BlobPos, explainability.BlobSnapshot) asserting: zero-value omits all three, resolved identity emits camelCase keys, and the *bool tri-state round-trips. BLE sidecar population is a separate follow-up bead. Verified gofmt/vet/test clean. Commit ca7a0f2.","created_at":"2026-07-06T23:13:25.604774018Z"},{"id":100,"issue_id":"bf-4qto","author":"cli","text":"Re-verification (auto-split dispatch refused): the dispatcher fired a forced split on failure-count:3, but two independent checks reject decomposition. (1) The dispatch's own splittability pre-check returned {\"splittable\": false}. (2) The bead's objective is already met in code and committed as ca7a0f2: every identity-bearing blob type (tracker.Blob, tracking.Blob, volume.BlobPos, explainability.BlobSnapshot, api.Track, dashboard.blobJSON, automation.TrackedBlob, signal.TrackedBlob) carries PersonName/AssignedColor as empty string + omitempty and IdentityResolved as nil *bool + omitempty — both serialize as omitted = undefined in JS, the uniform default the AC require. All four identity_fields_test.go suites pass (zero-value omits all three; resolved identity emits camelCase keys; *bool tri-state round-trips). Prior comment [99] already closed this with the same rationale; the close did not persist (known br close flakiness), so re-closing. Splitting would manufacture child beads for already-done work — not done.","created_at":"2026-07-06T23:15:47.700495945Z"}]} {"id":"bf-4qwmy","title":"Simulator: GDOP overlay + shopping-list output (coverage gaps, recommended node count + positions)","description":"","design":"","acceptance_criteria":"","notes":"","status":"closed","priority":2,"issue_type":"task","assignee":"claude-code-glm-4.7-foxtrot","created_at":"2026-05-06T00:40:16.874114137Z","updated_at":"2026-05-06T03:18:31.808932181Z","closed_at":"2026-05-06T03:18:31.808932181Z","close_reason":"Completed","source_system":"","source_repo":".","deleted_by":"","delete_reason":"","original_type":"","compaction_level":0,"compacted_at_commit":"","sender":""} -{"id":"bf-4th1x","title":"Post synthesized REJECT/token root-cause finding as comment on bf-4iewr","description":"Fourth and final link of the bf-34lwt split. Synthesize the confirmed facts from children 1-3 into the definitive root-cause finding and post it as a comment on the grandparent bf-4iewr, so the remaining bf-4iewr children have a precise target.\n\n## Scope\n- Assemble the confirmed file:line citations for points 1-6 (validator wiring, hello.Token check, sim header-only token, ignored header, 24h default migration window, window-closed reject branch).\n- Post the synthesis as a comment on bf-4iewr using `br comments add bf-4iewr ...`.\n\n## Acceptance Criteria\n- [ ] A written root-cause finding posted as a comment on bf-4iewr, confirming points 1-6 with file:line citations.\n- [ ] Explicit statement in the comment that current 'no reject' is a 24h-window MASK, not a real fix.\n- [ ] Explicit statement in the comment that the token-supply path is currently dead (header ignored, hello.Token empty).","design":"","acceptance_criteria":"","notes":"","status":"closed","priority":2,"issue_type":"task","assignee":"claude-code-glm-5-alpha","created_at":"2026-07-07T10:24:19.637847909Z","updated_at":"2026-07-07T11:40:57.885551472Z","closed_at":"2026-07-07T11:40:57.885551472Z","close_reason":"Completed","source_repo":".","compaction_level":0,"labels":["failure-count:1","split-child"],"dependencies":[{"issue_id":"bf-4th1x","depends_on_id":"bf-2hdbg","type":"blocks","created_at":"2026-07-07T10:24:42.516504023Z","created_by":"cli","thread_id":""}],"comments":[{"id":116,"issue_id":"bf-4th1x","author":"cli","text":"Synthesized the three bf-34lwt children (bf-5ig3e, bf-29wyl, bf-2hdbg) into a definitive root-cause comment on bf-4iewr (comment 115), with file:line citations for all six confirmed points (validator wiring at main.go:4494, hello.Token check at server.go:513, sim header-only token at cmd/sim/main.go:313, unread header confirmed by zero grep hits, 24h default window at config.go:139, reject branch at server.go:519-527). States explicitly that 'no reject' is a 24h-window MASK and that the token-supply path is currently dead. Bead checkpoint committed and pushed (005ae09).","created_at":"2026-07-07T11:36:22.229183037Z"},{"id":117,"issue_id":"bf-4th1x","author":"cli","text":"DONE (re-dispatch of a run that posted the comment but failed to close). Synthesis comment [115] is posted on the grandparent bf-4iewr and verified accurate against current source — all six root-cause points with file:line citations:\n\n1. Validator wired unconditionally: mothership/cmd/mothership/main.go:4494.\n2. Validator reads hello.Token only: mothership/internal/ingestion/server.go:513.\n3. Sim sends token only as X-Spaxel-Token header (no body token): cmd/sim/main.go:313 (+ mothership/cmd/sim/{main.go:633,scenario.go:318}).\n4. Mothership never reads that header -> token-supply path DEAD: server.go:469 upgrades unconditionally; zero X-Spaxel-Token hits in cmd/mothership + internal.\n5. Default 24h migration window is the only thing accepting sim nodes: config.go:139; main.go:4495-4498; server.go:515.\n6. REJECT fires only when window closed: server.go:519-527 (else branch) -> sendReject (842) -> conn.Close.\n\nAcceptance criteria all met: (a) points 1-6 posted with citations; (b) explicit 'no reject' is a 24h-window MASK not a fix; (c) explicit token-supply path is dead (header ignored, hello.Token empty). Commit 005ae09 pushed to origin/main.","created_at":"2026-07-07T11:39:56.282540111Z"}]} +{"id":"bf-4th1x","title":"Post synthesized REJECT/token root-cause finding as comment on bf-4iewr","description":"Fourth and final link of the bf-34lwt split. Synthesize the confirmed facts from children 1-3 into the definitive root-cause finding and post it as a comment on the grandparent bf-4iewr, so the remaining bf-4iewr children have a precise target.\n\n## Scope\n- Assemble the confirmed file:line citations for points 1-6 (validator wiring, hello.Token check, sim header-only token, ignored header, 24h default migration window, window-closed reject branch).\n- Post the synthesis as a comment on bf-4iewr using `br comments add bf-4iewr ...`.\n\n## Acceptance Criteria\n- [ ] A written root-cause finding posted as a comment on bf-4iewr, confirming points 1-6 with file:line citations.\n- [ ] Explicit statement in the comment that current 'no reject' is a 24h-window MASK, not a real fix.\n- [ ] Explicit statement in the comment that the token-supply path is currently dead (header ignored, hello.Token empty).","design":"","acceptance_criteria":"","notes":"","status":"closed","priority":2,"issue_type":"task","assignee":"claude-code-glm-5-alpha","created_at":"2026-07-07T10:24:19.637847909Z","updated_at":"2026-07-07T11:40:57.885551472Z","closed_at":"2026-07-07T11:40:57.885551472Z","close_reason":"Completed","source_repo":".","compaction_level":0,"labels":["split-child"],"dependencies":[{"issue_id":"bf-4th1x","depends_on_id":"bf-2hdbg","type":"blocks","created_at":"2026-07-07T10:24:42.516504023Z","created_by":"cli","thread_id":""}],"comments":[{"id":116,"issue_id":"bf-4th1x","author":"cli","text":"Synthesized the three bf-34lwt children (bf-5ig3e, bf-29wyl, bf-2hdbg) into a definitive root-cause comment on bf-4iewr (comment 115), with file:line citations for all six confirmed points (validator wiring at main.go:4494, hello.Token check at server.go:513, sim header-only token at cmd/sim/main.go:313, unread header confirmed by zero grep hits, 24h default window at config.go:139, reject branch at server.go:519-527). States explicitly that 'no reject' is a 24h-window MASK and that the token-supply path is currently dead. Bead checkpoint committed and pushed (005ae09).","created_at":"2026-07-07T11:36:22.229183037Z"},{"id":117,"issue_id":"bf-4th1x","author":"cli","text":"DONE (re-dispatch of a run that posted the comment but failed to close). Synthesis comment [115] is posted on the grandparent bf-4iewr and verified accurate against current source — all six root-cause points with file:line citations:\n\n1. Validator wired unconditionally: mothership/cmd/mothership/main.go:4494.\n2. Validator reads hello.Token only: mothership/internal/ingestion/server.go:513.\n3. Sim sends token only as X-Spaxel-Token header (no body token): cmd/sim/main.go:313 (+ mothership/cmd/sim/{main.go:633,scenario.go:318}).\n4. Mothership never reads that header -> token-supply path DEAD: server.go:469 upgrades unconditionally; zero X-Spaxel-Token hits in cmd/mothership + internal.\n5. Default 24h migration window is the only thing accepting sim nodes: config.go:139; main.go:4495-4498; server.go:515.\n6. REJECT fires only when window closed: server.go:519-527 (else branch) -> sendReject (842) -> conn.Close.\n\nAcceptance criteria all met: (a) points 1-6 posted with citations; (b) explicit 'no reject' is a 24h-window MASK not a fix; (c) explicit token-supply path is dead (header ignored, hello.Token empty). Commit 005ae09 pushed to origin/main.","created_at":"2026-07-07T11:39:56.282540111Z"}]} {"id":"bf-4truh","title":"Comprehensive notification system tests (open bead spaxel-40tl expansion)","description":"Open bead spaxel-40tl 'Write comprehensive tests for notification system' is open. The notify package (internal/notify/) has ntfy.go, pushover.go, webhook.go but tests are missing or incomplete. Needs tests covering: batching logic (30s dedup window), quiet hours gate (suppress non-critical during quiet window), morning digest aggregation, delivery retry logic, channel enable/disable, test-notification endpoint, and notification history API. The existing service_enhanced.go has complex batching logic that needs coverage.","design":"","acceptance_criteria":"","notes":"","status":"closed","priority":2,"issue_type":"task","created_at":"2026-05-02T18:26:14.365679205Z","updated_at":"2026-05-02T18:26:51.067231767Z","closed_at":"2026-05-02T18:26:51.067231767Z","close_reason":"Duplicate of existing open bead spaxel-40tl which already comprehensively tracks notification system tests","source_repo":".","compaction_level":0} {"id":"bf-4wwt","title":"Inventory blob projection and derived types with construction sites","description":"Scope: the blob-shaped PROJECTION/derived types — read-only views built FROM a tracked blob for a subsystem (not new tracked entities). Any field added to a tracked blob that a projection copies must be propagated at each construction site.\n\nProjection types to document (definition file:line + every construction boundary site):\n- explainability.BlobSnapshot, explainability.BlobExplanation\n- falldetect.BlobSnapshot (distinct type, shared name)\n- volume.BlobState, volume.BlobPos, api.BlobPos\n- tracking.BlobEvent\n- replay.BlobUpdate\n- simulator.BlobResult\n\nReference (already enumerated — RE-VERIFY, do not re-discover): notes/bf-4bhd.md \"Related Blob-shaped projection types\" table (9 types, ~15 construction sites across handler.go, detector.go, pipeline.go, simulator/engine.go, tracking/tracker.go, volume/shape.go).\n\nAcceptance Criteria:\n- [ ] Each projection type definition recorded with file:line and which tracked-blob type it is built from\n- [ ] Every projection construction boundary site listed (file:line)\n- [ ] Re-locate moved sites with: grep -rn \"BlobSnapshot{|BlobState{|BlobPos{|BlobUpdate{|BlobEvent{|BlobResult{|BlobExplanation{\" mothership/\n- [ ] All line numbers verified against current HEAD\n- [ ] Findings written to notes/bf--projections.md","design":"","acceptance_criteria":"","notes":"","status":"closed","priority":2,"issue_type":"task","assignee":"claude-code-glm-5-alpha","created_at":"2026-07-06T15:03:36.904550067Z","updated_at":"2026-07-06T19:39:55.785656755Z","closed_at":"2026-07-06T19:39:55.785656755Z","close_reason":"Completed","source_repo":".","compaction_level":0,"labels":["split-child"],"dependencies":[{"issue_id":"bf-4wwt","depends_on_id":"bf-5uzm","type":"blocks","created_at":"2026-07-06T15:04:21.277568858Z","created_by":"cli","thread_id":""}],"comments":[{"id":80,"issue_id":"bf-4wwt","author":"cli","text":"Re-verified all 9 blob-shaped projection/derived types and 15 non-test construction sites against current HEAD (68bd308) — every type definition (handler.go:27/95, detector.go:69, shape.go:139/1080, triggers.go:624, tracker.go:52, types.go:303, engine.go:80) and every construction site (grep -rnE of BlobSnapshot{/BlobState{/BlobPos{/BlobUpdate{/BlobEvent{/BlobResult{/BlobExplanation{) matches notes/bf-4wwt-projections.md exactly. Work committed in 68bd308 and pushed to origin/main. Findings written to notes/bf-4wwt-projections.md (companion to bf-4bhd): 9 types, 15 sites + 1 falldetect boundary (main.go:2288), with corrections noting api.BlobPos is test-only/dead, replay.BlobUpdate & simulator.BlobResult are synthetic (not blob-derived), and volume.BlobState's only blob-derived site is shape.go:575.","created_at":"2026-07-06T19:38:49.791215302Z"}]} {"id":"bf-4zio","title":"BLE-to-blob 3D visualization: per-person colored humanoid + name labels in scene","description":"Phase 6 item 30 requires the 3D dashboard to show identified persons as colored humanoid figures with name labels ('Alice is in Kitchen') instead of generic 'Blob #2' markers. The BLE registry (spaxel-2wg) is closed but the frontend visualization using resolved identities is not implemented. Update the Three.js scene to render identity-matched blobs as named humanoid meshes.","design":"","acceptance_criteria":"","notes":"","status":"open","priority":2,"issue_type":"task","created_at":"2026-05-26T22:14:42.798449947Z","updated_at":"2026-07-06T12:46:52.964790297Z","source_repo":".","compaction_level":0,"labels":["umbrella"],"dependencies":[{"issue_id":"bf-4zio","depends_on_id":"bf-3dip","type":"blocks","created_at":"2026-07-06T04:05:42.020469537Z","created_by":"cli","thread_id":""}]}