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>
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>
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>
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>
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
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>
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>
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>
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>
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>
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>
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>
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
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>
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>
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>
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>
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>
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>
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
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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
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>
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.
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>
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>
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>
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>
- 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>
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.
- Add showCoverageDegradation() to display before/after GDOP comparison
- Add dismissCoverageDegradation() to clear overlay on node recovery
- Create ground plane cells colored by degradation severity (red/amber/yellow)
- Add legend with before/after statistics and coverage delta
- Export functions to public API for WebSocket integration
Implements plan.md Component 12: before/after coverage overlay
when fleet self-healing fires due to node loss.
Co-Authored-By: Claude <noreply@anthropic.com>
- Merged TypeScript and JavaScript search results
- Combined Go backend blob creation sites
- Documented 25 JavaScript instances across 7 files
- Documented 10+ Go blob types across 8 files
- Included TypeScript type definitions
- Each location includes file path, line number, and blob structure pattern
- Organized by language with detailed pattern analysis
Bead-Id: bf-3tlw
- Found 25 blob-shaped object literals across 7 files
- Identified canonical creation pattern in state.js:290
- Documented field usage patterns and test fixtures
- Compared blob, zone, node, and event structures