Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions AI_NOTES.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,23 @@
# Native Insulin Form Audit - 2026-10-23

- Five pure regressions reproduced hidden basal/extended fields in carbohydrate,
unrelated, instantaneous and emulated-child branches. A sixth regression used
accepted wire data and the native Room source. Original failing logs retained.
- Audit explicit delivery evidence before native mapper precedence. Preserve
legitimate bolus/basal/extended forms and one native emulated child; competing
forms refuse a partial inventory. Unrelated durations/glucose/profile context
and null fields do not create insulin. Storage remains lossless and unchanged.
- Retained-cache refresh, database reopen and valid correction recovery pass;
no clinical/action rows are written. All164focused tests pass without skips.
Frozen full test/lint/compile/assemble passed in6m48s:6800passed/six existing
skips, zero failures/errors, real pristine SDK TLS2.142s and zero lint errors
with382existing warnings/eight hints. All1431source hashes stayed unchanged
and APK retention passed. Mainfa6f22b7 CI39906923561 passed all three jobs.
Exact-head feature CI and main integration remain separate post-commit gates.
- Existing0.1.11 release, AAPS, live onset clock, targets, doses and alarms remain
unchanged. This stage does not attest complete upstream/pump history or enable
a clinically recommended meal waiting time.

# Insulin History Knowledge Boundary And 0.1.11 - 2026-10-23

- Main6a0a7894 CI39881365894 passed all three jobs. Native basal/profile source
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,9 +111,14 @@ internal class AapsV3InsulinInventoryTimeline(snapshot: LocalV3RetainedSnapshot)

private fun dose(body: JsonObject): AapsV3InsulinDose? {
val insulin = number(body, "insulin")
val event = string(body, "eventType")
val intervalFields = listOf("enteredinsulin", "absolute", "percent", "rate", "extendedEmulated")
val intervalEvidence = event in setOf("Temp Basal", "Combo Bolus") ||
intervalFields.any { present(body, it) } || boolean(body, "isEmulatingTempBasal") == true
require(insulin == null || insulin >= 0)
// The native mapper chooses insulin before eventType and before carbohydrate fields.
// Native branch precedence is not proof that competing delivery fields are absent.
if (insulin != null && insulin > 0) {
require(!intervalEvidence)
require((number(body, "carbs") ?: 0.0) == 0.0)
require((number(body, "duration") ?: 0.0) == 0.0 && (number(body, "durationInMilliseconds") ?: 0.0) == 0.0)
require(body["extendedEmulated"] == null || body["extendedEmulated"] == JsonNull)
Expand All @@ -122,10 +127,20 @@ internal class AapsV3InsulinInventoryTimeline(snapshot: LocalV3RetainedSnapshot)
val config = body["icfg"]?.takeUnless { it == JsonNull }?.let { AapsV3ProfileJson.insulin(HistoryJson.obj(it)) }
return AapsV3InsulinDose.Bolus(insulin, type, boolean(body, "isBasalInsulin") ?: false, config)
}
if ((number(body, "carbs") ?: 0.0) != 0.0) return null
return when (string(body, "eventType")) {
require(boolean(body, "isBasalInsulin") != true)
if ((number(body, "carbs") ?: 0.0) != 0.0) {
require(!intervalEvidence)
return null
}
return when (event) {
"Temp Basal" -> {
body["extendedEmulated"]?.takeUnless { it == JsonNull }?.let { return extended(HistoryJson.obj(it)) }
require(!present(body, "enteredinsulin"))
body["extendedEmulated"]?.takeUnless { it == JsonNull }?.let {
val child = HistoryJson.obj(it)
require(boolean(child, "isEmulatingTempBasal") == true)
return extended(child)
}
require(boolean(body, "isEmulatingTempBasal") != true)
val duration = duration(body)
val absolute = number(body, "absolute")?.also { require(it >= 0) }
val percent = number(body, "percent")?.also { require(it >= -100) }
Expand All @@ -137,11 +152,18 @@ internal class AapsV3InsulinInventoryTimeline(snapshot: LocalV3RetainedSnapshot)
}
"Combo Bolus" -> extended(body)
"Correction Bolus", "Meal Bolus", "Snack Bolus" -> throw IllegalArgumentException("missing_native_insulin")
else -> null
else -> {
require(!intervalEvidence)
null
}
}
}

private fun extended(body: JsonObject): AapsV3InsulinDose.Extended {
require((number(body, "insulin") ?: 0.0) == 0.0 && (number(body, "carbs") ?: 0.0) == 0.0)
require(boolean(body, "isBasalInsulin") != true)
require(listOf("absolute", "percent", "extendedEmulated").none { present(body, it) })
number(body, "rate")?.let { require(it >= 0) }
val units = checkNotNull(number(body, "enteredinsulin")).also { require(it >= 0) }
return AapsV3InsulinDose.Extended(units, duration(body), boolean(body, "isEmulatingTempBasal") ?: false)
}
Expand Down Expand Up @@ -176,4 +198,5 @@ internal class AapsV3InsulinInventoryTimeline(snapshot: LocalV3RetainedSnapshot)
private fun number(body: JsonObject, key: String) = body[key]?.takeUnless { it == JsonNull }?.let(HistoryJson::double)
private fun string(body: JsonObject, key: String) = body[key]?.takeUnless { it == JsonNull }?.let(HistoryJson::string)
private fun boolean(body: JsonObject, key: String) = body[key]?.takeUnless { it == JsonNull }?.let(HistoryJson::bool)
private fun present(body: JsonObject, key: String) = body[key]?.let { it != JsonNull } == true
}
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,41 @@ class AapsV3EffectiveProfileSourceTest {
Unit
}

