8000 chore(release): append the app-live and scan-close lanes to the 0.4.0 aggregate by shroominic · Pull Request #163 · shroominic/OpenGlucose · GitHub
[go: up one dir, main page]

Skip to content

chore(release): append the app-live and scan-close lanes to the 0.4.0 aggregate - #163

Merged
shroominic merged 53 commits into
release/v0.4.0from
release/v0.4.0-lane-append
Sep 19, 2026
Merged

shroominic merged 53 commits into
release/v0.4.0from
release/v0.4.0-lane-append

Conversation

@shroominic
@shroominic shroominic commented Sep 18, 2026 •
Copy link
Copy Markdown
Owner

Assembled 0.4.0 candidate for #154. Base release/v0.4.0; head release/v0.4.0-lane-append @ 4a865cd, mergeStateStatus: CLEAN. Every lane tip claimed below is a real ancestor. Nothing here merges: this PR and every lane PR stay open.

Gate matrix — all ten checks green on 4a865cd

Check Result Head Evidence
Quality (tooling-check format-check lint) pass 3m29s 4a865cd run 35314886052 job 105504308629
Unit Tests pass 3m51s 4a865cd run 35314886052 job 105504308544
Integration Tests pass 1m31s 4a865cd 8000 run 35314886052 job 105504308472
Secret Scan pass 8s 4a865cd run 35314886090 job 105504308405
Vendor Material Guard pass 6s 4a865cd run 35314886090 job 105504308568
Android Build pass 6m28s 4a865cd run 35314886052 job 105504308554
iOS Build and Native Tests pass 14m16s 4a865cd run 35314886052 job 105504308350
Build Apple-silicon macOS preview pass 9m8s 4a865cd run 35314886042 job 105504308255
Web Build pass 2m5s 4a865cd run 35314886052 job 105504308522
Dependency Review pass 7s 4a865cd run 35314886090 job 105504308606

The same matrix was green on 92a1de2 (Quality 3m24s, Unit 3m6s, Integration 1m48s, Vendor Guard 4s, Android 5m54s, iOS 11m53s, macOS 11m15s, Web 2m16s, Secret Scan 6s, Dependency Review 9s); 4a865cd adds only the two changelog entries, and this table is that head's own run.

The head before 92a1de2, a084d8f, was green on nine of ten: Unit failed on protocol_capture_status_test.dart:123 (Expected: >= <3>, Actual: <0>) — a fixed event-loop budget rather than a product contract. The same job passed on 018dd90, git diff --name-only 018dd906..a084d8f touches nothing that test imports, and the file passes 5/5 locally. Filed as #198, fixed by #203, absorbed here as 92a1de2, rather than absorbed as a re-run.

Local cross-check on 4a865cd: bootstrap, format-check, lint (all nine projects, 0 issues), test-unit (all packages, app 497), test-integration, and scripts/check-cbio-vendor-material.sh (canary rejected, tree clean) exit 0.

Absorbed this cycle

Merge resolutions, all recorded in the commits: the app-live advance was a union on packages/cgm_cbio/README.md (the aggregate's FF31 reply-decode paragraph and the lane's injected-material section are independent) and took the lane's test file where it supersedes #148's masked-vector fixture. #182 forked before the redaction, so its session conflict re-introduced the compiled credential provider: the resolution keeps the aggregate's injected API and ports only the new reconnect logic. #186's write-verdict change needed CbioFrameWrite.unavailable where an auto-merged line returned bool. Both #185 harnesses took the branch's instrumented file with the aggregate's credential boundary re-applied by hand. Three #186 cases that constructed a session without material now take the suite's synthetic source.

Packaging and attribution: the root CHANGELOG.md entry for the GS1 lane and the cgm_cbio 0.1.0 entry did not mention this cycle's additions, so 4a865cd records the session-evidence model, the redacted evidence artifact with the harness verdicts, and the self-proving vendor-material guard.

Still excluded

