Appearance
api-gateway 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-001
Owns: services/api-gateway, assigned non-default deploy/gateway/*.json route rehearsal examples, and gateway route tests.
Must read:
docs/architecture/api-contract-strategy.mddocs/architecture/service-responsibility-matrix.mddocs/architecture/legacy-api-map.md
Legacy source evidence:
/Users/velikho/Desktop/WORKING/HOCTAPAZ/node-platform/apps/api/src/main.ts/Users/velikho/Desktop/WORKING/HOCTAPAZ/node-platform/apps/api/src/modules/**- Legacy frontend API callers under
/Users/velikho/Desktop/WORKING/HOCTAPAZ/node-platform/apps/web.
Writable files:
services/api-gateway/**deploy/gateway/*.jsononly for assigned route rehearsals- gateway-specific tests and docs under
docs/api/api-gateway.md
Orchestrator-owned files:
- default route-table cutover decisions, shared Compose/Helm/K8s manifests, and service-owner API contracts unless explicitly delegated.
Initial tasks:
- Audit all non-default native route examples and confirm frontend-only-via-gateway invariant.
- Add route-table coverage when any service task introduces a native cutover candidate.
- Keep JWT/JWKS verification compatible with
iam-serviceclaims. Gateway auth guards must accept IAM RS256 tokens throughIAM_JWKS_URLwhile preserving the HS256AUTH_JWT_SECRETfallback for local route rehearsals. - For learning public workflow cutovers, require service APIs first, then add route adapters/live smokes/browser smokes for question/course/exam/attempt.
- Done locally: attempt start route rehearsal uses the gateway
exam_start_adapterstate forPOST /api/exams/:examId/start. The adapter calls exam-service runtime access before attempt-service, attaches its shared internal Gateway authentication to both hops, and is covered by focused gateway tests plusmake test-attempt-routes. - Done locally:
DOCX_PHYSICS_GATEWAY_ATTEMPT_LOCAL_POSTGRES_CONFIRM=local-postgres make test-docx-physics-attempt-local-postgresruns a complete non-default candidate against fresh isolated IAM, Document, Classroom, Import, Question Bank, Exam, and Attempt databases. It compares the served dynamic route table exactly, registers teacher/student identities in one organization, imports the 28-question Physics fixture, publishes an online exam, then starts, saves, submits, and reads aGRADEDattempt. The object store is in-memory and the proof neither uses a browser or Mistral credential nor changes the default route table. - Done locally: organization membership write rehearsal uses the gateway
organization_membership_adapterstate in non-default route tables. The adapter converts legacyuserIdadd-member bodies to IAMaccountId, routes member add/remove toiam-service, and keeps PATCH member updates plus other organization writes legacy-proxied.make test-organization-membership-write-liveis available for the explicit add/remove live smoke with an IAM admin bearer token and a disposable member account id. - Done locally: auth-native read-only live smoke is available through
make test-auth-routes-live. The self-test runs undermake test-auth-routes; confirmed live mode requiresAUTH_ROUTE_LIVE_CONFIRM=auth-nativeand checks IAM Google config, unauthenticated/api/auth/me, and a legacy sibling route without promoting the default route table. Latest local live proof on 2026-07-09 is underoutput/qa/auth-route-live-20260709-115133/, with Google config routed throughauth-google-config/native_read,/api/auth/merouted throughauth-me/native_read, and the sibling profile route onauth/legacy_proxy. - Done locally: auth-native browser smoke is available through
make test-auth-routes-browser. The self-test also runs undermake test-auth-routes; confirmed browser mode requiresAUTH_ROUTE_BROWSER_CONFIRM=auth-native, opens/auth/login, clicks the Google Auth entry, and verifies browser-observed IAM route headers without promoting the default route table. Latest local browser proof on 2026-07-09 is underoutput/playwright/auth-route-browser-smoke-20260709-115940/, with gateway-handled CORS preflight, Google config routed throughauth-google-config/native_read,/api/auth/merouted throughauth-me/native_read, and the sibling profile route onauth/legacy_proxy. - Done locally: IAM/Profile rollback rehearsal is available through
make test-iam-profile-route-rollback. It validates the auth/profile non-default route examples against the default rollback route table, and optional live mode requiresIAM_PROFILE_ROUTE_ROLLBACK_CONFIRM=default-legacy. - Done locally: profile update live gateway smoke is available through
make test-profile-routes-live. Confirmed live mode uses the non-defaultdeploy/gateway/routes.profile-native-localhost-example.jsonroute table, verifiesPATCH /api/auth/me/profilethroughprofile-base-update/native_write, verifiesPATCH /api/auth/me/teacher-profilethroughprofile-teacher-update/native_write, and keeps the siblingGET /api/auth/meroute onauth/legacy_proxy. Latest local proof on 2026-07-09 is underoutput/qa/profile-route-live-20260709-121055/. - Done locally: profile update browser smoke is available through
make test-profile-routes-browser. Confirmed browser mode opens the real/teacher/profilepage, sends browser-originated profile update requests throughapi-gateway, verifiesprofile-base-update/native_writeandprofile-teacher-update/native_write, and keeps the siblingGET /api/auth/meroute onauth/legacy_proxy. Latest local proof on 2026-07-09 is underoutput/playwright/profile-route-browser-smoke-20260709-121825/. - Done locally: gateway route-operation audit projection is available when
AUDIT_SERVICE_URL/AUDIT_SERVICE_BASE_URLis set. The gateway posts one best-effort, redacted client-facing route event per matched/api/*route to audit-servicePOST /v1/audit-eventswithsourceService=api-gateway,sourceEventId=gateway:route:<requestId>,category=OPERATIONS,action=gateway.route.request,entity=GatewayRoute,retentionPolicy=gateway-route-operations, andmetadataSchemaVersion=1. Disabled or failing audit projection never blocks the gateway response; no default route table was promoted for this slice. - Done locally: teacher Word print rehearsal is isolated in
routes.teacher-print-export-bff-native-*.json. It maps only print-template create/list/update and durable DOCX export create/list/download to BFF, requiresADMIN/TEACHERplus organization scope, and preserves the broad/api/teacherlegacy route inroutes.json. The named static proof ismake test-teacher-print-export-bff-routes; the hermetic Gateway test proves path rewrite, role/org denial, and replacement of forged actor headers. - Done locally: teacher AI preview rehearsal is isolated in
routes.teacher-ai-preview-native-*.json. It maps only the exact generation and editor-solution previewPOSTroutes toai-classifier-service, plus reviewed generation-draft and canonical-solution writes toquestion-bank-service. Every route requiresADMIN/TEACHERplus organization scope, and the default broad/api/questionsroute remains legacy-proxied. The named static proof ismake test-teacher-ai-preview-routes; the hermetic Gateway test proves path rewrite, forged-header replacement, student denial, and missing-organization denial. - Done locally: teacher exam blueprint rehearsal is isolated in
routes.teacher-exam-blueprint-bff-native-*.json. It maps only matrix list/create/detail/update/delete through BFF, requiresADMIN/TEACHERplus organization scope, and leavesroutes.jsonunchanged. The named proof ismake test-teacher-exam-blueprint-bff-routes.
Acceptance:
- Default route table remains safe for unproven routes.
- Native examples route only exact approved paths.
- Auth/role/org guards are tested for every protected native route.
- RS256/JWKS and HS256 fallback verification stay covered before any protected default route promotion.
- Audit projection metadata stays redacted: no authorization headers, cookies, raw request bodies, or query values.
- Attempt start, auth/profile, and organization membership writes are not promoted by default until route/browser proof and rollback evidence exist.
Verification:
GOTOOLCHAIN=go1.25.11 go test ./services/api-gateway/...make test-auth-routes test-auth-routes-live test-auth-routes-browser test-iam-profile-route-rollback test-organization-routes test-question-read-routes test-question-write-routes test-attempt-routes test-teacher-print-export-bff-routes test-teacher-exam-blueprint-bff-routes test-teacher-ai-preview-routesGOTOOLCHAIN=go1.25.11 go test ./services/api-gateway/internal/gateway -run TeacherPrintExportBFF -count=1GOTOOLCHAIN=go1.25.11 go test ./services/api-gateway/internal/gateway -run TeacherAIPreview -count=1DOCX_PHYSICS_GATEWAY_ATTEMPT_LOCAL_POSTGRES_CONFIRM=local-postgres HOCTAPAZ_DOCX_CORPUS_DIR=/Users/velikho/Desktop/test-hoctapaz make test-docx-physics-attempt-local-postgresAUTH_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-liveAUTH_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-browserPROFILE_ROUTE_LIVE_CONFIRM=profile-native PROFILE_ROUTE_LIVE_SEED_CONFIRM=seed-profile PROFILE_ROUTE_LIVE_ARTIFACT_DIR=output/qa/profile-route-live-20260709-121055 GATEWAY_BASE_URL=http://localhost:18088 PROFILE_SERVICE_BASE_URL=http://localhost:8082 AUTH_JWT_SECRET=local-hoctapaz-development-secret-change-before-deploy make test-profile-routes-livePROFILE_ROUTE_BROWSER_CONFIRM=profile-native PROFILE_ROUTE_BROWSER_SEED_CONFIRM=seed-profile PROFILE_ROUTE_BROWSER_ARTIFACT_DIR=output/playwright/profile-route-browser-smoke-20260709-121825 PROFILE_ROUTE_BROWSER_HEADLESS=1 WEB_BASE_URL=http://localhost:5212 GATEWAY_BASE_URL=http://localhost:18089 PROFILE_SERVICE_BASE_URL=http://localhost:8082 AUTH_JWT_SECRET=local-hoctapaz-development-secret-change-before-deploy make test-profile-routes-browser- JSON parse for touched
deploy/gateway/*.json.