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.
26 KiB
phase, plan, subsystem, tags, requires, provides, affects, tech-stack, key-files, key-decisions, patterns-established, requirements-completed, duration, completed
| phase | plan | subsystem | tags | requires | provides | affects | tech-stack | key-files | key-decisions | patterns-established | requirements-completed | duration | completed | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 02-authentication-foundation | 01 | auth |
|
|
|
|
|
|
|
|
16m | 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/mewire contract:MeResponseis@Serializable, decodes withignoreUnknownKeysso Phase 3 can addhouseholdIdwithout breaking Phase 2 clients, and round-trips throughtoUser()to a stable domainUser. - Stood up
dev.ulfrx.recipe.shared.ConstantswithOIDC_ISSUER(trailing slash placeholder host),OIDC_CLIENT_ID = "recipe-app",OIDC_REDIRECT_URI = "recipe://callback", plusAPI_BASE_URLandSERVER_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 vialibs.versions.appauth.ios.get()so no version literals leak into build files (./tools/verify-no-version-literals.shstays green). - Shipped
docs/authentik-setup.mdas 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
- Task 1 RED — Failing serialization test for MeResponse DTO —
6504b46(test) - Task 1 GREEN — Constants and MeResponse/User DTOs in shared —
7e73a9a(feat) - Task 2 — Phase 2 dependency aliases without bumping Ktor —
c1cc713(feat) - 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, exactrecipe://callbackredirect URI,recipe-appclient id (alsoaudper D-07).shared/src/commonMain/kotlin/dev/ulfrx/recipe/shared/dto/User.kt— created. Domain identity DTO; id isString(server UUID) so shared/commonMain stays free of UUID library deps.shared/src/commonMain/kotlin/dev/ulfrx/recipe/shared/dto/MeResponse.kt— created.@Serializablewire DTO forGET /api/v1/me(D-27). One-to-onetoUser()mapper. Forward-compatible with Phase 3householdIdviaignoreUnknownKeysdecoders.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. Appliedalias(libs.plugins.kotlinSerialization); addedapi(libs.kotlinx.serializationJson)so consumers inherit the runtime;shared/commonMainpurity 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.packageOfResClasslock 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 alibs.plugins.kotlinCocoapodsalias 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.packageOfResClasslocked to Phase 1's historical package — addinggroup = "dev.ulfrx.recipe"(mandatory for the cocoapods podspec generator) would otherwise rewrite the generatedRespackage and breakApp.ktimports.- 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 inapplication.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 thatrecipe.kotlin.multiplatformalready brings in. - Fix: Apply via
id("org.jetbrains.kotlin.native.cocoapods")(no version) inside theplugins { ... }block ofcomposeApp/build.gradle.kts. Kept thekotlinCocoapodsalias ingradle/libs.versions.tomlper the plan's stated catalog additions; downstream plans can still referencelibs.versions.kotlinCocoapods.versionif they ever need the version programmatically. - Files modified: composeApp/build.gradle.kts
- Verification:
:composeApp:dependenciesand:composeApp:compileKotlinIosSimulatorArm64(withcinteropAppAuthIosSimulatorArm64) 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 withConstants.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:compileDebugKotlinAndroidnow 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"andversion = "1.0.0"at the top ofcomposeApp/build.gradle.kts(mandatory for the Kotlin CocoaPods plugin's podspec generator —cocoapodsrequiresproject.versionif the block doesn't override it) shifted the Compose ResourcesResclass generated package fromrecipe.composeapp.generated.resources(Phase 1) todev.ulfrx.recipe.composeapp.generated.resources, breakingApp.kt'simport recipe.composeapp.generated.resources.Resandcompose_multiplatform. - Fix: Added an explicit
compose.resources { packageOfResClass = "recipe.composeapp.generated.resources" }block tocomposeApp/build.gradle.kts, locking the generated package to the Phase 1 name regardless ofgroup. This keeps Plan 02-01's diff inside its stated files; Plan 02-06 will replaceApp.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:compileDebugKotlinAndroidnow passes;App.ktimports unchanged. - Committed in:
c1cc713
4. [Rule 3 - Housekeeping] Ignore *.podspec files generated by the cocoapods plugin
- Found during: Task 2 (
git statusafter first cocoapods Gradle invocation) - Issue: Adding the cocoapods plugin causes
composeApp/composeApp.podspecto 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.shif it ever extended to.podspecor churn on every clean build. - Fix: Added
*.podspecto.gitignorewith an explanatory comment. - Files modified: .gitignore
- Verification:
git status --shortshows no untrackedcomposeApp.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.ktsacceptance 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.shexits 0; both! grepacceptance 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 isdebugCompileClasspath(orreleaseCompileClasspath). 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-phaseat agent start mutated.planning/STATE.md(advancedcurrent_plan: 0 → 1, statusplanned → executing). Per the parallel-execution rules, worktree agents must not modifySTATE.md; the orchestrator owns those writes. Reverted viagit checkout -- .planning/STATE.mdbefore 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.
MeResponseDTO is importable fromdev.ulfrx.recipe.shared.dto; Ktor server auth/JWT/CallLogging/StatusPages, Exposed DSL trio, Hikari, and Testcontainers are wired intoserver/build.gradle.kts.application.confenv-var contract is documented indocs/authentik-setup.md§ Server Env Vars; Plan 02-02 implements it. - Plan 02-03 (common OIDC/store contracts, JVM/Wasm actuals) —
Constantsandmultiplatform-settings(+ coroutines) are available inshared/composeApp/commonMain. Ktor client core/auth/content-negotiation/logging are wired into commonMain. - Plan 02-04 (Android AppAuth actual + secure store) —
libs.appauthandlibs.androidx.security.cryptoare wired intoandroidMain. TheappAuthRedirectScheme=recipemanifest placeholder is already set; Plan 02-04 only needs to add the explicit<intent-filter>and theRedirectUriReceiverActivityregistration. - Plan 02-05 (iOS AppAuth actual) — The cocoapods block is configured with the
AppAuthpod at the catalog version;libs.ktor.clientDarwinis iniosMaindeps. TheInfo.plistCFBundleURLTypesregistration 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.ktkeeps compiling. Plan 02-06 will replaceApp.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.mdWave 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— FOUNDshared/src/commonMain/kotlin/dev/ulfrx/recipe/shared/dto/User.kt— FOUNDshared/src/commonMain/kotlin/dev/ulfrx/recipe/shared/dto/MeResponse.kt— FOUNDshared/src/commonTest/kotlin/dev/ulfrx/recipe/shared/dto/MeResponseSerializationTest.kt— FOUNDdocs/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 (*.podspecignore)
- Commits exist:
6504b46(test RED) — FOUND7e73a9a(feat GREEN) — FOUNDc1cc713(Task 2 wiring) — FOUND62040d4(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 (cinteropAppAuthIosSimulatorArm64exercised 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 onMeResponse/Userunresolved 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