@Test fun acceptedMixedWirePayloadCannotHideDeliveryAcrossCacheAndReopen() = runBlocking {
val from = at - 60_000
store().create(input("profile", from))
val api = LocalNightscoutV3EntriesApi(db) { at + 100_000 }
val body = buildJsonObject {
put("app", "AAPS"); put("eventType", "Temp Basal"); put("date", from)
put("duration", 1); put("absolute", 0.8); put("carbs", 24)
}.toString()
val accepted = api.handle("POST", "/api/v3/treatments", emptyMap()) { body.toByteArray() }
assertEquals(201, accepted.status)
val id = Json.parseToJsonElement(accepted.body).jsonObject.getValue("identifier").jsonPrimitive.content
suspend fun rejected(reader: AapsV3EffectiveProfileSource) {
val result = reader.readEvidence(from, at, at + 200_000)
assertEquals(AapsV3ObservedInsulinStatus.INVALID, result.insulin.status)
assertTrue(result.insulin.records.isEmpty())
assertEquals(AapsV3ReportedBasalStatus.INPUT_UNAVAILABLE, result.reportedBasal.status)
assertTrue(result.reportedBasal.segments.isEmpty())
}
val source = AapsV3EffectiveProfileSource(store())
rejected(source)
db.close(); db = open()
val reopened = AapsV3EffectiveProfileSource(store())
rejected(reopened)
val original = checkNotNull(store().get(LocalV3Collection.TREATMENTS, id))
val corrected = JsonObject(Json.parseToJsonElement(original.payloadJson).jsonObject - "carbs")
store().update(LocalV3DocumentInput(LocalV3Collection.TREATMENTS, id, "local-v3-owner", from,
corrected.toString()), original.modifiedRevision)
val result = reopened.readEvidence(from, at, at + 200_000)
assertEquals(AapsV3ObservedInsulinStatus.OBSERVED, result.insulin.status)
assertEquals(AapsV3ReportedBasalStatus.OBSERVED_WINDOW, result.reportedBasal.status)
assertFalse(result.insulin.upstreamCompletenessVerified)
assertTrue(db.therapyDao().since(0).isEmpty())
assertTrue(db.actionCommandDao().latest(10).isEmpty())
}

