Commit Graph

4 Commits

Author SHA1 Message Date
Kgothatso Ngako
506dfea802 docs: correct phase 2 against what the jdbc drivers actually needed
Phase 2 is implemented and verified in the fork on
claude/jvm-target-actuals (d1a82ea). Three corrections and one omission.

**Schema handling does not need hand-rolling.** The plan said "you call
Schema.create(driver) and the migration path explicitly, and you have to
track the applied version yourself". SQLDelight 2.x ships a factory
function that shadows the constructor -- JdbcSqliteDriver(url, properties,
schema, migrateEmptySchema, vararg callbacks) -- which does all three,
user_version included. The same-named constructor does none of it, which
is the trap worth naming rather than the work that was budgeted for.

**Foreign keys were the actual work, and the plan never mentioned them.**
Off by default in SQLite, and the pragma is per connection while
JdbcSqliteDriver opens one per thread, so it has to go through the
connection Properties rather than be issued once against the driver.
Recorded along with why that needs no compile dependency on
org.xerial:sqlite-jdbc, which arrives at runtime scope only.

**commonTest has an expect too.** The 23 counted at the top of this
document are commonMain's. Declaring jvm() also creates jvmTest, which
inherits commonTest, so `connect` in ElectrumServersTest blocks every jvm
test from compiling. Noted along with the reason not to stub it empty the
way ios does: the class is @Ignore'd everywhere, so an empty body looks
harmless right up until somebody removes the @Ignore and
connect_to_mainnet_servers starts passing without connecting to anything.

**Phase 2 is the first phase that can be run, and the plan told you not to
bother.** It said "none of this is exercisable until Phase 4. Write the
SQLDelight schema-creation path against a scratch main() if you want
feedback sooner." That was wrong twice: library/src/jvmTest/ already
exists, and the two properties worth checking are exactly the ones a
compiler cannot see. Schema creation and the foreign-key pragma both fail
silently in production -- a missing table only shows up at first query,
and foreign keys being off means cascading deletes quietly do not happen.
The phase now carries a real exit condition, and DbFactoryJvmTest meets it
with five passing tests.

Recorded with it: the two KeyStoreFunctions actuals have to exist before
phase 3 decides anything, because nothing jvm compiles without them, and
they should throw rather than do something plausible.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-06 01:04:02 +02:00
Kgothatso Ngako
0757e50dc5 docs: correct the jvm plan against what phase 1 actually did
Phase 1 is implemented and verified in the lightning-kmp-app fork on
claude/jvm-target-actuals (27a0054). Four things in the plan were wrong,
and doing the work is what surfaced them.

**jvm() belongs at the start of phase 1, not phase 4 -- for the library.**
The plan said leave it off in both builds until phase 4. That is right for
mantra and wrong for the fork: library/src/jvmMain/ is an orphan source
set until the library declares the target, so phases 1-3 would all have
been written blind. Declared first, `:library:compileKotlinJvm` names the
remaining expects, and that list beats grepping for `expect ` -- it
shrinks by exactly what you implement and cannot drift from the truth. The
build stays red across phases 1-3 by design.

That checklist is now recorded as the phase 1 exit condition: exactly
eight expects should remain, and exactly which eight. Anything else means
something in the phase is wrong.

**Phase 3 is two decisions, not four.** gracefulSingleSeedDecryption and
gracefulMultiSeedDecryption are pure exception mapping into a
DecryptSeedResult, and the exception they branch on is
java.security.KeyStoreException -- a plain JCA type that exists on the jvm.
Both are near-copies of the android actuals and need nothing settled
first, so they move alongside phase 2. Only keyStoreEncryption and
keyStoreDecryption are the security decision, and that part of the
analysis stands.

**The Fibonacci template must not be deleted.** The plan said to drop it
"assuming nothing references them". Things do: generateFibi is exercised
by template tests in commonTest, androidHostTest, iosTest, jvmTest and
linuxX64Test, and JvmFibiTest asserts a value that depends on precisely
the two properties fibiprops.jvm.kt defines. That file already satisfies
two of the 25 expects, which is why the count was 23 missing rather than
25. Removing the template is five test files plus four fibiprops.*
actuals, and it is a separate cleanup.

