Appearance
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.yamldocs/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*.jsonexamples,scripts/test/profile-*.sh,docs/qa/profile-*.md, and rootMakefiletest-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, staffscopes,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-kycand/v1/profiles/{accountId}/teacher-kyc. The service validates CCCD, stores a deterministic hash plus last4, returns onlyTeacherKycSafe, and emits a redactedprofile.teacher_kyc.submittedevent. Exact teacher self-service/api/auth/me/kyc/teachergateway 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 orcccdNumberHash, and emits redactedprofile.teacher_kyc.reviewedevents. These owner-admin routes requireX-Internal-Serviceplus the sharedX-Internal-Tokenafter 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 suppliedstudentIds, supports producer/BFF handoff such asWEAK_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/summaryfor admin-serviceidentity.countssource 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 staffscopesfields 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-serviceas a deprecated compatibility scaffold: new identity work belongs toiam-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 staffscopesare 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_URLis configured, profile-service projects those events to audit-service/v1/audit-eventswithsourceService=profile-service,sourceEventId=<profile event id>,category=DATA,severity=INFO,retentionPolicy=profile-events,metadataSchemaVersion=1, andoccurredAtfrom the local event. Publish failures are ignored andprofile.snapshot.upsertedis covered separately fromprofile.created.profile.teacher_kyc.submittedandprofile.teacher_kyc.reviewedmust 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 assertsprofile_uuid_shadow_validationreturns zero issues. make test-profile-routescovers 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 requiresPROFILE_ROUTE_LIVE_CONFIRM=profile-nativeandPROFILE_ROUTE_LIVE_SEED_CONFIRM=seed-profile, seeds a disposable owner profile inprofile-service, then verifiesPATCH /api/auth/me/profileandPATCH /api/auth/me/teacher-profilethroughapi-gateway. Latest local proof on 2026-07-09 is underoutput/qa/profile-route-live-20260709-121055/, withprofile-base-update/native_write,profile-teacher-update/native_write, and theGET /api/auth/mesibling onauth/legacy_proxy. - Done locally: current-user profile update browser smoke is available through
make test-profile-routes-browser. Confirmed browser mode requiresPROFILE_ROUTE_BROWSER_CONFIRM=profile-nativeandPROFILE_ROUTE_BROWSER_SEED_CONFIRM=seed-profile, opens the real/teacher/profilepage with browser auth state, then verifiesPATCH /api/auth/me/profileandPATCH /api/auth/me/teacher-profilethroughapi-gateway. Latest local proof on 2026-07-09 is underoutput/playwright/profile-route-browser-smoke-20260709-121825/, withprofile-base-update/native_write,profile-teacher-update/native_write, and theGET /api/auth/mesibling onauth/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=1make test-profile-routes test-profile-kyc-routes test-profile-admin-kyc-routesPROFILE_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-livePROFILE_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-browserruby -e 'require "yaml"; ARGV.each { |path| YAML.load_file(path) }' services/profile-service/api/openapi.yaml contracts/openapi/services/profile-service.yamldiff -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.