Decisions

  1. No main-based re-cut in this cycle, and the candidate is behind main. main is six commits ahead of release/v0.4.0 (EN/zh localization feat(app): add English and Simplified Chinese localization #85, TestFlight delivery workflow ci: add protected TestFlight delivery workflow #66, iOS export share sheet fix(ios): restore archived export share sheet #80, v0.1.5/v0.1.6 prep chore(release): prepare v0.1.5 #81/Release OpenGlucose 0.1.6 #88, Android dependency bump chore(deps): bump com.android.application from 9.3.1 to 9.4.0 in /openhealth/android #105). A trial merge of main into this head conflicts in 12 files / 33 hunks, and openhealth/lib/main.dart alone holds 537 conflict lines, because feat(app): add English and Simplified Chinese localization #85 rewrote the same file the sensor lane rewrote. Re-cutting is a lane-level reconciliation with bilingual copy — its own focused task — not a merge to do under a release deadline. Residual exposure, stated plainly: this candidate is newer than main in identity (0.4.0+29) but not a superset of it — it lacks main's localization, its TestFlight workflow, and its iOS export fix. It must not be merged to main as the release until that reconciliation lands.
  2. The unit-claim removal is the owner's call. fix(cbio): drop the unverified mg/dL claim from the raw record #168 is out (above) because absorbing it changes what the live surface renders, not because it is unsafe.
  3. A gate must be deterministic to certify anything. The Unit flake was fixed by a focused PR (test(capture): wait for the status condition, not a fixed event-loop budget #203) rather than absorbed as a re-run, so the green table means the same thing on every run.
  4. Identity (chore(release): put the 0.4.0 identity ahead of main and describe the shipped cgm_cbio driver #170) and notice (docs(deps): record the cgm_cbio vendor-derived material review #165) already hold here. openhealth/pubspec.yaml is 0.4.0+29, cgm_cbio is 0.1.0 with a live-driver description, NOTICE.md carries the interoperability-material paragraph, and docs/dependencies.md records the vendor-material review — both halves of Release packaging metadata is stale: aggregate version behind main, cgm_cbio manifest and notices unreviewed #157 on this branch.

Queued

The main-reconciliation lane (#85 localization against the rewritten sensor surface); an app-live adoption of #168; #155/#156 follow-ups beyond #185/#189.

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.
FlutterBluePlusTransport.scan returned a stream that outlived the scan.
flutter_blue_plus stops the radio at its own timeout, but the transport's
cleanup awaited cancelResults, cancelScanning, awaitPendingStart, and
stopScan before closeController. On a stack that leaves any of those
pending, controller.close() never ran, so callers waited forever and
cancelling the subscription hung for the same reason.

- close the caller's stream first in closeFlutterBluePlusScanResources,
- bound each cleanup step so a pending plugin future cannot hold a caller,
- arm a scan deadline so a scan still closes when the plugin stops the
  radio without publishing isScanning = false,
- cover it with a regression test that drives cleanup with futures that
  never complete.

Measured before the fix on a Pixel 6: a 6 s window left isScanningNow
false at ~7 s while the transport stream was still open at 12 s, and a
25 s timeout on the stream fired, while the plugin's raw result stream
delivered 429 advertisements in the same period.

Fixes #143.
The Secret Scan gate fails on this lane with one gitleaks `generic-api-key`
finding in `packages/cgm_cbio/test/cbio_crypto_test.dart`. The value is a
published synthetic masking vector, but it is a 50-character high-entropy
string assigned to a name containing "auth", which is exactly the shape the
detector is built to catch.

The five masking vectors are now byte lists. The vectors and the assertions are
unchanged; `dart test` still passes 60 tests, and the same package tree now
reports no leaks to gitleaks 8.24.3.
`make format-check` fails on this lane in the Quality job: the new
`openhealth/integration_test/cbio_glucose_query_test.dart` was committed
unformatted, so the CI gate exits 2 before lint runs.

This is the formatter's own output, nothing else: `dart format` reports one
changed file and `make format-check` then exits 0 across every project.
`make test-integration` handed `test/integration/*` and `integration_test/*`
to one `flutter test` call, which Flutter rejects:

  Integration tests and unit tests cannot be run in a single invocation.

The two roots now run as separate lanes. The host lane keeps its behaviour
(`test/integration/*` plus tag-marked files, and `integration_test/*` for plain
Dart packages). The device lane runs `integration_test/*` in its own
invocation and only against hardware named by `FLUTTER_TEST_DEVICE_ID`;
otherwise it reports the deferred files and the knob that enables them, so a
host without a device stays green instead of failing closed on a lane it
cannot run.

Adds scripts/test-workspace-integration-runner.rb, which drives the real
script against a fixture repository with stubbed Flutter tooling and pins the
lane split, the deferral notice, the device-id plumbing, and the existing
nothing-found diagnostic. The check runs from `make tooling-check`.

(cherry picked from commit d8c9acb)
…e formatter output

Merges the two lanes the aggregate was missing (#142 app-live, #144 scan-close)
and cherry-picks the integration-runner gate fix (#141) instead of merging main,
so the aggregate keeps its own history.

Also carries `dart format` output for four files that the lanes committed
unformatted, so the aggregate's own Quality gate can run to completion.
The dependency review still described cgm_cbio as an offline scaffold with no
vendor material. That is no longer true: the package carries link material and
a stream masking constant derived from a shipped third-party sensor
application, i.e. exactly the shape that needs a notice and redistribution
review before an artifact ships.

Corrects the package section, states the two open reviews (notice and
redistribution terms, and how the material reaches a build at all), and adds
the matching paragraph to NOTICE.md. References the derivation record by path
instead of restating any bytes.
The vector table printed the authentication frame's plaintext hex, whose
bytes 9..24 are the 16-byte link credential, and the masked column was
derived from the real stream key. Both contradict the record's own stated
policy and belong to the material tracked in #164.

Keep the plaintext layouts, omit the credential field, and generate masked
bytes at run time from injected material instead.
The archive divided the raw 08 field by ten and then multiplied by 18.0182 to
produce an mg/dL number, and the test asserted that number as a fact. Nothing in
this repository establishes that /10 is mmol/L, and no reference measurement
exists for this sensor, so the accessor asserted a unit the protocol does not
carry. The app rendered that number as a glucose value.

Expose the raw field and its scaled value and nothing else: rawCurrentScaled is
documented as an unverified engineering value with no unit, the unit-bearing
accessors are gone, and a regression test fails if either name comes back.

Refs #145
Merging this lane into the sensor aggregate broke
openhealth/test/session_sync_failure_test.dart, the guard for the bounded
session-sync failure contract (#133/#134): the connection screen started
passing CgmSessionSnapshot.statusText straight into the progress card for every
cbio snapshot, so a session that never decoded rendered the driver's internal
string ('Listening for notifications' in the guard) instead of the stage label
'Syncing sensor history' the contract asserts.

That is a surface regression beyond the guard: statusText is unbounded and
internal, and the screen deliberately renders a closed set of user-facing
strings for every other driver.

Map the closed phase in cbioPhaseMetadataKey (connecting, authenticating,
history, live, disconnected) to product copy instead, and fall through to the
generic stage label when the phase is absent or the snapshot is not cbio.

The guard is untouched. New coverage: a presentation test for the mapping and a
widget test that drives the real connect button and asserts the bounded-sync
stage label is shown and the driver's text is not.

Refs #162
…m_cbio driver

The aggregates still declared 0.1.4+25 while main shipped 0.1.6+28, so an
assembled 0.4.0 artifact would carry a version and build number below the
released one. The release workflow also requires the tag to equal the pubspec
version, so the train now records 0.4.0+29 (next build after main's +28).

The cgm_cbio manifest and changelog still described an offline scaffold with no
transport, authentication, or glucose support. Both now describe the delivered
live driver, and the package version moves to 0.1.0 to match the other sensor
packages.
The path dependency records the package version, so the 0.1.0 bump has to reach
openhealth/pubspec.lock or make bootstrap fails under --enforce-lockfile.
origin/fix/cbio-progress-copy left openhealth/test/cbio_app_surface_test.dart
unformatted, so format-check would stop the Quality job before lint ran.
Formatter output only; the lane keeps its own formatting PR.
The sensor lane added the app's `integration_test` dev dependency and its
harness directory, so `pod install` now resolves an `integration_test` pod that
the committed lockfile never recorded. The iOS job fails closed on that drift:

  the iOS build changed ios/Podfile.lock; update and review the lockfile explicitly

This records the change the pinned toolchain produced on CI (Xcode 26.6,
CocoaPods 1.16.2): the PODS, DEPENDENCIES, EXTERNAL SOURCES and SPEC CHECKSUMS
entries for `integration_test` only. main is unaffected, its lockfile is
byte-identical to the pre-image.

No local Xcode or CocoaPods on this host, so the change is applied from the CI
diff and verified by blob hash: the result is ad15bd2, byte-identical to the
post-image the iOS job generated.
@shroominic
shroominic force-pushed the release/v0.4.0-lane-append branch from a2f9ce4 to 018dd90 Compare September 18, 2026 05:16
…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`.
…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.
@shroominic
Copy link
Copy Markdown
Owner Author

Head 018dd90 is green on every check, mergeStateStatus: CLEAN:

Check Result Evidence
Quality pass 3m5s run 35310179815 job 105490417861
Unit Tests pass 2m47s run 35310179815 job 105490417714
Integration Tests pass 1m23s run 35310179815 job 105490417841
Secret Scan pass 10s run 35310179799 job 105490417779
Android Build pass 6m44s run 35310179815 job 105490417836
iOS Build and Native Tests pass 8m16s run 35310179815 job 105490417773
macOS preview pass 11m4s run 35310179809 job 105490417700
Web Build pass 2m30s run 35310179815 job 105490417864
Dependency Review pass 7s run 35310179799 job 105490417900

This supersedes the a2f9ce4 matrix in the body; the only delta is #183 replacing #179.

Absorbs feature/cbio-gs1-app-live at 05785b0, which carries the redacted
credential API (#166 verbatim as a7c21bc) and the live lane's adoption of an
injected CbioCredentialSource (PR #192, issue #191).

Conflict resolution:
- packages/cgm_cbio/README.md: union. The aggregate's FF31 reply-decode
  paragraph (#132) and the lane's new injected-material section are both kept;
  neither lineage had the other.
- packages/cgm_cbio/test/cbio_crypto_test.dart: take the lane's version. It
  supersedes the masked-vector fixture from #148 by removing the published
  vectors entirely and testing the mask with synthetic material.
…edential API

Merges fix/bounded-automatic-reconnect (#182). The branch forked at 17db5b4, so
its session conflict re-introduced the compiled credential provider that #166
removed. Resolution keeps the aggregate's injected-credential API and ports
only the new logic:

- cbio_FailureAllowsAutomaticReconnect is added; the provider classes stay out.
- The session's reconnect-allowed field, metadata marker, and _fail gate apply
  unchanged.
- app_controller.dart and the persistence test are unions: the aggregate's
  sync-stage deadline tracking and the wedged-scan test both stay.

No credential, address, or vendor material is added by this merge.
Merges fix/cbio-failure-taxonomy (#186). Conflicts were both additive:

- session_presentation.dart: union. The aggregate's closed-phase progress copy
  and the new per-code failure sentences are independent additions.
- cbio_glucose_session.dart: take the aggregate's keyed builder call and keep
  the branch's write verdict. One auto-merged line returned bool from a
  CbioFrameWrite method; corrected to CbioFrameWrite.unavailable.
Merges fix/cbio-history-holes (#188). The one conflict was additive: the
aggregate's closed-failure sentence test and the branch's hole-caption tests
both insert at the same point, so both are kept.

No credential, address, or vendor material is added by this merge.
…lane

Merges fix/cbio-device-harness-assertions (#185). The branch forked at 4702025,
before the app-live lane's injected-credential adoption, so its harness files
call the pre-redaction API.

Resolutions:
- packages/cgm_cbio/lib/cgm_cbio.dart: union of both exports.
- scripts/flutter-workspace.sh: union. device_integration_files() keeps the
  aggregate's device lane; device_backed_harnesses() feeds the new test-e2e
  deferral report.
- openhealth/integration_test/cbio_glucose_query_test.dart: take the branch's
  file. It is a strict superset of the aggregate's (formatting only).
- openhealth/integration_test/cbio_glucose_authenticated_test.dart: take the
  branch's instrumented file and re-apply the aggregate's injected-credential
  boundary: _credentials from the environment/defines, a noted
  vendor_material_missing abort, and key:/material: arguments on every builder
  and unmask call.
The guard that keeps the vendor material out of the tree had never been
exercised, and as written it belonged in the wrong place.

- `security/gitleaks-vendor-material.toml` holds one tree-scoped rule that
  matches a material-shaped binding plus a literal payload. It restates no
  byte, no key-derived vector, and no device address, and it carries no
  allowlist, because an exemption would have to name the value it exempts.
- `scripts/check-cbio-vendor-material.sh` makes the rule prove itself: it scans
  the checked-out tree, then asserts the same rule still reports a deliberate
  synthetic canary. A rule that silently stops matching now fails instead of
  reporting a clean tree.
- The scanner is installed through the repository's existing checksum-pinned
  quality-tool installer, so local runs and CI use gitleaks 8.24.3, the version
  `gitleaks-action` resolves to.
- `make vendor-material-guard` runs it locally and is part of `make check`; the
  `Vendor Material Guard` job in `security.yml` runs it on every pull request.

The rule is deliberately not added to `.gitleaks.toml`: the material and a
key-derived masked vector are already in this repository's public history, so a
history-wide rule would fail forever and no change on the tip could clear it.

Fixes #172.
Two union resolutions joined two adjacent test blocks without the shared closing
brace, which the Dart parser rejects; both now close before the next test.

dart format also normalised three files the absorbed branches left unformatted
or that the resolution re-flowed: session_presentation.dart, the authenticated
harness, and the live-capture harness.
…andidate

Reverts the merge of fix/cbio-archive-unit-claim (#168).

#168 removes CbioRawGlucoseRecord.derivedMilligramsPerDecilitre and
.derivedMillimolesPerLitre and leaves only an explicitly unitless
rawCurrentScaled. The app-live surface still fills CgmReading.valueMgdl from the
removed mg/dL accessor, so the merge leaves six analyzer errors in cgm_cbio and,
fixing them, would change the number the live surface renders.

That is a unit-reference decision on the product surface, not a mechanical
merge, and it is the owner's to make. The lane needs an adoption change (as
#192 was for #166) before this can land. Recorded as excluded in the PR body.
Three cases added by #186 constructed a session with the default credential
source, which now fails closed with cbio.auth.material before the code under
test can run. They take the suite's synthetic source like their neighbours, so
they reach cbio.auth.rejected, cbio.auth.timeout, and cbio.write.failed again.
…budget

Closes #198.

The Unit gate failed on protocol_capture_status_test.dart:123 with
'Expected: a value greater than or equal to <3> / Actual: <0>' on head a084d8f,
while the same job passed on 018dd90 with the same test and the same app sources
for this path. The test fired its heartbeat timer twice and then counted statuses
after a fixed 40 event-loop turns; the status writer commits through real
asynchronous work, so a loaded runner can finish those turns before the writes
land.

_pumpUntil() pumps in small chunks until the condition holds, bounded by a
wall-clock timeout so a real hang still fails the following assertion with the
real evidence. The strictly-advancing assertions are unchanged.

Verification:
- flutter test test/protocol_capture_status_test.dart 3/3 pass
- flutter test test/ (483 tests) pass twice
- dart format --set-exit-if-changed lib test integration_test exit 0
…gelogs

The repository changelog requires package-level public API changes to be recorded
in the package changelog too, and neither file mentioned this cycle's additions.

- packages/cgm_cbio/CHANGELOG.md (0.1.0): CbioSessionEvidence,
  CbioSessionOutcome and CbioWriteKind, including the redaction boundary and the
  unverified unit status.
- CHANGELOG.md (Unreleased): the redacted GS1 evidence artifact and harness
  verdicts, and the self-proving vendor-material guard.

Docs only; no code or gate input changes.
@shroominic
Copy link
Copy Markdown
Owner Author

Release aggregate note: #163 remains open as the lane append and is not the final source of truth for CBIO GS1. The complete implementation and release workflow fix are in #209 at 7e58f86; re-cut the v0.4.0 aggregate after the implementation lands so the aggregate carries the exact main-rooted release commit.

@shroominic
shroominic merged commit c85f827 into release/v0.4.0 Sep 19, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

0