@openauditmodel/cli 0.2.1 → 0.4.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 (41) hide show
  1. package/README.md +75 -9
  2. package/dist/conformance/src/cli.js +149 -10
  3. package/dist/conformance/src/cli.js.map +1 -1
  4. package/dist/conformance/src/integrity/canonicalize.d.ts +6 -2
  5. package/dist/conformance/src/integrity/canonicalize.js +7 -2
  6. package/dist/conformance/src/integrity/canonicalize.js.map +1 -1
  7. package/dist/conformance/src/integrity/verify-event.d.ts +4 -3
  8. package/dist/conformance/src/integrity/verify-event.js +25 -6
  9. package/dist/conformance/src/integrity/verify-event.js.map +1 -1
  10. package/dist/conformance/src/privacy/size-analysis.js +3 -1
  11. package/dist/conformance/src/privacy/size-analysis.js.map +1 -1
  12. package/dist/conformance/src/privacy/token-patterns.js +17 -2
  13. package/dist/conformance/src/privacy/token-patterns.js.map +1 -1
  14. package/dist/conformance/src/privacy/types.d.ts +19 -0
  15. package/dist/conformance/src/privacy/types.js +8 -0
  16. package/dist/conformance/src/privacy/types.js.map +1 -1
  17. package/dist/conformance/src/profiles/coverage.d.ts +62 -0
  18. package/dist/conformance/src/profiles/coverage.js +114 -0
  19. package/dist/conformance/src/profiles/coverage.js.map +1 -0
  20. package/package.json +15 -2
  21. package/profiles/README.md +42 -0
  22. package/profiles/api-and-integration-management/0.1/profile.json +258 -0
  23. package/profiles/backup-and-recovery/0.1/profile.json +178 -0
  24. package/profiles/customer-and-account-management/0.1/profile.json +237 -0
  25. package/profiles/deployment-and-change-management/0.1/profile.json +298 -0
  26. package/profiles/document-management/0.1/profile.json +170 -0
  27. package/profiles/financial-transaction-management/0.1/profile.json +247 -0
  28. package/profiles/identity-and-access-management/0.1/profile.json +120 -0
  29. package/profiles/incident-management/0.1/profile.json +256 -0
  30. package/profiles/incident-management/0.2/profile.json +287 -0
  31. package/profiles/incident-management/README.md +39 -21
  32. package/profiles/incident-management/profile.json +37 -6
  33. package/profiles/message-broker-management/0.1/profile.json +399 -0
  34. package/profiles/secrets-and-key-management/0.1/profile.json +219 -0
  35. package/semantic-conventions/README.md +15 -10
  36. package/semantic-conventions/backup-and-recovery.md +124 -0
  37. package/semantic-conventions/customer-and-account.md +133 -0
  38. package/semantic-conventions/financial-transactions.md +128 -0
  39. package/semantic-conventions/message-brokers.md +128 -0
  40. package/semantic-conventions/secrets-and-keys.md +132 -0
  41. package/specification/integrity.md +8 -3
