Skip to content

IAM/Profile Cutover Hardening Task

Agent workflow: follow README.md for Audit -> Investigate -> Code -> Test -> Fix; keep this pack's writable scope and verification commands authoritative.

Dispatch type: orchestrator

Owner: orchestrator with iam-service, profile-service, api-gateway, and migration owners.

Problem:

The native IAM/Profile foundation exists, but pasted architecture requirements still have cutover-grade gaps: production-like populated target validation, default-route proof, browser proof, final text-id cleanup, and service-local security hardening. These must be handled explicitly before claiming the auth-service to iam-service transition is production-ready.

Goal:

Turn the remaining IAM/Profile cutover risks into small, verifiable slices without reintroducing auth-service, broad user-service, cross-service DB reads, or frontend calls to service hostnames.

Legacy source evidence:

  • /Users/velikho/Desktop/WORKING/HOCTAPAZ/node-platform/apps/api/src/modules/auth/**
  • /Users/velikho/Desktop/WORKING/HOCTAPAZ/node-platform/apps/api/src/modules/app-data/**
  • /Users/velikho/Desktop/WORKING/HOCTAPAZ/node-platform/apps/api/prisma/schema.prisma
  • frontend auth/profile callers under /Users/velikho/Desktop/WORKING/HOCTAPAZ/node-platform/apps/web.

Writable files:

  • services/iam-service/**
  • services/profile-service/**
  • services/api-gateway/** only for non-default rehearsal/cutover routes
  • contracts/openapi/services/iam-service.yaml
  • contracts/openapi/services/profile-service.yaml
  • services/iam-service/api/openapi.yaml
  • services/profile-service/api/openapi.yaml
  • docs/api/iam-service.md
  • docs/api/profile-service.md
  • docs/microservices/iam-profile-*.md
  • docs/migration/**
  • docs/agents/service-tasks/iam-profile-cutover-hardening.md

Orchestrator-owned files:

  • Default gateway route table, root Makefile, Compose, Helm, K8s manifests, migration job lists, OpenAPI index pages, VitePress navigation, and final services/user-service retirement/removal.

Tasks:

  • Completed first profile indexing slice: 000004_profile_query_indexes.sql adds explicit account-id, display-name, and phone lookup indexes for current text-compatible API storage. Newly generated IAM/Profile ids are UUIDv4 strings, and the promoted target schemas use UUID-backed PostgreSQL primary keys while preserving text ids for route/API compatibility.
  • Completed first IAM service-local management guard slice: account directory, role, permission, organization membership, access-scope, invite create/revoke, and audit list routes now require an ADMIN bearer token in iam-service itself. HTTP regression coverage proves unauthenticated 401, non-admin 403, and admin 200 behavior while public auth/JWKS/invite-accept routes remain open as designed.
  • Completed first IAM account/profile boundary guard: PATCH /v1/accounts/{id} has an explicit OpenAPI request body and HTTP regression coverage proving profile-owned fields such as avatarUrl are rejected instead of becoming IAM account data. fullName remains documented as temporary legacy session compatibility material until profile/gateway cutover.
  • Completed first IAM password reset/change parity slice: native POST /v1/auth/me/password, POST /v1/auth/forgot-password, and POST /v1/auth/reset-password now cover current-password validation, neutral forgot responses, one active hashed reset token per account, single-use reset, refresh/session revocation, and audit/security events without raw credential material. Default /api/auth/* route promotion remains pending gateway proof.
  • Completed first IAM Google provider-link parity slice: native GET /v1/auth/google/config, POST /v1/auth/google, and admin-only GET/PATCH /v1/admin/auth-settings/google now cover env/DB settings precedence, Google ID-token verification, allowed-domain policy, existing provider login, email auto-link, register-mode account creation, provider-link storage, and audit/security events without raw Google credentials or client id. Non-default gateway route-table rehearsal now covers /api/auth/google*, /api/auth/forgot-password, /api/auth/reset-password, /api/auth/me/password, and /api/admin/auth-settings/google; default route promotion and browser proof remain pending.
  • Completed first Profile OpenAPI parity slice: service and mirrored contract schemas now cover full profile, public profile, base update, compatibility profile routes, student/teacher/parent/staff profile request/response envelopes, staff display metadata, and redacted TeacherKycSafe. A static profile-service test keeps the mirror and required schemas from regressing.
  • Completed first IAM/Profile deploy-bootstrap slice: local Compose now persists Postgres data, runs IAM/Profile schema migrations before the services start, uses /readyz healthchecks for distroless service images, and connects IAM/Profile runtime plus migrations through service-owned database roles. Static offline K8s and Helm now render Postgres init scripts, a postgres-local-data PVC, IAM/Profile role DSNs, and migration completion waits. Live clean-cluster runtime proof is now covered by the clean Compose and local kind smoke runs below.
  • Completed named IAM/Profile runtime proof guard: make test-iam-profile-runtime-proof checks the Compose IAM/Profile migration-before-service dependency, service-owned DSNs, distroless readychecks, profile IAM_JWKS_URL, K8s offline migration/service manifests, Postgres init/persistence, and Helm-rendered IAM/Profile migration/runtime wiring. This is static/render proof only; live runtime proof is covered by the opt-in clean Compose and local K8s smokes below.
  • Added opt-in live clean Compose smoke target: make test-iam-profile-clean-compose runs scripts/test/iam-profile-clean-compose-smoke.sh. It requires IAM_PROFILE_CLEAN_COMPOSE_CONFIRM=clean-runtime, creates an isolated Compose project, starts only Postgres, IAM/Profile migrations, iam-service, and profile-service, verifies migration container success, schema_migrations rows for each IAM/Profile SQL file, Postgres health, /healthz, /readyz, and IAM JWKS over real HTTP, then tears the project down with volumes by default. The live smoke preflights host ports 5433, 8081, and 8082 by default and supports IAM_PROFILE_HOST_POSTGRES_PORT, IAM_PROFILE_HOST_IAM_PORT, and IAM_PROFILE_HOST_PROFILE_PORT overrides; do not stop another developer stack just to run it. If service images are already present locally, set IAM_PROFILE_CLEAN_COMPOSE_USE_LOCAL_IMAGES=1 to use IAM_PROFILE_IAM_IMAGE, IAM_PROFILE_PROFILE_IMAGE, and a generated IAM_PROFILE_MIGRATOR_IMAGE with Docker Compose --no-build; set IAM_PROFILE_REBUILD_MIGRATOR_IMAGE=1 when migration files changed. A self-test path exists for CI syntax/fixture coverage. Running this target successfully is the clean Compose runtime proof.
  • Added opt-in local/offline Kubernetes smoke target: make test-iam-profile-local-k8s runs scripts/test/iam-profile-local-k8s-smoke.sh. It requires IAM_PROFILE_K8S_LIVE_CONFIRM=local-k8s, applies the two-service local IAM/Profile manifest into an isolated namespace, runs temporary migrate-iam-service and migrate-profile-service Jobs with the local migrator image, verifies schema_migrations rows inside each service-owned Postgres StatefulSet, restarts IAM/Profile deployments, checks /healthz, /readyz, and IAM JWKS through kubectl port-forward, then deletes the namespace by default. This is the live local K8s runtime proof path; it still has to be run against a real local cluster with images already loaded before default route cutover can use it as evidence. It refuses non-local contexts unless IAM_PROFILE_K8S_ALLOW_REMOTE=1 is set deliberately.
  • Local K8s live proof refreshed on 2026-07-08 after the IAM 13-file/Profile 10-file readiness/FK/PK-promotion migrations with kind-hoctapaz-go, rebuilt and loaded hoctapaz/iam-service:local, hoctapaz/profile-service:local, and hoctapaz/migrator:local, then IAM_PROFILE_K8S_LIVE_CONFIRM=local-k8s IAM_PROFILE_K8S_NAMESPACE=hoctapaz-iam-profile-uuidpk2-20260708 IAM_PROFILE_K8S_TIMEOUT=300s IAM_PROFILE_K8S_IAM_PORT=58191 IAM_PROFILE_K8S_PROFILE_PORT=58192 make test-iam-profile-local-k8s through a temporary kubeconfig set to kind-hoctapaz-go. It verified IAM/Profile migration Jobs, service-owned schema_migrations counts for the current IAM/Profile migration sets, service /healthz//readyz, IAM JWKS over kubectl port-forward, and namespace cleanup.
  • Clean Compose live proof refreshed on 2026-07-08 after the IAM 13-file/Profile 10-file readiness/FK/PK-promotion migrations with IAM_PROFILE_CLEAN_COMPOSE_CONFIRM=clean-runtime, IAM_PROFILE_HOST_POSTGRES_PORT=55433, IAM_PROFILE_HOST_IAM_PORT=58081, and IAM_PROFILE_HOST_PROFILE_PORT=58082. It rebuilt IAM/Profile/migrator images for Compose project hoctapaz-iam-profile-smoke, verified migration container completion and per-file schema_migrations rows for the current IAM/Profile migration sets, Postgres health, service /healthz//readyz, and IAM JWKS over HTTP, then cleaned up the isolated project.
  • Clean Compose live proof refreshed again on 2026-07-09 with IAM_PROFILE_CLEAN_COMPOSE_USE_LOCAL_IMAGES=1, hoctapaz/iam-service:local, hoctapaz/profile-service:local, hoctapaz/migrator:local, host ports 55435, 18081, and 18082, and artifacts under output/qa/iam-profile-clean-compose-20260709-114124/. Migration containers exited 0, IAM/Profile /healthz and /readyz returned healthy responses, and IAM JWKS returned compatibility metadata. This local-image path avoided a build-mode wait on external golang:1.22 image metadata.
  • Clean Compose build-mode proof refreshed on 2026-07-10 with an isolated project hoctapaz-iam-profile-clean-20260710, host ports 5543, 18081, and 18082, and artifacts under output/qa/iam-profile-clean-compose-20260710-1555/. Docker rebuilt the IAM, Profile, and migrator images, both migration containers exited 0, every current IAM/Profile migration was recorded in its service-owned database, both services answered /healthz and /readyz, IAM served JWKS compatibility metadata, and cleanup removed the project, network, and volume. This is still clean-runtime evidence, not populated-target cutover proof.
  • Completed UUID migration design slice: docs/microservices/iam-profile-uuid-cutover-design.md defines additive UUID columns, old text-id compatibility/backfill ids, Profile account_id semantics, OpenAPI string ids, service-to-service event ids, validation queries, and rollback/backfill ordering for UUID primary-key promotion.
  • Completed first additive UUID shadow-column migration slice: 000009_iam_uuid_shadow_columns.sql and 000006_profile_uuid_shadow_columns.sql add id_uuid, legacy_text_id, reference UUID columns, parse-or-hash backfill, indexes, and owner-local validation views without changing text primary keys or OpenAPI string ids.
  • Completed UUID shadow dual-write slice: 000010_iam_uuid_shadow_dual_write.sql and 000007_profile_uuid_shadow_dual_write.sql add owner-DB triggers that keep UUID shadow ids and reference UUID columns populated for repository writes while the service interfaces continue to use string ids. The IAM trigger also keeps nullable audit/security log account references from producing false validation issues.
  • Completed UUID primary-key readiness guard slice: 000011_iam_uuid_primary_key_readiness.sql and 000008_profile_uuid_primary_key_readiness.sql add read-only readiness views that check every owner-local UUID primary-key candidate for nulls, duplicates, and rollback legacy_text_id coverage. make test-iam-profile-uuid-primary-key-readiness is the opt-in read-only live gate for already-migrated IAM/Profile target databases; its self-test is part of runtime-foundation coverage. This does not swap primary keys.
  • Completed UUID foreign-key validation guard slice: 000012_iam_uuid_foreign_key_constraints.sql and 000009_profile_uuid_foreign_key_constraints.sql add owner-local UUID foreign keys as NOT VALID constraints plus status views. make test-iam-profile-uuid-foreign-key-validation validates those constraints on already-migrated IAM/Profile target databases and checks ON DELETE policy metadata; it scans tables and updates constraint metadata only. This does not swap primary keys or promote default routes.
  • Completed UUID primary-key promotion slice: 000013_iam_uuid_primary_key_promotion.sql and 000010_profile_uuid_primary_key_promotion.sql validate the owner-local UUID FKs, keep public text ids unique for string-id APIs and backfill upserts, move service-owned primary-key constraints to id_uuid or UUID composite keys, and expose iam_uuid_primary_key_constraints / profile_uuid_primary_key_constraints for read-only validation. make test-iam-profile-uuid-primary-key-promotion proves the migrated target DB metadata. This does not promote default public routes or drop text id columns.
  • Promote default public routes only after production-like populated validation reports, clean UUID readiness/FK/promotion output on target DSNs, browser proof, and rollback evidence.
  • Use docs/agents/service-tasks/iam-profile-default-cutover-proof.md for the final route-promotion evidence bundle. This cutover hardening pack tracks the blockers; the default cutover proof pack owns candidate route lists, production-like run-report validation, gateway live proof, browser proof, and rollback artifacts.
  • For IAM, remove the temporary full_name/legacy account envelope material only after profile/gateway cutover evidence exists.
  • For Profile, model account_id as the explicit logical IAM reference in the storage/API contract, add required account_id, display_name, and phone indexes, and keep cross-database IAM foreign keys out.
  • Build legacy user backfill tooling that writes only to hoctapaz_iam_db and hoctapaz_profile_db, emits count/hash validation reports, and leaves the legacy database read-only. Use docs/agents/service-tasks/iam-profile-backfill-loader.md for the dedicated write-side loader slice; this cutover pack tracks readiness, not all loader implementation details.
  • Completed first legacy IAM/Profile backfill validation scaffold: migrations/legacy-to-go/002_migrate_auth.sql now targets hoctapaz_iam_db, migrations/legacy-to-go/003_migrate_users.sql targets hoctapaz_profile_db, and scripts/migration/iam-profile-backfill-report.sh runs both reports against the legacy database inside a read-only transaction. This report path remains read-only; write-side loading is handled only by the confirmed migrator flow.
  • Completed first IAM/Profile write-side loader preflight contract: migrator validate --service iam-profile-backfill accepts LEGACY_DATABASE_URL, IAM_DATABASE_URL, and PROFILE_DATABASE_URL or matching CLI flags, validates DSN separation plus hoctapaz_iam_db/hoctapaz_profile_db target ownership, emits JSON preflight evidence, and performs no database writes. Confirmed writes remain gated behind migrator run --service iam-profile-backfill --confirm.
  • Completed first IAM/Profile write-side loader dry-run plan: migrator dry-run --service iam-profile-backfill validates the same DSNs, emits deterministic JSON steps with ownerDatabase boundaries, target table groups, migration_runs/migration_id_map checkpoints, reconciliation evidence requirements, and a default-route cutover gate. This is still no-write planning evidence; target writes require the confirmed run.
  • Completed IAM/Profile write-side loader slices now load IAM identities, refresh tokens, derived sessions, provider settings/links, roles, permission catalog rows, role-permission mappings, user-role assignments, organization/member projections, parent-student access links, and teacher subject scopes plus explicit zero-source staff permission scope reconciliation, Profile users, contacts, education profiles, teacher KYC, and parent-child read models into owner databases with migration_runs, row-level migration_id_map, and postload count/hash reconciliation. Production-like populated target validation, browser/default-promotion proof, final text-id cleanup, and default-route promotion remain blocked.
  • Added opt-in populated IAM/Profile backfill fixture smoke: make test-iam-profile-backfill-populated runs scripts/test/iam-profile-backfill-populated-smoke.sh. It requires IAM_PROFILE_BACKFILL_POPULATED_CONFIRM=populated-fixture, starts an isolated Postgres container, applies IAM/Profile target migrations, seeds representative legacy auth/profile rows, runs confirmed migrator run --service iam-profile-backfill --confirm, checks reconciliation output including zero-source staff_permission_scopes, and requires iam_uuid_shadow_validation plus profile_uuid_shadow_validation to report zero issues. It also asserts completed migration_runs rows with source/target hash evidence. This proves loader/migration/UUID-shadow compatibility on a hermetic fixture only; production-like populated target validation, browser/default-promotion proof, and route promotion remain blocked.
  • Added read-only live populated target validation: make test-iam-profile-live-populated-validation runs scripts/test/iam-profile-live-populated-validation.sh. It requires IAM_PROFILE_LIVE_VALIDATION_CONFIRM=live-populated, IAM_DATABASE_URL, and PROFILE_DATABASE_URL, validates the two owner DB names differ, wraps SQL in read-only transactions, requires non-empty IAM/Profile base rows, completed migration_runs rows with source/target hash evidence, migration_id_map rows, and zero issues from iam_uuid_shadow_validation plus profile_uuid_shadow_validation. It also checks owner-local IAM/Profile orphan references. For cutover-grade evidence, set IAM_PROFILE_LIVE_VALIDATION_RUN_REPORT to the captured migrator run --service iam-profile-backfill --confirm JSON and IAM_PROFILE_LIVE_VALIDATION_REQUIRE_RUN_REPORT=1; the script then requires every postload-reconciliation item to be matched with zero issues, zero rejected rows, and source/target hashes. It passed on the populated fixture after the loader completed migration_runs; production-like/live DSN proof remains a separate cutover gate.
  • Same-target fixture validation passed on 2026-07-09 under output/qa/iam-profile-target-validation-20260709-114607/: the populated fixture was kept online after backfill, then live populated validation, UUID primary-key readiness, UUID FK validation, and UUID primary-key promotion validation all ran against the same IAM/Profile DSNs. The summaries recorded 22 matched postload-reconciliation items, IAM/Profile migration_id_map rows 63/17, zero orphan references, readiness rows 81/39 with no blocking issues, validated FK constraints 26/10, and promoted UUID PK metadata for 21/9 IAM/Profile tables with zero legacy text FKs. This remains fixture evidence, not production-like DSN approval.
  • Added static IAM/Profile route rollback rehearsal target: make test-iam-profile-route-rollback runs scripts/test/iam-profile-route-rollback-rehearsal.sh. It validates the non-default auth-native and profile-native localhost route tables against the default rollback table, requires broad auth, admin, and fallback routes in deploy/gateway/routes.json to remain legacy_proxy, and fails if exact IAM/Profile native carve-outs stay active in the default table. Set IAM_PROFILE_ROUTE_ROLLBACK_ARTIFACT_DIR to write summary.json plus a Markdown rollback evidence note; optional live mode requires IAM_PROFILE_ROUTE_ROLLBACK_CONFIRM=default-legacy. This is rollback evidence for the current route-table state, not default public promotion.
  • Local live rollback proof refreshed on 2026-07-09 with a standalone api-gateway on HTTP_ADDR=:18085, GATEWAY_ROUTE_TABLE=deploy/gateway/routes.json, and dummy LEGACY_API_BASE_URL=http://127.0.0.1:9. The command IAM_PROFILE_ROUTE_ROLLBACK_LIVE=1 IAM_PROFILE_ROUTE_ROLLBACK_CONFIRM=default-legacy IAM_PROFILE_ROUTE_ROLLBACK_ARTIFACT_DIR=output/qa/iam-profile-route-rollback-20260709-114911 GATEWAY_BASE_URL=http://localhost:18085 make test-iam-profile-route-rollback verified /v1/routes plus /api/auth/google/config, /api/auth/me/profile, and /api/admin/auth-settings/google route headers as auth/admin with legacy_proxy. The 502 response statuses were expected from the dummy legacy target; the proof is route-header rollback evidence only. Artifacts include summary.json, live-routes.json, request headers/bodies, and rollback-rehearsal.md under output/qa/iam-profile-route-rollback-20260709-114911/.
  • Completed first canonical IAM naming guard: service task rules now state that /api/auth and /v1/auth are compatibility route namespaces only, while the deployable service, task pack, OpenAPI contract, deployment manifest, and runtime directory remain iam-service. The service-task coverage guard fails if an auth-service API/doc/deploy/runtime surface is reintroduced.
  • Add gateway route rehearsal, browser proof, and rollback notes before promoting additional /api/auth/* routes.
  • Extend the completed IAM route guard into gateway cutover/rehearsal coverage and keep profile routes free of credential, JWT, role, or permission ownership.
  • Completed first KYC ownership slice: profile-service owns teacher KYC storage, self submit/read, deterministic CCCD hash plus last4, redacted TeacherKycSafe, and profile.teacher_kyc.submitted audit-shaped events.
  • Completed owner-side admin KYC review slice: profile-service now owns native /v1/admin/users/kyc/teacher* list/detail/approve/reject over the same profile-owned storage, preserves pagination/search semantics, never returns plaintext CCCD or cccdNumberHash, and emits redacted profile.teacher_kyc.reviewed events.
  • Completed non-default gateway route-table rehearsal for teacher self-service KYC: exact GET/POST /api/auth/me/kyc/teacher calls can route to profile-service /v1/users/{userId}/teacher-kyc with {userId} from the verified gateway JWT principal, a required TEACHER role, no organization requirement, and broad auth/admin/fallback routes still legacy.
  • Completed non-default gateway route-table rehearsal for admin teacher KYC review: exact list plus detail/approve/reject suffix routes under /api/admin/users/kyc/teacher* can route to profile-service /v1/admin/users/kyc/teacher* with a required ADMIN role, no organization requirement, query/body preservation, and broad auth/admin/fallback routes still legacy. Admin-service wrapper decisions, default promotion, and browser/runtime evidence remain pending.
  • Completed first IAM management OpenAPI schema expansion: IAM auth/session and management routes now expose explicit request bodies and success-envelope response schemas for account directory, roles, permissions, account-role assignment, organization membership, access scopes, Google settings, and audit lists. service-task-pack-coverage.sh parses the IAM OpenAPI document and fails if these route schemas disappear before direct public promotion.
  • Completed first IAM Postgres parity expansion beyond invite-only coverage: the opt-in repository integration suite now covers role creation, permission assignment, account-role permission checks, organization member upsert/remove, identity-membership synchronization, and membership outbox payloads against an isolated migrated schema when IAM_SERVICE_POSTGRES_TEST_DATABASE_URL is set. Clean Compose and local K8s live migration/runtime smoke proof now exists for IAM/Profile.
  • Prove default gateway cutover through route rehearsal, browser/runtime evidence, rollback notes, and user-service retirement criteria. The first default candidate boundary is now guarded by make test-iam-profile-default-cutover-candidates: only auth-google-config and unauthenticated auth-me are eligible for the next default-route review, while auth writes, profile writes, teacher KYC, and admin KYC routes remain blocked until route-specific evidence is attached. make test-iam-profile-default-candidate-diff generates the non-applying candidate JSON/diff bundle for that review and keeps the default route table legacy-safe.
  • Completed first profile update live gateway smoke: PROFILE_ROUTE_LIVE_CONFIRM=profile-native PROFILE_ROUTE_LIVE_SEED_CONFIRM=seed-profile runs make test-profile-routes-live against the non-default profile route table. Latest local artifacts under output/qa/profile-route-live-20260709-121055/ prove profile-base-update/native_write, profile-teacher-update/native_write, and broad auth/legacy_proxy sibling routing. This does not satisfy populated-target validation, candidate default diff, rollback-after-candidate proof, or default-route promotion approval.
  • Completed first profile update browser smoke: PROFILE_ROUTE_BROWSER_CONFIRM=profile-native PROFILE_ROUTE_BROWSER_SEED_CONFIRM=seed-profile runs make test-profile-routes-browser against the real /teacher/profile page with the frontend pointed at api-gateway. Latest local artifacts under output/playwright/profile-route-browser-smoke-20260709-121825/ prove profile-base-update/native_write, profile-teacher-update/native_write, and broad auth/legacy_proxy sibling routing from a browser context. This is browser evidence for the non-default route table only; it does not promote default routes.
  • Keep runtime examples aligned with Compose service ports and service DNS. Example local env values must not point IAM school sync at unrelated services.

Acceptance:

  • No deployable auth-service or broad replacement user-service is created.
  • IAM remains the only owner of credentials, sessions, JWT/JWKS, roles, permissions, and access-control membership.
  • Profile remains the owner of display/profile and profile-verification/KYC data only; role/status/scopes stay projection metadata.
  • UUID shadow migration, dual-write triggers, legacy backfill, UUID primary-key readiness views, owner-local UUID foreign-key validation, and UUID primary-key promotion are implemented with guard evidence. Default route promotion, production-like target validation, browser proof, and final text-id cleanup remain explicit cutover blockers.
  • IAM/Profile backfill reports must stay read-only against the legacy database and must not reintroduce hoctapaz_auth_db, hoctapaz_user_db, or any deployable auth-service/broad user-service boundary.
  • UUID implementation follows docs/microservices/iam-profile-uuid-cutover-design.md; PostgreSQL service-owned primary keys are UUID-backed in promoted target schemas while public OpenAPI ids remain string-compatible.
  • IAM management routes are protected by service-local authorization tests; gateway authorization proof now covers HS256 fallback and IAM RS256/JWKS verification before direct public exposure. IAM management routes also have explicit OpenAPI request/response schemas guarded by service-task coverage. IAM repository permission and organization-membership paths have memory coverage plus opt-in Postgres parity coverage.
  • IAM password reset/change parity is implemented at native /v1/auth/* service routes, with non-default gateway route rehearsal for the matching /api/auth/* paths. A read-only opt-in gateway smoke is available with AUTH_ROUTE_LIVE_CONFIRM=auth-native make test-auth-routes-live; it proves Google config and unauthenticated /api/auth/me route through IAM while a sibling profile route remains legacy. That live smoke passed on 2026-07-09 against local iam-service and api-gateway with artifacts under output/qa/auth-route-live-20260709-115133/: Google config returned 200 through auth-google-config/native_read, unauthenticated /api/auth/me returned 401 with AUTH_UNAUTHORIZED through auth-me/native_read, and the sibling profile route stayed on auth/legacy_proxy. A matching opt-in browser proof is available with AUTH_ROUTE_BROWSER_CONFIRM=auth-native make test-auth-routes-browser; it exercises /auth/login and the same IAM carve-outs from a page context. That browser proof passed on 2026-07-09 with artifacts under output/playwright/auth-route-browser-smoke-20260709-115940/ and proved gateway-handled CORS preflight for the login page X-Request-Id requests. make test-iam-profile-route-rollback validates rollback to the default legacy route table. Default-route promotion still remains blocked until live/browser proof is approved for the promoted route set and the remaining data/UUID blockers are closed. The default candidate browser wrapper is now available as make test-iam-profile-default-candidate-browser; local proof under output/qa/iam-profile-default-candidate-browser-20260709-095159/ uses the narrow candidate-readonly route set and keeps login/register/admin/profile and KYC routes out of the candidate table.
  • IAM Google provider-link parity is implemented at native /v1/auth/google* service routes, with non-default gateway route rehearsal for /api/auth/google* and admin settings; default-route/browser proof remains required before claiming legacy replacement.
  • Profile indexes, KYC ownership, and Profile OpenAPI request/response schemas are explicit enough for independent service agents to implement without guessing.
  • Teacher KYC self-service responses keep plaintext CCCD, hashes, image URLs, permanent address, issuing metadata, verifier, and notes out of the safe profile/API surface; admin KYC review responses are admin-only, still omit plaintext CCCD/hash, and public route promotion remains separate cutover work.
  • Non-default teacher self-service KYC route rehearsal remains exact, teacher-only, and rollback-safe; the default route table continues to keep /api/auth/me/kyc/teacher legacy until browser proof and rollback evidence are complete.
  • Non-default admin teacher KYC route rehearsal remains admin-only, suffix-guarded, and rollback-safe; the default route table continues to keep /api/admin/users/kyc/teacher* legacy until browser proof and rollback evidence are complete.
  • Compose, static offline K8s, and Helm render IAM/Profile database bootstrap with persisted Postgres storage, init scripts, service-role DSNs, IAM_JWKS_URL gateway/profile wiring, and migration-before-service ordering.
  • Default public route promotion has partial gateway/browser proof and rollback evidence for the current read-only auth candidates, plus non-default live/browser proof for the base and teacher profile update routes. Broader auth/profile/KYC promotion remains gated by production-like target validation, candidate default diffs, and route-specific rollback evidence.
  • The final default route evidence bundle is recorded through docs/agents/service-tasks/iam-profile-default-cutover-proof.md; do not mark this cutover complete from fixture-only loader proof or non-default route rehearsals.

Verification:

  • GOTOOLCHAIN=go1.25.11 go test ./services/iam-service/... ./services/profile-service/... ./services/api-gateway/internal/gateway -count=1
  • IAM_SERVICE_POSTGRES_TEST_DATABASE_URL=postgres://... GOTOOLCHAIN=go1.25.11 go test ./services/iam-service/internal/repository -run 'TestPostgres(InviteWorkflow|IAMDirectoryPermissionAndMembership)Integration' -count=1
  • IAM_SERVICE_POSTGRES_TEST_DATABASE_URL=postgres://... GOTOOLCHAIN=go1.25.11 go test ./services/iam-service/internal/repository -run TestPostgresIAMUUIDShadowDualWriteIntegration -count=1
  • PROFILE_SERVICE_POSTGRES_TEST_DATABASE_URL=postgres://... GOTOOLCHAIN=go1.25.11 go test ./services/profile-service/internal/repository -run TestPostgresProfileUUIDShadowDualWriteIntegration -count=1
  • make test-auth-routes test-profile-routes test-service-task-packs
  • make test-iam-profile-route-rollback
  • AUTH_ROUTE_LIVE_CONFIRM=auth-native AUTH_ROUTE_LIVE_ARTIFACT_DIR=output/qa/auth-route-live-20260709-115133 GATEWAY_BASE_URL=http://localhost:18086 make test-auth-routes-live
  • AUTH_ROUTE_LIVE_SELF_TEST=1 make test-auth-routes-live
  • AUTH_ROUTE_BROWSER_SELF_TEST=1 make test-auth-routes-browser
  • PROFILE_ROUTE_LIVE_SELF_TEST=1 make test-profile-routes-live
  • PROFILE_ROUTE_BROWSER_SELF_TEST=1 make test-profile-routes-browser
  • make test-iam-profile-backfill-report
  • make test-iam-profile-backfill-loader
  • IAM_PROFILE_BACKFILL_POPULATED_SELF_TEST=1 make test-iam-profile-backfill-populated
  • IAM_PROFILE_LIVE_VALIDATION_SELF_TEST=1 make test-iam-profile-live-populated-validation
  • IAM_PROFILE_UUID_PK_READINESS_SELF_TEST=1 make test-iam-profile-uuid-primary-key-readiness
  • IAM_PROFILE_UUID_FK_VALIDATION_SELF_TEST=1 make test-iam-profile-uuid-foreign-key-validation
  • IAM_PROFILE_UUID_PK_PROMOTION_SELF_TEST=1 make test-iam-profile-uuid-primary-key-promotion
  • Optional populated fixture smoke: IAM_PROFILE_BACKFILL_POPULATED_CONFIRM=populated-fixture make test-iam-profile-backfill-populated
  • Optional read-only populated target smoke; not sufficient for cutover: IAM_PROFILE_LIVE_VALIDATION_CONFIRM=live-populated IAM_DATABASE_URL=postgres://.../hoctapaz_iam_db?sslmode=disable PROFILE_DATABASE_URL=postgres://.../hoctapaz_profile_db?sslmode=disable make test-iam-profile-live-populated-validation
  • Cutover gate for populated target validation: IAM_PROFILE_LIVE_VALIDATION_CONFIRM=live-populated IAM_PROFILE_LIVE_VALIDATION_RUN_REPORT=/path/to/migrator-run.json IAM_PROFILE_LIVE_VALIDATION_REQUIRE_RUN_REPORT=1 IAM_DATABASE_URL=postgres://.../hoctapaz_iam_db?sslmode=disable PROFILE_DATABASE_URL=postgres://.../hoctapaz_profile_db?sslmode=disable make test-iam-profile-live-populated-validation
  • Optional UUID primary-key readiness validation: IAM_PROFILE_UUID_PK_READINESS_CONFIRM=uuid-primary-key-readiness IAM_DATABASE_URL=postgres://.../hoctapaz_iam_db?sslmode=disable PROFILE_DATABASE_URL=postgres://.../hoctapaz_profile_db?sslmode=disable make test-iam-profile-uuid-primary-key-readiness
  • Optional UUID foreign-key validation: IAM_PROFILE_UUID_FK_VALIDATION_CONFIRM=uuid-foreign-key-validation IAM_DATABASE_URL=postgres://.../hoctapaz_iam_db?sslmode=disable PROFILE_DATABASE_URL=postgres://.../hoctapaz_profile_db?sslmode=disable make test-iam-profile-uuid-foreign-key-validation
  • Optional UUID primary-key promotion validation: IAM_PROFILE_UUID_PK_PROMOTION_CONFIRM=uuid-primary-key-promotion IAM_DATABASE_URL=postgres://.../hoctapaz_iam_db?sslmode=disable PROFILE_DATABASE_URL=postgres://.../hoctapaz_profile_db?sslmode=disable make test-iam-profile-uuid-primary-key-promotion
  • sh scripts/test/service-task-pack-coverage.sh
  • make test-iam-profile-runtime-proof
  • IAM_PROFILE_CLEAN_COMPOSE_SELF_TEST=1 make test-iam-profile-clean-compose
  • IAM_PROFILE_K8S_SELF_TEST=1 make test-iam-profile-local-k8s
  • Optional live clean Compose smoke: IAM_PROFILE_CLEAN_COMPOSE_CONFIRM=clean-runtime make test-iam-profile-clean-compose
  • Optional local-image live smoke when service images are already built: IAM_PROFILE_CLEAN_COMPOSE_CONFIRM=clean-runtime IAM_PROFILE_CLEAN_COMPOSE_USE_LOCAL_IMAGES=1 IAM_PROFILE_REBUILD_MIGRATOR_IMAGE=1 make test-iam-profile-clean-compose
  • Optional local/offline K8s smoke after images are built and loaded: IAM_PROFILE_K8S_LIVE_CONFIRM=local-k8s make test-iam-profile-local-k8s
  • OpenAPI YAML parse and mirror check for IAM/Profile service and contract files.
  • make test-runtime-foundation
  • pnpm docs:build

Go-platform documentation is generated from repository Markdown.