Skip to content

Read reservoir before bolusing - #2

Merged
loudnate merged 5 commits into
devfrom
read-reservoir
May 27, 2016
Merged

Read reservoir before bolusing#2
loudnate merged 5 commits into
devfrom
read-reservoir

Conversation

@loudnate

Copy link
Copy Markdown
Collaborator

Relates to #1.

@loudnate loudnate added this to the 0.2.0 milestone May 27, 2016
@loudnate
loudnate merged commit ad522c9 into dev May 27, 2016
@loudnate
loudnate deleted the read-reservoir branch May 27, 2016 03:46
awaxa pushed a commit to awaxa/Loop that referenced this pull request Jul 26, 2017
ps2 pushed a commit that referenced this pull request Dec 13, 2017
florianpreknya pushed a commit to florianpreknya/Loop that referenced this pull request May 31, 2019
dthornley pushed a commit to dthornley/Loop that referenced this pull request Oct 9, 2019
…5f4d96a2bbeef7

parent a4568c4
author Cal Young <cyoung1024@gmail.com> 1565904731 +0200
committer David Thornley <david.thornley@darnt.com> 1570653504 +0200

# This is a combination of 6 commits.

# This is the 1st commit message:

Add Spike Client and change version

# This is the commit message LoopKit#2:

Add Spike as CGM source

# This is the commit message LoopKit#3:

Add Spike frameworks

# This is the commit message LoopKit#4:

Add Spike to LSApplicationQueriesSchemes

# This is the commit message LoopKit#5:

Update Cartfile.resolved

# This is the commit message LoopKit#6:

Add Spike Calibration guide

# This is the commit message LoopKit#11:

Fixed delay between Spike update and Loop update
ardillen pushed a commit to ardillen/Loop that referenced this pull request Oct 21, 2020
jeremybarnum pushed a commit to jeremybarnum/Loop that referenced this pull request May 12, 2022
…n-issue-report

Show profile expiration in issue report
bjorkert pushed a commit to bjorkert/Loop that referenced this pull request Jun 10, 2023
threecee pushed a commit to threecee/Loop that referenced this pull request May 2, 2026
…LoopKit#2)

Until now the four HandoffPolicyEngine state hooks
(markCurrentOwner / markPhoneStableSince / markUserInteractedAt /
markCachedPodStateAge) were called only from tests. In production they
were dead code, which meant:

- phoneStableReachableSince was always nil, so the auto-revert path
  in (.watch, .automatic) and (.watch, .manualWithAutoRevert) was
  unreachable. Once the watch took over, only manual user action
  returned control.
- cachedPodStateAt was always nil, so the cached-pod-state safety gate
  treated any payload age as "fresh" — defeating the whole point of
  the freshness check.

This commit wires both sides:

- markCurrentOwner — called from execute(.notifyUI(state)) on every
  state transition + at start() with the initial state. Owner is
  derived via the new HandoffState.currentOwner extension (in OmniBLE).
- markPhoneStableSince — Combine subscription to coordinator's
  $isCounterpartReachable, with a 60s debounce (matches the engine's
  absenceThreshold) on flip-on, immediate clear on flip-off.
- markUserInteractedAt — called in userRequestHandoff(to:).
- markCachedPodStateAge (watch-side only) — called after
  ownership.cachePayload(decoded) in handleIncoming(.pairingHandoff).

BEHAVIOR CHANGE: auto-revert from watch back to phone WILL FIRE in
.automatic and .manualWithAutoRevert modes when the phone reconnects
after being out of range for >=60s. This is the original B.3.a design
intent; today it's dead code. Worth deliberate hardware testing.

Adds wiring tests on both sides using a real HandoffPolicyEngine
(not a mock) to verify the wiring actually propagates state. Adds
debug<X> internal accessors on HandoffPolicyEngine (currentOwner,
phoneStableReachableSince, lastUserInteractionAt, cachedPodStateAt)
for @testable inspection. Adds a test-only phoneStableDebounceOverride
init parameter on HandoffOrchestrator (defaults to 60s) so the
reachability-debounce test can run in <1s.

