docs(02-01): complete shared auth contracts and Authentik setup plan
Adds the per-plan SUMMARY for 02-01: shared MeResponse/User DTOs + Constants, full Phase 2 dependency catalog wired into composeApp/ server without bumping Ktor 3.4.1, and docs/authentik-setup.md reproducible-provider playbook with multi-source audit. 3 tasks (Task 1 ran TDD: RED + GREEN), 4 commits, 6 deviations auto-fixed (5 × Rule 3 blocking, 1 × Rule 1 bug), 0 scope creep. All plan-level verifications PASS. Per parallel-execution rules, this commit does not modify STATE.md or ROADMAP.md — the orchestrator owns those updates after the wave completes.
This commit is contained in:
253
.planning/phases/02-authentication-foundation/02-01-SUMMARY.md
Normal file
253
.planning/phases/02-authentication-foundation/02-01-SUMMARY.md
Normal file
@@ -0,0 +1,253 @@
|
||||
---
|
||||
phase: 02-authentication-foundation
|
||||
plan: 01
|
||||
subsystem: auth
|
||||
tags: [oidc, authentik, kotlinx-serialization, kmp, ktor, gradle-version-catalog, cocoapods, appauth, exposed, testcontainers]
|
||||
|
||||
requires:
|
||||
- phase: 01-project-infrastructure-module-wiring
|
||||
provides: gradle version catalog, recipe.kotlin.multiplatform convention plugin, shared module scaffold (D-19), Koin/Kermit bootstrap, Ktor server skeleton with ContentNegotiation + Database.migrate, verify-shared-pure.sh + verify-no-version-literals.sh
|
||||
|
||||
provides:
|
||||
- dev.ulfrx.recipe.shared.Constants with OIDC_ISSUER (trailing slash), OIDC_CLIENT_ID = recipe-app, OIDC_REDIRECT_URI = recipe://callback, API_BASE_URL, SERVER_PORT (D-11)
|
||||
- dev.ulfrx.recipe.shared.dto.User (domain identity)
|
||||
- dev.ulfrx.recipe.shared.dto.MeResponse (@Serializable wire DTO with toUser() per D-27)
|
||||
- shared/build.gradle.kts wired with kotlinSerialization plugin and api(libs.kotlinx.serializationJson)
|
||||
- Phase 2 dependency aliases in gradle/libs.versions.toml (AppAuth, AndroidX Security Crypto, multiplatform-settings + coroutines, Exposed core/jdbc/java-time, HikariCP, Testcontainers postgresql + junit-jupiter, Ktor server auth/JWT/CallLogging/StatusPages, Ktor client core/auth/content-negotiation/logging/okhttp/darwin/cio + MPP serialization-kotlinx-json) without bumping Ktor (stays 3.4.1)
|
||||
- composeApp/build.gradle.kts with kotlinSerialization + kotlin.native.cocoapods applied, cocoapods block bringing AppAuth-iOS via libs.versions.appauth.ios.get(), Phase 2 commonMain/androidMain/iosMain/jvmMain dependency wiring, manifestPlaceholders["appAuthRedirectScheme"] = "recipe", and locked compose.resources packageOfResClass
|
||||
- server/build.gradle.kts with Ktor auth/JWT/CallLogging/StatusPages, Exposed DSL trio, Hikari, kotlinx.serialization-json, plus Testcontainers test dependencies
|
||||
- docs/authentik-setup.md — reproducible Authentik OIDC provider playbook (D-10) with mandatory sections Provider/Scopes/Redirect URI/Server Env Vars/Logout/Manual UAT/Source Audit, plus an exhaustive multi-source audit table mapping AUTH-01..AUTH-06, CONTEXT D-01..D-34, RESEARCH constraints, UI-SPEC, VALIDATION Wave 0, and PATTERNS file map to either this doc or a downstream Phase 2 plan, with all deferred ideas explicitly excluded
|
||||
|
||||
affects:
|
||||
- 02-02-PLAN.md (server JWT validation, JIT users, /api/v1/me — depends on shared MeResponse DTO + ktor server auth/jwt/exposed/hikari/testcontainers aliases)
|
||||
- 02-03-PLAN.md (common OIDC/store contracts — depends on Constants, multiplatform-settings, ktor client, Coroutines stack)
|
||||
- 02-04-PLAN.md (Android AppAuth actual + secure store — depends on libs.appauth + libs.androidx.security.crypto + manifestPlaceholders bootstrap)
|
||||
- 02-05-PLAN.md (iOS AppAuth actual — depends on cocoapods AppAuth pod + libs.ktor.clientDarwin)
|
||||
- 02-06-PLAN.md (LoginScreen/PostLoginPlaceholder UI — depends on MeResponse DTO and AuthSession contract from 02-03)
|
||||
- 02-07-PLAN.md (integration glue / phase verification — depends on every prior plan)
|
||||
|
||||
tech-stack:
|
||||
added:
|
||||
- kotlinx-serialization-json (api scope in shared/commonMain)
|
||||
- kotlinSerialization Gradle plugin on shared/composeApp/server (server already had it)
|
||||
- kotlin.native.cocoapods plugin on composeApp (applied by id; bundled with KGP)
|
||||
- AppAuth-Android (net.openid:appauth 0.11.1)
|
||||
- AppAuth-iOS (CocoaPod 2.0.0) — Gradle CocoaPods DSL pulls it via libs.versions.appauth.ios.get()
|
||||
- androidx.security:security-crypto 1.1.0 (Android secure AuthState store)
|
||||
- com.russhwolf:multiplatform-settings + multiplatform-settings-coroutines 1.3.0
|
||||
- Ktor client family (core/auth/content-negotiation/logging) + engines (okhttp Android, darwin iOS, cio JVM)
|
||||
- Ktor server auth + auth-jwt + call-logging + status-pages (3.4.1, no patch bump)
|
||||
- Exposed 0.55.0 (core + jdbc + java-time) and HikariCP 6.2.1
|
||||
- Testcontainers 1.21.4 (postgresql + junit-jupiter)
|
||||
patterns:
|
||||
- Shared DTO contract pattern — kotlinx.serialization @Serializable data class with explicit camelCase wire keys, decoded with ignoreUnknownKeys for forward compat (Phase 3 will add householdId)
|
||||
- Catalog-only version pinning — every Phase 2 dependency declared in gradle/libs.versions.toml; module build files reference libs.* only; verify-no-version-literals.sh enforces it
|
||||
- Cocoapods-via-catalog pattern — pod("AppAuth") { version = libs.versions.appauth.ios.get() } keeps the build script literal-free even with native Pod integration
|
||||
- Compose Resources package locking — explicit compose.resources { packageOfResClass = "..." } isolates UI code from build-script identity changes (group/version)
|
||||
- Authentik provider audit — markdown audit table that traces every locked source (REQ/CONTEXT/RESEARCH/UI-SPEC/VALIDATION/PATTERNS) to either an in-doc anchor or a downstream plan, with deferred ideas explicitly listed
|
||||
|
||||
key-files:
|
||||
created:
|
||||
- shared/src/commonMain/kotlin/dev/ulfrx/recipe/shared/Constants.kt
|
||||
- shared/src/commonMain/kotlin/dev/ulfrx/recipe/shared/dto/User.kt
|
||||
- shared/src/commonMain/kotlin/dev/ulfrx/recipe/shared/dto/MeResponse.kt
|
||||
- shared/src/commonTest/kotlin/dev/ulfrx/recipe/shared/dto/MeResponseSerializationTest.kt
|
||||
- docs/authentik-setup.md
|
||||
modified:
|
||||
- gradle/libs.versions.toml
|
||||
- shared/build.gradle.kts
|
||||
- composeApp/build.gradle.kts
|
||||
- server/build.gradle.kts
|
||||
- .gitignore (added *.podspec — generated by cocoapods plugin)
|
||||
|
||||
key-decisions:
|
||||
- "Apply kotlin.native.cocoapods plugin by id, not via libs.plugins alias — the plugin ships inside the Kotlin Gradle plugin already on the classpath via recipe.kotlin.multiplatform; aliasing it forces a duplicate version request that Gradle rejects with 'already on the classpath, compatibility cannot be checked'."
|
||||
- "Add manifestPlaceholders[\"appAuthRedirectScheme\"] = \"recipe\" to composeApp Android defaultConfig from Plan 02-01 (not Plan 02-04). AppAuth-Android's bundled manifest declares a ${appAuthRedirectScheme} placeholder that breaks AGP merge as soon as the dependency is on the classpath, even before any auth wiring. Setting it here is a Rule 3 prerequisite for the dependency to be cataloged."
|
||||
- "Lock compose.resources packageOfResClass to the Phase 1 historical name. Adding top-level group = \"dev.ulfrx.recipe\" (required by the cocoapods plugin's podspec generator) shifts the generated Res-class package from recipe.composeapp.generated.resources to dev.ulfrx.recipe.composeapp.generated.resources, breaking Phase 1 App.kt imports. Locking the package keeps the diff inside Plan 02-01's stated files."
|
||||
- "Ship a *.podspec gitignore entry. The Kotlin CocoaPods plugin regenerates composeApp/composeApp.podspec on every Gradle sync and that file legitimately contains 'AppAuth', '2.0.0' as a literal pin (CocoaPods semantics). Tracking it would either fail verify-no-version-literals.sh (if the verifier ever extends to *.podspec) or churn on every clean build."
|
||||
- "kotlinx.serialization-json declared as api(...) in shared/commonMain so consumers (composeApp, server) inherit the @Serializable runtime without each re-declaring it. shared/commonMain stays free of Ktor / Compose / SQLDelight / Koin / Kermit per D-19 / INFRA-06."
|
||||
- "Use the MPP variant of ktor-serialization-kotlinx-json for composeApp/commonMain (io.ktor:ktor-serialization-kotlinx-json) and keep the -jvm variant for the server module. Mixing variants between modules is the supported pattern; introducing a single MPP variant on the server breaks the existing ktor.serializationKotlinxJson alias used by the (jvm-only) server."
|
||||
- "Server-side OIDC config (OIDC_ISSUER / OIDC_AUDIENCE / OIDC_JWKS_URL) is documented as env-var-driven in docs/authentik-setup.md (D-12) but the actual application.conf wiring is deferred to Plan 02-02. Plan 02-01 establishes the contract; 02-02 implements it."
|
||||
|
||||
patterns-established:
|
||||
- "Shared DTO purity: shared/commonMain depends only on kotlin stdlib + kotlinx.serialization. Verified by ./tools/verify-shared-pure.sh."
|
||||
- "Catalog discipline: every library and plugin version lives in gradle/libs.versions.toml; Gradle artifact identity (group/version) is allowed at the top of module build files but library/plugin pins are not. Verified by ./tools/verify-no-version-literals.sh."
|
||||
- "TDD gate sequence: RED commit (test(02-01)) followed by GREEN commit (feat(02-01)) — the Phase 2 plans don't all use TDD but Plan 02-01's Task 1 sets the precedent for downstream auth plans."
|
||||
- "Multi-source audit pattern: docs/authentik-setup.md ## Source Audit table is the template. Future phase docs that span multiple sources (REQ + CONTEXT + RESEARCH + VALIDATION + PATTERNS) should mirror this structure so audits stay reproducible."
|
||||
|
||||
requirements-completed: []
|
||||
|
||||
duration: 16m
|
||||
completed: 2026-04-28
|
||||
---
|
||||
|
||||
# Phase 02 Plan 01: Shared Auth Contracts, Dependency Aliases, and Authentik Setup Summary
|
||||
|
||||
**Phase 2 foundation: shared MeResponse/User DTOs + Constants, full Phase 2 dependency catalog (AppAuth/Exposed/Testcontainers/Ktor auth) wired into composeApp/server without bumping Ktor 3.4.1, plus the docs/authentik-setup.md reproducible-provider playbook with multi-source audit.**
|
||||
|
||||
## Performance
|
||||
|
||||
- **Duration:** 16 min
|
||||
- **Started:** 2026-04-28T08:40:29Z
|
||||
- **Completed:** 2026-04-28T08:55:58Z
|
||||
- **Tasks:** 3 (Task 1 ran TDD: RED + GREEN)
|
||||
- **Files modified:** 9 (5 created, 4 modified)
|
||||
|
||||
## Accomplishments
|
||||
|
||||
- Locked the `/api/v1/me` wire contract: `MeResponse` is `@Serializable`, decodes with `ignoreUnknownKeys` so Phase 3 can add `householdId` without breaking Phase 2 clients, and round-trips through `toUser()` to a stable domain `User`.
|
||||
- Stood up `dev.ulfrx.recipe.shared.Constants` with `OIDC_ISSUER` (trailing slash placeholder host), `OIDC_CLIENT_ID = "recipe-app"`, `OIDC_REDIRECT_URI = "recipe://callback"`, plus `API_BASE_URL` and `SERVER_PORT` — the single config object every Phase 2 plan compiles against.
|
||||
- Cataloged every Phase 2 dependency (AppAuth Android + iOS pod, AndroidX Security Crypto, multiplatform-settings, Ktor client/server auth family, Exposed DSL trio, Hikari, Testcontainers) and wired them into composeApp/server without bumping Ktor off `3.4.1`. CocoaPods integration brings AppAuth-iOS via `libs.versions.appauth.ios.get()` so no version literals leak into build files (`./tools/verify-no-version-literals.sh` stays green).
|
||||
- Shipped `docs/authentik-setup.md` as a 240-line reproducible Authentik provider playbook covering Provider, Scopes, Redirect URI, Server Env Vars, Logout, Manual UAT (UAT-01..UAT-04), and a Source Audit table that traces every Phase 2 input (GOAL, AUTH-01..AUTH-06, CONTEXT D-01..D-34, RESEARCH, UI-SPEC, VALIDATION Wave 0, PATTERNS) to either this doc or a downstream Phase 2 plan, with all deferred ideas explicitly excluded.
|
||||
|
||||
## Task Commits
|
||||
|
||||
1. **Task 1 RED — Failing serialization test for MeResponse DTO** — `6504b46` (test)
|
||||
2. **Task 1 GREEN — Constants and MeResponse/User DTOs in shared** — `7e73a9a` (feat)
|
||||
3. **Task 2 — Phase 2 dependency aliases without bumping Ktor** — `c1cc713` (feat)
|
||||
4. **Task 3 — Authentik provider setup and Phase 2 source audit** — `62040d4` (docs)
|
||||
|
||||
_Note: TDD task 1 produced two commits (RED then GREEN); no REFACTOR commit was needed because the GREEN implementation is already minimal._
|
||||
|
||||
## Files Created/Modified
|
||||
|
||||
- `shared/src/commonMain/kotlin/dev/ulfrx/recipe/shared/Constants.kt` — created. OIDC + API config object (D-11). Trailing-slash issuer, exact `recipe://callback` redirect URI, `recipe-app` client id (also `aud` per D-07).
|
||||
- `shared/src/commonMain/kotlin/dev/ulfrx/recipe/shared/dto/User.kt` — created. Domain identity DTO; id is `String` (server UUID) so shared/commonMain stays free of UUID library deps.
|
||||
- `shared/src/commonMain/kotlin/dev/ulfrx/recipe/shared/dto/MeResponse.kt` — created. `@Serializable` wire DTO for `GET /api/v1/me` (D-27). One-to-one `toUser()` mapper. Forward-compatible with Phase 3 `householdId` via `ignoreUnknownKeys` decoders.
|
||||
- `shared/src/commonTest/kotlin/dev/ulfrx/recipe/shared/dto/MeResponseSerializationTest.kt` — created. Three-test contract: round-trip via camelCase wire keys, Phase 3 forward-compat decode, `toUser()` no-data-loss mapping.
|
||||
- `shared/build.gradle.kts` — modified. Applied `alias(libs.plugins.kotlinSerialization)`; added `api(libs.kotlinx.serializationJson)` so consumers inherit the runtime; `shared/commonMain` purity preserved (still no Ktor/Compose/SQLDelight/Koin/Kermit imports).
|
||||
- `gradle/libs.versions.toml` — modified. Added Phase 2 versions/libraries/plugins per D-13/D-26/research; Ktor stays at 3.4.1.
|
||||
- `composeApp/build.gradle.kts` — modified. Added kotlinSerialization + kotlin.native.cocoapods plugins; cocoapods block (AppAuth pod via catalog version); per-source-set Phase 2 deps; manifestPlaceholders for AppAuth-Android scheme; `compose.resources.packageOfResClass` lock to keep Phase 1 App.kt imports valid.
|
||||
- `server/build.gradle.kts` — modified. Added Ktor server auth/JWT/CallLogging/StatusPages, Exposed core/jdbc/java-time, HikariCP, kotlinx.serialization-json, plus Testcontainers postgresql + junit-jupiter test deps.
|
||||
- `docs/authentik-setup.md` — created. Reproducible Authentik playbook + Phase 2 source audit (D-10).
|
||||
- `.gitignore` — modified. Ignore `*.podspec` (regenerated on every Gradle sync by the cocoapods plugin).
|
||||
|
||||
## Decisions Made
|
||||
|
||||
See frontmatter `key-decisions` for the load-bearing list. Highlights:
|
||||
|
||||
- **kotlin.native.cocoapods applied by id, not by alias** — the plugin ships inside KGP already on the classpath via `recipe.kotlin.multiplatform`, so a `libs.plugins.kotlinCocoapods` alias triggers a duplicate-version-request error.
|
||||
- **manifestPlaceholders["appAuthRedirectScheme"] = "recipe"** lands in this plan, not 02-04 — it's a Rule 3 prerequisite for the AppAuth dependency to be cataloged at all.
|
||||
- **`compose.resources.packageOfResClass` locked to Phase 1's historical package** — adding `group = "dev.ulfrx.recipe"` (mandatory for the cocoapods podspec generator) would otherwise rewrite the generated `Res` package and break `App.kt` imports.
|
||||
- **Ktor stays at 3.4.1** — Open Question resolved during planning; auth artifacts catalog against the same `version.ref = "ktor"`. Patch bump deferred unless a concrete incompatibility appears.
|
||||
- **Server OIDC config wiring deferred to Plan 02-02** — Plan 02-01 documents the env-var contract in `docs/authentik-setup.md` (`OIDC_ISSUER`, `OIDC_AUDIENCE`, `OIDC_JWKS_URL`); Plan 02-02 implements it in `application.conf`.
|
||||
|
||||
## Deviations from Plan
|
||||
|
||||
### Auto-fixed Issues
|
||||
|
||||
**1. [Rule 3 - Blocking] Apply kotlin.native.cocoapods by id, not via `alias(libs.plugins.kotlinCocoapods)`**
|
||||
|
||||
- **Found during:** Task 2 verification (`:composeApp:dependencies`)
|
||||
- **Issue:** `Error resolving plugin [id: 'org.jetbrains.kotlin.native.cocoapods', version: '2.3.20']: The request for this plugin could not be satisfied because the plugin is already on the classpath with an unknown version, so compatibility cannot be checked.` The cocoapods plugin ships bundled with the Kotlin Gradle plugin classpath that `recipe.kotlin.multiplatform` already brings in.
|
||||
- **Fix:** Apply via `id("org.jetbrains.kotlin.native.cocoapods")` (no version) inside the `plugins { ... }` block of `composeApp/build.gradle.kts`. Kept the `kotlinCocoapods` alias in `gradle/libs.versions.toml` per the plan's stated catalog additions; downstream plans can still reference `libs.versions.kotlinCocoapods.version` if they ever need the version programmatically.
|
||||
- **Files modified:** composeApp/build.gradle.kts
|
||||
- **Verification:** `:composeApp:dependencies` and `:composeApp:compileKotlinIosSimulatorArm64` (with `cinteropAppAuthIosSimulatorArm64`) now pass.
|
||||
- **Committed in:** `c1cc713`
|
||||
|
||||
**2. [Rule 3 - Blocking] Add `manifestPlaceholders["appAuthRedirectScheme"] = "recipe"` to composeApp Android defaultConfig**
|
||||
|
||||
- **Found during:** Task 2 (`:composeApp:compileDebugKotlinAndroid`)
|
||||
- **Issue:** `Manifest merger failed : Attribute data@scheme at AndroidManifest.xml requires a placeholder substitution but no value for <appAuthRedirectScheme> is provided.` AppAuth-Android's bundled manifest declares an unsubstituted `${appAuthRedirectScheme}` placeholder that breaks AGP's merger as soon as the dependency is on the classpath, even before Plan 02-04 wires the full `<intent-filter>`.
|
||||
- **Fix:** Added the placeholder to `defaultConfig.manifestPlaceholders`. Value is `"recipe"`, byte-for-byte consistent with `Constants.OIDC_REDIRECT_URI = "recipe://callback"`. Plan 02-04 will still land the explicit `<intent-filter>` in the Android manifest; this placeholder satisfies AppAuth's *built-in* manifest entry until then.
|
||||
- **Files modified:** composeApp/build.gradle.kts
|
||||
- **Verification:** `:composeApp:compileDebugKotlinAndroid` now passes.
|
||||
- **Committed in:** `c1cc713`
|
||||
|
||||
**3. [Rule 3 - Blocking] Lock `compose.resources.packageOfResClass` to the Phase 1 historical package**
|
||||
|
||||
- **Found during:** Task 2 (`:composeApp:compileDebugKotlinAndroid`, after the AppAuth manifest fix)
|
||||
- **Issue:** Adding `group = "dev.ulfrx.recipe"` and `version = "1.0.0"` at the top of `composeApp/build.gradle.kts` (mandatory for the Kotlin CocoaPods plugin's podspec generator — `cocoapods` requires `project.version` if the block doesn't override it) shifted the Compose Resources `Res` class generated package from `recipe.composeapp.generated.resources` (Phase 1) to `dev.ulfrx.recipe.composeapp.generated.resources`, breaking `App.kt`'s `import recipe.composeapp.generated.resources.Res` and `compose_multiplatform`.
|
||||
- **Fix:** Added an explicit `compose.resources { packageOfResClass = "recipe.composeapp.generated.resources" }` block to `composeApp/build.gradle.kts`, locking the generated package to the Phase 1 name regardless of `group`. This keeps Plan 02-01's diff inside its stated files; Plan 02-06 will replace `App.kt`'s template body with the real auth gate (D-30) and can choose to migrate the package then.
|
||||
- **Files modified:** composeApp/build.gradle.kts
|
||||
- **Verification:** `:composeApp:compileDebugKotlinAndroid` now passes; `App.kt` imports unchanged.
|
||||
- **Committed in:** `c1cc713`
|
||||
|
||||
**4. [Rule 3 - Housekeeping] Ignore `*.podspec` files generated by the cocoapods plugin**
|
||||
|
||||
- **Found during:** Task 2 (`git status` after first cocoapods Gradle invocation)
|
||||
- **Issue:** Adding the cocoapods plugin causes `composeApp/composeApp.podspec` to be regenerated on every Gradle sync. The file legitimately embeds `'AppAuth', '2.0.0'` as a literal CocoaPods version pin (CocoaPods Ruby DSL semantics), which would either fail `./tools/verify-no-version-literals.sh` if it ever extended to `.podspec` or churn on every clean build.
|
||||
- **Fix:** Added `*.podspec` to `.gitignore` with an explanatory comment.
|
||||
- **Files modified:** .gitignore
|
||||
- **Verification:** `git status --short` shows no untracked `composeApp.podspec`.
|
||||
- **Committed in:** `c1cc713`
|
||||
|
||||
**5. [Rule 1 - Bug] Strip "version = \"2.0.0\"" substring from a comment in composeApp/build.gradle.kts**
|
||||
|
||||
- **Found during:** Task 2 (`./tools/verify-no-version-literals.sh`)
|
||||
- **Issue:** A comment paraphrased the Plan 02-01 acceptance criterion using the literal text `version = "2.0.0"`. The verifier doesn't distinguish comments from code and flagged the line. The plan's `! grep -q 'version = "2.0.0"' composeApp/build.gradle.kts` acceptance criterion ALSO reads from comments, so the comment was fundamentally incompatible with the rule.
|
||||
- **Fix:** Rewrote the comment to describe the rule without quoting the forbidden literal pattern.
|
||||
- **Files modified:** composeApp/build.gradle.kts
|
||||
- **Verification:** `./tools/verify-no-version-literals.sh` exits 0; both `! grep` acceptance criteria now hold.
|
||||
- **Committed in:** `c1cc713`
|
||||
|
||||
**6. [Rule 1 - Bug] Use `debugCompileClasspath` instead of nonexistent `androidMainCompileClasspath` in Task 2 verify command**
|
||||
|
||||
- **Found during:** Task 2 (`:composeApp:dependencies --configuration androidMainCompileClasspath`)
|
||||
- **Issue:** Plan 02-01 Task 2 specifies `--configuration androidMainCompileClasspath`, but under the current AGP/Gradle/Kotlin combination the actual configuration name is `debugCompileClasspath` (or `releaseCompileClasspath`). The plan's command name doesn't exist in the configuration container.
|
||||
- **Fix:** Ran the functionally equivalent command (`./gradlew :composeApp:dependencies --configuration debugCompileClasspath :server:dependencies --configuration runtimeClasspath`) which resolves the same Phase 2 deps the plan was checking for. Documented in the Task 2 commit message so a future planner can update the plan if needed.
|
||||
- **Files modified:** none — this is a verification-command rename, not a code change.
|
||||
- **Verification:** Both classpaths resolve cleanly and contain every Phase 2 dep (AppAuth, AndroidX Security Crypto, Ktor client family, Exposed core/jdbc/java-time, Hikari, Ktor server auth-jwt/call-logging/status-pages, Testcontainers).
|
||||
- **Committed in:** N/A (procedural; no code change)
|
||||
|
||||
---
|
||||
|
||||
**Total deviations:** 6 auto-fixed (5 × Rule 3 blocking, 1 × Rule 1 bug, 1 × procedural rename).
|
||||
**Impact on plan:** All deviations were unavoidable consequences of cataloging the AppAuth/CocoaPods dependency stack and integrating it with Phase 1's existing build setup. Net diff stays inside Plan 02-01's `files_modified` frontmatter list (plus `.gitignore`, which is a build-hygiene artifact). Zero scope creep into Plan 02-02..02-07.
|
||||
|
||||
## Issues Encountered
|
||||
|
||||
- **STATE.md drift from orchestrator init.** Running `gsd-sdk query init.execute-phase` at agent start mutated `.planning/STATE.md` (advanced `current_plan: 0 → 1`, status `planned → executing`). Per the parallel-execution rules, worktree agents must not modify `STATE.md`; the orchestrator owns those writes. Reverted via `git checkout -- .planning/STATE.md` before staging the first commit so the orchestrator's later state update is the single source of truth. No follow-up needed.
|
||||
|
||||
## User Setup Required
|
||||
|
||||
None — Plan 02-01 is wiring + docs only. The `docs/authentik-setup.md` Manual UAT section documents what the user will need to configure in Authentik before Plan 02-02..02-07 can be exercised end-to-end, but Plan 02-01 itself doesn't require any external service interaction.
|
||||
|
||||
## Next Phase Readiness
|
||||
|
||||
- **Plan 02-02 (server JWT validation, JIT users, /api/v1/me)** — All catalog dependencies and DTOs are in place. `MeResponse` DTO is importable from `dev.ulfrx.recipe.shared.dto`; Ktor server auth/JWT/CallLogging/StatusPages, Exposed DSL trio, Hikari, and Testcontainers are wired into `server/build.gradle.kts`. `application.conf` env-var contract is documented in `docs/authentik-setup.md` § Server Env Vars; Plan 02-02 implements it.
|
||||
- **Plan 02-03 (common OIDC/store contracts, JVM/Wasm actuals)** — `Constants` and `multiplatform-settings` (+ coroutines) are available in `shared`/`composeApp/commonMain`. Ktor client core/auth/content-negotiation/logging are wired into commonMain.
|
||||
- **Plan 02-04 (Android AppAuth actual + secure store)** — `libs.appauth` and `libs.androidx.security.crypto` are wired into `androidMain`. The `appAuthRedirectScheme=recipe` manifest placeholder is already set; Plan 02-04 only needs to add the explicit `<intent-filter>` and the `RedirectUriReceiverActivity` registration.
|
||||
- **Plan 02-05 (iOS AppAuth actual)** — The cocoapods block is configured with the `AppAuth` pod at the catalog version; `libs.ktor.clientDarwin` is in `iosMain` deps. The `Info.plist` `CFBundleURLTypes` registration is the remaining iOS step.
|
||||
- **Plan 02-06 (UI: SplashScreen / LoginScreen / PostLoginPlaceholderScreen)** — No blockers; Compose Resources package is locked to the Phase 1 historical name so existing `App.kt` keeps compiling. Plan 02-06 will replace `App.kt`'s template body with the auth gate (D-30) and optionally migrate the resources package then.
|
||||
- **Plan 02-07 (integration glue / phase verification)** — All Phase 2 source files in `02-VALIDATION.md` Wave 0 will exist by the end of 02-02..02-06; this plan establishes the catalog and DTOs they depend on.
|
||||
|
||||
**No outstanding blockers.** Phase 2's per-plan execution can proceed.
|
||||
|
||||
## Self-Check: PASSED
|
||||
|
||||
- Created files exist:
|
||||
- `shared/src/commonMain/kotlin/dev/ulfrx/recipe/shared/Constants.kt` — FOUND
|
||||
- `shared/src/commonMain/kotlin/dev/ulfrx/recipe/shared/dto/User.kt` — FOUND
|
||||
- `shared/src/commonMain/kotlin/dev/ulfrx/recipe/shared/dto/MeResponse.kt` — FOUND
|
||||
- `shared/src/commonTest/kotlin/dev/ulfrx/recipe/shared/dto/MeResponseSerializationTest.kt` — FOUND
|
||||
- `docs/authentik-setup.md` — FOUND
|
||||
- Modified files reflect intended changes:
|
||||
- `gradle/libs.versions.toml` — FOUND (Phase 2 aliases present)
|
||||
- `shared/build.gradle.kts` — FOUND (kotlinSerialization plugin + api(libs.kotlinx.serializationJson))
|
||||
- `composeApp/build.gradle.kts` — FOUND (cocoapods + Phase 2 deps)
|
||||
- `server/build.gradle.kts` — FOUND (Ktor auth/JWT, Exposed, Hikari, Testcontainers)
|
||||
- `.gitignore` — FOUND (`*.podspec` ignore)
|
||||
- Commits exist:
|
||||
- `6504b46` (test RED) — FOUND
|
||||
- `7e73a9a` (feat GREEN) — FOUND
|
||||
- `c1cc713` (Task 2 wiring) — FOUND
|
||||
- `62040d4` (Task 3 docs) — FOUND
|
||||
- Plan-level verification:
|
||||
- `./gradlew :shared:jvmTest :shared:compileCommonMainKotlinMetadata` — PASS
|
||||
- `./tools/verify-shared-pure.sh` — PASS
|
||||
- `./tools/verify-no-version-literals.sh` — PASS
|
||||
- `./gradlew :composeApp:compileDebugKotlinAndroid :server:compileKotlin` — PASS
|
||||
- `./gradlew :composeApp:compileKotlinIosSimulatorArm64` — PASS (`cinteropAppAuthIosSimulatorArm64` exercised the AppAuth pod end-to-end)
|
||||
|
||||
## TDD Gate Compliance
|
||||
|
||||
Plan 02-01 frontmatter has `type: execute` (not `type: tdd`), so plan-level RED/GREEN/REFACTOR enforcement does not apply. However, Task 1 was tagged `tdd="true"` and produced the expected gate sequence inside its scope:
|
||||
|
||||
- RED: `6504b46` (`test(02-01): add failing serialization test for MeResponse DTO`) — confirmed failing on `MeResponse` / `User` unresolved references.
|
||||
- GREEN: `7e73a9a` (`feat(02-01): land Constants and MeResponse/User DTOs in shared`) — test now passes.
|
||||
- REFACTOR: omitted; the GREEN implementation is already minimal.
|
||||
|
||||
---
|
||||
*Phase: 02-authentication-foundation*
|
||||
*Completed: 2026-04-28*
|
||||
Reference in New Issue
Block a user