@openauditmodel/cli 0.3.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/README.md +62 -6
  2. package/dist/conformance/src/cli.js +113 -6
  3. package/dist/conformance/src/cli.js.map +1 -1
  4. package/dist/conformance/src/privacy/types.d.ts +19 -0
  5. package/dist/conformance/src/privacy/types.js +8 -0
  6. package/dist/conformance/src/privacy/types.js.map +1 -1
  7. package/dist/conformance/src/profiles/coverage.d.ts +62 -0
  8. package/dist/conformance/src/profiles/coverage.js +114 -0
  9. package/dist/conformance/src/profiles/coverage.js.map +1 -0
  10. package/package.json +7 -2
  11. package/profiles/README.md +42 -0
  12. package/profiles/api-and-integration-management/0.1/profile.json +258 -0
  13. package/profiles/backup-and-recovery/0.1/profile.json +178 -0
  14. package/profiles/customer-and-account-management/0.1/profile.json +237 -0
  15. package/profiles/deployment-and-change-management/0.1/profile.json +298 -0
  16. package/profiles/document-management/0.1/profile.json +170 -0
  17. package/profiles/financial-transaction-management/0.1/profile.json +247 -0
  18. package/profiles/identity-and-access-management/0.1/profile.json +120 -0
  19. package/profiles/incident-management/0.1/profile.json +256 -0
  20. package/profiles/incident-management/0.2/profile.json +287 -0
  21. package/profiles/incident-management/README.md +39 -21
  22. package/profiles/incident-management/profile.json +37 -6
  23. package/profiles/message-broker-management/0.1/profile.json +399 -0
  24. package/profiles/secrets-and-key-management/0.1/profile.json +219 -0
  25. package/semantic-conventions/README.md +15 -10
  26. package/semantic-conventions/backup-and-recovery.md +124 -0
  27. package/semantic-conventions/customer-and-account.md +133 -0
  28. package/semantic-conventions/financial-transactions.md +128 -0
  29. package/semantic-conventions/message-brokers.md +128 -0
  30. package/semantic-conventions/secrets-and-keys.md +132 -0
@@ -31,22 +31,22 @@ cannot be reconstructed at all.
31
31
 
32
32
  ### Event families
33
33
 
34
- | Family | Governed | Notes |
35
- | --------------------------------------------- | -------- | ------------------------------------------------- |
36
- | `incident.case.create` | yes | Raising a case |
37
- | `incident.priority.change` | yes | Reassessment of priority |
38
- | `incident.assignment.change` | yes | Change of accountable owner |
39
- | `incident.major.declare` | yes | Escalation to major |
40
- | `incident.case.resolve` | yes | Service restored |
41
- | `incident.case.close`, `incident.case.cancel` | yes | Terminal transitions |
42
- | `incident.case.reopen` | yes | A new lifecycle transition, governed on its own |
43
- | `incident.rca.*` | yes | Root cause analysis, including approval |
44
- | `incident.sla.breach` | yes | A missed commitment; excluded from `INC-CORE-001` |
45
- | `problem.case.create`, `problem.case.close` | yes | Problem management, where it is a separate record |
46
- | `corrective-action.*` | yes | Opening, verifying and closing an action |
47
- | `monitoring.alert.*` | **no** | Alert observations |
48
- | `incident.note.*`, `incident.timeline.*` | **no** | Working notes and timeline chatter |
49
- | `incident.case.view` and other reads | **no** | Reading a case record |
34
+ | Family | Governed | Notes |
35
+ | -------------------------------------------------------------- | -------- | ------------------------------------------------- |
36
+ | `incident.case.create`, `incident.create` | yes | Raising a case |
37
+ | `incident.priority.change` | yes | Reassessment of priority |
38
+ | `incident.assignment.change` | yes | Change of accountable owner |
39
+ | `incident.major.declare` | yes | Escalation to major |
40
+ | `incident.case.resolve`, `incident.resolve` | yes | Service restored |
41
+ | `incident.case.close`, `incident.case.cancel`, and their twins | yes | Terminal transitions |
42
+ | `incident.case.reopen`, `incident.reopen` | yes | A new lifecycle transition, governed on its own |
43
+ | `incident.rca.*` | yes | Root cause analysis, including approval |
44
+ | `incident.sla.breach` | yes | A missed commitment; excluded from `INC-CORE-001` |
45
+ | `problem.case.create`, `problem.case.close`, and their twins | yes | Problem management, where it is a separate record |
46
+ | `corrective-action.*` | yes | Opening, verifying and closing an action |
47
+ | `monitoring.alert.*` | **no** | Alert observations |
48
+ | `incident.note.*`, `incident.timeline.*` | **no** | Working notes and timeline chatter |
49
+ | `incident.case.view`, `incident.view` and other reads | **no** | Reading a case record |
50
50
 
