Files
2026-04-29 21:07:49 +02:00

20 KiB

phase: 01-project-infrastructure-module-wiring plan: 01 type: execute wave: 1 depends_on: [] files_modified: - gradle/libs.versions.toml - gradle.properties - tools/verify-no-version-literals.sh - tools/verify-shared-pure.sh - tools/verify-ios-flags.sh autonomous: true requirements: [INFRA-01, INFRA-03] requirements_addressed: [INFRA-01, INFRA-03] must_haves: truths: - "gradle/libs.versions.toml is the sole source of library/plugin versions (D-09 / INFRA-01 SC#2)" - "iOS K/N binary flags kotlin.native.binary.gc=cms and kotlin.native.binary.objcDisposeOnMain=false are set in gradle.properties (D-18 / INFRA-03)" - "Shell-based invariant checks (no-version-literals, shared-pure, ios-flags) are executable and fail-loud" artifacts: - path: "gradle/libs.versions.toml" provides: "Version + library + plugin aliases for Koin, Kermit, Spotless, Flyway, PostgreSQL JDBC, Ktor content-negotiation, Ktor JSON serializer" contains: "koin = ", "kermit = ", "spotless = ", "flyway = ", "postgresql =" - path: "gradle.properties" provides: "iOS K/N binary flags" contains: "kotlin.native.binary.gc=cms", "kotlin.native.binary.objcDisposeOnMain=false" - path: "tools/verify-no-version-literals.sh" provides: "Invariant check — no numeric version literals outside catalog in any *.gradle.kts (except build-logic/build.gradle.kts bootstrap coordinates)" - path: "tools/verify-shared-pure.sh" provides: "Invariant check — shared/src/commonMain must not import Ktor / Compose / SQLDelight" - path: "tools/verify-ios-flags.sh" provides: "Invariant check — both iOS K/N flags present in gradle.properties" key_links: - from: "build-logic/ (Plan 02)" to: "gradle/libs.versions.toml" via: "VersionCatalogsExtension.named("libs").findLibrary(...) inside precompiled plugins" pattern: "findLibrary\("koin-core"\)" - from: "gradle.properties" to: ":composeApp:linkDebugFrameworkIosSimulatorArm64" via: "Kotlin/Native compiler reads project properties at link time" pattern: "kotlin\.native\.binary\." Extend the Gradle version catalog with every new alias required by Phase 1 (Koin, Kermit, Spotless, Flyway, Postgres JDBC, ktor-serverContentNegotiation, ktor-serializationKotlinxJson), append the two mandatory iOS Kotlin/Native binary flags to `gradle.properties`, and ship three shell-based invariant scripts under `tools/` that Plan 07 will use as phase-gate checks.

Purpose: This plan creates the foundation on which every other Phase 1 plan rests. Without these catalog entries, build-logic/ (Plan 02) cannot resolve findLibrary("koin-core"); without the iOS flags, INFRA-03 fails silently. The verification scripts are required by 01-VALIDATION.md Wave 0 — every subsequent plan's <automated> block calls one of them.

Output: An extended gradle/libs.versions.toml (additive only, no version bumps to existing entries), extended gradle.properties with exactly two new lines, and three executable .sh scripts under a new tools/ directory.

<execution_context> @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md </execution_context>

@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/REQUIREMENTS.md @.planning/phases/01-project-infrastructure-module-wiring/01-CONTEXT.md @.planning/phases/01-project-infrastructure-module-wiring/01-RESEARCH.md @.planning/phases/01-project-infrastructure-module-wiring/01-PATTERNS.md @.planning/phases/01-project-infrastructure-module-wiring/01-VALIDATION.md @gradle/libs.versions.toml @gradle.properties @CLAUDE.md

From gradle/libs.versions.toml (current state, to extend):

[versions]
kotlin = "2.3.20"
ktor = "3.4.1"
composeMultiplatform = "1.10.3"
# (plus agp, androidx-*, composeHotReload, junit, kotlinx-coroutines, logback, material3)

[libraries]
# Existing: kotlin-test, kotlin-testJunit, junit, androidx-*, compose-*, kotlinx-coroutinesSwing,
#           logback, ktor-serverCore, ktor-serverNetty, ktor-serverTestHost

