@openauditmodel/cli 0.1.0

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 (134) hide show
  1. package/LICENSE +215 -0
  2. package/README.md +647 -0
  3. package/dist/conformance/src/cli.d.ts +20 -0
  4. package/dist/conformance/src/cli.js +560 -0
  5. package/dist/conformance/src/cli.js.map +1 -0
  6. package/dist/conformance/src/format-errors.d.ts +20 -0
  7. package/dist/conformance/src/format-errors.js +116 -0
  8. package/dist/conformance/src/format-errors.js.map +1 -0
  9. package/dist/conformance/src/integrity/canonicalize.d.ts +25 -0
  10. package/dist/conformance/src/integrity/canonicalize.js +100 -0
  11. package/dist/conformance/src/integrity/canonicalize.js.map +1 -0
  12. package/dist/conformance/src/integrity/digest.d.ts +54 -0
  13. package/dist/conformance/src/integrity/digest.js +126 -0
  14. package/dist/conformance/src/integrity/digest.js.map +1 -0
  15. package/dist/conformance/src/integrity/types.d.ts +86 -0
  16. package/dist/conformance/src/integrity/types.js +36 -0
  17. package/dist/conformance/src/integrity/types.js.map +1 -0
  18. package/dist/conformance/src/integrity/verify-chain.d.ts +23 -0
  19. package/dist/conformance/src/integrity/verify-chain.js +235 -0
  20. package/dist/conformance/src/integrity/verify-chain.js.map +1 -0
  21. package/dist/conformance/src/integrity/verify-event.d.ts +29 -0
  22. package/dist/conformance/src/integrity/verify-event.js +140 -0
  23. package/dist/conformance/src/integrity/verify-event.js.map +1 -0
  24. package/dist/conformance/src/privacy/entropy.d.ts +14 -0
  25. package/dist/conformance/src/privacy/entropy.js +86 -0
  26. package/dist/conformance/src/privacy/entropy.js.map +1 -0
  27. package/dist/conformance/src/privacy/field-names.d.ts +36 -0
  28. package/dist/conformance/src/privacy/field-names.js +121 -0
  29. package/dist/conformance/src/privacy/field-names.js.map +1 -0
  30. package/dist/conformance/src/privacy/lint-event.d.ts +18 -0
  31. package/dist/conformance/src/privacy/lint-event.js +272 -0
  32. package/dist/conformance/src/privacy/lint-event.js.map +1 -0
  33. package/dist/conformance/src/privacy/rules.d.ts +44 -0
  34. package/dist/conformance/src/privacy/rules.js +161 -0
  35. package/dist/conformance/src/privacy/rules.js.map +1 -0
  36. package/dist/conformance/src/privacy/safe-formats.d.ts +32 -0
  37. package/dist/conformance/src/privacy/safe-formats.js +118 -0
  38. package/dist/conformance/src/privacy/safe-formats.js.map +1 -0
  39. package/dist/conformance/src/privacy/size-analysis.d.ts +36 -0
  40. package/dist/conformance/src/privacy/size-analysis.js +87 -0
  41. package/dist/conformance/src/privacy/size-analysis.js.map +1 -0
  42. package/dist/conformance/src/privacy/token-patterns.d.ts +24 -0
  43. package/dist/conformance/src/privacy/token-patterns.js +126 -0
  44. package/dist/conformance/src/privacy/token-patterns.js.map +1 -0
  45. package/dist/conformance/src/privacy/traverse.d.ts +33 -0
  46. package/dist/conformance/src/privacy/traverse.js +61 -0
  47. package/dist/conformance/src/privacy/traverse.js.map +1 -0
  48. package/dist/conformance/src/privacy/types.d.ts +58 -0
  49. package/dist/conformance/src/privacy/types.js +32 -0
  50. package/dist/conformance/src/privacy/types.js.map +1 -0
  51. package/dist/conformance/src/privacy/url-analysis.d.ts +40 -0
  52. package/dist/conformance/src/privacy/url-analysis.js +148 -0
  53. package/dist/conformance/src/privacy/url-analysis.js.map +1 -0
  54. package/dist/conformance/src/profiles/check-profile.d.ts +22 -0
  55. package/dist/conformance/src/profiles/check-profile.js +72 -0
  56. package/dist/conformance/src/profiles/check-profile.js.map +1 -0
  57. package/dist/conformance/src/profiles/evaluate-rule.d.ts +27 -0
  58. package/dist/conformance/src/profiles/evaluate-rule.js +87 -0
  59. package/dist/conformance/src/profiles/evaluate-rule.js.map +1 -0
  60. package/dist/conformance/src/profiles/load-profile.d.ts +30 -0
  61. package/dist/conformance/src/profiles/load-profile.js +95 -0
  62. package/dist/conformance/src/profiles/load-profile.js.map +1 -0
  63. package/dist/conformance/src/profiles/resolve-pointer.d.ts +44 -0
  64. package/dist/conformance/src/profiles/resolve-pointer.js +123 -0
  65. package/dist/conformance/src/profiles/resolve-pointer.js.map +1 -0
  66. package/dist/conformance/src/profiles/select-rules.d.ts +13 -0
  67. package/dist/conformance/src/profiles/select-rules.js +42 -0
  68. package/dist/conformance/src/profiles/select-rules.js.map +1 -0
  69. package/dist/conformance/src/profiles/types.d.ts +101 -0
  70. package/dist/conformance/src/profiles/types.js +31 -0
  71. package/dist/conformance/src/profiles/types.js.map +1 -0
  72. package/dist/conformance/src/profiles/validate-profile-definition.d.ts +12 -0
  73. package/dist/conformance/src/profiles/validate-profile-definition.js +47 -0
  74. package/dist/conformance/src/profiles/validate-profile-definition.js.map +1 -0
  75. package/dist/conformance/src/sources.d.ts +53 -0
  76. package/dist/conformance/src/sources.js +153 -0
  77. package/dist/conformance/src/sources.js.map +1 -0
  78. package/dist/conformance/src/validate-core.d.ts +30 -0
  79. package/dist/conformance/src/validate-core.js +62 -0
  80. package/dist/conformance/src/validate-core.js.map +1 -0
  81. package/dist/conformance/src/validate.d.ts +34 -0
  82. package/dist/conformance/src/validate.js +62 -0
  83. package/dist/conformance/src/validate.js.map +1 -0
  84. package/dist/conformance/src/validator-interface.d.ts +35 -0
  85. package/dist/conformance/src/validator-interface.js +19 -0
  86. package/dist/conformance/src/validator-interface.js.map +1 -0
  87. package/package.json +85 -0
  88. package/profiles/README.md +201 -0
  89. package/profiles/api-and-integration-management/README.md +271 -0
  90. package/profiles/api-and-integration-management/profile.json +258 -0
  91. package/profiles/backup-and-recovery/README.md +318 -0
  92. package/profiles/backup-and-recovery/profile.json +178 -0
  93. package/profiles/customer-and-account-management/README.md +340 -0
  94. package/profiles/customer-and-account-management/profile.json +237 -0
  95. package/profiles/deployment-and-change-management/README.md +359 -0
  96. package/profiles/deployment-and-change-management/profile.json +298 -0
  97. package/profiles/document-management/README.md +120 -0
  98. package/profiles/document-management/profile.json +170 -0
  99. package/profiles/financial-transaction-management/README.md +339 -0
  100. package/profiles/financial-transaction-management/profile.json +247 -0
  101. package/profiles/identity-and-access-management/README.md +112 -0
  102. package/profiles/identity-and-access-management/profile.json +120 -0
  103. package/profiles/incident-management/README.md +338 -0
  104. package/profiles/incident-management/profile.json +256 -0
  105. package/profiles/message-broker-management/README.md +344 -0
  106. package/profiles/message-broker-management/profile.json +399 -0
  107. package/profiles/profile-definition.schema.json +213 -0
  108. package/profiles/secrets-and-key-management/README.md +331 -0
  109. package/profiles/secrets-and-key-management/profile.json +219 -0
  110. package/schemas/v0.1/audit-event.schema.json +911 -0
  111. package/semantic-conventions/README.md +52 -0
  112. package/semantic-conventions/authentication.md +111 -0
  113. package/semantic-conventions/configuration-and-change.md +122 -0
  114. package/semantic-conventions/correlation-and-tracing.md +387 -0
  115. package/semantic-conventions/data-access.md +115 -0
  116. package/semantic-conventions/event-naming.md +114 -0
  117. package/semantic-conventions/identity-and-access.md +112 -0
  118. package/semantic-conventions/privileged-operations.md +110 -0
  119. package/semantic-conventions/workflow-and-approval.md +109 -0
  120. package/specification/actor-model.md +135 -0
  121. package/specification/approval-and-delegation.md +150 -0
  122. package/specification/authentication.md +116 -0
  123. package/specification/authorization.md +117 -0
  124. package/specification/change-model.md +134 -0
  125. package/specification/delivery.md +136 -0
  126. package/specification/design-principles.md +137 -0
  127. package/specification/event-model.md +387 -0
  128. package/specification/evidence-model.md +109 -0
  129. package/specification/extension-model.md +159 -0
  130. package/specification/integrity.md +314 -0
  131. package/specification/overview.md +172 -0
  132. package/specification/privacy.md +386 -0
  133. package/specification/resource-model.md +116 -0
  134. package/specification/terminology.md +135 -0
