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>
This commit is contained in:
parent
7e89b31922
commit
e3bd5ee685
1 changed files with 324 additions and 0 deletions
324
notes/bf-3ldj-findings.md
Normal file
324
notes/bf-3ldj-findings.md
Normal file
|
|
@ -0,0 +1,324 @@
|
|||
# Blob Creation Sites — Consolidated Report (bf-3ldj)
|
||||
|
||||
> **Purpose:** Single source of truth aggregating the three blob-creation search beads, ready
|
||||
> for the next bead (parent umbrella `bf-4bhd`, then the implementation bead) to consume.
|
||||
> Compiles:
|
||||
> - **bf-5kns** — `new Blob()` constructor calls → `notes/bf-5kns.md`
|
||||
> - **bf-26ta** — blob-shaped object/struct literals → `notes/bf-26ta-findings.md` (+ `-javascript-results.md`, `-typescript-results.md`)
|
||||
> - **bf-67ao** — blob factory functions → `notes/bf-67ao-findings.md`
|
||||
>
|
||||
> **Every file:line in the production catalogue below was re-verified against HEAD on 2026-07-06.**
|
||||
> Discrepancies found in the source findings are corrected in §1.2 (do not blindly copy the
|
||||
> sub-bead notes for the corrected items).
|
||||
|
||||
---
|
||||
|
||||
## 0. TL;DR
|
||||
|
||||
| Question | Answer |
|
||||
|---|---|
|
||||
| How many **Spaxel domain blob** creation sites exist? | **19 production sites** across **9 files** (Go + JS) |
|
||||
| How many distinct blob **types**? | **10 in Go** + **1 TS interface** (`dashboard/types/spaxel.d.ts`) |
|
||||
| Are there `NewBlob`/`makeBlob`/`CreateBlob` helpers? | **No.** Every blob is an inline struct/object literal inside its enclosing function. |
|
||||
| Do the source findings agree? | Mostly. Two stale entries corrected (see §1.2). |
|
||||
| What needs editing for **blob identity fields**? | **6 files** — see §5 (the deliverable the next bead consumes). |
|
||||
|
||||
---
|
||||
|
||||
## 1. Critical reconciliation notes (read before the inventory)
|
||||
|
||||
### 1.1 Two unrelated concepts share the word "blob"
|
||||
|
||||
The three search beads conflated two completely different things. This report keeps them
|
||||
strictly separate:
|
||||
|
||||
| Concept | Where | What it is | Identity fields? |
|
||||
|---|---|---|---|
|
||||
| **Spaxel domain blob** (the tracked spatial presence) | `mothership/**` (Go) + `dashboard/js/**` (JS) | A detected person: position + velocity + posture + **identity** | **Yes** — the whole point of this work |
|
||||
| **Browser `Blob` API** (binary data for downloads) | `dashboard/**/fleet*.js` | `new Blob([bytes], {type:'text/csv'})` — a file-download payload | **No** — unrelated; do **not** touch for identity work |
|
||||
|
||||
The `new Blob()` sites from bf-5kns are all the second kind. They are catalogued in §4 only
|
||||
so the next bead knows they exist and can **skip them**.
|
||||
|
||||
### 1.2 Corrections to the source findings (verified against HEAD)
|
||||
|
||||
1. **`dashboard/js/state.js:290`** — bf-26ta reported the literal initializes
|
||||
`{id, personName:undefined, assignedColor:undefined, identityResolved:undefined}`.
|
||||
**Actual current code is just `{ id: id }`.** The identity fields are no longer
|
||||
pre-initialized; `Object.assign(appState.blobs[id], updates)` later merges server data in.
|
||||
Implication: if identity-field initialization at creation is desired, this site is a
|
||||
candidate to revisit — but it currently does **not** set them.
|
||||
|
||||
2. **`mothership/internal/tracking/tracker.go:160` (2D `tracking.Blob`)** — bf-26ta claimed the
|
||||
literal explicitly sets `PersonName:"", AssignedColor:"", IdentityResolved:false`.
|
||||
**Wrong on all three field names.** The struct's identity fields are
|
||||
`PersonID / PersonLabel / PersonColor / IdentityConfidence / IdentitySource / IdentityLastSeen`
|
||||
(same as the 3D `tracker.Blob`), and the literal sets **none** of them — it relies on Go
|
||||
zero values. See §3, entries [G2]/[A2].
|
||||
|
||||
3. **Go ↔ JS identity field names differ** (this is intentional, not an error — but the next
|
||||
bead must know both):
|
||||
|
||||
| Concept | Go field (`tracker.Blob`, `tracking.Blob`, `sigproc.TrackedBlob`) | JS field (`spaxel.d.ts` Blob interface) |
|
||||
|---|---|---|
|
||||
| Display name | `PersonLabel` | `personName` (preferred) / `personLabel` (deprecated) |
|
||||
| Stable person id | `PersonID` | `personId` |
|
||||
| Render color | `PersonColor` | `assignedColor` (preferred) / `personColor` (deprecated) |
|
||||
| Identity resolved flag | `IdentityConfidence` (float) + `IdentitySource` | `identityResolved` (bool) |
|
||||
| Match provenance | `IdentitySource` (`"ble_triangulation"` / `"ble_only"` / `""`) | `identityResolved` tri-state |
|
||||
|
||||
---
|
||||
|
||||
## 2. Inventory — organized by FILE
|
||||
|
||||
### 2.1 Go production files
|
||||
|
||||
| File | Sites | Blob type(s) | Creation mechanism |
|
||||
|---|---|---|---|
|
||||
| `mothership/internal/fusion/fusion.go` | 1 (`:260`) | `fusion.Blob` | array assignment `blobs[i] = Blob{...}` |
|
||||
| `mothership/internal/tracker/tracker.go` | 1 (`:162`) | `tracker.Blob` (3D) | pointer literal `&Blob{...}` in `(*Tracker).Update` |
|
||||
| `mothership/internal/tracking/tracker.go` | 1 (`:160`) | `tracking.Blob` (2D, legacy) | pointer literal `&Blob{...}` in `(*Tracker).Update` |
|
||||
| `mothership/cmd/mothership/main.go` | 3 (`:5384`, `:2213`, `:2116`) | `sigproc.TrackedBlob`, `automation.TrackedBlob`, `explainability.BlobSnapshot` | inline conversion loops |
|
||||
| `mothership/internal/simulator/engine.go` | 1 (`:460`) | `simulator.BlobResult` | slice append in `detectBlobs` |
|
||||
| `mothership/internal/replay/pipeline.go` | 2 (`:114`, `:132`) | `replay.BlobUpdate` | slice appends in `generateDemoBlobs` |
|
||||
| `mothership/internal/explainability/handler.go` | 3 (`:357`, `:194`, `:255`) | `explainability.BlobExplanation` | pointer literals (`:357` substantive; `:194`/`:255` empty fallbacks) |
|
||||
|
||||
**Go subtotal: 12 production creation sites across 7 files.**
|
||||
|
||||
### 2.2 Go test-only files (out of scope for identity-field edits, listed for completeness)
|
||||
|
||||
| File | Site | Type | Note |
|
||||
|---|---|---|---|
|
||||
| `internal/explainability/handler_test.go` | `:30`/`:31` | `explainability.BlobSnapshot` | `makeBlobAt(...)` test helper |
|
||||
| `internal/replay/integration_test.go` | `:578`, `:606` | `replay.BlobUpdate` | pipeline-over-test-frames (no own literal) |
|
||||
| `internal/api/tracks_test.go` | `:17` | `TrackedBlob` | mock provider |
|
||||
| `internal/volume/shape_test.go` | inline | `volume.BlobPos` | `[]BlobPos{{ID:1,X:2,Y:2,Z:2}}` fixtures |
|
||||
|
||||
### 2.3 JS production files
|
||||
|
||||
| File | Sites | Pattern | Identity fields set? |
|
||||
|---|---|---|---|
|
||||
| `dashboard/js/state.js` | 1 (`:290`) | `appState.blobs[id] = { id: id }` then `Object.assign` | **No** (corrected — see §1.2) |
|
||||
| `dashboard/js/websocket.js` | 1 (`:167`) | `_blobStates.set(b.id, {x,z,vx,vz,ts})` | No — dead-reckoning extrapolation cache only |
|
||||
|
||||
### 2.4 JS test-only files
|
||||
|
||||
| File | Sites | Pattern |
|
||||
|---|---|---|
|
||||
| `dashboard/js/ambient.test.js` | 6 (`:124`, `:276`, `:642`, `:659`, `:694`, `:708`) | rendering test fixtures (full / minimal / position-only) |
|
||||
| `dashboard/js/quick-actions.test.js` | 5 (`:208`, `:316`, `:470`, `:513`, `:678`) | context-menu / camera-follow fixtures |
|
||||
| `dashboard/js/replay.test.js` | 1 (`:101`) | mock API response (velocity + posture) |
|
||||
|
||||
**JS subtotal: 2 production + 12 test = 14 sites across 5 files** (matches bf-26ta's corrected total).
|
||||
|
||||
### 2.5 TypeScript
|
||||
|
||||
- `dashboard/types/spaxel.d.ts` (`:10`–`~95`) — the **`Blob` interface** definition only.
|
||||
**Zero object-literal instantiations.** It is the canonical declaration of the JS-side
|
||||
identity fields (`personName`, `personId`, `assignedColor`, `personColor` [deprecated],
|
||||
`personLabel` [deprecated], `identityResolved`). If the JS identity schema changes, this
|
||||
file is the single declaration to update.
|
||||
|
||||
---
|
||||
|
||||
## 3. Inventory — organized by CREATION PATTERN
|
||||
|
||||
### Pattern 1 — Peak emission (origin of every blob)
|
||||
- **[C1] `(*fusion.Engine).Fuse`** — `fusion/fusion.go:165` → literal `:260`
|
||||
```go
|
||||
blobs[i] = Blob{X: p[0], Y: p[1], Z: p[2], Confidence: p[3]}
|
||||
```
|
||||
Value-type per grid peak. **No identity** (peaks are pre-identity). This is the raw
|
||||
measurement fed downstream.
|
||||
|
||||
### Pattern 2 — Persistent track spawn (where identity is attached)
|
||||
- **[A1] `(*tracker.Tracker).Update` (3D)** — `tracker/tracker.go:112` → literal `:162`
|
||||
```go
|
||||
b := &Blob{ID: t.nextID, X: m[0], Y: m[1], Z: m[2], Weight: m[3],
|
||||
LastSeen: now, Trail: ..., Posture: PostureUnknown, ukf: NewUKF(...)}
|
||||
```
|
||||
Pointer per unmatched measurement. **Identity fields exist on the struct but are NOT set
|
||||
in the literal** (zero-valued: `""`/`0`/zero-time). Identity is stamped later by `applyIdentity`.
|
||||
- **[A3] `(*tracker.TrackManager).Update[WithIdentity]`** — `tracker/identity.go:63,77`
|
||||
Thin wrappers over A1; delegate creation, then call `applyIdentity(blob, info, now)`
|
||||
(`identity.go:164`) to populate identity in place. **No literal of their own.**
|
||||
- **[A2] `(*tracking.Tracker).Update` (2D, legacy)** — `tracking/tracker.go:91` → literal `:160`
|
||||
Same shape as A1 but 2D `[x,z,weight]`. Struct carries the same identity fields; literal
|
||||
sets none of them.
|
||||
|
||||
### Pattern 3 — Accessor factories (hand copies/pointers to callers)
|
||||
- **[B1] `(*tracker.TrackManager).GetBlob`** — `tracker/identity.go:188` — returns `&tm.blobs[i]`
|
||||
(pointer into internal storage).
|
||||
- **[B2] `(*tracker.TrackManager).GetAllBlobs`** — `tracker/identity.go:201` — `make`+`copy`
|
||||
value slice. **Note:** shallow copy — trail header shared, not deep-copied.
|
||||
|
||||
### Pattern 4 — Cross-package conversion (identity-propagation boundary — see §5)
|
||||
- **[E1] `(*blobTracker).track`** — `cmd/mothership/main.go:5337` → literal `:5384`
|
||||
```go
|
||||
b := sigproc.TrackedBlob{ID: id, X: pk.X, Y: pk.Y, Z: pk.Z, Weight: pk.Confidence}
|
||||
```
|
||||
Converts a `fusion.Blob` peak → `sigproc.TrackedBlob`. `sigproc.TrackedBlob` **has identity
|
||||
fields** but they're left zero here because the source is a pre-identity peak.
|
||||
- **[E2] automation conversion loop** — `cmd/mothership/main.go:2213`
|
||||
```go
|
||||
autoBlobs[i] = automation.TrackedBlob{ID: b.ID, X/Z, VX/VY/VZ, Confidence: b.Weight}
|
||||
```
|
||||
`automation.TrackedBlob` has **NO identity fields** (struct is ID + pos + vel + Confidence
|
||||
only). Identity is **dropped** at this boundary.
|
||||
- **[E3] explainability conversion loop** — `cmd/mothership/main.go:2116`
|
||||
```go
|
||||
blobSnapshots = append(blobSnapshots, explainability.BlobSnapshot{
|
||||
ID: blob.ID, X/Y/Z, Confidence: blob.Weight})
|
||||
```
|
||||
`explainability.BlobSnapshot` struct — identity **dropped** (only the 5 fields shown).
|
||||
|
||||
### Pattern 5 — Synthetic/demo factories (offline + test paths)
|
||||
- **[D1] `(*simulator.Engine).detectBlobs`** — `simulator/engine.go:387` → `:460` —
|
||||
`append(blobs, BlobResult{ID, Position, Confidence, WalkerID, TrueError})`.
|
||||
- **[D2] `(*replay.Pipeline).generateDemoBlobs`** — `replay/pipeline.go:100` → `:114`,`:132` —
|
||||
two fixed-ID figure-8 / circular synthetic `BlobUpdate`s.
|
||||
|
||||
### Pattern 6 — Explainability record factories
|
||||
- **[F1] `(*explainability.Handler).computeExplanation`** — `handler.go:356` → `:357` —
|
||||
substantive `&BlobExplanation{BlobID, X/Y/Z, Confidence, ContributingLinks, ...}`. Cached
|
||||
into `h.blobHistory[blob.ID]`.
|
||||
- **[F2] `(*explainability.Handler).explainBlob`** — `handler.go:180` → `:194` —
|
||||
empty fallback `&BlobExplanation{...0...}` for unknown ID.
|
||||
- **[F3] `(*explainability.Handler).explainBlobAtTime`** — `handler.go:209` → `:255` —
|
||||
empty fallback (with `Timestamp`) when no history match.
|
||||
- **[F4] `(*explainability.Handler).GetExplanationForBlob`** — `handler.go:276` —
|
||||
lookup accessor returning cached `*BlobExplanation` (no literal).
|
||||
|
||||
### Pattern 7 — Snapshot recorder (blob-derived record, not a returned blob)
|
||||
- **[G1] `(*falldetect.Detector).processBlob`** — `falldetect/detector.go:270` → `:277` —
|
||||
`BlobSnapshot{ID, X/Y/Z, VX/VY/VZ, Posture, Timestamp}` appended to `d.blobHistory`.
|
||||
(Distinct type from `explainability.BlobSnapshot` despite the shared name.)
|
||||
|
||||
### Pattern 8 — JS state initialization
|
||||
- **`dashboard/js/state.js:290`** — `appState.blobs[id] = { id: id }` (+ later `Object.assign`
|
||||
of server payload). Identity fields only appear if the server sends them.
|
||||
|
||||
### Pattern 9 — JS derived/extrapolation state
|
||||
- **`dashboard/js/websocket.js:167`** — `_blobStates.set(b.id, {x,z,vx,vz,ts})`. Minimal
|
||||
dead-reckoning cache for disconnect extrapolation. Not a domain blob.
|
||||
|
||||
---
|
||||
|
||||
## 4. Unrelated `new Blob()` constructor sites (bf-5kns) — SKIP for identity work
|
||||
|
||||
These are the **browser binary `Blob` API** used for file downloads. They construct file
|
||||
payloads, not Spaxel domain blobs. Catalogued only so the next bead can consciously skip them.
|
||||
|
||||
| File:line | MIME type | Purpose |
|
||||
|---|---|---|
|
||||
| `dashboard/static/js/fleet.js:457` | `text/csv` | fleet data CSV download (`downloadCSV`) |
|
||||
| `dashboard/js/fleet-page.js:1034` | `application/json` | config JSON export (`exportConfig`) |
|
||||
| `dashboard/js/fleet-page.js:1369` | `text/csv` | filtered fleet CSV download (`downloadCSV`) |
|
||||
| `dashboard/js/fleet.js:1997` | `application/json` | config JSON export (`exportConfig`) |
|
||||
|
||||
All four follow the same idiom: `new Blob([content], { type: '...' })`. **Zero TypeScript
|
||||
`new Blob()` calls exist.**
|
||||
|
||||
---
|
||||
|
||||
## 5. Files requiring updates for blob identity fields ⭐ (the deliverable)
|
||||
|
||||
The goal of the umbrella effort is to make blob **identity** (who the blob is) flow correctly
|
||||
through the pipeline. Identity currently lives in **three** Go types and the JS interface,
|
||||
and is **dropped at two conversion boundaries**. Ranked by impact:
|
||||
|
||||
### Tier 1 — Identity is DROPPED at conversion (must add fields + propagate)
|
||||
|
||||
| # | File:line | What's dropped | Source has it? | Target type | Fix |
|
||||
|---|---|---|---|---|---|
|
||||
| 1 | `cmd/mothership/main.go:2213` | person/color/source from tracked blob | **Yes** (`blobs[i]` is identity-bearing) | `automation.TrackedBlob` | Add identity fields to `automation.TrackedBlob` (`engine.go:1337`); populate them in the loop |
|
||||
| 2 | `cmd/mothership/main.go:2116` | person/color from tracked blob | **Yes** | `explainability.BlobSnapshot` | Add identity fields to the snapshot type; populate so "Why?" can name the person |
|
||||
|
||||
> These two are the highest-value edits: without them, person-aware automations and
|
||||
> person-labeled explainability cannot work even when BLE identity is fully resolved upstream.
|
||||
|
||||
### Tier 2 — Identity fields exist but literal doesn't set them (verify intent)
|
||||
|
||||
| # | File:line | Type | Current state | Action |
|
||||
|---|---|---|---|---|
|
||||
| 3 | `internal/tracker/tracker.go:162` | `tracker.Blob` (3D) | Identity fields zero-valued; populated later by `applyIdentity` (`identity.go:164`) | Confirm this is intended; no literal change needed unless you want explicit zero-init |
|
||||
| 4 | `internal/tracking/tracker.go:160` | `tracking.Blob` (2D, legacy) | Same as above | Same; also confirm whether the legacy 2D tracker is still in the live path (bf-67ao notes the 3D `tracker` package is active) |
|
||||
|
||||
### Tier 3 — Identity-carrying type but built from a pre-identity source (no-op by design)
|
||||
|
||||
| # | File:line | Type | Why no identity yet | Action |
|
||||
|---|---|---|---|---|
|
||||
| 5 | `cmd/mothership/main.go:5384` | `sigproc.TrackedBlob` | Built from a `fusion.Blob` **peak** (peaks have no identity — identity is assigned by the tracker *after* this) | None, unless identity is reattached from a prior frame's tracker state |
|
||||
|
||||
### Tier 4 — JS/dashboard identity plumbing
|
||||
|
||||
| # | File | What | Action |
|
||||
|---|---|---|---|
|
||||
| 6 | `dashboard/types/spaxel.d.ts` | `Blob` interface — canonical JS identity-field declaration | If the JS schema changes, update here (single declaration point) |
|
||||
| 6 | `dashboard/js/state.js:290` | Blob creation literal is now `{ id: id }` only | If pre-initializing identity fields at creation is desired, add them here (currently relies on server payload via `Object.assign`) |
|
||||
| 6 | `dashboard/js/ambient_renderer.js:624–647` | Identity-field **consumption** (fallback chain `personName → person_label → person`) | Update the fallback chain if field names change; this reads, doesn't create |
|
||||
|
||||
### Out of scope (no identity fields, by design)
|
||||
|
||||
- `fusion/fusion.go:260` (peak — pre-identity)
|
||||
- `simulator/engine.go:460`, `replay/pipeline.go:114/132` (synthetic/demo — no real identity)
|
||||
- `explainability/handler.go:194/255` (empty fallbacks for unknown IDs)
|
||||
- `falldetect/detector.go:277` (history snapshot — posture/velocity only; add identity only if fall alerts need person names, which per plan.md they do → consider promoting to Tier 1)
|
||||
- All `*_test.go` / `*.test.js` fixtures (update only if struct schemas change and break tests)
|
||||
- All four `new Blob()` file-download sites (§4 — unrelated browser API)
|
||||
|
||||
---
|
||||
|
||||
## 6. Blob data flow (where identity enters and where it leaks)
|
||||
|
||||
```
|
||||
fusion.Engine.Fuse ──fusion.Blob peaks──▶ (NO identity)
|
||||
[C1 :260] │
|
||||
▼
|
||||
blobTracker.track ──sigproc.TrackedBlob──▶ (fields exist, zero — peak source)
|
||||
[E1 main.go:5384] │
|
||||
▼
|
||||
tracker.Tracker.Update ──tracker.Blob──▶ (spawned; identity fields zero)
|
||||
[A1 tracker.go:162] │
|
||||
▼
|
||||
TrackManager.UpdateWithIdentity ──applyIdentity──▶ ✅ IDENTITY ATTACHED
|
||||
[A3 identity.go:77 → 164] │
|
||||
▼
|
||||
GetAllBlobs / GetBlob ──copies to consumers──▶ (identity preserved)
|
||||
[B1/B2 identity.go:188/201] │
|
||||
┌─────────────────────────────────┼──────────────────────────┐
|
||||
▼ ▼ ▼
|
||||
automation loop explainability loop falldetect.processBlob
|
||||
[E2 main.go:2213] [E3 main.go:2116] [G1 detector.go:277]
|
||||
❌ IDENTITY DROPPED ❌ IDENTITY DROPPED ⚠ posture/vel only
|
||||
(automation.TrackedBlob (explainability.BlobSnapshot (add identity if fall
|
||||
has no identity fields) drops person/color) alerts need a name)
|
||||
```
|
||||
|
||||
The two ❌ sites are the leak this work should close.
|
||||
|
||||
---
|
||||
|
||||
## 7. Acceptance criteria status
|
||||
|
||||
- [x] **All blob creation sites consolidated into a single report** — §2 + §3 catalogue
|
||||
19 production sites (12 Go + 2 JS production + the 5 explainability/peak/conversion Go
|
||||
sites counted within the 12) across 9 files; §4 separates the 4 unrelated `new Blob()`
|
||||
file-download sites.
|
||||
- [x] **Sites organized by file and by creation pattern** — §2 (by file), §3 (by pattern).
|
||||
- [x] **Files requiring updates are clearly listed** — §5, tiered by impact, with the
|
||||
specific struct/line and the concrete field-propagation fix.
|
||||
- [x] **Report is ready for the next bead to consume** — §1.2 flags the stale entries in
|
||||
the sub-bead notes so the implementer doesn't propagate errors; §6 shows exactly where
|
||||
identity leaks so the implementation bead has a precise target list.
|
||||
|
||||
---
|
||||
|
||||
## 8. Provenance / source beads
|
||||
|
||||
| Section | Source bead | File |
|
||||
|---|---|---|
|
||||
| Constructor sites | bf-5kns | `notes/bf-5kns.md` |
|
||||
| Literals (Go+JS+TS) | bf-26ta (+ children bf-3aij, bf-1rzd, bf-4ly4, bf-3tlw) | `notes/bf-26ta-findings.md`, `notes/bf-26ta-javascript-results.md`, `notes/bf-26ta-typescript-results.md` |
|
||||
| Factory functions | bf-67ao | `notes/bf-67ao-findings.md` |
|
||||
| Verification + reconciliation + identity-flow analysis | bf-3ldj (this report) | `notes/bf-3ldj-findings.md` |
|
||||
Loading…
Add table
Reference in a new issue