Skip to content

Exam Start Adapter Task

Agent workflow: follow README.md for Audit -> Investigate -> Code -> Test -> Fix; keep this pack's writable scope and verification commands authoritative.

Dispatch type: orchestrator

Owner: orchestrator with exam-service, attempt-service, api-gateway, and optional bff-service workers.

Goal:

Promote student exam start only through an adapter that calls exam-service runtime access first, then attempt-service start with the resolver-owned data.startInput payload.

Legacy source evidence:

  • /Users/velikho/Desktop/WORKING/HOCTAPAZ/node-platform/apps/api/src/modules/exams/**
  • /Users/velikho/Desktop/WORKING/HOCTAPAZ/node-platform/apps/api/src/modules/attempts/**
  • /Users/velikho/Desktop/WORKING/HOCTAPAZ/node-platform/apps/web

Current foundation:

  • exam-service exposes POST /v1/exams/{id}/runtime-access.
  • The resolver returns data.startInput shaped for attempt-service and denies missing/wrong password, unpublished/offline/closed/not-open exams, inactive links, unassigned students through verifier, and missing snapshots.
  • Default gateway routes must keep /api/exams/:examId/start on legacy until this task has route proof.

Writable files:

  • deploy/gateway/routes.attempt-native-example.json
  • deploy/gateway/routes.attempt-native-localhost-example.json
  • services/api-gateway/**
  • services/bff-service/** only if BFF is explicitly chosen as the adapter host
  • services/attempt-service/** only for trusted decision/idempotency hardening
  • contracts/openapi/services/{api-gateway,bff-service,attempt-service}.yaml
  • docs/api/api-gateway.md
  • docs/api/exam-service.md
  • docs/api/attempt-service.md
  • docs/qa/attempt-live-smoke.md
  • docs/qa/attempt-browser-smoke.md
  • docs/qa/exam-authoring-route-rehearsals.md
  • scripts/test/attempt-route-coverage.sh
  • scripts/test/attempt-live-smoke.sh
  • scripts/test/attempt-browser-smoke.sh
  • scripts/test/attempt-browser-smoke.mjs
  • root Makefile only for test-attempt-routes, test-attempt-live, and test-attempt-browser
  • this task file

Delegated gateway verification scope:

  • Route examples: deploy/gateway/routes.attempt-native-example.json and deploy/gateway/routes.attempt-native-localhost-example.json.
  • Test scripts: scripts/test/attempt-route-coverage.sh, scripts/test/attempt-live-smoke.sh, scripts/test/attempt-browser-smoke.sh, and scripts/test/attempt-browser-smoke.mjs.
  • QA docs: docs/qa/attempt-live-smoke.md, docs/qa/attempt-browser-smoke.md, and docs/qa/exam-authoring-route-rehearsals.md.
  • Make targets: test-attempt-routes, test-attempt-live, and test-attempt-browser.

Orchestrator-owned files:

  • default gateway route promotion
  • frontend public route callers and browser smoke artifacts
  • production rollout/rollback notes

Must not edit:

  • exam-service policy rules unless the resolver contract itself changes.
  • frontend public route callers until the non-default adapter rehearsal is passing.

Tasks:

  • Choose adapter host: api-gateway aggregation or BFF write adapter. Do not split policy across both.
  • Adapter request flow:
    1. forward actor/org/correlation headers to exam-service runtime access
    2. if allowed=false, return a legacy-compatible denial without calling attempt-service
    3. if allowed=true, post only data.startInput to attempt-service
    4. propagate idempotency/correlation context through both calls
  • Add attempt-service hardening so public-compatible start aliases reject caller-supplied runtime/access bodies unless they are internal/trusted adapter calls.
  • Done locally: both attempt-service start endpoints now require X-Exam-Runtime-Decision-Source: exam-service plus idempotency/correlation context before accepting data.startInput.
  • Done locally: attempt-service usecase coverage proves attempt-owned retake/open-attempt/submitted count behavior after a trusted resolver decision, including access-link per-student limits, guest-link shared capacity, inactive/expired link rejection, overdue assignment rejection, deadline clamp to future assignment due times, and legacy expiry equality semantics.
  • Done locally: attempt-service persists the selected start idempotency or correlation value per exam/student, exact replays return the original attempt after submit/grade, and a partial unique index prevents concurrent duplicate IN_PROGRESS attempts for the same exam/student.
  • Done locally: api-gateway owns an exam_start_adapter route state for POST /api/exams/:examId/start. The adapter calls exam-service /v1/exams/{id}/runtime-access, returns a denial without calling attempt-service when allowed=false, and forwards only data.startInput to attempt-service /v1/exams/{id}/start with X-Internal-Service: api-gateway, the shared internal token, and the trusted runtime decision marker when allowed=true. Direct requests without that token are rejected by both native hops.
  • Done locally: non-default attempt route-table examples now use exam_target + attempt_target for the start adapter instead of a direct attempt-service proxy target.
  • Done locally: focused gateway tests prove /api/exams/:examId/start calls exam-service before attempt-service and never reaches attempt-service on a runtime-access denial.
  • Done locally: named opt-in route/live/browser proof hooks exist for the adapter flow without promoting the default route table. make test-attempt-routes validates the non-default route-table shape, make test-attempt-live checks /v1/routes plus gateway route headers against a running gateway, and make test-attempt-browser runs the same header checks from a Playwright browser page context.
  • Keep default deploy/gateway/routes.json on legacy until browser/route smoke has been run with real disposable fixtures and rollback evidence is recorded.

Acceptance:

  • Gateway/BFF cannot bypass exam-service link/password/window/assignment/snapshot decisions.
  • Attempt-service owns attempt counts and copied attempt snapshots; it does not query exam DBs.
  • Public start route has a rollback route table and a test command.

Verification:

  • GOTOOLCHAIN=go1.25.11 go test ./services/api-gateway/... ./services/attempt-service/...
  • make test-attempt-routes
  • ATTEMPT_LIVE_SELF_TEST=1 bash scripts/test/attempt-live-smoke.sh
  • ATTEMPT_BROWSER_SELF_TEST=1 bash scripts/test/attempt-browser-smoke.sh
  • ATTEMPT_LIVE_CONFIRM=attempt-native ATTEMPT_EXAM_ID=<published-exam-id> ATTEMPT_AUTHORIZATION='Bearer <student-token>' ATTEMPT_ORGANIZATION_ID=<org-id> make test-attempt-live
  • ATTEMPT_BROWSER_CONFIRM=attempt-native ATTEMPT_BROWSER_EXAM_ID=<published-exam-id> ATTEMPT_BROWSER_AUTHORIZATION='Bearer <student-token>' ATTEMPT_BROWSER_ORGANIZATION_ID=<org-id> make test-attempt-browser

Go-platform documentation is generated from repository Markdown.