Commit graph

968 commits

Author SHA1 Message Date
jedarden
250056cb56 test(e2e): run harness under SPAXEL_MIGRATION_WINDOW_HOURS=0 (bf-qzrmq)
Isolate the window-independence harness change in TestHarness.Start: set
SPAXEL_MIGRATION_WINDOW_HOURS=0 (the strict window, not the open 24h
default) so the e2e no-reject path actually proves token-based admission
regardless of the migration window, instead of silently passing on the
implicit/explicit 24h crutch. A comment documents the bf-4mle6 / bf-qzrmq
policy and why spaxel-sim nodes connect on real HMAC tokens rather than
the window.

This is the commit-hygiene half of bf-qzrmq: it stages ONLY the env-var +
comment (concern A). The unrelated IO-6 hard-gate test code
(TestIO6HardGate_WalkerProducesTrackedBlob, CaptureIO6Diagnostics,
GetNodeRecords, GetStatus, io6RunMaxima + the "strings" import — concern B,
belonging to bf-4q5w) is deliberately left uncommitted in the working tree.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-07 12:28:10 -04:00
jedarden
9c38cbb9ef fix(bf-1o7qi): bridge X-Spaxel-Token header into hello.Token for validation
The token validator reads only hello.Token (server.go:537), but spaxel-sim and
firmware following the plan.md auth contract present their token solely as the
X-Spaxel-Token upgrade-request header, omitting it from the hello JSON body. The
WS upgrade handler never read that header, so hello.Token was always "" and
every such node fell through to the migration-window branch — the dead
token-supply path.

Fix (approach A, server-side): in HandleNodeWS, after parsing hello and before
the validation check, copy X-Spaxel-Token into hello.Token when the body omits
it. The body token wins when both are present, so existing body-token clients
are unaffected. This does not weaken the validator: an empty/invalid token is
still rejected once the migration window closes (SPAXEL_MIGRATION_WINDOW_HOURS=0
or deadline past).

Test TestTokenValidation_HeaderTokenBridged wires the real
provisioning.Server.ValidateToken (HMAC-SHA256(installSecret, mac)) and mints the
token through the real HandleProvision handler, proving the bridge end-to-end:
valid header token accepted + paired with window closed; empty/invalid rejected;
body-token-wins regression.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-07 12:28:10 -04:00
jedarden
5b0f4c179c docs(bf-34lwt): close — root-cause re-verified at HEAD 402bb7b
Independently re-verified all 6 REJECT/token root-cause points against the
current source (zero line drift). Posted comment 121 on bf-4iewr with
file:line citations and the two required explicit statements:
  - "no reject" is a 24h-window MASK, not a real fix
  - the token-supply path is currently DEAD (header ignored, hello.Token empty)

go vet ./internal/ingestion/ clean. bf-34lwt closed; the remaining bf-4iewr
children should target making the token contract real (point 4) rather than
re-opening the migration window.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-07 12:28:10 -04:00
jedarden
1679f00640 docs(bf-34lwt): re-dispatch verification — 6 facts re-confirmed at HEAD 9df77e3, close
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 <noreply@anthropic.com>
2026-07-07 12:28:10 -04:00
jedarden
4b54789302 docs(bf-4th1x): close bead — synthesis comment verified on bf-4iewr
Re-dispatch of a run that posted the synthesized root-cause comment [115]
on the grandparent bf-4iewr but failed at br close (worker_sessions
claimed_at naive-timestamp bug). Re-verified all six file:line citations
against current source, posted closing summary comment [117], repaired the
two naive worker_sessions rows, and closed bf-4th1x.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-07 07:42:22 -04:00
jedarden
005ae09ae7 docs(bf-4th1x): post synthesized REJECT/token root-cause comment on bf-4iewr
Synthesize the three bf-34lwt children (bf-5ig3e, bf-29wyl, bf-2hdbg) into the
definitive root-cause finding on the grandparent bf-4iewr, with file:line
citations for all six confirmed points. States explicitly that current
'no reject' is a 24h migration-window MASK (not a real fix) and that the
token-supply path is currently dead (X-Spaxel-Token header ignored,
hello.Token always empty).

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-07 07:35:17 -04:00
jedarden
8116dc8fc2 docs(bf-2hdbg): migration window (not tokens) accepts tokenless sim nodes
Third link of the bf-34lwt split. Pins down that the bf-3hji 'no REJECT,
sim nodes accepted' state is a side effect of the default 24h migration
window a fresh boot opens — not a token/auth fix. Tokens never enter the
admission decision: sim nodes are tokenless where checked (hello.Token
body field is empty; the X-Spaxel-Token header they set is unread per
bf-29wyl). The sole acceptor is the migration deadline.

Verified against current source:
- config.go:139 MigrationWindowHours defaults to 24 (config.go:43 field
  comment '0 = disabled'); main.go:4495-4497 installs deadline=now+24h
  only when >0, so a fresh boot opens the window.
- server.go:510-518 acceptance branch gates on server.go:515
  !deadline.IsZero() && time.Now().Before(deadline) — tokenOK is
  irrelevant here; tokenless node admitted as Unpaired.
- server.go:519-526 reject branch (else of 515) hits server.go:525
  sendReject(conn,"invalid_token") + conn.Close(); sendReject writes
  {"type":"reject","reason":"invalid_token"} (server.go:841-845).

Conclusion: 'no reject' is a 24h-window MASK. REJECT returns when
SPAXEL_MIGRATION_WINDOW_HOURS=0 (deadline never set -> IsZero() ->
reject) or uptime > 24h (Before(deadline) false -> reject). Real fix is
on the token/supply side, not the window.

