Fallback And Issued-Dose Reconciliation Design Refactor Plan
Last updated: 2026-06-20
Status: planning / safety design review; not formal freeze evidence
Implementation note, 2026-06-20:
- Initial shadow-only classifier added in
BionicLoopCore/Sources/BionicLoopCore/Domain/ReconciliationCaseClassifier.swift. - Decision/result types added in
BionicLoopCore/Sources/BionicLoopCore/Domain/ReconciliationDecision.swift. - Support helpers added in
BionicLoopCore/Sources/BionicLoopCore/Domain/ReconciliationCaseClassifierSupport.swift. - Executable 43-row matrix coverage added in
BionicLoopCore/Tests/BionicLoopCoreTests/ReconciliationCaseClassifierDecisionMatrixTests.swift. - Runtime execution is not routed through the classifier yet, so this pass is a regression net and review surface, not a behavior change.
Purpose
Fallback basal recovery and unresolved issued-dose reconciliation are now one combined safety problem. The current implementation has most of the needed rules, but those rules are distributed across runtime coordination, app runtime reconnect handling, pure fallback reconciler code, telemetry publication, and Home UI state projection. That makes it hard for engineering, clinical, and quality reviewers to answer a simple question: "what will the system do in this recovery case, and why?"
This plan documents the current shape and compares future refactor options. The recommended path is to centralize reconciliation decisions first, with minimal behavior change, so the current safety behavior becomes reviewable before any larger rewrite.
Related references:
Docs/Planning/ReconciliationClinicalReviewMatrix.mdDocs/Planning/ReconciliationSafetyWalkthrough.mdDocs/Planning/IssuedDoseDisconnectSequenceDiagrams.mdDocs/Planning/MealAnnouncementDisconnectRecoveryPlan.mdDocs/Planning/DashOfflineBasalFallbackFeasibilityReview.mdDocs/Planning/LoopRuntimeCoordinatorDecompositionPlan.mdDocs/Quality/SoftwareRequirementsSpecification.mdDocs/Quality/RiskAnalysis.mdDocs/Quality/TraceabilityMatrix.md
Traceability Scope
This document does not create a new formal requirement. It is a planning artifact for improving the implementation surface for existing safety claims.
Impacted requirement/risk/verification areas:
| Area | IDs |
|---|---|
| Requirements | SRS-PUMP-009, SRS-PUMP-010, SRS-MEAL-008, SRS-MEAL-009, SRS-MEAL-010, SRS-MEAL-012, SRS-STATE-004, SRS-STATE-005, SRS-LOG-001, SRS-LOG-007 |
| Risk analysis | RA-003, RA-006, RA-016, RA-017 |
| Design | SDD-POL-023, SDD-POL-024, SDD-POL-028, SDD-POL-029, SDD-DATA-003, SDD-LOG-001 |
| Verification | TV-PUMP-009, TV-MEAL-013, TV-STATE-004, TV-STATE-005, TV-LOG-001, TV-LOG-007 |
Current Design Snapshot
Current implementation surfaces:
| Layer | Current responsibility |
|---|---|
LoopRuntimeStateStore |
Persists pending issued-dose attribution, issued-dose reconciliation evidence/failure/hold, masked fallback state, fallback recovery state, and pending fallback replay plan. |
LoopRuntimeCoordinator |
Orders issued-dose replay, fallback replay, live-step execution, pump input shaping, active-delivery skips, unresolved-delivery skips, assumed-delivered consumption, and pending-state clearing. |
LoopRuntimeEnginePureSupport |
Resolves masked fallback reconnect/restore events, evaluates pump-total reconciliation, creates fallback replay plans, and stores partitioned issued-dose evidence from fallback pump totals. |
FallbackBasalExposureReconciler |
Pure same-pod pump-total reconciliation, issued-dose partitioning, residual fallback delivery calculation, and modeled-vs-actual credibility classification. |
FallbackBasalReplayPlanner |
Pure allocation of fallback delivered units across missed fallback-active step windows, weighted by the persisted programmed fallback schedule. |
LoopRuntimeEngineMealAnnouncementSupport |
Reconciles meal-linked issued-dose evidence on status refresh, unavailable-pod cases, canceled meals, and user-confirmed pod replacement escape. |
| Home UI helpers | Project pending meal progress, replacement escape affordance, recent-dose rows, chart suppression, and user-facing availability/blocking text. |
| Telemetry stores | Emit replay rows, fallback events, request-step metadata, delivered/requested units, evidence source, disposition, and failure reason. |
The current implementation has the right primitives:
PendingIssuedDoseAttributionIssuedDoseReconciliationEvidenceIssuedDoseReconciliationFailureIssuedDoseReconciliationDispositionFallbackBasalExposureReconciliationFallbackBasalReplayPlanFallbackBasalPodContinuity- local/cloud replay telemetry fields
The current issue is not that these concepts are absent. The issue is that the case classification is implicit. Reviewers must mentally combine multiple files to answer whether a case will be confirmed, assumed, blocked, replayed, merged into fallback replay, consumed live, or shown as a UI escape.
Safety Invariants To Preserve
These should be treated as non-negotiable during any refactor:
- Algorithm start, stop, reset, and replacement session creation occur only by explicit user action.
- Replay rows never issue catch-up pump commands.
- Historical CGM is not required for replay; replay may use
CGM=-1. - Primary and secondary/safety algorithms receive the same reconciled delivered insulin accounting.
- An issued dose is attributed to the first eligible attribution step unless it must be merged into a bounded fallback replay row by an explicit decision.
- A later live step must not aggregate old delivered insulin unless that live step is the intended attribution step under the decision rule.
- Credible same-pod evidence may confirm actual delivery.
- Missing, mismatched, non-credible, or ambiguous same-pod evidence must never
be presented as pump-confirmed delivery. If current clinical policy assumes
delivery for an idle/suspended unresolved pump state, it must be labeled as
assumed_delivered_per_clinical_policy. - Different/new/no-active/retired/service-stopped old-pod paths may use
clinically approved
assumed_delivered_per_clinical_policyevidence, but the telemetry and UI must not present it as pump-confirmed evidence. - Replacement-pod delivery evidence cannot prove old-pod fallback basal delivery.
- Fallback pump-counter replay is limited to credible same-pod pump-total evidence, except the explicit retired/no-active/service-stop modeled fallback assumption path.
- Issued-dose insulin and fallback basal in one pump-total delta must be partitioned before fallback residual replay.
- Meal progress UI must not strand the user or block pod replacement when old pod evidence is unrecoverable.
- Formal evidence must distinguish local replay rows from cloud/Scout replay visibility.
Current Flow Map
flowchart TD
A["Wake, reconnect, foreground, or meal submit"] --> B["Load persisted runtime state"]
B --> C["Refresh pump status, restoring pending issued-dose context when possible"]
C --> D{"Pending issued-dose attribution?"}
D -->|No| J{"Pending fallback recovery or replay?"}
D -->|Yes| E{"Same pod still delivering?"}
E -->|Yes| F["Skip live step; keep attribution pending"]
E -->|No| G{"Matching evidence or runtime-supported assumed-delivered policy available?"}
G -->|No| H["Skip standalone live advancement while unresolved or not yet eligible"]
G -->|Yes| I["Prepare attribution for live input, standalone replay, or fallback-merge"]
I --> J
J -->|No| N["Run live due step with shaped pump input"]
J -->|Yes| K{"Fallback replay plan executable for current due step?"}
K -->|No| L["Clear invalid/stale plan or resume without fallback replay"]
K -->|Yes| M["Replay bounded fallback plan rows; merge issued-dose evidence if decision says same row"]
L --> N
M --> N
N --> O["Persist state, step history, telemetry, and UI projection inputs"]
This ordering is safety-critical, but currently the decision points in the
diagram are not represented by one named object or one test matrix. They are
spread across helpers such as pendingIssuedDoseReconciliationBlocksLiveAdvancement,
replayPendingIssuedDoseAttributionIfNeeded,
performPendingFallbackReplayIfNeeded,
resolvedIssuedDoseDelivery,
assumedDeliveredIssuedDoseIfDifferentPod,
assumedDeliveredIssuedDoseIfUnresolved,
reconciledMealIssuedDoseEvidenceState, and fallback recovery planning.
Critical Current Cases
| Case | Current intended behavior | Review concern |
|---|---|---|
| Healthy meal announce completes before next step | Store pending attribution, then consume confirmed or assumed evidence on first eligible live step. | UI and runtime closure are separate; progress may close before attribution is consumed. That is valid but needs clear projection. |
| Meal/correction issued, app disconnects, same pod returns with last-delivery evidence | Replay missed attribution step(s) before live resume; inject delivered insulin only on first attribution step. | Correct, but replay/live/clear behavior is encoded in coordinator control flow rather than a case decision. |
| Same pod still delivering at next step | Skip live advancement and keep pending attribution. | Alert timing and UI language still need clinical/UX disposition. |
| Issued dose plus fallback basal in same pump-total delta | Partition issued dose first when credible; replay only residual fallback delivery over fallback-active slots. | Good pure reconciler exists, but coordinator merge behavior is hard to reason about from outside the code. |
| Issued dose predates fallback-active replay interval | Merge resolved issued-dose delivery into the first fallback replay row when the chosen rule says no separate pre-fallback replay row is needed. | Needs a single decision-table row and test names that state this exact rule. |
| Fallback-active exposure only, same pod credible pump delta | Replay exactly bounded fallback plan deliveries < currentDueStep; no catch-up pump commands. |
Planner is pure and testable; execution trace should be tied to one decision output. |
| Fallback recovery with different/new/unknown pod | Suppress old-pod pump-counter fallback replay; may resume unreconciled or use explicit assumed fallback only under no-active/retired/service-stop policy. | UI must make assumed-vs-confirmed obvious. |
| Old pod unavailable/no-active/service-stopped with pending meal dose | Store assumed-delivered evidence, clear only meal UI fields, keep attribution for replay/live consumption. | This is clinically pragmatic but easy to confuse with pump-confirmed delivery. |
| Canceled meal partial delivery | Store pump-last-delivery evidence with normalized request step; consume on the next eligible input, including another meal submit when allowed. | Prior bugs show this path needs a first-class decision row so request-step 0 normalization cannot leak. |
| Force-quit during meal delivery | Pending attribution must persist as soon as the pump command starts, before pump response. | This is implemented by command-start persistence, but should be covered by a named decision test and UI projection test. |
Human Factors And Review Pain Points
- The code uses good domain terms, but similar terms live at different layers:
pending,uncertain,ambiguous,assumed,different/new,unresolved,confirmed,corrected,reconciled,restore failed, andresume without reconciliation. - UI state is derived from runtime fragments instead of one reconciled projection. A modal can be technically correct and still hard to explain.
- Some behaviors are encoded as guard ordering rather than named decisions. That makes regressions likely when fixing edge cases.
- Fallback residual partitioning is pure and understandable, but the handoff from fallback reconciliation into issued-dose replay/live consumption is not a single explicit contract.
- Tests are broad but historically organized around bugs. That is useful for regression, but not sufficient as a reviewer-facing decision table.
- Telemetry and Recent Dose Steps must use identical vocabulary. If local UI says "assumed" and cloud says "replayed" without the same source/disposition fields, review becomes ambiguous.
- The current single-pending-attribution invariant is probably correct for safe operation, but it should be documented as a runtime invariant and enforced at the decision boundary.
Future Design Options
Option A: Keep Current Design, Add Documentation And More Tests
Description:
- Leave code structure mostly unchanged.
- Expand
ReconciliationSafetyWalkthrough.mdand sequence diagrams. - Add more coordinator and UI tests for each observed bug class.
Benefits:
- Lowest implementation risk.
- No churn in safety-critical runtime sequencing.
- Fastest path if hardware behavior is currently acceptable.
Costs:
- Human review remains difficult because the code still lacks a single decision surface.
- Future bug fixes will continue to patch guard ordering.
- Clinical/quality reviewers still need an engineer to translate code paths.
Verdict:
- Useful as a stopgap, but insufficient for a stable IDE baseline if this area keeps changing.
Option B: Extract A Pure Reconciliation Case Classifier
Description:
- Add a pure domain module that consumes a normalized
ReconciliationSnapshotand emits a singleReconciliationDecision. - Keep existing executor functions initially.
- Use the decision to drive coordinator ordering, meal UI projection, telemetry labels, and test fixtures.
Proposed types:
struct ReconciliationSnapshot {
var now: Date
var currentDueStep: UInt32
var lastExecutedStep: UInt32?
var firstSuccessfulStepAt: Date?
var pumpStatus: PumpStatus
var pendingIssuedDose: PendingIssuedDoseAttribution?
var issuedDoseEvidence: IssuedDoseReconciliationEvidence?
var fallbackRecovery: MaskedFallbackRecoveryState?
var fallbackReplayPlan: FallbackBasalReplayPlan?
var fallbackEvent: FallbackBasalEvent?
var hasActivePod: Bool?
var podServiceStopAt: Date?
}
enum ReconciliationDecision {
case runLiveStep(LiveInputPolicy)
case skip(SkipPolicy)
case replayIssuedDose(IssuedDoseReplayPolicy)
case replayFallback(FallbackReplayPolicy)
case replayFallbackWithMergedIssuedDose(FallbackReplayPolicy, IssuedDoseMergePolicy)
case assumeIssuedDoseDelivered(AssumptionPolicy)
case assumeFallbackDelivered(FallbackAssumptionPolicy)
case clearMealProgressOnly(MealProgressResolution)
}
The actual implementation can split these into smaller structs; the key is that the classifier returns one named outcome with a reason code.
Benefits:
- Gives humans one place to review the safety rule.
- Enables a compact decision-table test suite independent of async pump calls.
- Preserves current execution code while making behavior explainable.
- Allows UI and telemetry to use the same source/disposition/reason vocabulary.
Costs:
- Requires careful adapter work from current state into the snapshot.
- Requires dual-running or characterization to prove no behavior change.
- Does not by itself simplify all existing runtime code until Phase 2.
Verdict:
- Recommended near-term path. It is the best balance of reviewability, test value, and behavior-change risk.
Option C: Event-Sourced Reconciliation Ledger
Description:
- Replace scattered pending fields with an append-only ledger: issued command started, command result, pump status observed, fallback armed, mask expired, restore attempted, pod identity changed, evidence recorded, decision made, attribution consumed.
- Derive current pending state and UI projection from ledger events.
Benefits:
- Strongest audit trail.
- Natural fit for regulatory evidence and cloud replay.
- Makes force-quit/relaunch reconstruction explicit.
Costs:
- Largest implementation change.
- Requires migration from existing persisted runtime fields.
- Higher risk this close to hardware testing and IDE baseline work.
Verdict:
- Good long-term direction, but too much surgery for the immediate baseline unless current design becomes unmanageable.
Option D: Formal State Machine Table
Description:
- Model issued-dose and fallback recovery as explicit state machines with transition tables.
- Runtime calls transition functions, then executes side effects.
Benefits:
- Easy to diagram.
- Good for proving legal/illegal transitions.
- Could generate reviewer-facing tables and test fixtures.
Costs:
- The combined state space is large because issued-dose, fallback, pod continuity, UI, and telemetry states overlap.
- Risk of creating a rigid abstraction that still needs escape hatches.
Verdict:
- Useful for documentation and tests, but the implementation should start with Option B's classifier before committing to a full state-machine framework.
Option Comparison
| Option | Implementation risk | Review clarity | Test leverage | Runtime churn | Recommended use |
|---|---|---|---|---|---|
| A. More docs/tests only | Low | Medium | Medium | Low | Immediate stabilization only |
| B. Pure classifier | Medium | High | High | Medium | Recommended next refactor |
| C. Event ledger | High | Very high | Very high | High | Future architecture if needed |
| D. Formal state machine | Medium-high | High | High | Medium-high | Later if classifier still feels too implicit |
Recommended Architecture
flowchart LR
State["Persisted runtime state"] --> Adapter["ReconciliationSnapshot adapter"]
Pump["Current pump/status evidence"] --> Adapter
CGM["Current cadence/CGM context"] --> Adapter
Adapter --> Classifier["Pure ReconciliationCaseClassifier"]
Classifier --> Decision["ReconciliationDecision + reason code"]
Decision --> Executor["Coordinator executor"]
Decision --> Fallback["Fallback replay executor"]
Decision --> MealUI["Meal progress UI projection"]
Decision --> RecentDose["Recent Dose Steps projection"]
Decision --> Telemetry["Cloud/local telemetry fields"]
Decision --> Tests["Decision-table tests"]
Core rule: async device calls and state mutation stay outside the classifier. The classifier gets a complete snapshot and says what should happen. Executors then perform the existing replay/live/clear/persist behavior.
Proposed Phase Plan
Phase 0: Freeze The Current Decision Matrix
Deliverables:
- Add a compact machine-readable or table-backed decision matrix for current behavior.
- Map each row to an existing or new test.
- Mark rows as
current,desired, orclinical-disposition-needed.
Candidate matrix fields:
| Field | Meaning |
|---|---|
pendingIssuedDose |
none, meal, correction, mixed, basal |
issuedDoseEvidence |
none, pump-last-delivery, partitioned-pump-delta, assumed-policy |
podContinuity |
same, different/new, unknown, no-active, service-stopped |
pumpDeliveryState |
idle, suspended, delivering, unknown |
fallbackState |
none, armed-mask-active, recovery-required, replay-plan-pending |
currentDueRelation |
before attribution, at attribution, after attribution, at fallback replay |
decision |
live, skip, replay issued dose, replay fallback, merge, assume, clear UI |
evidenceLabel |
confirmed, corrected, assumed-policy, ambiguous, no-replay |
Exit criteria:
- Every hardware-observed flow has exactly one row.
- Every row has a stable reason code.
- Unknown rows fail closed in tests.
Phase 1: Add Pure Classifier Without Routing Runtime Through It
Deliverables:
- Add pure domain types under
BionicLoopCore/Sources/BionicLoopCore/Domain/Reconciliation/. - Add
ReconciliationCaseClassifier.classify(_:). - Add tests that compare classifier decisions to current expected behavior.
- Do not change runtime execution yet.
Status:
- Initial implementation complete as a shadow-only classifier in
BionicLoopCore/Sources/BionicLoopCore/Domain/. - The first executable matrix covers 43 critical rows spanning issued-dose gates, same-pod confirmation, assumed-policy delivery, fallback replay, issued-dose/fallback merge, stale/invalid replay plans, pod-continuity suppression, recovery-required/no-plan blocking, partial/canceled delivery evidence, non-credible evidence boundaries, persisted assumed-delivery reason preservation, runtime-parity merge limits, replayable fallback status gating, and combined issued-dose/fallback precedence.
- Remaining Phase 1 work before coordinator routing is parity review against the existing coordinator tests and any additional hardware-observed cases.
Suggested files:
ReconciliationSnapshot.swiftReconciliationDecision.swiftReconciliationCaseClassifier.swiftReconciliationDecisionReason.swiftReconciliationCaseClassifierTests.swift
Initial decisions to cover:
- no pending issued dose, no fallback recovery -> live step allowed
- pending dose before first attribution -> delivery-in-progress wins; otherwise wait without standalone live advancement unless bounded fallback replay has mergeable issued-dose evidence
- same pod delivering -> skip
issuedDoseDeliveryInProgress - same pod pump-last-delivery confirmed -> consume live or replay
- same pod evidence mismatch -> not confirmed; if the same-pod idle/suspended assumption policy remains approved, classify as assumed-policy rather than confirmed evidence
- idle/suspended unresolved same pod -> explicit policy-assumed branch, if this remains intended
- different/new pod -> assume old issued dose, scrub replacement delivery
- no-active/service-stopped meal -> assume old issued dose and clear meal progress only
- fallback same-pod credible residual -> replay fallback plan
- fallback different/new/unknown -> no pump-counter fallback replay
- fallback no-active/service-stopped -> modeled assumed fallback replay
- issued dose plus fallback plan -> merge or sequence according to first fallback replay row
Exit criteria:
- Classifier tests can be reviewed without knowing async runtime internals.
- Existing runtime tests still pass.
- No behavior change claimed.
Phase 2: Route Coordinator Through The Classifier
Deliverables:
- Replace scattered guard decisions with one classifier call in
LoopRuntimeCoordinator. - Keep existing executor helpers where possible:
- issued-dose live input shaping
- issued-dose replay row generation
- fallback replay row generation
- fallback plan clearing
- pending-state clearing
- Add trace telemetry with classifier reason code.
Exit criteria:
- The order remains: issue-dose resolution/merge decision, fallback replay, live step.
- The classifier reason code appears in local trace/telemetry for review.
- Current focused replay/fallback/meal tests pass without weakening assertions.
Phase 3: Add A Single UI Projection
Deliverables:
- Add
MealDeliveryProgressProjectionor similar. - Home views consume projection instead of recomputing from raw pending fields.
- Projection states should be explicit:
- hidden
- composing
- delivery in progress
- reconnecting
- confirmed delivered
- assumed delivered by policy
- unresolved, user action available
- blocked by fallback recovery
- pod replacement escape available
Exit criteria:
- Meal modal text, availability blocking, pod replacement escape, and Recent Dose Steps use the same source/disposition vocabulary.
- A resolved/assumed/consumed attribution cannot leave stale unresolved UI.
- UI tests cover background/foreground, force-quit/relaunch, old-pod unavailable, canceled partial meal, and next meal submit after partial delivery.
Phase 4: Normalize Telemetry And Recent Dose Rendering Around Decisions
Deliverables:
- Ensure replay rows, fallback events, and Home recent-dose rows all carry:
- requested step
- attributed step
- requested units
- delivered units
- source
- disposition
- failure/reason code
- confirmed vs assumed label
- Add a small "evidence label mapping" helper used by both UI and cloud export.
Exit criteria:
- Scout/cloud and local UI explain the same event the same way.
- Cloud replay-row acceptance checks exist for hardware fallback and interrupted meal flows.
Phase 5: Consider Ledger Only If Needed
Trigger for a ledger rewrite:
- More than one pending attribution must be supported.
- More than one UI/telemetry surface continues to drift from runtime truth.
- Force-quit/relaunch reconstruction needs event-level audit beyond current persisted state.
- Clinical/quality review requires a full chronological event chain.
Until one of those triggers is real, the ledger should remain deferred.
Proposed Decision Table Seed
| ID | Scenario | Evidence | Current intended action | Future classifier decision |
|---|---|---|---|---|
| R-001 | No pending issued dose, no fallback recovery | current pump status | Run live due step if cadence/CGM/pump gates allow | runLiveStep(.currentPumpInput) |
| R-002 | Pending issued dose, current step before first attribution | pending attribution only | If the restored pump state reports active same-pod delivery, skip live advancement. Otherwise do not replay the issued dose and do not run a standalone live step before attribution is eligible; independently executable fallback replay may still proceed and can merge already-resolved issued-dose evidence. | waitForFutureAttributionStep(.issuedDoseNotYetEligible) or fallback merge/skip when runtime evidence requires it |
| R-003 | Pending issued dose, same pod still delivering | same pod delivering | Skip live step | skip(.issuedDoseDeliveryInProgress) |
| R-004 | Pending issued dose, same pod confirmed last delivery, due step is first attribution | matching same-pod evidence | Feed delivered amount into live step | runLiveStep(.issuedDoseConfirmedLive) |
| R-005 | Pending issued dose, same pod confirmed last delivery, due step after first attribution | matching same-pod evidence | Replay first attribution step before live | replayIssuedDose(.confirmed) |
| R-006 | Pending issued dose, same pod evidence mismatch | mismatched request/units/pod/range | Do not confirm from mismatched evidence; current idle/suspended policy may assume delivered with explicit assumed label | assumeIssuedDoseDelivered(.unresolvedIdle) if retained, otherwise skip(.issuedDoseEvidenceMismatch) |
| R-007 | Pending old-pod issued dose, different/new pod observed | different pod identity | Assume delivered, consume by replay/live, scrub replacement lastDelivery | assumeIssuedDoseDelivered(.differentOrNewPod) |
| R-008 | Pending meal dose, no active pod or service stop | no-active/service-stop | Assume delivered for UI escape, keep attribution until replay/live consumption | assumeIssuedDoseDelivered(.oldPodUnavailable) plus clearMealProgressOnly |
| R-009 | Pending canceled meal partial | pump-last-delivery with normalized request step | Store actual partial evidence and consume on next eligible input | replayIssuedDose(.confirmedPartial) or runLiveStep(.confirmedPartial) |
| R-010 | Fallback recovery, same pod credible pump delta, no issued dose | pump-total delta | Replay bounded fallback-active plan rows | replayFallback(.confirmedResidual) |
| R-011 | Fallback recovery, same pod credible pump delta with issued dose inside delta | partitioned pump delta | Partition issued dose, replay residual fallback, merge if same row | replayFallbackWithMergedIssuedDose(.partitioned) |
| R-012 | Fallback recovery, same pod pump total cannot cover issued dose | pump total lower than issued dose | Ambiguous/no fallback replay | skipOrResume(.ambiguousPumpTotalCannotPartition) |
| R-013 | Fallback recovery, different/new pod | replacement identity | Suppress old-pod pump-counter fallback replay | skipFallbackReplay(.differentOrNewPod) |
| R-014 | Fallback recovery, old pod no-active/retired/service-stopped | old pod unrecoverable | Use modeled assumed fallback replay with explicit assumed label | assumeFallbackDelivered(.oldPodUnavailable) |
| R-015 | Fallback plan stale/current due mismatch | stale persisted plan | Clear plan after trace; no replay against later step | skipFallbackReplay(.currentDueMismatch) |
| R-016 | Positive fallback delta but zero schedule weights | no allocation basis | Suppress replay | skipFallbackReplay(.noPositiveScheduleWeight) |
| R-017 | Stale fallback plan plus confirmed issued-dose evidence | issued-dose evidence and stale fallback metadata | Preserve the issued-dose replay/live attribution; fallback cleanup must not mask delivered insulin accounting | replayIssuedDose(.confirmed) plus fallback cleanup trace |
| R-018 | Suppressed fallback event plus confirmed issued-dose evidence | replacement/suppression event and issued-dose evidence | Suppress only fallback replay; still consume the confirmed issued-dose evidence | replayIssuedDose(.confirmed) |
| R-019 | Same pod still delivering plus pending fallback replay | same pod delivering | Skip live advancement and do not replay fallback until delivery state is safe | skip(.issuedDoseDeliveryInProgress) |
| R-020 | Explicit zero-delivery fallback rows | bounded replay plan with zero units | Replay evidence rows without pump commands so the algorithm receives explicit zero-delivery slots | replayFallback(.confirmedZeroDelivery) |
Coverage Gaps To Close Or Track
These are not necessarily implementation defects. They are review gaps that should be visible before freeze.
| Gap | Why it matters | Proposed closure |
|---|---|---|
| One pending attribution invariant | Current state supports one pending issued dose. That must be deliberate. | Add classifier tests and runtime assertions/tests proving later insulin-adding commands are blocked or safely assumed/consumed before a second pending record can overwrite the first. |
| Same-pod idle unresolved assumption | Current support can assume delivery when pump is idle/suspended and evidence is otherwise unresolved. | Decide whether this is the intended clinical policy for all automatic boluses; make it a named classifier reason if retained. |
| Meal learning for assumed meals | Safety accounting assumes delivery, but algorithm learning may need to exclude unconfirmed meals. | Keep as deferred algorithm-owner design unless clinical team requires it for IDE baseline. |
| Lower-than-modeled fallback residual | Reconciler may use lower pump-reported residual as corrected delivery. | Confirm clinical/quality stance and pin tests around accepted lower-bound behavior. |
| Partial issued-dose overlap with fallback baseline | Bolus may cross baseline boundary; deterministic allocation would be a new rule. | Keep explicit-evidence-only partition unless clinical/quality approves pulse-rate allocation. |
| UI copy for assumed vs confirmed | User must not confuse policy assumption with pump confirmation. | Move label mapping into one helper used by modal, alerts, recent dose, and telemetry. |
| Cloud replay visibility | Local replay rows are not enough if Scout/cloud is the review surface. | Add telemetry acceptance checks for representative hardware traces. |
| Expired/retired pod fallback assumption size | Modeled fallback assumption may over-credit if pod did not deliver. | Decide if large modeled fallback exposure requires acknowledgment or escalated alert. |
Clinical / Quality Questions
- Should same-pod idle/suspended unresolved delivery be assumed delivered for all automatic bolus types, or only when the original pod cannot provide more specific evidence?
- When old-pod fallback pump-counter evidence is unrecoverable because the pod is no-active, retired, or service-stopped, is modeled fallback exposure acceptable for all durations/rates, or should there be a threshold requiring user acknowledgment or clinical review?
- Should assumed-delivered meal announcements be excluded from meal learning unless delivery is pump-confirmed?
- What UI wording should distinguish:
- pump-confirmed delivered
- assumed delivered by clinical policy
- unresolved but still reconcilable
- fallback delivery modeled because old pod evidence is unrecoverable
- Is the current two-minute post-expected-completion meal escape grace acceptable, or should it depend on requested units, context, or CGM trend?
- Should fallback basal lower-than-modeled pump totals always be trusted as authoritative delivered insulin, or only accepted when tied to a known partitioned issued dose?
Recommended Next Work
Recommended next engineering sequence:
- Add the decision matrix rows in this document to tests as plain fixtures.
- Implement the pure classifier in
BionicLoopCorewithout routing live runtime execution through it. - Add a "current behavior parity" test group comparing classifier outcomes to existing coordinator/fallback policy tests.
- Route coordinator decision branches through the classifier after parity passes.
- Add a single Home UI projection that consumes classifier disposition and evidence labels.
- Normalize local Recent Dose Steps and cloud telemetry labels from the same mapping helper.
- Defer event-sourced ledger work unless the one-pending-attribution invariant breaks or audit requirements demand chronological event reconstruction.
This sequence should make the system easier to reason about without forcing a large rewrite in the middle of hardware testing.
Refactor Non-Goals
- Do not change the explicit-user-action-only algorithm start/stop rule.
- Do not add catch-up pump commands.
- Do not introduce multiple pending issued-dose attribution records until a concrete reachable case requires it.
- Do not replace confirmed pump evidence with modeled assumptions except in the explicitly approved old-pod-unrecoverable paths.
- Do not make UI dismissal clear algorithm attribution state by itself.
- Do not promote this plan as formal evidence until tests, traceability, and code-review records are aligned to a specific implementation SHA.