threecee-claude
threecee pushed a commit to threecee/Loop that referenced this pull request May 2, 2026
…wiring

Three nits from code review of 93a4ddf, all mechanical:

1. Renamed `phoneStableDebounceSeconds` static constant to
   `defaultPhoneStableDebounceSeconds` so it stops shadowing the
   instance let of the same name. Both names existed (compile-legal
   thanks to `Self.` qualifier) but readers had to look twice.

2. Renamed the 4 `debug<X>` test-observability accessors on
   HandoffPolicyEngine to `<X>ForTesting` (Apple convention). The
   `debug` prefix wrongly suggested they could be #if DEBUG-gated and
   stripped from release builds — they cannot. Both iOS and watch sides.

3. Added a 2-line comment in handleReachabilityChanged() explaining
   why the inner `if self.coordinator.isReachable` re-check inside
   `await MainActor.run` is necessary after the `Task.isCancelled`
   guard. It defends against the actor-hop ordering window where
   reachability flipped off during the sleep but the off-callback
   hasn't been processed yet. Without the comment a future reader
   could "fix" the apparent redundancy and break the safety property.

threecee-claude
threecee pushed a commit to threecee/Loop that referenced this pull request May 2, 2026
Four fixes from code review of 38102e1:

CRITICAL — Add gate 5: pumpManager.status.deliveryIsUncertain. iOS
DeviceDataManager.swift:1416 has this guard to prevent firing a dose on
top of an in-flight unconfirmed dose (e.g., last command's ack was lost
or BLE reconnect happened mid-bolus). The watch driver was missing it,
which meant we could double-dose under transient pump comms failures.

The new gate is the ONE suppression path that does NOT call
completion(nil). delivery-uncertain is transient; we WANT the algorithm
to retry next tick when pump state may have settled. Returns
LoopError.connectionError so LoopAlgorithmRunner does NOT clear its
cached recommendation — it'll come back next tick. Function header
comment updated to acknowledge gate 5's exception to the
"completion(nil) is load-bearing" rule.

IMPORTANT — RecordingPumpManager: change static var → static let on
constants (pluginIdentifier + onboarding* properties). Mutation across
tests would silently couple them. With static let, the compiler enforces
immutability.

IMPORTANT — makeSuppressedDecision now sets decision.controllerStatus
from the controllerStatusAdapter (held as new instance property). Per
Phase 1 discovery point LoopKit#4: suppression decisions should have the same
battery / charging context as non-suppressed ones (analytic parity).

IMPORTANT — TODO comment in enactRecommendedDose pointing at iOS's
crashRecoveryManager.dosingStarted/Finished bracketing
(DeviceDataManager.swift:1423). Watch has no equivalent; partial
mitigation via OmniBLEPumpManager's own dose history. Future B.7 phase
should add a proper crash-recovery layer.

7th unit test added: testDidRecommend_deliveryIsUncertain_returnsError-
AndDoesNotEnact. Verifies the new gate returns a retryable error
(non-nil) AND doesn't record a suppressed decision (the retry semantic
is "try again next tick" not "this dose is suppressed").

threecee-claude
threecee pushed a commit to threecee/Loop that referenced this pull request May 2, 2026
Two trivial follow-ups from B.5 code reviews:

- WatchSettingsCache.resetForTesting() now wrapped in #if DEBUG.
  Previously the test-only helper was unconditionally available; the
  doc comment said "Production never calls this" but didn't enforce
  it. Compile-time gate prevents accidental production calls.

- Em-dash -> semicolon in watch split-brain log message. Aligns with
  project writing style guidance avoiding em-dashes in log strings.

Audit of B.5.1 LoopKit#2 (do synced ISF/CR/target-range/maxBolus/
suspendThreshold flow into watch algorithm decisions?) confirmed YES —
WatchAlgorithmDriver.init at lines 89-122 maps every PhoneWatchSettingsSync
field into settingsSnapshot.loopSettings. No code change needed.