[plugins]
# Existing: androidApplication, androidLibrary, composeHotReload, composeMultiplatform,
#           composeCompiler, kotlinJvm, ktor, kotlinMultiplatform

From gradle.properties (current state — 10 lines of Kotlin + Gradle + Android config):

kotlin.code.style=official
kotlin.daemon.jvmargs=-Xmx3072M
org.gradle.jvmargs=-Xmx4096M -Dfile.encoding=UTF-8
org.gradle.configuration-cache=true
org.gradle.caching=true
android.nonTransitiveRClass=true
android.useAndroidX=true
Task 1: Extend gradle/libs.versions.toml with Phase 1 aliases gradle/libs.versions.toml - gradle/libs.versions.toml (see current state of versions/libraries/plugins tables) - .planning/phases/01-project-infrastructure-module-wiring/01-RESEARCH.md lines 110-175 (§ Standard Stack + Installation TOML fragments) - .planning/phases/01-project-infrastructure-module-wiring/01-PATTERNS.md lines 446-490 (delta blocks for [versions] / [libraries] / [plugins]) - .planning/phases/01-project-infrastructure-module-wiring/01-CONTEXT.md D-09 (catalog-only hard rule), D-14 (Koin deps needed), D-15 (Kermit), D-10 (Spotless), D-16 (Flyway + Postgres + content-negotiation) Extend `gradle/libs.versions.toml` with the new aliases for Phase 1. Preserve every existing entry verbatim (do NOT rename, remove, or bump any existing version).

Append the following to [versions], in the existing alphabetical-ish order:

flyway = "12.4.0"
kermit = "2.1.0"
koin = "4.2.1"
kotlinx-serialization = "1.7.3"
postgresql = "42.7.10"
spotless = "8.4.0"

Append the following to [libraries]:

# Koin (client DI — D-14)
koin-bom = { module = "io.insert-koin:koin-bom", version.ref = "koin" }
koin-core = { module = "io.insert-koin:koin-core" }
koin-compose = { module = "io.insert-koin:koin-compose" }
koin-composeViewmodel = { module = "io.insert-koin:koin-compose-viewmodel" }
koin-android = { module = "io.insert-koin:koin-android" }

# Kermit (client logger — D-15)
kermit = { module = "co.touchlab:kermit", version.ref = "kermit" }

# Server: Ktor content-negotiation + JSON serializer + Flyway + Postgres (D-16)
ktor-serverContentNegotiation = { module = "io.ktor:ktor-server-content-negotiation-jvm", version.ref = "ktor" }
ktor-serializationKotlinxJson = { module = "io.ktor:ktor-serialization-kotlinx-json-jvm", version.ref = "ktor" }
flyway-core = { module = "org.flywaydb:flyway-core", version.ref = "flyway" }
flyway-database-postgresql = { module = "org.flywaydb:flyway-database-postgresql", version.ref = "flyway" }
postgresql = { module = "org.postgresql:postgresql", version.ref = "postgresql" }

Append the following to [plugins]:

spotless = { id = "com.diffplug.spotless", version.ref = "spotless" }
flywayPlugin = { id = "org.flywaydb.flyway", version.ref = "flyway" }

