Repository navigation
feat(cbio): stream GS1 history and live readings into the app - #142
shroominic wants to merge 11 commits into
Conversation
The GS1 driver now owns one authenticated session: connect, subscribe on FF31, authenticate, ingest the pushed 08 history, then poll the live edge and publish every reading as an explicitly provisional raw record. App surface changes: - the nearby-sensor list can finally complete, so a real sensor is selectable from the dashboard flow, - the hero value renders the sensor's raw field / 10 with no glucose unit, - the hero reports how many records were stored and their sensor-minute range, so the fetch is verifiable without opening a chart, - the provisional marker is on every rendered number and reads "sensor raw / 10, scale unverified". Reading positions come from the protocol's own index counter; the sensor's second counter has no epoch, so no wall-clock timestamp is invented from it. Evidence, screenshots, and the reproduction steps are in docs/testing/cbio-gs1-app-live.md.
|
Correctness items raised against this work are now tracked separately, and this PR carries their fix:
Screenshots for the current screen: |
|
Cross-lane gate note from the release assembly (aggregate #163 @ Merging this lane into the aggregate together with the open hardening PRs leaves the tree non-compiling, and the break is on this lane. Two independent causes, both mechanical:
I am not editing this lane's files. The aggregate currently carries an explicit revert of both merges so the assembled candidate compiles and CI can run; the revert is recorded, not a silent drop. The aggregate cannot absorb #166/#168 until this lane adopts the injected Also: this lane's |
|
Cross-lane note: the app-live branch no longer compiles against the redacted vendor API, so #192 carries the adoption ( One reviewable decision is flagged in #192: the platform registry now registers the GS1 driver only when the build supplied the vendor material, so |
…ants The vendor GS1 stream key, link credential, and authentication prompt were plaintext constants in packages/cgm_cbio/lib/src/cbio_crypto.dart. The repository is public, the file is on the 0.4.0 release aggregate, and the Secret Scan job is red on the same lane for a fixture derived from it. Remove the constants and make the material an explicit input: * cbio_credentials.dart adds CbioCredentials (validated, immutable, redacting toString), CbioCredentialSource, CbioMapCredentialSource, CbioDefineCredentialSource, and the fail-closed CbioCredentialUnavailable. * cbioRc4Keystream, maskCbioFrame, unmaskCbioFrame, and every builder in cbio_vendor_frames.dart now take the key and material as required arguments, so a caller that has not resolved material cannot compile a call. * the device harness reads the material from the process environment or --dart-define and aborts with vendor-material-missing before touching radio. * tests use clearly synthetic fixtures; the real-material checks skip unless the values are injected. * .gitleaks.toml adds a rule for this exact class of literal and a narrow allowlist for synthetic fixtures only. Refs #164 (cherry picked from commit 7fa477c)
…ource The live session still called the pre-redaction crypto API: `maskCbioFrame`, `unmaskCbioFrame`, and every masked builder defaulted to compiled constants, and the session read its link credential from `CbioCompiledAuthMaterialProvider`. Those defaults are gone, so the lane has to resolve the material explicitly. - `CbioGlucoseSession` takes a `CbioCredentialSource` and resolves it once per session, before the transport is touched. An unconfigured build fails closed with `cbio.auth.material` and a configuration status text instead of opening a link it could never authenticate or unmask. - Every mask, unmask, and masked builder receives the resolved key explicitly, and the authentication frame receives the resolved material. - `CbioSensorDriver` exposes its source and a `canAuthenticate` predicate. - The app's platform registry registers the GS1 driver only when the build supplied the material, so a plain build does not offer a sensor it cannot read. The capture harness reads the material from the environment or `--dart-define` and reports `vendor-material-missing` when it is absent. Tests: cgm_cbio 81 passed (2 skipped), openhealth 480 passed; `flutter analyze` clean in both, including `openhealth/integration_test`.
|
Lane head moved: #192 landed as a rebase merge, so
Verified on the new head in a clean worktree: |
…ishes #146 recorded a +7h28m skew on screen: the hero read `Latest reading at 09:04` and the 12 h axis ended at `09:01` while the phone clock read `02:34`. The counter advances 60 s per stored record, so 448 stored records rendered as 7 h 28 m of apparent elapsed time. The rendering half of that fix lives in the app-live branch: the session publishes `recordedAt: null` for these records, the hero reports the protocol position (`Sensor raw value · index 10067`), and the chart's null path renders `m<minute>`. Nothing on the lane pins that, so this adds the guard: - the removed counter-to-clock formula is asserted to be the on-screen skew (448 x 60 s == 7 h 28 m), so the arithmetic cannot be re-derived as harmless; - a GS1 reading is asserted to carry no `recordedAt`, and the hero's only time formatter is asserted to refuse to invent one; - the real dashboard is rendered with those readings and asserted to show the protocol position with no `HH:mm` anywhere in the hero, and to hand the chart only timestamp-free readings. Mutation check: reinstating `recordedAt: DateTime.fromMillisecondsSinceEpoch(record.rawTime * 1000, isUtc: true)` in `CbioGlucoseSession._historyReadings` fails `cbio_glucose_session_test.dart` with `Expected: null / Actual: DateTime:<...>`. Tests: openhealth 483 passed; `flutter analyze` clean; `dart format` clean on the added file.
The GS1 surface published no time at all: the `08` counter is epoch-less, so #146/`#193` forbid rendering a wall clock from it. That left the hero with `Sensor raw value · index 10070` and nothing else, and any surface that did ask for a time showed a placeholder. The one reference the app establishes itself is the sensor clock it writes once per session. `CbioIndexTimeAnchor` turns that into absolute time honestly: - an anchor exists only when the session wrote the clock and the sensor's own newest record stamp agrees with the app's clock within `cbioAnchorTolerance` (3 min); - the anchored position carries its own stamp, and every older position steps 60 s per index, as the vendor layout reports the counter (`base + 60 * i`); - the range the anchor speaks for ends at the first hole or counter jump, so positions the archive cannot support keep no timestamp; - the anchor travels in the snapshot metadata (`anchorIndex`, `anchorEpochSeconds`, `anchorCoveredFrom`, `referenceEpochSeconds`) and the diagnostics name it, so provenance is readable without the radio. The counter is never the source: a sensor that did not take the written clock (the 7 h 28 m skew of #146) publishes no anchor, and the hero says `Sensor clock unsynced · ordered by sensor index, not by clock` instead of a placeholder or an invented minute. With an anchor the hero reads `Sensor clock set by this app · latest 10:05 (±1 min)`, localised by the device. Refs #146, #193. Tests: cgm_cbio 91 passed (2 skipped), openhealth 485 passed; `flutter analyze` clean in both, including `openhealth/integration_test`.
Adopt the archive fix from `fix/cbio-archive-unit-claim` (#168, e81e335) into the app-live lane. The record used to expose the raw `08` field three ways: `rawCurrent`, a derived mmol/L accessor (`rawCurrent / 10`) and a derived mg/dL accessor (`rawCurrent / 10 * 18.0182`). Nothing in this repository establishes that either unit belongs to the field, so the record now exposes `rawCurrent` and `rawCurrentScaled` and no unit-bearing accessor at all. The six `cgm_cbio` analyzer errors that blocked the adoption were the session and its tests still reading the removed accessors: - `_historyReadingsFor` published the mg/dL derivation into `CgmReading.valueMgdl`; it now publishes `rawCurrentScaled`, the same unit-free `/10` scale the hero already renders. - the session tests asserted the removed accessors; they now pin the scale itself and the number the session publishes (`raw 97 -> 9.7`). This changes the number a cbio record carries, so it changes what the dashboard chart plots (`raw 53`: `95` before, `5.3` now). The hero is unchanged - it already rendered `rawValue / 10` with no unit - and provisional readings stay excluded from Apple Health, so no surface claims the number as mg/dL. Documented in `docs/testing/cbio-gs1-app-live.md`. Refs #168, #145
|
Carried by the GS1 consolidation in PR #209 (branch feature/cbio-gs1, head 3eca4f4). This PR is contained in the consolidated line; the merge that carries it is 128d952. Closing as subsumed by the consolidation - not as won't fix. The per-defect evidence stays in this thread, and the carrying commit above is where the change now lives. |
Stacks on
feature/cbio-gs1-glucose-live. Refs #135, #137.What this proves on hardware
The installed debug app on the Pixel 6 reaches the live sensor screen and shows
both halves of the goal: a live value and fetched history.
5.3, no unit suffix, stage pillLiveSensor raw value · index 1006710067 readings stored · sensor minutes 1–10067Provisional reading. Sensor raw / 10, scale unverified.10067 readings, same marker,Sensor history complete. 1 records.Screenshots:
/tmp/og-cbio-live-raw-800.png(current) and/tmp/og-cbio-live-800.png(earlier capture that still claimed mg/dL).adb logcatshows[FBP] onCharacteristicChanged: chr: ff31roughly every500 ms while that screen is up. Reproduction is in
docs/testing/cbio-gs1-app-live.md.Unit and time provenance
0x08record's raw counter is shown divided by ten (raw56→5.3).The capture harness read raw
47..53from the same field at index9940..9992, i.e.4.7..5.3; app and harness agree on field and scale.keeps
isUnitVerified = falseand the marker travels with every number.invented wall-clock timestamp; positions are the protocol's
indexcounter.Verification
packages/cgm_cbio:flutter analyzeclean,flutter test75 passed.openhealth:flutter analyzeclean,flutter test480 passed.com.openglucose.app.debugbuild.Depends on
The nearby-sensor scan in this repo hangs forever because the
FlutterBluePlus transport never closes its scan stream; the focused fix is in
its own PR. This branch assumes discovery completes.