@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,318 @@
1
+ # Backup and Recovery Profile
2
+
3
+ **Status: Experimental. Implemented in v0.1, 13 rules (12 enforceable).**
4
+
5
+ Scope: systems that create, verify, retain, delete and restore recovery points, and systems that move
6
+ service between sites — database and virtual machine backup products, storage snapshot managers,
7
+ object-storage archival tooling, replication and disaster-recovery orchestrators, and the backup
8
+ policy engines that decide what is protected.
9
+
10
+ The profile is vendor-neutral. It describes operations that any protection system performs, and
11
+ assumes no particular storage backend, transport, replication topology, scheduler, approval workflow
12
+ or regulatory framework. No rule names a product, a cloud, a filesystem or a protocol.
13
+
14
+ The enforceable rules are in [profile.json](profile.json).
15
+
16
+ ```bash
17
+ auditmodel check-profile examples/profiles/backup-and-recovery/valid --profile backup-and-recovery
18
+ ```
19
+
20
+ ## Purpose
21
+
22
+ Backup and recovery tooling holds two things nothing else in an organization holds at once: standing
23
+ read access to the most complete copy of the data, and the ability to overwrite production with an
24
+ older version of it. A restore is simultaneously a recovery and a data-loss event. A backup deletion
25
+ destroys the only evidence of what was deleted. A failover moves production somewhere else, usually
26
+ in a hurry, usually under an incident.
27
+
28
+ These operations are also the ones most often audited least, because they are performed by service
29
+ accounts on a schedule and the volume is dominated by machinery. The profile draws the line at the
30
+ operations a human would be asked about afterwards, and requires of them the handful of facts that
31
+ cannot be reconstructed once the moment has passed.
32
+
33
+ ## Scope
34
+
35
+ ### Event families
36
+
37
+ | Family | Events | Governed |
38
+ | ------------- | ---------------------------------------------------------------------------------------- | -------- |
39
+ | Backup set | `backup.create`, `backup.complete`, `backup.verify`, `backup.delete`, `backup.expire` | yes |
40
+ | Backup policy | `backup.policy.*` | yes |
41
+ | Snapshot | `snapshot.create`, `snapshot.delete` | yes |
42
+ | Restore | `restore.start`, `restore.complete` | yes |
43
+ | Recovery | `recovery.start`, `recovery.complete`, `recovery.failover`, `recovery.failback` | yes |
44
+ | Data plane | `backup.chunk.write`, `backup.progress`, `restore.progress`, `storage.replica.heartbeat` | **no** |
45
+
46
+ `backup.create` records that a protection run was requested or started; `backup.complete` records
47
+ that it finished, and on success that a restorable copy now exists. Producers that emit a single
48
+ event for the whole run should use `backup.complete`, because that is the event the profile asks for a
49
+ recovery point.
50
+
51
+ `/resource` always names the **protected source** — the database, volume, cluster or service whose
52
+ data is at stake — not the copy. The copy is named by `/metadata/backup/backupId` or
53
+ `/metadata/backup/snapshotId`. That convention is what lets a reviewer filter on one resource and see
54
+ its whole protection history: the runs, the verifications, the disposals and the restores.
55
+
56
+ ### Explicit exclusions
57
+
58
+ The profile deliberately governs **none** of the following:
59
+
60
+ - **Chunk, block and segment writes.** A single backup run emits thousands. They are machinery, not
61
+ decisions.
62
+ - **Progress and percentage-complete reports.** Heartbeats of an operation that is already audited at
63
+ its start and its end.
64
+ - **Replication and health heartbeats.** Continuous telemetry about a standing arrangement.
65
+ - **Catalogue reads, index scans and backup listings.** Ordinary reads of protection metadata.
66
+
67
+ Requiring an authorization decision and a justification on each of these would add cost to the
68
+ highest-volume events in the system in exchange for almost no review value, and the requirement would
69
+ be switched off rather than met.
70
+
71
+ The exclusion is **structural**, not a matter of discipline. Every rule that governs a family other
72
+ than backup policy selects **exact event names**, and the only prefix in the whole profile is
73
+ `backup.policy.`. There is no bare `backup.`, `snapshot.`, `restore.`, `recovery.` or `storage.`
74
+ prefix anywhere, so `backup.chunk.write` and `restore.progress` match no rule at all and
75
+ `check-profile` reports them as not applicable. A test asserts this, because widening one selector
76
+ later would silently start governing every chunk write in every backup run in a deployment.
77
+
78
+ Excluded does not mean unaudited. A chunk write is still a conforming OpenAuditModel event; the
79
+ profile simply adds nothing to it.
80
+
81
+ ### Failure is an outcome, not an event name
82
+
83
+ The profile defines no `backup.fail`, `restore.fail` or `recovery.fail` event. A failed backup run is
84
+ `backup.complete` with `event.outcome: "failure"` and an `event.error` descriptor, which the core
85
+ schema already requires. Encoding the outcome in the name doubles the vocabulary and turns "how often
86
+ do our restores fail?" into a string-matching problem; see
87
+ [event-naming.md §5](../../semantic-conventions/event-naming.md).
88
+
89
+ ## Rules
90
+
91
+ | Rule | Applies to | Requires |
92
+ | --------------------- | ---------------------------------------------------------------- | ---------------------------------------------------------------- |
93
+ | `BACKUP-CORE-001` | every governed event | `/authorization` |
94
+ | `BACKUP-CORE-002` | every governed event | _recommends_ `/reason`, `/request/correlationId` (**warning**) |
95
+ | `BACKUP-SET-001` | `backup.create/.complete/.verify/.delete/.expire` | `/metadata/backup/backupId`, `/metadata/backup/backupType` |
96
+ | `BACKUP-SET-002` | `backup.complete` **when** `event.outcome` is `success` | `/metadata/backup/recoveryPoint` |
97
+ | `BACKUP-VERIFY-001` | `backup.verify` | `/metadata/backup/verificationStatus` |
98
+ | `BACKUP-DELETE-001` | `backup.delete`, `snapshot.delete` | `/reason`; recommends `/approval` |
99
+ | `BACKUP-EXPIRE-001` | `backup.expire` | `/metadata/backup/retentionClass` |
100
+ | `BACKUP-SNAPSHOT-001` | `snapshot.create`, `snapshot.delete` | `/metadata/backup/snapshotId` |
101
+ | `BACKUP-RESTORE-001` | `restore.start`, `restore.complete` | `/reason`, `/metadata/backup/{restoreId,sourceId,recoveryPoint}` |
102
+ | `BACKUP-RECOVERY-001` | `recovery.start/.complete/.failover/.failback` | `/metadata/backup/recoveryId` |
103
+ | `BACKUP-FAILOVER-001` | `recovery.failover`, `recovery.failback` | `/reason`, `/metadata/backup/targetScope` |
104
+ | `BACKUP-APPROVAL-001` | destructive and site-moving events **when** approval is declared | `/approval` |
105
+ | `BACKUP-POLICY-001` | `backup.policy.*` | `/change`, `/reason`, `/metadata/backup/policyId` |
106
+
107
+ Each rule's full text and rationale is in [profile.json](profile.json). `BACKUP-CORE-002` is the only
108
+ rule with severity `warning`; every other rule is `error` and fails conformance.
109
+
110
+ ## Metadata namespace
111
+
112
+ All profile requirements live under `/metadata/backup/`. Namespacing keeps two profiles from
113
+ assigning different meanings to the same key when an event is governed by both — a `recoveryPoint` on
114
+ a backup and a `retentionClass` on a document are not the same fact — and it matches the
115
+ `/metadata/role/…` and `/metadata/share/…` convention of the published IAM and document profiles.
116
+
117
+ | Field | Type | Meaning |
118
+ | -------------------- | ------- | --------------------------------------------------------------------------------------------------------- |
119
+ | `backupId` | string | Identifier of the backup copy the event acted on or produced |
120
+ | `backupType` | string | Kind of copy. Open vocabulary; `full`, `incremental`, `differential`, `synthetic-full`, `transaction-log` |
121
+ | `snapshotId` | string | Identifier of the snapshot the event created or removed |
122
+ | `restoreId` | string | Identifier of the restore operation, shared by its start and its completion |
123
+ | `recoveryId` | string | Identifier of the recovery operation, shared by every stage of it |
124
+ | `sourceId` | string | Identifier of the copy a restore read **from** |
125
+ | `recoveryPoint` | string | Timestamp of the data state the copy holds or the restore returned to |
126
+ | `targetScope` | string | Logical destination — a site, region, cluster, replica or storage tier |
127
+ | `verificationStatus` | string | Verdict a verification reached about the copy |
128
+ | `retentionClass` | string | Producer-defined retention class governing the copy |
129
+ | `policyId` | string | Identifier of the backup policy a `backup.policy.*` event changed |
130
+ | `approvalRequired` | boolean | The producer's declaration that local policy required an approval for this operation |
131
+
132
+ `targetScope` is a **logical name**, never an endpoint, URL, bucket URI or connection string. See
133
+ [Privacy considerations](#privacy-considerations).
134
+
135
+ ### `/integrity/batchId` is not a backup identifier
136
+
137
+ This profile never uses `/integrity/batchId`, and neither should a producer of backup events. In this
138
+ repository `batchId` identifies an **integrity sealing or verification batch**: the group of events
139
+ whose digests were computed, or whose chain was verified, together. It does not identify a job run, a
140
+ backup set or any business operation, and a consumer must not read it as one — see
141
+ [integrity.md §2.1](../../specification/integrity.md).
142
+
143
+ The identifiers for a protection operation are `/metadata/backup/backupId`, `snapshotId`, `restoreId`
144
+ and `recoveryId`; the identifier for the logical run that spans several events is
145
+ `/request/correlationId`. A test asserts that no rule reaches into `/integrity` and that no fixture
146
+ carries a `batchId`, so this cannot drift.
147
+
148
+ ## Conditional-policy fields
149
+
150
+ The v0.1 rule language offers exactly one conditional mechanism: one path compared for equality
151
+ against one scalar. This profile spends it twice.
152
+
153
+ **`BACKUP-SET-002` conditions on the outcome.** A backup run that failed produced no recovery point,
154
+ so requiring one unconditionally would force producers to fabricate a value for the failure case. The
155
+ rule fires only on `event.outcome: "success"`, where the claim "a restorable copy now exists as of
156
+ this moment" is being made and must be substantiated. A condition is one equality test and nothing
157
+ else, so `partial` and `unknown` also fall outside it: a `backup.complete` recorded as `partial` is
158
+ not asked for a recovery point. Producers that record a partially successful run SHOULD record the
159
+ recovery point anyway when the run produced one, because a consumer cannot tell the two cases apart.
160
+
161
+ **`BACKUP-APPROVAL-001` conditions on a producer declaration.** See below.
162
+
163
+ When a condition's path is absent the condition does not hold and the rule contributes nothing. That
164
+ is why an event omitting `approvalRequired` is not failed for missing approval.
165
+
166
+ ## Approval model
167
+
168
+ **Routine backup creation is never asked for an approval.** `backup.create`, `backup.complete` and
169
+ `snapshot.create` are outside `BACKUP-APPROVAL-001` entirely, and a test asserts it. A profile that
170
+ demanded an approval on every scheduled nightly run would be describing a process no organization
171
+ operates, and would be switched off within a week.
172
+
173
+ **Approval is conditional for the five operations that destroy or move.** `backup.delete`,
174
+ `snapshot.delete`, `restore.start`, `recovery.failover` and `recovery.failback` must carry
175
+ `/approval` **when the producer has set `/metadata/backup/approvalRequired` to `true`**. The profile
176
+ does not decide which deployments need an approval — that is an organizational judgement, and a rule
177
+ that imposed one answer would describe a single company's process. The producer declares the answer
178
+ and the profile enforces the consequence: an event that states approval was required and carries no
179
+ approval record documents an operation performed without the control its own system said was needed.
180
+
181
+ **The profile requires the record, not a particular decision.** It does not require
182
+ `approval.status: "approved"`. A rule carries at most one condition, and that one is already spent on
183
+ `approvalRequired`; requiring `approved` unconditionally would fail an event that correctly records a
184
+ restore that was requested, rejected and did not proceed. Recording the rejection is exactly the
185
+ behaviour an audit trail should encourage.
186
+
187
+ **Approval is recommended, never required, for deletion generally.** `BACKUP-DELETE-001` recommends
188
+ `/approval` so that its absence prompts a question rather than a build failure. Many deployments
189
+ legitimately let an owner remove a failed or superseded copy of their own service's data.
190
+
191
+ ## Privacy considerations
192
+
193
+ Backup metadata is an unusually attractive place to leak infrastructure secrets, because the fields a
194
+ producer reaches for — "where did this go?", "how did we reach it?" — are exactly the ones that
195
+ contain credentials.
196
+
197
+ **Never record in a backup event:** storage account keys, access key identifiers or secret access
198
+ keys, pre-signed or time-limited URLs, SAS tokens, repository passwords or passphrases, encryption
199
+ keys or key material, database connection strings, SSH keys, or credentials of any kind belonging to
200
+ the backup agent, the storage target or the restored system.
201
+
202
+ Record instead a **logical** name for the destination — `targetScope: "region-south-secondary"`, not
203
+ an endpoint, bucket URI or connection string — and a **key identifier** rather than a key, where the
204
+ encryption key needs to be identified at all.
205
+
206
+ `event.error.message` on a failed run must be sanitized. Backup agents habitually surface the whole
207
+ transport error, and the whole transport error habitually contains the URL, which habitually contains
208
+ the signature.
209
+
210
+ Recovery events also touch personal data indirectly: a restore returns data that may include personal
211
+ records, and `/privacy` is the place to record that the event itself was minimized. The profile
212
+ requires no personal data anywhere, and no rule requires a display name, an email address or an
213
+ account identifier.
214
+
215
+ Every fixture in this profile is checked by `auditmodel lint-privacy` in the test suite, and the
216
+ linter is complementary to the profile rather than replaced by it: a profile says which fields must be
217
+ present, the linter says which values must not.
218
+
219
+ ## Known rule-language limitations
220
+
221
+ The v0.1 rule vocabulary checks presence, JSON type and scalar equality, and nothing else. The profile
222
+ states the strongest honest rule available and records the rest here as informative guidance.
223
+
224
+ - **A recovery point is only checked as a present string.** The engine cannot verify that
225
+ `recoveryPoint` is a valid RFC 3339 timestamp, that it precedes `time`, or that the gap between
226
+ them is within any recovery point objective. Producers SHOULD record it as an RFC 3339 timestamp;
227
+ consumers should not assume the profile validated it.
228
+ - **No numeric ranges.** The profile cannot express "`receivedApprovals` must be at least
229
+ `requiredApprovals`", "the copy must be no older than 24 hours" or any threshold. Those comparisons
230
+ need a policy engine; OpenAuditModel records decisions and does not evaluate them.
231
+ - **No cross-field comparison.** `BACKUP-RESTORE-001` requires both `sourceId` and `recoveryPoint`
232
+ but cannot assert that the recovery point is the one the source copy actually holds.
233
+ - **No disjunction.** A rule cannot say "`backupId` **or** `snapshotId`". That is why the backup and
234
+ snapshot families are governed by separate rules with separate identifiers rather than one rule
235
+ accepting either.
236
+ - **One condition per rule.** `BACKUP-APPROVAL-001` cannot additionally condition on the outcome, so
237
+ it requires the approval record rather than an approved status; see
238
+ [Approval model](#approval-model).
239
+ - **A condition is one equality test, so it cannot express "not a failure".** `BACKUP-SET-002` fires
240
+ on `event.outcome: "success"` and therefore leaves `partial` and `unknown` unchecked. Expressing
241
+ "any outcome other than `failure`" needs negation or disjunction, and the v0.1 vocabulary has
242
+ neither.
243
+ - **Array contents are never inspected.** `BACKUP-VERIFY-001` recommends `/evidence` but cannot
244
+ require that it contains an entry of type `document`, and `BACKUP-POLICY-001` requires `/change`
245
+ but cannot require a particular entry in `change.changedFields`.
246
+ - **Verification and retention vocabularies are open.** The profile requires that
247
+ `verificationStatus`, `backupType` and `retentionClass` be recorded as strings. It fixes none of
248
+ their values, because what verification means ranges from a checksum comparison to a full restore
249
+ rehearsal, and retention classes are an organizational scheme this specification does not set.
250
+
251
+ ## Not required, and why
252
+
253
+ - **`/request/correlationId`.** Recommended by `BACKUP-CORE-002` and by three family rules, never
254
+ required. Correlation infrastructure is not universal, and an appliance emitting events with no
255
+ request context would otherwise be pushed to invent one. It remains the field that ties a restore's
256
+ start to its completion, and the one this profile recommends most insistently.
257
+ - **Trace and span identifiers.** Never required. A backup appliance may have no tracing at all.
258
+ - **`/approval` universally.** See [Approval model](#approval-model).
259
+ - **`/metadata/backup/approvalRequired` itself.** The IAM profile requires its `privileged` flag
260
+ because privilege is a property of the role — a fact about the object, knowable from the object.
261
+ Whether an approval was required is a property of the **organization's process**, and forcing every
262
+ producer to assert one on every restore would make the profile a statement about how companies
263
+ ought to be run rather than about what an event must record.
264
+ - **A verification schedule, an RPO or an RTO target.** These are policy values, not event facts. A
265
+ profile that required them would be encoding one organization's service levels.
266
+ - **The storage destination of a backup.** Not required, because the honest answer is frequently a
267
+ URL with a signature in it. `targetScope` is available for a logical name and is required only
268
+ where the destination is the point of the event, on failover and failback.
269
+ - **`event.error` on a failed operation.** Already required by the core schema whenever
270
+ `event.outcome` is `failure`; a profile rule would duplicate it.
271
+ - **Immutability, retention-lock or worm claims.** A producer may record them in metadata, but the
272
+ profile requires nothing of the sort and asserts nothing about storage guarantees. This
273
+ specification uses the term _tamper-evident_ and only that term; see
274
+ [integrity.md §1](../../specification/integrity.md).
275
+
276
+ ## Cross-profile overlaps
277
+
278
+ - **`identity-and-access-management`.** Rotating the credential a backup agent uses is
279
+ `identity.credential.rotate` and belongs to the IAM profile; this profile governs what the agent
280
+ then does. The two never select the same event name.
281
+ - **`document-management`.** Both profiles use a `retentionClass` concept, and they mean different
282
+ things: a document's retention class governs a business record, a backup's governs a copy of a
283
+ system. Namespacing keeps them apart — `/metadata/retention/class` versus
284
+ `/metadata/backup/retentionClass` — and no event is selected by both profiles.
285
+ - **`incident-management`.** A failover is usually performed during an incident. The incident's own
286
+ lifecycle belongs to that profile; the failover belongs here. `/reason/reference` and
287
+ `/request/correlationId` are the fields that join them, which is why this profile recommends the
288
+ correlation identifier on every recovery event.
289
+ - **`deployment-and-change-management`.** A backup policy change is a configuration change, and a
290
+ producer may reasonably emit both a `backup.policy.update` and a change-management event for the
291
+ same approval. `BACKUP-POLICY-001` requires `/change`, which is the same core object that profile
292
+ uses, so the two descriptions agree rather than compete.
293
+ - **Integrity.** Sealing and chain verification are described by `/integrity` and are orthogonal to
294
+ this profile. See [`/integrity/batchId` is not a backup identifier](#integritybatchid-is-not-a-backup-identifier).
295
+
296
+ ## Fixtures
297
+
298
+ [examples/profiles/backup-and-recovery/](../../examples/profiles/backup-and-recovery/) — fifteen
299
+ valid, twelve invalid, three not-applicable. Every fixture is core-conforming and privacy-clean; each
300
+ invalid fixture is core-**valid** and fails exactly one profile rule at one pointer. Every event name
301
+ a rule selects has a valid fixture, and every rule with severity `error` has an invalid one; tests
302
+ hold both directions.
303
+
304
+ ## Open questions
305
+
306
+ - Should `backup.create` and `backup.complete` remain two events, or should the profile push all
307
+ producers to a single completion event? Two-event runs are common enough that requiring one shape
308
+ would exclude real systems, but the split means `backup.create` carries no recovery point.
309
+ - Is `verificationStatus` the right level of granularity, or does a useful verification record need to
310
+ distinguish a checksum comparison from a mounted restore rehearsal? The profile currently leaves the
311
+ vocabulary open and records the verdict only.
312
+ - Should a failover distinguish a rehearsal from an emergency response with a producer-set boolean, in
313
+ the way `BACKUP-APPROVAL-001` uses `approvalRequired`? There is no adoption evidence yet for what
314
+ the field should be called, so the fixtures illustrate `rehearsal` without any rule depending on it.
315
+ - Should `restore.start` on a production environment require multi-factor authentication, as the IAM
316
+ profile requires for privileged role changes? Expressing that needs a discriminator the profile does
317
+ not yet have, since `application.environment` is an open vocabulary and `"production"` is a
318
+ recommendation rather than a fixed token.
@@ -0,0 +1,178 @@
1
+ {
2
+ "profileVersion": "0.1",
3
+ "name": "backup-and-recovery",
4
+ "version": "0.1",
5
+ "status": "experimental",
6
+ "coreVersions": ["0.1"],
7
+ "title": "Backup and Recovery Profile",
8
+ "description": "Additional conformance requirements for backup, snapshot, restore, recovery and failover audit events: creating and verifying recovery points, deleting and expiring them, restoring data, moving service between sites, and changing the policy that decides what is protected. Every requirement adds to the OpenAuditModel Core Specification; none relaxes it. Data-plane events such as chunk writes, progress reports and replication heartbeats are deliberately not governed.",
9
+ "rules": [
10
+ {
11
+ "id": "BACKUP-CORE-001",
12
+ "description": "Every governed backup, snapshot, restore, recovery or backup-policy operation records the authorization decision that permitted it.",
13
+ "rationale": "Backup and recovery tooling holds standing access to the most complete copy of an organization's data and to the switch that moves production between sites. Without a recorded decision, nothing in the trail separates an operation a policy permitted from one that ran because the backup service could. An operation performed by a scheduler is not exempt: it records the decision the schedule represents, or `not-applicable` where the producer made no decision at all, so that the absence of a control is stated rather than inferred from a missing field.",
14
+ "severity": "error",
15
+ "events": [
16
+ "backup.create",
17
+ "backup.complete",
18
+ "backup.verify",
19
+ "backup.delete",
20
+ "backup.expire",
21
+ "snapshot.create",
22
+ "snapshot.delete",
23
+ "restore.start",
24
+ "restore.complete",
25
+ "recovery.start",
26
+ "recovery.complete",
27
+ "recovery.failover",
28
+ "recovery.failback"
29
+ ],
30
+ "eventPrefixes": ["backup.policy."],
31
+ "requiredPaths": ["/authorization"]
32
+ },
33
+ {
34
+ "id": "BACKUP-CORE-002",
35
+ "description": "A governed operation should record why it happened and how it correlates with the wider recovery workflow.",
36
+ "rationale": "Backup and recovery operations are multi-stage by nature: a restore is a start and a completion, a failover is a sequence across storage, database and traffic layers, and each stage is usually emitted by a different component. A correlation identifier is what turns those events back into one operation. It is recommended rather than required because correlation infrastructure is not universal, and an appliance that emits events with no request context would otherwise be pushed to invent one. A justification is recommended everywhere and required on the operations that destroy or move data.",
37
+ "severity": "warning",
38
+ "events": [
39
+ "backup.create",
40
+ "backup.complete",
41
+ "backup.verify",
42
+ "backup.delete",
43
+ "backup.expire",
44
+ "snapshot.create",
45
+ "snapshot.delete",
46
+ "restore.start",
47
+ "restore.complete",
48
+ "recovery.start",
49
+ "recovery.complete",
50
+ "recovery.failover",
51
+ "recovery.failback"
52
+ ],
53
+ "eventPrefixes": ["backup.policy."],
54
+ "recommendedPaths": ["/reason", "/request/correlationId"]
55
+ },
56
+ {
57
+ "id": "BACKUP-SET-001",
58
+ "description": "A backup lifecycle event identifies the backup copy it acted on and the kind of copy it is.",
59
+ "rationale": "An identifier is what ties the event to the copy it describes; without it the trail records that a backup happened but not which one, and a later question about a specific recovery point has no answer. The kind of copy matters just as much, because a full, incremental, differential or transaction-log copy carry different consequences: deleting a full copy can invalidate every incremental that depends on it, while deleting one incremental usually does not. A reviewer cannot infer either fact from the event name.",
60
+ "severity": "error",
61
+ "events": [
62
+ "backup.create",
63
+ "backup.complete",
64
+ "backup.verify",
65
+ "backup.delete",
66
+ "backup.expire"
67
+ ],
68
+ "requiredMetadata": [
69
+ { "path": "/backup/backupId", "type": "string" },
70
+ { "path": "/backup/backupType", "type": "string" }
71
+ ],
72
+ "recommendedPaths": ["/metadata/backup/retentionClass"]
73
+ },
74
+ {
75
+ "id": "BACKUP-SET-002",
76
+ "description": "A backup run that completed successfully records the point in time the copy represents.",
77
+ "rationale": "A backup is only meaningful as a position on a timeline. Without the recovery point, the trail can say a copy exists but not what state it holds, and the question every recovery review begins with — how much data would be lost by restoring this copy — is unanswerable. The requirement is conditional on a successful outcome because a failed run produces no recovery point, and a rule that demanded one would force producers to fabricate a value for the failure case. The profile checks that the field is present and is a string; it cannot check that it is a valid timestamp or that it precedes the event time.",
78
+ "severity": "error",
79
+ "events": ["backup.complete"],
80
+ "when": { "path": "/event/outcome", "equals": "success" },
81
+ "requiredMetadata": [{ "path": "/backup/recoveryPoint", "type": "string" }],
82
+ "recommendedPaths": ["/metadata/backup/retentionClass"]
83
+ },
84
+ {
85
+ "id": "BACKUP-VERIFY-001",
86
+ "description": "A backup verification event records the verdict it reached about the copy.",
87
+ "rationale": "`event.outcome` says whether the verification ran; it does not say what the verification found. A check that completed successfully and proved the copy restorable, and a check that completed successfully and found it corrupt, are both `outcome: success`, and conflating them turns a verification programme into a record that jobs executed. The separate verdict is the only field that distinguishes an organization that tests its backups from one that tests whether its test harness runs. The profile does not fix the vocabulary of verdicts, because what verification means ranges from a checksum comparison to a full restore rehearsal.",
88
+ "severity": "error",
89
+ "events": ["backup.verify"],
90
+ "requiredMetadata": [{ "path": "/backup/verificationStatus", "type": "string" }],
91
+ "recommendedPaths": ["/evidence", "/metadata/backup/recoveryPoint"]
92
+ },
93
+ {
94
+ "id": "BACKUP-DELETE-001",
95
+ "description": "Deleting a backup copy or a snapshot before its retention has elapsed is justified.",
96
+ "rationale": "Deleting a recovery point removes the only means of returning to the state it held, and it is the operation an audit trail is least able to reconstruct afterwards, because the evidence is precisely what was removed. It is also the operation an attacker performs before the one they want to hide. A stated justification is what separates a deliberate disposal from destruction of evidence, and it must be recorded at the moment of deletion because there is nothing left to ask afterwards. Approval is recommended rather than required: many deployments legitimately let an owner remove a failed or superseded copy.",
97
+ "severity": "error",
98
+ "events": ["backup.delete", "snapshot.delete"],
99
+ "requiredPaths": ["/reason"],
100
+ "recommendedPaths": ["/approval", "/metadata/backup/retentionClass"]
101
+ },
102
+ {
103
+ "id": "BACKUP-EXPIRE-001",
104
+ "description": "A retention-driven expiry records the retention class that disposed of the copy.",
105
+ "rationale": "An expiry claims that a copy was removed because a policy said its time was up, and that claim is the entire justification for the event. Recording the retention class is what makes the claim checkable: it lets a reviewer test the disposal against the policy that supposedly drove it, and it makes a deletion recorded under the wrong name visible, because an operator-initiated removal dressed as an expiry has no class to point at. Retention metadata is recommended across the rest of this profile and required only here, where it is the operation's reason for existing.",
106
+ "severity": "error",
107
+ "events": ["backup.expire"],
108
+ "requiredMetadata": [{ "path": "/backup/retentionClass", "type": "string" }]
109
+ },
110
+ {
111
+ "id": "BACKUP-SNAPSHOT-001",
112
+ "description": "A snapshot event identifies the snapshot it created or removed.",
113
+ "rationale": "Snapshots are the recovery point people actually reach for, because they are cheap enough to take before every risky change and fast enough to restore from. That also makes them numerous, and a snapshot event without an identifier is indistinguishable from every other snapshot event on the same resource. The identifier is what allows a restore to be traced back to the copy it used, and a deletion to be traced to the copy that is now gone.",
114
+ "severity": "error",
115
+ "events": ["snapshot.create", "snapshot.delete"],
116
+ "requiredMetadata": [{ "path": "/backup/snapshotId", "type": "string" }],
117
+ "recommendedPaths": ["/metadata/backup/recoveryPoint", "/metadata/backup/retentionClass"]
118
+ },
119
+ {
120
+ "id": "BACKUP-RESTORE-001",
121
+ "description": "A restore records why it was performed, which operation it belongs to, which copy it read from and the point in time it restored to.",
122
+ "rationale": "A restore overwrites live data with older data, so it is simultaneously a data-loss event and a recovery: everything written between the recovery point and the restore is discarded. Three facts make it reviewable and none can be reconstructed later. The source names the copy that was trusted, which is what an investigation needs when the copy turns out to have been compromised. The recovery point bounds what was lost. The justification separates a disaster recovery from a quiet reversal of someone else's work — the single most common way a restore is misused. The restore identifier ties the start and the completion into one operation across the components that emit them.",
123
+ "severity": "error",
124
+ "events": ["restore.start", "restore.complete"],
125
+ "requiredPaths": ["/reason"],
126
+ "requiredMetadata": [
127
+ { "path": "/backup/restoreId", "type": "string" },
128
+ { "path": "/backup/sourceId", "type": "string" },
129
+ { "path": "/backup/recoveryPoint", "type": "string" }
130
+ ],
131
+ "recommendedPaths": ["/approval", "/request/correlationId"]
132
+ },
133
+ {
134
+ "id": "BACKUP-RECOVERY-001",
135
+ "description": "A recovery or failover event identifies the recovery operation it belongs to.",
136
+ "rationale": "A recovery is never one event. Declaring it, moving storage, promoting a replica, redirecting traffic and standing the original site back up are separate operations, usually in separate systems, often hours apart and frequently performed by different people under pressure. The recovery identifier is what makes them one operation rather than an unexplained sequence, and it is the field a post-incident review depends on to reconstruct the order in which decisions were taken.",
137
+ "severity": "error",
138
+ "events": ["recovery.start", "recovery.complete", "recovery.failover", "recovery.failback"],
139
+ "requiredMetadata": [{ "path": "/backup/recoveryId", "type": "string" }],
140
+ "recommendedPaths": ["/request/correlationId", "/metadata/backup/recoveryPoint"]
141
+ },
142
+ {
143
+ "id": "BACKUP-FAILOVER-001",
144
+ "description": "A failover or failback records where service was moved to and why it was moved.",
145
+ "rationale": "A failover moves production service somewhere else. Without the destination the event says service moved but not where, which is useless during the incident and worse afterwards, when the question is which site was serving traffic at a given moment. Without a reason, a rehearsed drill, a planned maintenance move and an emergency response to an outage are the same event, and they demand entirely different scrutiny. The destination is recorded as a logical scope — a site, region, cluster or replica name — never as an endpoint, URL or connection string.",
146
+ "severity": "error",
147
+ "events": ["recovery.failover", "recovery.failback"],
148
+ "requiredPaths": ["/reason"],
149
+ "requiredMetadata": [{ "path": "/backup/targetScope", "type": "string" }],
150
+ "recommendedPaths": ["/approval", "/request/correlationId"]
151
+ },
152
+ {
153
+ "id": "BACKUP-APPROVAL-001",
154
+ "description": "When the producer declares that local policy required approval, the operation carries the approval record.",
155
+ "rationale": "This profile does not decide which deployments need an approval before a destructive deletion, a production restore or a failover; that is an organizational judgement, and a rule that imposed one answer would describe a single company's process and be switched off everywhere else. The producer declares the answer, and the profile enforces the consequence: an event that states approval was required and carries no approval record documents an operation performed without the control its own system said was needed. That gap is exactly what a review is looking for, and it is invisible unless the declaration and the record are held to each other.",
156
+ "severity": "error",
157
+ "events": [
158
+ "backup.delete",
159
+ "snapshot.delete",
160
+ "restore.start",
161
+ "recovery.failover",
162
+ "recovery.failback"
163
+ ],
164
+ "when": { "path": "/metadata/backup/approvalRequired", "equals": true },
165
+ "requiredPaths": ["/approval"]
166
+ },
167
+ {
168
+ "id": "BACKUP-POLICY-001",
169
+ "description": "A change to a backup policy records the policy, what changed and why.",
170
+ "rationale": "A backup policy decides what is protected, how often and for how long, so editing it changes future recoverability without touching any data. The damage surfaces only when a recovery is attempted, which may be months later, and by then the event that caused it is one line in a change log. Recording the transition in `change` is what makes it possible to say what protection was reduced rather than merely that a policy was edited, and the justification is what distinguishes a deliberate cost decision from a silent weakening of the organization's ability to recover.",
171
+ "severity": "error",
172
+ "eventPrefixes": ["backup.policy."],
173
+ "requiredPaths": ["/change", "/reason"],
174
+ "requiredMetadata": [{ "path": "/backup/policyId", "type": "string" }],
175
+ "recommendedPaths": ["/approval", "/metadata/backup/retentionClass"]
176
+ }
177
+ ]
178
+ }