Carries the adjacent bf-3hji section it builds on ('the bf-3hji no-reject
finding above'), also uncommitted docs. No Go code touched; build + unit
tests + go vet clean.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-07 07:24:19 -04:00
jedarden
4dcc06af2b docs(bf-29wyl): confirm token-supply path dead — header-only, unread
Verify the sim/header-side half of the tokenless-sim REJECT root cause
against current source (second link of the bf-34lwt split). Three facts
re-checked against current source:

- Sim supplies its token ONLY as the X-Spaxel-Token HTTP header on the
  WS dial — cmd/sim/main.go:313, mothership/cmd/sim/main.go:633,
  mothership/cmd/sim/scenario.go:318 (all three connect paths).
- The hello JSON map the sim sends has NO token field —
  cmd/sim/main.go:348-359, mothership/cmd/sim/main.go:652-665,
  mothership/cmd/sim/scenario.go:331-342. Only "--token" hits are CLI
  flag defs, never a JSON body key.
- The mothership NEVER reads that header on the WS path —
  grep 'X-Spaxel-Token' mothership/ excluding _test.go and cmd/sim/
  returns zero hits. HandleNodeWS (server.go:455-469) passes r straight
  to Upgrade; the only Header ref on the path is the outbound
  w.Header().Set response header.

Conclusion: header ignored -> hello.Token (message.go:22) empty ->
tokenOK (server.go:513) always false. The "have the sim supply valid
tokens" option is currently DEAD. Companion to bf-5ig3e (validator half);
both roll up into bf-34lwt.

Co-Authored-By: Claude <noreply@anthropic.com>
Bead-Id: bf-29wyl
2026-07-07 06:55:56 -04:00
jedarden
3c5b79b791 docs(bf-5ig3e): confirm validator wired unconditionally + hello.Token-only check
Verify the validator-side half of the tokenless-sim REJECT root cause
against current source (first link of the bf-34lwt split):

- mothership/cmd/mothership/main.go:4494 — SetTokenValidator(...) is a
  bare statement with no env/build guard; provSrv is created
  unconditionally at :4492. 'validator configured?' is always YES.
- mothership/internal/ingestion/server.go:513 — tokenOK :=
  hello.Token != "" && validator(hello.MAC, hello.Token) depends
  solely on the deserialized hello JSON body field (message.go:22),
  not any HTTP header. No X-Spaxel-Token read exists on this path.

Conclusion: validator always configured; accept/reject hinges entirely
on hello.Token.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-07 06:38:33 -04:00
jedarden
5c6a6924c1 docs(bf-34lwt): root-cause tokenless-sim REJECT in WS hello path
Definitive root-cause for the bf-4iewr split's first child. Confirms
against current source (with file:line citations) that:

- The token validator is wired UNCONDITIONALLY (main.go:4494)
- It reads hello.Token only (server.go:513); the sim never populates it
- The sim sends its token only as the X-Spaxel-Token header (cmd/sim
  main.go:633/scenario.go:318), and the mothership never reads that
  header (HandleNodeWS upgrades unconditionally at server.go:469) -> the
  token-supply path is currently DEAD
- The default 24h migration window (config.go:139) is the only thing
  accepting sim nodes -> current 'no reject' is a window MASK, not a fix
- REJECT fires only when the window closes (SPAXEL_MIGRATION_WINDOW_HOURS=0
  or uptime > 24h)

Corroborated by the e2e harness comment (tests/e2e/e2e_test.go:86-94)
which pins the window open and documents exactly this.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-07 06:19:29 -04:00
jedarden
8014ad1768 docs(bf-3zll): document hardware-free mothership build + boot to /healthz
Verified the mothership builds from a clean checkout (mothership/ module root,
plain 'go build', no -tags=embed needed locally) and boots to /healthz with
no ESP32 hardware, reaching status=ok in ~230ms with no panic/crash in stderr.
Records the exact commands for the next child in the bf-40hc chain; canonical
path is TestHarness.Start in mothership/tests/e2e/e2e_test.go.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-07 00:37:30 -04:00
jedarden
3b00ccfbd4 test(bf-2aqf): hard-assert >=1 blob in TestFullE2EIntegration
Replace the minimum-count dodge in TestFullE2EIntegration with two hard
assertions: AssertBlobObserved on the WatchDashboardWS blobCounts (captured
concurrently during the run via a goroutine alongside AssertDuringRun) and
AssertDetectionEventsObserved on the /api/events "detection" result. The
"we verify the API responds correctly rather than asserting a minimum
count" dodge comment is gone. 4-node/2-walker/30s config and AssertDuringRun
kept. Expected RED until upstream bf-4q5w lands — deliberately not weakened.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-06 20:41:29 -04:00
jedarden
a8d0759c21 test(bf-16c1): assert >=1 detection event in TestDetectionEvents
Replace the nil-only events check with a hard assertion that
len(events.Events) >= 1, using the AssertDetectionEventsObserved
helper added in bf-2330. An empty detection-event list now fails
with an actionable message naming the count observed and the likely
upstream cause (fusion/tracking produced no blobs). The 4-node /
2-walker / 15s RunSimulator config and timeout handling are
preserved. This is intentionally strict and expected to stay RED
until upstream bf-4q5w (fusion SetNodePosition wiring) lands.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-06 20:32:19 -04:00
jedarden
06460fb197 test(bf-2330): add hard-fail blob/detection assertion primitives to e2e harness
AssertDuringRun now returns a descriptive error (elapsed time + counts
observed) when the fusion pipeline produces zero blobs AND zero detection
events across the whole run window, instead of silently returning nil.
The health==ok and nodes-online==expected assertions are left intact.

Add reusable AssertBlobObserved(blobCounts) and
AssertDetectionEventsObserved(*EventsResponse) helpers plus a GetBlobCount
poller for TestDetectionEvents / TestFullE2EIntegration / the IO-6
hard-gate test (children of bf-5jeo) to reuse. Table-driven unit tests
cover pass/fail/empty/nil cases and the non-nil failure contract.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-06 20:22:36 -04:00
jedarden
7e0bf66ddd fix(bf-56uk): make blob-identity TypeScript verification compile-clean
Add the TypeScript tooling and compile-time check that verifies the blob
identity fields (personName/assignedColor/identityResolved + deprecated
aliases) declared on the Blob interface are TypeScript-compliant:

  - dashboard/tsconfig.json: strict tsc config, noEmit, includes types/**
  - dashboard/package.json: add `typescript` devDep and `npm run typecheck`
  - dashboard/types/blob-identity.check.ts: pure compile-time assertions

The check file is the TypeScript mirror of the Go identity-field
serialization suites (bf-5151/bf-2ibc/bf-5v3q/bf-3wkz/bf-4qto): a zero-value
blob (no identity set), a resolved-identity blob (camelCase keys the Go
dashboard projection emits), and the identityResolved tri-state must all
satisfy the Blob interface with no missing-field or type-mismatch errors.

Fix the one compilation error the check surfaced: the HasField helper used
`NonNullable<T[K]> extends NonNullable<V>`, which TypeScript does not
reliably evaluate for an indexed type parameter inside a nested conditional —
it produced a spurious TS2344 `false` for the optional `boolean`
identityResolved field. Rewrite it as `V extends T[K]` (concrete V on the
left, indexed T[K] on the right), which resolves correctly in every case and
still catches missing keys and type mismatches.

Verified: `npm run typecheck` exits 0; `go vet`/`go build`/`go test ./...`
on mothership all pass (no Go touched).

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-06 19:32:31 -04:00
jedarden
ca7a0f21f3 fix(bf-4qto): propagate canonical identity fields through all blob projections
The three canonical identity fields (PersonName, AssignedColor,
IdentityResolved) had been added to every identity-bearing blob type
(bf-5151/bf-2ibc/bf-5v3q/bf-3wkz) but three projection sites in the
fusion loop were silently dropping them when converting the source
sigproc.TrackedBlob:

  - explainability.BlobSnapshot  (cmd/mothership/main.go)
  - automation.TrackedBlob       (cmd/mothership/main.go)
  - volume.BlobPos               (cmd/mothership/main.go)

The api.Track (tracks.go) and dashboard.blobJSON (hub.go) projections
already propagated them, so this left default-handling inconsistent
across projection contexts. Propagate the three fields from the source
blob at all three sites so the canonical fields flow through every
projection instead of being zeroed.

The fields stay at their Go zero values for existing blobs (identity is
not yet resolved at the signal layer): empty strings and a nil *bool all
carry omitempty, so they serialize as omitted (undefined in JS), which is
the consistent default the acceptance criteria require. The BLE identity
sidecar that actually populates them is a separate follow-up bead.

Also add the companion serialization-consistency suites that were missing
for the four types whose fields landed across the split beads:
tracker.Blob, tracking.Blob, volume.BlobPos, explainability.BlobSnapshot.
Each asserts the three task-required properties: zero-value blobs omit
all three fields (undefined), a resolved identity emits the camelCase
keys matching dashboard/types/spaxel.d.ts, and the *bool tri-state
(nil=omitted, &true=emitted, &false still emits identityResolved:false).

Verified: gofmt clean, go vet ./... clean, go test ./... passes
(incl. the four new suites and the existing api/automation/signal ones).

Co-Authored-By: Claude <noreply@anthropic.com>
Bead-Id: bf-4qto
2026-07-06 19:12:57 -04:00
jedarden
d0ac1b27b0 test(bf-5151): guard canonical identity-field serialization on blob types
Add table-driven serialization tests for the three bf-5151 canonical
identity fields (PersonName/AssignedColor/IdentityResolved) on the
tracked-blob types whose fields were added in a612584 + 01a415d and
propagated through the JSON projections:

  - internal/signal/identity_fields_test.go      -> signal.TrackedBlob
    (the canonical type /api/blobs serializes; api.TrackedBlob aliases it)
  - internal/automation/identity_fields_test.go  -> automation.TrackedBlob
    (Tier-1 #1 type that previously carried NO identity fields)
  - internal/api/tracks_identity_test.go         -> api.Track via the real
    GET /api/tracks handler, asserting propagation from signal.TrackedBlob

Each suite asserts the three task-required properties:
  1. zero-value blobs omit all three fields (omitempty -> undefined in JS),
     satisfying "fields set to undefined for existing blobs";
  2. a resolved identity emits the camelCase keys matching
     dashboard/types/spaxel.d.ts;
  3. the *bool tri-state: nil=omitted, &true=emitted, &false still emits
     "identityResolved":false (resolution attempted, failed).

Verified: gofmt clean, go vet ./... clean, go test ./... passes
(50 packages ok, including the 3 new suites).

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-06 18:44:12 -04:00
jedarden
1871a8a42c docs(bf-1mwy): correct coverage-gate HEAD ref to real ancestor 908adf2
The committed bf-1mwy coverage gate (7f697a3) cited HEAD `37d628e`, a
pre-rebase dangling SHA not in history (the same class of stale-hash error
the doc's own §6 warns about for 1446ccf/248d0e0). Re-verified at the real
code HEAD `908adf2` (confirmed ancestor of HEAD): both inventory greps
return the documented 5 + 15 hits, all 8 in-scope blob types carry the
three canonical identity fields, and gofmt/vet/test are green.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-06 18:24:50 -04:00
jedarden
7f697a388d docs(bf-1mwy): coverage gate proving every blob-creation site accounted for
Re-run the bf-1q3m §1.1 inventory greps at HEAD 37d628e and map every
production blob-creation site (5 primary + 16 projection = 21) to either
"has the 3 canonical identity fields" or "out-of-scope with explicit
reason" in notes/bf-5151-coverage.md. This is the coverage gate closing
bf-5151's "No blob creation code is missed" acceptance criterion.

All 8 in-scope types confirmed carrying PersonName/AssignedColor/
IdentityResolved at zero values: signal.TrackedBlob, tracker.Blob,
tracking.Blob, automation.TrackedBlob, api.Track, dashboard.blobJSON,
explainability.BlobSnapshot, volume.BlobPos. All 15 out-of-scope sites
enumerated by reason (pre-identity peak C1; synthetic replay/sim P8-P10;
anon falldetect input P3; lifecycle events P11/P12; trigger-state
volume.BlobState P13-P16; rendering fallbacks P4-P6; distinct-name
falldetect.BlobSnapshot P7; dead api.BlobPos) -- none silently dropped.

Also notes the task-cited SHAs 1446ccf/248d0e0 are pre-rebase dangling
commits superseded by a612584/01a415d in HEAD.

gofmt/vet/test gate: 7 files carried pre-existing gofmt drift unrelated
to the identity work (verified unmodified by this bead before the format
pass); fixed mechanically with gofmt -w to satisfy the clean-gate
requirement. gofmt -l empty, go vet ./... exit 0, go test ./... all ok.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-06 18:15:22 -04:00
jedarden
908adf2049 docs(bf-3wkz): record independent re-verification of dashboard identity surface
Second pass (retry) re-confirmed all 8 Go types emit the canonical camelCase trio
(personName/assignedColor/identityResolved), state.js:288-294 surfaces server identity
via Object.assign, websocket.js dead-reckoning cache is identity-free by design.
go vet + go test green. No source change needed.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-06 17:58:29 -04:00
jedarden
730956411c fix(bf-3wkz): align volume.BlobPos identity JSON tags with dashboard camelCase
volume.BlobPos was the sole outlier carrying the canonical identity trio
(personName/assignedColor/identityResolved) under snake_case JSON tags
(person_name/assigned_color/identity_resolved); the other 7 blob-shaped
types and dashboard/types/spaxel.d.ts all use camelCase (bf-5151).

The tags were inert today (volume.BlobPos is consumed in Go by
Store.Evaluate, which returns fired-trigger IDs; it is never
JSON-marshalled to the dashboard), but would leak snake_case keys
inconsistent with the TS interface the moment it is ever surfaced.
Aligning removes the latent drift.

Verified (Tier-4 dashboard track of notes/bf-1q3m-consolidated.md §6):
- spaxel.d.ts Blob interface declares personName/assignedColor/
  identityResolved (+ deprecated personLabel/personColor aliases)
- dashboard.blobJSON, api.Track, explainability.BlobSnapshot already match
- state.js:290 surfaces server identity via Object.assign (no change needed)
- websocket.js:167 dead-reckoning cache is intentionally identity-free
Cross-check documented in notes/bf-3wkz-dashboard-identity-surface.md.
go vet ./... and go test ./... both pass.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-06 17:52:34 -04:00
jedarden
29a114c961 feat(bf-5v3q): add identity fields to volume.BlobPos type
Add the three canonical identity fields — PersonName, AssignedColor,
IdentityResolved — to volume.BlobPos (the Tier-1 #3 type gap from the
bf-1q3m/bf-5cgc handoff). BlobPos is the trigger-evaluation blob type
constructed at cmd/mothership/main.go:2326; it carried only PersonID.

Mirrors commit 1446ccf (bf-5151), which added the same three fields to
the four dashboard-facing tracked-blob types — but here the JSON tags
are snake_case (person_name, assigned_color, identity_resolved) matching
the volume package's internal convention, versus 1446ccf's camelCase
keys that mirror the dashboard Blob TS interface.

Per the task scope, the fields are DECLARATIONS ONLY: left at zero values
(empty strings, nil *bool) with omitempty, so they serialize as omitted
(undefined in JS). The production literal at main.go:2326 needs no change
— Go zero values handle it. A separate downstream bead populates them
from identityMatcher.GetMatch(blob.ID); that one-line populate is out of
scope here.

Verified: gofmt clean, go vet ./... clean, go test ./... passes.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-06 17:42:33 -04:00
jedarden
c3f4b0dd4a feat(bf-2ibc): add identity fields to explainability.BlobSnapshot
Add the three canonical identity fields — PersonName, AssignedColor,
IdentityResolved — to explainability.BlobSnapshot (handler.go), the only
tracked-blob view that still lacked them (Tier-1 #2 type gap from the
bf-1q3m/bf-5cgc handoff).

The camelCase JSON keys (personName, assignedColor, identityResolved) and the
*bool tri-state choice mirror exactly how commit 1446ccf added them to
signal.TrackedBlob / tracker.Blob / tracking.Blob / automation.TrackedBlob, and
match the dashboard Blob type in dashboard/types/spaxel.d.ts.

Per scope, the fields are DECLARATIONS ONLY: the production creation literal at
mothership/cmd/mothership/main.go:2206 (and the handler_test.go literals) set
only {ID,X,Y,Z,Confidence}, so the new fields stay at zero values — empty
strings and a nil *bool all carry omitempty and serialize as omitted (undefined
in JS). Populating them from the ble.IdentityMatcher sidecar and folding in the
parallel identityMap (main.go:2216) is a separate downstream bead.

Verified: gofmt clean, go vet ./... clean, go build ./... clean,
go test ./... passes.

Co-Authored-By: Claude <noreply@anthropic.com>
Bead-Id: bf-2ibc
2026-07-06 17:37:06 -04:00
jedarden
01a415dd5c feat(bf-5151): propagate canonical identity fields to blob JSON projections
Complete the identity-field surface started by 1446ccf (which added
PersonName/AssignedColor/IdentityResolved to the four primary tracked-blob
structs). Add the same three canonical fields to the two struct types that
serialize tracked blobs for external consumers, and propagate them from the
source structs:

  - api.Track (internal/api/tracks.go)        -> /api/tracks JSON; sourced
                                                 from signal.TrackedBlob in
                                                 listTracks
  - dashboard.blobJSON (internal/dashboard/hub.go) -> /ws/dashboard feed;
                                                 sourced from tracking.Blob in
                                                 BroadcastLocUpdate

/api/blobs needs no change: it serializes signal.TrackedBlob directly
(pm.GetTrackedBlobs), which gained the fields in 1446ccf.

Per the task scope the fields are left at their zero values for existing
blobs (empty strings + nil *bool, all omitempty -> undefined in JS). A
follow-up bead wires the BLE identity sidecar to populate them. The
camelCase keys match the dashboard Blob interface in
dashboard/types/spaxel.d.ts.

The Tier-1 leak-fix targets from bf-1q3m (explainability.BlobSnapshot,
volume.BlobPos) are intentionally untouched: their identity flows via a
separate mechanism (the identityMap BLEMatch side-channel / PersonID), and
adding blob-struct identity fields there is step (a) of a cohesive
add+populate+fold fix that belongs to the follow-up bead (bf-1q3m §6),
not this fields-as-undefined bead.

Verified: gofmt clean, go vet ./... clean, go test ./... passes (api,
dashboard, signal, tracker, tracking, automation re-run with -count=1).

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-06 17:37:06 -04:00
jedarden
a612584841 feat(bf-5151): add canonical identity fields to blob creation types
Add the three new canonical identity fields — PersonName, AssignedColor,
IdentityResolved — to the four primary tracked-blob struct types that are
constructed at the blob-creation sites catalogued in bf-1q3m:

  - signal.TrackedBlob   (internal/signal/processor.go)   — canonical type;
                                                            api.TrackedBlob is
                                                            a type alias of it
  - tracker.Blob (3D)    (internal/tracker/tracker.go)
  - tracking.Blob (2D)   (internal/tracking/tracker.go)
  - automation.TrackedBlob (internal/automation/engine.go)

The camelCase JSON keys (personName, assignedColor, identityResolved) match
the dashboard Blob interface in dashboard/types/spaxel.d.ts, which viz3d.js
reads directly. The legacy snake_case PersonLabel/PersonColor fields are
retained as deprecated aliases for backward compatibility.

Per the task, the fields are left at their zero values for existing blobs:
empty strings and a nil *bool all carry omitempty, so they serialize as
omitted (undefined in JS). A follow-up bead populates them from the BLE
identity sidecar. IdentityResolved is *bool to faithfully represent the
tri-state semantics (nil=unattempted, &true=resolved, &false=failed).

fusion.Blob is intentionally untouched: it is the pre-identity Fresnel peak
type ({X,Y,Z,Confidence} only) and is out of scope for identity per bf-1q3m.

Verified: gofmt clean, go vet ./... clean, go test ./... passes; marshaling
check confirms zero-value blobs omit the fields (undefined) while set blobs
emit the camelCase keys.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-06 17:37:06 -04:00
jedarden
a1e76e0c8d docs(bf-4bhd): re-verify blob-creation inventory current at HEAD 63f0c87
bf-4bhd ("Find all blob creation code paths") was auto-re-dispatched after
the task was already fully delivered by the closed chain
(bf-1q3m -> bf-1m2x -> bf-5cgc). This re-dispatch confirms currency rather
than re-doing discovery (the documented anti-pattern).

Empirical greps at HEAD 63f0c87 match the consolidated report exactly:
5 primary literals, 15 non-test projection literals, 15 blob types
(14 structs + 1 alias), 2 JS production sites. Zero Go/JS/firmware source
drift across f144aad..HEAD (every commit since is docs/.beads only).

New finding: SHA provenance correction. The prior notes cite unreachable
dangling SHAs (1a26c12 is actually bf-1bmg, not bf-1q3m; 0ae0c03 is an
amended-away copy of the bf-1q3m commit, real one is f144aad) because
history was rewritten after those notes were written. Inventory is
unaffected; mapped to real reachable SHAs so a future agent doesn't trust
dead citations.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-06 16:50:22 -04:00
jedarden
21c702f461 docs(bf-5c): checkpoint closed state of bf-5cgc in issues.jsonl
Re-flush after br close: bf-5cgc is now closed, summary recorded as
comment 91. No source change.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-06 16:42:14 -04:00
jedarden
e27fafe320 docs(bf-5cgc): hand off blob-creation inventory to identity-field beads
Formalize notes/bf-1q3m-consolidated.md §6 (tiered fix-target list) into a
tracked site→fix→bead deliverable (notes/bf-5cgc-handoff.md):

- automation  main.go:2303 + engine.go:1337  → bf-5151 / bf-64h5
- explainability main.go:2206 + handler.go:95 (fold identityMap :2216) → bf-5151
- volume      main.go:2326 + shape.go:1080    → bf-5151 (one-line PersonID)

All three mirror the reference population pattern analytics.TrackUpdate
(main.go:2267-2271): pull PersonID via identityMatcher.GetMatch(b.ID).
Sites re-verified exact at HEAD 1ecc999. Includes Tier-2/3/out-of-scope
(tracker wiring decision, E1 no-op, dead api.BlobPos) and a scope note
flagging the Go-backend vs TS-framing mismatch on the sibling beads.

Comments posted on bf-5151, bf-64h5, bf-1wvm, bf-iner, bf-4bhd. Closes
bf-4bhd's "Report is ready for the next bead to use" criterion.

Docs/.beads only — zero Go/JS source changed; go vet ./... passes.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-06 16:40:27 -04:00
jedarden
1ecc9992d1 docs(bf-5ywk): banner superseded blob-inventory notes, confirm bf-1q3m as sole source
bf-4bhd failed repeatedly because re-dispatched agents trust its stale
"Analysis Report" framing (no supersession marker on the file). Per
notes/bf-1q3m-consolidated.md §4.1, add a visible SUPERSEDED banner to the
top of notes/bf-4bhd.md and notes/bf-3ldj-findings.md naming
notes/bf-1q3m-consolidated.md as the sole trusted blob inventory, stating the
material reasons:
  - bf-4bhd: 2D field-list drift (claims tracking.Blob has PersonName/
    AssignedColor/IdentityResolved — those fields no longer exist) + line
    drift (E2/E3/volume +90, E1 +110) vs HEAD.
  - bf-3ldj: same line drift + a fundamentally wrong "IDENTITY ATTACHED"
    data-flow claim (tracker.TrackManager.UpdateWithIdentity is not wired
    into main.go at HEAD; identity is sidecar-based via ble.IdentityMatcher).

Add proportionate "secondary - see consolidated" banners to the bf-3ldj-input
notes (bf-26ta, bf-67ao, bf-5kns) and bf-26ta's JS/TS result dumps, so no
inventory-titled note remains that a future agent could mistake for
authoritative. notes/bf-1q3m-consolidated.md already declares itself the
single source of truth (header banner + §9 provenance) - confirmed unchanged.

All stale files retained for provenance; none deleted. Docs-only change, no
Go/JS source modified (go vet + go test ./... clean).

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-06 16:28:50 -04:00
jedarden
64a2202ac9 docs(bf-1m2x): verify blob-creation inventory is current at HEAD
Re-ran the two completeness greps from notes/bf-1q3m-consolidated.md §1
against current HEAD 0ae0c03. Confirmed 5 primary / 15 non-test named
projection / 2 JS production sites — zero deltas vs the report's
verification commit 1a26c12 (no Go/JS source changed in the interval;
only notes/ + .beads/). Closes the inventory-currency loop without
re-doing discovery. Notes-only; no source modified.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-06 16:18:25 -04:00
jedarden
f144aad1f8 docs(bf-1q3m): consolidate & re-verify all blob-creation findings into single report
Merges the four child inventory beads (bf-55rp primary types, bf-5uzm
conversions, bf-4wwt projections, bf-1bmg JS/TS) into one re-verified
source-of-truth report for the upcoming identity-leak implementation bead.

- Re-verified every file:line against HEAD 1a26c12 by running the greps
  (15 type defs, 5 primary + 16 projection production sites, 4 named
  conversion boundaries, 2 JS production sites — all exact)
- Flagged stale entries: supersedes bf-3ldj-findings.md & bf-4bhd.md
  (line drift + 2D field-list drift + wrong data-flow); minor off-by-one
  drifts in child secondary citations noted
- Confirmed the material claim: tracker.TrackManager/applyIdentity is NOT
  wired into main.go; live identity lives in the ble.IdentityMatcher sidecar,
  never written onto the blob struct
- Produced the tiered fix-target list (Tier 1 = main.go:2303 automation +
  :2206 explainability + :2326 volume) and the corrected blob data-flow
  diagram showing where identity enters and leaks

Documentation only — no Go/JS source modified.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-06 16:03:28 -04:00
jedarden
2b44ff633d docs(bf-1bmg): inventory JS/TS dashboard blob-creation sites
Catalogue every blob-creation site on the dashboard JS/TS side, re-verified
against HEAD 68bd308:
- 2 production domain-blob sites: state.js:290 ({id:id} + Object.assign of
  server payload) and websocket.js:167 (dead-reckoning _blobStates cache).
- spaxel.d.ts:10-91 Blob interface documented as the single canonical JS
  identity-field declaration (personName/personId/assignedColor/identityResolved
  + deprecated personLabel/personColor), with Go<->JS field name map.
- 4 new Blob() file-download sites (static/js/fleet.js:457, fleet-page.js:1034
  & :1369, js/fleet.js:1997) explicitly flagged OUT OF SCOPE (browser Blob API).
- 12 test fixtures across ambient/quick-actions/replay .test.js listed for
  completeness.
- Corrects two drifts vs bf-3ldj: the :457 CSV site is in static/js/fleet.js
  (a different file from js/fleet.js), and ambient.test.js literal openings are
  124/273/636/653/688/702 (not the :276/:642/etc cited).

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-06 15:46:02 -04:00
jedarden
68bd308c3c docs(bf-4wwt): inventory blob projection/derived types & construction sites
Re-verify the 9 blob-shaped projection types and their 15 non-test
construction sites against HEAD 3591865. Only the two cmd/mothership/main.go
sites moved (+90 lines); all others unchanged.

Corrections/additions over bf-4bhd:
- api.BlobPos is test-only (TriggersHandler.EvaluateTriggers is not wired
  in main.go; production uses volumeTriggersHandler + volume.BlobPos).
- falldetect enters via an anonymous struct; the real boundary is
  main.go:2288, not enumerated in bf-4bhd.
- replay.BlobUpdate and simulator.BlobResult are synthetic, not blob copies.
- Of volume.BlobState's 4 sites, only shape.go:575 is blob-derived.
- volume.BlobPos.PersonID is not populated at main.go:2326.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-06 15:36:14 -04:00
jedarden
585532f5b2 docs(bf-5uzm): map cross-package blob conversion boundaries in main.go
Re-verified all four named conversion sites against HEAD 7846408 and found the
task's line numbers had drifted (+90..+110). Recorded the corrected locations:
  E1 fusion.Blob peak -> sigproc.TrackedBlob       :5384 -> :5494
  E2 sigproc.TrackedBlob -> automation.TrackedBlob  :2213 -> :2303  (identity dropped)
  E3 sigproc.TrackedBlob -> explainability.BlobSnap :2116 -> :2206  (identity dropped)
  volume sigproc.TrackedBlob -> volume.BlobPos      :2236 -> :2326  (PersonID field exists, unpopulated)

Enumerated all 9 blob-projection loops in the live 10 Hz fusion loop with source/
target types and copied fields, flagged the two identity-leak boundaries (E2/E3)
as the umbrella goal's fix targets, and noted per-target whether the type even
carries identity fields. Includes a corrected live data-flow diagram: the live
loop runs blobTracker (greedy associator), not tracker.TrackManager/applyIdentity;
resolved identity lives in the ble.IdentityMatcher sidecar and is never written
onto the blob struct.

go vet ./... and go test ./... clean.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-06 15:21:10 -04:00
jedarden
d99f42d3bf docs(bf-55rp): re-verify primary tracked-blob Go types & construction sites
Inventory of the 5 primary tracked-blob Go types + the api.TrackedBlob
alias, re-verified against HEAD c4d42e8. Corrects two stale line numbers
(automation.TrackedBlob :2213->:2303, signal.TrackedBlob :5384->:5494)
and one stale field list (tracking.Blob 2D no longer carries PersonName/
AssignedColor/IdentityResolved — identity fields now match tracker.Blob).

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-06 14:56:35 -04:00
jedarden
c4d42e8c30 test(bf-4b1c): differential lock-in for geometry placement and fusion peaks
bf-4b1c wants test coverage proving geometry placement drives fusion peaks,
with the explicit "would fail with old co-located placement but passes with
new geometry" property. The prior children (bf-18yn -> bf-3y9r) cover each
half separately — TestEngine_DefaultPlacementProducesPeaks (spread alone,
geometry-dependent synthetic links) and TestEngine_CoLocatedOriginYields
NoPeaks (co-located alone). What was missing was a single run that isolates
placement as the SOLE variable and demonstrates the non-zero-peak condition
flipping with geometry, proving the test is genuinely regression-sensitive.

Add TestEngine_GeometryPlacementDrivesFusionPeaks: a fuseWithPlacement helper
builds an engine sized to DefaultSpace, seeds N nodes at a given placement,
and fires an explicit geometry-independent motion link for EVERY node pair
(DeltaRMS=1.0, Motion=true) — the link set, grid, and room are identical
across both legs, so only where nodes sit differs. For default (spread)
placement the grid accumulates a non-zero max (gridMax>0); for the old
co-located (0,0,1) collapse every link is degenerate (<0.1 m), AddLink
Influence early-returns, and the grid stays at zero (gridMax==0). The
differential asserts spread>0 && colocated==0. For count>=4 the crossing
links additionally extract >=1 blob, confirming the non-zero grid localizes.

The trace under `go test -v` shows the flip:
  [spread] nodes=4 gridMax=1.0000 blobs=2 top=(2.90,2.50,1.30) conf=0.768 activeLinks=6
  [colocated] nodes=4 gridMax=0.0000 blobs=0 activeLinks=6 — degenerate links, no accumulation

go test ./... , go vet ./... , and go build ./... (all three modules) pass.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-06 13:49:20 -04:00
jedarden
747638ed1d test(bf-3y9r): verify simulator positions reach fusion accumulation grid
bf-3y9r (verify position propagation). The wiring from prior children
(bf-5lii -> bf-69ym -> bf-u7ds -> bf-95tx) already traces a posted node
position through simulator VirtualNodeStore -> fleet.Registry (SQLite) ->
fusion.Engine.nodePos mirror, and bf-95tx's TestPositionPropagation_
FullPipeline logs the value agreeing at all three stages. What was missing
was a single end-to-end assertion that those positions reach the engine's
*accumulation grid* and actually drive localization -- the last inch the
bead's acceptance criterion ("Verify positions reach the fusion engine
accumulation grid") asks for.

Add TestPositionPropagation_ReachesAccumulationGrid: posts two
explicitly-positioned nodes through the simulator REST API, runs one Fuse
step with a link between the two MACs the engine now holds, and asserts a
blob localizes near the link midpoint (confidence 1.0, 1 active link). A
blob there is only reachable if both endpoints' positions are in the grid
lookup -- AddLinkInfluence reads nodePos[mac] for both endpoints, and a
missing/co-located position yields no peak
(cf. TestEngine_CoLocatedOriginYieldsNoPeaks). The grid trace logs the
value's path under `go test -v`:

  [grid] position propagation  midpoint=(3.00,1.00)  peak=(2.90,0.90,0.90) conf=1.000 activeLinks=1

No production wiring change was needed -- prior children completed it; this
bead is verification, with this test as its lock-in.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-06 13:30:19 -04:00
jedarden
c69b0f7ca1 test(bf-95tx): integration test tracing positions through simulator→registry→fusion
bf-95tx wants a single demonstrable verification that node positions flow
through every runtime stage the position-propagation work touched. bf-69ym
locked in simulator→fleet-registry; bf-u7ds locked in simulator→fusion. This
adds the missing end-to-end lock-in that ties both together in one run.

TestPositionPropagation_FullPipeline wires the production chain behind the
SimulatorHandler (handler → bridge.SyncToRegistry → adapter → fleet.Registry
SQLite, with adapter forwardPos → fleet.Manager.ForwardNodePosition → sink →
fusion.Engine.SetNodePosition), posts one node with an explicit position,
then reads that SAME position back from all three stages and asserts they
agree — at both AddNode and UpdateNode. A pipelineStages helper logs each
stage's value so `go test -v` visibly traces the value's path:

  [create] position propagation  simulator={1.5 2.5 1}  registry={1.5 2.5 1}  fusion={1.5 2.5 1}
  [update] position propagation  simulator={3.5 4.5 2}  registry={3.5 4.5 2}  fusion={3.5 4.5 2}

The update leg also asserts no stage still holds the stale pre-update value
and that a single node stays a single node at every stage (no orphan/dup).

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-06 13:13:28 -04:00
jedarden
dbb5cd0cc4 fix(bf-u7ds): forward simulator registry positions to fusion engine
The simulator→fleet-registry path landed node positions in SQLite but never
reached the blob-producing 3D fusion engine at runtime — SyncToRegistry wrote
through fleetRegistryAdapter, which had no forwarding hook, so the engine's
nodePos mirror stayed stale and Fuse localized against wrong/missing geometry
(seeded correctly only at startup).

Add a forwardPos callback to fleetRegistryAdapter (AddVirtualNode /
SetNodePosition) and wire it to fleetMgr.ForwardNodePosition in main, which
already routes through the nodePositionSink → fusion.Engine.SetNodePosition.
This converges all three registry-write paths (PATCH /position, OnNodeConnected,
simulator sync) on the single ForwardNodePosition accessor.

Lock-in: TestSimulatorHandlerToRegistry_PositionsReachFusionEngine recreates
the full chain behind the SimulatorHandler (real Registry + Manager sink →
fusion.Engine) and asserts the engine mirror updates on both AddNode and
UpdateNode — mirroring the two bf-3p6g lock-ins for the simulator path.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-06 12:43:46 -04:00
jedarden
fd7918f7fc docs(bf-4oiz): mark registry-bridge position audit resolved, close out
The bf-4pqj audit flagged a compile blocker (cmd/mothership did not build:
*fleet.Registry didn't satisfy simulator.RegistryNodeAdapter) and recorded
"go vet/test fail pre-close". bf-5lii since fixed it (fleetRegistryAdapter +
immediate SyncToRegistry on node create/update) and bf-69ym added two
end-to-end simulator→fleet.Registry tests.

Re-verified the full position flow for this umbrella bead's close-out:
- go build ./cmd/mothership — OK
- go vet ./... — clean
- go test ./... — all green (simulator + api fresh)

Update bf-4oiz-findings.md so its "fails" claims no longer mislead: add a
RESOLVED header summarizing the fixes, and correct the conclusion table's
"go vet green" row to . Verification-only; no production code changes.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-06 12:22:25 -04:00
jedarden
14565743c5 test(bf-69ym): end-to-end simulator→fleet-registry position flow
The only coverage of the simulator→registry position path was either
bridge-level unit tests (registry_bridge_test.go against a fake adapter) or
handler-level immediate-sync tests (TestImmediateSyncOnAddAndUpdate against a
recording fake with explicit positions). Nothing proved a position travels
from the simulator handler all the way into the live fleet.Registry — and
specifically that an origin-default node is written to the registry at the
spread geometry DefaultNodePositions assigns, not co-located at (0,0,0).

Add two end-to-end tests that wire a real SQLite-backed *fleet.Registry
behind the SimulatorHandler (the same wiring cmd/mothership/main.go uses,
via a test-local fleetRegistryTestAdapter that mirrors the production
fleetRegistryAdapter since the latter lives in package main and isn't
importable):

- TestSimulatorHandlerToRegistry_CreateAndUpdate: AddNode (no position) →
  CreateVirtualNode → SyncToRegistry → AddVirtualNode into the live registry,
  asserting the node lands at exactly DefaultNodePositions(space,1) (never
  (0,0,0)); then UpdateNode → SetNodePosition, asserting the new position
  flows through to the same registry row without duplicating it.

- TestSimulatorHandlerToRegistry_OriginNodesGetSpreadGeometry: 4 nodes
  posted at the DB default origin {0,0,1} must land in the registry at the
  distinct, spread-out DefaultNodePositions(space,4) set — never co-located
  at the origin (the failure mode in bf-18yn / bf-4q5w).

Split from bf-4oiz. go test ./internal/simulator/... ./internal/api/... and
full go test/vet ./... all green.

Co-Authored-By: Claude <noreply@anthropic.com>
Bead-Id: bf-69ym
2026-07-06 12:08:27 -04:00
jedarden
41e8233011 feat(bf-5lii): wire immediate registry sync on simulator node create/update
The simulator API handlers AddNode and UpdateNode only logged that a node
"will be synced to registry on next periodic sync" — they held a
registryBridge field but had no RegistryNodeAdapter to pass to
SyncToRegistry, so new nodes waited up to 30s for the periodic ticker
before reaching the fleet registry.

Wire the missing half:
- SimulatorHandler gains a registryAdapter (simulator.RegistryNodeAdapter)
  field threaded into NewSimulatorHandler.
- AddNode calls registryBridge.SyncToRegistry(adapter) right after
  CreateVirtualNode succeeds; UpdateNode does the same after
  UpdateNodePosition succeeds. Both run outside the handler lock.
- main.go adds fleetRegistryAdapter (adapts *fleet.Registry to the
  simulator interface, converting fleet.NodeRecord <-> simulator.NodeRecord),
  shared by the immediate syncs and the 30s periodic ticker (which stays
  as a safety net).

New table-driven TestImmediateSyncOnAddAndUpdate covers both the wired and
nil-adapter no-op paths.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-06 11:46:43 -04:00
jedarden
4705c9f432 docs(bf-4pqj): audit registry_bridge position wiring — flag compile blocker
Re-verified the registry_bridge position data flow (split from bf-4oiz).
The stale 'wiring missing' claim is corrected: the 30s periodic
SyncToRegistry IS wired in source (main.go:4195-4226), and the bridge
logic + float64 XYZ formatting are confirmed correct and backed by
registry_bridge_test.go.

However the audit found cmd/mothership does NOT build: main.go:4217
passes *fleet.Registry to SyncToRegistry, but *fleet.Registry does not
satisfy simulator.RegistryNodeAdapter (GetAllNodes/GetNode return
[]fleet.NodeRecord vs []simulator.NodeRecord). A second failure,
internal/api/simulator_test.go:19, passes no args to the now-2-arg
NewSimulatorHandler. Both introduced by e92d5bc (bf-5dpu). The
internal/simulator package itself builds and tests green.

Verification-only bead: no production code changes. go vet/test fail
pre-close, so the blocker is documented for a wiring-owner fix.
2026-07-06 11:46:43 -04:00
jedarden
d43b0095e6 docs(bf-4bhd): enumerate projection blob construction sites in report
Verified every line number in the blob-creation report against current main
(5 primary types, 8 primary sites, 2 identity-mutation funcs, 9 projection
types — all accurate). Added an enumerated table of the 15 projection
construction sites across 7 files so the next bead needs no further searching.
Completeness sweep confirmed no new(Blob), no missed factories, no uncatalogued
make([]Blob) allocators.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-06 11:46:43 -04:00
jedarden
e3bd5ee685 docs(bf-3ldj): consolidate blob creation site findings into single report
Aggregates bf-5kns (new Blob constructors), bf-26ta (struct/object
literals), and bf-67ao (factory functions) into notes/bf-3ldj-findings.md.
Re-verified every production file:line against HEAD; corrected two stale
entries (state.js:290 now {id} only; tracking.Blob field names) and
reconciled the Go-vs-JS identity-field naming. Key deliverable for the
next bead: identity is dropped at two main.go conversion boundaries
(:2213 automation, :2116 explainability) — the leak this work closes.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-06 10:22:55 -04:00
jedarden
7e89b31922 docs(bf-67ao): add completeness sweep — 10th blob type + missed explainability site
Extend the blob-factory catalogue with findings from a verification sweep of
all documented file:line references (all 15 original entries confirmed exact):

- Add the 10th blob type, explainability.BlobSnapshot (handler.go:95), a distinct
  type from the already-listed falldetect.BlobSnapshot — note their shared name.
- Add entry E3: the production conversion loop at main.go:2116 (inside main()'s
  fusion loop) that builds []explainability.BlobSnapshot to feed computeExplanation
  (F1). Sibling inline-conversion to E2; the only site constructing this type.
- Add a 'test-only factories' caveat listing the *_test.go helpers the sweep found
  (makeBlobAt, processFramesDirectly/WithThreshold, mock providers) and documenting
  why they are scoped out, plus the newBlobTracker struct constructor (not a blob).

Catalogue now: 16 documented creation sites (14 dedicated functions/methods +
2 inline conversion loops) across 10 blob types.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-06 10:14:03 -04:00
jedarden
43d3a710e7 docs(bf-67ao): document blob factory functions across codebase
Catalogue the 15 functions/methods that construct and return blob objects,
covering 9 blob types (tracker.Blob, tracking.Blob, fusion.Blob,
signal.TrackedBlob, automation.TrackedBlob, replay.BlobUpdate,
simulator.BlobResult, explainability.BlobExplanation, falldetect.BlobSnapshot).

Each entry records the function signature, file:line of both the function
definition and the struct-literal creation site, and the construction idiom
(pointer/value literal, slice copy, delegation, or record append). Includes a
summary table and the end-to-end data flow (Fuse -> track -> Update ->
GetBlob). Complements the earlier bf-4bhd literal-site survey with a
function-level (factory) view.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-06 10:04:07 -04:00
jedarden
c595d13208 docs(bf-26ta): correct blob literal counts, add missing ambient.test.js sites
Re-verified all blob-shaped object literals against current code. The prior
docs drifted: quick-actions.test.js was mis-counted as 16 literals (actual 5)
and only 4 of 6 ambient.test.js blob-array sites were documented.

Corrections (notes/bf-26ta-findings.md, bf-26ta-javascript-results.md):
- Add missing ambient.test.js sites at lines 694 (lerp source) and 708 (lerp target)
- Fix quick-actions.test.js count: 16 -> 5 literals (lines 208, 316, 470, 513, 678)
- Fix JS totals: 25 across 7 files -> 14 across 5 files
- state.js:290, websocket.js:167, replay.test.js:101 unchanged (still accurate)
- TypeScript: still 0 literals (only spaxel.d.ts type defs)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-06 09:30:26 -04:00
jedarden
756cc03a00 docs(bf-5613): analyze multi-person bedroom sleep monitoring edge case
- Created comprehensive findings document at notes/bf-5613-findings.md
- Added TestTwoPersonBedroomScenario to integration_test.go
- Verified that multi-person bedroom edge case is NOT implemented:
  * No detection of multiple blobs in bedroom zones
  * No BLE-first assignment logic
  * No zone-based fallback records
  * No lowest-deltaRMS blob selection for breathing analysis
- Test documents expected behavior per plan.md specification

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-06 02:47:37 -04:00
jedarden
36886ab60c docs(bf-3f16): document API path verification - all endpoints match plan spec
Verified all REST API endpoints against plan.md specification:
- GET /api/analytics/flow - matches exactly
- GET /api/localization/weights - matches exactly
- POST /api/localization/weights/reset - alias route added for plan spec compliance

No deviations found. API_IMPLEMENTATION_STATUS.md already documents
the canonical path decisions with backward compatibility maintained.
2026-07-06 02:40:53 -04:00