Appearance
Attempt Public Surfaces 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: attempt-service.
Goal:
Prepare public attempt list/detail/start/save/submit/result surfaces without weakening exam assignment/access policy.
Legacy source evidence:
/Users/velikho/Desktop/WORKING/HOCTAPAZ/node-platform/apps/api/src/modules/exams/**/Users/velikho/Desktop/WORKING/HOCTAPAZ/node-platform/apps/api/prisma/schema.prisma- student/teacher attempt callers under
/Users/velikho/Desktop/WORKING/HOCTAPAZ/node-platform/apps/web.
Writable files:
services/attempt-service/**contracts/openapi/services/attempt-service.yamldocs/api/attempt-service.mddocs/agents/service-tasks/attempt-public-surfaces.md
Orchestrator-owned files:
- Gateway route tables, exam runtime cross-service contracts, frontend/browser smoke artifacts, and default route promotion.
Tasks:
- Keep student attempt list parity green:
GET /v1/student/attemptsbacks non-defaultGET /api/student/attemptsroute rehearsal with lightweight summaries, newest-first ordering, student scoping, and score redaction. - Done at usecase layer: access-policy tests cover own-student, other-student denial, parent denial until a relationship adapter exists, teacher/admin internal reads, event reads, result reads, student-owner-only event writes, and unauthenticated denial.
- Done at HTTP layer: attempt detail denies missing actor headers, other students, and parents while allowing owner-student and internal teacher/admin reads. Attempt event writes deny missing actor headers, other students, parents, teachers, and admins.
- Done at usecase layer: result visibility tests cover hidden scores, submitted
IMMEDIATE, hidden/releasedMANUAL, hidden/opened/releasedAFTER_CLOSE, and in-progress nil hidden reason. - Done at usecase layer: timeout result visibility is covered with a retake workflow. A first
TIMEOUTattempt underMANUALstays score-redacted for the student while teacher reads remain visible; a second retake attempt afterresultsReleasedAtexposes the timeout score to the student. - Done at service layer: teacher detail/events/result reads now require copied exam creator or managed-classroom teacher scope from the trusted start snapshot.
- Done at orchestrator layer: named attempt route coverage plus opt-in live and browser smoke targets now exist.
make test-attempt-routeskeeps the default table legacy and validates the non-defaultexam_start_adapterroute table.make test-attempt-livecan exercise/v1/routes,GET /api/student/attempts,POST /api/exams/:examId/start,GET /api/attempts/:attemptId,GET /api/attempts/:attemptId/events, andGET /api/attempts/:attemptId/resultagainst a running gateway with disposable fixtures; answer/event/submit writes require explicit request body or submit confirmation.make test-attempt-browserruns the same gateway header checks from a Playwright browser page context and writes artifacts underoutput/playwright/attempt-browser-smoke-*. - Remaining: run live gateway and browser proof with real disposable fixtures before default promotion.
- Integrate only after
exam-runtime-access.mdis complete. - For start promotion, the public adapter must call exam-service
POST /v1/exams/{id}/runtime-accessand forwarddata.startInputto attempt-service. Do not routePOST /api/exams/:examId/startdirectly to attempt-service.
Acceptance:
- Start requires hydrated exam runtime snapshot/access decision.
- Attempt snapshots remain independent of later question edits.
- Result reads preserve legacy redaction rules.
- Public list/detail/start/save/submit/result promotion requires named route tests, browser/runtime proof for the changed student/teacher path, and rollback evidence. Start promotion must use
exam-start-adapter.md.
Verification:
GOTOOLCHAIN=go1.25.11 go test ./services/attempt-service/...make test-attempt-routesATTEMPT_LIVE_SELF_TEST=1 bash scripts/test/attempt-live-smoke.shATTEMPT_LIVE_CONFIRM=attempt-native ATTEMPT_EXAM_ID=<published-exam-id> ATTEMPT_AUTHORIZATION='Bearer <student-token>' ATTEMPT_ORGANIZATION_ID=<org-id> make test-attempt-liveATTEMPT_BROWSER_SELF_TEST=1 bash scripts/test/attempt-browser-smoke.shATTEMPT_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