From c817ec1da0a3cd07f7afb2f778c3fb4cbbd5c077 Mon Sep 17 00:00:00 2001 From: Antoine Toussaint <312536060+antoinetoussaint-byte@users.noreply.github.com> Date: Mon, 12 Oct 2026 19:13:39 -0400 Subject: [PATCH 1/2] feat(audit): register the six reserved types the document module emits A document module reports six audit types in the host's reserved `saas` namespace, which a module cannot declare through DeclareAuditEventTypes. The host refused them, so they stayed undelivered in the module's outbox: saas.datasource.protection_changed, saas.document.source_verified, saas.document.read_unscoped, saas.document.search_unscoped, saas.document.confirmed and saas.document.extractor_revised. Registered in the canonical registry with the payload the module sends; the events contribution, event catalog, asyncapi and communication docs are regenerated by the declared composition generator. Co-Authored-By: Claude Sonnet 5.5 --- .../accounts/saas.events.codefly.yaml | 24 + module/deployment/generated/asyncapi.json | 150 +++++ module/deployment/generated/communication.md | 48 ++ .../deployment/generated/event-catalog.json | 628 +++++++++++++++++- .../code/pkg/business/audit_parity_test.go | 8 + .../code/pkg/business/audit_registry.go | 94 +++ .../code/pkg/business/audit_registry_test.go | 102 +++ .../code/pkg/eventcatalog/catalog_gen.go | 6 + 8 files changed, 1046 insertions(+), 14 deletions(-) diff --git a/module/contracts/accounts/saas.events.codefly.yaml b/module/contracts/accounts/saas.events.codefly.yaml index 65de25b29..c1186439c 100644 --- a/module/contracts/accounts/saas.events.codefly.yaml +++ b/module/contracts/accounts/saas.events.codefly.yaml @@ -207,6 +207,10 @@ publishes: schema: saas/events/v1/events.proto#EventEnvelope visibility: external retention: 30d + - type: saas.datasource.protection_changed + schema: saas/events/v1/events.proto#EventEnvelope + visibility: external + retention: 30d - type: saas.datasource.receipts.pruned schema: saas/events/v1/events.proto#EventEnvelope visibility: external @@ -267,6 +271,10 @@ publishes: schema: saas/events/v1/events.proto#EventEnvelope visibility: external retention: 30d + - type: saas.document.confirmed + schema: saas/events/v1/events.proto#EventEnvelope + visibility: external + retention: 30d - type: saas.document.dead_letter_redriven schema: saas/events/v1/events.proto#EventEnvelope visibility: external @@ -279,6 +287,10 @@ publishes: schema: saas/events/v1/events.proto#EventEnvelope visibility: external retention: 30d + - type: saas.document.extractor_revised + schema: saas/events/v1/events.proto#EventEnvelope + visibility: external + retention: 30d - type: saas.document.frozen schema: saas/events/v1/events.proto#EventEnvelope visibility: external @@ -319,6 +331,10 @@ publishes: schema: saas/events/v1/events.proto#EventEnvelope visibility: external retention: 30d + - type: saas.document.read_unscoped + schema: saas/events/v1/events.proto#EventEnvelope + visibility: external + retention: 30d - type: saas.document.renamed schema: saas/events/v1/events.proto#EventEnvelope visibility: external @@ -327,6 +343,10 @@ publishes: schema: saas/events/v1/events.proto#EventEnvelope visibility: external retention: 30d + - type: saas.document.search_unscoped + schema: saas/events/v1/events.proto#EventEnvelope + visibility: external + retention: 30d - type: saas.document.snapshot.committed schema: saas/events/v1/events.proto#EventEnvelope visibility: external @@ -335,6 +355,10 @@ publishes: schema: saas/events/v1/events.proto#EventEnvelope visibility: external retention: 30d + - type: saas.document.source_verified + schema: saas/events/v1/events.proto#EventEnvelope + visibility: external + retention: 30d - type: saas.document.subscribed schema: saas/events/v1/events.proto#EventEnvelope visibility: external diff --git a/module/deployment/generated/asyncapi.json b/module/deployment/generated/asyncapi.json index 3171ef3e1..04bd2b564 100644 --- a/module/deployment/generated/asyncapi.json +++ b/module/deployment/generated/asyncapi.json @@ -559,6 +559,17 @@ "x-visibility": "external", "x-retention": "30d" }, + "saas.datasource.protection_changed": { + "address": "saas.datasource.protection_changed", + "title": "saas.datasource.protection_changed", + "messages": { + "envelope": { + "$ref": "#/components/messages/saas.datasource.protection_changed" + } + }, + "x-visibility": "external", + "x-retention": "30d" + }, "saas.datasource.receipts.pruned": { "address": "saas.datasource.receipts.pruned", "title": "saas.datasource.receipts.pruned", @@ -724,6 +735,17 @@ "x-visibility": "external", "x-retention": "30d" }, + "saas.document.confirmed": { + "address": "saas.document.confirmed", + "title": "saas.document.confirmed", + "messages": { + "envelope": { + "$ref": "#/components/messages/saas.document.confirmed" + } + }, + "x-visibility": "external", + "x-retention": "30d" + }, "saas.document.dead_letter_redriven": { "address": "saas.document.dead_letter_redriven", "title": "saas.document.dead_letter_redriven", @@ -757,6 +779,17 @@ "x-visibility": "external", "x-retention": "30d" }, + "saas.document.extractor_revised": { + "address": "saas.document.extractor_revised", + "title": "saas.document.extractor_revised", + "messages": { + "envelope": { + "$ref": "#/components/messages/saas.document.extractor_revised" + } + }, + "x-visibility": "external", + "x-retention": "30d" + }, "saas.document.frozen": { "address": "saas.document.frozen", "title": "saas.document.frozen", @@ -867,6 +900,17 @@ "x-visibility": "external", "x-retention": "30d" }, + "saas.document.read_unscoped": { + "address": "saas.document.read_unscoped", + "title": "saas.document.read_unscoped", + "messages": { + "envelope": { + "$ref": "#/components/messages/saas.document.read_unscoped" + } + }, + "x-visibility": "external", + "x-retention": "30d" + }, "saas.document.renamed": { "address": "saas.document.renamed", "title": "saas.document.renamed", @@ -889,6 +933,17 @@ "x-visibility": "external", "x-retention": "30d" }, + "saas.document.search_unscoped": { + "address": "saas.document.search_unscoped", + "title": "saas.document.search_unscoped", + "messages": { + "envelope": { + "$ref": "#/components/messages/saas.document.search_unscoped" + } + }, + "x-visibility": "external", + "x-retention": "30d" + }, "saas.document.snapshot.committed": { "address": "saas.document.snapshot.committed", "title": "saas.document.snapshot.committed", @@ -911,6 +966,17 @@ "x-visibility": "external", "x-retention": "30d" }, + "saas.document.source_verified": { + "address": "saas.document.source_verified", + "title": "saas.document.source_verified", + "messages": { + "envelope": { + "$ref": "#/components/messages/saas.document.source_verified" + } + }, + "x-visibility": "external", + "x-retention": "30d" + }, "saas.document.subscribed": { "address": "saas.document.subscribed", "title": "saas.document.subscribed", @@ -2406,6 +2472,13 @@ }, "title": "saas publishes saas.datasource.operation.invoked" }, + "send:saas.datasource.protection_changed": { + "action": "send", + "channel": { + "$ref": "#/channels/saas.datasource.protection_changed" + }, + "title": "saas publishes saas.datasource.protection_changed" + }, "send:saas.datasource.receipts.pruned": { "action": "send", "channel": { @@ -2511,6 +2584,13 @@ }, "title": "saas publishes saas.document.archived" }, + "send:saas.document.confirmed": { + "action": "send", + "channel": { + "$ref": "#/channels/saas.document.confirmed" + }, + "title": "saas publishes saas.document.confirmed" + }, "send:saas.document.dead_letter_redriven": { "action": "send", "channel": { @@ -2532,6 +2612,13 @@ }, "title": "saas publishes saas.document.effect.committed" }, + "send:saas.document.extractor_revised": { + "action": "send", + "channel": { + "$ref": "#/channels/saas.document.extractor_revised" + }, + "title": "saas publishes saas.document.extractor_revised" + }, "send:saas.document.frozen": { "action": "send", "channel": { @@ -2602,6 +2689,13 @@ }, "title": "saas publishes saas.document.read" }, + "send:saas.document.read_unscoped": { + "action": "send", + "channel": { + "$ref": "#/channels/saas.document.read_unscoped" + }, + "title": "saas publishes saas.document.read_unscoped" + }, "send:saas.document.renamed": { "action": "send", "channel": { @@ -2616,6 +2710,13 @@ }, "title": "saas publishes saas.document.search" }, + "send:saas.document.search_unscoped": { + "action": "send", + "channel": { + "$ref": "#/channels/saas.document.search_unscoped" + }, + "title": "saas publishes saas.document.search_unscoped" + }, "send:saas.document.snapshot.committed": { "action": "send", "channel": { @@ -2630,6 +2731,13 @@ }, "title": "saas publishes saas.document.snapshot.skipped_stale" }, + "send:saas.document.source_verified": { + "action": "send", + "channel": { + "$ref": "#/channels/saas.document.source_verified" + }, + "title": "saas publishes saas.document.source_verified" + }, "send:saas.document.subscribed": { "action": "send", "channel": { @@ -3704,6 +3812,13 @@ "$ref": "#/components/schemas/saas_events_v1_events.proto_EventEnvelope" } }, + "saas.datasource.protection_changed": { + "name": "saas.datasource.protection_changed", + "title": "saas.datasource.protection_changed", + "payload": { + "$ref": "#/components/schemas/saas_events_v1_events.proto_EventEnvelope" + } + }, "saas.datasource.receipts.pruned": { "name": "saas.datasource.receipts.pruned", "title": "saas.datasource.receipts.pruned", @@ -3809,6 +3924,13 @@ "$ref": "#/components/schemas/saas_events_v1_events.proto_EventEnvelope" } }, + "saas.document.confirmed": { + "name": "saas.document.confirmed", + "title": "saas.document.confirmed", + "payload": { + "$ref": "#/components/schemas/saas_events_v1_events.proto_EventEnvelope" + } + }, "saas.document.dead_letter_redriven": { "name": "saas.document.dead_letter_redriven", "title": "saas.document.dead_letter_redriven", @@ -3830,6 +3952,13 @@ "$ref": "#/components/schemas/saas_events_v1_events.proto_EventEnvelope" } }, + "saas.document.extractor_revised": { + "name": "saas.document.extractor_revised", + "title": "saas.document.extractor_revised", + "payload": { + "$ref": "#/components/schemas/saas_events_v1_events.proto_EventEnvelope" + } + }, "saas.document.frozen": { "name": "saas.document.frozen", "title": "saas.document.frozen", @@ -3900,6 +4029,13 @@ "$ref": "#/components/schemas/saas_events_v1_events.proto_EventEnvelope" } }, + "saas.document.read_unscoped": { + "name": "saas.document.read_unscoped", + "title": "saas.document.read_unscoped", + "payload": { + "$ref": "#/components/schemas/saas_events_v1_events.proto_EventEnvelope" + } + }, "saas.document.renamed": { "name": "saas.document.renamed", "title": "saas.document.renamed", @@ -3914,6 +4050,13 @@ "$ref": "#/components/schemas/saas_events_v1_events.proto_EventEnvelope" } }, + "saas.document.search_unscoped": { + "name": "saas.document.search_unscoped", + "title": "saas.document.search_unscoped", + "payload": { + "$ref": "#/components/schemas/saas_events_v1_events.proto_EventEnvelope" + } + }, "saas.document.snapshot.committed": { "name": "saas.document.snapshot.committed", "title": "saas.document.snapshot.committed", @@ -3928,6 +4071,13 @@ "$ref": "#/components/schemas/saas_events_v1_events.proto_EventEnvelope" } }, + "saas.document.source_verified": { + "name": "saas.document.source_verified", + "title": "saas.document.source_verified", + "payload": { + "$ref": "#/components/schemas/saas_events_v1_events.proto_EventEnvelope" + } + }, "saas.document.subscribed": { "name": "saas.document.subscribed", "title": "saas.document.subscribed", diff --git a/module/deployment/generated/communication.md b/module/deployment/generated/communication.md index cb4a9672f..c563f1e02 100644 --- a/module/deployment/generated/communication.md +++ b/module/deployment/generated/communication.md @@ -408,6 +408,14 @@ Every domain event type, who publishes it, and who consumes it. The machine-read - **Retention:** 30d - **Consumers:** _none_ +## saas.datasource.protection_changed + +- **Publisher:** saas +- **Visibility:** external +- **Schema:** `saas/events/v1/events.proto#EventEnvelope` (major v1) +- **Retention:** 30d +- **Consumers:** _none_ + ## saas.datasource.receipts.pruned - **Publisher:** saas @@ -528,6 +536,14 @@ Every domain event type, who publishes it, and who consumes it. The machine-read - **Retention:** 30d - **Consumers:** _none_ +## saas.document.confirmed + +- **Publisher:** saas +- **Visibility:** external +- **Schema:** `saas/events/v1/events.proto#EventEnvelope` (major v1) +- **Retention:** 30d +- **Consumers:** _none_ + ## saas.document.dead_letter_redriven - **Publisher:** saas @@ -552,6 +568,14 @@ Every domain event type, who publishes it, and who consumes it. The machine-read - **Retention:** 30d - **Consumers:** _none_ +## saas.document.extractor_revised + +- **Publisher:** saas +- **Visibility:** external +- **Schema:** `saas/events/v1/events.proto#EventEnvelope` (major v1) +- **Retention:** 30d +- **Consumers:** _none_ + ## saas.document.frozen - **Publisher:** saas @@ -632,6 +656,14 @@ Every domain event type, who publishes it, and who consumes it. The machine-read - **Retention:** 30d - **Consumers:** _none_ +## saas.document.read_unscoped + +- **Publisher:** saas +- **Visibility:** external +- **Schema:** `saas/events/v1/events.proto#EventEnvelope` (major v1) +- **Retention:** 30d +- **Consumers:** _none_ + ## saas.document.renamed - **Publisher:** saas @@ -648,6 +680,14 @@ Every domain event type, who publishes it, and who consumes it. The machine-read - **Retention:** 30d - **Consumers:** _none_ +## saas.document.search_unscoped + +- **Publisher:** saas +- **Visibility:** external +- **Schema:** `saas/events/v1/events.proto#EventEnvelope` (major v1) +- **Retention:** 30d +- **Consumers:** _none_ + ## saas.document.snapshot.committed - **Publisher:** saas @@ -664,6 +704,14 @@ Every domain event type, who publishes it, and who consumes it. The machine-read - **Retention:** 30d - **Consumers:** _none_ +## saas.document.source_verified + +- **Publisher:** saas +- **Visibility:** external +- **Schema:** `saas/events/v1/events.proto#EventEnvelope` (major v1) +- **Retention:** 30d +- **Consumers:** _none_ + ## saas.document.subscribed - **Publisher:** saas diff --git a/module/deployment/generated/event-catalog.json b/module/deployment/generated/event-catalog.json index 43f05380a..e2b73b7af 100644 --- a/module/deployment/generated/event-catalog.json +++ b/module/deployment/generated/event-catalog.json @@ -5004,6 +5004,106 @@ } ] }, + { + "type": "saas.datasource.protection_changed", + "namespace": "saas", + "schema": "saas/events/v1/events.proto#EventEnvelope", + "major": 1, + "visibility": "external", + "retention": "30d", + "fields": [ + { + "name": "id", + "number": 1, + "type": "string" + }, + { + "name": "type", + "number": 2, + "type": "string" + }, + { + "name": "source", + "number": 3, + "type": "string" + }, + { + "name": "subject", + "number": 4, + "type": "string" + }, + { + "name": "time", + "number": 5, + "type": "google.protobuf.Timestamp" + }, + { + "name": "specversion", + "number": 6, + "type": "string" + }, + { + "name": "datacontenttype", + "number": 7, + "type": "string" + }, + { + "name": "dataschema", + "number": 8, + "type": "string" + }, + { + "name": "data", + "number": 9, + "type": "bytes" + }, + { + "name": "tenant_id", + "number": 20, + "type": "string" + }, + { + "name": "boundary_id", + "number": 21, + "type": "string" + }, + { + "name": "partition_key", + "number": 22, + "type": "string" + }, + { + "name": "correlation_id", + "number": 23, + "type": "string" + }, + { + "name": "causation_id", + "number": 24, + "type": "string" + }, + { + "name": "actor_principal_id", + "number": 25, + "type": "string" + }, + { + "name": "owner_principal_id", + "number": 26, + "type": "string" + }, + { + "name": "traceparent", + "number": 27, + "type": "string" + }, + { + "name": "schema_version", + "number": 28, + "type": "uint32" + } + ] + }, { "type": "saas.datasource.receipts.pruned", "namespace": "saas", @@ -6504,6 +6604,106 @@ } ] }, + { + "type": "saas.document.confirmed", + "namespace": "saas", + "schema": "saas/events/v1/events.proto#EventEnvelope", + "major": 1, + "visibility": "external", + "retention": "30d", + "fields": [ + { + "name": "id", + "number": 1, + "type": "string" + }, + { + "name": "type", + "number": 2, + "type": "string" + }, + { + "name": "source", + "number": 3, + "type": "string" + }, + { + "name": "subject", + "number": 4, + "type": "string" + }, + { + "name": "time", + "number": 5, + "type": "google.protobuf.Timestamp" + }, + { + "name": "specversion", + "number": 6, + "type": "string" + }, + { + "name": "datacontenttype", + "number": 7, + "type": "string" + }, + { + "name": "dataschema", + "number": 8, + "type": "string" + }, + { + "name": "data", + "number": 9, + "type": "bytes" + }, + { + "name": "tenant_id", + "number": 20, + "type": "string" + }, + { + "name": "boundary_id", + "number": 21, + "type": "string" + }, + { + "name": "partition_key", + "number": 22, + "type": "string" + }, + { + "name": "correlation_id", + "number": 23, + "type": "string" + }, + { + "name": "causation_id", + "number": 24, + "type": "string" + }, + { + "name": "actor_principal_id", + "number": 25, + "type": "string" + }, + { + "name": "owner_principal_id", + "number": 26, + "type": "string" + }, + { + "name": "traceparent", + "number": 27, + "type": "string" + }, + { + "name": "schema_version", + "number": 28, + "type": "uint32" + } + ] + }, { "type": "saas.document.dead_letter_redriven", "namespace": "saas", @@ -6805,7 +7005,7 @@ ] }, { - "type": "saas.document.frozen", + "type": "saas.document.extractor_revised", "namespace": "saas", "schema": "saas/events/v1/events.proto#EventEnvelope", "major": 1, @@ -6905,7 +7105,7 @@ ] }, { - "type": "saas.document.ingest_skipped_stale", + "type": "saas.document.frozen", "namespace": "saas", "schema": "saas/events/v1/events.proto#EventEnvelope", "major": 1, @@ -7005,7 +7205,7 @@ ] }, { - "type": "saas.document.ingested", + "type": "saas.document.ingest_skipped_stale", "namespace": "saas", "schema": "saas/events/v1/events.proto#EventEnvelope", "major": 1, @@ -7105,7 +7305,7 @@ ] }, { - "type": "saas.document.knowledge.published", + "type": "saas.document.ingested", "namespace": "saas", "schema": "saas/events/v1/events.proto#EventEnvelope", "major": 1, @@ -7205,7 +7405,7 @@ ] }, { - "type": "saas.document.ownership_transferred", + "type": "saas.document.knowledge.published", "namespace": "saas", "schema": "saas/events/v1/events.proto#EventEnvelope", "major": 1, @@ -7305,7 +7505,7 @@ ] }, { - "type": "saas.document.payload_conflict", + "type": "saas.document.ownership_transferred", "namespace": "saas", "schema": "saas/events/v1/events.proto#EventEnvelope", "major": 1, @@ -7405,7 +7605,7 @@ ] }, { - "type": "saas.document.production.committed", + "type": "saas.document.payload_conflict", "namespace": "saas", "schema": "saas/events/v1/events.proto#EventEnvelope", "major": 1, @@ -7505,7 +7705,7 @@ ] }, { - "type": "saas.document.quarantine_released", + "type": "saas.document.production.committed", "namespace": "saas", "schema": "saas/events/v1/events.proto#EventEnvelope", "major": 1, @@ -7605,7 +7805,7 @@ ] }, { - "type": "saas.document.quarantined", + "type": "saas.document.quarantine_released", "namespace": "saas", "schema": "saas/events/v1/events.proto#EventEnvelope", "major": 1, @@ -7705,7 +7905,7 @@ ] }, { - "type": "saas.document.read", + "type": "saas.document.quarantined", "namespace": "saas", "schema": "saas/events/v1/events.proto#EventEnvelope", "major": 1, @@ -7805,7 +8005,7 @@ ] }, { - "type": "saas.document.renamed", + "type": "saas.document.read", "namespace": "saas", "schema": "saas/events/v1/events.proto#EventEnvelope", "major": 1, @@ -7905,7 +8105,7 @@ ] }, { - "type": "saas.document.search", + "type": "saas.document.read_unscoped", "namespace": "saas", "schema": "saas/events/v1/events.proto#EventEnvelope", "major": 1, @@ -8005,7 +8205,7 @@ ] }, { - "type": "saas.document.snapshot.committed", + "type": "saas.document.renamed", "namespace": "saas", "schema": "saas/events/v1/events.proto#EventEnvelope", "major": 1, @@ -8105,7 +8305,407 @@ ] }, { - "type": "saas.document.snapshot.skipped_stale", + "type": "saas.document.search", + "namespace": "saas", + "schema": "saas/events/v1/events.proto#EventEnvelope", + "major": 1, + "visibility": "external", + "retention": "30d", + "fields": [ + { + "name": "id", + "number": 1, + "type": "string" + }, + { + "name": "type", + "number": 2, + "type": "string" + }, + { + "name": "source", + "number": 3, + "type": "string" + }, + { + "name": "subject", + "number": 4, + "type": "string" + }, + { + "name": "time", + "number": 5, + "type": "google.protobuf.Timestamp" + }, + { + "name": "specversion", + "number": 6, + "type": "string" + }, + { + "name": "datacontenttype", + "number": 7, + "type": "string" + }, + { + "name": "dataschema", + "number": 8, + "type": "string" + }, + { + "name": "data", + "number": 9, + "type": "bytes" + }, + { + "name": "tenant_id", + "number": 20, + "type": "string" + }, + { + "name": "boundary_id", + "number": 21, + "type": "string" + }, + { + "name": "partition_key", + "number": 22, + "type": "string" + }, + { + "name": "correlation_id", + "number": 23, + "type": "string" + }, + { + "name": "causation_id", + "number": 24, + "type": "string" + }, + { + "name": "actor_principal_id", + "number": 25, + "type": "string" + }, + { + "name": "owner_principal_id", + "number": 26, + "type": "string" + }, + { + "name": "traceparent", + "number": 27, + "type": "string" + }, + { + "name": "schema_version", + "number": 28, + "type": "uint32" + } + ] + }, + { + "type": "saas.document.search_unscoped", + "namespace": "saas", + "schema": "saas/events/v1/events.proto#EventEnvelope", + "major": 1, + "visibility": "external", + "retention": "30d", + "fields": [ + { + "name": "id", + "number": 1, + "type": "string" + }, + { + "name": "type", + "number": 2, + "type": "string" + }, + { + "name": "source", + "number": 3, + "type": "string" + }, + { + "name": "subject", + "number": 4, + "type": "string" + }, + { + "name": "time", + "number": 5, + "type": "google.protobuf.Timestamp" + }, + { + "name": "specversion", + "number": 6, + "type": "string" + }, + { + "name": "datacontenttype", + "number": 7, + "type": "string" + }, + { + "name": "dataschema", + "number": 8, + "type": "string" + }, + { + "name": "data", + "number": 9, + "type": "bytes" + }, + { + "name": "tenant_id", + "number": 20, + "type": "string" + }, + { + "name": "boundary_id", + "number": 21, + "type": "string" + }, + { + "name": "partition_key", + "number": 22, + "type": "string" + }, + { + "name": "correlation_id", + "number": 23, + "type": "string" + }, + { + "name": "causation_id", + "number": 24, + "type": "string" + }, + { + "name": "actor_principal_id", + "number": 25, + "type": "string" + }, + { + "name": "owner_principal_id", + "number": 26, + "type": "string" + }, + { + "name": "traceparent", + "number": 27, + "type": "string" + }, + { + "name": "schema_version", + "number": 28, + "type": "uint32" + } + ] + }, + { + "type": "saas.document.snapshot.committed", + "namespace": "saas", + "schema": "saas/events/v1/events.proto#EventEnvelope", + "major": 1, + "visibility": "external", + "retention": "30d", + "fields": [ + { + "name": "id", + "number": 1, + "type": "string" + }, + { + "name": "type", + "number": 2, + "type": "string" + }, + { + "name": "source", + "number": 3, + "type": "string" + }, + { + "name": "subject", + "number": 4, + "type": "string" + }, + { + "name": "time", + "number": 5, + "type": "google.protobuf.Timestamp" + }, + { + "name": "specversion", + "number": 6, + "type": "string" + }, + { + "name": "datacontenttype", + "number": 7, + "type": "string" + }, + { + "name": "dataschema", + "number": 8, + "type": "string" + }, + { + "name": "data", + "number": 9, + "type": "bytes" + }, + { + "name": "tenant_id", + "number": 20, + "type": "string" + }, + { + "name": "boundary_id", + "number": 21, + "type": "string" + }, + { + "name": "partition_key", + "number": 22, + "type": "string" + }, + { + "name": "correlation_id", + "number": 23, + "type": "string" + }, + { + "name": "causation_id", + "number": 24, + "type": "string" + }, + { + "name": "actor_principal_id", + "number": 25, + "type": "string" + }, + { + "name": "owner_principal_id", + "number": 26, + "type": "string" + }, + { + "name": "traceparent", + "number": 27, + "type": "string" + }, + { + "name": "schema_version", + "number": 28, + "type": "uint32" + } + ] + }, + { + "type": "saas.document.snapshot.skipped_stale", + "namespace": "saas", + "schema": "saas/events/v1/events.proto#EventEnvelope", + "major": 1, + "visibility": "external", + "retention": "30d", + "fields": [ + { + "name": "id", + "number": 1, + "type": "string" + }, + { + "name": "type", + "number": 2, + "type": "string" + }, + { + "name": "source", + "number": 3, + "type": "string" + }, + { + "name": "subject", + "number": 4, + "type": "string" + }, + { + "name": "time", + "number": 5, + "type": "google.protobuf.Timestamp" + }, + { + "name": "specversion", + "number": 6, + "type": "string" + }, + { + "name": "datacontenttype", + "number": 7, + "type": "string" + }, + { + "name": "dataschema", + "number": 8, + "type": "string" + }, + { + "name": "data", + "number": 9, + "type": "bytes" + }, + { + "name": "tenant_id", + "number": 20, + "type": "string" + }, + { + "name": "boundary_id", + "number": 21, + "type": "string" + }, + { + "name": "partition_key", + "number": 22, + "type": "string" + }, + { + "name": "correlation_id", + "number": 23, + "type": "string" + }, + { + "name": "causation_id", + "number": 24, + "type": "string" + }, + { + "name": "actor_principal_id", + "number": 25, + "type": "string" + }, + { + "name": "owner_principal_id", + "number": 26, + "type": "string" + }, + { + "name": "traceparent", + "number": 27, + "type": "string" + }, + { + "name": "schema_version", + "number": 28, + "type": "uint32" + } + ] + }, + { + "type": "saas.document.source_verified", "namespace": "saas", "schema": "saas/events/v1/events.proto#EventEnvelope", "major": 1, diff --git a/module/services/accounts/code/pkg/business/audit_parity_test.go b/module/services/accounts/code/pkg/business/audit_parity_test.go index 3171d1734..dd2ab889f 100644 --- a/module/services/accounts/code/pkg/business/audit_parity_test.go +++ b/module/services/accounts/code/pkg/business/audit_parity_test.go @@ -45,6 +45,14 @@ var eventsEmittedOutsideRPC = []EventType{ EventDatasourceSourceAccessLost, EventDatasourceSourceAccessRestored, EventDatasourceSourceRecovered, + // A document module reports these through EmitAuditEvent: the `saas` + // namespace is the host's, so a module cannot declare them itself. + EventDatasourceProtectionChanged, + EventDocumentConfirmed, + EventDocumentExtractorRevised, + EventDocumentReadUnscoped, + EventDocumentSearchUnscoped, + EventDocumentSourceVerified, EventDocumentArchived, // A document module's operator redrive reports this through EmitAuditEvent. EventDocumentDeadLetterRedriven, diff --git a/module/services/accounts/code/pkg/business/audit_registry.go b/module/services/accounts/code/pkg/business/audit_registry.go index 5ac568517..37c4534ff 100644 --- a/module/services/accounts/code/pkg/business/audit_registry.go +++ b/module/services/accounts/code/pkg/business/audit_registry.go @@ -486,6 +486,8 @@ const ( EventDatasourceDomainClaimed EventType = "saas.datasource.domain_claimed" EventDatasourceDomainVerified EventType = "saas.datasource.domain_verified" EventDatasourceDomainRemoved EventType = "saas.datasource.domain_removed" + // A deployment installed or retired a protected upload source policy. + EventDatasourceProtectionChanged EventType = "saas.datasource.protection_changed" EventDatasourceGitHubAppSetupStarted EventType = "saas.datasource.github_app.setup_started" EventDatasourceGitHubAppSetupCompleted EventType = "saas.datasource.github_app.setup_completed" @@ -554,6 +556,26 @@ const ( // is who-did-what-to-which-resource (the entry is re-derived on an operator's // say-so), not pipeline bookkeeping, so it has a type of its own. EventDocumentDeadLetterRedriven EventType = "saas.document.dead_letter_redriven" + + // A document module's own facts, reported through the module-facing + // EmitAuditEvent. The `saas` namespace is the host's, so a module cannot + // declare a type here through DeclareAuditEventTypes: the host registers + // each one, with the shape the emitting module sends. + // + // A verified source provenance was attached to an immutable version. + EventDocumentSourceVerified EventType = "saas.document.source_verified" + // A read or search that involved no collection: the caller could read none, + // or its request resolved to none. saas.document.read / search each record + // one collection and require its boundary, so without these the refusal and + // the empty answer reached only the producer's own log. + EventDocumentReadUnscoped EventType = "saas.document.read_unscoped" + EventDocumentSearchUnscoped EventType = "saas.document.search_unscoped" + // A person confirmed the value a reference reads at one version of a + // document: an append-only fact about that reference. + EventDocumentConfirmed EventType = "saas.document.confirmed" + // An extractor attached to a document was revised: attached, corrected or + // retired. + EventDocumentExtractorRevised EventType = "saas.document.extractor_revised" ) var auditEventCatalog = []AuditEventDefinition{ @@ -905,6 +927,78 @@ var auditEventCatalog = []AuditEventDefinition{ PayloadField{Name: "producer", Kind: FieldString, Required: true, MaxLen: 128}, PayloadField{Name: "error_class", Kind: FieldEnum, Required: true, Enum: []string{"permanent", "exhausted", "cancelled", "unknown"}})...))), + mutation(EventDatasourceProtectionChanged, CategorySystem, "Deployment installed or retired a protected upload source policy.", + str("solution"), + PayloadField{Name: "policy_revision", Kind: FieldString, Required: true}, + PayloadField{Name: "retired", Kind: FieldBool, Required: true}, + PayloadField{Name: "owner", Kind: FieldString, Required: true}, + PayloadField{Name: "binding_sha256", Kind: FieldString, Required: true}), + mutation(EventDocumentSourceVerified, CategorySystem, "An immutable document version received verified source provenance.", + str("solution"), + PayloadField{Name: "version_id", Kind: FieldString, Required: true}, + PayloadField{Name: "source_id", Kind: FieldString, Required: true}, + PayloadField{Name: "receipt_job_id", Kind: FieldString, Required: true}, + PayloadField{Name: "policy_revision", Kind: FieldString, Required: true}, + PayloadField{Name: "binding_sha256", Kind: FieldString, Required: true}, + PayloadField{Name: "effect_sha256", Kind: FieldString, Required: true}, + PayloadField{Name: "owner", Kind: FieldString, Required: true}), + observation(EventDocumentReadUnscoped, CategoryAccess, "A document read that involved no collection ended with an explicit outcome: it was denied, found nothing the caller may read, or failed before any collection was resolved.", documentUnscopedReadFields...), + observation(EventDocumentSearchUnscoped, CategoryAccess, "A search that involved no collection ended with an explicit outcome: it was denied, the caller may read no collection it names, or it failed before any collection was resolved.", documentUnscopedReadFields...), + // A confirmation is recorded only when it commits: the module appends the + // event in the same transaction as its append-only confirmation row, and a + // refused confirmation writes nothing, so the type carries no `outcome`. The + // entry is the resource and the confirmation id is the idempotency key, so a + // retried emission collapses. The confirmed value is deliberately not a + // field: the spine records that a reference was confirmed, where, and by + // whom, never what it read. The version and every reference field are + // required: a confirmation that cannot say which version and which reference + // it confirmed is not one. `address` is the reference's position in its + // format's own notation, bounded in bytes like the module's request. + requiresIdempotencyKey(requiresEntry(mutation(EventDocumentConfirmed, CategoryLifecycle, + "A person confirmed the value a reference read at one version of a document.", + append(required(documentFields, "version"), + PayloadField{Name: "extractor", Kind: FieldString, Required: true}, + PayloadField{Name: "extractor_version", Kind: FieldString, Required: true}, + PayloadField{Name: "field", Kind: FieldString, Required: true}, + PayloadField{Name: "format", Kind: FieldString, Required: true}, + PayloadField{Name: "address", Kind: FieldString, Required: true, MaxLen: 1024})...))), + // The native extractor itself stays off the trail; its digest names it. + // Everything a revision always names is required: the version it was + // derived from, the key and revision, its digest, whether it retires the + // key, the native request type and what proposed it. `proposal_source` is + // free text a person or a tool supplied, so it is PII. + requiresIdempotencyKey(requiresEntry(mutation(EventDocumentExtractorRevised, CategoryLifecycle, + "An extractor attached to a document was revised: attached, corrected or retired.", + append(required(documentFields, "version"), + PayloadField{Name: "extractor_key", Kind: FieldString, Required: true, MaxLen: 128}, + PayloadField{Name: "revision", Kind: FieldInt, Required: true}, + PayloadField{Name: "digest", Kind: FieldString, Required: true, MaxLen: 64}, + PayloadField{Name: "parent_digest", Kind: FieldString, MaxLen: 64}, + PayloadField{Name: "retired", Kind: FieldBool, Required: true}, + PayloadField{Name: "request_type", Kind: FieldString, Required: true, MaxLen: 128}, + PayloadField{Name: "proposal_kind", Kind: FieldString, Required: true, MaxLen: 16}, + pii(PayloadField{Name: "proposal_source", Kind: FieldString, MaxLen: 512}))...))), +} + +// documentUnscopedReadFields is the shape of a read or search that involved no +// collection. It is a type of its own, not saas.document.read with an optional +// boundary, because the registry declares each field's requirement on its own: +// "boundary is required unless the outcome is denied, empty or failed" is a +// conditional the registry and its JSON Schema projection cannot state, so it +// would be enforced nowhere. Here the rule is mechanical: `boundary` is not a +// field, so a payload naming a collection is refused as an unregistered field; +// `returned` is not an outcome, because evidence is always returned from some +// collection and belongs on the scoped type; and `result_count` is absent, +// because with no collection there is no result to count. A refusal is the +// outcome `denied`, never a type of its own. +var documentUnscopedReadFields = []PayloadField{ + str("solution"), + uid("actor_principal_id"), + uid("owner_principal_id"), + str("initiator"), + {Name: "correlation_id", Kind: FieldString, Required: true}, + {Name: "outcome", Kind: FieldEnum, Required: true, Enum: []string{"empty", "denied", "failed"}}, + {Name: "duration_ms", Kind: FieldInt}, } // webhookAdminVersion is version 2 of the webhook administration events: the diff --git a/module/services/accounts/code/pkg/business/audit_registry_test.go b/module/services/accounts/code/pkg/business/audit_registry_test.go index 4387ea657..49319651a 100644 --- a/module/services/accounts/code/pkg/business/audit_registry_test.go +++ b/module/services/accounts/code/pkg/business/audit_registry_test.go @@ -227,6 +227,108 @@ func TestValidatePayload_DocumentDeadLetterRedriven(t *testing.T) { require.NoError(t, ValidatePayload(EventDocumentDeadLetterRedriven, atBound)) } +// The document module's reserved types: each is registered with exactly the +// payload its emitter sends, because declared types only grow. +func TestValidatePayload_DocumentModuleReservedTypes(t *testing.T) { + t.Run("confirmed", func(t *testing.T) { + complete := func() map[string]any { + return map[string]any{ + "solution": "documents", "version": "v1", "initiator": "RecordConfirmation", + "extractor": "x", "extractor_version": "1", "field": "f", "format": "xlsx", "address": "Sheet1!A1", + } + } + require.NoError(t, ValidatePayload(EventDocumentConfirmed, complete())) + for _, field := range []string{"version", "extractor", "extractor_version", "field", "format", "address"} { + p := complete() + delete(p, field) + require.Errorf(t, ValidatePayload(EventDocumentConfirmed, p), "%s is required", field) + } + p := complete() + p["value"] = "42" + require.Error(t, ValidatePayload(EventDocumentConfirmed, p), "the confirmed value is never on the spine") + p = complete() + p["address"] = strings.Repeat("a", 1025) + require.Error(t, ValidatePayload(EventDocumentConfirmed, p), "address is bounded at 1024 bytes") + p["address"] = strings.Repeat("a", 1024) + require.NoError(t, ValidatePayload(EventDocumentConfirmed, p)) + require.True(t, AuditEventRequiresEntry(EventDocumentConfirmed)) + require.True(t, AuditEventRequiresIdempotencyKey(EventDocumentConfirmed)) + }) + t.Run("extractor_revised", func(t *testing.T) { + complete := func() map[string]any { + return map[string]any{ + "version": "v1", "extractor_key": "k", "revision": 2, "digest": "d", "parent_digest": "p", + "retired": false, "request_type": "t", "proposal_kind": "human", "proposal_source": "a note", + } + } + require.NoError(t, ValidatePayload(EventDocumentExtractorRevised, complete())) + p := complete() + delete(p, "parent_digest") + delete(p, "proposal_source") + require.NoError(t, ValidatePayload(EventDocumentExtractorRevised, p), "a first attachment has no parent and may name no source") + for _, field := range []string{"version", "extractor_key", "revision", "digest", "retired", "request_type", "proposal_kind"} { + p := complete() + delete(p, field) + require.Errorf(t, ValidatePayload(EventDocumentExtractorRevised, p), "%s is required", field) + } + p = complete() + p["proposal_source"] = strings.Repeat("s", 513) + require.Error(t, ValidatePayload(EventDocumentExtractorRevised, p), "proposal_source is bounded at 512 bytes") + require.True(t, AuditEventRequiresEntry(EventDocumentExtractorRevised)) + require.True(t, AuditEventRequiresIdempotencyKey(EventDocumentExtractorRevised)) + def, ok := LookupAuditEvent(EventDocumentExtractorRevised) + require.True(t, ok) + for _, f := range def.Fields { + require.Equalf(t, f.Name == "proposal_source", f.PII, "PII mark of %s", f.Name) + } + }) + t.Run("unscoped reads", func(t *testing.T) { + for _, event := range []EventType{EventDocumentReadUnscoped, EventDocumentSearchUnscoped} { + complete := func() map[string]any { + return map[string]any{"solution": "documents", "correlation_id": "c1", "outcome": "empty", "duration_ms": 3} + } + require.NoError(t, ValidatePayload(event, complete())) + for _, outcome := range []string{"empty", "denied", "failed"} { + p := complete() + p["outcome"] = outcome + require.NoErrorf(t, ValidatePayload(event, p), "outcome %q", outcome) + } + p := complete() + p["outcome"] = "returned" + require.Errorf(t, ValidatePayload(event, p), "%s: returned belongs on the scoped type", event) + p = complete() + p["boundary"] = "b" + require.Errorf(t, ValidatePayload(event, p), "%s: no collection, no boundary", event) + p = complete() + delete(p, "correlation_id") + require.Errorf(t, ValidatePayload(event, p), "%s: correlation_id is required", event) + } + }) + t.Run("source_verified and protection_changed", func(t *testing.T) { + verified := map[string]any{"solution": "documents", "version_id": "v", "source_id": "s", "receipt_job_id": "j", + "policy_revision": "r", "binding_sha256": "b", "effect_sha256": "e", "owner": "o"} + require.NoError(t, ValidatePayload(EventDocumentSourceVerified, verified)) + for field := range verified { + if field == "solution" { + continue + } + p := maps.Clone(verified) + delete(p, field) + require.Errorf(t, ValidatePayload(EventDocumentSourceVerified, p), "source_verified %s is required", field) + } + changed := map[string]any{"solution": "documents", "policy_revision": "r", "retired": true, "owner": "o", "binding_sha256": "b"} + require.NoError(t, ValidatePayload(EventDatasourceProtectionChanged, changed)) + for field := range changed { + if field == "solution" { + continue + } + p := maps.Clone(changed) + delete(p, field) + require.Errorf(t, ValidatePayload(EventDatasourceProtectionChanged, p), "protection_changed %s is required", field) + } + }) +} + // The nonempty rule belongs to the declaration, not to a list of the event types // somebody remembered to add: a required string-valued field on ANY registered // type refuses a blank, and a required bool or int still records false and 0. diff --git a/module/services/accounts/code/pkg/eventcatalog/catalog_gen.go b/module/services/accounts/code/pkg/eventcatalog/catalog_gen.go index f91b0b386..bdd2b43cf 100644 --- a/module/services/accounts/code/pkg/eventcatalog/catalog_gen.go +++ b/module/services/accounts/code/pkg/eventcatalog/catalog_gen.go @@ -75,6 +75,7 @@ var published = [...]PublishedEvent{ {Type: "saas.datasource.group_unbound", Namespace: "saas", Schema: "saas/events/v1/events.proto#EventEnvelope", Major: 1, Visibility: "external", Partition: "", Retention: "30d"}, {Type: "saas.datasource.operation.declared", Namespace: "saas", Schema: "saas/events/v1/events.proto#EventEnvelope", Major: 1, Visibility: "external", Partition: "", Retention: "30d"}, {Type: "saas.datasource.operation.invoked", Namespace: "saas", Schema: "saas/events/v1/events.proto#EventEnvelope", Major: 1, Visibility: "external", Partition: "", Retention: "30d"}, + {Type: "saas.datasource.protection_changed", Namespace: "saas", Schema: "saas/events/v1/events.proto#EventEnvelope", Major: 1, Visibility: "external", Partition: "", Retention: "30d"}, {Type: "saas.datasource.receipts.pruned", Namespace: "saas", Schema: "saas/events/v1/events.proto#EventEnvelope", Major: 1, Visibility: "external", Partition: "", Retention: "30d"}, {Type: "saas.datasource.snapshot_too_large", Namespace: "saas", Schema: "saas/events/v1/events.proto#EventEnvelope", Major: 1, Visibility: "external", Partition: "", Retention: "30d"}, {Type: "saas.datasource.source.access_lost", Namespace: "saas", Schema: "saas/events/v1/events.proto#EventEnvelope", Major: 1, Visibility: "external", Partition: "", Retention: "30d"}, @@ -90,9 +91,11 @@ var published = [...]PublishedEvent{ {Type: "saas.delegation.denied", Namespace: "saas", Schema: "saas/events/v1/events.proto#EventEnvelope", Major: 1, Visibility: "external", Partition: "", Retention: "30d"}, {Type: "saas.delegation.requested", Namespace: "saas", Schema: "saas/events/v1/events.proto#EventEnvelope", Major: 1, Visibility: "external", Partition: "", Retention: "30d"}, {Type: "saas.document.archived", Namespace: "saas", Schema: "saas/events/v1/events.proto#EventEnvelope", Major: 1, Visibility: "external", Partition: "", Retention: "30d"}, + {Type: "saas.document.confirmed", Namespace: "saas", Schema: "saas/events/v1/events.proto#EventEnvelope", Major: 1, Visibility: "external", Partition: "", Retention: "30d"}, {Type: "saas.document.dead_letter_redriven", Namespace: "saas", Schema: "saas/events/v1/events.proto#EventEnvelope", Major: 1, Visibility: "external", Partition: "", Retention: "30d"}, {Type: "saas.document.deleted", Namespace: "saas", Schema: "saas/events/v1/events.proto#EventEnvelope", Major: 1, Visibility: "external", Partition: "", Retention: "30d"}, {Type: "saas.document.effect.committed", Namespace: "saas", Schema: "saas/events/v1/events.proto#EventEnvelope", Major: 1, Visibility: "external", Partition: "", Retention: "30d"}, + {Type: "saas.document.extractor_revised", Namespace: "saas", Schema: "saas/events/v1/events.proto#EventEnvelope", Major: 1, Visibility: "external", Partition: "", Retention: "30d"}, {Type: "saas.document.frozen", Namespace: "saas", Schema: "saas/events/v1/events.proto#EventEnvelope", Major: 1, Visibility: "external", Partition: "", Retention: "30d"}, {Type: "saas.document.ingest_skipped_stale", Namespace: "saas", Schema: "saas/events/v1/events.proto#EventEnvelope", Major: 1, Visibility: "external", Partition: "", Retention: "30d"}, {Type: "saas.document.ingested", Namespace: "saas", Schema: "saas/events/v1/events.proto#EventEnvelope", Major: 1, Visibility: "external", Partition: "", Retention: "30d"}, @@ -103,10 +106,13 @@ var published = [...]PublishedEvent{ {Type: "saas.document.quarantine_released", Namespace: "saas", Schema: "saas/events/v1/events.proto#EventEnvelope", Major: 1, Visibility: "external", Partition: "", Retention: "30d"}, {Type: "saas.document.quarantined", Namespace: "saas", Schema: "saas/events/v1/events.proto#EventEnvelope", Major: 1, Visibility: "external", Partition: "", Retention: "30d"}, {Type: "saas.document.read", Namespace: "saas", Schema: "saas/events/v1/events.proto#EventEnvelope", Major: 1, Visibility: "external", Partition: "", Retention: "30d"}, + {Type: "saas.document.read_unscoped", Namespace: "saas", Schema: "saas/events/v1/events.proto#EventEnvelope", Major: 1, Visibility: "external", Partition: "", Retention: "30d"}, {Type: "saas.document.renamed", Namespace: "saas", Schema: "saas/events/v1/events.proto#EventEnvelope", Major: 1, Visibility: "external", Partition: "", Retention: "30d"}, {Type: "saas.document.search", Namespace: "saas", Schema: "saas/events/v1/events.proto#EventEnvelope", Major: 1, Visibility: "external", Partition: "", Retention: "30d"}, + {Type: "saas.document.search_unscoped", Namespace: "saas", Schema: "saas/events/v1/events.proto#EventEnvelope", Major: 1, Visibility: "external", Partition: "", Retention: "30d"}, {Type: "saas.document.snapshot.committed", Namespace: "saas", Schema: "saas/events/v1/events.proto#EventEnvelope", Major: 1, Visibility: "external", Partition: "", Retention: "30d"}, {Type: "saas.document.snapshot.skipped_stale", Namespace: "saas", Schema: "saas/events/v1/events.proto#EventEnvelope", Major: 1, Visibility: "external", Partition: "", Retention: "30d"}, + {Type: "saas.document.source_verified", Namespace: "saas", Schema: "saas/events/v1/events.proto#EventEnvelope", Major: 1, Visibility: "external", Partition: "", Retention: "30d"}, {Type: "saas.document.subscribed", Namespace: "saas", Schema: "saas/events/v1/events.proto#EventEnvelope", Major: 1, Visibility: "external", Partition: "", Retention: "30d"}, {Type: "saas.document.unarchived", Namespace: "saas", Schema: "saas/events/v1/events.proto#EventEnvelope", Major: 1, Visibility: "external", Partition: "", Retention: "30d"}, {Type: "saas.document.unfrozen", Namespace: "saas", Schema: "saas/events/v1/events.proto#EventEnvelope", Major: 1, Visibility: "external", Partition: "", Retention: "30d"}, From 08e6d8765dd67c8cca8e91236bc4eec4a3fcd6e1 Mon Sep 17 00:00:00 2001 From: Antoine Toussaint <312536060+antoinetoussaint-byte@users.noreply.github.com> Date: Mon, 12 Oct 2026 19:28:56 -0400 Subject: [PATCH 2/2] fix(audit): register only the four document types a module emits today saas.datasource.protection_changed and saas.document.source_verified have no emitter on the document module's main (their last one was removed with the unpublished source-provenance work). Declared types only grow, so they are registered with the PR that brings their emitter back, aligned with the other saas.document.* types. The proposal_source PII comment now states the real reason: an unvalidated 512-byte string, though it names a run, receipt or derivation. Co-Authored-By: Claude Opus 5.5 --- .../accounts/saas.events.codefly.yaml | 8 - module/deployment/generated/asyncapi.json | 50 ----- module/deployment/generated/communication.md | 16 -- .../deployment/generated/event-catalog.json | 200 ------------------ .../code/pkg/business/audit_parity_test.go | 4 +- .../code/pkg/business/audit_registry.go | 25 +-- .../code/pkg/business/audit_registry_test.go | 25 +-- .../code/pkg/eventcatalog/catalog_gen.go | 2 - 8 files changed, 6 insertions(+), 324 deletions(-) diff --git a/module/contracts/accounts/saas.events.codefly.yaml b/module/contracts/accounts/saas.events.codefly.yaml index c1186439c..13112f15b 100644 --- a/module/contracts/accounts/saas.events.codefly.yaml +++ b/module/contracts/accounts/saas.events.codefly.yaml @@ -207,10 +207,6 @@ publishes: schema: saas/events/v1/events.proto#EventEnvelope visibility: external retention: 30d - - type: saas.datasource.protection_changed - schema: saas/events/v1/events.proto#EventEnvelope - visibility: external - retention: 30d - type: saas.datasource.receipts.pruned schema: saas/events/v1/events.proto#EventEnvelope visibility: external @@ -355,10 +351,6 @@ publishes: schema: saas/events/v1/events.proto#EventEnvelope visibility: external retention: 30d - - type: saas.document.source_verified - schema: saas/events/v1/events.proto#EventEnvelope - visibility: external - retention: 30d - type: saas.document.subscribed schema: saas/events/v1/events.proto#EventEnvelope visibility: external diff --git a/module/deployment/generated/asyncapi.json b/module/deployment/generated/asyncapi.json index 04bd2b564..4d283e53e 100644 --- a/module/deployment/generated/asyncapi.json +++ b/module/deployment/generated/asyncapi.json @@ -559,17 +559,6 @@ "x-visibility": "external", "x-retention": "30d" }, - "saas.datasource.protection_changed": { - "address": "saas.datasource.protection_changed", - "title": "saas.datasource.protection_changed", - "messages": { - "envelope": { - "$ref": "#/components/messages/saas.datasource.protection_changed" - } - }, - "x-visibility": "external", - "x-retention": "30d" - }, "saas.datasource.receipts.pruned": { "address": "saas.datasource.receipts.pruned", "title": "saas.datasource.receipts.pruned", @@ -966,17 +955,6 @@ "x-visibility": "external", "x-retention": "30d" }, - "saas.document.source_verified": { - "address": "saas.document.source_verified", - "title": "saas.document.source_verified", - "messages": { - "envelope": { - "$ref": "#/components/messages/saas.document.source_verified" - } - }, - "x-visibility": "external", - "x-retention": "30d" - }, "saas.document.subscribed": { "address": "saas.document.subscribed", "title": "saas.document.subscribed", @@ -2472,13 +2450,6 @@ }, "title": "saas publishes saas.datasource.operation.invoked" }, - "send:saas.datasource.protection_changed": { - "action": "send", - "channel": { - "$ref": "#/channels/saas.datasource.protection_changed" - }, - "title": "saas publishes saas.datasource.protection_changed" - }, "send:saas.datasource.receipts.pruned": { "action": "send", "channel": { @@ -2731,13 +2702,6 @@ }, "title": "saas publishes saas.document.snapshot.skipped_stale" }, - "send:saas.document.source_verified": { - "action": "send", - "channel": { - "$ref": "#/channels/saas.document.source_verified" - }, - "title": "saas publishes saas.document.source_verified" - }, "send:saas.document.subscribed": { "action": "send", "channel": { @@ -3812,13 +3776,6 @@ "$ref": "#/components/schemas/saas_events_v1_events.proto_EventEnvelope" } }, - "saas.datasource.protection_changed": { - "name": "saas.datasource.protection_changed", - "title": "saas.datasource.protection_changed", - "payload": { - "$ref": "#/components/schemas/saas_events_v1_events.proto_EventEnvelope" - } - }, "saas.datasource.receipts.pruned": { "name": "saas.datasource.receipts.pruned", "title": "saas.datasource.receipts.pruned", @@ -4071,13 +4028,6 @@ "$ref": "#/components/schemas/saas_events_v1_events.proto_EventEnvelope" } }, - "saas.document.source_verified": { - "name": "saas.document.source_verified", - "title": "saas.document.source_verified", - "payload": { - "$ref": "#/components/schemas/saas_events_v1_events.proto_EventEnvelope" - } - }, "saas.document.subscribed": { "name": "saas.document.subscribed", "title": "saas.document.subscribed", diff --git a/module/deployment/generated/communication.md b/module/deployment/generated/communication.md index c563f1e02..465dc99da 100644 --- a/module/deployment/generated/communication.md +++ b/module/deployment/generated/communication.md @@ -408,14 +408,6 @@ Every domain event type, who publishes it, and who consumes it. The machine-read - **Retention:** 30d - **Consumers:** _none_ -## saas.datasource.protection_changed - -- **Publisher:** saas -- **Visibility:** external -- **Schema:** `saas/events/v1/events.proto#EventEnvelope` (major v1) -- **Retention:** 30d -- **Consumers:** _none_ - ## saas.datasource.receipts.pruned - **Publisher:** saas @@ -704,14 +696,6 @@ Every domain event type, who publishes it, and who consumes it. The machine-read - **Retention:** 30d - **Consumers:** _none_ -## saas.document.source_verified - -- **Publisher:** saas -- **Visibility:** external -- **Schema:** `saas/events/v1/events.proto#EventEnvelope` (major v1) -- **Retention:** 30d -- **Consumers:** _none_ - ## saas.document.subscribed - **Publisher:** saas diff --git a/module/deployment/generated/event-catalog.json b/module/deployment/generated/event-catalog.json index e2b73b7af..7b179359f 100644 --- a/module/deployment/generated/event-catalog.json +++ b/module/deployment/generated/event-catalog.json @@ -5004,106 +5004,6 @@ } ] }, - { - "type": "saas.datasource.protection_changed", - "namespace": "saas", - "schema": "saas/events/v1/events.proto#EventEnvelope", - "major": 1, - "visibility": "external", - "retention": "30d", - "fields": [ - { - "name": "id", - "number": 1, - "type": "string" - }, - { - "name": "type", - "number": 2, - "type": "string" - }, - { - "name": "source", - "number": 3, - "type": "string" - }, - { - "name": "subject", - "number": 4, - "type": "string" - }, - { - "name": "time", - "number": 5, - "type": "google.protobuf.Timestamp" - }, - { - "name": "specversion", - "number": 6, - "type": "string" - }, - { - "name": "datacontenttype", - "number": 7, - "type": "string" - }, - { - "name": "dataschema", - "number": 8, - "type": "string" - }, - { - "name": "data", - "number": 9, - "type": "bytes" - }, - { - "name": "tenant_id", - "number": 20, - "type": "string" - }, - { - "name": "boundary_id", - "number": 21, - "type": "string" - }, - { - "name": "partition_key", - "number": 22, - "type": "string" - }, - { - "name": "correlation_id", - "number": 23, - "type": "string" - }, - { - "name": "causation_id", - "number": 24, - "type": "string" - }, - { - "name": "actor_principal_id", - "number": 25, - "type": "string" - }, - { - "name": "owner_principal_id", - "number": 26, - "type": "string" - }, - { - "name": "traceparent", - "number": 27, - "type": "string" - }, - { - "name": "schema_version", - "number": 28, - "type": "uint32" - } - ] - }, { "type": "saas.datasource.receipts.pruned", "namespace": "saas", @@ -8704,106 +8604,6 @@ } ] }, - { - "type": "saas.document.source_verified", - "namespace": "saas", - "schema": "saas/events/v1/events.proto#EventEnvelope", - "major": 1, - "visibility": "external", - "retention": "30d", - "fields": [ - { - "name": "id", - "number": 1, - "type": "string" - }, - { - "name": "type", - "number": 2, - "type": "string" - }, - { - "name": "source", - "number": 3, - "type": "string" - }, - { - "name": "subject", - "number": 4, - "type": "string" - }, - { - "name": "time", - "number": 5, - "type": "google.protobuf.Timestamp" - }, - { - "name": "specversion", - "number": 6, - "type": "string" - }, - { - "name": "datacontenttype", - "number": 7, - "type": "string" - }, - { - "name": "dataschema", - "number": 8, - "type": "string" - }, - { - "name": "data", - "number": 9, - "type": "bytes" - }, - { - "name": "tenant_id", - "number": 20, - "type": "string" - }, - { - "name": "boundary_id", - "number": 21, - "type": "string" - }, - { - "name": "partition_key", - "number": 22, - "type": "string" - }, - { - "name": "correlation_id", - "number": 23, - "type": "string" - }, - { - "name": "causation_id", - "number": 24, - "type": "string" - }, - { - "name": "actor_principal_id", - "number": 25, - "type": "string" - }, - { - "name": "owner_principal_id", - "number": 26, - "type": "string" - }, - { - "name": "traceparent", - "number": 27, - "type": "string" - }, - { - "name": "schema_version", - "number": 28, - "type": "uint32" - } - ] - }, { "type": "saas.document.subscribed", "namespace": "saas", diff --git a/module/services/accounts/code/pkg/business/audit_parity_test.go b/module/services/accounts/code/pkg/business/audit_parity_test.go index dd2ab889f..6aeaccbdc 100644 --- a/module/services/accounts/code/pkg/business/audit_parity_test.go +++ b/module/services/accounts/code/pkg/business/audit_parity_test.go @@ -45,15 +45,13 @@ var eventsEmittedOutsideRPC = []EventType{ EventDatasourceSourceAccessLost, EventDatasourceSourceAccessRestored, EventDatasourceSourceRecovered, + EventDocumentArchived, // A document module reports these through EmitAuditEvent: the `saas` // namespace is the host's, so a module cannot declare them itself. - EventDatasourceProtectionChanged, EventDocumentConfirmed, EventDocumentExtractorRevised, EventDocumentReadUnscoped, EventDocumentSearchUnscoped, - EventDocumentSourceVerified, - EventDocumentArchived, // A document module's operator redrive reports this through EmitAuditEvent. EventDocumentDeadLetterRedriven, EventDocumentDeleted, diff --git a/module/services/accounts/code/pkg/business/audit_registry.go b/module/services/accounts/code/pkg/business/audit_registry.go index 37c4534ff..af9e64d89 100644 --- a/module/services/accounts/code/pkg/business/audit_registry.go +++ b/module/services/accounts/code/pkg/business/audit_registry.go @@ -486,8 +486,6 @@ const ( EventDatasourceDomainClaimed EventType = "saas.datasource.domain_claimed" EventDatasourceDomainVerified EventType = "saas.datasource.domain_verified" EventDatasourceDomainRemoved EventType = "saas.datasource.domain_removed" - // A deployment installed or retired a protected upload source policy. - EventDatasourceProtectionChanged EventType = "saas.datasource.protection_changed" EventDatasourceGitHubAppSetupStarted EventType = "saas.datasource.github_app.setup_started" EventDatasourceGitHubAppSetupCompleted EventType = "saas.datasource.github_app.setup_completed" @@ -562,8 +560,6 @@ const ( // declare a type here through DeclareAuditEventTypes: the host registers // each one, with the shape the emitting module sends. // - // A verified source provenance was attached to an immutable version. - EventDocumentSourceVerified EventType = "saas.document.source_verified" // A read or search that involved no collection: the caller could read none, // or its request resolved to none. saas.document.read / search each record // one collection and require its boundary, so without these the refusal and @@ -927,21 +923,6 @@ var auditEventCatalog = []AuditEventDefinition{ PayloadField{Name: "producer", Kind: FieldString, Required: true, MaxLen: 128}, PayloadField{Name: "error_class", Kind: FieldEnum, Required: true, Enum: []string{"permanent", "exhausted", "cancelled", "unknown"}})...))), - mutation(EventDatasourceProtectionChanged, CategorySystem, "Deployment installed or retired a protected upload source policy.", - str("solution"), - PayloadField{Name: "policy_revision", Kind: FieldString, Required: true}, - PayloadField{Name: "retired", Kind: FieldBool, Required: true}, - PayloadField{Name: "owner", Kind: FieldString, Required: true}, - PayloadField{Name: "binding_sha256", Kind: FieldString, Required: true}), - mutation(EventDocumentSourceVerified, CategorySystem, "An immutable document version received verified source provenance.", - str("solution"), - PayloadField{Name: "version_id", Kind: FieldString, Required: true}, - PayloadField{Name: "source_id", Kind: FieldString, Required: true}, - PayloadField{Name: "receipt_job_id", Kind: FieldString, Required: true}, - PayloadField{Name: "policy_revision", Kind: FieldString, Required: true}, - PayloadField{Name: "binding_sha256", Kind: FieldString, Required: true}, - PayloadField{Name: "effect_sha256", Kind: FieldString, Required: true}, - PayloadField{Name: "owner", Kind: FieldString, Required: true}), observation(EventDocumentReadUnscoped, CategoryAccess, "A document read that involved no collection ended with an explicit outcome: it was denied, found nothing the caller may read, or failed before any collection was resolved.", documentUnscopedReadFields...), observation(EventDocumentSearchUnscoped, CategoryAccess, "A search that involved no collection ended with an explicit outcome: it was denied, the caller may read no collection it names, or it failed before any collection was resolved.", documentUnscopedReadFields...), // A confirmation is recorded only when it commits: the module appends the @@ -965,8 +946,10 @@ var auditEventCatalog = []AuditEventDefinition{ // The native extractor itself stays off the trail; its digest names it. // Everything a revision always names is required: the version it was // derived from, the key and revision, its digest, whether it retires the - // key, the native request type and what proposed it. `proposal_source` is - // free text a person or a tool supplied, so it is PII. + // key, the native request type and what proposed it. `proposal_source` + // names the proposing run, receipt or derivation, but nothing bounds it to + // an identifier: it is an unvalidated string of up to 512 bytes, so it is + // marked PII and redacted from exports and webhooks. requiresIdempotencyKey(requiresEntry(mutation(EventDocumentExtractorRevised, CategoryLifecycle, "An extractor attached to a document was revised: attached, corrected or retired.", append(required(documentFields, "version"), diff --git a/module/services/accounts/code/pkg/business/audit_registry_test.go b/module/services/accounts/code/pkg/business/audit_registry_test.go index 49319651a..47ec1d771 100644 --- a/module/services/accounts/code/pkg/business/audit_registry_test.go +++ b/module/services/accounts/code/pkg/business/audit_registry_test.go @@ -258,7 +258,7 @@ func TestValidatePayload_DocumentModuleReservedTypes(t *testing.T) { complete := func() map[string]any { return map[string]any{ "version": "v1", "extractor_key": "k", "revision": 2, "digest": "d", "parent_digest": "p", - "retired": false, "request_type": "t", "proposal_kind": "human", "proposal_source": "a note", + "retired": false, "request_type": "t", "proposal_kind": "person", "proposal_source": "a note", } } require.NoError(t, ValidatePayload(EventDocumentExtractorRevised, complete())) @@ -304,29 +304,6 @@ func TestValidatePayload_DocumentModuleReservedTypes(t *testing.T) { require.Errorf(t, ValidatePayload(event, p), "%s: correlation_id is required", event) } }) - t.Run("source_verified and protection_changed", func(t *testing.T) { - verified := map[string]any{"solution": "documents", "version_id": "v", "source_id": "s", "receipt_job_id": "j", - "policy_revision": "r", "binding_sha256": "b", "effect_sha256": "e", "owner": "o"} - require.NoError(t, ValidatePayload(EventDocumentSourceVerified, verified)) - for field := range verified { - if field == "solution" { - continue - } - p := maps.Clone(verified) - delete(p, field) - require.Errorf(t, ValidatePayload(EventDocumentSourceVerified, p), "source_verified %s is required", field) - } - changed := map[string]any{"solution": "documents", "policy_revision": "r", "retired": true, "owner": "o", "binding_sha256": "b"} - require.NoError(t, ValidatePayload(EventDatasourceProtectionChanged, changed)) - for field := range changed { - if field == "solution" { - continue - } - p := maps.Clone(changed) - delete(p, field) - require.Errorf(t, ValidatePayload(EventDatasourceProtectionChanged, p), "protection_changed %s is required", field) - } - }) } // The nonempty rule belongs to the declaration, not to a list of the event types diff --git a/module/services/accounts/code/pkg/eventcatalog/catalog_gen.go b/module/services/accounts/code/pkg/eventcatalog/catalog_gen.go index bdd2b43cf..68c78da8f 100644 --- a/module/services/accounts/code/pkg/eventcatalog/catalog_gen.go +++ b/module/services/accounts/code/pkg/eventcatalog/catalog_gen.go @@ -75,7 +75,6 @@ var published = [...]PublishedEvent{ {Type: "saas.datasource.group_unbound", Namespace: "saas", Schema: "saas/events/v1/events.proto#EventEnvelope", Major: 1, Visibility: "external", Partition: "", Retention: "30d"}, {Type: "saas.datasource.operation.declared", Namespace: "saas", Schema: "saas/events/v1/events.proto#EventEnvelope", Major: 1, Visibility: "external", Partition: "", Retention: "30d"}, {Type: "saas.datasource.operation.invoked", Namespace: "saas", Schema: "saas/events/v1/events.proto#EventEnvelope", Major: 1, Visibility: "external", Partition: "", Retention: "30d"}, - {Type: "saas.datasource.protection_changed", Namespace: "saas", Schema: "saas/events/v1/events.proto#EventEnvelope", Major: 1, Visibility: "external", Partition: "", Retention: "30d"}, {Type: "saas.datasource.receipts.pruned", Namespace: "saas", Schema: "saas/events/v1/events.proto#EventEnvelope", Major: 1, Visibility: "external", Partition: "", Retention: "30d"}, {Type: "saas.datasource.snapshot_too_large", Namespace: "saas", Schema: "saas/events/v1/events.proto#EventEnvelope", Major: 1, Visibility: "external", Partition: "", Retention: "30d"}, {Type: "saas.datasource.source.access_lost", Namespace: "saas", Schema: "saas/events/v1/events.proto#EventEnvelope", Major: 1, Visibility: "external", Partition: "", Retention: "30d"}, @@ -112,7 +111,6 @@ var published = [...]PublishedEvent{ {Type: "saas.document.search_unscoped", Namespace: "saas", Schema: "saas/events/v1/events.proto#EventEnvelope", Major: 1, Visibility: "external", Partition: "", Retention: "30d"}, {Type: "saas.document.snapshot.committed", Namespace: "saas", Schema: "saas/events/v1/events.proto#EventEnvelope", Major: 1, Visibility: "external", Partition: "", Retention: "30d"}, {Type: "saas.document.snapshot.skipped_stale", Namespace: "saas", Schema: "saas/events/v1/events.proto#EventEnvelope", Major: 1, Visibility: "external", Partition: "", Retention: "30d"}, - {Type: "saas.document.source_verified", Namespace: "saas", Schema: "saas/events/v1/events.proto#EventEnvelope", Major: 1, Visibility: "external", Partition: "", Retention: "30d"}, {Type: "saas.document.subscribed", Namespace: "saas", Schema: "saas/events/v1/events.proto#EventEnvelope", Major: 1, Visibility: "external", Partition: "", Retention: "30d"}, {Type: "saas.document.unarchived", Namespace: "saas", Schema: "saas/events/v1/events.proto#EventEnvelope", Major: 1, Visibility: "external", Partition: "", Retention: "30d"}, {Type: "saas.document.unfrozen", Namespace: "saas", Schema: "saas/events/v1/events.proto#EventEnvelope", Major: 1, Visibility: "external", Partition: "", Retention: "30d"},