Skip to content

profile-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-004

Owns: services/profile-service, contracts/openapi/services/profile-service.yaml, docs/api/profile-service.md.

Must read:

  • docs/architecture/service-responsibility-matrix.md
  • legacy user/profile/KYC modules: /Users/velikho/Desktop/WORKING/HOCTAPAZ/node-platform/apps/api/src/modules/auth/auth.controller.ts, /Users/velikho/Desktop/WORKING/HOCTAPAZ/node-platform/apps/api/src/modules/auth/auth.service.ts, /Users/velikho/Desktop/WORKING/HOCTAPAZ/node-platform/apps/api/prisma/schema.prisma.

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/web/components/account/**

Writable files:

  • services/profile-service/**
  • contracts/openapi/services/profile-service.yaml
  • docs/api/profile-service.md

Orchestrator-owned files:

  • IAM role/session contracts, shared gateway route tables, default auth/profile cutover, and shared deploy manifests unless explicitly delegated.

Delegated gateway verification scope:

  • Only when the orchestrator assigns a profile route/live/browser proof subtask, this service agent may update non-default deploy/gateway/routes.profile*.json examples, scripts/test/profile-*.sh, docs/qa/profile-*.md, and root Makefiletest-profile-* targets.
  • The default gateway route table, IAM session envelope, admin-service wrapper decisions, and frontend account UI remain orchestrator-owned unless explicitly delegated. Public promotion still requires browser/runtime proof and rollback notes.

Initial tasks:

  • Finish public/internal profile contracts for student, teacher, parent, staff.
  • Done: OpenAPI service and contract files now include concrete request and response schemas for full profile, public profile, base updates, student/teacher/parent/staff profile routes, compatibility profile routes, staff display metadata, and redacted TeacherKycSafe.
  • Maintain profile contact/privacy behavior without moving credentials, roles, permissions, JWT claims, or route authorization here.
  • Done: public staff profile tests now prove role, status, staff scopes, staffProfile, and permission-like fields are redacted, and base profile updates reject projection/access-control fields as unknown JSON.
  • Done: teacher KYC self submit/read is profile-service-owned at /v1/users/{userId}/teacher-kyc and /v1/profiles/{accountId}/teacher-kyc. The service validates CCCD, stores a deterministic hash plus last4, returns only TeacherKycSafe, and emits a redacted profile.teacher_kyc.submitted event. Exact teacher self-service /api/auth/me/kyc/teacher gateway route-table rehearsal is covered by the non-default profile KYC route tables; default browser cutover, admin KYC compatibility routing, and any admin-service wrapper remain separate work.
  • Done: owner-side admin teacher KYC review is profile-service-owned at /v1/admin/users/kyc/teacher*. The service preserves legacy list/detail pagination/search shapes, exposes moderation-only document metadata, approves or rejects profile-owned KYC state, never returns plaintext CCCD or cccdNumberHash, and emits redacted profile.teacher_kyc.reviewed events. These owner-admin routes require X-Internal-Service plus the shared X-Internal-Token after gateway/admin authorization. Admin public gateway route-table rehearsal is covered by the non-default profile admin KYC route tables; default browser cutover and any admin-service compatibility wrapper remain separate orchestrator/admin-service work.
  • Done: internal parent-recipient resolution is profile-service-owned at /v1/internal/profiles/parent-recipients/resolve. The endpoint reads the parent-child projection to return parent account ids for supplied studentIds, supports producer/BFF handoff such as WEAK_TOPIC_ALERT, and does not validate or grant IAM parent-student access permissions. It requires the same internal service token headers.
  • Done: add read-only owner GET /v1/admin/profiles/summary for admin-service identity.counts source snapshots. It reports only profile/display/KYC/ contact/read-model counts; IAM remains authoritative for credentials, sessions, roles, permissions, and access control. It is protected by the same internal service token headers.
  • Treat any role, status, or staff scopes fields in profile payloads as display/sync projections only. They must not drive access control, permission checks, JWT claims, or route authorization.
  • Treat old services/user-service as a deprecated compatibility scaffold: new identity work belongs to iam-service, and new profile work belongs here. Remove or retire it only after route compatibility and data backfill evidence are explicit.
  • Coordinate UUID primary-key, profile backfill, and default profile cutover work through docs/agents/service-tasks/iam-profile-cutover-hardening.md. Do not treat the UUID primary-key requirement as complete while native profile primary keys still use text ids for compatibility; additive UUID shadow columns and dual-write triggers are only the pre-promotion phase.

Acceptance:

  • No password/JWT/role/permission source of truth in profile-service.
  • Profile role, status, and staff scopes are tested/documented as projection metadata, not authoritative access-control state.
  • Profile contacts are internal metadata with validation, primary-per-kind behavior, OpenAPI/docs coverage, and tests proving public profile redaction.
  • Parent-child relationships are profile read models only: public list routes return redacted child profile views, internal sync routes populate/delete projections, and IAM parent-student permission links remain the access-control source of truth.
  • Parent-recipient resolution may expose only parent account ids derived from those profile read models; it must not perform notification delivery, mutate IAM links, or become an authorization decision.
  • Profile summary may expose only aggregate profile-service counts. It must not list users or become an IAM/session/permission dashboard.
  • Teacher KYC self submit/read stores privacy-sensitive document fields only in profile-service, never stores plaintext CCCD, and never returns document hashes, image URLs, permanent address, issue/expiry metadata, verifier, notes, or plaintext CCCD from self-service safe views.
  • Teacher KYC admin review list/detail/approve/reject stays on profile-service storage, returns admin-only document metadata without plaintext CCCD/hash, and requires gateway actor identity for review mutations.
  • Profile mutation events carry a local event id, action/entity/outcome, account id, request id, correlation id, and optional gateway actor context. When AUDIT_SERVICE_URL/AUDIT_SERVICE_BASE_URL is configured, profile-service projects those events to audit-service /v1/audit-events with sourceService=profile-service, sourceEventId=<profile event id>, category=DATA, severity=INFO, retentionPolicy=profile-events, metadataSchemaVersion=1, and occurredAt from the local event. Publish failures are ignored and profile.snapshot.upserted is covered separately from profile.created. profile.teacher_kyc.submitted and profile.teacher_kyc.reviewed must stay redacted.
  • Public profile responses redact email/phone/contacts/private metadata.
  • Role-specific profile updates preserve existing Vietnamese product fields.
  • OpenAPI schemas stay mirrored between service and contracts and cover base, public, internal, compatibility, and role-specific profile request/response envelopes.
  • 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.
  • Profile UUID shadow dual-write is covered by TestPostgresProfileUUIDShadowDualWriteIntegration, which applies Profile migrations in an isolated schema, writes representative repository rows, and asserts profile_uuid_shadow_validation returns zero issues.
  • make test-profile-routes covers current-user profile update route rehearsal, exact teacher self-service KYC route rehearsal, and admin teacher KYC review route rehearsal while the default route table remains legacy.
  • Done locally: current-user profile update live gateway smoke is available through make test-profile-routes-live. Confirmed live mode requires PROFILE_ROUTE_LIVE_CONFIRM=profile-native and PROFILE_ROUTE_LIVE_SEED_CONFIRM=seed-profile, seeds a disposable owner profile in profile-service, then verifies PATCH /api/auth/me/profile and PATCH /api/auth/me/teacher-profile through api-gateway. Latest local proof on 2026-07-09 is under output/qa/profile-route-live-20260709-121055/, with profile-base-update/native_write, profile-teacher-update/native_write, and the GET /api/auth/me sibling on auth/legacy_proxy.
  • Done locally: current-user profile update browser smoke is available through make test-profile-routes-browser. Confirmed browser mode requires PROFILE_ROUTE_BROWSER_CONFIRM=profile-native and PROFILE_ROUTE_BROWSER_SEED_CONFIRM=seed-profile, opens the real /teacher/profile page with browser auth state, then verifies PATCH /api/auth/me/profile and PATCH /api/auth/me/teacher-profile through api-gateway. Latest local proof on 2026-07-09 is under output/playwright/profile-route-browser-smoke-20260709-121825/, with profile-base-update/native_write, profile-teacher-update/native_write, and the GET /api/auth/me sibling on auth/legacy_proxy. Default-route promotion remains orchestrator-owned and still requires populated-target validation, a candidate diff, and rollback-after-candidate proof.

Verification:

  • GOTOOLCHAIN=go1.25.11 go test ./services/profile-service/...
  • PROFILE_SERVICE_POSTGRES_TEST_DATABASE_URL=postgres://... GOTOOLCHAIN=go1.25.11 go test ./services/profile-service/internal/repository -run TestPostgresProfileUUIDShadowDualWriteIntegration -count=1
  • make test-profile-routes test-profile-kyc-routes test-profile-admin-kyc-routes
  • PROFILE_ROUTE_LIVE_CONFIRM=profile-native PROFILE_ROUTE_LIVE_SEED_CONFIRM=seed-profile GATEWAY_BASE_URL=http://localhost:8085 PROFILE_SERVICE_BASE_URL=http://localhost:8082 AUTH_JWT_SECRET=... make test-profile-routes-live
  • PROFILE_ROUTE_BROWSER_CONFIRM=profile-native PROFILE_ROUTE_BROWSER_SEED_CONFIRM=seed-profile WEB_BASE_URL=http://localhost:5202 GATEWAY_BASE_URL=http://localhost:8085 PROFILE_SERVICE_BASE_URL=http://localhost:8082 AUTH_JWT_SECRET=... make test-profile-routes-browser
  • ruby -e 'require "yaml"; ARGV.each { |path| YAML.load_file(path) }' services/profile-service/api/openapi.yaml contracts/openapi/services/profile-service.yaml
  • diff -u services/profile-service/api/openapi.yaml contracts/openapi/services/profile-service.yaml
  • Current event/audit tests prove profile event context propagation, best-effort audit projection, redaction, and no duplicate snapshot event for CreateProfile, plus KYC safe response redaction, duplicate CCCD detection, and admin KYC review redaction/status behavior.

Go-platform documentation is generated from repository Markdown.