diff --git a/.planning/phases/01-project-infrastructure-module-wiring/01-VALIDATION.md b/.planning/phases/01-project-infrastructure-module-wiring/01-VALIDATION.md new file mode 100644 index 0000000..4caa3aa --- /dev/null +++ b/.planning/phases/01-project-infrastructure-module-wiring/01-VALIDATION.md @@ -0,0 +1,101 @@ +--- +phase: 1 +slug: project-infrastructure-module-wiring +status: draft +nyquist_compliant: false +wave_0_complete: false +created: 2026-04-24 +--- + +# Phase 1 — Validation Strategy + +> Per-phase validation contract derived from `01-RESEARCH.md § Validation Architecture`. Phase 1 is predominantly **build-level** verification (Gradle tasks, file structure, grep invariants) rather than unit tests. The existing `ApplicationTest.kt` is the one test file extended (adds `/health` coverage). + +--- + +## Test Infrastructure + +| Property | Value | +|----------|-------| +| **Framework** | `kotlin.test` (commonTest) + `ktor-server-test-host` (JUnit 4 runner for server) + existing KMP template test stubs | +| **Config file** | `composeApp/src/commonTest/kotlin/ComposeAppCommonTest.kt`, `shared/src/commonTest/kotlin/SharedCommonTest.kt`, `server/src/test/kotlin/dev/ulfrx/recipe/ApplicationTest.kt` (all present from template) | +| **Quick run command** | `./gradlew :server:test :composeApp:jvmTest :shared:jvmTest` (JVM-only, <30s) | +| **Full suite command** | `./gradlew check` (runs `spotlessCheck` + every `*Test` task across all targets) | +| **Estimated runtime** | ~30s quick / ~3–5 min full (cold) | + +--- + +## Sampling Rate + +- **After every task commit:** `./gradlew spotlessCheck :server:test :shared:jvmTest` (fast subset, <30s) +- **After every plan wave:** `./gradlew build` (includes iOS framework link + Android APK) +- **Before `/gsd-verify-work` (phase gate):** `./gradlew check` + manual server `/health` curl + iOS simulator boot check +- **Max feedback latency:** 30s (quick subset) / 5 min (full) + +--- + +## Per-Task Verification Map + +**Note:** Task IDs are populated by `gsd-planner` when PLAN.md files are written. Each row below is the per-requirement contract the planner MUST map to at least one task's `` block. Rows marked "Wave 0" require a helper file to be created before task execution can verify it. + +| Behavior | Requirement | Test Type | Automated Command | File Exists | Status | +|----------|-------------|-----------|-------------------|-------------|--------| +| No version literals in any `build.gradle.kts` | INFRA-01 | shell grep | `tools/verify-no-version-literals.sh` | ❌ Wave 0 | ⬜ pending | +| `gradle/libs.versions.toml` is the single source of truth | INFRA-01 | grep | `grep -rE "libs\\.(versions\|plugins\|bundles)" build-logic/src/main/kotlin/` returns all version lookups | ✅ catalog exists | ⬜ pending | +| Convention plugins apply without duplication | INFRA-02 | Gradle | `./gradlew :composeApp:help :server:help :shared:help` shows `recipe.*` in applied plugins | ❌ Wave 0 (plugins don't exist yet) | ⬜ pending | +| Adding a new KMP module only needs `id("recipe.kotlin.multiplatform")` | INFRA-02 | visual | refactored `shared/build.gradle.kts` ≤15 LOC | Target Wave 2 | ⬜ pending | +| `gradle.properties` contains both iOS K/N flags | INFRA-03 | grep | `tools/verify-ios-flags.sh` | ❌ Wave 0 | ⬜ pending | +| iOS simulator build has no legacy memory-manager warnings | INFRA-03 | build-log | `./gradlew :composeApp:linkDebugFrameworkIosSimulatorArm64 --info 2>&1 \| grep -iE 'legacy\|freeze\|SharedImmutable'` is empty | Wave 2 (iOS) | ⬜ pending | +| `shared/commonMain` has no Ktor/Compose/SQLDelight imports | INFRA-06 | grep | `tools/verify-shared-pure.sh` | ❌ Wave 0 | ⬜ pending | +| `shared/` package scaffold exists | INFRA-06 | file | `test -d shared/src/commonMain/kotlin/dev/ulfrx/recipe/shared` | Wave 2 | ⬜ pending | +| SC1: `./gradlew build` succeeds + produces iOS framework + APK | ROADMAP SC1 | Gradle | `./gradlew build && test -f composeApp/build/outputs/apk/debug/composeApp-debug.apk && test -d composeApp/build/bin/iosSimulatorArm64/debugFramework/ComposeApp.framework` | Phase gate | ⬜ pending | +| SC4: each module's `help` shows its convention plugins | ROADMAP SC4 | Gradle | `./gradlew :composeApp:help -q \| grep 'recipe.kotlin.multiplatform'` etc. | Phase gate | ⬜ pending | +| Server `/health` returns 200 JSON `{"status":"ok"}` | D-16 | integration | `./gradlew :server:test --tests "*HealthRoute*"` (added to ApplicationTest.kt) | ❌ Wave 0 (test update) | ⬜ pending | +| Server fails loudly if Postgres unreachable | D-16 | manual | `docker compose down; ./gradlew :server:run` exits non-zero with "Database unreachable" in logs | Phase gate | ⬜ pending | +| Spotless formatting clean | D-10 | Gradle | `./gradlew spotlessCheck` | Per-commit | ⬜ pending | +| Koin starts without double-init | D-14 | Gradle test | `./gradlew :composeApp:jvmTest` (template test exercises App() composition path; no `KoinApplicationAlreadyStartedException`) | Per-wave | ⬜ pending | + +*Status: ⬜ pending · ✅ green · ❌ red · ⚠️ flaky* + +--- + +## Wave 0 Requirements + +These assets MUST exist before any verification task can run green. The planner should place them in Wave 0 (or inside the plan that creates the infrastructure they verify). + +- [ ] `tools/verify-no-version-literals.sh` — greps every `build.gradle.kts` + `build-logic/**/*.gradle.kts` for a non-test numeric version literal; exits non-zero on match +- [ ] `tools/verify-shared-pure.sh` — greps `shared/src/commonMain/` for forbidden imports (`io.ktor`, `androidx.compose`, `org.jetbrains.compose`, `app.cash.sqldelight`); exits non-zero on match +- [ ] `tools/verify-ios-flags.sh` — greps `gradle.properties` for `kotlin.native.binary.objcDisposeOnMain=false` AND `kotlin.native.binary.gc=cms`; exits non-zero if either is missing +- [ ] `build-logic/` scaffold — `settings.gradle.kts`, `build.gradle.kts`, and 5 `src/main/kotlin/recipe.*.gradle.kts` stubs +- [ ] `server/src/main/resources/application.conf` — HOCON with `ktor.deployment`, `database.url/user/password` using `${?X}` env overrides +- [ ] `server/src/main/resources/db/migration/.gitkeep` — directory placeholder for Flyway +- [ ] `docker-compose.yml` — `postgres:16` service with named volume + healthcheck +- [ ] `server/src/test/kotlin/dev/ulfrx/recipe/ApplicationTest.kt` — extended with `/health` endpoint assertion +- [ ] `composeApp/src/commonMain/kotlin/dev/ulfrx/recipe/di/Koin.kt` + `AppModule.kt` — `initKoin()` helper + empty module +- [ ] `composeApp/src/commonMain/kotlin/dev/ulfrx/recipe/logging/Logging.kt` — Kermit `setTag("recipe")` +- [ ] `composeApp/src/androidMain/kotlin/dev/ulfrx/recipe/MainApplication.kt` + `AndroidManifest.xml` registration — calls `initKoin { androidContext(this) }` +- [ ] `composeApp/src/iosMain/kotlin/dev/ulfrx/recipe/di/KoinIos.kt` — `fun doInitKoin()` exported for Swift +- [ ] `iosApp/iosApp/iOSApp.swift` — modified to call `KoinIosKt.doInitKoin()` in `init()` + +--- + +## Manual-Only Verifications + +| Behavior | Requirement | Why Manual | Test Instructions | +|----------|-------------|------------|-------------------| +| iOS simulator debug launch has no legacy K/N memory-manager warnings | INFRA-03 / SC3 | Requires Xcode simulator boot; not scriptable from Gradle reliably on CI | Run `./gradlew :composeApp:iosSimulatorArm64Test` OR open `iosApp.xcworkspace` in Xcode, run on iPhone 15 simulator, inspect console for `legacy`/`freeze`/`SharedImmutable` — expect none | +| Hot-reload dev loop on Desktop still works post-refactor (regression check for commit c50d747) | — | Interactive | `./gradlew :composeApp:jvmRun --mainClass MainKt --auto-reload`; edit `App.kt`, observe reload without rebuild | +| Server `/health` reachable via curl when Postgres up | D-16 | Requires running Postgres + server process | `docker compose up -d postgres`, `./gradlew :server:run &`, `sleep 5`, `curl -sf http://localhost:8080/health` returns `{"status":"ok"}` | + +--- + +## Validation Sign-Off + +- [ ] All tasks have `` verify or Wave 0 dependencies +- [ ] Sampling continuity: no 3 consecutive tasks without automated verify +- [ ] Wave 0 covers all MISSING references (13 items listed above) +- [ ] No watch-mode flags in any verification command +- [ ] Feedback latency < 30s (quick) / 5min (full) +- [ ] `nyquist_compliant: true` set in frontmatter after planner maps every task to a row above + +**Approval:** pending