51
51
  The names come from
52
52
  [workflow-and-approval.md](../../semantic-conventions/workflow-and-approval.md), which already
@@ -56,6 +56,22 @@ that revealed it, and `corrective-action.*` for the remediation that outlives bo
56
56
  core naming rules; `corrective-action.open` uses the two-segment form permitted where a resource
57
57
  segment would be artificial, exactly as `authentication.login` does.
58
58
 
59
+ ### Both segment forms of a case operation are governed
60
+
61
+ Every `<domain>.case.<action>` selector is accompanied by its two-segment twin, `<domain>.<action>`.
62
+ Not every incident system models the incident as a separate _case_ record: a system that manages the
63
+ incident directly emits `incident.create`, and only "at least two segments" is normative
64
+ ([event-model.md](../../specification/event-model.md) §7). Both names describe the same operation, so
65
+ both carry the same rules, and a test asserts the two selections are identical rather than merely
66
+ overlapping.
67
+
68
+ The alternative was to govern one spelling and leave the other `not-applicable`. That would have been
69
+ worse than silence: `not-applicable` is not conformance, and a producer whose only difference from a
70
+ governed system is a redundant middle segment would have been told nothing at all.
71
+
72
+ The twins are exact names, never prefixes, so nothing beneath them is drawn in — `incident.note.create`
73
+ and `incident.view` stay ungoverned, and a test names each of them.
74
+
59
75
  ### Explicit exclusions
60
76
 
61
77
  **Alert and monitoring observations.** A monitoring system emits alerts continuously, the
@@ -88,14 +104,14 @@ event.
88
104
  | ------------------ | ------------------------------------------------------ | ---------------------------------------------------------- |
89
105
  | `INC-CORE-001` | every governed event except `incident.sla.breach` | `/authorization`, `/metadata/incident/status` |
90
106
  | `INC-CORE-002` | every governed event | _recommends_ `/request/correlationId`, `/relatedResources` |
91
- | `INC-CREATE-001` | `incident.case.create`, `problem.case.create` | _recommends_ `/reason`, detection time, impact, urgency |
107
+ | `INC-CREATE-001` | `incident.case.create`, `problem.case.create`, twins | _recommends_ `/reason`, detection time, impact, urgency |
92
108
  | `INC-STATE-001` | every state transition except reopen | `/change` |
93
109
  | `INC-STATE-002` | reprioritisation, escalation, closure, cancellation | `/reason` |
94
110
  | `INC-PRIORITY-001` | creation, reprioritisation, escalation, breach | `/metadata/incident/priority` |
95
111
  | `INC-ASSIGN-001` | `incident.assignment.change`, `corrective-action.open` | `/metadata/incident/assigneeId`; recommends `/reason` |
96
- | `INC-RESOLVE-001` | `incident.case.resolve` | `/metadata/incident/resolutionType` |
112
+ | `INC-RESOLVE-001` | `incident.case.resolve`, `incident.resolve` | `/metadata/incident/resolutionType` |
97
113
  | `INC-CLOSE-001` | closure or cancellation **declared to need approval** | `/approval/status` |
98
- | `INC-REOPEN-001` | `incident.case.reopen` | `/change`, `/reason`; recommends `/evidence` |
114
+ | `INC-REOPEN-001` | `incident.case.reopen`, `incident.reopen` | `/change`, `/reason`; recommends `/evidence` |
99
115
  | `INC-RCA-001` | `incident.rca.*` | `/metadata/incident/rca/method` |
100
116
  | `INC-RCA-002` | `incident.rca.approve` | `/approval/status` |
101
117
  | `INC-CAPA-001` | `corrective-action.verify` | `/metadata/incident/correctiveAction/verificationMethod` |
@@ -260,7 +276,7 @@ possible without collision.
260
276
 
261
277
  ## Fixture matrix
262
278
 
263
- [examples/profiles/incident-management/](../../examples/profiles/incident-management/) — thirteen
279
+ [examples/profiles/incident-management/](../../examples/profiles/incident-management/) — fourteen
264
280
  valid, fourteen invalid, three not-applicable. Every fixture is core-conforming and privacy-clean;
265
281
  every invalid fixture is core-**valid** and fails exactly one profile rule with exactly one error.
266
282
  There is one invalid fixture per _requirement_, not per rule, so the two rules that require two
