Skip to content

Handoff: SVC-020-attempt-grade-notification-producer-contract attempt grade notification producer contract

Scope

  • Service/area: attempt-service producer and notification-service event contract.
  • Task pack: docs/agents/service-tasks/notification-service.md
  • Goal: accept the bounded SVC-020 slice 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.json
  • go-platform/docs/agents/service-agent-closeout-ledger.json
  • go-platform/docs/agents/service-tasks/notification-service.md
  • go-platform/docs/agents/service-tasks/attempt-service.md
  • go-platform/docs/agents/service-tasks/profile-service.md
  • go-platform/docs/agents/agent-handoff-format.md
  • go-platform/docs/api/attempt-service.md
  • go-platform/docs/api/notification-service.md
  • go-platform/docs/api/profile-service.md
  • go-platform/services/attempt-service/README.md
  • go-platform/services/notification-service/api/openapi.yaml
  • go-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.go
  • go-platform/services/attempt-service/internal/usecase/answer.go
  • go-platform/services/attempt-service/internal/usecase/notification_events.go
  • go-platform/services/attempt-service/internal/usecase/attempt_test.go
  • go-platform/services/attempt-service/internal/http/server.go
  • go-platform/services/attempt-service/internal/notificationsink/client.go
  • go-platform/services/attempt-service/internal/notificationsink/client_test.go
  • go-platform/services/attempt-service/internal/profilesink/client.go
  • go-platform/services/attempt-service/internal/profilesink/client_test.go
  • go-platform/services/attempt-service/README.md
  • go-platform/services/attempt-service/api/openapi.yaml
  • go-platform/contracts/openapi/services/attempt-service.yaml
  • go-platform/docs/api/attempt-service.md
  • go-platform/docs/api/notification-service.md
  • go-platform/docs/agents/service-tasks/attempt-service.md
  • go-platform/docs/agents/service-tasks/notification-service.md
  • go-platform/docs/agents/service-agent-execution-queue.json
  • go-platform/docs/agents/handoffs/SVC-020-attempt-grade-notification-producer-contract.md
  • go-platform/docs/agents/service-agent-closeout-ledger.json
  • go-platform/docs/agents/service-agent-closeout-ledger.md

Behavior Implemented

  • Added optional attempt-service notification publishing for attempt.grade_released.notification.v1 after first successful native submit/grade when copied result policy makes the result visible to the student.
  • Added stable source ids {attemptId}:notification:grade_released for students and {attemptId}:notification:grade_released_parent for parents.
  • Added preferenceType=GRADE_RELEASED and preferenceType=GRADE_RELEASED_PARENT event contracts for notification-service.
  • Added profile-service parent-recipient resolution through /v1/internal/profiles/parent-recipients/resolve when PROFILE_SERVICE_URL or PROFILE_SERVICE_BASE_URL is 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_RELEASED and GRADE_RELEASED_PARENT preference 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_RELEASED preference/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/start remain legacy-proxied by default.
  • Native route: attempt-service submit can publish to notification-service /v1/events/notification when env-configured; profile parent resolution uses profile-service /v1/internal/profiles/parent-recipients/resolve when 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_URL and PROFILE_SERVICE_URL/PROFILE_SERVICE_BASE_URL to disable the producer path while leaving core attempt writes local. Keep deploy/gateway/routes.json as 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.

Go-platform documentation is generated from repository Markdown.