Skip to content

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.yaml
  • docs/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, and deploy/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, and scripts/test/auth-route-browser-smoke.mjs, with evidence in docs/qa/auth-route-live-smoke.md and docs/qa/auth-route-browser-smoke.md.
  • IAM/Profile rollback proof must use scripts/test/iam-profile-route-rollback-rehearsal.sh, with evidence in docs/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.sh and scripts/test/organization-membership-write-live-smoke.sh for 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, and test-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-service consuming 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}/check for service-to-service access decisions. The endpoint requires X-Internal-Service plus the shared X-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_memberships and the canonical organization_members projection in one transaction. Migration 000015 backfills existing projections. This is required for the internal membership decision used by exam runtime; see docs/qa/docx-fixture-persistence-runtime-proof.md.
  • Done: add read-only owner GET /v1/admin/identity/summary for admin-service identity.counts source 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_events through AUDIT_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-live for the opt-in read-only gateway smoke and AUTH_ROUTE_BROWSER_CONFIRM=auth-native make test-auth-routes-browser for browser-originated login page proof against the non-default auth route table. Use AUTH_SESSION_BROWSER_CONFIRM=auth-session make test-auth-routes-session-browser for browser-originated register/login/refresh/logout lifecycle proof with redacted token artifacts. Use make test-iam-profile-route-rollback to 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-service served Google config and unauthenticated /v1/auth/me through 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/login observed gateway-handled CORS preflight, auth-google-config/native_read, auth-me/native_read, and the profile sibling on broad auth/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 through api-gateway, authenticated /api/auth/me succeeded 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 userId add-member bodies to IAM accountId and routes add/remove to iam-service; default /api/organizations* writes and PATCH member updates remain legacy until browser proof and rollback evidence exist. Use make test-organization-membership-write-live only with an IAM admin bearer token because the service-local management guard reads Authorization directly.
  • 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 ADMIN bearer token.
  • Keep /v1/admin/identity/summary read-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. TestPostgresIAMDirectoryPermissionAndMembershipIntegration applies 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 by IAM_SERVICE_POSTGRES_TEST_DATABASE_URL.
  • Done locally: TestPostgresIAMUUIDShadowDualWriteIntegration applies IAM migrations in an isolated schema, writes representative repository rows, and asserts iam_uuid_shadow_validation returns zero issues. It remains gated by IAM_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-admin 403, and admin 200 behavior 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=1
  • make test-auth-routes
  • AUTH_ROUTE_LIVE_SELF_TEST=1 make test-auth-routes-live
  • AUTH_ROUTE_BROWSER_SELF_TEST=1 make test-auth-routes-browser
  • AUTH_SESSION_BROWSER_SELF_TEST=1 make test-auth-routes-session-browser
  • make 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-routes when 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.yaml
  • diff -u services/iam-service/api/openapi.yaml contracts/openapi/services/iam-service.yaml
  • 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

Go-platform documentation is generated from repository Markdown.