**Phase 1 is fifteen actuals, not fourteen**, and two of them are not
copies of android -- platformElectrumRegtestConf (10.0.2.2 is the
emulator's alias for the host loopback; a jvm process is already on the
host) and phoenixLogWriters (android routes kermit into slf4j because
android tooling reads that back).

Also recorded, because it cost time: a worktree cannot run gradle at all
until the submodules are checked out *and* local.properties exists at five
levels. Neither is version controlled, so a fresh worktree has neither,
and the failure surfaces four builds down at
:...:secp256k1-kmp:jni:android as "SDK location not found" rather than
anywhere obviously related.

Both builds were run: `:library:compileKotlinJvm` fails only on the known
eight, and `:composeApp:compileDebugKotlinAndroid` still passes with the
library's jvm target declared -- the check that matters, since a new
variant must not change how the android target resolves the library.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-06 00:53:51 +02:00
Kgothatso Ngako
2aaa7b99a6 build: phase 0 of the jvm target -- clear the ground, correct the plan
First phase of docs/jvm-target.md. Nothing here turns the target on; it
removes what would break the moment it is turned on, and stages the two
catalog entries that cannot be derived automatically. Two of the four
steps as written in the doc turned out to be wrong, and implementing them
is how that surfaced -- both are corrected in the doc in this commit.

**Deleted the stale jvmMain tree.** Six files under
composeApp/src/jvmMain/kotlin/ac/cord/auxiliary/ survived from the Aux
project this codebase grew out of. They have gone unnoticed because
`jvmMain` is currently an orphan source set -- the accessor creates it,
no target compiles it -- so the wrong package, the Room 2 imports
(androidx.room, not androidx.room3), and the references to a long-gone
AuxDatabase and AuxGlobal have never had to resolve. They would all become
compile errors in phase 4.

They are not lost: they are the closest thing to a skeleton for five of
the six platform actuals phase 4 needs, and main.kt is a reasonable
starting shape for the phase 5 desktop entry point. `git show HEAD~1` has
them.

**Added two catalog entries, not four.** sqlite-bundled-jvm and
sqldelight-sqlite-driver. Both earn their place by being unreachable
otherwise: sqlite-bundled-jvm has to be named explicitly because
variant-aware resolution hands the *android* artifact to anything running
on the host, and sqldelight-sqlite-driver is the jvm counterpart to the
android-driver and native-driver entries already there.

The doc also listed room3-runtime-jvm and sqldelight-jdbc-driver. Neither
is right. Once jvm() exists, commonMain's existing androidx-room3-runtime
resolves to the -jvm variant on its own, so an explicit entry is
redundant and would drift. And the SQLDelight drivers phase 2 needs are
for DbFactory, which lives in lightning-kmp-app -- a separate gradle build
with its own version catalog, where an entry here is simply not visible.

**kspJvm cannot be wired yet, and the build file already said so.** The
doc's phase 0 told you to uncomment
composeApp/build.gradle.kts:194. It contradicted its own phase 4, which is
where jvm() gets turned on. The comment three lines above it states the
rule:

    These configurations only exist when the ios targets are declared,
    which the kotlin block above does only on a mac.

The same holds for kspJvm -- `dependencies { add("kspJvm", ...) }` throws
UnknownConfigurationException until a jvm() target creates the
configuration. So it moves into phase 4, into the same edit that declares
the target. composeApp/build.gradle.kts is deliberately untouched by this
commit.

**Also documented: gradle does not run in a worktree here at all** until
the submodule is checked out, which worktrees do not do automatically.
`lightning-kmp-app/` is empty and configuration fails with "Project with
path ':library' not found in build ':lightning-kmp-app'". Recorded in the
phase 0 verification section along with the caveat that a linked worktree
shares .git/modules/ with the main checkout, so both trees end up on one
submodule git dir.

**Not verified by a build.** For that reason. The deletion is an orphan
source set and the additions are unreferenced catalog lines, so neither
can change a build's outcome -- but that is an argument, not a green
check, and it is the second commit in a row on this branch that has not
compiled anything. Phase 4 is the first phase that genuinely cannot be
done without a working gradle invocation.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-06 00:44:10 +02:00
Kgothatso Ngako
5abc37e463 docs: scope the jvm target, and separate it from testing the daos
Two questions arrived together -- whether Room's own testing guidance
applies to this project, and what desktop support would cost -- and they
turned out to have opposite answers. Both are now in docs/jvm-target.md,
phased, with the blocking work separated from the mechanical work.

