Skip to content

Handoff: SVC-020-notification-recipient-route-proof notification recipient route proof

Scope

  • Service/area: attempt-service producer and notification-service recipient/replay boundary.
  • Goal: prove notification events carry already-resolved recipient ids and that notification, parent-alert, and attempt route rehearsals retain explicit rollback state.
  • Non-goals: public/default route promotion, browser proof, gateway route changes, recipient discovery, parent-student authorization, delivery-provider runtime, provider secrets, or changes to legacy source.

Docs Read

  • docs/agents/handoff-phase-9-notification-foundation.md
  • docs/agents/service-tasks/notification-service.md
  • docs/agents/handoffs/SVC-020-attempt-grade-notification-producer-contract.md
  • docs/agents/service-tasks/attempt-service.md
  • docs/agents/service-tasks/profile-service.md
  • docs/agents/service-agent-next-slice-queue.json
  • docs/agents/service-agent-next-slice-closeout-ledger.json
  • docs/agents/agent-handoff-format.md
  • docs/api/attempt-service.md
  • docs/api/notification-service.md
  • docs/api/profile-service.md
  • docs/qa/notification-route-rehearsals.md
  • docs/qa/parent-alert-route-rehearsals.md

Legacy Evidence Read

  • /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
  • /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/analytics/analytics.controller.ts
  • /Users/velikho/Desktop/WORKING/HOCTAPAZ/node-platform/apps/api/src/modules/admin/inbox.controller.ts
  • /Users/velikho/Desktop/WORKING/HOCTAPAZ/node-platform/apps/api/src/modules/support/support.service.ts
  • /Users/velikho/Desktop/WORKING/HOCTAPAZ/node-platform/apps/api/src/modules/app-data/app-data.classrooms-engagement.ts
  • /Users/velikho/Desktop/WORKING/HOCTAPAZ/node-platform/apps/api/src/modules/app-data/app-data.exam-runtime-core.ts
  • Legacy source was read-only evidence; no files under /Users/velikho/Desktop/WORKING/HOCTAPAZ/ were changed.

Files Changed

  • docs/agents/handoffs/SVC-020-notification-recipient-route-proof.md
  • No attempt-service implementation, notification-service implementation, gateway route, migration, or legacy file was changed by this proof handoff.

Behavior Implemented

  • None in this handoff. Existing producers already resolve parent recipients through profile-service or accept explicit recipient ids before publishing; notification-service remains the replay, preference, inbox, and delivery-state owner.

Tests And Verification

  • Command: GOTOOLCHAIN=go1.25.11 go test ./services/attempt-service/...
  • Result: passed with -count=1.
  • Evidence: attempt-service HTTP, use-case, notification-sink, profile-sink, analytics-sink, and repository packages passed.
  • Command: GOTOOLCHAIN=go1.25.11 go test ./services/notification-service/...
  • Result: passed with -count=1.
  • Evidence: notification-service auditsink, HTTP, repository, and use-case packages passed, including replay and preference tests.
  • Command: make test-notification-routes test-parent-alert-routes test-attempt-routes
  • Result: passed.
  • Evidence: default route tables stayed legacy and all three non-default notification, parent-alert, and attempt rehearsal tables passed method/order/auth guards.
  • Required verification: the three commands above match both workflowVerification and requiredVerification for SVC-020-notification-recipient-route-proof.

Current Reverification (2026-07-13)

  • Command: GOTOOLCHAIN=go1.25.11 go test ./services/attempt-service/... -count=1, GOTOOLCHAIN=go1.25.11 go test ./services/notification-service/... -count=1, and make test-notification-routes test-parent-alert-routes test-attempt-routes.
  • Result: passed. This covered the attempt producer profile/notification sinks, notification replay and preference packages, and the notification, parent-alert, and attempt default/non-default route guards.
  • Runtime: a disposable hoctapaz-notification-proof-20260713 Compose project created only two explicit-recipient events: an analytics weak-topic event with parentIds=[parent_svc020_20260713] and an attempt grade-release event with userId=student_svc020_20260713. Replaying the weak-topic source created 0 new rows; replaying the attempt source returned the original event id. Artifact: output/qa/notification-recipient-route-proof-20260713/.
  • Parent Gateway rehearsal: an HS256 PARENT token reached GET /api/parent/alerts, POST /api/parent/alerts/{id}/read, and POST /api/parent/alerts/read-all through deploy/gateway/routes.parent-alerts-native-localhost-example.json on a temporary Gateway. Responses carried parent-alerts-list / parent-alerts-read / parent-alerts-read-all with native_read or native_write; a STUDENT token was rejected with 403 AUTH_FORBIDDEN.
  • Notification Gateway rehearsal: the temporary Gateway then used deploy/gateway/routes.notifications-native-localhost-example.json. A STUDENT token reached list, detail, mark-read, and preferences routes with the expected notifications-* native route headers and a persisted readAt value. This proves Gateway actor injection reaches the notification owner without recipient discovery.
  • Rollback: the same temporary Gateway was restarted with GATEWAY_ROUTE_TABLE=deploy/gateway/routes.json. GET /api/notifications immediately returned route state legacy_proxy; its 502 came from the deliberately unreachable proof legacy target, confirming selection returned to the default legacy route rather than native notification-service.