Two larger B.5.1 items deferred to B.5.2:
- Add timeZone to PhoneWatchSettingsSync (protocol bump 3 -> 4 + both
  sides plumbing + watch BasalRateSchedule construction adopts phone's
  zone instead of TimeZone.current).
- Widen commandsAllowedCheck gate to runTemporaryBasalProgram /
  cancelBolus / suspendDelivery (each needs a semantically appropriate
  "blocked" completion response).

threecee-claude
threecee pushed a commit to threecee/Loop that referenced this pull request May 4, 2026
…ngle source

Closes the two B.7 follow-ups deferred from the 2026-05-02 algorithm-
fidelity audit (see ~/Projects/loop-and-learn/.claude/skills/algorithm-
fidelity-audit/references/known-deviations.md "Code smells" section).

Issue LoopKit#1 — enabledEffects duplication:
- Adds canonical `static let allEnabled: PredictionInputEffect = .all`
  in LoopAlgorithmCore/PredictionInputEffect.swift.
- LoopSettings.enabledEffects (iOS, Loop/Models/LoopSettings+Loop.swift)
  now delegates: returns `PredictionInputEffect.allEnabled` when
  `LoopConstants.retrospectiveCorrectionEnabled` is true (today: always),
  else `.allEnabled.subtracting(.retrospection)`.
- LoopSettings.loopAlgorithmCore_enabledEffects (LoopAlgorithmCore,
  LoopAlgorithmRunner.swift) now returns `PredictionInputEffect.allEnabled`.

Issue LoopKit#2 — PredictionInputEffect.swift deduplication:
- Removed Loop/Models/PredictionInputEffect.swift (was on disk, but
  Phase 1 discovery confirmed it was already a true orphan with no
  pbxproj entry — the dual-target wiring described in the
  known-deviations note had been silently fixed previously, both
  PBXBuildFile entries already point at LoopAlgorithmCore/
  PredictionInputEffect.swift).
- LoopAlgorithmCore/PredictionInputEffect.swift remains the canonical
  source, wired into both LoopAlgorithmCore + LoopAlgorithmCore-watchOS
  framework targets.
- iOS callers (LoopDataManager, PredictionTableViewController,
  LoopSettings+Loop, BolusEntryViewModelTests) all already
  `import LoopAlgorithmCore`; no callsite changes needed.

Tests:
- New LoopTests/PredictionInputEffectTests.swift with 3 invariant
  tests: `allEnabled == .all`, iOS `enabledEffects == allEnabled`,
  LoopAlgorithmCore `loopAlgorithmCore_enabledEffects == allEnabled`.
- Wired into LoopTests target via xcodeproj gem (B85 UUID prefix).
- Full LoopTests run: 303 tests, 1 skipped, 0 failures.
- Full WatchApp ExtensionTests run: 121 tests, 0 failures.

Phase 1 finding: pbxproj surgery was NOT needed (the prior orphan
wiring described in known-deviations was already corrected). Phase 3
ruby script ran as a verification gate only — no pbxproj edits beyond
the new test file's wiring.

threecee-claude
jeremybarnum pushed a commit to jeremybarnum/Loop that referenced this pull request Jul 10, 2026
…ts on temp-over-temp)

Changing an active temp from the watch faulted bench pod LoopKit#2 terminally
(0x31 -> critical fault 049): PodProofKit.setTempBasal programmed a temp
while one was running. Stock OmniBLE always safe-cancels a possibly-running
temp first (enactTempBasal, OmniBLEPumpManager.swift:2104-2136) and guards
against temping during a bolus or while suspended; the facade skipped all
three because the EMULATOR accepts overlapping temps.

setTempBasal now mirrors the stock idiom: cancelDelivery(.tempBasal) first
(plumbing, not journaled; the new .tempBasal event closes the prior segment
in the net-basal math), then the bolusing/suspended guards, then the program.