@@ -286,7 +302,9 @@ things each carry two fixtures; a test derives that obligation from `profile.jso
286
302
 
287
303
  `problem-case-close.json` and `major-declare.json` exercise the problem domain and the escalation
288
304
  path; `problem-case-close.json` is also the fixture that proves the closure-approval condition stays
289
- quiet when the producer declares approval was not required.
305
+ quiet when the producer declares approval was not required. `create-short-form.json` is
306
+ `incident.create` rather than `incident.case.create` — the same operation as `case-create.json` under
307
+ the two-segment name, so a reader of the fixture corpus alone sees that both spellings conform.
290
308
 
291
309
  ## Not-applicable rationale
292
310
 
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "profileVersion": "0.1",
3
3
  "name": "incident-management",
4
- "version": "0.1",
4
+ "version": "0.2",
5
5
  "status": "experimental",
6
6
  "coreVersions": ["0.1"],
7
7
  "title": "Incident Management Profile",
@@ -18,17 +18,24 @@
18
18
  "corrective-action.verify",
19
19
  "incident.assignment.change",
20
20
  "incident.case.cancel",
21
+ "incident.cancel",
21
22
  "incident.case.close",
23
+ "incident.close",
22
24
  "incident.case.create",
25
+ "incident.create",
23
26
  "incident.case.reopen",
27
+ "incident.reopen",
24
28
  "incident.case.resolve",
29
+ "incident.resolve",
25
30
  "incident.major.declare",
26
31
  "incident.priority.change",
27
32
  "incident.rca.approve",
28
33
  "incident.rca.create",
29
34
  "incident.rca.update",
30
35
  "problem.case.close",
31
- "problem.case.create"
36
+ "problem.close",
37
+ "problem.case.create",
38
+ "problem.create"
32
39
  ],
33
40
  "requiredPaths": ["/authorization"],
34
41
  "requiredMetadata": [
@@ -49,10 +56,15 @@
49
56
  "corrective-action.verify",
50
57
  "incident.assignment.change",
51
58
  "incident.case.cancel",
59
+ "incident.cancel",
52
60
  "incident.case.close",
61
+ "incident.close",
53
62
  "incident.case.create",
63
+ "incident.create",
54
64
  "incident.case.reopen",
65
+ "incident.reopen",
55
66
  "incident.case.resolve",
67
+ "incident.resolve",
56
68
  "incident.major.declare",
57
69
  "incident.priority.change",
58
70
  "incident.rca.approve",
@@ -60,7 +72,9 @@
60
72
  "incident.rca.update",
61
73
  "incident.sla.breach",
62
74
  "problem.case.close",
63
- "problem.case.create"
75
+ "problem.close",
76
+ "problem.case.create",
77
+ "problem.create"
64
78
  ],
65
79
  "recommendedPaths": ["/request/correlationId", "/relatedResources"]
66
80
  },
@@ -69,7 +83,12 @@
69
83
  "description": "Raising a case should record how the condition was noticed, when it began, and how badly it hurt.",
70
84
  "rationale": "Detection time and business impact are known accurately only at the moment a case is opened, and are reconstructed badly or not at all afterwards. They are recommended rather than required because impact and urgency scales differ between organizations and some cases are raised by a customer with no measurable detection point, and a requirement that could not be met honestly would be satisfied with a placeholder instead.",
71
85
  "severity": "warning",
72
- "events": ["incident.case.create", "problem.case.create"],
86
+ "events": [
87
+ "incident.case.create",
88
+ "incident.create",
89
+ "problem.case.create",
90
+ "problem.create"
91
+ ],
73
92
  "recommendedPaths": [
74
93
  "/reason",
75
94
  "/metadata/incident/detectedAt",
@@ -87,9 +106,13 @@
87
106
  "incident.assignment.change",
88
107
  "incident.major.declare",
89
108
  "incident.case.resolve",
109
+ "incident.resolve",
90
110
  "incident.case.close",
111
+ "incident.close",
91
112
  "incident.case.cancel",
113
+ "incident.cancel",
92
114
  "problem.case.close",
115
+ "problem.close",
93
116
  "corrective-action.close"
94
117
  ],
95
118
  "requiredPaths": ["/change"]
@@ -103,8 +126,11 @@
103
126
  "incident.priority.change",
104
127
  "incident.major.declare",
105
128
  "incident.case.close",
129
+ "incident.close",
106
130
  "incident.case.cancel",
131
+ "incident.cancel",
107
132
  "problem.case.close",
133
+ "problem.close",
108
134
  "corrective-action.close"
109
135
  ],
110
136
  "requiredPaths": ["/reason"]
@@ -116,7 +142,9 @@
116
142
  "severity": "error",
117
143
  "events": [
118
144
  "incident.case.create",
145
+ "incident.create",
119
146
  "problem.case.create",
147
+ "problem.create",
120
148
  "incident.priority.change",
121
149
  "incident.major.declare",
122
150
  "incident.sla.breach"
@@ -147,7 +175,7 @@
147
175
  "description": "Resolving a case records what kind of resolution it was.",
148
176
  "rationale": "A workaround, a permanent fix, a duplicate and a condition that disappeared on its own are four different outcomes with four different follow-up obligations, and a status of `resolved` collapses all of them into one word. The distinction is what tells a later reader whether the underlying fault is still present in production. The recommended fields point at the rest of the story: when the service was actually restored, and which corrective action carries the remaining work.",
149
177
  "severity": "error",
150
- "events": ["incident.case.resolve"],
178
+ "events": ["incident.case.resolve", "incident.resolve"],
151
179
  "requiredMetadata": [
152
180
  {
153
181
  "path": "/incident/resolutionType",
@@ -167,8 +195,11 @@
167
195
  "severity": "error",
168
196
  "events": [
169
197
  "incident.case.close",
198
+ "incident.close",
170
199
  "incident.case.cancel",
200
+ "incident.cancel",
171
201
  "problem.case.close",
202
+ "problem.close",
172
203
  "corrective-action.close"
173
204
  ],
174
205
  "when": {
@@ -183,7 +214,7 @@
183
214
  "description": "Reopening a closed case is a new lifecycle transition, recorded with the transition it made and the reason it was made.",
184
215
  "rationale": "A reopen is the strongest evidence an incident trail can carry that a resolution was wrong, so it is the transition most worth recording well. It is treated as a new auditable step and nothing more: the earlier closure and any approval attached to it remain valid historical records of what was decided at that time, and this profile does not assert that a reopen retracts them or that a fresh approval is owed, because that is an organizational process this specification does not make. Reusing the original correlation identifier, recommended by INC-CORE-002, is what keeps the whole handling readable as one story rather than two unrelated cases.",
185
216
  "severity": "error",
186
- "events": ["incident.case.reopen"],
217
+ "events": ["incident.case.reopen", "incident.reopen"],
187
218
  "requiredPaths": ["/change", "/reason"],
188
219
  "recommendedPaths": ["/evidence"]
189
220
  },
@@ -0,0 +1,399 @@
1
+ {
2
+ "profileVersion": "0.1",
3
+ "name": "message-broker-management",
4
+ "version": "0.1",
5
+ "status": "experimental",
6
+ "coreVersions": ["0.1"],
7
+ "title": "Message Broker Management Profile",
8
+ "description": "Additional conformance requirements for message broker control-plane audit events: cluster, topic, queue, exchange, stream and consumer-group administration, access control lists, quotas, broker configuration, offset resets and message replay. Every requirement adds to the OpenAuditModel Core Specification; none relaxes it. Data-plane traffic — publishing, consuming, acknowledging and automatic rebalancing — is deliberately not governed, and message payloads are never recorded.",
9
+ "rules": [
10
+ {
11
+ "id": "BROKER-CORE-001",
12
+ "description": "Every governed control-plane operation records the authorization decision that permitted it, the broker family it was performed against and the broker deployment it acted on.",
13
+ "rationale": "A broker administration event without a recorded decision cannot be reviewed: nothing distinguishes an operation policy allowed from one performed with a standing cluster-admin credential that policy never saw. The broker family is what makes every other field interpretable, because the same word means different things across families — an offset is a durable log position on one broker and has no meaning at all on a work queue, and an ACL is a per-resource rule on one and a virtual-host regular expression on another. The deployment identifier is what makes the trail searchable: fleets run dozens of clusters holding identically named topics, and `application.environment` alone cannot tell a reviewer which of them lost its data.",
14
+ "severity": "error",
15
+ "events": [
16
+ "broker.acl.grant",
17
+ "broker.acl.revoke",
18
+ "broker.cluster.create",
19
+ "broker.cluster.delete",
20
+ "broker.cluster.failover",
21
+ "broker.cluster.scale",
22
+ "broker.cluster.upgrade",
23
+ "broker.configuration.update",
24
+ "broker.consumer-group.create",
25
+ "broker.consumer-group.delete",
26
+ "broker.consumer-group.update",
27
+ "broker.exchange.create",
28
+ "broker.exchange.delete",
29
+ "broker.exchange.update",
30
+ "broker.message.replay",
31
+ "broker.offset.reset",
32
+ "broker.permission.grant",
33
+ "broker.permission.revoke",
34
+ "broker.queue.create",
35
+ "broker.queue.delete",
36
+ "broker.queue.purge",
37
+ "broker.queue.update",
38
+ "broker.quota.create",
39
+ "broker.quota.delete",
40
+ "broker.quota.update",
41
+ "broker.stream.create",
42
+ "broker.stream.delete",
43
+ "broker.stream.trim",
44
+ "broker.stream.update",
45
+ "broker.topic.create",
46
+ "broker.topic.delete",
47
+ "broker.topic.update"
48
+ ],
49
+ "requiredPaths": ["/authorization"],
50
+ "requiredMetadata": [
51
+ {
52
+ "path": "/broker/system",
53
+ "type": "string"
54
+ },
55
+ {
56
+ "path": "/broker/clusterId",
57
+ "type": "string"
58
+ }
59
+ ]
60
+ },
61
+ {
62
+ "id": "BROKER-CORE-002",
63
+ "description": "A governed control-plane operation should record why it happened, how the operator was authenticated, which container the affected resource lives in, and how it correlates with the wider administrative workflow.",
64
+ "rationale": "These are the fields a reviewer reaches for first and an automated control plane omits most often. They are recommended rather than required because a conforming producer may legitimately have none of them: a broker managed entirely by a reconciliation loop has no interactive authentication, a cluster-level event has no containing resource, and a single-step change has nothing to correlate with. A missing justification should prompt a question, not fail a build.",
65
+ "severity": "warning",
66
+ "events": [
67
+ "broker.acl.grant",
68
+ "broker.acl.revoke",
69
+ "broker.cluster.create",
70
+ "broker.cluster.delete",
71
+ "broker.cluster.failover",
72
+ "broker.cluster.scale",
73
+ "broker.cluster.upgrade",
74
+ "broker.configuration.update",
75
+ "broker.consumer-group.create",
76
+ "broker.consumer-group.delete",
77
+ "broker.consumer-group.update",
78
+ "broker.exchange.create",
79
+ "broker.exchange.delete",
80
+ "broker.exchange.update",
81
+ "broker.message.replay",
82
+ "broker.offset.reset",
83
+ "broker.permission.grant",
84
+ "broker.permission.revoke",
85
+ "broker.queue.create",
86
+ "broker.queue.delete",
87
+ "broker.queue.purge",
88
+ "broker.queue.update",
89
+ "broker.quota.create",
90
+ "broker.quota.delete",
91
+ "broker.quota.update",
92
+ "broker.stream.create",
93
+ "broker.stream.delete",
94
+ "broker.stream.trim",
95
+ "broker.stream.update",
96
+ "broker.topic.create",
97
+ "broker.topic.delete",
98
+ "broker.topic.update"
99
+ ],
100
+ "recommendedPaths": [
101
+ "/reason",
102
+ "/authentication",
103
+ "/resource/parentId",
104
+ "/request/correlationId"
105
+ ]
106
+ },
107
+ {
108
+ "id": "BROKER-RISK-001",
109
+ "description": "Every governed control-plane operation declares whether it discards data or withdraws access.",
110
+ "rationale": "A broker control plane exposes reversible and irreversible operations through the same interface, and the profile cannot tell them apart from an event name alone: whether a configuration change discards retained messages depends on the direction of the change, and whether a consumer-group update loses committed positions depends on the broker. Only the producer knows. Recording the answer — including `false`, which is an answer rather than an absence — is what lets a reviewer filter the small set of operations that cannot be undone out of a stream dominated by routine ones, and it is the flag the conditional rules in this profile depend on.",
111
+ "severity": "error",
112
+ "events": [
113
+ "broker.acl.grant",
114
+ "broker.acl.revoke",
115
+ "broker.cluster.create",
116
+ "broker.cluster.delete",
117
+ "broker.cluster.failover",
118
+ "broker.cluster.scale",
119
+ "broker.cluster.upgrade",
120
+ "broker.configuration.update",
121
+ "broker.consumer-group.create",
122
+ "broker.consumer-group.delete",
123
+ "broker.consumer-group.update",
124
+ "broker.exchange.create",
125
+ "broker.exchange.delete",
126
+ "broker.exchange.update",
127
+ "broker.message.replay",
128
+ "broker.offset.reset",
129
+ "broker.permission.grant",
130
+ "broker.permission.revoke",
131
+ "broker.queue.create",
132
+ "broker.queue.delete",
133
+ "broker.queue.purge",
134
+ "broker.queue.update",
135
+ "broker.quota.create",
136
+ "broker.quota.delete",
137
+ "broker.quota.update",
138
+ "broker.stream.create",
139
+ "broker.stream.delete",
140
+ "broker.stream.trim",
141
+ "broker.stream.update",
142
+ "broker.topic.create",
143
+ "broker.topic.delete",
144
+ "broker.topic.update"
145
+ ],
146
+ "requiredMetadata": [
147
+ {
148
+ "path": "/broker/operation/destructive",
149
+ "type": "boolean"
150
+ }
151
+ ]
152
+ },
153
+ {
154
+ "id": "BROKER-RISK-002",
155
+ "description": "An operation the producer declared destructive is justified, and should be approved.",
156
+ "rationale": "Purging a queue, deleting a topic or shortening a retention window destroys the very messages an investigation would later want, so the event is the only surviving evidence of why it was acceptable. These operations are also the ones most often performed under incident pressure, when the justification is clearest to the operator and least recoverable afterwards. Approval is recommended rather than required because a great many destructive broker operations are legitimately unilateral — clearing a development queue, deleting a topic the same team created an hour ago — and a rule that demanded a second signature for all of them would be switched off rather than met.",
157
+ "severity": "error",
158
+ "events": [
159
+ "broker.acl.grant",
160
+ "broker.acl.revoke",
161
+ "broker.cluster.create",
162
+ "broker.cluster.delete",
163
+ "broker.cluster.failover",
164
+ "broker.cluster.scale",
165
+ "broker.cluster.upgrade",
166
+ "broker.configuration.update",
167
+ "broker.consumer-group.create",
168
+ "broker.consumer-group.delete",
169
+ "broker.consumer-group.update",
170
+ "broker.exchange.create",
171
+ "broker.exchange.delete",
172
+ "broker.exchange.update",
173
+ "broker.message.replay",
174
+ "broker.offset.reset",
175
+ "broker.permission.grant",
176
+ "broker.permission.revoke",
177
+ "broker.queue.create",
178
+ "broker.queue.delete",
179
+ "broker.queue.purge",
180
+ "broker.queue.update",
181
+ "broker.quota.create",
182
+ "broker.quota.delete",
183
+ "broker.quota.update",
184
+ "broker.stream.create",
185
+ "broker.stream.delete",
186
+ "broker.stream.trim",
187
+ "broker.stream.update",
188
+ "broker.topic.create",
189
+ "broker.topic.delete",
190
+ "broker.topic.update"
191
+ ],
192
+ "when": {
193
+ "path": "/metadata/broker/operation/destructive",
194
+ "equals": true
195
+ },
196
+ "requiredPaths": ["/reason"],
197
+ "recommendedPaths": ["/approval"]
198
+ },
199
+ {
200
+ "id": "BROKER-RISK-003",
201
+ "description": "An operation the producer declared to need approval records the approval it obtained or failed to obtain.",
202
+ "rationale": "Which broker operations require a second signature is an organizational decision that differs by cluster, environment and team, so the profile does not attempt to decide it: the producer declares that policy applied, and the profile enforces the consequence. Without the approval object an event asserts that a control existed and says nothing about whether it was satisfied, which is the least useful thing an audit trail can record. The rule requires the approval to be present, not to be granted, so that a rejected or expired approval remains recordable — a change blocked by a control is exactly the event a reviewer most wants to find.",
203
+ "severity": "error",
204
+ "events": [
205
+ "broker.acl.grant",
206
+ "broker.acl.revoke",
207
+ "broker.cluster.create",
208
+ "broker.cluster.delete",
209
+ "broker.cluster.failover",
210
+ "broker.cluster.scale",
211
+ "broker.cluster.upgrade",
212
+ "broker.configuration.update",
213
+ "broker.consumer-group.create",
214
+ "broker.consumer-group.delete",
215
+ "broker.consumer-group.update",
216
+ "broker.exchange.create",
217
+ "broker.exchange.delete",
218
+ "broker.exchange.update",
219
+ "broker.message.replay",
220
+ "broker.offset.reset",
221
+ "broker.permission.grant",
222
+ "broker.permission.revoke",
223
+ "broker.queue.create",
224
+ "broker.queue.delete",
225
+ "broker.queue.purge",
226
+ "broker.queue.update",
227
+ "broker.quota.create",
228
+ "broker.quota.delete",
229
+ "broker.quota.update",
230
+ "broker.stream.create",
231
+ "broker.stream.delete",
232
+ "broker.stream.trim",
233
+ "broker.stream.update",
234
+ "broker.topic.create",
235
+ "broker.topic.delete",
236
+ "broker.topic.update"
237
+ ],
238
+ "when": {
239
+ "path": "/metadata/broker/approvalRequired",
240
+ "equals": true
241
+ },
242
+ "requiredPaths": ["/approval"]
243
+ },
244
+ {
245
+ "id": "BROKER-FAIL-001",
246
+ "description": "A failed control-plane operation records a coarse classification of the failure.",
247
+ "rationale": "Failed broker administration is where an outage and an intrusion look identical at first glance, and both arrive in bulk: a misconfigured operator retries, and a principal probing for permissions produces the same shape of traffic. The core requires a producer-defined failure code; the profile additionally requires the coarse class, because a reviewer triaging several hundred failures needs to separate authorization refusals from timeouts and validation errors without first learning every producer's code vocabulary. The classification is a small token and, unlike a message, cannot carry broker state or payload fragments.",
248
+ "severity": "error",
249
+ "events": [
250
+ "broker.acl.grant",
251
+ "broker.acl.revoke",
252
+ "broker.cluster.create",
253
+ "broker.cluster.delete",
254
+ "broker.cluster.failover",
255
+ "broker.cluster.scale",
256
+ "broker.cluster.upgrade",
257
+ "broker.configuration.update",
258
+ "broker.consumer-group.create",
259
+ "broker.consumer-group.delete",
260
+ "broker.consumer-group.update",
261
+ "broker.exchange.create",
262
+ "broker.exchange.delete",
263
+ "broker.exchange.update",
264
+ "broker.message.replay",
265
+ "broker.offset.reset",
266
+ "broker.permission.grant",
267
+ "broker.permission.revoke",
268
+ "broker.queue.create",
269
+ "broker.queue.delete",
270
+ "broker.queue.purge",
271
+ "broker.queue.update",
272
+ "broker.quota.create",
273
+ "broker.quota.delete",
274
+ "broker.quota.update",
275
+ "broker.stream.create",
276
+ "broker.stream.delete",
277
+ "broker.stream.trim",
278
+ "broker.stream.update",
279
+ "broker.topic.create",
280
+ "broker.topic.delete",
281
+ "broker.topic.update"
282
+ ],
283
+ "when": {
284
+ "path": "/event/outcome",
285
+ "equals": "failure"
286
+ },
287
+ "requiredPaths": ["/event/error/type"],
288
+ "recommendedPaths": ["/event/error/retryable"]
289
+ },
290
+ {
291
+ "id": "BROKER-LIFECYCLE-001",
292
+ "description": "Creating a topic, queue, stream or exchange records the classification of the data it will carry, and should record who owns it.",
293
+ "rationale": "A broker resource is created once and lives for years, and the sensitivity of what flows through it is decided at that moment and almost never revisited. An unclassified topic becomes a data store nobody can triage: when it is later replayed, exported or exposed by a misconfigured ACL, the first question a responder asks — was this payment data or build telemetry — has no recorded answer, and reconstructing it means reading the messages, which is precisely what the audit trail exists to avoid. Ownership is recommended rather than required because a resource created by a platform pipeline may genuinely have no owning principal at creation time.",
294
+ "severity": "error",
295
+ "events": [
296
+ "broker.topic.create",
297
+ "broker.queue.create",
298
+ "broker.stream.create",
299
+ "broker.exchange.create"
300
+ ],
301
+ "requiredPaths": ["/resource/classification"],
302
+ "recommendedPaths": ["/resource/ownerId"]
303
+ },
304
+ {
305
+ "id": "BROKER-CHANGE-001",
306
+ "description": "An operation that modifies broker configuration, access control, a quota or a consumer position records what it changed.",
307
+ "rationale": "These operations mutate state that was already there, so the event's value is entirely in the transition: an access-control change that records only that an ACL was written cannot answer whether a principal gained or lost the ability to read a topic, and a configuration change that records only the resulting value cannot show that retention was cut from thirty days to one. The core deliberately accepts changed field names, a sanitized subset, a hash or a reference in place of full before and after states, so this requirement can be met without copying broker state into the audit trail.",
308
+ "severity": "error",
309
+ "events": [
310
+ "broker.acl.grant",
311
+ "broker.acl.revoke",
312
+ "broker.configuration.update",
313
+ "broker.consumer-group.update",
314
+ "broker.exchange.update",
315
+ "broker.offset.reset",
316
+ "broker.permission.grant",
317
+ "broker.permission.revoke",
318
+ "broker.queue.update",
319
+ "broker.quota.create",
320
+ "broker.quota.delete",
321
+ "broker.quota.update",
322
+ "broker.stream.update",
323
+ "broker.topic.update"
324
+ ],
325
+ "requiredPaths": ["/change"],
326
+ "recommendedPaths": ["/change/changedFields"]
327
+ },
328
+ {
329
+ "id": "BROKER-ACL-001",
330
+ "description": "A broker access-control change names the principal whose access changed and the permission involved, and should record whether the rule allows or denies.",
331
+ "rationale": "Broker access control is where the confidentiality of every topic on a cluster is actually decided, and an ACL event that does not name the principal is not reviewable at all — the resource tells a reviewer which topic was touched and nothing about who can now read it.The allow-or-deny effect is recommended rather than required because several broker families support only positive grants, and forcing them to assert `allow` on every event adds a constant rather than a fact. The principal is recorded in namespaced metadata rather than in `/subject`, because [actor-model.md](../../specification/actor-model.md) §5 defines `subject` as the principal *on whose behalf* the actor acted, and forbids its use as a generic target. A grantee or assignee is neither. This follows the identity profile, which records a role in `/metadata/role/id` for the same reason.",
332
+ "severity": "error",
333
+ "events": [
334
+ "broker.acl.grant",
335
+ "broker.acl.revoke",
336
+ "broker.permission.grant",
337
+ "broker.permission.revoke"
338
+ ],
339
+ "requiredMetadata": [
340
+ {
341
+ "path": "/broker/acl/permission",
342
+ "type": "string"
343
+ },
344
+ {
345
+ "path": "/broker/acl/principalId",
346
+ "type": "string"
347
+ }
348
+ ],
349
+ "recommendedPaths": ["/metadata/broker/acl/effect"]
350
+ },
351
+ {
352
+ "id": "BROKER-QUOTA-001",
353
+ "description": "A quota change records which dimension of consumption was limited.",
354
+ "rationale": "A quota is an availability control, and tightening one is a denial-of-service risk while loosening one removes a guardrail that protected every other tenant on the cluster. Neither can be assessed without knowing what was limited: a change to a throughput ceiling, a connection count and a storage allowance have completely different consequences and completely different suspects. The dimension is required as a token; the profile deliberately does not constrain the numeric values, because the rule language cannot compare numbers and a limit is only meaningful against a deployment's own baseline.",
355
+ "severity": "error",
356
+ "events": ["broker.quota.create", "broker.quota.delete", "broker.quota.update"],
357
+ "requiredMetadata": [
358
+ {
359
+ "path": "/broker/quota/dimension",
360
+ "type": "string"
361
+ }
362
+ ]
363
+ },
364
+ {
365
+ "id": "BROKER-OFFSET-001",
366
+ "description": "An offset reset records the position it moved from, the position it moved to, and why.",
367
+ "rationale": "Resetting a consumer position is the broker operation with the largest invisible blast radius: moving backwards replays side effects that already happened, and moving forwards silently abandons messages that will never be processed. Neither is visible in the resource, the outcome or the actor — only the two positions show what happened, and only the justification shows whether it was deliberate. Both positions are recorded as strings because broker position types differ irreconcilably: a 64-bit log offset, a stream entry identifier and a composite message identifier are all positions, and forcing one numeric shape would exclude most brokers.",
368
+ "severity": "error",
369
+ "events": ["broker.offset.reset"],
370
+ "requiredPaths": ["/reason"],
371
+ "requiredMetadata": [
372
+ {
373
+ "path": "/broker/offset/previous",
374
+ "type": "string"
375
+ },
376
+ {
377
+ "path": "/broker/offset/target",
378
+ "type": "string"
379
+ }
380
+ ],
381
+ "recommendedPaths": ["/metadata/broker/offset/strategy"]
382
+ },
383
+ {
384
+ "id": "BROKER-REPLAY-001",
385
+ "description": "A message replay records how much was replayed and why, and should record how many messages it covered.",
386
+ "rationale": "A replay re-delivers messages that consumers already handled, so its consequence is measured in duplicated side effects — repeated payments, repeated notifications, repeated downstream writes — and the difference between replaying one message and replaying an entire topic is the difference between a correction and an incident. The scope is required because it is the only field that carries that difference; the count is recommended because a producer streaming a replay may not know it in advance. The replayed messages themselves are never recorded: an audit event states that a replay happened, and the messages remain in the broker under their own controls.",
387
+ "severity": "error",
388
+ "events": ["broker.message.replay"],
389
+ "requiredPaths": ["/reason"],
390
+ "requiredMetadata": [
391
+ {
392
+ "path": "/broker/replay/scope",
393
+ "type": "string"
394
+ }
395
+ ],
396
+ "recommendedPaths": ["/metadata/broker/replay/messageCount"]
397
+ }
398
+ ]
399
+ }