--- 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 `` 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. @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md @.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): ```toml [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): ```properties 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: ```toml 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]`: ```toml # 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]`: ```toml 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*\{\s*id\s*=' gradle/libs.versions.toml && grep -E '^flywayPlugin\s*=\s*\{\s*id\s*=' gradle/libs.versions.toml - `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) 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: ```properties # 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$' - `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`) 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): ```sh #!/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): ```sh #!/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): ```sh #!/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 - `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` Three executable verification scripts exist, each runs green against the current repo state. ## 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 `` greps for exact alias presence; Wave 2 plans that consume the catalog will fail fast if an alias is misspelled. | 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). - `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) 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).