Full incident + pod LoopKit#3 test matrix in WATCH_LOAN_TESTING_BUGS.md BUG-6.

Build-verified: PodSDK + full LoopWorkspace scheme.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
jeremybarnum pushed a commit to jeremybarnum/Loop that referenced this pull request Jul 10, 2026
…p basal input guards

Three independent code audits of the remaining command paths for the 0x31
class that killed bench pod LoopKit#2 (suspend / resume / zero-rate temp). Verdicts:
suspend is structurally safe (0x1f stop, not a program; stock suspends over
temps and mid-bolus); resume is safe on every watch-reachable path because
the vendored setBasalSchedule already cancel-alls unless certain the pod is
suspended; dialing 0 routes to suspend() and never reaches the temp path.

Hardening the two residuals found:
- resume() now does a fresh getStatus before resumeBasal so the driver's
  cancel-skip decision rests on live pod state, not local mirrors (closes
  the dual-controller stale-suspend hole; belt-and-suspenders on top of the
  formal handoff's single-writer contract).
- setTempBasal snaps rates to the 0.05 pulse grid (negatives clamp to a
  legal zero temp instead of trapping in UInt16 encoding) and refuses
  durations outside Pod.supportedTempBasalDurations — off-grid inputs made
  the 0x1a table and 0x16 extra command encode inconsistently, a pair stock
  can never emit and the emulator would silently accept. Not UI-reachable
  today; closed as pure defense-in-depth.

Full audit written up under BUG-6 in WATCH_LOAN_TESTING_BUGS.md.
7 new guard unit tests; PodSDK suite 150/150. Workspace scheme build green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
jeremybarnum pushed a commit to jeremybarnum/Loop that referenced this pull request Jul 16, 2026
…asal — maxTempBasalRate computed from settings, pod proof limit set to match before each temp (no more TEMP-TEST-CAP 3.0, no revert chore, no display/enact mismatch). (LoopKit#4) IOB clamp now EXACT phone parity: min(rate, (maxBolus*2 - IOB)*2 + scheduledBasal) floored at 0, matching DoseMath additionalActiveInsulinClamp — replaces the cruder hold-at-schedule
jeremybarnum pushed a commit to jeremybarnum/Loop that referenced this pull request Jul 17, 2026
manualSuspendUntil was mutated optimistically before the command was even
sent: a busy/phase-guard drop (fileLog only) or a definitive pod refusal
left the HUD showing Suspended and the closed loop blocked for up to 3 h
while the pod kept delivering (review C4); mirror image on Resume/Set Basal
(cleared upfront, showing delivery running while the pod stayed at zero).

runPodCommand now reports acceptance and takes an onCertainFailure rollback:
manual suspend/resume/setBasalRate capture prior state, apply the new state
only when the command is accepted (optimistic in flight, matching the dose
screens), and restore it on a definitive refusal. UNCERTAIN outcomes keep
the rider's intent - the existing may-have-applied alert fires. Busy-drops
of MANUAL commands are now loud (retry haptic + "Not Sent - pod is busy"
alert) instead of a silent log line; this also makes the crown-confirmed
meal-bolus drop visible (part of high finding on runPodCommand's silent
guard). Loop-issued enacts/status refreshes keep quiet drops (they retry on
their own cadence).

Approved by Jeremy (fix batch LoopKit#2). watchOS build verified.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
jeremybarnum pushed a commit to jeremybarnum/Loop that referenced this pull request Jul 17, 2026
…niature

New WatchApp Extension/StockLoop/ (app code, no PODLOAN needed):

- WatchLoopManager.swift (801 lines): mirrors the phone's stock policy paths
  method-for-method, cited inline — loop()/update(for:) effect refresh over
  the same GlucoseStore/CarbStore/DoseStore entry points, recency gating via
  LoopCoreConstants.inputDataRecencyInterval/futureGlucoseDataInterval,
  retrospective correction, LoopMath.predictGlucose, and recommendation via
  DoseMath.recommendedTempBasal with additionalActiveInsulinClamp
  (= maxBolus×2 − IOB) passed INSIDE the call — never post-hoc. Manual bolus
  via the recency-validated path, no fabricated glucose placeholder.
  WatchDoseEnactor mirrors DoseEnactor over stock PumpManager methods.
  The enact seam is typed against the stock PumpManager protocol (the M2
  OmniPumpManager) but UNCONNECTED: pumpManager stays nil, and a nil seam
  yields an explicit error, never a silent success. Missing settings deny
  dosing — no fabricated defaults. Four TODO(M5-ruling) markers where the
  work ends at owner-reserved dosing-semantics decisions (LoopKit#1, LoopKit#2, LoopKit#4).

- StockLoopStack.swift (125 lines): the single assembly point — constructs
  stores + the M3 CGM stack and wires G7CGMManager.cgmManagerDelegate →
  WatchLoopManager → GlucoseStore → loop(). UNINVOKED in M4: construction +
  compile proof, no behavior change to the stock watch app.

Scaffolding retired (purpose absorbed):
- StoreBringup.swift (M1) deleted — store construction lives verbatim in
  StockLoopStack.makeStores(), caveats carried along.
- G7TransportBringup (M3) deleted from G7ClientTransportAdapter.swift —
  assemble() builds the same stack and adds the delegate wiring.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
jeremybarnum pushed a commit to jeremybarnum/Loop that referenced this pull request Jul 18, 2026
…tingsManager exists (stock bug)

The build-77 pre-unlock crash (symbolicated: AppDelegate.didRegister... →
LoopAppManager.remoteNotificationRegistrationDidFinish, SIGTRAP) is the SIBLING
of the resetLoopManager crash (e4e347f): an async launch callback touching an
IUO manager that only exists after the protected-data gate.

registerForRemoteNotifications() ran in initialize() (pre-unlock). On a
pre-first-unlock launch, checkProtectedDataAvailable() defers the launch so
launchManagers() — which assigns `settingsManager` (SettingsManager!) — never
runs; the async push-token callback then force-unwraps nil settingsManager at
LoopAppManager.swift:340. (Line 326 already optional-chains settingsManager? —
340 was the missed sibling.) Reliably hit because the always-on watch app
relaunches Loop pre-unlock at every boot.

The crashing code is pure STOCK (verified byte-identical to origin/dev:
settingsManager! decl, the registration block, line 340). We did not author it;
our watch project's pre-unlock relaunch surfaces a latent base-Loop bug — same
class as e4e347f. Fix: move the registration block into launchManagers() after
settingsManager is wired, so the token callback always has a live
settingsManager (remote commands can't be serviced pre-unlock anyway).
Upstream-candidate stock fix, NOT a watch-feature change; commented as such.

Closes the boot-crash saga: it was TWO stacked pre-unlock IUO crashes on
different async paths (resetLoopManager + settingsManager), not one.

Diagnosis confirmed by symbolication (build-77 dSYM UUID match). iOS build
verified. Approved by Jeremy.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
jeremybarnum pushed a commit to jeremybarnum/Loop that referenced this pull request Jul 18, 2026
…er (upstream candidates)

Building the watch feature surfaced two latent base-Loop launch crashes — the
code is byte-identical to upstream/dev; our always-on watch app just relaunches
Loop pre-first-unlock on every boot, exposing them. Both are the same shape: an
async launch callback force-unwraps an IUO manager that launchManagers() (which
a pre-unlock launch defers) hasn't assigned.

LoopKit#1 resetLoopManager: askUserToConfirmLoopReset() optional-chained.
LoopKit#2 settingsManager: registerForRemoteNotifications() moved from initialize() to
   launchManagers() so the push-token callback has a live settingsManager.

Marked STOCK LAUNCH-CRASH FIX in-code (NOT PODLOAN — not part of the watch
module). Upstream candidates; Jeremy submits the PRs separately. Documented in
M4_NOTES. Full-workspace build verified.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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