Follow-up Candidate Recheck (2026-07-15)

  • Commands: make test-notification-routes test-parent-alert-routes, NOTIFICATION_EVENT_FANOUT_POSTGRES_CONFIRM=disposable-postgres make test-notification-event-fanout-postgres, and make test-student-notification-browser all passed.
  • Browser artifact: output/qa/student-notification-browser-local-runtime-20260715-034031/ used a memory-only notification-service, temporary Gateway table, and Student Vite process. An anonymous list was denied; the browser then listed two owner rows, marked one and all read, and read/wrote preferences through the five expected native candidate routes. The shell list check was 1.06 ms, the browser trace was 14 ms, and the request trace stores Bearer [redacted] only.
  • Storage artifact: output/qa/notification-event-fanout-postgres-20260715-034023/ revalidated rollback of the source event and recipient rows on an injected second-recipient failure, then exact-once retry/replay behavior in a disposable Postgres container. The browser runtime directory and container were removed; default deploy/gateway/routes.json had no diff.

Required Evidence

  • already-resolved-recipient proof: attempt-service profile-sink tests resolve parent_1 through /v1/internal/profiles/parent-recipients/resolve; producer tests verify GRADE_RELEASED and GRADE_RELEASED_PARENT events carry concrete recipient ids, while notification-service event adapters require explicit recipients and do not perform relationship lookup. The current runtime artifact uses supplied parent/student IDs and verifies source replay cannot duplicate their owner rows.
  • notification route proof: make test-notification-routes test-parent-alert-routes test-attempt-routes passed. Fresh non-default Gateway calls cover parent alert list/read/read-all with PARENT enforcement plus notification list/detail/read/preferences with actor injection. docs/qa/notification-route-rehearsals.md and docs/qa/parent-alert-route-rehearsals.md retain the bounded route/auth scope.
  • rollback notes: deploy/gateway/routes.json remains the default rollback table. A temporary live restart proved /api/notifications returns to legacy_proxy; broad notification, parent-alert, attempt, admin inbox, and fallback routes remain legacy.

Route State

  • Legacy route: public /api/notifications*, /api/parent/alerts*, /api/alerts*, /api/admin/inbox*, and attempt routes remain legacy-proxied by default.
  • Native route: non-default notification and parent-alert route-table rehearsals cover read/preferences and bounded parent alert reads; attempt-service can publish service-local notification events when its sink and profile resolver are configured.
  • Gateway state: public notification, parent-alert, and attempt routes remain default legacy; producer contract is service-local until live/browser and rollback proof exist; no default route table was changed.
  • Delegated gateway scope: route proof only; default gateway route tables remain orchestrator-owned unless a later slice produces reviewed promotion and rollback evidence.
  • Rollback: keep GATEWAY_ROUTE_TABLE=deploy/gateway/routes.json; unset NOTIFICATION_SERVICE_URL/NOTIFICATION_SERVICE_BASE_URL and profile resolver URLs to disable producer-side calls without changing core attempt writes.

Data Notes

  • Tables touched: only the disposable notification database in the named Compose proof project; no shared or legacy table was read or written.
  • Migration run: notification-service migration ran inside hoctapaz-notification-proof-20260713.
  • Validation report: docker compose -p hoctapaz-notification-proof-20260713 -f deploy/docker-compose.yml down -v --remove-orphans completed; no project container, volume, or listener remained on 5433, 8092, or 18093.

Risks / Blockers

  • The current proof includes a Student inbox/preferences browser candidate, but not notification-bell or parent-alert UI parity; both remain separate promotion gates.
  • Parent relationship freshness remains profile-service responsibility; notification-service does not grant parent access or query classroom/profile databases.
  • Provider delivery attempts, email/SMS/push runtime dispatch, admin recipient discovery, and support ticket storage remain separate slices.
  • This bounded handoff does not complete notification-service or the broader service rollout goal.

Next Step

  • Run browser rechecks for the notification bell and parent alert UI against the same non-default route tables, then review any default route promotion independently from recipient discovery and provider delivery work.

Go-platform documentation is generated from repository Markdown.