Appearance
iam-service Agent Tasks
Agent workflow: follow README.md for Audit -> Investigate -> Code -> Test -> Fix; keep this pack's writable scope and verification commands authoritative.
Dispatch type: per-service
Dispatch ID: SVC-003
Owns: services/iam-service, contracts/openapi/services/iam-service.yaml, docs/api/iam-service.md.
Must read:
docs/architecture/service-responsibility-matrix.md- legacy auth modules under
/Users/velikho/Desktop/WORKING/HOCTAPAZ/node-platform/apps/api/src
Legacy source evidence:
/Users/velikho/Desktop/WORKING/HOCTAPAZ/node-platform/apps/api/src/modules/auth/**/Users/velikho/Desktop/WORKING/HOCTAPAZ/node-platform/apps/api/prisma/schema.prisma- frontend auth callers under
/Users/velikho/Desktop/WORKING/HOCTAPAZ/node-platform/apps/web.
Writable files:
services/iam-service/**contracts/openapi/services/iam-service.yamldocs/api/iam-service.md
Orchestrator-owned files:
- shared gateway route tables, school-service membership boundary decisions, Compose/Helm/K8s manifests, and default public auth cutover.
Delegated gateway verification scope:
- Agents may maintain only non-default auth and organization membership write rehearsals in
deploy/gateway/routes.auth-native-example.json,deploy/gateway/routes.auth-native-localhost-example.json,deploy/gateway/routes.organizations-membership-write-native-example.json, anddeploy/gateway/routes.organizations-membership-write-native-localhost-example.json; the default public/api/auth/*and/api/organizations*route table remains orchestrator-owned. - Auth proof must use
scripts/test/auth-route-coverage.sh,scripts/test/auth-route-live-smoke.sh,scripts/test/auth-route-browser-smoke.sh, andscripts/test/auth-route-browser-smoke.mjs, with evidence indocs/qa/auth-route-live-smoke.mdanddocs/qa/auth-route-browser-smoke.md. - IAM/Profile rollback proof must use
scripts/test/iam-profile-route-rollback-rehearsal.sh, with evidence indocs/qa/iam-profile-route-rollback-rehearsal.md; this validates rollback to the default route table and does not promote/api/auth/*. - Organization membership write proof must use
scripts/test/organization-route-coverage.shandscripts/test/organization-membership-write-live-smoke.shfor the IAM-owned add/remove route rehearsal while default PATCH/member promotion remains blocked on orchestrator evidence. - Makefile targets:
test-auth-routes,test-auth-routes-live,test-auth-routes-browser,test-auth-routes-session-browser,test-iam-profile-route-rollback,test-organization-routes, andtest-organization-membership-write-live.
Initial tasks:
- Complete invitation persistence and accept workflow over existing schema.
- Maintain password change/reset parity: current-password validation, neutral forgot response, single-use hashed reset tokens, refresh/session revocation, and audit/security events without raw credential material.
- Maintain Google Auth parity: public config, Google ID-token login/register, existing-email provider linking, admin settings, service-owned provider-link storage, and audit/security events without raw Google credentials.
- Harden JWKS/token verification contract for gateway use.
- Done: add role/permission/org membership workflow tests covering admin, staff, teacher, student, and parent. The HTTP coverage creates per-role IAM roles/permissions, assigns organization membership, checks internal membership decisions, and verifies refreshed JWT/session membership claims.
- Maintain the IAM access-scope APIs for parent-student links, teacher subject scopes, and staff permission scopes; these are IAM-owned access-control rows, not profile or classroom data.
- Maintain IAM as the canonical organization membership owner with
school-serviceconsuming only an IAM-synchronized read model for tenant validation; school-service must not become a competing write source for permission-bearing membership. - Done locally: add
GET /v1/internal/organizations/{organizationId}/members/{accountId}/checkfor service-to-service access decisions. The endpoint requiresX-Internal-Serviceplus the sharedX-Internal-Token, returns only one{allowed, role, status, unitId}decision, and treats only active IAM membership as allowed. - Done locally: migrations seed the local bootstrap organization required by public registration after UUID primary-key promotion, and Postgres identity creation now writes both
identity_membershipsand the canonicalorganization_membersprojection in one transaction. Migration000015backfills existing projections. This is required for the internal membership decision used by exam runtime; seedocs/qa/docx-fixture-persistence-runtime-proof.md. - Done: add read-only owner
GET /v1/admin/identity/summaryfor admin-serviceidentity.countssource snapshots. It reports only IAM-owned account, role, permission, organization membership, and access-scope counts; profile display/read-model counts stay in profile-service. It is protected by the same internal service token headers. - Maintain optional audit-service projection for IAM
security_eventsthroughAUDIT_SERVICE_URL/AUDIT_SERVICE_BASE_URL: local IAM logs stay authoritative, downstream failures are non-blocking, and projection metadata is redacted before posting. - Coordinate UUID primary-key, legacy backfill, and default auth cutover work through
docs/agents/service-tasks/iam-profile-cutover-hardening.md. Do not treat the UUID primary-key requirement as complete while native IAM primary keys still use text ids for compatibility; additive UUID shadow columns and dual-write triggers are only the pre-promotion phase. - Treat the non-default auth-native route-table rehearsal for password reset, password change, Google Auth, and admin Google settings as orchestrator-owned gateway evidence; default public route promotion still requires browser proof and rollback evidence. Use
AUTH_ROUTE_LIVE_CONFIRM=auth-native make test-auth-routes-livefor the opt-in read-only gateway smoke andAUTH_ROUTE_BROWSER_CONFIRM=auth-native make test-auth-routes-browserfor browser-originated login page proof against the non-default auth route table. UseAUTH_SESSION_BROWSER_CONFIRM=auth-session make test-auth-routes-session-browserfor browser-originated register/login/refresh/logout lifecycle proof with redacted token artifacts. Usemake test-iam-profile-route-rollbackto prove the current default route table remains a rollback-safe legacy table. - Latest local live auth proof on 2026-07-09 is under
output/qa/auth-route-live-20260709-115133/:iam-serviceserved Google config and unauthenticated/v1/auth/methrough gateway native route headers, while the profile sibling remained on broad legacy auth routing. - Latest local browser auth proof on 2026-07-09 is under
output/playwright/auth-route-browser-smoke-20260709-115940/:/auth/loginobserved gateway-handled CORS preflight,auth-google-config/native_read,auth-me/native_read, and the profile sibling on broadauth/legacy_proxy. - Latest local browser session lifecycle proof on 2026-07-09 is under
output/playwright/auth-route-session-browser-smoke-20260709-100744/: browser-originated register/login/refresh/logout requests went throughapi-gateway, authenticated/api/auth/mesucceeded before and after refresh, the old refresh token and post-logout refresh were rejected, and artifacts redacted access tokens, refresh tokens, and passwords. - Treat the non-default organization membership write route-table rehearsal as orchestrator-owned gateway evidence. The gateway adapter translates legacy
userIdadd-member bodies to IAMaccountIdand routes add/remove toiam-service; default/api/organizations*writes and PATCH member updates remain legacy until browser proof and rollback evidence exist. Usemake test-organization-membership-write-liveonly with an IAM admin bearer token because the service-local management guard readsAuthorizationdirectly. - Preserve service-local admin guards on IAM management routes. Public routes are limited to health/ready, JWKS, register, login, refresh, logout, and invite acceptance; account directory, roles, permissions, organization membership, access scopes, invite create/revoke, and audit lists require an
ADMINbearer token. - Keep
/v1/admin/identity/summaryread-only and count-only. Do not add profile, wallet, billing, learning, or subscription aggregation to this IAM-owned owner API. - Done locally: expand opt-in Postgres repository parity beyond invite-only coverage.
TestPostgresIAMDirectoryPermissionAndMembershipIntegrationapplies IAM migrations in an isolated schema, then proves role/permission assignment, account permission checks, organization membership upsert/remove, identity-membership synchronization, and membership outbox attributes. It remains gated byIAM_SERVICE_POSTGRES_TEST_DATABASE_URL. - Done locally:
TestPostgresIAMUUIDShadowDualWriteIntegrationapplies IAM migrations in an isolated schema, writes representative repository rows, and assertsiam_uuid_shadow_validationreturns zero issues. It remains gated byIAM_SERVICE_POSTGRES_TEST_DATABASE_URL.
Acceptance:
- IAM owns auth/access only; no profile/subscription/wallet fields are added.
- Register/login/refresh/logout/me remain legacy-compatible.
- Password change/reset routes remain legacy-compatible at the service contract level while gateway/default
/api/auth/*promotion stays orchestrator-owned. - Google Auth routes remain legacy-compatible at the service contract level while gateway/default
/api/auth/google*promotion stays orchestrator-owned. - Permission checks are test-covered in memory and the opt-in Postgres repository path.
- Parent-student permission, teacher subject, and staff permission scopes have HTTP, usecase, memory/Postgres store, OpenAPI, and audit-event coverage.
- Access-control organization membership cannot diverge from tenant validation; IAM writes membership and school-service consumes the IAM read model with replay/tombstone ordering before any public member-write route promotion.
- Management route tests prove unauthenticated
401, non-admin403, and admin200behavior before any direct exposure. - Internal service tests prove missing marker, missing token, and valid marker+token behavior for owner/internal routes before any gateway promotion.
- UUID/backfill/default-route cutover blockers are either completed with validation evidence or tracked in the IAM/Profile cutover hardening pack.
Verification:
GOTOOLCHAIN=go1.25.11 go test ./services/iam-service/...IAM_SERVICE_POSTGRES_TEST_DATABASE_URL=postgres://... GOTOOLCHAIN=go1.25.11 go test ./services/iam-service/internal/repository -run TestPostgresIAMUUIDShadowDualWriteIntegration -count=1make test-auth-routesAUTH_ROUTE_LIVE_SELF_TEST=1 make test-auth-routes-liveAUTH_ROUTE_BROWSER_SELF_TEST=1 make test-auth-routes-browserAUTH_SESSION_BROWSER_SELF_TEST=1 make test-auth-routes-session-browsermake test-iam-profile-route-rollback- Optional read-only live gateway smoke:
AUTH_ROUTE_LIVE_CONFIRM=auth-native make test-auth-routes-live - Latest local read-only live gateway smoke:
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 - Optional browser gateway smoke:
AUTH_ROUTE_BROWSER_CONFIRM=auth-native make test-auth-routes-browser - Optional session lifecycle browser smoke:
AUTH_SESSION_BROWSER_CONFIRM=auth-session make test-auth-routes-session-browser - Latest local browser gateway smoke:
AUTH_ROUTE_BROWSER_CONFIRM=auth-native AUTH_ROUTE_BROWSER_ARTIFACT_DIR=output/playwright/auth-route-browser-smoke-20260709-115940 AUTH_ROUTE_BROWSER_HEADLESS=1 WEB_BASE_URL=http://localhost:5205 GATEWAY_BASE_URL=http://localhost:18087 make test-auth-routes-browser - Latest local session lifecycle browser smoke:
AUTH_SESSION_BROWSER_CONFIRM=auth-session AUTH_SESSION_BROWSER_ARTIFACT_DIR=output/playwright/auth-route-session-browser-smoke-20260709-100744 AUTH_SESSION_BROWSER_HEADLESS=1 WEB_BASE_URL=http://127.0.0.1:5207 GATEWAY_BASE_URL=http://localhost:18095 make test-auth-routes-session-browser make test-organization-routeswhen organization membership, tenant context, or/api/organizations*route behavior changes.ruby -e 'require "yaml"; ARGV.each { |path| YAML.load_file(path) }' services/iam-service/api/openapi.yaml contracts/openapi/services/iam-service.yamldiff -u services/iam-service/api/openapi.yaml contracts/openapi/services/iam-service.yamlIAM_SERVICE_POSTGRES_TEST_DATABASE_URL=postgres://... GOTOOLCHAIN=go1.25.11 go test ./services/iam-service/internal/repository -run 'TestPostgres(InviteWorkflow|IAMDirectoryPermissionAndMembership)Integration' -count=1