@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.
- package/LICENSE +215 -0
- package/README.md +647 -0
- package/dist/conformance/src/cli.d.ts +20 -0
- package/dist/conformance/src/cli.js +560 -0
- package/dist/conformance/src/cli.js.map +1 -0
- package/dist/conformance/src/format-errors.d.ts +20 -0
- package/dist/conformance/src/format-errors.js +116 -0
- package/dist/conformance/src/format-errors.js.map +1 -0
- package/dist/conformance/src/integrity/canonicalize.d.ts +25 -0
- package/dist/conformance/src/integrity/canonicalize.js +100 -0
- package/dist/conformance/src/integrity/canonicalize.js.map +1 -0
- package/dist/conformance/src/integrity/digest.d.ts +54 -0
- package/dist/conformance/src/integrity/digest.js +126 -0
- package/dist/conformance/src/integrity/digest.js.map +1 -0
- package/dist/conformance/src/integrity/types.d.ts +86 -0
- package/dist/conformance/src/integrity/types.js +36 -0
- package/dist/conformance/src/integrity/types.js.map +1 -0
- package/dist/conformance/src/integrity/verify-chain.d.ts +23 -0
- package/dist/conformance/src/integrity/verify-chain.js +235 -0
- package/dist/conformance/src/integrity/verify-chain.js.map +1 -0
- package/dist/conformance/src/integrity/verify-event.d.ts +29 -0
- package/dist/conformance/src/integrity/verify-event.js +140 -0
- package/dist/conformance/src/integrity/verify-event.js.map +1 -0
- package/dist/conformance/src/privacy/entropy.d.ts +14 -0
- package/dist/conformance/src/privacy/entropy.js +86 -0
- package/dist/conformance/src/privacy/entropy.js.map +1 -0
- package/dist/conformance/src/privacy/field-names.d.ts +36 -0
- package/dist/conformance/src/privacy/field-names.js +121 -0
- package/dist/conformance/src/privacy/field-names.js.map +1 -0
- package/dist/conformance/src/privacy/lint-event.d.ts +18 -0
- package/dist/conformance/src/privacy/lint-event.js +272 -0
- package/dist/conformance/src/privacy/lint-event.js.map +1 -0
- package/dist/conformance/src/privacy/rules.d.ts +44 -0
- package/dist/conformance/src/privacy/rules.js +161 -0
- package/dist/conformance/src/privacy/rules.js.map +1 -0
- package/dist/conformance/src/privacy/safe-formats.d.ts +32 -0
- package/dist/conformance/src/privacy/safe-formats.js +118 -0
- package/dist/conformance/src/privacy/safe-formats.js.map +1 -0
- package/dist/conformance/src/privacy/size-analysis.d.ts +36 -0
- package/dist/conformance/src/privacy/size-analysis.js +87 -0
- package/dist/conformance/src/privacy/size-analysis.js.map +1 -0
- package/dist/conformance/src/privacy/token-patterns.d.ts +24 -0
- package/dist/conformance/src/privacy/token-patterns.js +126 -0
- package/dist/conformance/src/privacy/token-patterns.js.map +1 -0
- package/dist/conformance/src/privacy/traverse.d.ts +33 -0
- package/dist/conformance/src/privacy/traverse.js +61 -0
- package/dist/conformance/src/privacy/traverse.js.map +1 -0
- package/dist/conformance/src/privacy/types.d.ts +58 -0
- package/dist/conformance/src/privacy/types.js +32 -0
- package/dist/conformance/src/privacy/types.js.map +1 -0
- package/dist/conformance/src/privacy/url-analysis.d.ts +40 -0
- package/dist/conformance/src/privacy/url-analysis.js +148 -0
- package/dist/conformance/src/privacy/url-analysis.js.map +1 -0
- package/dist/conformance/src/profiles/check-profile.d.ts +22 -0
- package/dist/conformance/src/profiles/check-profile.js +72 -0
- package/dist/conformance/src/profiles/check-profile.js.map +1 -0
- package/dist/conformance/src/profiles/evaluate-rule.d.ts +27 -0
- package/dist/conformance/src/profiles/evaluate-rule.js +87 -0
- package/dist/conformance/src/profiles/evaluate-rule.js.map +1 -0
- package/dist/conformance/src/profiles/load-profile.d.ts +30 -0
- package/dist/conformance/src/profiles/load-profile.js +95 -0
- package/dist/conformance/src/profiles/load-profile.js.map +1 -0
- package/dist/conformance/src/profiles/resolve-pointer.d.ts +44 -0
- package/dist/conformance/src/profiles/resolve-pointer.js +123 -0
- package/dist/conformance/src/profiles/resolve-pointer.js.map +1 -0
- package/dist/conformance/src/profiles/select-rules.d.ts +13 -0
- package/dist/conformance/src/profiles/select-rules.js +42 -0
- package/dist/conformance/src/profiles/select-rules.js.map +1 -0
- package/dist/conformance/src/profiles/types.d.ts +101 -0
- package/dist/conformance/src/profiles/types.js +31 -0
- package/dist/conformance/src/profiles/types.js.map +1 -0
- package/dist/conformance/src/profiles/validate-profile-definition.d.ts +12 -0
- package/dist/conformance/src/profiles/validate-profile-definition.js +47 -0
- package/dist/conformance/src/profiles/validate-profile-definition.js.map +1 -0
- package/dist/conformance/src/sources.d.ts +53 -0
- package/dist/conformance/src/sources.js +153 -0
- package/dist/conformance/src/sources.js.map +1 -0
- package/dist/conformance/src/validate-core.d.ts +30 -0
- package/dist/conformance/src/validate-core.js +62 -0
- package/dist/conformance/src/validate-core.js.map +1 -0
- package/dist/conformance/src/validate.d.ts +34 -0
- package/dist/conformance/src/validate.js +62 -0
- package/dist/conformance/src/validate.js.map +1 -0
- package/dist/conformance/src/validator-interface.d.ts +35 -0
- package/dist/conformance/src/validator-interface.js +19 -0
- package/dist/conformance/src/validator-interface.js.map +1 -0
- package/package.json +85 -0
- package/profiles/README.md +201 -0
- package/profiles/api-and-integration-management/README.md +271 -0
- package/profiles/api-and-integration-management/profile.json +258 -0
- package/profiles/backup-and-recovery/README.md +318 -0
- package/profiles/backup-and-recovery/profile.json +178 -0
- package/profiles/customer-and-account-management/README.md +340 -0
- package/profiles/customer-and-account-management/profile.json +237 -0
- package/profiles/deployment-and-change-management/README.md +359 -0
- package/profiles/deployment-and-change-management/profile.json +298 -0
- package/profiles/document-management/README.md +120 -0
- package/profiles/document-management/profile.json +170 -0
- package/profiles/financial-transaction-management/README.md +339 -0
- package/profiles/financial-transaction-management/profile.json +247 -0
- package/profiles/identity-and-access-management/README.md +112 -0
- package/profiles/identity-and-access-management/profile.json +120 -0
- package/profiles/incident-management/README.md +338 -0
- package/profiles/incident-management/profile.json +256 -0
- package/profiles/message-broker-management/README.md +344 -0
- package/profiles/message-broker-management/profile.json +399 -0
- package/profiles/profile-definition.schema.json +213 -0
- package/profiles/secrets-and-key-management/README.md +331 -0
- package/profiles/secrets-and-key-management/profile.json +219 -0
- package/schemas/v0.1/audit-event.schema.json +911 -0
- package/semantic-conventions/README.md +52 -0
- package/semantic-conventions/authentication.md +111 -0
- package/semantic-conventions/configuration-and-change.md +122 -0
- package/semantic-conventions/correlation-and-tracing.md +387 -0
- package/semantic-conventions/data-access.md +115 -0
- package/semantic-conventions/event-naming.md +114 -0
- package/semantic-conventions/identity-and-access.md +112 -0
- package/semantic-conventions/privileged-operations.md +110 -0
- package/semantic-conventions/workflow-and-approval.md +109 -0
- package/specification/actor-model.md +135 -0
- package/specification/approval-and-delegation.md +150 -0
- package/specification/authentication.md +116 -0
- package/specification/authorization.md +117 -0
- package/specification/change-model.md +134 -0
- package/specification/delivery.md +136 -0
- package/specification/design-principles.md +137 -0
- package/specification/event-model.md +387 -0
- package/specification/evidence-model.md +109 -0
- package/specification/extension-model.md +159 -0
- package/specification/integrity.md +314 -0
- package/specification/overview.md +172 -0
- package/specification/privacy.md +386 -0
- package/specification/resource-model.md +116 -0
- package/specification/terminology.md +135 -0
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# Document Management Profile
|
|
2
|
+
|
|
3
|
+
**Status: Experimental. Implemented in v0.1, 11 rules.**
|
|
4
|
+
|
|
5
|
+
Scope: applications that store, version, share and retain documents — content platforms, contract
|
|
6
|
+
systems, records management, engineering document control, knowledge bases.
|
|
7
|
+
|
|
8
|
+
The profile is vendor-neutral. It describes operations that any document system performs, not the
|
|
9
|
+
feature list of any product. It assumes no particular storage backend, approval workflow or
|
|
10
|
+
regulatory framework.
|
|
11
|
+
|
|
12
|
+
The enforceable rules are in [profile.json](profile.json).
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
auditmodel check-profile examples/profiles/document-management/valid --profile document-management
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## What this profile governs
|
|
19
|
+
|
|
20
|
+
| Event | Governed |
|
|
21
|
+
| ------------------------------------------------ | -------- |
|
|
22
|
+
| `document.file.upload`, `.delete`, `.download` | yes |
|
|
23
|
+
| `document.share.*` | yes |
|
|
24
|
+
| `document.permission.*` | yes |
|
|
25
|
+
| `document.version.*` | yes |
|
|
26
|
+
| `document.retention.*` | yes |
|
|
27
|
+
| `document.legal-hold.*` | yes |
|
|
28
|
+
| `document.file.view` and other high-volume reads | **no** |
|
|
29
|
+
|
|
30
|
+
### Why reads are excluded
|
|
31
|
+
|
|
32
|
+
A document system emits a view event every time anyone opens anything. Requiring an authorization
|
|
33
|
+
decision, a classification and a justification on each of them would add cost to the highest-volume
|
|
34
|
+
event in the system in exchange for very little review value, and the requirement would be switched
|
|
35
|
+
off rather than met.
|
|
36
|
+
|
|
37
|
+
The exclusion is structural, not a matter of discipline: **no selector in this profile uses a bare
|
|
38
|
+
`document.` prefix**, so `document.file.view` matches no rule and `check-profile` reports it as not
|
|
39
|
+
applicable. A test asserts that, because widening one prefix later would silently start governing
|
|
40
|
+
every read in a deployment.
|
|
41
|
+
|
|
42
|
+
Excluded does not mean unaudited. A view event is still a conforming OpenAuditModel event, and
|
|
43
|
+
[data-access.md](../../semantic-conventions/data-access.md) covers recording reads.
|
|
44
|
+
|
|
45
|
+
## Rules
|
|
46
|
+
|
|
47
|
+
| Rule | Applies to | Requires |
|
|
48
|
+
| ------------------- | -------------------------------- | ------------------------------------------------------------ |
|
|
49
|
+
| `DOC-CORE-001` | every governed event | `/authorization`, `/resource/classification` |
|
|
50
|
+
| `DOC-CORE-002` | every governed event | _recommends_ `/reason`, `/resource/parentId`, correlation ID |
|
|
51
|
+
| `DOC-SHARE-001` | `document.share.*` | `/metadata/share/recipientType` |
|
|
52
|
+
| `DOC-SHARE-002` | `document.share.create` | `/metadata/share/permission`; recommends an expiry |
|
|
53
|
+
| `DOC-SHARE-003` | external `document.share.create` | `/reason`, `/metadata/share/expiresAt` |
|
|
54
|
+
| `DOC-PERM-001` | `document.permission.*` | `/metadata/permission/granteeId`, `/metadata/permission/id` |
|
|
55
|
+
| `DOC-VERSION-001` | `document.version.*` | `/metadata/version/id` |
|
|
56
|
+
| `DOC-VERSION-002` | `document.version.rollback` | `/reason`, `/metadata/version/previousId` |
|
|
57
|
+
| `DOC-DELETE-001` | `document.file.delete` | `/reason`; recommends `/approval` |
|
|
58
|
+
| `DOC-RETENTION-001` | `document.retention.*` | `/change`, `/reason`, `/metadata/retention/class` |
|
|
59
|
+
| `DOC-HOLD-001` | `document.legal-hold.*` | `/reason`, `/metadata/legalHold/active` |
|
|
60
|
+
|
|
61
|
+
Each rule's full text and rationale is in [profile.json](profile.json).
|
|
62
|
+
|
|
63
|
+
## Design decisions
|
|
64
|
+
|
|
65
|
+
**External sharing is the only conditional rule.** `DOC-SHARE-003` fires only when the producer has
|
|
66
|
+
declared `/metadata/share/recipientType` to be `external`. The profile does not try to decide what
|
|
67
|
+
external means for a given deployment — a partner tenant is external to some organizations and
|
|
68
|
+
internal to others — so the producer makes that call and the profile enforces the consequence.
|
|
69
|
+
|
|
70
|
+
This is the same shape as the IAM profile's privileged-role rule, and it is the only conditional
|
|
71
|
+
mechanism v0.1 offers: one path compared for equality against one scalar.
|
|
72
|
+
|
|
73
|
+
**Expiry is recommended generally and required externally.** Indefinite internal access is often a
|
|
74
|
+
deliberate and correct choice. Indefinite external access rarely is.
|
|
75
|
+
|
|
76
|
+
**Approval is recommended, never required.** Many document systems legitimately let an owner delete
|
|
77
|
+
their own draft or reclassify their own file. A rule that required approval for every deletion would
|
|
78
|
+
describe one organization's process and be ignored by everyone else.
|
|
79
|
+
|
|
80
|
+
**Metadata is namespaced.** Requirements use `/metadata/share/...`, `/metadata/version/...`,
|
|
81
|
+
`/metadata/retention/...` rather than flat keys, matching the IAM profile's `/metadata/role/...`
|
|
82
|
+
convention. Namespacing keeps two profiles from assigning different meanings to the same key when an
|
|
83
|
+
event is governed by both — `expiresAt` on a share and on a credential are not the same fact.
|
|
84
|
+
|
|
85
|
+
**Retention and legal hold record state, not obligations.** The profile requires that the resulting
|
|
86
|
+
retention class and hold condition be recorded, and that the change be justified. It says nothing
|
|
87
|
+
about how long anything must be kept, what any class means, or what any jurisdiction requires.
|
|
88
|
+
Conformance with these rules is not compliance with any legal duty.
|
|
89
|
+
|
|
90
|
+
**Array contents are never inspected.** The v0.1 rule language checks presence, JSON type and scalar
|
|
91
|
+
equality. It cannot assert that `/evidence` contains an entry of a particular type, and this profile
|
|
92
|
+
does not pretend otherwise.
|
|
93
|
+
|
|
94
|
+
## Not required, and why
|
|
95
|
+
|
|
96
|
+
- **Watermarking, password protection, download permissions.** Real controls, but product features
|
|
97
|
+
rather than universal document operations. They belong in `metadata` or an extension.
|
|
98
|
+
- **A document approval event family.** Approval is already modelled by `workflow.approval.*` in
|
|
99
|
+
[workflow-and-approval.md](../../semantic-conventions/workflow-and-approval.md), and by the core
|
|
100
|
+
`/approval` object. Inventing `document.approval.*` would duplicate both.
|
|
101
|
+
- **`/organization/workspaceId` as an alternative to `/resource/parentId`.** The earlier placeholder
|
|
102
|
+
proposed "one or the other", which the rule language cannot express — there is no disjunction.
|
|
103
|
+
`parentId` is recommended rather than required, so a flat repository is not forced to invent a
|
|
104
|
+
container.
|
|
105
|
+
|
|
106
|
+
## Fixtures
|
|
107
|
+
|
|
108
|
+
[examples/profiles/document-management/](../../examples/profiles/document-management/) — ten valid,
|
|
109
|
+
eleven invalid, one not-applicable. Every valid fixture is core-conforming and privacy-clean; every
|
|
110
|
+
invalid fixture is core-**valid** and fails exactly one profile rule.
|
|
111
|
+
|
|
112
|
+
## Open questions
|
|
113
|
+
|
|
114
|
+
- Is a share to an internal group meaningfully different from a share to an external recipient, or is
|
|
115
|
+
`recipientType` sufficient? The profile currently assumes the producer's declaration is enough.
|
|
116
|
+
- How should a share whose scope changes over time be modelled: as revoke plus create, or as an
|
|
117
|
+
update? The profile accepts either and requires the recipient type in both.
|
|
118
|
+
- Should `document.file.download` of a restricted document require stronger evidence, such as
|
|
119
|
+
multi-factor authentication? Expressing that needs a producer-set discriminator like the IAM
|
|
120
|
+
profile's `privileged` flag, and there is no adoption evidence yet for what it should be called.
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
{
|
|
2
|
+
"profileVersion": "0.1",
|
|
3
|
+
"name": "document-management",
|
|
4
|
+
"version": "0.1",
|
|
5
|
+
"status": "experimental",
|
|
6
|
+
"coreVersions": ["0.1"],
|
|
7
|
+
"title": "Document Management Profile",
|
|
8
|
+
"description": "Additional conformance requirements for document management audit events: creation, deletion, download, versioning, sharing, access policy, retention and legal hold. Every requirement adds to the OpenAuditModel Core Specification; none relaxes it. High-volume read events such as document.file.view are deliberately not governed.",
|
|
9
|
+
"rules": [
|
|
10
|
+
{
|
|
11
|
+
"id": "DOC-CORE-001",
|
|
12
|
+
"description": "Every governed document operation records the authorization decision that permitted it and the classification of the document it acted on.",
|
|
13
|
+
"rationale": "A document operation without a recorded decision cannot be reviewed: nothing distinguishes an action policy allowed from one that bypassed policy. Without a classification, a reviewer cannot tell whether the operation touched public material or the most sensitive record the organization holds, and every triage decision downstream depends on that difference.",
|
|
14
|
+
"severity": "error",
|
|
15
|
+
"events": ["document.file.upload", "document.file.delete", "document.file.download"],
|
|
16
|
+
"eventPrefixes": [
|
|
17
|
+
"document.share.",
|
|
18
|
+
"document.permission.",
|
|
19
|
+
"document.version.",
|
|
20
|
+
"document.retention.",
|
|
21
|
+
"document.legal-hold."
|
|
22
|
+
],
|
|
23
|
+
"requiredPaths": ["/authorization", "/resource/classification"]
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"id": "DOC-CORE-002",
|
|
27
|
+
"description": "A governed document operation should record why it happened, where the document lives, and how it correlates with the wider operation.",
|
|
28
|
+
"rationale": "These are the fields a reviewer reaches for first and a producer omits most often. They are recommended rather than required because a conforming system may legitimately have no container hierarchy, and because a missing justification should prompt a question rather than fail a build.",
|
|
29
|
+
"severity": "warning",
|
|
30
|
+
"events": ["document.file.upload", "document.file.delete", "document.file.download"],
|
|
31
|
+
"eventPrefixes": [
|
|
32
|
+
"document.share.",
|
|
33
|
+
"document.permission.",
|
|
34
|
+
"document.version.",
|
|
35
|
+
"document.retention.",
|
|
36
|
+
"document.legal-hold."
|
|
37
|
+
],
|
|
38
|
+
"recommendedPaths": ["/reason", "/resource/parentId", "/request/correlationId"]
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"id": "DOC-SHARE-001",
|
|
42
|
+
"description": "A share event records the kind of recipient the access was granted to or withdrawn from.",
|
|
43
|
+
"rationale": "Whether a document went to a colleague, a partner or an anonymous link is the single fact that determines the risk of the operation, and it cannot be reconstructed later from an identifier alone.",
|
|
44
|
+
"severity": "error",
|
|
45
|
+
"eventPrefixes": ["document.share."],
|
|
46
|
+
"requiredMetadata": [
|
|
47
|
+
{
|
|
48
|
+
"path": "/share/recipientType",
|
|
49
|
+
"type": "string"
|
|
50
|
+
}
|
|
51
|
+
]
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"id": "DOC-SHARE-002",
|
|
55
|
+
"description": "Creating a share records the permission granted, and should record when the access ends.",
|
|
56
|
+
"rationale": "A share that does not say what the recipient may do records that something was shared without recording what was given away. An expiry is recommended here and required for external recipients, because indefinite internal access is often a deliberate and reasonable choice.",
|
|
57
|
+
"severity": "error",
|
|
58
|
+
"events": ["document.share.create"],
|
|
59
|
+
"requiredMetadata": [
|
|
60
|
+
{
|
|
61
|
+
"path": "/share/permission",
|
|
62
|
+
"type": "string"
|
|
63
|
+
}
|
|
64
|
+
],
|
|
65
|
+
"recommendedPaths": ["/metadata/share/expiresAt"]
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"id": "DOC-SHARE-003",
|
|
69
|
+
"description": "A share to an external recipient is time-bounded and justified.",
|
|
70
|
+
"rationale": "An external share is a disclosure outside the organization's control. Without an expiry it is indefinite, and without a stated reason there is no record of who decided the disclosure was acceptable. This rule fires only when the producer has declared the recipient external, so the profile does not have to guess what external means for a given deployment.",
|
|
71
|
+
"severity": "error",
|
|
72
|
+
"events": ["document.share.create"],
|
|
73
|
+
"when": {
|
|
74
|
+
"path": "/metadata/share/recipientType",
|
|
75
|
+
"equals": "external"
|
|
76
|
+
},
|
|
77
|
+
"requiredPaths": ["/reason"],
|
|
78
|
+
"requiredMetadata": [
|
|
79
|
+
{
|
|
80
|
+
"path": "/share/expiresAt",
|
|
81
|
+
"type": "string"
|
|
82
|
+
}
|
|
83
|
+
]
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
"id": "DOC-PERM-001",
|
|
87
|
+
"description": "A document permission change names the principal whose access changed and the permission involved.",
|
|
88
|
+
"rationale": "An access-policy change that does not say whose access changed is not reviewable at all. 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.",
|
|
89
|
+
"severity": "error",
|
|
90
|
+
"eventPrefixes": ["document.permission."],
|
|
91
|
+
"requiredMetadata": [
|
|
92
|
+
{
|
|
93
|
+
"path": "/permission/id",
|
|
94
|
+
"type": "string"
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
"path": "/permission/granteeId",
|
|
98
|
+
"type": "string"
|
|
99
|
+
}
|
|
100
|
+
],
|
|
101
|
+
"recommendedPaths": ["/reason"]
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
"id": "DOC-VERSION-001",
|
|
105
|
+
"description": "A version event identifies the version it produced or restored.",
|
|
106
|
+
"rationale": "Version history is the record of what a document said at a point in time. An event that does not name the version cannot be tied to the content it describes.",
|
|
107
|
+
"severity": "error",
|
|
108
|
+
"eventPrefixes": ["document.version."],
|
|
109
|
+
"requiredMetadata": [
|
|
110
|
+
{
|
|
111
|
+
"path": "/version/id",
|
|
112
|
+
"type": "string"
|
|
113
|
+
}
|
|
114
|
+
]
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
"id": "DOC-VERSION-002",
|
|
118
|
+
"description": "A rollback records the version it replaced and why the current content was discarded.",
|
|
119
|
+
"rationale": "A rollback destroys the prevailing content. Without the replaced version the trail cannot say what was undone, and without a reason it cannot say whether the change was a correction or a reversal of someone else's work.",
|
|
120
|
+
"severity": "error",
|
|
121
|
+
"events": ["document.version.rollback"],
|
|
122
|
+
"requiredPaths": ["/reason"],
|
|
123
|
+
"requiredMetadata": [
|
|
124
|
+
{
|
|
125
|
+
"path": "/version/previousId",
|
|
126
|
+
"type": "string"
|
|
127
|
+
}
|
|
128
|
+
]
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
"id": "DOC-DELETE-001",
|
|
132
|
+
"description": "Deleting a document is justified, and should be approved.",
|
|
133
|
+
"rationale": "Deletion is the operation an audit trail is least able to reconstruct after the fact, because the evidence is what was removed. Approval is recommended rather than required: many systems legitimately allow an owner to delete their own draft, and a rule that forbade it would be ignored rather than followed.",
|
|
134
|
+
"severity": "error",
|
|
135
|
+
"events": ["document.file.delete"],
|
|
136
|
+
"requiredPaths": ["/reason"],
|
|
137
|
+
"recommendedPaths": ["/approval"]
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
"id": "DOC-RETENTION-001",
|
|
141
|
+
"description": "A retention change records the resulting retention class, the transition and why it was made.",
|
|
142
|
+
"rationale": "Retention determines how long the evidence exists at all, so a change to it is a change to the audit trail's own reach. The profile requires the state and the transition to be recorded; it does not say what any retention class must mean, or how long anything must be kept, because those are organization and jurisdiction decisions this specification does not make.",
|
|
143
|
+
"severity": "error",
|
|
144
|
+
"eventPrefixes": ["document.retention."],
|
|
145
|
+
"requiredPaths": ["/change", "/reason"],
|
|
146
|
+
"requiredMetadata": [
|
|
147
|
+
{
|
|
148
|
+
"path": "/retention/class",
|
|
149
|
+
"type": "string"
|
|
150
|
+
}
|
|
151
|
+
],
|
|
152
|
+
"recommendedPaths": ["/approval"]
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
"id": "DOC-HOLD-001",
|
|
156
|
+
"description": "A legal hold event records whether the hold is in force and why it changed.",
|
|
157
|
+
"rationale": "A hold suspends normal retention, so an event that does not state the resulting condition leaves the document's disposition ambiguous. The profile records the state and the justification only; it asserts nothing about any jurisdiction's obligations, and conformance with this rule is not compliance with any legal duty.",
|
|
158
|
+
"severity": "error",
|
|
159
|
+
"eventPrefixes": ["document.legal-hold."],
|
|
160
|
+
"requiredPaths": ["/reason"],
|
|
161
|
+
"requiredMetadata": [
|
|
162
|
+
{
|
|
163
|
+
"path": "/legalHold/active",
|
|
164
|
+
"type": "boolean"
|
|
165
|
+
}
|
|
166
|
+
],
|
|
167
|
+
"recommendedPaths": ["/approval"]
|
|
168
|
+
}
|
|
169
|
+
]
|
|
170
|
+
}
|