Appearance
Handoff: SVC-020-attempt-grade-notification-producer-contract attempt grade notification producer contract
Scope
- Service/area:
attempt-serviceproducer andnotification-serviceevent contract. - Task pack:
docs/agents/service-tasks/notification-service.md - Goal: accept the bounded
SVC-020slice that names attempt-service as the grade-released notification producer and profile-service as the parent-recipient source while keeping notification-service as the replay/preference owner. - Non-goals: public/default route promotion, browser proof, gateway route changes, notification delivery-provider runtime, direct notification database writes from attempt-service, parent/student access authorization, live profile-service data migration, or changes to legacy source under
/Users/velikho/Desktop/WORKING/HOCTAPAZ/.
Docs Read
go-platform/docs/agents/service-agent-execution-queue.jsongo-platform/docs/agents/service-agent-closeout-ledger.jsongo-platform/docs/agents/service-tasks/notification-service.mdgo-platform/docs/agents/service-tasks/attempt-service.mdgo-platform/docs/agents/service-tasks/profile-service.mdgo-platform/docs/agents/agent-handoff-format.mdgo-platform/docs/api/attempt-service.mdgo-platform/docs/api/notification-service.mdgo-platform/docs/api/profile-service.mdgo-platform/services/attempt-service/README.mdgo-platform/services/notification-service/api/openapi.yamlgo-platform/services/profile-service/api/openapi.yaml
Legacy Evidence Read
/Users/velikho/Desktop/WORKING/HOCTAPAZ/node-platform/apps/api/src/modules/app-data/app-data.exam-runtime-core.ts/Users/velikho/Desktop/WORKING/HOCTAPAZ/node-platform/apps/api/src/modules/app-data/app-data.notifications.ts/Users/velikho/Desktop/WORKING/HOCTAPAZ/node-platform/apps/api/src/modules/notifications/notification.service.ts/Users/velikho/Desktop/WORKING/HOCTAPAZ/node-platform/apps/api/src/modules/notifications/notifications.controller.ts- Legacy source was read-only evidence only; no legacy files were changed.
Files Changed
go-platform/services/attempt-service/internal/usecase/attempt.gogo-platform/services/attempt-service/internal/usecase/answer.gogo-platform/services/attempt-service/internal/usecase/notification_events.gogo-platform/services/attempt-service/internal/usecase/attempt_test.gogo-platform/services/attempt-service/internal/http/server.gogo-platform/services/attempt-service/internal/notificationsink/client.gogo-platform/services/attempt-service/internal/notificationsink/client_test.gogo-platform/services/attempt-service/internal/profilesink/client.gogo-platform/services/attempt-service/internal/profilesink/client_test.gogo-platform/services/attempt-service/README.mdgo-platform/services/attempt-service/api/openapi.yamlgo-platform/contracts/openapi/services/attempt-service.yamlgo-platform/docs/api/attempt-service.mdgo-platform/docs/api/notification-service.mdgo-platform/docs/agents/service-tasks/attempt-service.mdgo-platform/docs/agents/service-tasks/notification-service.mdgo-platform/docs/agents/service-agent-execution-queue.jsongo-platform/docs/agents/handoffs/SVC-020-attempt-grade-notification-producer-contract.mdgo-platform/docs/agents/service-agent-closeout-ledger.jsongo-platform/docs/agents/service-agent-closeout-ledger.md
Behavior Implemented
- Added optional attempt-service notification publishing for
attempt.grade_released.notification.v1after first successful native submit/grade when copied result policy makes the result visible to the student. - Added stable source ids
{attemptId}:notification:grade_releasedfor students and{attemptId}:notification:grade_released_parentfor parents. - Added
preferenceType=GRADE_RELEASEDandpreferenceType=GRADE_RELEASED_PARENTevent contracts for notification-service. - Added profile-service parent-recipient resolution through
/v1/internal/profiles/parent-recipients/resolvewhenPROFILE_SERVICE_URLorPROFILE_SERVICE_BASE_URLis configured. - Kept notification publishing best-effort: notification-service failures and profile-service parent resolver failures do not fail submit/grading.
- Kept notification-service as inbox, replay, and preference owner; attempt-service only emits already-resolved event intents.
- Kept payloads intentionally narrow: no answers, answer keys, access-link ids, email, phone, or profile data are included.
Tests And Verification
- Command:
GOTOOLCHAIN=go1.25.11 go test ./services/attempt-service/internal/usecase ./services/attempt-service/internal/notificationsink ./services/attempt-service/internal/profilesink -run 'TestAttemptServicePublishesGradeReleasedNotificationsBestEffort|TestAttemptServiceSkipsGradeReleasedNotificationUntilResultVisible|TestAttemptServicePublishesStudentGradeNotificationWhenParentResolverFails|TestPublisherPublishesNotificationEventPayload|TestPublisherReportsNotificationHTTPFailure|TestResolverResolvesParentRecipients|TestResolverReportsProfileHTTPFailure' -count=1 -v - Result: passed.
- Evidence: focused tests covered stable source ids,
GRADE_RELEASEDandGRADE_RELEASED_PARENTpreference types, parent resolution through profile-service, best-effort notification failure handling, parent resolver failure handling, hidden-result skip, replay no-republish, and sanitized payload fields. - Command:
GOTOOLCHAIN=go1.25.11 go test ./services/attempt-service/... -count=1 - Result: passed.
- Evidence: attempt-service command, analytics sink, notification sink, profile sink, HTTP, repository, and usecase packages passed.
- Command:
GOTOOLCHAIN=go1.25.11 go test ./services/notification-service/... -count=1 - Result: passed.
- Evidence: notification-service command, auditsink, HTTP, repository, and usecase packages passed, including existing
GRADE_RELEASEDpreference/replay coverage. - Command:
make test-notification-routes test-parent-alert-routes test-attempt-routes - Result: passed.
- Evidence: default route tables remained legacy for notification, parent-alert, and attempt surfaces; non-default native rehearsal route tables still parsed.
- Command:
ruby -e 'require "yaml"; ARGV.each { |path| YAML.load_file(path); puts "ok #{path}" }' services/attempt-service/api/openapi.yaml contracts/openapi/services/attempt-service.yaml - Result: passed.
- Evidence: both attempt OpenAPI files parsed successfully.
- Command:
cmp -s services/attempt-service/api/openapi.yaml contracts/openapi/services/attempt-service.yaml - Result: passed.
- Evidence: service OpenAPI and contract mirror are byte-identical.
Route State
- Legacy route: public
/api/notifications*,/api/parent/alerts*,/api/alerts*,/api/admin/inbox*,/api/attempts*,/api/student/attempts, and/api/exams/:examId/startremain legacy-proxied by default. - Native route: attempt-service submit can publish to notification-service
/v1/events/notificationwhen env-configured; profile parent resolution uses profile-service/v1/internal/profiles/parent-recipients/resolvewhen env-configured. - Gateway state: no default route table was changed and no public notification/attempt route was promoted.
- Rollback: unset
NOTIFICATION_SERVICE_URL/NOTIFICATION_SERVICE_BASE_URLandPROFILE_SERVICE_URL/PROFILE_SERVICE_BASE_URLto disable the producer path while leaving core attempt writes local. Keepdeploy/gateway/routes.jsonas the active default route table.
Data Notes
- Tables touched: none in this handoff run; tests used in-memory stores.
- Migration run: none.
- Validation report: not applicable because no live data was migrated or backfilled.
Risks / Blockers
- Manual result release currently updates copied attempt visibility timestamps but does not back-publish grade notifications for already-graded attempts; this slice covers first submit/grade when the result is already visible.
- Parent relationship correctness depends on profile-service read-model freshness; IAM remains access-control owner and this producer does not grant parent access.
- No live profile-service/notification-service runtime proof or browser proof is included.
- Provider delivery attempts and push/email/SMS runtime dispatch remain notification-service delivery-slice work.
Next Step
- Add an outbox-backed or event-consumer slice for delayed manual/after-close grade-release notifications if product requirements need notifications when visibility changes after grading, then attach live notification-service/profile-service runtime proof and rollback artifacts before public route promotion.