Skip to content

Operational Producer Events Task Pack

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 the producing service owner and the consuming service owner. Do not treat this pack as a standalone deployable service.

Purpose: finish versioned, replay-safe producer event contracts between owner services and the operational services that store copied views or delivery state: analytics-service, notification-service, and audit-service.

Must read:

  • docs/architecture/service-responsibility-matrix.md
  • docs/agents/service-workflow-test-matrix.md
  • producing service task pack
  • consuming service task pack
  • consuming service OpenAPI contract

Legacy source evidence:

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

Writable files:

  • The selected producer service files only, for example services/attempt-service/** for an attempt analytics producer.
  • The selected producer service API docs/OpenAPI mirrors only if the public or native service contract changes.
  • The selected producer service task pack.
  • The selected consumer task pack only for status/handoff wording.

Orchestrator-owned files:

  • Shared route tables, root Makefile, Compose, Helm, VitePress navigation, this pack, and service wave board updates unless explicitly delegated.

Event contract invariants:

  • Producers must call the consumer service API; they must never read or write a consumer service database directly.
  • Every replayable event must carry sourceService, stable sourceEventId, event type, entity, entity id, occurred timestamp, organization scope when available, and redacted payload metadata.
  • Producer delivery must be best-effort or outbox-backed so the producer's canonical write path is not made unavailable by an operational projection service outage.
  • Consumer idempotency must be by sourceService + sourceEventId.
  • Public /api/* promotion is out of scope unless a dedicated gateway task adds route coverage, browser/runtime proof, and rollback evidence.

Current priority slices:

  • Done: Attempt -> Analytics publishes attempt.submitted.v1 and attempt.graded.v1 lifecycle/result metadata to analytics-service event ingestion with replay-safe source ids.
  • Done: Course -> Analytics publishes course.enrollment.granted.v1 metadata on successful enrollment grants with replay-safe source ids.
  • Done: Course -> Analytics publishes course.lesson_progress.saved.v1 metadata from RecordLessonProgress, with mutable progress source ids that include the saved lastViewedAt timestamp.
  • Done: Course -> Analytics publishes course.video_progress.saved.v1 and course.material_view.saved.v1 with timestamped source ids and redacted display metadata only.
  • Done: Exam -> Analytics publishes exam.published.v1 metadata after native publish succeeds, with replay-safe {examId}:published source ids and redacted access-link metadata.
  • Done: Exam -> Analytics publishes exam.assigned.v1 metadata after native published-exam classroom assignment upserts, with replay-safe {assignmentId}:assigned source ids and redacted exam/assignment refs.
  • Done: Exam -> Analytics publishes exam.results_released.v1 metadata after native published/closed exam result release, with stable exam:{examId}:results-released source ids and redacted exam visibility metadata only.
  • Done: Exam -> Notification publishes resolved-recipient exam.assigned.notification.v1 events after native published-exam assignment when the internal assignment request supplies student/parent recipients. Source ids are {assignmentId}:notification:exam_assigned and {assignmentId}:notification:exam_assigned_parent; notification-service owns replay/preference handling, while exam-service does not discover recipients.
  • Done: Classroom -> Notification publishes classroom.notification.sent.v1 events from internal POST /v1/classrooms/{classroomId}/notifications. Source ids are classroom:{classroomId}:notification:{clientRequestId} and student recipients come from classroom-service owned STUDENT member rows only. When profile-service is configured, classroom-service calls /v1/internal/profiles/parent-recipients/resolve with those student ids and publishes a separate classroom.notification.parent.sent.v1 event with source id classroom:{classroomId}:notification:{clientRequestId}:parents.
  • Done: Exam -> Attempt result-policy propagation sends exam.results_released.v1 through authenticated internal POST /v1/events/exam-results-released, updating copied exam_results_released_at snapshots by examId without cross-service database access or public route promotion. The direct handoff is best-effort and replay-safe; durable outbox delivery remains a later hardening slice.
  • Done: Classroom -> Analytics publishes classroom.membership.snapshot.v1 after native membership mutations by reloading classroom + member refs and excluding profile PII. Source ids use classroom:{classroomId}:membership-snapshot:{occurredAtRFC3339Nano}.
  • Done: Admin -> Notification publishes explicit-recipient admin.notification.broadcast.v1 events from internal POST /v1/admin/notification-broadcasts. Source ids are admin:broadcast:{clientRequestId} and recipients must be supplied by the caller; admin-service does not resolve legacy ALL/ROLES targets.
  • Support -> Notification: publish notification intent events only with already-resolved recipients or with an approved resolver handoff. Notification-service must not discover recipients by cross-service database joins. Notification-service is consumer-ready for POST /v1/events/notification and POST /v1/events/weak-topic-alert; the first native support producer is now available at admin-service POST /v1/support/notification-events for callers that already have concrete recipients. It emits support.ticket.notification.v1 with sourceEventId=support:ticket:{ticketId}:notification:{clientRequestId} and preserves legacy inbox types such as SUPPORT_TICKET_CREATED, SUPPORT_TICKET_REPLY, and SUPPORT_TICKET_STATUS. Recipient discovery, support ticket persistence, public /api/support* route cutover, and runtime proof remain separate work.
  • IAM/Admin/Gateway -> Audit: publish redacted security/admin/route operation events to audit-service with replay-safe source ids and no secrets. IAM, admin, payment, billing, wallet, notification, and profile have first best-effort audit projection slices; gateway now has a first best-effort route-operation projection using sourceService=api-gateway, sourceEventId=gateway:route:<requestId>, action=gateway.route.request, and redacted query-key-only metadata. Richer organization-membership audit semantics, outbox-backed retry/replay, runtime smoke, and rollback evidence remain separate work.

Acceptance:

  • The selected producer has focused tests proving payload shape and replay id.
  • The selected producer has a test for disabled or upstream-failure behavior.
  • The selected consumer task pack records whether the handoff is done or still pending.
  • OpenAPI service and contract mirrors remain byte-identical if changed.
  • Default public route behavior is unchanged.

Verification:

  • Producer service package tests, for example GOTOOLCHAIN=go1.25.11 go test ./services/attempt-service/... -count=1
  • Consumer service package tests when the consumer contract changed.
  • OpenAPI YAML parse and mirror diff for changed service contracts.
  • make test-service-task-packs
  • pnpm docs:build
  • git diff --check

Closeout:

  • Name the producer and consumer services.
  • List the event type, source id format, and target endpoint.
  • List failure behavior: disabled, best-effort failure, retry, or outbox.
  • State that no public route was promoted unless a gateway task proves it.

Go-platform documentation is generated from repository Markdown.