@@ -0,0 +1,237 @@
1
+ {
2
+ "profileVersion": "0.1",
3
+ "name": "customer-and-account-management",
4
+ "version": "0.1",
5
+ "status": "experimental",
6
+ "coreVersions": ["0.1"],
7
+ "title": "Customer and Account Management Profile",
8
+ "description": "Additional conformance requirements for material customer and business-account lifecycle audit events: customer record creation, update, merge, restriction, closure and deletion, and account opening, update, status transition, limit change, freeze, restriction and closure. Every requirement adds to the OpenAuditModel Core Specification; none relaxes it. This profile governs business parties and business accounts; identities, authentication and access rights are governed by the identity-and-access-management profile. High-volume read events such as customer.profile.view, customer.search and account.balance.view are deliberately not governed.",
9
+ "rules": [
10
+ {
11
+ "id": "CUSTOMER-CORE-001",
12
+ "description": "Every governed customer or account operation records the kind of party the record belongs to.",
13
+ "rationale": "Whether a record describes an individual consumer, a business, an intermediary, an internal test party or a partner is the single fact that decides how an operation on it should be reviewed, and it is not derivable from an opaque identifier. Carrying it on the event rather than expecting a join is deliberate: the customer record the join would need may since have been merged, restricted or deleted, and the operations this profile governs are exactly the ones that do that. The party type is a category, not personal data.",
14
+ "severity": "error",
15
+ "events": [
16
+ "customer.create",
17
+ "customer.update",
18
+ "customer.merge",
19
+ "customer.close",
20
+ "customer.delete",
21
+ "customer.restrict",
22
+ "customer.restore",
23
+ "account.open",
24
+ "account.update",
25
+ "account.close",
26
+ "account.reopen",
27
+ "account.freeze",
28
+ "account.unfreeze",
29
+ "account.restrict"
30
+ ],
31
+ "eventPrefixes": ["account.limit.", "account.status."],
32
+ "requiredMetadata": [{ "path": "/customer/customerType", "type": "string" }]
33
+ },
34
+ {
35
+ "id": "CUSTOMER-CORE-002",
36
+ "description": "A governed customer or account operation should record why it happened, how sensitive the record is, and how it correlates with the wider workflow it belongs to.",
37
+ "rationale": "Onboarding, periodic review and offboarding are multi-step workflows whose steps are emitted by different services, so a correlation identifier is what turns a scattering of events back into one case. These are recommended rather than required because a conforming producer may have no correlation mechanism at all, because a classification scheme is an organization's decision rather than this profile's, and because a missing justification on a routine update should prompt a question rather than fail a build. Where a justification is genuinely load-bearing, a separate rule requires it.",
38
+ "severity": "warning",
39
+ "events": [
40
+ "customer.create",
41
+ "customer.update",
42
+ "customer.merge",
43
+ "customer.close",
44
+ "customer.delete",
45
+ "customer.restrict",
46
+ "customer.restore",
47
+ "account.open",
48
+ "account.update",
49
+ "account.close",
50
+ "account.reopen",
51
+ "account.freeze",
52
+ "account.unfreeze",
53
+ "account.restrict"
54
+ ],
55
+ "eventPrefixes": ["account.limit.", "account.status."],
56
+ "recommendedPaths": ["/reason", "/request/correlationId", "/resource/classification"]
57
+ },
58
+ {
59
+ "id": "CUSTOMER-ACCOUNT-001",
60
+ "description": "An account operation records the kind of account and the customer the account belongs to.",
61
+ "rationale": "An account event that does not name its owning customer cannot be joined to the party it affects, so a reviewer asking which parties were frozen last quarter has to reconstruct ownership from a system whose current state may no longer match what was true at the time. The account kind matters for the same reason the party type does: freezing a dormant internal settlement account and freezing a customer's only operating account are different acts that otherwise look identical in the trail.",
62
+ "severity": "error",
63
+ "events": [
64
+ "account.open",
65
+ "account.update",
66
+ "account.close",
67
+ "account.reopen",
68
+ "account.freeze",
69
+ "account.unfreeze",
70
+ "account.restrict"
71
+ ],
72
+ "eventPrefixes": ["account.limit.", "account.status."],
73
+ "requiredPaths": ["/resource/ownerId"],
74
+ "requiredMetadata": [{ "path": "/customer/accountType", "type": "string" }]
75
+ },
76
+ {
77
+ "id": "CUSTOMER-CONTROL-001",
78
+ "description": "An operation that restricts, closes, restores, merges, deletes or re-limits a customer or an account records the authorization decision that permitted it and why it was performed.",
79
+ "rationale": "These are the operations that take a customer's ability to transact away or give it back, and they are the ones a complaint, a dispute or a supervisory question later turns on. Without a recorded decision nothing distinguishes an action policy allowed from one that bypassed policy; without a stated justification the trail cannot say whether a freeze was a fraud control, a billing dispute or a mistake, and the person who knew will not be reachable when the question is asked. Approval is recommended rather than required because many organizations legitimately let a single trained operator freeze an account on suspicion; a rule that forbade it would be switched off rather than met.",
80
+ "severity": "error",
81
+ "events": [
82
+ "customer.merge",
83
+ "customer.close",
84
+ "customer.delete",
85
+ "customer.restrict",
86
+ "customer.restore",
87
+ "account.close",
88
+ "account.reopen",
89
+ "account.freeze",
90
+ "account.unfreeze",
91
+ "account.restrict"
92
+ ],
93
+ "eventPrefixes": ["account.limit.", "account.status."],
94
+ "requiredPaths": ["/authorization", "/reason"],
95
+ "recommendedPaths": ["/approval"]
96
+ },
97
+ {
98
+ "id": "CUSTOMER-UPDATE-001",
99
+ "description": "An update to a customer record or an account records what changed.",
100
+ "rationale": "An update event without a change context records only that something was altered, which is the least useful thing an audit trail can say about a mutation. The profile requires the change object and nothing about its shape: the core already accepts changed field names, sanitized state, hashes or references, and a customer record is exactly the kind of data where recording field names instead of values is the right answer.",
101
+ "severity": "error",
102
+ "events": ["customer.update", "account.update"],
103
+ "requiredPaths": ["/change"],
104
+ "recommendedPaths": ["/change/changedFields"]
105
+ },
106
+ {
107
+ "id": "CUSTOMER-STATE-001",
108
+ "description": "A status transition on a customer or an account records the resulting status and the transition itself.",
109
+ "rationale": "A closure, freeze, restriction or reinstatement is meaningful only as a move between two states. An event that names the action but not the resulting status forces every consumer to model the producer's state machine correctly in order to know where the record ended up, and consumers get that wrong. Recording the resulting status makes the trail replayable: the state after any transition can be read directly instead of inferred from the event name.",
110
+ "severity": "error",
111
+ "events": [
112
+ "customer.close",
113
+ "customer.restrict",
114
+ "customer.restore",
115
+ "account.close",
116
+ "account.reopen",
117
+ "account.freeze",
118
+ "account.unfreeze",
119
+ "account.restrict"
120
+ ],
121
+ "eventPrefixes": ["account.status."],
122
+ "requiredPaths": ["/change"],
123
+ "requiredMetadata": [{ "path": "/customer/status", "type": "string" }]
124
+ },
125
+ {
126
+ "id": "CUSTOMER-RESTRICT-001",
127
+ "description": "A restriction or freeze records what the restriction actually prevents.",
128
+ "rationale": "Restriction is not one control. Blocking outgoing payments, blocking new orders, blocking sign-in and blocking the whole relationship have very different consequences for the customer, and they are routinely emitted under the same event name. Without the scope, nobody can later tell whether a restriction was proportionate, nor lift it safely, and a customer complaint about being cut off cannot be answered from the trail at all. The review that triggered the restriction is recommended rather than required, because a restriction may equally be an immediate operational decision with no review behind it.",
129
+ "severity": "error",
130
+ "events": ["customer.restrict", "account.restrict", "account.freeze"],
131
+ "requiredMetadata": [{ "path": "/customer/restrictionScope", "type": "string" }],
132
+ "recommendedPaths": ["/approval", "/metadata/customer/reviewId"]
133
+ },
134
+ {
135
+ "id": "CUSTOMER-LIMIT-001",
136
+ "description": "A limit change records which limit changed and both the prior and the resulting value.",
137
+ "rationale": "A limit change is only reviewable as a pair of numbers: raising a transfer ceiling from a small figure to a very large one and lowering it are the same event name with opposite risk. Recording only the new value hides the direction and the magnitude of the change. The profile requires which limit was touched because an account carries several, and it requires both states rather than a computed delta because the rule language compares nothing: the engine can check that both values were recorded and must leave the comparison, and any threshold, to the reviewer.",
138
+ "severity": "error",
139
+ "eventPrefixes": ["account.limit."],
140
+ "requiredPaths": ["/change/before", "/change/after"],
141
+ "requiredMetadata": [{ "path": "/customer/limitType", "type": "string" }]
142
+ },
143
+ {
144
+ "id": "CUSTOMER-MERGE-001",
145
+ "description": "A customer merge names the record that was retired and the records the merge related.",
146
+ "rationale": "A merge collapses two parties into one and is the hardest customer operation to undo or explain. Every identifier that pointed at the retired record becomes unresolvable at the moment of the merge, so an event that does not name that record breaks the trail of everything that referenced it, and the surviving record appears to acquire history from nowhere. Requiring the related resources as well keeps both sides of the merge on the event, so that a reviewer never has to guess which direction it ran.",
147
+ "severity": "error",
148
+ "events": ["customer.merge"],
149
+ "requiredPaths": ["/relatedResources"],
150
+ "requiredMetadata": [{ "path": "/customer/mergedFromId", "type": "string" }]
151
+ },
152
+ {
153
+ "id": "CUSTOMER-DELETE-001",
154
+ "description": "Deleting a customer record records how far the deletion reached.",
155
+ "rationale": "Deletion is the operation an audit trail is least able to reconstruct afterwards, because the evidence is precisely what was removed. Erasing a party's personal data while retaining the transactional record is a different act from removing the record entirely, and the two are habitually emitted under one name. Recording the scope is what lets a later reader answer what still exists, and it is the only honest way to distinguish an erasure that was performed from one that was claimed. The privacy context and an approval are recommended rather than required because neither is universal.",
156
+ "severity": "error",
157
+ "events": ["customer.delete"],
158
+ "requiredMetadata": [{ "path": "/customer/deletionScope", "type": "string" }],
159
+ "recommendedPaths": ["/approval", "/privacy"]
160
+ },
161
+ {
162
+ "id": "CUSTOMER-APPROVAL-001",
163
+ "description": "When the producer declares that an operation required approval, the state of that approval is recorded.",
164
+ "rationale": "This profile does not decide which customer or account operations need a second person, because that varies by organization, product and jurisdiction and no vendor-neutral answer exists. It enforces the consequence of the producer's own declaration instead: an operation the system itself said required approval, carried out with no approval state on the event, is either an unapproved change or an unrecorded one, and the trail cannot tell which. The rule requires the status rather than the approval object so that the finding points at the fact that is missing rather than at its container.",
165
+ "severity": "error",
166
+ "events": [
167
+ "customer.create",
168
+ "customer.update",
169
+ "customer.merge",
170
+ "customer.close",
171
+ "customer.delete",
172
+ "customer.restrict",
173
+ "customer.restore",
174
+ "account.open",
175
+ "account.update",
176
+ "account.close",
177
+ "account.reopen",
178
+ "account.freeze",
179
+ "account.unfreeze",
180
+ "account.restrict"
181
+ ],
182
+ "eventPrefixes": ["account.limit.", "account.status."],
183
+ "when": { "path": "/metadata/customer/approvalRequired", "equals": true },
184
+ "requiredPaths": ["/approval/status"]
185
+ },
186
+ {
187
+ "id": "CUSTOMER-SUBJECT-001",
188
+ "description": "An operation the producer declares was carried out on behalf of another party names that party.",
189
+ "rationale": "Customer and account operations are frequently performed by an agent, a branch operator or a support engineer for someone else, and in that case the actor is not the party the operation was about. The core already requires a subject when a delegation object is present, but a great many systems record acting-for relationships without modelling delegation at all, and then the trail attributes a closure to the operator who typed it. This rule fires only on the producer's own declaration, so the profile does not have to decide what acting on behalf of means in a given deployment.",
190
+ "severity": "error",
191
+ "events": [
192
+ "customer.create",
193
+ "customer.update",
194
+ "customer.merge",
195
+ "customer.close",
196
+ "customer.delete",
197
+ "customer.restrict",
198
+ "customer.restore",
199
+ "account.open",
200
+ "account.update",
201
+ "account.close",
202
+ "account.reopen",
203
+ "account.freeze",
204
+ "account.unfreeze",
205
+ "account.restrict"
206
+ ],
207
+ "eventPrefixes": ["account.limit.", "account.status."],
208
+ "when": { "path": "/metadata/customer/onBehalfOf", "equals": true },
209
+ "requiredPaths": ["/subject"]
210
+ },
211
+ {
212
+ "id": "CUSTOMER-OVERRIDE-001",
213
+ "description": "An operation the producer declares was a manual override records the authorization decision it was recorded against and why the override was made.",
214
+ "rationale": "A manual override is a person setting aside what the system would otherwise have done, and it is the single most reviewable act in customer administration: overrides are where fraud, favouritism and honest emergency handling all look alike. Without a justification the trail cannot separate them, and without the recorded decision it cannot even show what was overridden. The rule extends this to ordinary updates and openings, which no other rule in this profile requires a justification for, because an overridden routine change is not a routine change.",
215
+ "severity": "error",
216
+ "events": [
217
+ "customer.create",
218
+ "customer.update",
219
+ "customer.merge",
220
+ "customer.close",
221
+ "customer.delete",
222
+ "customer.restrict",
223
+ "customer.restore",
224
+ "account.open",
225
+ "account.update",
226
+ "account.close",
227
+ "account.reopen",
228
+ "account.freeze",
229
+ "account.unfreeze",
230
+ "account.restrict"
231
+ ],
232
+ "eventPrefixes": ["account.limit.", "account.status."],
233
+ "when": { "path": "/metadata/customer/manualOverride", "equals": true },
234
+ "requiredPaths": ["/authorization", "/reason"]
235
+ }
236
+ ]
237
+ }
@@ -0,0 +1,298 @@
1
+ {
2
+ "profileVersion": "0.1",
3
+ "name": "deployment-and-change-management",
4
+ "version": "0.1",
5
+ "status": "experimental",
6
+ "coreVersions": ["0.1"],
7
+ "title": "Deployment and Change Management Profile",
8
+ "description": "Additional conformance requirements for audit events that describe material change to a running system: releases, deployments, promotions, rollbacks, cancellations, infrastructure application, configuration and secret changes, and change requests. Every requirement adds to the OpenAuditModel Core Specification; none relaxes it. The profile supports fully automated continuous delivery as well as human-gated release: it never decides which change needs approval, it requires the producer to record whether one was required. Routine pipeline telemetry such as polling, build log output and secret reads is deliberately not governed.",
9
+ "rules": [
10
+ {
11
+ "id": "DEPLOY-CORE-001",
12
+ "description": "Every governed change identifies the deployment, release or change record it belongs to, and the environment the change was applied to.",
13
+ "rationale": "One change produces events from many stages and often from several different services, and nothing else in an event ties those stages together: `id` is unique per event and `resource` legitimately differs between the release, the approval and the target. Without a shared identifier a reviewer cannot say which approval belongs to which deployment, or which rollback undid which release. The environment is recorded separately from `application.environment` because `application` describes the system that performed the change, not the system that received it: a delivery platform runs in one environment and deploys into others, and conflating the two makes every environment-scoped review wrong.",
14
+ "severity": "error",
15
+ "events": [
16
+ "change.request.approve",
17
+ "change.request.cancel",
18
+ "change.request.close",
19
+ "change.request.create",
20
+ "change.request.reject",
21
+ "configuration.feature.toggle",
22
+ "configuration.policy.update",
23
+ "configuration.retention.update",
24
+ "configuration.secret.rotate",
25
+ "configuration.setting.create",
26
+ "configuration.setting.delete",
27
+ "configuration.setting.update"
28
+ ],
29
+ "eventPrefixes": ["deployment.release.", "deployment.infrastructure."],
30
+ "requiredMetadata": [
31
+ {
32
+ "path": "/deployment/id",
33
+ "type": "string"
34
+ },
35
+ {
36
+ "path": "/deployment/environment",
37
+ "type": "string"
38
+ }
39
+ ]
40
+ },
41
+ {
42
+ "id": "DEPLOY-CORE-002",
43
+ "description": "Every governed change states whether policy required it to be approved.",
44
+ "rationale": "This profile deliberately does not decide which changes need a human approval. Continuous delivery, human-gated release, scheduled maintenance and emergency repair are all legitimate delivery models, and a profile that demanded an approval on every production deployment would describe one of them and be switched off by everyone using another. The producer declares the policy in this flag and DEPLOY-APPROVAL-002 enforces the consequence. The flag is required rather than inferred because a conditional rule contributes nothing when its path is absent: silence would read as \"no approval was required\" when it may mean \"nobody recorded whether one was\", and those are the two answers a change review most needs to tell apart.",
45
+ "severity": "error",
46
+ "events": [
47
+ "change.request.approve",
48
+ "change.request.cancel",
49
+ "change.request.close",
50
+ "change.request.create",
51
+ "change.request.reject",
52
+ "configuration.feature.toggle",
53
+ "configuration.policy.update",
54
+ "configuration.retention.update",
55
+ "configuration.secret.rotate",
56
+ "configuration.setting.create",
57
+ "configuration.setting.delete",
58
+ "configuration.setting.update"
59
+ ],
60
+ "eventPrefixes": ["deployment.release.", "deployment.infrastructure."],
61
+ "requiredMetadata": [
62
+ {
63
+ "path": "/deployment/approvalRequired",
64
+ "type": "boolean"
65
+ }
66
+ ]
67
+ },
68
+ {
69
+ "id": "DEPLOY-CORE-003",
70
+ "description": "A governed change should record why it was made, the pipeline that produced it, and the identifier that correlates it with the rest of the operation.",
71
+ "rationale": "These are the fields a reviewer reaches for first and a producer omits most often. They are recommended rather than required because a routine automated deployment often has no justification beyond the change having been merged, because not every change comes from a pipeline, and because a configuration change made by hand from a console has no correlation identifier to carry. A missing one should prompt a question rather than fail a build.",
72
+ "severity": "warning",
73
+ "events": [
74
+ "change.request.approve",
75
+ "change.request.cancel",
76
+ "change.request.close",
77
+ "change.request.create",
78
+ "change.request.reject",
79
+ "configuration.feature.toggle",
80
+ "configuration.policy.update",
81
+ "configuration.retention.update",
82
+ "configuration.secret.rotate",
83
+ "configuration.setting.create",
84
+ "configuration.setting.delete",
85
+ "configuration.setting.update"
86
+ ],
87
+ "eventPrefixes": ["deployment.release.", "deployment.infrastructure."],
88
+ "recommendedPaths": ["/reason", "/request/correlationId", "/metadata/deployment/pipelineId"]
89
+ },
90
+ {
91
+ "id": "DEPLOY-RELEASE-001",
92
+ "description": "A release or infrastructure event names the version it concerns.",
93
+ "rationale": "A deployment trail that does not name a version records that something was released without recording what. Every later question depends on it: which build introduced a regression, whether an environment is running the artifact that was approved, and whether a rollback reached the version it aimed at. The profile requires an identifier and asserts nothing about its form, because semantic versions, tags, ordinal build numbers and content digests are all in legitimate use and no scheme is universal. What was changed by the release belongs in `relatedResources`, which is recommended rather than required so that a single-target deployment is not forced to duplicate its own `resource`.",
94
+ "severity": "error",
95
+ "eventPrefixes": ["deployment.release.", "deployment.infrastructure."],
96
+ "requiredMetadata": [
97
+ {
98
+ "path": "/deployment/version",
99
+ "type": "string"
100
+ }
101
+ ],
102
+ "recommendedPaths": ["/relatedResources", "/change/deploymentId"]
103
+ },
104
+ {
105
+ "id": "DEPLOY-RELEASE-002",
106
+ "description": "An event that changes what is running records the version it replaced.",
107
+ "rationale": "An incident review starts from what was running before, and that answer is only cheaply available at the moment of the change: reconstructing it later means trusting that no other event was lost from the same environment. Recording both versions on the event that moved between them makes the transition self-describing, lets a rollback be matched to the deployment it reverted, and turns \"which version was live at this time?\" into a lookup rather than a replay. A first deployment into an empty environment has a real answer too, and producers should record it explicitly rather than omit the field.",
108
+ "severity": "error",
109
+ "events": [
110
+ "deployment.release.deploy",
111
+ "deployment.release.promote",
112
+ "deployment.release.rollback",
113
+ "deployment.infrastructure.apply"
114
+ ],
115
+ "requiredMetadata": [
116
+ {
117
+ "path": "/deployment/previousVersion",
118
+ "type": "string"
119
+ }
120
+ ]
121
+ },
122
+ {
123
+ "id": "DEPLOY-REVERT-001",
124
+ "description": "Reverting or stopping a change in flight records the decision that permitted it and why the change was undone.",
125
+ "rationale": "A rollback or a cancellation discards work that the normal path had already accepted, and it is usually performed under time pressure by whoever was available rather than by whoever owns the change. Without a recorded authorization decision nothing distinguishes a considered revert from one person overriding another team, and without a justification the trail cannot say whether the change was withdrawn because it was wrong, because it was risky, or because something unrelated was failing at the time. Approval is recommended rather than required here precisely because a revert is often the correct emergency action and the approval is retrospective.",
126
+ "severity": "error",
127
+ "events": ["deployment.release.rollback", "deployment.release.cancel"],
128
+ "requiredPaths": ["/authorization", "/reason"],
129
+ "recommendedPaths": ["/approval"]
130
+ },
131
+ {
132
+ "id": "DEPLOY-CONFIG-001",
133
+ "description": "A configuration, feature, policy, retention or secret change records the decision that permitted it and which settings changed.",
134
+ "rationale": "Configuration is where controls are switched off, and a configuration change usually leaves no trace in business data at all, so the event is the only evidence that it happened. The authorization decision is what separates a change policy allowed from one that bypassed policy. The names of the settings that changed are required rather than their values, because the field list is the one representation every configuration system can produce and the one that never discloses a secret: a rotation records that the secret field changed and nothing about the secret. Producers may add sanitized before and after values where they are safe; this profile does not require them.",
135
+ "severity": "error",
136
+ "events": [
137
+ "configuration.setting.create",
138
+ "configuration.setting.update",
139
+ "configuration.setting.delete",
140
+ "configuration.secret.rotate",
141
+ "configuration.feature.toggle",
142
+ "configuration.policy.update",
143
+ "configuration.retention.update"
144
+ ],
145
+ "requiredPaths": ["/authorization", "/change/changedFields"]
146
+ },
147
+ {
148
+ "id": "DEPLOY-APPROVAL-001",
149
+ "description": "An event that is itself an approval decision records the resulting approval status.",
150
+ "rationale": "These events exist to record a human decision, so an approval event without an approval state records that a decision was reached without recording which one. The status is required because it is the fact the event is about; the workflow and the approving principals are recommended because a two-person review conducted in a ticket is a real approval process and has no workflow engine behind it. The profile does not require the status to match the event name: an approval attempt can fail, and an event that had to lie about its own outcome to conform would be worse evidence than one that did not conform.",
151
+ "severity": "error",
152
+ "events": ["deployment.release.approve", "change.request.approve", "change.request.reject"],
153
+ "requiredPaths": ["/approval/status"],
154
+ "recommendedPaths": ["/approval/workflowId", "/approval/approvers"]
155
+ },
156
+ {
157
+ "id": "DEPLOY-APPROVAL-002",
158
+ "description": "When the producer has declared that policy required approval, the event that carried the change out records the approval state it was carried out under.",
159
+ "rationale": "This is the rule the approvalRequired flag exists for, and it is deliberately about the executing event rather than the approving one: the question a review asks is not whether an approval exists somewhere but whether the change that actually reached the environment was covered by it. Requiring the status rather than an approved status is intentional. A change executed while approval was still pending is a control bypass, and the specification requires producers to record that honestly rather than suppress it; a rule that only accepted `approved` would make the most important event in the trail unrepresentable and would be met by omitting the flag instead.",
160
+ "severity": "error",
161
+ "events": [
162
+ "deployment.release.deploy",
163
+ "deployment.release.promote",
164
+ "deployment.release.rollback",
165
+ "deployment.release.cancel",
166
+ "deployment.infrastructure.apply",
167
+ "configuration.setting.create",
168
+ "configuration.setting.update",
169
+ "configuration.setting.delete",
170
+ "configuration.secret.rotate",
171
+ "configuration.feature.toggle",
172
+ "configuration.policy.update",
173
+ "configuration.retention.update"
174
+ ],
175
+ "when": {
176
+ "path": "/metadata/deployment/approvalRequired",
177
+ "equals": true
178
+ },
179
+ "requiredPaths": ["/approval/status"],
180
+ "recommendedPaths": ["/change/ticketId"]
181
+ },
182
+ {
183
+ "id": "DEPLOY-EMERGENCY-001",
184
+ "description": "A change the producer has flagged as bypassing the normal change path is justified.",
185
+ "rationale": "An emergency change, a break-glass repair and an overridden pipeline gate are the same fact from an audit point of view: the controls that normally govern change were not applied, by a decision someone made at the time. That decision is the thing a post-incident review needs and the thing nobody writes down afterwards, because the people involved were busy. The profile does not define when a change may bypass the path, and it does not require the bypass to have been approved; it requires the reason to exist while the answer is still known. The incident the change responded to and any retrospective approval are recommended for the same reason.",
186
+ "severity": "error",
187
+ "events": [
188
+ "change.request.approve",
189
+ "change.request.cancel",
190
+ "change.request.close",
191
+ "change.request.create",
192
+ "change.request.reject",
193
+ "configuration.feature.toggle",
194
+ "configuration.policy.update",
195
+ "configuration.retention.update",
196
+ "configuration.secret.rotate",
197
+ "configuration.setting.create",
198
+ "configuration.setting.delete",
199
+ "configuration.setting.update"
200
+ ],
201
+ "eventPrefixes": ["deployment.release.", "deployment.infrastructure."],
202
+ "when": {
203
+ "path": "/metadata/deployment/emergency",
204
+ "equals": true
205
+ },
206
+ "requiredPaths": ["/reason"],
207
+ "recommendedPaths": ["/approval", "/change/incidentId"]
208
+ },
209
+ {
210
+ "id": "DEPLOY-FAILURE-001",
211
+ "description": "A change that failed records the state the target was left in.",
212
+ "rationale": "A failed change is not self-describing. `outcome: failure` says the operation did not complete; it does not say whether the target was left untouched, left half-updated, or automatically reverted, and those three answers call for completely different responses. The state is the one fact only the producer can supply and the one nobody can reconstruct from the outside once the pipeline has moved on. The profile requires the answer to be recorded and does not constrain the vocabulary used to express it, because what counts as partially applied depends on what was being changed. The core model already requires a sanitized error code on a failure; a human readable message is recommended in addition.",
213
+ "severity": "error",
214
+ "events": [
215
+ "change.request.approve",
216
+ "change.request.cancel",
217
+ "change.request.close",
218
+ "change.request.create",
219
+ "change.request.reject",
220
+ "configuration.feature.toggle",
221
+ "configuration.policy.update",
222
+ "configuration.retention.update",
223
+ "configuration.secret.rotate",
224
+ "configuration.setting.create",
225
+ "configuration.setting.delete",
226
+ "configuration.setting.update"
227
+ ],
228
+ "eventPrefixes": ["deployment.release.", "deployment.infrastructure."],
229
+ "when": {
230
+ "path": "/event/outcome",
231
+ "equals": "failure"
232
+ },
233
+ "requiredMetadata": [
234
+ {
235
+ "path": "/deployment/resultingState",
236
+ "type": "string"
237
+ }
238
+ ],
239
+ "recommendedPaths": ["/event/error/message"]
240
+ },
241
+ {
242
+ "id": "DEPLOY-FAILURE-002",
243
+ "description": "A change that succeeded for some targets and failed for others records the state the targets were left in.",
244
+ "rationale": "A deployment across many instances is the clearest case in the model for `outcome: partial`, and it is also the case where the resulting state matters most: a partially applied change means two versions are serving at once, which is a configuration nobody designed and nobody tested. This rule is a duplicate of DEPLOY-FAILURE-001 with a different condition because the v0.1 rule language allows exactly one equality condition per rule and has no disjunction. Stating the requirement twice is preferred to stating it once and quietly leaving the partial case ungoverned.",
245
+ "severity": "error",
246
+ "events": [
247
+ "change.request.approve",
248
+ "change.request.cancel",
249
+ "change.request.close",
250
+ "change.request.create",
251
+ "change.request.reject",
252
+ "configuration.feature.toggle",
253
+ "configuration.policy.update",
254
+ "configuration.retention.update",
255
+ "configuration.secret.rotate",
256
+ "configuration.setting.create",
257
+ "configuration.setting.delete",
258
+ "configuration.setting.update"
259
+ ],
260
+ "eventPrefixes": ["deployment.release.", "deployment.infrastructure."],
261
+ "when": {
262
+ "path": "/event/outcome",
263
+ "equals": "partial"
264
+ },
265
+ "requiredMetadata": [
266
+ {
267
+ "path": "/deployment/resultingState",
268
+ "type": "string"
269
+ }
270
+ ]
271
+ },
272
+ {
273
+ "id": "DEPLOY-AUTOMATION-001",
274
+ "description": "When automation carried the change out, the principal it acted for should be named in `subject` and the transfer of authority in `delegation`.",
275
+ "rationale": "Automation performs most material change, so the actor on a deployment event is usually a service and the person who decided the change should happen is not in the event at all. The core model can express that in two ways, and this profile picks one: the human author is the `subject`, the service is the `actor`, and `delegation` says how authority moved between them. Recording the author in metadata would work for one product and be unreadable across two. It is a recommendation rather than a requirement because unattended automation such as drift correction or a scheduled reconciliation genuinely has no human author, and the resulting warning is the correct signal for a reviewer rather than a defect.",
276
+ "severity": "warning",
277
+ "events": [
278
+ "deployment.release.deploy",
279
+ "deployment.release.promote",
280
+ "deployment.release.rollback",
281
+ "deployment.release.cancel",
282
+ "deployment.infrastructure.apply",
283
+ "configuration.setting.create",
284
+ "configuration.setting.update",
285
+ "configuration.setting.delete",
286
+ "configuration.secret.rotate",
287
+ "configuration.feature.toggle",
288
+ "configuration.policy.update",
289
+ "configuration.retention.update"
290
+ ],
291
+ "when": {
292
+ "path": "/actor/type",
293
+ "equals": "service"
294
+ },
295
+ "recommendedPaths": ["/subject", "/delegation"]
296
+ }
297
+ ]
298
+ }