docs(bf-14wx5): document BLE identity fixture + verifiable run-sim-ble-fixture.sh
The bf-14wx5 deliverables were written but uncommitted (stuck-open-but-done — auto-split fired at failure-count:3 only because the templated br close --body had been failing). Commit the focused set so the bead is genuinely closable. Adds: - scripts/run-sim-ble-fixture.sh — canonical, self-verifying fixture: starts a fresh-data-dir mothership, registers a person (POST /api/people), binds the sim's advertised AA:BB:CC:DD:EE:00 (walker 0) via preregister + PUT, and gates on GET /api/ble/devices?registered=true showing person_id/person_name in BOTH phases. - notes/ble-identity-fixture.md — reuse-note recording the exact REST sequence, the observed sim BLE address convention (AA:BB:CC:DD:EE:%02X per walker, ids from 0), and the answer to "must the device be seen-live first?": NO — PreregisterDevice sets last_seen_at=now so it falls inside the GetDevicesSeenInHours window with no live sighting, and the binding survives live sim --ble advertisements (ProcessRelayMessage upserts never touch person_id). Verified 2026-07-08 at HEAD 36b9729: ./scripts/run-sim-ble-fixture.sh PASSes phaseA (bind with NO live adv) and phaseB (binding survives live adv), exit 0. All four acceptance criteria met; deliberately excludes scripts/run-sim- identity.sh (parent bf-2m534 capstone) and notes/hardware-free-runtime.md (bf-40hc re-verify). Bead-Id: bf-14wx5
This commit is contained in:
parent
09bd6e372d
commit
83c761a5e8
2 changed files with 423 additions and 0 deletions
192
notes/ble-identity-fixture.md
Normal file
192
notes/ble-identity-fixture.md
Normal file
|
|
@ -0,0 +1,192 @@
|
|||
# BLE Identity Fixture — Registered Person + Sim Device (bf-14wx5)
|
||||
|
||||
**Canonical script:** `scripts/run-sim-ble-fixture.sh`
|
||||
**Verified:** 2026-07-08 — HEAD `36b9729` (post bf-21v71 Stage 2b write-back).
|
||||
**Resolves:** bf-14wx5 — the FIRST link of the bf-2m534 identity chain. Establishes the
|
||||
BLE fixture (a registered person + the sim's advertised device bound to that person) that
|
||||
the identity matcher needs to produce ANY match. Without it the matcher has nothing to
|
||||
match and identity stays empty regardless of the wire fix.
|
||||
|
||||
This note records the exact REST sequence, the observed sim BLE address convention, and
|
||||
the answer to "must the device be seen-live first?" so that later beads (the matcher /
|
||||
`/api/blobs` identity capstone under bf-2m534, and `scripts/run-sim-identity.sh`) can
|
||||
**reuse, not re-derive.**
|
||||
|
||||
---
|
||||
|
||||
## TL;DR
|
||||
|
||||
```bash
|
||||
./scripts/run-sim-ble-fixture.sh
|
||||
# expects:
|
||||
# [fixture] phaseA (preregister+PUT, NO live adv): PASS
|
||||
# [fixture] phaseB (live sim --ble adv, binding survives): PASS
|
||||
# [fixture] PASS: GET /api/ble/devices confirms AA:BB:CC:DD:EE:00 <-> "Alice" in both phases
|
||||
```
|
||||
|
||||
The sim advertises each walker as device addr `AA:BB:CC:DD:EE:%02X` (walker.ID, from 0),
|
||||
name `sim-person-%d`. With `--walkers 1` the advertised addr is exactly
|
||||
`AA:BB:CC:DD:EE:00`. Bind it to a person with three REST calls, then `GET /api/ble/devices`
|
||||
confirms `person_id` / `person_name` are non-empty.
|
||||
|
||||
---
|
||||
|
||||
## The repeatable REST sequence (the fixture)
|
||||
|
||||
Against a running mothership (start one with `scripts/run-sim-local.sh`'s env, or any
|
||||
fresh-data-dir instance — `SPAXEL_DATA_DIR=$(mktemp -d)`):
|
||||
|
||||
```bash
|
||||
PORT=8088
|
||||
MAC="AA:BB:CC:DD:EE:00" # sim walker 0 (see convention below)
|
||||
NAME="Alice"
|
||||
COLOR="#4488ff"
|
||||
|
||||
# 1. Create the device row for the known MAC. Sets last_seen_at=now, so the device
|
||||
# appears in GET /api/ble/devices even with NO live advertisement (see "seen-live").
|
||||
curl -s -X POST http://localhost:$PORT/api/ble/devices/preregister \
|
||||
-H 'Content-Type: application/json' -d "{\"mac\":\"$MAC\",\"label\":\"$NAME\"}"
|
||||
|
||||
# 2. Create the person; capture its id.
|
||||
PERSON_ID=$(curl -s -X POST http://localhost:$PORT/api/people \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d "{\"name\":\"$NAME\",\"color\":\"$COLOR\"}" | jq -r .id)
|
||||
|
||||
# 3. Assign the device to the person (sets ble_devices.person_id).
|
||||
curl -s -X PUT http://localhost:$PORT/api/ble/devices/$MAC \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d "{\"person_id\":\"$PERSON_ID\",\"label\":\"$NAME\"}"
|
||||
|
||||
# 4. Verify: device is associated to the seeded person.
|
||||
curl -s "http://localhost:$PORT/api/ble/devices?registered=true" \
|
||||
| jq '.devices[]|select(.mac=="AA:BB:CC:DD:EE:00")|{mac,label,person_id,person_name}'
|
||||
```
|
||||
|
||||
Expected (verified 2026-07-08):
|
||||
|
||||
```json
|
||||
{ "mac": "AA:BB:CC:DD:EE:00", "label": "Alice",
|
||||
"person_id": "<uuid>", "person_name": "Alice" }
|
||||
```
|
||||
|
||||
Endpoint reference: `mothership/internal/ble/handler.go`
|
||||
(`POST /api/ble/devices/preregister`, `POST /api/people`, `PUT /api/ble/devices/{mac}`,
|
||||
`GET /api/ble/devices`).
|
||||
|
||||
---
|
||||
|
||||
## Sim BLE address convention (observed)
|
||||
|
||||
Source: `mothership/cmd/sim/main.go`, `sendBLEMessages` (line ~1125). For each walker:
|
||||
|
||||
```go
|
||||
"addr": fmt.Sprintf("AA:BB:CC:DD:EE:%02X", walker.ID),
|
||||
"rssi_dbm": int(rssi), // -50 - 20*log10(dist), floored at -90
|
||||
"name": fmt.Sprintf("sim-person-%d", walker.ID),
|
||||
```
|
||||
|
||||
`walker.ID = i` (the loop index, from 0 — `mothership/cmd/sim/main.go` walker constructors
|
||||
`ID: i`). So:
|
||||
|
||||
| `--walkers N` | Advertised addrs (one per walker) |
|
||||
|---------------|-----------------------------------|
|
||||
| 1 | `AA:BB:CC:DD:EE:00` |
|
||||
| 2 | `AA:BB:CC:DD:EE:00`, `AA:BB:CC:DD:EE:01` |
|
||||
| k | `AA:BB:CC:DD:EE:00` … `AA:BB:CC:DD:EE:%02X` (k−1) |
|
||||
|
||||
BLE cadence: the sim relays one `{type:"ble",...}` message per node every 5 s
|
||||
(`if *flagBLE && time.Since(lastBLETime) > 5*time.Second`). Each node reports every
|
||||
walker, so a 4-node fleet yields 4 RSSI samples per walker per 5 s window — enough for
|
||||
the matcher's triangulation.
|
||||
|
||||
> **Field-name note (bf-21v71):** the sim previously sent `"rssi"`; the mothership's BLE
|
||||
> parser reads `rssi_dbm`, so RSSI was read as 0 and triangulation collapsed. The sim now
|
||||
> sends `"rssi_dbm"` (uncommitted working-tree change at `mothership/cmd/sim/main.go`).
|
||||
> This is the third precondition the diagnosis `notes/blob-identity-diagnosis.md` named.
|
||||
|
||||
---
|
||||
|
||||
## "Must the device be seen-live first?" — NO (with evidence)
|
||||
|
||||
**Answer: `preregister` + `PUT` binds the sim device addr standalone; the device does NOT
|
||||
need to be seen live first.** Evidence (bf-14wx5 Phase A, `scripts/run-sim-ble-fixture.sh`):
|
||||
|
||||
The fixture was seeded with the sim **not running** (no live advertisement), and
|
||||
`GET /api/ble/devices?registered=true` still returned the device fully bound:
|
||||
|
||||
```
|
||||
[fixture] Phase A: seed fixture BEFORE any sim BLE advertisement (no live adv)...
|
||||
[fixture] [phaseA] OK: AA:BB:CC:DD:EE:00 -> person_id=345dcfeb-… person_name="Alice"
|
||||
```
|
||||
|
||||
**Why it works** (code, `mothership/internal/ble/registry.go`):
|
||||
|
||||
- `PreregisterDevice` (`INSERT … VALUES (… last_seen_at=now …)`) sets `last_seen_at` to
|
||||
the current time on insert.
|
||||
- `GetDevicesSeenInHours` (the query behind `GET /api/ble/devices`) filters
|
||||
`WHERE d.last_seen_at >= <now − hours>`. Because preregister sets `last_seen_at=now`,
|
||||
the device falls inside the default 24 h window and is served — no live sighting needed.
|
||||
|
||||
**The binding also survives live advertisements.** Phase B of the same run started
|
||||
`sim --ble` after the fixture was seeded, let advertisements flow, and re-verified:
|
||||
|
||||
```
|
||||
[fixture] [phaseB] OK: AA:BB:CC:DD:EE:00 -> person_id=345dcfeb-… person_name="Alice"
|
||||
…
|
||||
{ "mac": "AA:BB:CC:DD:EE:00", "label": "Alice", "person_id": "345dcfeb-…",
|
||||
"person_name": "Alice", "rssi_avg": -60, "last_seen_node": "AA:BB:CC:00:00:02" }
|
||||
```
|
||||
|
||||
`ProcessRelayMessage` (the live-adv path) upserts with `ON CONFLICT(mac) DO UPDATE` that
|
||||
touches only `device_type`, `device_name`, `manufacturer`, mfr fields, rssi stats,
|
||||
`last_seen_at`, `last_seen_node`, `is_wearable` — **it never writes `person_id`**, so a
|
||||
live sighting cannot clobber the assignment. (Conversely, registering a device that is
|
||||
already seen-live works identically — that is the realistic onboarding order exercised by
|
||||
`scripts/run-sim-identity.sh`.)
|
||||
|
||||
> **Caveat on re-running `preregister`:** the `ON CONFLICT(mac) DO UPDATE` clause updates
|
||||
> only `name`/`label`, **not** `last_seen_at`. So a second `preregister` on an already-seen
|
||||
> device will not refresh its visibility window. For the fixture this is irrelevant (one
|
||||
> preregister on a fresh data dir), but if you ever need to re-surface a stale device, send
|
||||
> a live advertisement or bump `last_seen_at` directly — don't rely on re-preregistering.
|
||||
|
||||
---
|
||||
|
||||
## What this fixture enables (for later beads)
|
||||
|
||||
The identity matcher (`mothership/internal/ble`) only considers devices with a non-null
|
||||
`person_id` (e.g. `GetAllPersonDevices` / `GetAllPersonDevicesWithAliases` filter
|
||||
`WHERE person_id IS NOT NULL`). So this fixture — a person + a bound device — is the
|
||||
**precondition** for any identity match to fire at all. Downstream:
|
||||
|
||||
- **bf-2m534 / `scripts/run-sim-identity.sh`** (the parent capstone): pairs this fixture
|
||||
with `sim --ble` and the Stage 2b write-back (bf-21v71) to assert a tracked blob served
|
||||
at `/api/blobs` carries non-empty canonical identity (`personName`/`assignedColor`).
|
||||
- **bf-m1ynp diagnosis** (`notes/blob-identity-diagnosis.md`): named "no person
|
||||
registered" as a required fix; this fixture is that fix, factored out as the first link.
|
||||
|
||||
This script deliberately does NOT gate on `/api/blobs` identity — that is the parent
|
||||
bead's concern. It gates only on `GET /api/ble/devices`, keeping the fixture link
|
||||
independently verifiable.
|
||||
|
||||
---
|
||||
|
||||
## Reuse, don't re-derive
|
||||
|
||||
- **Start from `scripts/run-sim-ble-fixture.sh`.** It builds both binaries from
|
||||
`mothership/cmd/{mothership,sim}` (the canonical sim with `--ble`; never the repo-root
|
||||
`cmd/sim/` — see `notes/hardware-free-runtime.md` gotcha #1), starts a fresh-data-dir
|
||||
mothership, and runs both phases.
|
||||
- **All params overridable:** `SIM_PORT`, `SIM_WALKER_MAC`, `SIM_PERSON_NAME`,
|
||||
`SIM_PERSON_COLOR`, `SIM_NODES`, `SIM_WALKERS`, `SIM_RATE`, `SIM_SEED`, `SIM_SPACE`.
|
||||
- **Determinism:** `--seed` pins walker paths; walker 0 always advertises
|
||||
`AA:BB:CC:DD:EE:00`. The only residual nondeterminism is the ~5 s BLE relay cadence,
|
||||
absorbed by the 7 s settle before the Phase B re-verify.
|
||||
|
||||
## Keeping this honest
|
||||
|
||||
If `scripts/run-sim-ble-fixture.sh` ever fails Phase A: the `PreregisterDevice` →
|
||||
`last_seen_at=now` invariant (or the `GetDevicesSeenInHours` window) changed; re-read
|
||||
`registry.go` before assuming the fixture is wrong. If it fails Phase B only:
|
||||
`ProcessRelayMessage`'s upsert started touching `person_id`, or the sim stopped emitting
|
||||
`rssi_dbm` — check `mothership/cmd/sim/main.go` `sendBLEMessages` first.
|
||||
231
scripts/run-sim-ble-fixture.sh
Executable file
231
scripts/run-sim-ble-fixture.sh
Executable file
|
|
@ -0,0 +1,231 @@
|
|||
#!/usr/bin/env bash
|
||||
#
|
||||
# run-sim-ble-fixture.sh — the BLE identity FIXTURE (bf-14wx5).
|
||||
#
|
||||
# This is the FIRST link of the bf-2m534 identity chain: it establishes the BLE
|
||||
# fixture a registered person + a sim device bound to that person that the
|
||||
# identity matcher needs to produce ANY match. It does NOT assert anything about
|
||||
# /api/blobs identity that is the parent bead's capstone (scripts/run-sim-identity.sh,
|
||||
# bf-2m534/bf-5h1t). This script's acceptance is narrower and directly verifiable:
|
||||
#
|
||||
# GET /api/ble/devices on a running mothership confirms the sim's advertised
|
||||
# device addr is associated to the seeded person (person_id / person_name non-empty).
|
||||
#
|
||||
# It also answers the "must the device be seen-live first?" question empirically:
|
||||
#
|
||||
# Phase A registers + binds the device with NO live BLE advertisement flowing
|
||||
# (sim not started yet), proving preregister+PUT binds standalone.
|
||||
# Phase B then starts sim --ble and re-verifies, proving the binding survives
|
||||
# live advertisements (ProcessRelayMessage upserts never touch person_id).
|
||||
#
|
||||
# Sim BLE addr convention (mothership/cmd/sim/main.go: sendBLEMessages):
|
||||
# each walker advertises addr = fmt.Sprintf("AA:BB:CC:DD:EE:%02X", walker.ID),
|
||||
# name = fmt.Sprintf("sim-person-%d", walker.ID), and walker.ID = i (from 0).
|
||||
# So --walkers 1 advertises exactly AA:BB:CC:DD:EE:00.
|
||||
#
|
||||
# No hardware, no Docker, no manual IP, no manual token. Reuses the build + health
|
||||
# path proven by run-sim-local.sh. See notes/ble-identity-fixture.md for the full
|
||||
# reasoning, exact REST calls, and evidence.
|
||||
#
|
||||
# Usage: ./scripts/run-sim-ble-fixture.sh
|
||||
# Env: SIM_NODES, SIM_WALKERS, SIM_RATE, SIM_DURATION, SIM_SEED, SIM_PORT,
|
||||
# SIM_SPACE, SIM_WALKER_MAC, SIM_PERSON_NAME, SIM_PERSON_COLOR,
|
||||
# SPAXEL_MOTHERSHIP_BIN, SPAXEL_SIM_BIN
|
||||
# Exit: 0 if GET /api/ble/devices confirms the device<->person binding in both
|
||||
# phases; 1 otherwise.
|
||||
#
|
||||
# Requires: go, curl, jq.
|
||||
|
||||
set -uo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
MS="${SPAXEL_MOTHERSHIP_BIN:-/tmp/spaxel-mothership}"
|
||||
SIM="${SPAXEL_SIM_BIN:-/tmp/spaxel-sim}"
|
||||
PORT="${SIM_PORT:-8088}"
|
||||
|
||||
# walkers=1: one sim device (AA:BB:CC:DD:EE:00) -> one registered person. rate=30
|
||||
# mirrors run-sim-local.sh's verified default (REQUIRED for deterministic blob
|
||||
# emission, though blobs are not this script's gate).
|
||||
SIM_NODES="${SIM_NODES:-4}"
|
||||
SIM_WALKERS="${SIM_WALKERS:-1}"
|
||||
SIM_RATE="${SIM_RATE:-30}"
|
||||
SIM_DURATION="${SIM_DURATION:-15}"
|
||||
SIM_SEED="${SIM_SEED:-42}"
|
||||
SIM_SPACE="${SIM_SPACE:-5x5x2.5}"
|
||||
|
||||
# Walker 0 -> BLE addr AA:BB:CC:DD:EE:00 (cmd/sim/main.go: AA:BB:CC:DD:EE:%02X, IDs from 0).
|
||||
WALKER_MAC="${SIM_WALKER_MAC:-AA:BB:CC:DD:EE:00}"
|
||||
PERSON_NAME="${SIM_PERSON_NAME:-Alice}"
|
||||
PERSON_COLOR="${SIM_PERSON_COLOR:-#4488ff}"
|
||||
|
||||
log() { printf '[fixture] %s\n' "$*"; }
|
||||
die() { printf '[fixture] ERROR: %s\n' "$*" >&2; exit 1; }
|
||||
|
||||
for dep in go curl jq; do
|
||||
command -v "$dep" >/dev/null 2>&1 || die "missing required dependency: $dep"
|
||||
done
|
||||
|
||||
# --- Build both binaries from source (reuse if present; force sim has --ble) ------
|
||||
# Canonical-source rule (see notes/hardware-free-runtime.md gotcha #1): build ONLY
|
||||
# mothership/cmd/sim (it has --ble/--verify); never the repo-root cmd/sim/ (older).
|
||||
log "building canonical binaries from source (reuse if present)..."
|
||||
need_build=0
|
||||
[ -x "$MS" ] || need_build=1
|
||||
[ -x "$SIM" ] || need_build=1
|
||||
if [ -x "$SIM" ] && ! "$SIM" --help 2>&1 | grep -q -- '--ble'; then
|
||||
log "existing $SIM lacks --ble (stale/standalone build); rebuilding..."
|
||||
rm -f "$SIM"; need_build=1
|
||||
fi
|
||||
if [ "$need_build" -eq 1 ]; then
|
||||
( cd "$ROOT/mothership" || exit 1
|
||||
go build -o "$MS" ./cmd/mothership || die "building mothership"
|
||||
go build -o "$SIM" ./cmd/sim || die "building sim"
|
||||
) || exit 1
|
||||
fi
|
||||
|
||||
DATA_DIR=$(mktemp -d -t spaxel-fixture-data-XXXXXX)
|
||||
MS_PID=""
|
||||
SIM_PID=""
|
||||
|
||||
cleanup() {
|
||||
[ -n "$SIM_PID" ] && kill "$SIM_PID" 2>/dev/null || true
|
||||
[ -n "$MS_PID" ] && kill -INT "$MS_PID" 2>/dev/null || true
|
||||
sleep 1
|
||||
[ -n "$MS_PID" ] && kill -9 "$MS_PID" 2>/dev/null || true
|
||||
wait 2>/dev/null || true
|
||||
rm -rf "$DATA_DIR"
|
||||
}
|
||||
trap cleanup EXIT INT TERM
|
||||
|
||||
log "mothership: $MS"
|
||||
log "sim: $SIM"
|
||||
log "fixture: $WALKER_MAC -> person \"$PERSON_NAME\" ($PERSON_COLOR) [walkers=$SIM_WALKERS]"
|
||||
|
||||
# --- Start mothership on an ephemeral data dir (identical env to run-sim-local) --
|
||||
SPAXEL_BIND_ADDR="127.0.0.1:$PORT" \
|
||||
SPAXEL_DATA_DIR="$DATA_DIR" \
|
||||
SPAXEL_LOG_LEVEL=warn \
|
||||
SPAXEL_MDNS_ENABLED=false \
|
||||
TZ=UTC \
|
||||
"$MS" > "$DATA_DIR/mothership.log" 2>&1 &
|
||||
MS_PID=$!
|
||||
|
||||
ok=""
|
||||
for _ in $(seq 1 50); do
|
||||
if resp=$(curl -s --max-time 2 "http://localhost:$PORT/healthz" 2>/dev/null) && \
|
||||
[ "$(printf '%s' "$resp" | jq -r '.status // empty' 2>/dev/null)" = "ok" ]; then
|
||||
ok=1; break
|
||||
fi
|
||||
sleep 0.3
|
||||
done
|
||||
[ -n "$ok" ] || { log "mothership never became healthy"; tail -30 "$DATA_DIR/mothership.log"; exit 1; }
|
||||
log "mothership healthy (port $PORT)"
|
||||
|
||||
# Bind a helper that asserts GET /api/ble/devices shows the device<->person binding.
|
||||
# Echoes "OK <json>" on success (person_id and person_name non-empty for $WALKER_MAC).
|
||||
verify_binding() {
|
||||
local label="$1"
|
||||
local rec
|
||||
rec=$(curl -s --max-time 2 "http://localhost:$PORT/api/ble/devices?registered=true" \
|
||||
| jq -c --arg mac "$WALKER_MAC" '[.devices[]|select(.mac==$mac)][0]' 2>/dev/null)
|
||||
if [ "$rec" = "null" ] || [ -z "$rec" ]; then
|
||||
log " [$label] $WALKER_MAC NOT in /api/ble/devices?registered=true"
|
||||
return 1
|
||||
fi
|
||||
local pid pname
|
||||
pid=$(printf '%s' "$rec" | jq -r '.person_id // empty')
|
||||
pname=$(printf '%s' "$rec" | jq -r '.person_name // empty')
|
||||
if [ -z "$pid" ] || [ -z "$pname" ]; then
|
||||
log " [$label] $WALKER_MAC present but unbound (person_id=\"$pid\" person_name=\"$pname\")"
|
||||
return 1
|
||||
fi
|
||||
log " [$label] OK: $WALKER_MAC -> person_id=$pid person_name=\"$pname\""
|
||||
printf '%s\n' "$rec" > "$DATA_DIR/${label}.json"
|
||||
return 0
|
||||
}
|
||||
|
||||
# --- The repeatable REST fixture sequence (register person + bind device) --------
|
||||
# POST /api/ble/devices/preregister -> create the device row (sets last_seen_at=now,
|
||||
# so the device shows in GET /api/ble/devices even with NO live advertisement).
|
||||
# POST /api/people -> create the person, capture its id.
|
||||
# PUT /api/ble/devices/{mac} -> assign device -> person (sets person_id).
|
||||
seed_fixture() {
|
||||
curl -s -X POST "http://localhost:$PORT/api/ble/devices/preregister" \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d "{\"mac\":\"$WALKER_MAC\",\"label\":\"$PERSON_NAME\"}" >/dev/null
|
||||
person_id=$(curl -s -X POST "http://localhost:$PORT/api/people" \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d "{\"name\":\"$PERSON_NAME\",\"color\":\"$PERSON_COLOR\"}" \
|
||||
| jq -r '.id // empty' 2>/dev/null)
|
||||
[ -n "$person_id" ] || die "could not create person \"$PERSON_NAME\" via /api/people"
|
||||
curl -s -X PUT "http://localhost:$PORT/api/ble/devices/$WALKER_MAC" \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d "{\"person_id\":\"$person_id\",\"label\":\"$PERSON_NAME\"}" >/dev/null
|
||||
SEED_PERSON_ID="$person_id"
|
||||
}
|
||||
|
||||
# ================= Phase A: bind with NO live advertisement ======================
|
||||
# Proves preregister+PUT binds standalone — the device does NOT need to be seen
|
||||
# live first. This directly answers bf-14wx5 acceptance criterion #4.
|
||||
log "Phase A: seed fixture BEFORE any sim BLE advertisement (no live adv)..."
|
||||
SEED_PERSON_ID=""
|
||||
seed_fixture
|
||||
log "registered: $WALKER_MAC -> person \"$PERSON_NAME\" (id=$SEED_PERSON_ID)"
|
||||
phaseA=1
|
||||
verify_binding "phaseA" || phaseA=0
|
||||
|
||||
# ================= Phase B: start sim --ble, re-verify ===========================
|
||||
# Proves the binding survives live advertisements (ProcessRelayMessage upserts
|
||||
# rssi/last_seen but never person_id) and the device is now seen live (rssi set).
|
||||
log "Phase B: starting sim --ble (walkers=$SIM_WALKERS, seed=$SIM_SEED)..."
|
||||
"$SIM" \
|
||||
--mothership "ws://localhost:$PORT/ws/node" \
|
||||
--nodes "$SIM_NODES" --walkers "$SIM_WALKERS" --rate "$SIM_RATE" \
|
||||
--space "$SIM_SPACE" \
|
||||
--duration "$SIM_DURATION" --seed "$SIM_SEED" --ble \
|
||||
> "$DATA_DIR/sim.log" 2>&1 &
|
||||
SIM_PID=$!
|
||||
# Let at least one BLE adv relay land (~5s BLE cadence in the sim).
|
||||
log "waiting 7s for sim BLE advertisements to land..."
|
||||
sleep 7
|
||||
phaseB=1
|
||||
verify_binding "phaseB" || phaseB=0
|
||||
|
||||
# Capture the full live device record (incl. rssi, now populated by live advs) and
|
||||
# the people view as corroboration for the note.
|
||||
curl -s --max-time 2 "http://localhost:$PORT/api/ble/devices" \
|
||||
| jq -c --arg mac "$WALKER_MAC" '[.devices[]|select(.mac==$mac)][0]' > "$DATA_DIR/phaseB_full.json" 2>/dev/null
|
||||
curl -s --max-time 2 "http://localhost:$PORT/api/people" > "$DATA_DIR/people.json" 2>/dev/null
|
||||
|
||||
# Stop the sim so the run is bounded.
|
||||
kill "$SIM_PID" 2>/dev/null || true
|
||||
wait "$SIM_PID" 2>/dev/null || true
|
||||
SIM_PID=""
|
||||
reject_count=$(grep -ciE 'reject|invalid_token|\b401\b|\b403\b' "$DATA_DIR/sim.log" 2>/dev/null)
|
||||
|
||||
echo ""
|
||||
log "---- RESULT ----"
|
||||
log "phaseA (preregister+PUT, NO live adv): $([ "$phaseA" = 1 ] && echo PASS || echo FAIL)"
|
||||
log "phaseB (live sim --ble adv, binding survives): $([ "$phaseB" = 1 ] && echo PASS || echo FAIL)"
|
||||
log "seeded person: \"$PERSON_NAME\" (id=$SEED_PERSON_ID) device: $WALKER_MAC"
|
||||
log "reject_in_sim_log=${reject_count:-0}"
|
||||
echo ""
|
||||
log "evidence: /api/ble/devices?registered=true record after live adv (phaseB_full):"
|
||||
if [ -s "$DATA_DIR/phaseB_full.json" ] && [ "$(cat "$DATA_DIR/phaseB_full.json")" != "null" ]; then
|
||||
jq '{mac,label,person_id,person_name,rssi_avg,last_seen_node,last_seen_at}' "$DATA_DIR/phaseB_full.json" 2>/dev/null \
|
||||
|| cat "$DATA_DIR/phaseB_full.json"
|
||||
else
|
||||
log " (no record)"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# --- Acceptance gate: GET /api/ble/devices confirms the binding in BOTH phases ----
|
||||
if [ "$phaseA" != 1 ] || [ "$phaseB" != 1 ]; then
|
||||
log "FAIL: binding not confirmed via GET /api/ble/devices (phaseA=$phaseA phaseB=$phaseB)"
|
||||
log " (mothership.log tail for diagnostics:)"
|
||||
tail -20 "$DATA_DIR/mothership.log" >&2 || true
|
||||
exit 1
|
||||
fi
|
||||
log "PASS: GET /api/ble/devices confirms $WALKER_MAC <-> \"$PERSON_NAME\" in both phases"
|
||||
log " (preregister+PUT binds with NO live adv; binding survives live sim --ble advs)"
|
||||
exit 0
|
||||
Loading…
Add table
Reference in a new issue