@@ -0,0 +1,256 @@
1
+ {
2
+ "profileVersion": "0.1",
3
+ "name": "incident-management",
4
+ "version": "0.1",
5
+ "status": "experimental",
6
+ "coreVersions": ["0.1"],
7
+ "title": "Incident Management Profile",
8
+ "description": "Additional conformance requirements for the lifecycle of incidents, problems and corrective actions: raising, reprioritising, assigning, escalating, resolving, closing, cancelling and reopening a case, recording a root cause analysis, and opening and verifying a corrective action. Every requirement adds to the OpenAuditModel Core Specification; none relaxes it. High-volume monitoring, timeline and read events such as monitoring.alert.raise, incident.note.create and incident.case.view are deliberately not governed.",
9
+ "rules": [
10
+ {
11
+ "id": "INC-CORE-001",
12
+ "description": "Every governed incident, problem and corrective-action operation records the authorization decision that permitted it and the lifecycle state the record was left in.",
13
+ "rationale": "A case record is rewritten by many hands over days or weeks, and the trail is read long after everyone involved has moved on. Without a recorded decision nothing distinguishes a transition policy allowed from one an operator forced through a console. Without the resulting state, a reviewer replaying the events cannot say what condition the case was actually in at any point, because the profile cannot look inside `/change/after` and a consumer would have to parse a free-form object to find out. Recording the state in one typed, namespaced field is what makes the sequence readable and indexable at all.",
14
+ "severity": "error",
15
+ "events": [
16
+ "corrective-action.close",
17
+ "corrective-action.open",
18
+ "corrective-action.verify",
19
+ "incident.assignment.change",
20
+ "incident.case.cancel",
21
+ "incident.case.close",
22
+ "incident.case.create",
23
+ "incident.case.reopen",
24
+ "incident.case.resolve",
25
+ "incident.major.declare",
26
+ "incident.priority.change",
27
+ "incident.rca.approve",
28
+ "incident.rca.create",
29
+ "incident.rca.update",
30
+ "problem.case.close",
31
+ "problem.case.create"
32
+ ],
33
+ "requiredPaths": ["/authorization"],
34
+ "requiredMetadata": [
35
+ {
36
+ "path": "/incident/status",
37
+ "type": "string"
38
+ }
39
+ ]
40
+ },
41
+ {
42
+ "id": "INC-CORE-002",
43
+ "description": "A governed incident operation should carry the correlation identifier shared by the rest of the handling and name the resources the case is about.",
44
+ "rationale": "Incident handling is the clearest example in this specification of an operation that is not one event. Raising, escalating, resolving, analysing, remediating and closing are separate events emitted hours or weeks apart, often by different applications and principals, and they are assembled by correlation rather than by timestamp. These are recommended and not required because a conforming producer may have no correlation facility at all, and because an incident about a process rather than a system legitimately has no affected resource to name. A missing correlation identifier should prompt a question, not fail a build.",
45
+ "severity": "warning",
46
+ "events": [
47
+ "corrective-action.close",
48
+ "corrective-action.open",
49
+ "corrective-action.verify",
50
+ "incident.assignment.change",
51
+ "incident.case.cancel",
52
+ "incident.case.close",
53
+ "incident.case.create",
54
+ "incident.case.reopen",
55
+ "incident.case.resolve",
56
+ "incident.major.declare",
57
+ "incident.priority.change",
58
+ "incident.rca.approve",
59
+ "incident.rca.create",
60
+ "incident.rca.update",
61
+ "incident.sla.breach",
62
+ "problem.case.close",
63
+ "problem.case.create"
64
+ ],
65
+ "recommendedPaths": ["/request/correlationId", "/relatedResources"]
66
+ },
67
+ {
68
+ "id": "INC-CREATE-001",
69
+ "description": "Raising a case should record how the condition was noticed, when it began, and how badly it hurt.",
70
+ "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
+ "severity": "warning",
72
+ "events": ["incident.case.create", "problem.case.create"],
73
+ "recommendedPaths": [
74
+ "/reason",
75
+ "/metadata/incident/detectedAt",
76
+ "/metadata/incident/impact",
77
+ "/metadata/incident/urgency"
78
+ ]
79
+ },
80
+ {
81
+ "id": "INC-STATE-001",
82
+ "description": "An operation that moves a case between lifecycle states records the transition as a before and after.",
83
+ "rationale": "The state a case ends in is not the same fact as the state it came from. Only the transition shows that a case went from open to closed rather than from cancelled to closed, and only the transition makes an impossible sequence visible — a closure of something never resolved, a resolution of something already closed. An event that records the destination alone is a snapshot, and a trail of snapshots cannot be replayed.",
84
+ "severity": "error",
85
+ "events": [
86
+ "incident.priority.change",
87
+ "incident.assignment.change",
88
+ "incident.major.declare",
89
+ "incident.case.resolve",
90
+ "incident.case.close",
91
+ "incident.case.cancel",
92
+ "problem.case.close",
93
+ "corrective-action.close"
94
+ ],
95
+ "requiredPaths": ["/change"]
96
+ },
97
+ {
98
+ "id": "INC-STATE-002",
99
+ "description": "A transition that reflects a judgement — reprioritisation, escalation to major, closure or cancellation — records why it was made.",
100
+ "rationale": "These are the transitions where the record and the decision can diverge. Downgrading a priority, declaring a major incident, closing a case or abandoning one are all defensible and all abusable, and the difference is the justification. A case closed with no stated reason is indistinguishable from a case closed to make a queue look shorter, and that ambiguity is precisely what an incident trail exists to remove. Resolution and reassignment are excluded because they carry their own structured explanation.",
101
+ "severity": "error",
102
+ "events": [
103
+ "incident.priority.change",
104
+ "incident.major.declare",
105
+ "incident.case.close",
106
+ "incident.case.cancel",
107
+ "problem.case.close",
108
+ "corrective-action.close"
109
+ ],
110
+ "requiredPaths": ["/reason"]
111
+ },
112
+ {
113
+ "id": "INC-PRIORITY-001",
114
+ "description": "Raising a case, reassessing its priority, escalating it to major and reporting a missed commitment all record the priority in force.",
115
+ "rationale": "Priority is the field every downstream obligation hangs from: who is paged, how fast a response is owed, whether an escalation was warranted and whether a service commitment was met. Recorded only at creation, it cannot answer any question about the middle of the incident. The profile requires the field and deliberately does not close its vocabulary, because priority scales are an organizational choice and a profile that imposed one would be describing a single service desk rather than a domain.",
116
+ "severity": "error",
117
+ "events": [
118
+ "incident.case.create",
119
+ "problem.case.create",
120
+ "incident.priority.change",
121
+ "incident.major.declare",
122
+ "incident.sla.breach"
123
+ ],
124
+ "requiredMetadata": [
125
+ {
126
+ "path": "/incident/priority",
127
+ "type": "string"
128
+ }
129
+ ]
130
+ },
131
+ {
132
+ "id": "INC-ASSIGN-001",
133
+ "description": "Handing a case or a corrective action to someone names the principal who became accountable for it.",
134
+ "rationale": "Accountability that is not recorded is not accountability. A reassignment or a corrective action opened without naming who now owns it produces a trail that shows work being moved and never shows who was asked to do it, which is the one question a follow-up review always asks.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.",
135
+ "severity": "error",
136
+ "events": ["incident.assignment.change", "corrective-action.open"],
137
+ "requiredMetadata": [
138
+ {
139
+ "path": "/incident/assigneeId",
140
+ "type": "string"
141
+ }
142
+ ],
143
+ "recommendedPaths": ["/reason"]
144
+ },
145
+ {
146
+ "id": "INC-RESOLVE-001",
147
+ "description": "Resolving a case records what kind of resolution it was.",
148
+ "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
+ "severity": "error",
150
+ "events": ["incident.case.resolve"],
151
+ "requiredMetadata": [
152
+ {
153
+ "path": "/incident/resolutionType",
154
+ "type": "string"
155
+ }
156
+ ],
157
+ "recommendedPaths": [
158
+ "/reason",
159
+ "/metadata/incident/resolvedAt",
160
+ "/metadata/incident/correctiveAction/id"
161
+ ]
162
+ },
163
+ {
164
+ "id": "INC-CLOSE-001",
165
+ "description": "When the producer has declared that closing this case required approval, the closure records the approval decision.",
166
+ "rationale": "Whether a closure needs a second pair of eyes is an organizational policy, not a fact this specification can decide: a service desk closing thousands of routine tickets a week and a safety board closing a reportable incident are both conforming, and requiring approval for every closure would describe one of them and be switched off by the other. The producer declares the obligation and the profile enforces the consequence. The rule requires the decision to be recorded, not to be favourable: an event whose approval is still pending or was rejected and whose outcome is success describes a control bypass, and the trail must be able to say so.",
167
+ "severity": "error",
168
+ "events": [
169
+ "incident.case.close",
170
+ "incident.case.cancel",
171
+ "problem.case.close",
172
+ "corrective-action.close"
173
+ ],
174
+ "when": {
175
+ "path": "/metadata/incident/approvalRequired",
176
+ "equals": true
177
+ },
178
+ "requiredPaths": ["/approval/status"],
179
+ "recommendedPaths": ["/approval/approvers", "/approval/approvedAt"]
180
+ },
181
+ {
182
+ "id": "INC-REOPEN-001",
183
+ "description": "Reopening a closed case is a new lifecycle transition, recorded with the transition it made and the reason it was made.",
184
+ "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
+ "severity": "error",
186
+ "events": ["incident.case.reopen"],
187
+ "requiredPaths": ["/change", "/reason"],
188
+ "recommendedPaths": ["/evidence"]
189
+ },
190
+ {
191
+ "id": "INC-RCA-001",
192
+ "description": "A root cause analysis event records the method the analysis was performed with.",
193
+ "rationale": "An analysis is trustworthy in proportion to how it was conducted, and the method is the only part of that a machine-readable trail can carry. Two organizations using different techniques still both know which one they used, so the field is universal even though its vocabulary is not, and it is left open for that reason. The profile does not attempt to require the analysis document itself: the rule language cannot inspect the contents of `/evidence`, so a supporting record is recommended by INC-EVIDENCE-001 rather than dishonestly claimed to be enforced.",
194
+ "severity": "error",
195
+ "events": ["incident.rca.approve", "incident.rca.create", "incident.rca.update"],
196
+ "requiredMetadata": [
197
+ {
198
+ "path": "/incident/rca/method",
199
+ "type": "string"
200
+ }
201
+ ]
202
+ },
203
+ {
204
+ "id": "INC-RCA-002",
205
+ "description": "Approving a root cause analysis records the approval decision.",
206
+ "rationale": "This event is an approval, so requiring the approval state is not an added process obligation but a requirement that the event describe itself. An approval event with no recorded decision states that an analysis was reviewed without stating what the review concluded. The status is required rather than required to be `approved`, so that a rejection or a lapsed request stays recordable and visible.",
207
+ "severity": "error",
208
+ "events": ["incident.rca.approve"],
209
+ "requiredPaths": ["/approval/status"],
210
+ "recommendedPaths": ["/approval/approvers", "/approval/approvedAt"]
211
+ },
212
+ {
213
+ "id": "INC-CAPA-001",
214
+ "description": "Verifying a corrective action records how the verification was carried out.",
215
+ "rationale": "Verification is the step that turns a promise into evidence, and an unqualified assertion that an action was verified is worth nothing: reading a ticket, watching production for a week and re-running the failure are all called verification and are not comparable. Recording the method is what lets a later reader judge whether the fix was actually demonstrated. The vocabulary is open because verification techniques are domain-specific.",
216
+ "severity": "error",
217
+ "events": ["corrective-action.verify"],
218
+ "requiredMetadata": [
219
+ {
220
+ "path": "/incident/correctiveAction/verificationMethod",
221
+ "type": "string"
222
+ }
223
+ ],
224
+ "recommendedPaths": ["/metadata/incident/correctiveAction/verifiedAt"]
225
+ },
226
+ {
227
+ "id": "INC-SLA-001",
228
+ "description": "A missed service commitment names the commitment that was missed.",
229
+ "rationale": "A breach event that does not say what was breached records only that someone was unhappy. The commitment identifier is what lets the breach be tied to the obligation it violated and counted against it later, when the commitment itself may have been renegotiated. The profile requires the identifier and asserts nothing about durations or thresholds: the v0.1 rule language compares values for equality and cannot evaluate a numeric range, so a target is recorded as a name rather than checked as a bound. This event is excluded from INC-CORE-001 because a breach is a system observation rather than an authorized human action, and demanding an authorization decision for it would force producers to invent one.",
230
+ "severity": "error",
231
+ "events": ["incident.sla.breach"],
232
+ "requiredMetadata": [
233
+ {
234
+ "path": "/incident/sla/target",
235
+ "type": "string"
236
+ }
237
+ ],
238
+ "recommendedPaths": ["/metadata/incident/sla/breachedAt"]
239
+ },
240
+ {
241
+ "id": "INC-EVIDENCE-001",
242
+ "description": "Root-cause and corrective-action events should reference the material that supports them.",
243
+ "rationale": "The analysis document, the change record and the verification report are what make an incident conclusion checkable rather than merely stated, and `/evidence` is where the core model puts references to them. This is a recommendation and not a requirement for a reason worth stating plainly: the v0.1 rule language can test that `/evidence` is present and non-empty, and cannot test that it contains an entry of any particular type. A rule that required `/evidence` would be satisfied by an unrelated reference, so the profile asks for evidence rather than pretending to verify it.",
244
+ "severity": "warning",
245
+ "events": [
246
+ "corrective-action.close",
247
+ "corrective-action.open",
248
+ "corrective-action.verify",
249
+ "incident.rca.approve",
250
+ "incident.rca.create",
251
+ "incident.rca.update"
252
+ ],
253
+ "recommendedPaths": ["/evidence"]
254
+ }
255
+ ]
256
+ }
@@ -0,0 +1,344 @@
1
+ # Message Broker Management Profile
2
+
3
+ **Status: Experimental. Implemented in v0.1, 12 rules (11 enforceable).**
4
+
5
+ Scope: the **control plane** of message brokers, queues, streams and event logs — the administrative
6
+ operations performed by operators, platform teams and reconciliation tooling on clusters, topics,
7
+ queues, exchanges, streams, consumer groups, access control lists, quotas and broker configuration,
8
+ plus the two operational interventions that move data without changing it: offset resets and message
9
+ replay.
10
+
11
+ The profile is vendor-neutral. It describes operations that any broker performs, and assumes no
12
+ particular protocol, storage engine, management API or deployment model. It applies equally to a
13
+ log-structured event platform, a work-queue broker, a stream store and a hosted messaging service.
14
+
15
+ The enforceable rules are in [profile.json](profile.json).
16
+
17
+ ```bash
18
+ auditmodel check-profile examples/profiles/message-broker-management/valid --profile message-broker-management
19
+ ```
20
+
21
+ ## Purpose
22
+
23
+ A broker is where an organization's data is in motion, and its control plane is the smallest set of
24
+ operations that can lose all of it. Deleting a topic, purging a queue, shortening a retention window
25
+ or rewinding a consumer group destroys or duplicates messages that no other system holds a copy of,
26
+ and the audit event is usually the only surviving record that the operation happened at all. Broker
27
+ access control is also where the confidentiality of every topic on a cluster is actually decided,
28
+ which is why this profile governs ACL administration rather than deferring it to the identity
29
+ domain.
30
+
31
+ ## What this profile governs
32
+
33
+ | Event family | Governed |
34
+ | --------------------------------------------------------------------- | -------------------- |
35
+ | `broker.cluster.create`, `.scale`, `.upgrade`, `.failover`, `.delete` | yes, by exact name |
36
+ | `broker.topic.*` | yes |
37
+ | `broker.queue.*` | yes |
38
+ | `broker.exchange.*` | yes |
39
+ | `broker.stream.*` | yes |
40
+ | `broker.acl.*`, `broker.permission.*` | yes |
41
+ | `broker.quota.*` | yes |
42
+ | `broker.configuration.*` | yes |
43
+ | `broker.consumer-group.create`, `.update`, `.delete` | yes, by exact name |
44
+ | `broker.offset.reset` | yes, by exact name |
45
+ | `broker.message.replay` | yes, by exact name |
46
+ | `broker.message.publish`, `.consume`, `.acknowledge`, `.deliver` | **no** — data plane |
47
+ | `broker.consumer-group.rebalance`, `.lag-report` | **no** — automatic |
48
+ | `broker.cluster.health-check` and other liveness or telemetry events | **no** — operational |
49
+
50
+ ### Explicit exclusions, and why
51
+
52
+ **The data plane.** A broker emits a publish or consume event for every message it carries. Requiring
53
+ an authorization decision, a destructiveness declaration and a cluster identifier on each of them
54
+ would add cost to the highest-volume event in the entire system in exchange for almost no review
55
+ value, and the requirement would be switched off rather than met.
56
+
57
+ The exclusion is **structural**, not a matter of discipline: every governed event is selected by
58
+ **exact name**, so an operation is governed because it appears in the profile, never because it
59
+ happens to share a family with one that does.
60
+
61
+ That matters more than it first appears. An earlier draft selected the administrative families by
62
+ prefix — `broker.topic.`, `broker.queue.`, `broker.stream.` — which reads as safe until you notice
63
+ that [event-naming.md](../../semantic-conventions/event-naming.md) tells producers to put the
64
+ **resource** in the middle segment. A publish to a topic is then plausibly `broker.topic.publish`,
65
+ and the profile's heaviest requirements would have landed on the highest-volume event in the estate.
66
+ Naming a data-plane operation `broker.message.publish` avoided it, but that was the producer's
67
+ discipline doing the work, not the profile's. Exact names remove the assumption.
68
+
69
+ Consumer-group and cluster administration are selected by exact name for the same reason, with an
70
+ additional one: `broker.consumer-group.rebalance` is emitted by the broker itself, has no human
71
+ actor and no authorization decision, and can fire many times a minute during a deployment.
72
+
73
+ The same reasoning applies to `broker.cluster.*`: cluster administration is selected by five exact
74
+ names, so a producer that emits `broker.cluster.health-check` from a liveness probe is not dragged
75
+ into scope. Tests assert each of these, because widening one prefix later would silently start
76
+ governing every message a broker delivers.
77
+
78
+ Excluded does not mean unaudited. A publish event is still a conforming OpenAuditModel event, and
79
+ [data-access.md](../../semantic-conventions/data-access.md) covers recording data-plane activity.
80
+
81
+ **Message payloads.** They are never recorded, by any rule in this profile, in any circumstance. See
82
+ [Privacy considerations](#privacy-considerations).
83
+
84
+ ## Rules
85
+
86
+ | Rule | Applies to | Requires |
87
+ | ---------------------- | ------------------------------------------------------------------------------ | --------------------------------------------------------------------- |
88
+ | `BROKER-CORE-001` | every governed event | `/authorization`, `/metadata/broker/system`, `.../clusterId` |
89
+ | `BROKER-CORE-002` | every governed event | _recommends_ reason, authentication, parent, correlation ID |
90
+ | `BROKER-RISK-001` | every governed event | `/metadata/broker/operation/destructive` as a boolean |
91
+ | `BROKER-RISK-002` | declared-destructive operations | `/reason`; recommends `/approval` |
92
+ | `BROKER-RISK-003` | operations declared to need approval | `/approval` |
93
+ | `BROKER-FAIL-001` | governed events with outcome failure | `/event/error/type`; recommends `/event/error/retryable` |
94
+ | `BROKER-LIFECYCLE-001` | topic, queue, stream, exchange create | `/resource/classification`; recommends `/resource/ownerId` |
95
+ | `BROKER-CHANGE-001` | configuration, ACL, quota, resource and consumer-group `.update`, offset reset | `/change`; recommends `/change/changedFields` |
96
+ | `BROKER-ACL-001` | `broker.acl.*`, `broker.permission.*` | `/metadata/broker/acl/principalId`, `/metadata/broker/acl/permission` |
97
+ | `BROKER-QUOTA-001` | `broker.quota.*` | `/metadata/broker/quota/dimension` |
98
+ | `BROKER-OFFSET-001` | `broker.offset.reset` | `/reason`, `/metadata/broker/offset/previous` and `.../target` |
99
+ | `BROKER-REPLAY-001` | `broker.message.replay` | `/reason`, `/metadata/broker/replay/scope` |
100
+
101
+ `BROKER-CORE-002` is the only rule with severity `warning`; the other eleven fail conformance. Each
102
+ rule's full text and rationale is in [profile.json](profile.json).
103
+
104
+ `BROKER-CHANGE-001` deliberately does not select `broker.cluster.scale` or `broker.cluster.upgrade`.
105
+ A cluster operation is frequently driven by a release pipeline that already records the transition in
106
+ its own deployment event, and requiring `/change` here would push producers into duplicating it under
107
+ two names — the coupling described under [cross-profile overlaps](#cross-profile-overlaps). The
108
+ `cluster-upgrade` fixture records `/change` anyway, because a producer that holds the version
109
+ transition should write it down.
110
+
111
+ ## Metadata namespace
112
+
113
+ Every metadata requirement in this profile lives under `/metadata/broker/`.
114
+
115
+ | Path | Type | Required by | Meaning |
116
+ | ---------------------------------------- | ------- | ------------------- | ------------------------------------------------------- |
117
+ | `/metadata/broker/system` | string | `BROKER-CORE-001` | Broker family or protocol the operation targeted |
118
+ | `/metadata/broker/clusterId` | string | `BROKER-CORE-001` | Broker deployment the operation acted on |
119
+ | `/metadata/broker/operation/destructive` | boolean | `BROKER-RISK-001` | Whether the operation discards data or withdraws access |
120
+ | `/metadata/broker/approvalRequired` | boolean | — | Producer's declaration that approval policy applied |
121
+ | `/metadata/broker/acl/permission` | string | `BROKER-ACL-001` | Permission granted, changed or withdrawn |
122
+ | `/metadata/broker/acl/effect` | string | _recommended_ | Whether the rule allows or denies |
123
+ | `/metadata/broker/quota/dimension` | string | `BROKER-QUOTA-001` | Dimension of consumption the quota limits |
124
+ | `/metadata/broker/offset/previous` | string | `BROKER-OFFSET-001` | Consumer position before the reset |
125
+ | `/metadata/broker/offset/target` | string | `BROKER-OFFSET-001` | Consumer position after the reset |
126
+ | `/metadata/broker/offset/strategy` | string | _recommended_ | How the target position was chosen |
127
+ | `/metadata/broker/replay/scope` | string | `BROKER-REPLAY-001` | How much was replayed |
128
+ | `/metadata/broker/replay/messageCount` | number | _recommended_ | How many messages the replay covered |
129
+
130
+ All values are open vocabularies. The profile does not enumerate broker families, quota dimensions,
131
+ reset strategies or replay scopes, because each is a moving target across products and none can be
132
+ closed without excluding a broker somebody runs. Nested, namespaced keys are used throughout so that
133
+ two profiles governing the same event cannot assign different meanings to one key — an `expiresAt`
134
+ on a share and a `scope` on a replay are not the same fact as anything else called `expiresAt` or
135
+ `scope`.
136
+
137
+ ### Deliberate non-requirements in metadata
138
+
139
+ - **The managed resource type is not duplicated into metadata.** `resource.type` is already REQUIRED
140
+ by the core model and already carries `topic`, `queue`, `stream`, `exchange`, `consumer-group` or
141
+ `cluster`. A `/metadata/broker/resourceType` alongside it would be a second copy of a core field,
142
+ which [event-model.md](../../specification/event-model.md) prohibits, and two copies eventually
143
+ disagree.
144
+ - **The resource name is not required.** `resource.id` is REQUIRED by the core model, and topic and
145
+ queue names routinely encode customer, tenant or product information. `resource.name` remains
146
+ available where the producer judges it safe.
147
+ - **Partition, node and broker identifiers are not required.** They are meaningful on some broker
148
+ families and meaningless on others. `/metadata/broker/offset/partition` appears in the fixtures as
149
+ an illustration, not as a requirement.
150
+
151
+ ## Conditional-policy fields
152
+
153
+ The v0.1 rule language offers exactly one conditional mechanism: one path compared for equality
154
+ against one scalar. This profile uses it three times, and each condition is a fact the **producer**
155
+ declares, because none of the three can be inferred from an event name.
156
+
157
+ | Condition | Rule | Consequence |
158
+ | -------------------------------------------------- | ----------------- | ----------------------------------------------- |
159
+ | `/metadata/broker/operation/destructive` is `true` | `BROKER-RISK-002` | `/reason` becomes required, `/approval` advised |
160
+ | `/metadata/broker/approvalRequired` is `true` | `BROKER-RISK-003` | `/approval` becomes required |
161
+ | `/event/outcome` is `"failure"` | `BROKER-FAIL-001` | `/event/error/type` becomes required |
162
+
163
+ `destructive` and `approvalRequired` are deliberately separate facts. Destructiveness is a property
164
+ of the **operation** — does it discard messages, positions or access? Approval need is a property of
165
+ the **organization's policy** for that operation, and differs by cluster, environment and team. Only
166
+ destructiveness is required to be recorded, because only it is knowable by the producing system
167
+ without reference to a policy the audit model does not hold.
168
+
169
+ When a condition's path is absent the condition does **not** hold and the rule contributes nothing.
170
+ That is why `BROKER-RISK-001` requires the destructiveness flag unconditionally: without it,
171
+ `BROKER-RISK-002` would quietly stop applying to every producer that omitted the field.
172
+
173
+ ## Approval model
174
+
175
+ **Approval is never required unconditionally.** A very large share of legitimate broker
176
+ administration is unilateral and should be — clearing a development queue, creating a topic for a
177
+ team's own service, raising a quota the same operator lowered an hour earlier. A rule that demanded a
178
+ second signature for all of it would describe one organization's change process and be ignored
179
+ everywhere else.
180
+
181
+ Instead:
182
+
183
+ - `BROKER-RISK-002` **recommends** `/approval` for any operation the producer declared destructive,
184
+ so an unapproved destructive operation produces a warning and a reviewable signal.
185
+ - `BROKER-RISK-003` **requires** `/approval` only when the producer declared that approval policy
186
+ applied to this operation.
187
+
188
+ `BROKER-RISK-003` requires the approval object to be **present**, not to be granted. A rejected,
189
+ expired or pending approval satisfies it. An audit trail that could only record approved changes
190
+ would be unable to record the most interesting event in the domain: the change a control stopped.
191
+
192
+ ## Privacy considerations
193
+
194
+ **Message payloads MUST NOT be captured.** This is a core rule
195
+ ([privacy.md](../../specification/privacy.md) §2), restated here because this is the domain where it
196
+ is most often violated: a broker console has the payload in hand and recording it is one line of
197
+ code. No rule in this profile requires message content, and no fixture contains any.
198
+
199
+ - An offset reset records two **positions**, never the messages between them.
200
+ - A replay records a **scope and a count**, never what was replayed.
201
+ - A purge records **how many** messages were discarded, never which.
202
+ - An ACL change records a **permission and a principal identifier**, never a credential, connection
203
+ string or bootstrap address.
204
+
205
+ Where an operator has a genuine need to preserve message content — a dispute over what was published
206
+ — that content is **evidence**, stored under its own access controls and referenced from the event.
207
+ See [evidence-model.md](../../specification/evidence-model.md).
208
+
209
+ Broker identifiers are also a disclosure surface. `clusterId`, topic names and consumer-group names
210
+ should be stable, opaque, lower-case identifiers. Host names, ports, bootstrap servers and
211
+ connection strings expose internal infrastructure and are rejected by `auditmodel lint-privacy`;
212
+ none appears in any fixture.
213
+
214
+ ## Known rule-language limitations
215
+
216
+ The v0.1 rule language checks presence, JSON type and scalar equality. These requirements are real
217
+ and are documented here rather than pretended into rules:
218
+
219
+ - **Positions cannot be compared.** `BROKER-OFFSET-001` requires both the previous and the target
220
+ position, but nothing can assert that they differ, that the target is earlier, or how many messages
221
+ lie between them. A reviewer reads both values; the engine only confirms they were recorded.
222
+ - **Numeric ranges cannot be checked.** A quota change records its dimension; the engine cannot say
223
+ whether the new limit is an increase, a decrease or a hundredfold jump, and a limit is only
224
+ meaningful against a deployment's own baseline anyway.
225
+ - **Positions are typed as strings.** Broker position types are irreconcilable — a 64-bit log offset,
226
+ a stream entry identifier, a composite message identifier — and the rule language has no union
227
+ type. A string carries all of them; a number would exclude most brokers.
228
+ - **Cross-field consistency cannot be expressed.** The profile cannot require that an event whose
229
+ authorization decision is `deny` also has outcome `failure`, because that needs two conditions.
230
+ - **Array contents are never inspected.** The profile cannot assert that `relatedResources` contains
231
+ the affected cluster, or that `/approval/approvers` holds someone other than the actor.
232
+ - **Only one condition per rule.** "Destructive **and** in production" is not expressible; a producer
233
+ that wants environment-sensitive strictness must express it in its own policy engine.
234
+ - **A prefix cannot exclude a verb.** Seven families are selected by prefix, so a read-shaped name
235
+ inside a governed family — `broker.acl.list`, `broker.topic.describe` — is selected too, and
236
+ `BROKER-ACL-001` or `BROKER-CHANGE-001` then asks it for a `/subject` or a `/change` that a listing
237
+ does not have. There is no negative selector in v0.1, and enumerating every mutating verb every
238
+ broker exposes would exclude the next one. This profile therefore assumes the prefixed families
239
+ carry operations that change something; a producer that audits administrative reads as well should
240
+ give them their own domain segment rather than a governed prefix. The same shape appears in
241
+ `document-management`, where `document.permission.` requires `/subject`, so it is a property of the
242
+ rule language rather than of this profile. The exclusions that matter — the data plane, automatic
243
+ rebalancing and liveness probes — are held by exact-name selection instead, which a prefix cannot
244
+ undo.
245
+
246
+ ## Cross-profile overlaps
247
+
248
+ - **Identity and access management.** Broker ACL administration is governed here, not by
249
+ `identity.*`. A broker ACL is scoped to a broker resource, expressed in a broker's permission
250
+ vocabulary and administered by the platform team that runs the cluster; routing it into the
251
+ identity domain would require that profile to understand topic patterns and permission types it
252
+ has no reason to model. The event may still carry `/subject`, `/authorization` and identity-shaped
253
+ resources, and an organization that reviews all access changes together can select on
254
+ `event.type` (`grant`, `revoke`) across both profiles.
255
+ - **Deployment and change management.** A broker upgrade is both a cluster operation and a release.
256
+ `broker.cluster.upgrade` is governed here; a pipeline that also emits a deployment event should
257
+ correlate the two through `/request/correlationId` or `/change/deploymentId` rather than
258
+ duplicating one event under two names.
259
+ - **Incident management.** Offset resets, purges and replays are usually performed during an
260
+ incident. This profile requires the justification; the incident record itself belongs to the
261
+ incident domain, linked through `/change/incidentId` and `/reason/reference`.
262
+ - **Backup and recovery.** Replaying from a dead-letter queue is a broker operation; restoring a
263
+ cluster from a snapshot is a recovery operation. The dividing line is whether the data came back
264
+ from the broker or from a backup.
265
+
266
+ ## Fixture matrix
267
+
268
+ [examples/profiles/message-broker-management/](../../examples/profiles/message-broker-management/) —
269
+ twelve valid, twelve invalid, three not-applicable.
270
+
271
+ | Family | Valid fixture | Negative fixture |
272
+ | --------------------- | ------------------------------ | --------------------------------------------------------------------------------- |
273
+ | Topic lifecycle | `topic-create`, `topic-delete` | missing authorization, cluster, classification, approval |
274
+ | Topic configuration | `topic-retention-reduce` | — |
275
+ | Failed administration | `topic-delete-denied` | missing error type |
276
+ | Queue administration | `queue-purge` | missing destructive flag, missing reason |
277
+ | Access control | `acl-grant` | missing subject |
278
+ | Quota | `quota-update` | missing dimension |
279
+ | Broker configuration | `configuration-update` | missing change |
280
+ | Consumer group | `consumer-group-delete` | — |
281
+ | Cluster | `cluster-upgrade` | — |
282
+ | Offset | `offset-reset` | missing target position |
283
+ | Replay | `message-replay` | missing scope |
284
+ | Data plane | — | `message-publish`, `message-consume`, `consumer-group-rebalance` (not applicable) |
285
+
286
+ Every fixture — valid, invalid and not-applicable alike — is core-conforming and privacy-clean. Every
287
+ invalid fixture is core-**valid** and fails exactly one profile rule, at one pointer.
288
+
289
+ ## Not-applicable rationale
290
+
291
+ Three fixtures exist to hold the exclusions in place rather than to demonstrate a requirement:
292
+
293
+ - `message-publish.json` and `message-consume.json` are ordinary data-plane events. If a future edit
294
+ widened any selector to a bare `broker.` or `broker.message.` prefix, they would start conforming
295
+ instead of being skipped, and the test would fail — which is the point, because that edit would
296
+ impose an authorization requirement on every message a broker carries.
297
+ - `consumer-group-rebalance.json` is emitted by the broker itself, with a `system` actor and no
298
+ authorization decision. It holds the decision to select consumer-group administration by exact
299
+ name.
300
+
301
+ `check-profile` reports all three as not applicable and exits `3`. **Not applicable is not
302
+ conformance**: it means the profile said nothing about the event, not that the event satisfied it.
303
+
304
+ ## Relationship to the existing `kafka.*` examples
305
+
306
+ The repository already ships
307
+ [examples/valid/kafka-consumer-offset-reset.json](../../examples/valid/kafka-consumer-offset-reset.json),
308
+ which follows the family-scoped naming that
309
+ [privileged-operations.md](../../semantic-conventions/privileged-operations.md) §6 suggests
310
+ (`kafka.consumer.offset-reset`, `rabbitmq.queue.purge`, `redis.key.delete`). This profile governs the
311
+ vendor-neutral `broker.*` vocabulary only, and does **not** select family-scoped names. Two reasons:
312
+
313
+ 1. A family-scoped prefix such as `kafka.` cannot be used as a selector without sweeping in
314
+ `kafka.message.publish`, which reintroduces exactly the data-plane problem the profile is
315
+ structured to avoid — and enumerating one family's admin names invites doing it for every broker
316
+ anyone runs.
317
+ 2. The broker family is carried as data, in `/metadata/broker/system`, where it can be filtered,
318
+ aggregated and extended without a new event name and without a new rule.
319
+
320
+ The consequence is recorded honestly rather than hidden: `kafka.consumer.offset-reset` is
321
+ **not applicable** to this profile.
322
+
323
+ It is a real inconsistency, not a curiosity. §6 recommends family-scoped names **and** links to this
324
+ profile in the same list, so a producer that follows the convention literally arrives at a profile
325
+ that governs none of its events. The two documents have to be reconciled, and there are only two
326
+ ways to do it: §6 starts recommending the vendor-neutral `broker.*` names, or this profile gains
327
+ family-scoped exact-name selectors for the operations §6 enumerates — exact names, never a `kafka.`
328
+ prefix. Either edit reaches outside this directory, which is why it is written down here rather than
329
+ left to be discovered by the first producer to run `check-profile` and be told its offset reset is
330
+ not applicable.
331
+
332
+ ## Open questions
333
+
334
+ - Should a multi-partition or multi-topic offset reset be one event or one event per partition? The
335
+ profile accepts either; `resource` holds the consumer group and `relatedResources` the topics.
336
+ - Cache and key-space operations (`redis.key.delete` and its equivalents) can affect an unbounded
337
+ number of keys. A scope declaration would be the right requirement, but there is not yet enough
338
+ adoption evidence to say whether it should be a pattern, a count or a named key space.
339
+ - Should `broker.subscription.*` be governed? A durable subscription is administrative on some
340
+ platforms and a per-client, per-session operation on others, where governing it would recreate the
341
+ data-plane problem. It is currently out of scope, and subscriptions that are genuinely
342
+ administrative should be recorded as queues or consumer groups.
343
+ - Is `destructive` the right discriminator, or should the profile distinguish "discards data" from
344
+ "withdraws access"? A single flag is currently enough to make the reviewable set small.