KMP phase 6D: core:data:database becomes a KMP module, on AndroidSQLiteDriver - #239
Conversation
… byte epochToIso becomes kotlin.time.Instant.fromEpochMilliseconds(ms).toString() — the zero-new-dependency replacement (stdlib, stable since Kotlin 2.3), taken now so WorkoutExportMapper is commonMain-eligible when the module converts. The rendering is pinned twice, in different currencies: a new literal-string test (whole-second, zero, three-digit fractional, negative epoch) and the existing DatabaseJsonExporterImplTest assertion, which keeps java.time on the test side deliberately — it is now a cross-library oracle, not a leftover. Mutation M-ISO1 (fromEpochSeconds) reds 4 tests including the cross-oracle; the epoch-zero pin alone survives, as 0s and 0ms name the same instant. Reverted, not committed. Module suite: 128 tests / 0 failures. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011kNU4XxzXp6NpCnVUQA6yH
Room.databaseBuilder(context, AppDatabase::class.java, name) becomes the KMP-shared Room.databaseBuilder<AppDatabase>(context, name), across the one production site, seven src/test, four androidTest, two database-test and one app:app androidTest. The sixteenth site — MigrationTestHelper's KClass argument — was already the Room 3 form and does not move. The module is still Android-only; the reified overload resolves the constructor reflectively until @ConstructedBy arrives with the conversion. One inference consequence, worth knowing before phase 7 repeats it: with the Class<T> parameter gone, a bare ApplicationProvider.getApplicationContext() argument no longer anchors either type parameter — the builder's factory overloads and the context accessor's own T deadlock inference. The three such sites pin it as getApplicationContext<Application>(). Suites: database 128/0, exercise 212/0 (RepositoryTestEnv consumers untouched); both androidTest APKs compile; detekt separate, 56/56. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011kNU4XxzXp6NpCnVUQA6yH
…ve before the module that needs them KmpLibraryConventionPlugin now calls withDeviceTest unconditionally — mirroring classic AGP, where every library carries an androidTest component whether or not sources exist — and registers two more aliases: assembleDebugAndroidTest → assembleAndroidDeviceTest (the vanish ConfigureInstrumentedSuiteGate pre-diagnosed) and connectedDebugAndroidTest → connectedAndroidDeviceTest. The second is a finding, not a plan item: ui_tests.yml invokes connectedDebugAndroidTest literally, so the assemble alias alone would build a device-test APK the weekly suite never runs — the same hole one workflow later. Proven both directions at the convention level: on the parent commit both repo-wide graphs contain 0 *AndroidDeviceTest tasks; with this commit, assembleDebugAndroidTest pulls every KMP module's device-test build and connectedDebugAndroidTest pulls connectedAndroidDeviceTest on all three. An empty device-test APK runs green on-device under the Regression annotation filter (the exact ui_tests.yml invocation), so componentizing modules without instrumented sources costs the weekly suite nothing. RoomLibraryConventionPlugin keeps one plugin id and branches: the KMP side declares the room bundle + paging-common in commonMain (paging-runtime-ktx is Android-only; PagingSource lives in paging-common), sqlite-framework in androidMain (the driver artifact is per-target by construction), room-compiler on kspAndroid + kspIosSimulatorArm64, room-testing on androidDeviceTest. Dead until the first KMP Room module lands in the next commit. The stale KmpLibraryConventionPlugin KDoc sentence pointing android.* needs at the phase-3-deleted core:core-android sibling is corrected to the androidMain + direct-Metro reality core:data:dataStore already proves. KMP modules re-verified green with the new component: core, dataStore, backup:api assemble + host tests, 140/140 executed. detekt separate, 56/56. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011kNU4XxzXp6NpCnVUQA6yH
…ing a byte of schema core:data:database converts to convention.kmpLibrary + the Room convention's KMP branch: 51 files to commonMain, 4 to androidMain (buildAppDatabase and the snapshot/ package — Context, framework SQLite, java.io.File), 26 test classes to androidHostTest, 5 to src/androidDeviceTest/kotlin. AppDatabase gains @ConstructedBy(AppDatabaseConstructor) — a bare expect object; Room KSP generates the actual per target, and NoActualForExpectSuppressionRule is why it carries no NO_ACTUAL_FOR_EXPECT suppression: the metadata compilation never demands an actual, and if codegen stops, the platform compile reds instead of green-over-nothing. The schema did not move, proven the way §6 prescribes: schemaInput redirected to an empty directory out-of-tree, the processor exported a fresh 6.json, byte-identical, identityHash 9a848458f02ef4c4ace9841b9f2e3d42. The mechanism that made the probe necessary is itself the finding — Room 3 exports write-on-diff against schemaInput (= the committed schemas/), so a production build cannot silently rewrite the directory. What the first instrumented conversion surfaced, each fixed at the root: - Room-KMP puts no schemas on the device-test APK; all 7 MigrationTestHelper tests failed loud (Missing file: …/5.json). The Room convention's KMP branch now wires schemas/ as a static device-test asset via the variant API — which requires androidResources.enable = true first, because with it off the component's sources.assets is null (measured): the asset pipeline does not exist. - The first KMP device APK depending on an Android-convention AAR fails checkAndroidDeviceTestAarMetadata over desugaring; the KMP convention now mirrors configureKotlinAndroid's repo-wide core-library desugaring. - Lint does not classify KSP output as generated on KMP: a consumer with checkDependencies analyzed the generated Room code and red 1087 RestrictedApi + 6 SyntheticAccessor — all under build/generated, none in handwritten code. lint.xml gains per-issue **/build/generated/** ignores; mutation M-LINT1 (break the path) brings back exactly 1093, reverted. - src/androidTest was never full-detekt-checked on classic modules (default sources are src/main + src/test); the KMP source-set model put the device tests under the full ruleset for the first time and surfaced two pre-existing Wrapping violations in AtomicRollbackDeviceTest — fixed. - detekt.yml: every list carrying the androidHostTest glob gains **/androidDeviceTest/**, and the header note now points at the convention (configureDetektSources) instead of the pre-05b75a4c per-module wiring. Gates, all --rerun-tasks --no-build-cache --no-configuration-cache: full forced gate 3248/3248 tasks 0 failed; detekt separate 56/56; unit tests 2230/230 (testDebugUnitTest scope) + 165/33 (testAndroidHostTest scope) = 2395, accounting exactly as 2354 baseline + 4 new epochToIso pins + 37 KMP-module tests the old testDebugUnitTest-glob methodology never counted (core:core 32, dataStore 5 — execution was never affected, only counting; report both scopes from here on). Device: Starting 28 tests on Pixel_6_API_34, 28/28 green via connectedDebugAndroidTest alias under the exact ui_tests.yml Regression annotation filter. Suite gate prints ":core:data:database: 5 instrumented source files, 206 classpath entries scanned, 0 missing" over androidDeviceTestRuntimeClasspath. iOS: klib compiled + kspKotlinIosSimulatorArm64 in the same repo-wide graph. Goldens 456, 0 touched. schemas/ untouched by git. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011kNU4XxzXp6NpCnVUQA6yH
… of its own claims New §9 carries the conversion's findings for phase 7's ~30 modules: the connectedDebugAndroidTest fifth vanish, the Room-KMP device-asset gap behind androidResources.enable, write-on-diff schema export, KSP2 config spellings, the forbidden-and-unneeded NO_ACTUAL_FOR_EXPECT suppression, the desugaring AAR-metadata check, lint's blindness to generated code on KMP, and detekt seeing device tests under the full ruleset for the first time. §3's setQueryCoroutineContext and 5-file-androidMain claims are dated in place, not deleted. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011kNU4XxzXp6NpCnVUQA6yH
Unit Test Results2 066 tests 2 066 ✅ 9m 55s ⏱️ Results for commit c857cb6. ♻️ This comment has been updated with latest results. |
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
@claude review |
|
@codex review |
|
Claude finished @stslex's task in 2m 17s —— View job ReviewWent through the full diff (
Two minor, non-blocking notes:
No correctness issues found in the conversion itself. The gate table in the PR description (2395 unit tests, 28/28 device suite, byte-identical schema/identityHash, both alias directions verified) is consistent with what the diff implements. Nice work isolating this from the |
|
Codex Review: Didn't find any major issues. Can't wait for the next one! Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
|
@claude review |
|
@codex review |
|
Claude finished @stslex's task in 1m 30s —— View job Review
Went through Confirmed correct / no new issues found:
Still open from the prior automated review, both non-blocking:
No correctness issues found in the KMP conversion itself. The port is well-isolated from the |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: fea9edd127
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
Review classification before the next push:
|
|
Additional classification before the same push:
|
|
@claude review |
|
@codex review |
|
Claude finished @stslex's task in 53s —— View job Review
Re-reviewed at HEAD ( Both P1 fixes verified correct:
All three anchors resolve to real, matching section headers in Other changes in this commit ( Housekeeping note: the working tree in this review checkout ( No correctness issues found. Both outstanding P1 comments from Codex are resolved as claimed, and the fix commit is scoped exactly to what those comments asked for — no unrelated changes riding along. |
|
Codex Review: Didn't find any major issues. You're on a roll. Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
) **KMP phase 6, the driver flip: Android moves to `BundledSQLiteDriver`. Stacked on #239** — review that first; this is one commit on top, deliberately separate so a SQLite-behaviour regression and a source-set regression stay distinguishable under bisect, and so this half reverts alone. #241 (core:data:exercise → KMP) stacks on top of this PR. ## What flips, what doesn't, and why the line is where it is | Surface | Driver after this PR | Why | |---|---|---| | Production (`buildAppDatabase`) | **Bundled** (SQLite 3.50.x) | The point: one SQLite build on every device instead of the per-OEM system one | | The 5 device-test classes (migration, rollback, invalidation, round-trip, recently-trained) | **Bundled** | The migration suite is the flip's gate, so it must run the flipped driver | | `InMemoryDatabaseProvider` (app:app androidTest via `MetroTestRule`) | **Bundled** | Device-side; the whole instrumented app suite now exercises the production driver | | `RepositoryTestEnv` + host tests (`BaseDatabaseTest`, snapshot test) | **Framework, pinned with a comment** | **Measured, not assumed**: the bundled android variant ships Android-ABI natives only — under Robolectric on a desktop JVM it dies with `UnsatisfiedLinkError`. Robolectric is not an admissible driver oracle in this repo anyway (`AtomicRollbackDeviceTest` KDoc: two false negatives) | The Room convention's KMP branch now declares `sqlite-bundled` for androidMain; the database module pins `sqlite-framework` on its host tests explicitly, next to the reason. ## The gate — the migration suite on a real device, under the flipped driver - **28/28 device tests green under `BundledSQLiteDriver`** on Pixel_6_API_34 (arm64), collected count printed (`Starting 28 tests`), via the `connectedDebugAndroidTest` alias under the exact ui_tests.yml Regression filter. That includes all 7 `AppDatabaseMigrationTest` cases — a real v5 file seeded on disk, `Migration6` applied, schema validated against `6.json` — plus the atomicity-rollback oracle (both production transaction shapes), invalidation across connections, and the on-disk round-trip. - **The same 28 ran green under `AndroidSQLiteDriver` in #239** — the spec's "under both drivers" requirement, satisfied across the two PRs by construction. - **41/41 app:app device Regression tests green** with the flipped in-memory provider (41 = the 35 pinned in tech-debt plus #232's six DataStore singleton tests). ## Risk register (from the pre-flip SQL audit of all 105 `@Query` sites + every raw-SQL site) - **Zero double-quoted string literals** repo-wide (the classic `SQLITE_DQS=0` breakage): every SQL literal is single-quoted. Zero version-sensitive constructs in use — no window functions, CTEs, upserts (`@Insert(REPLACE/IGNORE)` compile to the pre-upsert forms, frozen since 3.6.19), date/time/json/math functions, `RETURNING`, or `IIF`. - **One query with version-floating output, pre-existing**: `SessionDao.getRecentSessionsForExercise` projects a bare column under `GROUP BY` with no aggregate — legal and arbitrary in *both* SQLite builds; a planner change may pick a different in-group row when one session holds the same exercise twice. Not a flip regression; noted for the record. - **Cross-library file interop is deliberate and stays**: the `snapshot/` package opens the live DB via framework SQLite (peek + pre-migration checkpoint) while Room holds bundled connections. Main-db and WAL formats are frozen, so reads are safe by format; the concurrent-open path leans on POSIX locking shared by both libraries. This is the residual risk the device-restore pass (Ilya's gate, per the recovery spec) covers end-to-end; flagged here rather than silently carried. - Improvements that come free: host-variable limit 999 → 32766, and `ROW_NUMBER()` becomes available — `SessionDao`'s PR-batch comment now records the rewrite as *available but not taken* (this PR is behaviour-neutral by design). ## Gates | Gate | Result | |---|---| | device suite under bundled | ✅ 28/28, `Starting 28 tests on Pixel_6_API_34(AVD)` | | app:app device Regression | ✅ 41/41 | | host tests (framework pin) | ✅ database 128/26, exercise 212/19, 0 failures | | full forced gate (assembleDebug testDebugUnitTest verifyPaparazziDebug lintDebug assembleDebugAndroidTest) | ✅ `3248 actionable tasks: 3248 executed`, 0 failed | | detekt (separate invocation) | ✅ `56 actionable tasks: 56 executed` | | unit tests | ✅ 2230/230 + 165/33 = 2395, 0 failures — identical to #239, as a behaviour-neutral flip must be | | goldens | ✅ 456, 0 touched | 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_011kNU4XxzXp6NpCnVUQA6yH
…roidMain (#241) **KMP phase 6, increment E: `core:data:exercise` becomes a KMP module — with zero androidMain. Stacked on #240** → #239. Review in that order. The last code increment of phase 6. Four commits: the one genuine behaviour change (red-first, on device), the wall-clock swap, the pure-mechanics conversion, and the hook-deployment fix the conversion's own near-miss surfaced. ## 1. The behaviour change the spec predicted, observed live before it was fixed Spec §5 called `catch (SQLiteConstraintException)` in `ExerciseRepositoryImpl.saveItem` *control flow, not a type name*: under a Room 3 driver the constraint violation arrives as a different type, the catch silently stops matching, and the exception escapes — with no compile error. That is exactly what happened. A new on-device duplicate-name test (this module's first device test, `@Regression`, in-memory DB under the flipped `BundledSQLiteDriver`) ran **red against the unfixed repository**, escaping with: ``` android.database.SQLException: Error code: 2067, message: UNIQUE constraint failed: exercise_table.name ``` The fix is smaller than the spec's own sketch, because of a fact worth keeping: **on Android, `androidx.sqlite.SQLiteException` is `actual typealias … = android.database.SQLException`** (read from the 2.7.0 sources jar). So one common catch covers the framework driver's `SQLiteConstraintException` (a subtype), the bundled driver's own throw (measured above), and iOS's native actual — no expect/actual seam needed at all. The constraint check gates on the one string both engines emit (`UNIQUE constraint failed`), because the common type has no code field and the framework subtype is not nameable from common code. Proven in all four directions: device red on the old catch → green on the new; the Robolectric twin (framework driver) green throughout; mutation **M-DUP1** (never-matching string) reds **both** legs — 1 of 54 host tests, 1 of 1 device — reverted. ## 2. Mechanics - **Eight `System.currentTimeMillis()` sites** → `kotlin.time.Clock.System.now().toEpochMilliseconds()` (stdlib, same wall clock; its own commit so the conversion stays pure moves). - **The conversion**: all 42 production files → commonMain. **No androidMain** — after the catch fix, the module had no platform type left; the paging surface lives in `paging-common` (verified `iosSimulatorArm64` published). 19 test classes + seed helper → `androidHostTest`, the device test → `src/androidDeviceTest/kotlin`. The dead `compose-bom`/`compose-runtime`/`room3-runtime` declarations (zero usages, spec §5) do not return; `paging-runtime-ktx` → `paging-common`; the new `androidx-sqlite` (base) alias supplies the exception type the database module's `implementation` deps rightly don't leak. - The whole PR-D convention machinery ran unchanged for the second module: device-test component, both CI aliases, schema-asset wiring (n/a here), desugaring, detekt device-test globs. **Nothing new was needed** — the first conversion paid; the second rode. ## 3. A process finding, in the open `setup-hooks.sh` **copies** `.githooks/pre-commit` into `.git/hooks/` — it does not set `core.hooksPath`, though CLAUDE.md says it does. Consequences, both real: a clone that never ran the script has **no hook at all** (this session's clone — caught when a red detekt run and a passing commit coexisted; the separate forced detekt invocations in this arc's process were the actual gate all along), and a clone that ran it **before #235 still carries the ACM filter** the fixture-move PR proved blind to renames. Fixed here: the script now sets `core.hooksPath` to the tracked `.githooks/`, so hook fixes deploy with `git pull` instead of with a ritual — which is what CLAUDE.md already claimed. Reported rather than silently corrected. ## 4. The §7 riders, re-priced with numbers (closing the phase brief's ask) - **`java.time`**: production `core/data` is now clean — `WorkoutExportMapper`'s line was taken in #239. What remains: 1 test file (`DatabaseJsonExporterImplTest`, deliberately kept as a cross-library oracle) and 14 `feature:exercise-chart` files, which are Phase 7's by the spec's own split. **This rider is discharged for phase 6.** - **`java.io.File` → `okio.Path`**: the dependency half is now free (okio rides the graph via `datastore-preferences-core` since #233; using it in signatures needs only a toml alias). The remaining cost is unchanged in kind: a signature ripple across 7 modules (backup:api, google-drive, worker, database, recovery, settings, app:app). And its urgency **dropped** with this phase: after #239 every `File`-typed surface sits in an androidMain of a KMP module — nothing common is blocked by it. Still correctly deferred until a real iOS backup implementation makes the right shape observable; `android.net.Uri` still has no common analogue (SAF). ## Gates All `--rerun-tasks --no-build-cache --no-configuration-cache`. | Gate | Result | |---|---| | device: duplicate-name under bundled | ✅ red on old catch (escape above) → 1/1 green via the `connectedDebugAndroidTest` alias under the ui_tests.yml Regression filter | | host tests | ✅ exercise 212/19 (now `testAndroidHostTest` scope), feature:exercise 113/9, exercise-chart 66/8, 0 failures | | full forced gate (assembleDebug testDebugUnitTest verifyPaparazziDebug lintDebug assembleDebugAndroidTest) | ✅ `3265 actionable tasks: 3265 executed`, 0 failed | | detekt (separate invocation) | ✅ `57 actionable tasks: 57 executed` — the 57th is the suite gate over this module's new device test, previously NO-SOURCE | | repo-wide unit tests | ✅ **2018 / 211** (`testDebugUnitTest` scope) + **377 / 52** (`testAndroidHostTest` scope) = **2395**, 0 failures, 0 skipped — identical total to #239/#240; exercise's 212 moved scope, not existence | | iOS | ✅ exercise klib + `kspKotlinIosSimulatorArm64` in the repo-wide `assembleDebug` graph | | goldens | ✅ 456, 0 touched | 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_011kNU4XxzXp6NpCnVUQA6yH
KMP phase 6, increment D:
core:data:databasebecomes a KMP module, onAndroidSQLiteDriver.The centrepiece of phase 6's second half (#232 carried the spec; #235 was this PR's step 1). The port only — the
BundledSQLiteDriverflip is deliberately NOT here; it follows as its own PR with its own gate, so a SQLite regression and a source-set regression stay distinguishable under bisect. The schema did not move: the regenerated6.jsonis byte-identical,identityHash 9a848458f02ef4c4ace9841b9f2e3d42.The shape — five commits, bisect-green each
epochToIsoleavesjava.time—kotlin.time.Instant.fromEpochMilliseconds(ms).toString(), the zero-new-dependency stdlib replacement, measured byte-identical againstjava.timefor whole-second / zero / fractional / negative epochs. Pinned twice: new literal-string tests, andDatabaseJsonExporterImplTestkeepsjava.timeon the test side deliberately as a cross-library oracle. Mutation M-ISO1 (fromEpochSeconds) reds 4 tests — the epoch-zero pin alone survives, since 0 s and 0 ms name the same instant; reverted. This makesWorkoutExportMappercommonMain-eligible: androidMain ends at 4 files, not the spec's 5.Room.databaseBuilder<AppDatabase>(context, name)etc., while the module is still Android-only (the reified overload falls back to reflection until@ConstructedByexists — so this commit is pure mechanics, bisectable on its own). The 16th site was already the Room 3 KClass form; the spec's total said 15, measured 16. Phase-7 note: withClass<T>gone, a bareApplicationProvider.getApplicationContext()argument deadlocks inference against the builder's factory overloads — pin asgetApplicationContext<Application>().withDeviceTestunconditional in the KMP convention (classic-AGP parity: every library carries the component whether or not sources exist) plusassembleDebugAndroidTest → assembleAndroidDeviceTestandconnectedDebugAndroidTest → connectedAndroidDeviceTest. The second alias is a finding, not a plan item:ui_tests.ymlinvokesconnectedDebugAndroidTestliterally, so the pre-diagnosed assemble alias alone would build a device-test APK the weekly suite never runs — the fifth silent-vanish instance of this arc. Proven both directions: on the parent commit both repo-wide graphs contain 0*AndroidDeviceTesttasks; with the commit, both are fully wired for every KMP module, and an empty KMP device-test APK runs green on-device under the Regression annotation filter (the exact ui_tests.yml invocation), so the three source-less KMP modules cost the weekly suite nothing.RoomLibraryConventionPluginkeeps one plugin id and branches (KMP: room bundle +paging-commonin commonMain,sqlite-frameworkin androidMain,kspAndroid/kspIosSimulatorArm64, room-testing on the device suite).buildAppDatabase,snapshot/×3: Context, framework SQLite,java.io.File), 26 test classes →androidHostTest, 5 →src/androidDeviceTest/kotlin;@ConstructedBy+ a bareexpect object AppDatabaseConstructor(see below); detekt.yml gains**/androidDeviceTest/**beside every host-test glob, in this commit because that is when the sources exist (spec §3.4).kmp-phase-6-data-layer.md; two of the spec's own §3 claims dated in place.What the first instrumented conversion surfaced — each measured, each fixed at the root
MigrationTestHelpertests failed on device, loudly:FileNotFoundException … Missing file: …/5.json. The classic integration's androidTest-assets copy has no KMP counterpart, so the Room convention's KMP branch wiresschemas/as a static device-test asset via the variant API — which is null untilandroidResources.enable = true: AGP-KMP defaults the entire asset pipeline off (measured:sources.assets == nullbefore, six schemas inside the APK after).checkAndroidDeviceTestAarMetadatafails on the first KMP device APK that depends on an Android-convention AAR ("requires core library desugaring"). The KMP convention now mirrorsconfigureKotlinAndroid's repo-wide desugaring (flag +desugar_jdk_libs).checkGeneratedSources = falseis already set and honored on classic modules; here a consumer withcheckDependencies = truered 1087 RestrictedApi + 6 SyntheticAccessor — 100% underbuild/generated, zero in handwritten code. Encoded inlint.xmlas per-issue**/build/generated/**ignores. Mutation M-LINT1 (break the path) brings back exactly 1093; reverted.NoActualForExpectSuppressionRulerejected theNO_ACTUAL_FOR_EXPECTsuppression I first wrote on the expect object — and it is right: the metadata compilation never demands an actual, the platform compilations get Room's generated ones, and without the suppression a codegen failure reds the build instead of vanishing. And detekt, now seeing device tests through the KMP source-set model, surfaced two pre-existingWrappingviolations inAtomicRollbackDeviceTest—src/androidTestwas never full-detekt-checked on classic modules (default sources aresrc/main+src/test), the same shape as KMP phase 6D step 1: the DB fixtures leave testFixtures, and the pre-commit hook stops ignoring moves #235's testFixtures hole.schemaOutputonly when the generated schema differs fromschemaInput(= the committedschemas/), so a production build can never silently rewrite the schema directory. The §6 gate ran as prescribed: schemaInput redirected to an empty dir via an out-of-tree init script, fresh6.jsonexported, byte-identical.setQueryCoroutineContextdeliberately NOT added (deviation from spec §3, argued): the Android builder runs without it today, adding it would change which context Room queries use — not "the port only" — and nothing constructs a database off-Android until phase 7's iOS composition root, which is where it belongs, next to iOS's driver choice.String.toByteArray(Charsets.UTF_8)is JVM-only →encodeToByteArray()(one site).A counting correction, in the open
The repo-wide unit-test invariant number (2352 at #235) was produced by a
test-results/testDebugUnitTestglob — which never saw KMP modules'testAndroidHostTestresults (core:core 32, dataStore 5). Execution was never affected (CI's alias runs them); only the counting was blind, on both sides of every comparison, so past deltas stayed honest. From this PR on the number is reported in both scopes.Gates
All
--rerun-tasks --no-build-cache --no-configuration-cache; baseline measured on dev@c81c4490 the same way.3248 actionable tasks: 3248 executed, 0 failed (dev baseline: 3080/3080; the +168 is the KMP module's task set + device-test components)56 actionable tasks: 56 executedtestDebugUnitTestscope) + 165 / 33 (testAndroidHostTestscope) = 2395, 0 failures, 0 skipped, 0 stale XMLs. Accounting vs 2354 baseline: +4 new epochToIso pins, +37 previously uncounted KMP host tests, −0 lost; database's 128 moved scope, not existenceStarting 28 tests on Pixel_6_API_34(AVD), 28/28 green — via theconnectedDebugAndroidTestalias under the exact ui_tests.yml Regression annotation filter; red direction observed live (7 migration failures while schemas were missing from assets):core:data:database: 5 instrumented source files, 206 classpath entries scanned, 0 missing— resolved throughandroidDeviceTestRuntimeClasspath6.jsonbyte-identical to committed;git status schemas/emptycompileKotlinIosSimulatorArm64+kspKotlinIosSimulatorArm64+ klib in the same repo-wide graph (theassembleDebugalias pulls them)*AndroidDeviceTesttasks in either repo-wide graph; this branch: fully wiredStacked work: #240 (the
BundledSQLiteDriverflip) sits on top of this branch; PR E (core:data:exercise→ KMP) will stack on #240. Waiting on review here does not block that work.🤖 Generated with Claude Code
https://claude.ai/code/session_011kNU4XxzXp6NpCnVUQA6yH