@Test fun profileHistorySharesRevisionAndRetainsCorrectionsAndDeletionAcrossReopen() = runBlocking {
store().create(input("old", at - 60_000))
val original = store().create(input("new", at - 10_000, 6.5)).document
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -229,4 +229,93 @@ class AapsV3InsulinInventoryTest {
assertFalse(result.records.single().toString().contains("dose-1"))
assertFalse(result.records.single().dose.toString().contains("historical"))
}

@Test fun unrecognizedDeliveryFieldsCannotBecomeAnEmptyOrPartialInventory() {
val indicators = listOf(
buildJsonObject { put("enteredinsulin", 1.0) },
buildJsonObject { put("absolute", 0.0) },
buildJsonObject { put("percent", -100) },
buildJsonObject { put("rate", 0.8) },
buildJsonObject { put("isEmulatingTempBasal", true) },
buildJsonObject { put("isBasalInsulin", true) },
buildJsonObject { put("extendedEmulated", buildJsonObject {
put("enteredinsulin", 1.0); put("duration", 30); put("isEmulatingTempBasal", true)
}) }
)
for (indicator in indicators) for (event in listOf("Note", "Future Delivery")) {
val fields = JsonObject(indicator + ("eventType" to JsonPrimitive(event)))
val hidden = row("hidden", fields = fields)
for (result in listOf(read(hidden), read(row(), hidden))) {
assertEquals("$event $indicator", AapsV3ObservedInsulinStatus.INVALID, result.status)
assertTrue(result.records.isEmpty())
}
// Unknown interval support cannot be discarded as expired.
assertEquals(AapsV3ObservedInsulinStatus.INVALID,
read(row("old", time = at - 7_200_000, fields = fields)).status)
}
}

@Test fun carbohydrateBranchCannotHideBasalOrExtendedDelivery() {
for (delivery in listOf(
buildJsonObject { put("eventType", "Temp Basal"); put("absolute", 0.8); put("duration", 30) },
buildJsonObject { put("eventType", "Combo Bolus"); put("enteredinsulin", 1.0); put("duration", 30) },
buildJsonObject { put("eventType", "Carb Correction"); put("enteredinsulin", 1.0) }
)) {
val result = read(row(fields = JsonObject(delivery + ("carbs" to JsonPrimitive(24)))))
assertEquals(AapsV3ObservedInsulinStatus.INVALID, result.status)
assertTrue(result.records.isEmpty())
}
}

@Test fun instantaneousInsulinCannotHideAdditionalDeliveryForms() {
for (extra in listOf(
buildJsonObject { put("enteredinsulin", 1.0) },
buildJsonObject { put("absolute", 0.0) },
buildJsonObject { put("percent", -100) },
buildJsonObject { put("rate", 0.8) },
buildJsonObject { put("isEmulatingTempBasal", true) }
)) {
assertEquals(AapsV3ObservedInsulinStatus.INVALID, read(row(fields = JsonObject(extra +
("insulin" to JsonPrimitive(2.2))))).status)
}
}

@Test fun basalAndExtendedBranchesCannotHideAnotherForm() {
val basal = buildJsonObject { put("eventType", "Temp Basal"); put("duration", 30); put("absolute", 0.8) }
val extended = buildJsonObject { put("eventType", "Combo Bolus"); put("duration", 30); put("enteredinsulin", 1.0) }
for (fields in listOf(JsonObject(basal + ("enteredinsulin" to JsonPrimitive(1.0))),
JsonObject(extended + ("absolute" to JsonPrimitive(0.8))),
JsonObject(extended + ("percent" to JsonPrimitive(0.0))))) {
assertEquals(AapsV3ObservedInsulinStatus.INVALID, read(row(fields = fields)).status)
}
}

@Test fun emulatedChildCannotSilentlyDropMixedOrRecursivePayloads() {
val child = buildJsonObject { put("enteredinsulin", 1.0); put("duration", 30); put("isEmulatingTempBasal", true) }
for (extra in listOf(
buildJsonObject { put("insulin", 2.0) },
buildJsonObject { put("carbs", 24) },
buildJsonObject { put("absolute", 0.8) },
buildJsonObject { put("isEmulatingTempBasal", false) },
buildJsonObject { put("extendedEmulated", child) }
)) {
val result = read(row(fields = buildJsonObject {
put("eventType", "Temp Basal"); put("duration", 30); put("absolute", 0.8)
put("extendedEmulated", JsonObject(child + extra))
}))
assertEquals(AapsV3ObservedInsulinStatus.INVALID, result.status)
assertTrue(result.records.isEmpty())
}
}

@Test fun nonDeliveryDurationsGlucoseAndInsulinConfigurationDoNotBecomeDoses() {
for (fields in listOf(
buildJsonObject { put("eventType", "Carb Correction"); put("carbs", 24); put("duration", 120) },
buildJsonObject { put("eventType", "Exercise"); put("duration", 30) },
buildJsonObject { put("eventType", "Note"); put("glucose", 5.5); put("icfg", config()) },
buildJsonObject { put("eventType", "Note"); put("enteredinsulin", JsonNull); put("rate", JsonNull) }
)) {
assertEquals(AapsV3ObservedInsulinStatus.NOT_OBSERVED, read(row(fields = fields)).status)
}
}
}
9 changes: 9 additions & 0 deletions docs/AAPS4_INSULIN_OBSERVATIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,15 @@ earlier absence. Latest retained versions are not a historical version archive.
Queries include intervals that start before the requested window and overlap it.
Malformed records cannot be declared expired using an unknown duration.