IMPORTANT invariants:

  • koin-core, koin-compose, koin-compose-viewmodel, koin-android have NO version.ref — they are BOM-managed by koin-bom.
  • kotlin-test is already in the catalog (line 22) — do NOT re-add.
  • Do NOT bump any existing version alias (kotlin, ktor, composeMultiplatform, logback, etc.).
  • The koin-composeViewmodel alias name uses camelCase (Gradle converts dashes-to-dots for accessors, but camelCase preserves koin.composeViewmodel.get()). grep -E '^(flyway|kermit|koin|kotlinx-serialization|postgresql|spotless)\s*=' gradle/libs.versions.toml | wc -l | grep -q '^6$' && grep -E '^koin-bom\s*=' gradle/libs.versions.toml && grep -E '^koin-core\s*=' gradle/libs.versions.toml && grep -E '^koin-compose\s*=' gradle/libs.versions.toml && grep -E '^koin-composeViewmodel\s*=' gradle/libs.versions.toml && grep -E '^koin-android\s*=' gradle/libs.versions.toml && grep -E '^kermit\s*=' gradle/libs.versions.toml && grep -E '^ktor-serverContentNegotiation\s*=' gradle/libs.versions.toml && grep -E '^ktor-serializationKotlinxJson\s*=' gradle/libs.versions.toml && grep -E '^flyway-core\s*=' gradle/libs.versions.toml && grep -E '^flyway-database-postgresql\s*=' gradle/libs.versions.toml && grep -E '^postgresql\s*=' gradle/libs.versions.toml && grep -E '^spotless\s*=\s*{\sid\s=' gradle/libs.versions.toml && grep -E '^flywayPlugin\s*=\s*{\sid\s=' gradle/libs.versions.toml <acceptance_criteria>
    • grep -E '^kotlin\s*=\s*"2\.3\.20"' gradle/libs.versions.toml returns exactly 1 line (existing, unmodified)
    • grep -E '^ktor\s*=\s*"3\.4\.1"' gradle/libs.versions.toml returns exactly 1 line (existing, unmodified)
    • grep -E '^koin\s*=\s*"4\.2\.1"' gradle/libs.versions.toml returns exactly 1 line (new)
    • grep -E '^kermit\s*=\s*"2\.1\.0"' gradle/libs.versions.toml returns exactly 1 line (new)
    • grep -E '^spotless\s*=\s*"8\.4\.0"' gradle/libs.versions.toml returns exactly 1 line (new)
    • grep -E '^flyway\s*=\s*"12\.4\.0"' gradle/libs.versions.toml returns exactly 1 line (new)
    • grep -E '^postgresql\s*=\s*"42\.7\.10"' gradle/libs.versions.toml returns exactly 1 line (new)
    • grep -c '^koin-' gradle/libs.versions.toml returns 5 (koin-bom, koin-core, koin-compose, koin-composeViewmodel, koin-android)
    • grep -c '^flyway-' gradle/libs.versions.toml returns 2 (flyway-core, flyway-database-postgresql)
    • grep -E '^\s*module\s*=\s*"io.insert-koin:koin-core"' gradle/libs.versions.toml returns 1 line with NO version.ref attribute on same line (BOM-managed) </acceptance_criteria> All Phase 1 catalog aliases present; no existing aliases modified; file parses as valid TOML.