**The expensive part is already done.** The four-deep native chain --
secp256k1 -> bitcoin-kmp -> lightning-kmp -> lightning-kmp-app -- already
builds for JVM, on every android build we do. The comment at
composeApp/build.gradle.kts:50 records the mechanism without drawing the
conclusion: lightning-kmp-core publishes no android variant, so our
android target resolves it to the *jvm* one, which pulls
secp256k1-kmp-jni-jvm desktop natives, which is exactly why the build has
to name the android artifact by hand. Read the other way round, every JVM
artifact in the chain is already compiled from source by the composite
build. A jvm target adds no cinterop, no C compilation and no new native
constraints. That was the part worth being afraid of, and it is finished.

**The blocker is one level down, and smaller than it looks.**
lightning-kmp-app/library declares 25 expects and implements them across
35 androidMain files. Its jvmMain holds exactly one: fibiprops.jvm.kt, the
Kotlin multiplatform library template's Fibonacci boilerplate, satisfying
two of the 25 -- both of them the template's own. So 23 actuals are
missing, which is why jvm() is commented out there
(library/build.gradle.kts:18), which is why it is commented out here
(composeApp/build.gradle.kts:46). Mantra cannot declare the target until
the fork does.

Six phases, ordered by that dependency. 0 build config; 1 the fourteen
mechanical phoenix actuals; 2 the three SQLDelight JDBC drivers and
NetworkMonitor; 3 key storage; 4 mantra's own sixteen expects; 5 the
desktop entry point. 1-3 are independent and parallelisable, 4 is where
the compiler finally checks the whole thing. Roughly a week to a
launchable build.

**Phase 3 has no day estimate, deliberately.** keyStoreEncryption /
keyStoreDecryption and their two graceful* wrappers delegate on android to
KeystoreHelper.kt -- 116 lines against AndroidKeyStore, StrongBox
attempted first and fallen back from, key material never leaving hardware.
Desktop JVM has no equivalent, so this is a decision rather than a port,
and the doc gives the three real options against what each actually
protects. A fixed-key JCEKS file is named there as a liability rather than
a stopgap: this is wallet seed material, and it lands on top of the
plaintext-key finding already open against this codebase. Recommended
sequencing is a passphrase-derived KEK with the desktop build marked
unsuitable for real funds, so phases 4 and 5 can proceed without the
security question being quietly treated as answered.

Two inherited mistakes are called out rather than carried forward. The old
Aux jvmMain put the database in java.io.tmpdir behind a TODO -- the doc
says not to inherit that in either phase that touches it. And
schedulePlatformLogic goes through WorkManager on android with no desktop
counterpart, so the doc asks for an explicit choice between a no-op and an
in-process coroutine, written down.

**The DAO answer is an appendix, because it is the opposite answer.** None
of the above is needed to test the DAOs, and burying that would have been
misleading. room3-runtime-android:3.0.1 already exposes the no-Context
inMemoryDatabaseBuilder(Function0<T>) overload, and
MantraDatabaseConstructor already supplies what it needs, so Room's
recommended host-machine form compiles in commonTest and runs under
testDebugUnitTest today. The one trap is native and is the secp256k1
problem mirrored: sqlite-bundled-android ships only android-ABI .so under
jni/, so a local unit test's JVM cannot load it and BundledSQLiteDriver
fails at construction; sqlite-bundled-jvm on the androidUnitTest classpath
is the fix. Robolectric neither helps nor is needed -- it cannot load
android .so on the host either.

Everything structural here was checked against the artifacts rather than
recalled: the Room builder overloads by javap on room3-runtime-android,
the two sqlite-bundled native layouts by unzipping both, and the
availability of room3-runtime-jvm, room3-testing, quartz-jvm and the two
SQLDelight drivers by request against the repositories this build actually
resolves from. The absence of android.* and java.* imports in commonMain,
and of any NFC reference from it, was likewise grepped rather than
assumed.

**Not verified: anything that requires compiling.** No jvm target was
turned on, nothing was built, and the day estimates are estimates. Phase 4
is where dependency-substitution surprises would surface if there are any,
and it is precisely the phase nothing here exercises.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-06 00:38:11 +02:00