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

102 lines
7.9 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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 / ~35 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 `<automated>` 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 `<automated>` 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