EVENT_SCHEMAS — reporting-service
Sibling: DOMAIN_MODEL · APPLICATION_LOGIC · API_CONTRACTS · DATA_MODEL
Strategic anchors: 04 Event-Driven Architecture · standards/NAMING · standards/ERROR_CODES
All events follow the platform event subject grammar: melmastoon.reporting.<aggregate>.<verb-past-tense>.v<n>. JSON Schemas are committed to event-schemas/melmastoon/reporting/<aggregate>/<verb>.v<n>.json and validated in CI on every PR.
1. Common envelope
Every event is wrapped in the canonical EventEnvelope<T> from 04 §4. Reporting-specific defaults:
| Field | Value |
|---|---|
producedBy.service | reporting-service |
metadata.dataResidency | <tenant.region> (typically gcp-me-central1, gcp-asia-south1, or gcp-europe-west4) |
metadata.orderingKey | <tenantId>:<runId> for run events; <tenantId>:<scheduleId> for schedule events; <tenantId>:<subscriptionId> for subscription events; <tenantId>:<templateId> for template events |
causationId | the consumed event id for inbox-driven emissions; absent for command-driven emissions |
2. Subject taxonomy + retention + partition
| Subject | Retention class | Partition key |
|---|---|---|
melmastoon.reporting.report.requested.v1 | operational (90d) | tenantId:runId |
melmastoon.reporting.report.started.v1 | operational (90d) | tenantId:runId |
melmastoon.reporting.report.completed.v1 | regulated (7y for regulatory templates; operational 1y otherwise) | tenantId:runId |
melmastoon.reporting.report.failed.v1 | operational (1y) | tenantId:runId |
melmastoon.reporting.report.delivered.v1 | regulated (7y) | tenantId:subscriptionId |
melmastoon.reporting.report.cancelled.v1 | operational (1y) | tenantId:runId |
melmastoon.reporting.template.published.v1 | regulated (10y) | tenantId:templateId |
melmastoon.reporting.template.archived.v1 | regulated (10y) | tenantId:templateId |
melmastoon.reporting.schedule.created.v1 | operational (2y) | tenantId:scheduleId |
melmastoon.reporting.schedule.fired.v1 | operational (90d) | tenantId:scheduleId |
melmastoon.reporting.schedule.disabled.v1 | operational (2y) | tenantId:scheduleId |
melmastoon.reporting.subscription.created.v1 | operational (2y) | tenantId:subscriptionId |
melmastoon.reporting.subscription.cancelled.v1 | operational (2y) | tenantId:subscriptionId |
melmastoon.reporting.regulatory.submission_due.v1 | regulated (10y) | tenantId:submissionId |
melmastoon.reporting.regulatory.submission_succeeded.v1 | regulated (10y) | tenantId:submissionId |
melmastoon.reporting.regulatory.submission_failed.v1 | regulated (10y) | tenantId:submissionId |
Retention classes follow 04 §5. Sensitive PII never lives in event payloads — recipient emails are reduced to email_hash; report titles in displayName may pass through if the template is non-regulatory and tenant policy allows.
3. Published events — payload + JSON schema sketch + example
3.1 melmastoon.reporting.report.requested.v1
interface ReportRequestedV1 {
runId: string; // run_…
tenantId: string; // tnt_…
reportId: string; // rep_…
templateId: string; // tpl_rep_…
templateVersionNumber: number;
resolvedFilters: Record<string, unknown>; // post-validation, pre-render
formats: Array<'pdf'|'xlsx'|'csv'>;
locale: string; // BCP-47
requestedBy: { type: 'user'|'system'|'scheduler'|'partner'; id: string };
correlationId: string;
idempotencyKey: string;
queuedAt: string; // RFC3339
}
Example envelope:
{
"id": "evt_01H...",
"subject": "melmastoon.reporting.report.requested.v1",
"occurredAt": "2026-04-22T10:00:00Z",
"producedBy": { "service": "reporting-service", "version": "1.4.0" },
"tenantId": "tnt_01H...",
"metadata": { "orderingKey": "tnt_01H...:run_01H...", "dataResidency": "gcp-me-central1" },
"data": {
"runId": "run_01H...",
"tenantId": "tnt_01H...",
"reportId": "rep_01H...",
"templateId": "tpl_rep_01H...",
"templateVersionNumber": 4,
"resolvedFilters": { "propertyId": "ppt_01H...", "stayDate": { "from": "2026-04-22", "to": "2026-04-22" } },
"formats": ["pdf","csv"],
"locale": "fa-AF",
"requestedBy": { "type": "scheduler", "id": "sch_01H..." },
"correlationId": "01H...",
"idempotencyKey": "01H...",
"queuedAt": "2026-04-22T10:00:00Z"
}
}
3.2 melmastoon.reporting.report.started.v1
interface ReportStartedV1 {
runId: string; tenantId: string; reportId: string;
templateVersionNumber: number;
startedAt: string;
workerInstance: string; // Cloud Run instance id (for ops)
}
3.3 melmastoon.reporting.report.completed.v1
interface ReportCompletedV1 {
runId: string; tenantId: string; reportId: string;
templateId: string; templateVersionId: string; templateVersionNumber: number;
completedAt: string;
artifacts: Array<{
artifactId: string; // art_…
format: 'pdf'|'xlsx'|'csv';
locale: string;
sizeBytes: number;
sha256: string;
objectPath: string; // 'tnt_…/run_…/<sha>.pdf'
retentionClass: 'operational_2y'|'operational_7y'|'regulatory_10y_objectlock';
}>;
regulatory?: { jurisdictionCode: string; adapterRef: string }; // when template is regulatory
aiProvenance?: AIProvenance; // when AI assisted (anomaly callouts)
}
JSON Schema (excerpt):
{
"$id": "https://schemas.melmastoon.ghasi.io/reporting/report/completed.v1.json",
"type": "object",
"required": ["runId","tenantId","reportId","templateId","templateVersionId","templateVersionNumber","completedAt","artifacts"],
"properties": {
"runId": { "type": "string", "pattern": "^run_[0-9A-HJKMNP-TV-Z]{26}$" },
"tenantId": { "type": "string", "pattern": "^tnt_[0-9A-HJKMNP-TV-Z]{26}$" },
"artifacts": {
"type": "array", "minItems": 1,
"items": { "type": "object",
"required": ["artifactId","format","locale","sizeBytes","sha256","objectPath","retentionClass"],
"properties": {
"format": { "enum": ["pdf","xlsx","csv"] },
"retentionClass": { "enum": ["operational_2y","operational_7y","regulatory_10y_objectlock"] },
"sha256": { "type": "string", "pattern": "^[a-f0-9]{64}$" }
}
}
}
}
}
3.4 melmastoon.reporting.report.failed.v1
interface ReportFailedV1 {
runId: string; tenantId: string; reportId: string;
templateVersionNumber: number;
failedAt: string;
errorCode: string; // 'MELMASTOON.REPORTING.…' or 'MELMASTOON.GENERAL.INTERNAL'
errorDetail: string; // safe-to-show summary; never includes PII
retriable: boolean;
retryCount: number;
willRetry: boolean;
nextAttemptAt?: string;
}
3.5 melmastoon.reporting.report.delivered.v1
interface ReportDeliveredV1 {
runId: string; tenantId: string; reportId: string;
subscriptionId: string;
channel: 'email'|'in_app'|'desktop_sync'|'webdav'|'sftp';
recipientHash: string; // sha256(tenantSalt + canonical recipient)
deliveredAt: string;
proof?: { kind: 'smtp_250'|'in_app_ack'|'sync_pulled'|'webdav_201'|'sftp_201'; reference?: string };
}
3.6 melmastoon.reporting.report.cancelled.v1
interface ReportCancelledV1 {
runId: string; tenantId: string; reportId: string;
cancelledAt: string;
cancelledBy: { type: 'user'|'system'; id: string };
reason: 'manual'|'scheduler_disabled'|'template_archived'|'tenant_suspended';
}
3.7 melmastoon.reporting.template.published.v1
interface TemplatePublishedV1 {
templateId: string; tenantId: string | null;
templateVersionId: string; versionNumber: number;
key: string; category: 'operational'|'financial'|'compliance'|'regulatory'|'manager_dashboard';
regulatory: boolean;
jurisdictionCode?: string;
retentionClass: 'operational_2y'|'operational_7y'|'regulatory_10y_objectlock';
supportedFormats: Array<'pdf'|'xlsx'|'csv'>;
publishedAt: string;
publishedBy: { type: 'user'|'platform_admin'; id: string };
changesetSummary?: string; // human note from publisher
}
3.8 melmastoon.reporting.template.archived.v1
interface TemplateArchivedV1 {
templateId: string; tenantId: string | null;
archivedAt: string;
archivedBy: { type: 'user'|'platform_admin'; id: string };
reason?: string;
}
3.9 melmastoon.reporting.schedule.created.v1
interface ScheduleCreatedV1 {
scheduleId: string; tenantId: string; reportId: string;
cronExpr: string; timezone: string;
templateVersionPin: number | null;
filters: Record<string, unknown>;
subscriptionIds: string[];
createdAt: string;
createdBy: { type: 'user'; id: string };
}
3.10 melmastoon.reporting.schedule.fired.v1
interface ScheduleFiredV1 {
scheduleId: string; tenantId: string; reportId: string;
firedAt: string; // when Cloud Scheduler hit our endpoint
expectedAt: string; // ideal cron time
driftMs: number; // firedAt - expectedAt
firedRunId?: string; // populated once dispatch succeeded
}
3.11 melmastoon.reporting.schedule.disabled.v1
interface ScheduleDisabledV1 {
scheduleId: string; tenantId: string;
disabledAt: string;
reason: 'manual'|'consecutive_failures'|'template_archived'|'tenant_suspended';
consecutiveFailures: number;
}
3.12 melmastoon.reporting.subscription.created.v1
interface SubscriptionCreatedV1 {
subscriptionId: string; tenantId: string; reportId: string;
recipient: { kind: 'user'|'email'|'desktop_device'|'webdav'|'sftp'; ref: string }; // email reduced to email_hash
channel: 'email'|'in_app'|'desktop_sync'|'webdav'|'sftp';
format: 'pdf'|'xlsx'|'csv';
locale: string;
createdAt: string;
createdBy: { type: 'user'|'system'; id: string };
}
3.13 melmastoon.reporting.subscription.cancelled.v1
interface SubscriptionCancelledV1 {
subscriptionId: string; tenantId: string; reportId: string;
cancelledAt: string;
cancelledBy: { type: 'user'|'system'; id: string };
reason: 'manual'|'recipient_unsubscribed'|'recipient_invalid'|'tenant_suspended';
}
3.14 melmastoon.reporting.regulatory.submission_due.v1
Emitted when a regulatory submission has failed terminally OR when the regulatory window is approaching its deadline without a successful submission. Used by notification-service to escalate to tenant.owner.
interface RegulatorySubmissionDueV1 {
submissionId: string; runId: string; tenantId: string;
jurisdictionCode: string;
adapterRef: string;
due: 'window_approaching' | 'window_missed' | 'retry_exhausted';
windowEndsAt: string;
failureCode?: string;
failureDetail?: string;
attempts: number;
emittedAt: string;
}
3.15 melmastoon.reporting.regulatory.submission_succeeded.v1
interface RegulatorySucceededV1 {
submissionId: string; runId: string; tenantId: string;
jurisdictionCode: string;
adapterRef: string;
attempts: number;
succeededAt: string;
proofOfDelivery: {
receiptKind: 'http_2xx_body'|'signed_xml_receipt'|'sftp_inode_ack'|'paper_print_signature';
receiptHash: string;
receivedAt: string;
registerReference?: string;
storedAtObjectPath: string;
};
}
3.16 melmastoon.reporting.regulatory.submission_failed.v1
interface RegulatoryFailedV1 {
submissionId: string; runId: string; tenantId: string;
jurisdictionCode: string;
adapterRef: string;
attempts: number;
failedAt: string;
errorCode: string; // adapter-mapped, prefixed 'MELMASTOON.REPORTING.SUBMISSION.…'
errorDetail: string;
willRetry: boolean;
nextAttemptAt?: string;
}
4. Consumed events (inbox)
| Subject | Producer | Effect |
|---|---|---|
melmastoon.reporting.report.completed.v1 (self) | this service | Trigger subscription dispatch + (if regulatory) regulatory dispatch |
melmastoon.notification.delivery.recorded.v1 | notification-service | Update ReportSubscription.lastDeliveryStatus; emit report.delivered.v1 |
melmastoon.tenant.settings.changed.v1 | tenant-service | Refresh tenant cache (jurisdiction, locale, branding ref) |
melmastoon.theme_config.theme.updated.v1 | theme-config-service | Invalidate branding cache for the tenant |
melmastoon.tenant.deleted.v1 | tenant-service | Cascade soft-delete templates (tenant-private), schedules, subscriptions; mark artifacts for retention-respectful purge |
melmastoon.analytics.projection.refreshed.v1 | analytics-service | (Optional) trigger near-realtime template re-render for streaming reports |
internal report.run.queued (worker queue) | self | Worker pulls and runs StartReportRunUseCase |
internal report.run.requeue (backoff) | self | Re-queue after delay computed by ReportRun.retryAfter() |
All consumers run inside InboxIdempotency middleware keyed by (subject, messageId).
5. Versioning policy
- Additive changes only within
vN: optional new fields with safe defaults. - Breaking change (rename, type change, semantic change, required field removal) requires a new
v(N+1). Both versions ship in parallel for ≥ 60 days; consumer migration is tracked via the schema registry consumer-status board. - Deprecation marks the old version
deprecated=truein the registry and emits a daily warning metricreporting_event_deprecated_consumed_totaluntil consumers cut over.