Task 2: Append iOS K/N binary flags to gradle.properties gradle.properties - gradle.properties (see current 10-line content) - .planning/phases/01-project-infrastructure-module-wiring/01-RESEARCH.md lines 1082-1107 (§ `gradle.properties` — iOS binary flags — exact content to append) - .planning/phases/01-project-infrastructure-module-wiring/01-CONTEXT.md D-18 (INFRA-03, PITFALL #1) - CLAUDE.md convention #7 (iOS binary flags on day 1) Append the following 5 lines to `gradle.properties` exactly as shown (including the blank separator line and both comment lines). Do NOT modify any existing line:

# Kotlin/Native iOS (PITFALLS.md #1; D-18; INFRA-03) — MANDATORY day 1
# CMS GC + non-main-thread Obj-C deinit to avoid UI-thread pause spikes in Compose Multiplatform.
kotlin.native.binary.gc=cms
kotlin.native.binary.objcDisposeOnMain=false

IMPORTANT:

  • Place AT THE END of the file (append). The existing android.useAndroidX=true stays as the last non-iOS line.
  • Use EXACTLY the property keys kotlin.native.binary.gc and kotlin.native.binary.objcDisposeOnMain. Do not add quotes, spaces, or alternate spellings (the K/N compiler reads these keys literally).
  • Value cms is lowercase. Value false is lowercase. grep -E '^kotlin.native.binary.gc=cms$' gradle.properties | wc -l | grep -q '^1$' && grep -E '^kotlin.native.binary.objcDisposeOnMain=false$' gradle.properties | wc -l | grep -q '^1$' <acceptance_criteria>
    • grep -cE '^kotlin\.native\.binary\.gc=cms$' gradle.properties returns 1
    • grep -cE '^kotlin\.native\.binary\.objcDisposeOnMain=false$' gradle.properties returns 1
    • grep -c '^kotlin\.code\.style=official$' gradle.properties returns 1 (unmodified existing)
    • grep -c '^android\.useAndroidX=true$' gradle.properties returns 1 (unmodified existing)
    • No duplicate of either flag (run grep twice — expect 1 each time, not 2) </acceptance_criteria> Both iOS K/N flags present once; original 10 lines unchanged.
Task 3: Create verify-*.sh invariant scripts under tools/ tools/verify-no-version-literals.sh, tools/verify-shared-pure.sh, tools/verify-ios-flags.sh - .planning/phases/01-project-infrastructure-module-wiring/01-RESEARCH.md lines 1174-1218 (§ tools/verify-*.sh — canonical shell sketches) - .planning/phases/01-project-infrastructure-module-wiring/01-PATTERNS.md lines 1174-1218 (same scripts, same content — Pattern Map confirms no in-repo analog) - .planning/phases/01-project-infrastructure-module-wiring/01-VALIDATION.md lines 62-79 (Wave 0 Requirements — these three scripts gate every task's `` check) Create the three executable bash scripts under `tools/` (create the directory — it does not exist yet). Each must be marked executable (`chmod +x`).

File 1: tools/verify-no-version-literals.sh (enforces D-09 / INFRA-01 SC#2):

#!/usr/bin/env bash
# Enforces INFRA-01 SC#2 / D-09: no literal version strings outside catalog.
# Scans every *.gradle.kts for numeric version literals (e.g. version = "1.2.3"),
# excluding build-logic/build.gradle.kts which needs literal asDependency() coordinates.
set -euo pipefail
VIOLATIONS=$(grep -rn -E 'version[[:space:]]*=[[:space:]]*"[0-9]' --include='*.gradle.kts' . 2>/dev/null | grep -v 'build-logic/build.gradle.kts' || true)
if [ -n "$VIOLATIONS" ]; then
    echo "ERROR: version literals found outside catalog:" >&2
    echo "$VIOLATIONS" >&2
    exit 1
fi
echo "OK: no version literals outside catalog."

File 2: tools/verify-shared-pure.sh (enforces INFRA-06 / D-19):

#!/usr/bin/env bash
# Enforces INFRA-06 / D-19: shared/commonMain must not import Ktor, Compose, SQLDelight.
# Runs grep against shared/src/commonMain/ only. Allowed imports: kotlin.*, kotlinx.serialization, kotlinx.datetime.
set -euo pipefail
if [ ! -d shared/src/commonMain ]; then
    echo "OK: shared/src/commonMain does not exist yet (pre-scaffold)."
    exit 0
fi
VIOLATIONS=$(grep -rn -E '^import[[:space:]]+(io\.ktor|androidx\.compose|org\.jetbrains\.compose|app\.cash\.sqldelight)' shared/src/commonMain/ 2>/dev/null || true)
if [ -n "$VIOLATIONS" ]; then
    echo "ERROR: shared/commonMain has forbidden imports:" >&2
    echo "$VIOLATIONS" >&2
    exit 1
fi
echo "OK: shared/commonMain is pure."

File 3: tools/verify-ios-flags.sh (enforces INFRA-03 / D-18):

#!/usr/bin/env bash
# Enforces INFRA-03 / D-18: iOS K/N flags present in gradle.properties.
set -euo pipefail
grep -q '^kotlin\.native\.binary\.gc=cms$' gradle.properties || { echo "MISSING: kotlin.native.binary.gc=cms" >&2; exit 1; }
grep -q '^kotlin\.native\.binary\.objcDisposeOnMain=false$' gradle.properties || { echo "MISSING: kotlin.native.binary.objcDisposeOnMain=false" >&2; exit 1; }
echo "OK: iOS binary flags present."

After writing all three files, run: chmod +x tools/verify-no-version-literals.sh tools/verify-shared-pure.sh tools/verify-ios-flags.sh.

IMPORTANT:

  • Use #!/usr/bin/env bash (not #!/bin/sh) — set -euo pipefail requires bash semantics.
  • tools/verify-shared-pure.sh deliberately returns 0 if shared/src/commonMain does not exist (pre-scaffold state). This lets Plan 07 run the script before Plan 07 itself creates the scaffold.
  • tools/verify-no-version-literals.sh excludes build-logic/build.gradle.kts (its asDependency() trick requires literal plugin version coordinates — D-09 acknowledged exception). test -x tools/verify-no-version-literals.sh && test -x tools/verify-shared-pure.sh && test -x tools/verify-ios-flags.sh && bash tools/verify-ios-flags.sh && bash tools/verify-shared-pure.sh && bash tools/verify-no-version-literals.sh <acceptance_criteria>
    • test -f tools/verify-no-version-literals.sh && test -x tools/verify-no-version-literals.sh succeeds
    • test -f tools/verify-shared-pure.sh && test -x tools/verify-shared-pure.sh succeeds
    • test -f tools/verify-ios-flags.sh && test -x tools/verify-ios-flags.sh succeeds
    • bash tools/verify-ios-flags.sh exits 0 and prints OK: iOS binary flags present. (proves Task 2 wrote flags)
    • bash tools/verify-shared-pure.sh exits 0 (current shared/src/commonMain/kotlin/dev/ulfrx/recipe/ has only Greeting.kt/Platform.kt/Constants.kt — no ktor/compose imports)
    • bash tools/verify-no-version-literals.sh exits 0 (current *.gradle.kts files use libs.plugins.* aliases — no literal versions)
    • Each script has #!/usr/bin/env bash as line 1
    • Each script uses set -euo pipefail </acceptance_criteria> Three executable verification scripts exist, each runs green against the current repo state.

<threat_model>

Trust Boundaries

Boundary Description
developer → Gradle build Local-only; Gradle reads libs.versions.toml + gradle.properties verbatim. No untrusted input.
Gradle → Maven Central + Gradle Plugin Portal Existing repository declarations in settings.gradle.kts (Plan 03 doesn't change them). Pinned versions via catalog reduce supply-chain drift.

STRIDE Threat Register

Threat ID Category Component Disposition Mitigation Plan
T-01-01-01 Tampering (supply chain) gradle/libs.versions.toml new entries mitigate All new version refs are pinned to specific stable releases (koin = "4.2.1", kermit = "2.1.0", flyway = "12.4.0", spotless = "8.4.0", postgresql = "42.7.10") — no version ranges, no latest.release. Gradle verifies SHA-256 via gradle/verification-metadata.xml if enabled in later phases.
T-01-01-02 Tampering tools/*.sh scripts accept Scripts live in repo and run locally; their only effect is exit 0/1. Read gradle.properties and *.gradle.kts only — no network I/O, no write. Risk = low.
T-01-01-03 Information Disclosure gradle.properties iOS flags accept Flag values (cms, false) are build configuration, not secrets. Public in every iOS KMP tutorial.
T-01-01-04 Denial of Service wrong catalog syntax breaks build mitigate Task 1 <acceptance_criteria> greps for exact alias presence; Wave 2 plans that consume the catalog will fail fast if an alias is misspelled.
</threat_model>
Phase-level verification for this plan: - All three `tools/verify-*.sh` scripts run green against the post-plan repo. - `gradle/libs.versions.toml` parses (Gradle will surface a TOML parse error at next `./gradlew` invocation in Plan 02). - `gradle.properties` has exactly two new iOS K/N flag lines and is otherwise byte-identical to its pre-plan content.

No Gradle build is expected to run fully in this plan — we have not yet scaffolded build-logic/ (Plan 02) nor refactored modules (Plan 03), so ./gradlew build would fail to resolve the new library aliases. Catalog additions ARE safe for Gradle configuration though (unused entries are inert).

<success_criteria>

  • tools/verify-ios-flags.sh exits 0
  • tools/verify-no-version-literals.sh exits 0
  • tools/verify-shared-pure.sh exits 0
  • Catalog contains 6 new [versions] keys (flyway, kermit, koin, kotlinx-serialization, postgresql, spotless)
  • Catalog contains 10 new [libraries] entries (5 koin-, kermit, 2 ktor-, 2 flyway-*, postgresql)
  • Catalog contains 2 new [plugins] entries (spotless, flywayPlugin) </success_criteria>
After completion, create `.planning/phases/01-project-infrastructure-module-wiring/01-01-SUMMARY.md` recording: catalog entries added (count), gradle.properties append location, shell-script paths, and any deviation from the planned version pins (if Maven Central shows a newer stable, record the downgrade decision).