Native mapper precedence is not completeness evidence. A carbohydrate branch or
unknown event cannot hide entered insulin, basal rate/percentage or an emulated
child. An instantaneous bolus cannot silently discard interval fields. Conflicting
forms reject the whole observational inventory, including old records with unknown
support. Null fields, ordinary carbohydrate/exercise durations and glucose/profile
configuration alone are not delivery. Emulated wrappers still select one child,
which must explicitly be emulating basal and cannot contain another delivery form.
Transport retention stays lossless; successful storage is separate from admission.

The existing native connection owner publishes observation status and record count.
The count includes invalidated retained records and is not delivered insulin or IOB.
Stop/revocation/offline ownership clears the published observation status. No new
Expand Down
3 changes: 3 additions & 0 deletions docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ without separate coverage/clock/delivery admission. See AAPS4_REPORTED_BASAL.md.
The insulin index, like profile history, rejects a newer retained collection
revision before window filtering. Revisions of excluded rows must not erase
exposure from an earlier as-known query; this is not a versioned history archive.
The typed insulin index also audits delivery fields before applying native branch
precedence. Mixed/unknown forms remain retained for diagnosis but cannot produce a
partial insulin or basal read. Transport behavior and clinical tables are unchanged.

## RC1 Target Transaction Boundary

Expand Down
5 changes: 5 additions & 0 deletions docs/INVARIANTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@
- As-known insulin reads reject a collection revision newer than knowledge time
before filtering. A moved/shortened/deleted dose, unrelated change or empty
latest snapshot cannot be used to reconstruct an earlier absence of delivery.
- Native branch precedence cannot erase competing insulin forms. Carbohydrate,
unrelated or unknown records with explicit interval-delivery evidence reject
the inventory rather than disappearing. Instantaneous and extended forms cannot
absorb another form's fields. A valid emulated wrapper selects exactly one child.
Successful transport storage does not qualify such a payload for prediction.

## RC1 Target Admission

Expand Down
11 changes: 11 additions & 0 deletions docs/PLAN.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
# PLAN

## Native Insulin Form Audit (2026-10-23)

- [x] Reproduce five form omissions plus one accepted-wire/native-Room omission.
- [x] Reject hidden/competing native delivery forms without changing transport.
- [x] All164focused tests passed; frozen full Android quality passed6800tests
with six existing skips, real SDK TLS, lint and APK retention checks.
- [x] Mainfa6f22b7 CI39906923561 passed all three jobs.
- [ ] Publish reviewed source, inspect exact-head CI and integrate into main.
- Independent upload/pump/clock coverage and prospective meal-time validation
remain required. No recommended eating-time admission follows from this audit.

## Insulin History Knowledge Boundary And 0.1.11 (2026-10-23)

- [x] Confirm main6a0a7894 CI39881365894 passed all three jobs.
Expand Down
Loading