@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,340 @@
1
+ # Customer and Account Management Profile
2
+
3
+ **Profile version: 0.1 · Core versions: 0.1 · Status: Experimental · Implemented, 13 rules (12
4
+ enforceable)**
5
+
6
+ Additional conformance requirements for material **customer** and **business account** lifecycle
7
+ audit events: creating, updating, merging, restricting, closing and deleting a customer record, and
8
+ opening, updating, re-limiting, freezing, restricting, reinstating and closing an account.
9
+
10
+ ```bash
11
+ auditmodel check-profile examples/profiles/customer-and-account-management/valid \
12
+ --profile customer-and-account-management
13
+ ```
14
+
15
+ The rules live in [profile.json](profile.json) and are enforced by the declarative engine described in
16
+ [profiles/README.md](../README.md). Nothing in this profile is implemented in code.
17
+
18
+ ## Purpose
19
+
20
+ Customer and account administration is where a business decides who it will deal with and on what
21
+ terms. Freezing an account, restricting a relationship, raising a transfer limit, merging two parties
22
+ or erasing a record are the operations a complaint, a dispute, a fraud investigation or a supervisory
23
+ question later turns on — and they are typically performed by a handful of operators through internal
24
+ consoles that log the least.
25
+
26
+ The core model already records who did what to which resource. What it cannot know is the small set
27
+ of domain facts that make these particular events reviewable: what kind of party the record describes,
28
+ which account it was, what a restriction actually prevented, what a limit was before and after, which
29
+ record a merge retired, and whether an operator was acting for someone else. This profile requires
30
+ those facts and nothing else.
31
+
32
+ ## Scope
33
+
34
+ The profile is vendor-neutral. It describes operations that any system holding customers and accounts
35
+ performs — a bank, a telecommunications operator, an insurer, a utility, a marketplace, a SaaS
36
+ billing platform — and assumes no particular product, storage backend, workflow engine, account
37
+ numbering scheme, regulator or jurisdiction.
38
+
39
+ It governs **business parties and business accounts**. It is not an identity profile: see
40
+ [Cross-profile overlaps](#cross-profile-overlaps).
41
+
42
+ ## Event families
43
+
44
+ | Family | Events | Governed |
45
+ | -------------------------- | ----------------------------------------------------------------------------------------------------------- | -------- |
46
+ | Customer record lifecycle | `customer.create`, `.update`, `.merge`, `.close`, `.delete`, `.restrict`, `.restore` | yes |
47
+ | Account lifecycle | `account.open`, `.update`, `.close`, `.reopen`, `.freeze`, `.unfreeze`, `.restrict` | yes |
48
+ | Account status transitions | `account.status.` prefix (for producers that model transitions as one event rather than distinct verbs) | yes |
49
+ | Account limits | `account.limit.` prefix | yes |
50
+ | Reads and searches | `customer.profile.view`, `customer.search`, `account.balance.view`, `account.statement.view`, and their kin | **no** |
51
+
52
+ Both shapes of status change are accepted deliberately. Some systems emit `account.freeze`; others
53
+ emit a single `account.status.update` and carry the transition in `change`. The profile requires the
54
+ same facts from both rather than forcing a producer to rename its events, and the fixtures include
55
+ one of each.
56
+
57
+ `event.category` is left to the producer. A freeze is reasonably `security` in one system and
58
+ `resource-lifecycle` in another, and no rule here inspects the category.
59
+
60
+ ### Explicit exclusions
61
+
62
+ Ordinary reads are **not governed**: profile views, customer searches, balance and statement lookups,
63
+ transaction listings, entitlement checks, and any other high-volume data-plane traffic. A customer
64
+ platform emits these constantly. Requiring a party type, an owning customer and a justification on
65
+ each of them would add cost to the highest-volume events in the system in exchange for very little
66
+ review value, and the requirement would be switched off rather than met.
67
+
68
+ The exclusion is **structural, not a matter of discipline**: no selector in this profile uses a bare
69
+ `customer.` or `account.` prefix. The lifecycle families are named event-by-event, and only
70
+ `account.limit.` and `account.status.` — sub-families in which every member is a material change —
71
+ are matched by prefix. `customer.profile.view` and `account.balance.view` therefore match no rule at
72
+ all and `check-profile` reports them as not applicable. A test asserts this, because widening one
73
+ prefix later would silently start governing every profile view in a deployment.
74
+
75
+ Excluded does not mean unaudited. A view of a customer record is still a conforming OpenAuditModel
76
+ event, and [data-access.md](../../semantic-conventions/data-access.md) covers recording reads —
77
+ including the case, called out there, where a read crosses a customer boundary and deserves more.
78
+
79
+ ## Rules
80
+
81
+ Twelve rules are enforceable (`error`); `CUSTOMER-CORE-002` is a `warning` and never fails
82
+ conformance.
83
+
84
+ | Rule | Applies to | Requires |
85
+ | ----------------------- | --------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- |
86
+ | `CUSTOMER-CORE-001` | every governed event | `/metadata/customer/customerType` string |
87
+ | `CUSTOMER-CORE-002` | every governed event | _recommends_ `/reason`, `/request/correlationId`, `/resource/classification` |
88
+ | `CUSTOMER-ACCOUNT-001` | every governed `account.*` event | `/resource/ownerId`, `/metadata/customer/accountType` string |
89
+ | `CUSTOMER-CONTROL-001` | merge, close, delete, restrict, restore, reopen, freeze, unfreeze, limits, status | `/authorization`, `/reason`; _recommends_ `/approval` |
90
+ | `CUSTOMER-UPDATE-001` | `customer.update`, `account.update` | `/change`; _recommends_ `/change/changedFields` |
91
+ | `CUSTOMER-STATE-001` | status transitions and `account.status.` | `/change`, `/metadata/customer/status` string |
92
+ | `CUSTOMER-RESTRICT-001` | `customer.restrict`, `account.restrict`, `account.freeze` | `/metadata/customer/restrictionScope` string; _recommends_ `/approval`, `reviewId` |
93
+ | `CUSTOMER-LIMIT-001` | `account.limit.` | `/change/before`, `/change/after`, `/metadata/customer/limitType` string |
94
+ | `CUSTOMER-MERGE-001` | `customer.merge` | `/relatedResources`, `/metadata/customer/mergedFromId` string |
95
+ | `CUSTOMER-DELETE-001` | `customer.delete` | `/metadata/customer/deletionScope` string; _recommends_ `/approval`, `/privacy` |
96
+ | `CUSTOMER-APPROVAL-001` | every governed event, **when** `/metadata/customer/approvalRequired` is `true` | `/approval/status` |
97
+ | `CUSTOMER-SUBJECT-001` | every governed event, **when** `/metadata/customer/onBehalfOf` is `true` | `/subject` |
98
+ | `CUSTOMER-OVERRIDE-001` | every governed event, **when** `/metadata/customer/manualOverride` is `true` | `/authorization`, `/reason` |
99
+
100
+ Each rule's full text and rationale is in [profile.json](profile.json).
101
+
102
+ ## Metadata namespace
103
+
104
+ Every metadata requirement lives under **`/metadata/customer/`**. Nothing is required at a metadata
105
+ root key: `/metadata/status` or `/metadata/type` would collide with any other profile that governs the
106
+ same event, and a reader would have no way to tell whose `status` it was.
107
+
108
+ | Field | Type | Required by | Example |
109
+ | ------------------ | ------- | --------------------------------------- | ----------------------------------- |
110
+ | `customerType` | string | `CUSTOMER-CORE-001` (every event) | `business`, `individual`, `partner` |
111
+ | `accountType` | string | `CUSTOMER-ACCOUNT-001` (account events) | `settlement`, `subscription` |
112
+ | `status` | string | `CUSTOMER-STATE-001` (transitions) | `active`, `restricted`, `closed` |
113
+ | `restrictionScope` | string | `CUSTOMER-RESTRICT-001` | `outbound-payments` |
114
+ | `limitType` | string | `CUSTOMER-LIMIT-001` | `daily-transfer-limit` |
115
+ | `mergedFromId` | string | `CUSTOMER-MERGE-001` | `customer-8420` |
116
+ | `deletionScope` | string | `CUSTOMER-DELETE-001` | `personal-data`, `full-record` |
117
+ | `approvalRequired` | boolean | conditional discriminator | `true` |
118
+ | `onBehalfOf` | boolean | conditional discriminator | `true` |
119
+ | `manualOverride` | boolean | conditional discriminator | `true` |
120
+ | `reviewId` | string | recommended by `CUSTOMER-RESTRICT-001` | `review-2026-044` |
121
+
122
+ Every vocabulary above is **open**. The profile requires that a value be recorded and that it be a
123
+ string; it does not say what a customer type, an account type, a status or a restriction scope may be,
124
+ because those are product and organization decisions. A profile that enumerated them would be one
125
+ vendor's field list wearing a domain's name.
126
+
127
+ ## Conditional-policy fields
128
+
129
+ Three requirements fire only on a discriminator the **producer** sets. This is the whole conditional
130
+ mechanism v0.1 offers — one path compared for equality against one scalar — and it is used here for
131
+ the same reason the IAM profile uses `privileged` and the document profile uses `recipientType`: the
132
+ profile cannot decide these questions for a deployment, but it can enforce the consequence of the
133
+ deployment's own answer.
134
+
135
+ | Discriminator | Consequence |
136
+ | ------------------------------------- | --------------------------------------------------------------- |
137
+ | `/metadata/customer/approvalRequired` | `CUSTOMER-APPROVAL-001` requires `/approval/status` |
138
+ | `/metadata/customer/onBehalfOf` | `CUSTOMER-SUBJECT-001` requires `/subject` |
139
+ | `/metadata/customer/manualOverride` | `CUSTOMER-OVERRIDE-001` requires `/authorization` and `/reason` |
140
+
141
+ **When the discriminator is absent, the condition does not hold and the rule contributes nothing.**
142
+ That is deliberate and is documented in [profiles/README.md](../README.md): treating a missing flag as
143
+ possibly true would fail every event that never intended to declare one. If an organization wants the
144
+ flag itself to be mandatory, that is a local policy, and this profile does not impose it — an event
145
+ that simply never says `manualOverride` is conforming.
146
+
147
+ ## Approval model
148
+
149
+ `/approval` is **never universally required**. Customer administration is performed by trained
150
+ operators making single-person decisions in every organization the authors are aware of, and a profile
151
+ that demanded a second approver on every freeze would describe one company's process and be ignored
152
+ everywhere else.
153
+
154
+ Instead:
155
+
156
+ - `CUSTOMER-CONTROL-001` **recommends** `/approval` on every restrictive, destructive or
157
+ limit-changing operation, so its absence is visible as a warning.
158
+ - `CUSTOMER-RESTRICT-001` and `CUSTOMER-DELETE-001` repeat the recommendation for the two operations
159
+ where a second pair of eyes matters most.
160
+ - `CUSTOMER-APPROVAL-001` **requires** `/approval/status` when, and only when, the producer has itself
161
+ declared `approvalRequired: true`. An operation the system said needed approval, recorded with no
162
+ approval state at all, is either an unapproved change or an unrecorded one — and the trail cannot
163
+ tell which.
164
+
165
+ The rule requires `/approval/status` rather than `/approval`, so the finding points at the fact that is
166
+ missing rather than at its container. It does **not** require the status to equal `approved`: a
167
+ rejected or expired approval is a legitimate thing to record, and an event that failed for exactly
168
+ that reason must remain expressible.
169
+
170
+ ## Privacy considerations
171
+
172
+ Customer records are the densest concentration of personal data most systems hold, so this profile is
173
+ deliberately built out of **categories and opaque identifiers**, never people.
174
+
175
+ - Nothing in the profile requires a name, an email address, a telephone number, a postal address, a
176
+ date of birth, a national identifier, a payment instrument or an account number. The party is
177
+ identified by `/resource/id` and `/resource/ownerId`, which are opaque references.
178
+ - `customerType` is a **category** — `business`, `individual`, `partner`. It is what makes an access
179
+ review answerable and it identifies nobody.
180
+ - `CUSTOMER-UPDATE-001` requires `/change` and recommends `/change/changedFields` precisely so that a
181
+ producer can record _which_ fields of a customer record changed without recording their values. The
182
+ core accepts field names, sanitized state, hashes or references, and for this domain field names are
183
+ usually the right answer.
184
+ - `CUSTOMER-DELETE-001` recommends `/privacy` on erasure, because an erasure event that itself carries
185
+ the erased data is the worst possible outcome.
186
+ - Account numbers, card numbers and IBANs must never appear anywhere in an event — not in
187
+ `resource.id`, not in `metadata`, not in `change`. Use an opaque account reference such as
188
+ `account-ref-781`. `resource.name` is left unused by every fixture in this profile for the same
189
+ reason: for a customer it is usually a person's or a company's name.
190
+
191
+ Profile conformance is not privacy compliance. Run `auditmodel lint-privacy` as well; every fixture
192
+ here is required by test to pass both.
193
+
194
+ ## Known rule-language limitations
195
+
196
+ The v0.1 rule language checks presence, JSON type and strict scalar equality. It has no regular
197
+ expressions, no numeric comparison, no cross-field comparison, no boolean combination and no
198
+ array-content predicates. Consequences worth stating plainly:
199
+
200
+ - **`CUSTOMER-LIMIT-001` cannot check the direction or the size of a limit change.** It requires that
201
+ `/change/before` and `/change/after` both be recorded and leaves the comparison — and any threshold
202
+ above which a second approver is expected — to the reviewer or to a downstream policy engine. The
203
+ profile can guarantee the evidence exists; it cannot evaluate it.
204
+ - **No rule can require that a status actually changed.** `CUSTOMER-STATE-001` requires `/change` and
205
+ the resulting `status`, but it cannot assert that `/change/before/status` differs from
206
+ `/change/after/status`, because that is a cross-field comparison.
207
+ - **No rule can require that `/relatedResources` contains a customer.** `CUSTOMER-MERGE-001` requires
208
+ the array to be present and non-empty; array contents are never inspected. The retired record is
209
+ therefore _also_ required in `/metadata/customer/mergedFromId`, where a scalar check works.
210
+ - **A rule cannot say "either A or B".** There is no disjunction, so where two shapes are both
211
+ legitimate the profile requires whichever fact both shapes can carry.
212
+ - **Only one condition per rule.** "Required when the override is manual _and_ the account is frozen"
213
+ is not expressible; such a requirement would have to be split or dropped. It was dropped.
214
+ - **`account.limit.` and `account.status.` are prefixes**, so a producer that emitted
215
+ `account.limit.view` would find it governed. Reads should be named after what is read
216
+ (`account.balance.view`), not placed under a mutation sub-family. This is guidance, not something
217
+ the rule language can enforce.
218
+ - **Outcome and error are left entirely to the core.** The core schema already requires
219
+ `/event/error` whenever `outcome` is `failure`, and `errorDescriptor` already requires `code`. A
220
+ profile rule restating that would add nothing, so there is none.
221
+
222
+ ## Cross-profile overlaps
223
+
224
+ ### identity-and-access-management — the boundary that matters most
225
+
226
+ [identity-and-access-management](../identity-and-access-management/) governs **identities**: who can
227
+ sign in, what they may do, and with what credentials. It selects on the `identity.` prefix and covers
228
+ `identity.user.*`, `identity.role.*`, `identity.permission.*`, `identity.service-account.*` and
229
+ `identity.credential.rotate`.
230
+
231
+ This profile governs **business parties and business accounts**: who the organization deals with and
232
+ on what terms. The two never select the same event — `identity.` is not a prefix here, and neither
233
+ `customer.` nor `account.` is a prefix there — so no event is ever governed by both, and the
234
+ requirements can never conflict.
235
+
236
+ | Question | Profile | Example event |
237
+ | ---------------------------------------- | ------------------------------- | ---------------------------- |
238
+ | May this login reach that system? | identity-and-access-management | `identity.role.assign` |
239
+ | Is this party allowed to trade with us? | customer-and-account-management | `customer.restrict` |
240
+ | Disable a person's ability to sign in | identity-and-access-management | `identity.user.disable` |
241
+ | Freeze a business account's transactions | customer-and-account-management | `account.freeze` |
242
+ | Rotate a credential | identity-and-access-management | `identity.credential.rotate` |
243
+ | Close a commercial relationship | customer-and-account-management | `customer.close` |
244
+
245
+ **Do not reuse `identity.*` for business-account lifecycle.** A customer portal login account and the
246
+ settlement account it can operate are different objects with different owners, different lifecycles
247
+ and different reviewers; collapsing them makes "which accounts were frozen?" unanswerable. When a
248
+ single operation does both — offboarding closes the relationship _and_ disables the sign-in — emit two
249
+ events and correlate them with `/request/correlationId`. Each is then governed by exactly one profile
250
+ and both are complete.
251
+
252
+ Where the two profiles chose the same idea they chose the same shape, so that a reviewer moves between
253
+ them without relearning anything: a `type` category on the record (`/metadata/user/type` there,
254
+ `/metadata/customer/customerType` here), a producer-set boolean discriminator driving a conditional
255
+ rule, and namespaced metadata throughout.
256
+
257
+ ### financial-transaction-management — the other place a limit appears
258
+
259
+ That profile governs the movement of value: `financial.transfer.*`, `financial.payment.*`,
260
+ `financial.reversal.*`, `financial.settlement.*` and, notably, `financial.limit.*`. This profile
261
+ governs the _account_ whose limit it is, under `account.limit.*`. The two prefixes are disjoint, so
262
+ again no event is governed by both, but the boundary deserves stating because both concern limits:
263
+
264
+ - **`account.limit.*` is an attribute of the account.** Raising the daily transfer ceiling on
265
+ `account-ref-781` is an act of account administration performed by an operator. It belongs here.
266
+ - **`financial.limit.*` is a control on the payment flow.** A limit consumed, breached or waived while
267
+ a transfer is being processed belongs there, with the transaction it constrained.
268
+
269
+ If in doubt: the event that names an **account** as its resource and changes what that account may do
270
+ from now on is this profile's; the event that names a **transaction** and explains why it was allowed,
271
+ blocked or reversed is the financial profile's.
272
+
273
+ ### document-management
274
+
275
+ [document-management](../document-management/) may govern the contract or onboarding pack attached to
276
+ a customer. Those are `document.*` events and are not selected here. A customer file and a customer
277
+ record are different resources; keep them as different events.
278
+
279
+ ### Other profiles
280
+
281
+ `incident-management`, `deployment-and-change-management`, `secrets-and-key-management`,
282
+ `backup-and-recovery`, `message-broker-management` and `api-and-integration-management` all select
283
+ disjoint namespaces (`incident.`, `problem.`, `corrective-action.`, `deployment.`, `change.request.`,
284
+ `configuration.`, `secret.`, `key.`, `certificate.`, `backup.`, `snapshot.`, `restore.`, `recovery.`,
285
+ `broker.`, `api-key.`, `webhook.`, `integration.`). A fraud incident may cause an account freeze and a
286
+ migration may re-limit accounts, but the events stay separate and are joined by
287
+ `/request/correlationId` or `/change/incidentId` rather than by one profile governing another's
288
+ events.
289
+
290
+ ## Not required, and why
291
+
292
+ - **KYC, sanctions, credit and identity-verification outcomes.** Real and important, but they are
293
+ regulatory and jurisdictional constructs. [profiles/README.md](../README.md) is explicit that a
294
+ profile cites no regulation and encodes no jurisdiction. Producers should model these as their own
295
+ events and put the detail in `metadata` or an extension.
296
+ - **Balances, amounts and currencies.** A balance is business data, not audit interpretation data,
297
+ and requiring it would push monetary values into every audit event. `CUSTOMER-LIMIT-001` requires
298
+ before-and-after values only for the limit that changed, because there the pair _is_ the audit fact.
299
+ - **`/authentication` or MFA on restrictive operations.** The IAM profile requires
300
+ `/authentication/mfa` for privileged role changes because a role change is an access change. A
301
+ customer freeze is performed by an already-authenticated operator in a back-office console, and the
302
+ session's factors are a property of the console, not of the operation. It is recorded in the
303
+ `account-freeze` fixture as good practice and required by no rule.
304
+ - **Trace identifiers.** `/request/traceId` and `/request/spanId` are never required or recommended:
305
+ a conforming producer may have no tracing at all. `/request/correlationId` is recommended, because
306
+ onboarding, review and offboarding are genuinely multi-event workflows.
307
+ - **`/request/protocol`.** These operations arrive over consoles, APIs, batch files and back-office
308
+ terminals. Requiring a protocol would describe the mechanism rather than the operation.
309
+ - **A customer approval event family.** Approval is already modelled by `workflow.approval.*` in
310
+ [workflow-and-approval.md](../../semantic-conventions/workflow-and-approval.md) and by the core
311
+ `/approval` object.
312
+
313
+ ## Fixtures
314
+
315
+ [examples/profiles/customer-and-account-management/](../../examples/profiles/customer-and-account-management/)
316
+ — twelve valid, twelve invalid, three not-applicable.
317
+
318
+ | Kind | Count | Guarantee |
319
+ | ----------------- | ----- | ---------------------------------------------------------------------- |
320
+ | `valid/` | 12 | core-valid, privacy-clean, conforming with **zero** warnings |
321
+ | `invalid/` | 12 | core-valid, privacy-clean, exactly one error at one documented pointer |
322
+ | `not-applicable/` | 3 | core-valid, privacy-clean, governed by no rule (exit 3) |
323
+
324
+ Every enforceable rule has at least one invalid fixture. The mapping from fixture to rule and pointer
325
+ is in the [fixture README](../../examples/profiles/customer-and-account-management/README.md) and is
326
+ asserted by [conformance/tests/profile-customer-and-account-management.test.ts](../../conformance/tests/profile-customer-and-account-management.test.ts).
327
+
328
+ ## Open questions
329
+
330
+ - Should `restrictionScope` be a single string or a list of blocked capabilities? A list is more
331
+ honest for systems that restrict several capabilities at once, but the rule language cannot inspect
332
+ array contents, so a required array would be a presence check on an unexaminable value. v0.1 takes
333
+ the string.
334
+ - Is `deletionScope` the right discriminator for erasure, or should erasure be its own event name
335
+ (`customer.erase`) distinct from record deletion? There is not yet adoption evidence either way.
336
+ - Should account reopening require the reason the original closure was reversed, separately from the
337
+ reason for reopening? Two justifications on one event has no precedent in the core model.
338
+ - Is `onBehalfOf` redundant with `/delegation`? For producers that model delegation it is; for the
339
+ many that record acting-for relationships informally it is the only signal available. The profile
340
+ accepts the redundancy rather than lose the case.
@@ -0,0 +1,237 @@
1
+ {
2
+ "profileVersion": "0.1",
3
+ "name": "customer-and-account-management",
4
+ "version": "0.1",
5
+ "status": "experimental",
6
+ "coreVersions": ["0.1"],
7
+ "title": "Customer and Account Management Profile",
8
+ "description": "Additional conformance requirements for material customer and business-account lifecycle audit events: customer record creation, update, merge, restriction, closure and deletion, and account opening, update, status transition, limit change, freeze, restriction and closure. Every requirement adds to the OpenAuditModel Core Specification; none relaxes it. This profile governs business parties and business accounts; identities, authentication and access rights are governed by the identity-and-access-management profile. High-volume read events such as customer.profile.view, customer.search and account.balance.view are deliberately not governed.",
9
+ "rules": [
10
+ {
11
+ "id": "CUSTOMER-CORE-001",
12
+ "description": "Every governed customer or account operation records the kind of party the record belongs to.",
13
+ "rationale": "Whether a record describes an individual consumer, a business, an intermediary, an internal test party or a partner is the single fact that decides how an operation on it should be reviewed, and it is not derivable from an opaque identifier. Carrying it on the event rather than expecting a join is deliberate: the customer record the join would need may since have been merged, restricted or deleted, and the operations this profile governs are exactly the ones that do that. The party type is a category, not personal data.",
14
+ "severity": "error",
15
+ "events": [
16
+ "customer.create",
17
+ "customer.update",
18
+ "customer.merge",
19
+ "customer.close",
20
+ "customer.delete",
21
+ "customer.restrict",
22
+ "customer.restore",
23
+ "account.open",
24
+ "account.update",
25
+ "account.close",
26
+ "account.reopen",
27
+ "account.freeze",
28
+ "account.unfreeze",
29
+ "account.restrict"
30
+ ],
31
+ "eventPrefixes": ["account.limit.", "account.status."],
32
+ "requiredMetadata": [{ "path": "/customer/customerType", "type": "string" }]
33
+ },
34
+ {
35
+ "id": "CUSTOMER-CORE-002",
36
+ "description": "A governed customer or account operation should record why it happened, how sensitive the record is, and how it correlates with the wider workflow it belongs to.",
37
+ "rationale": "Onboarding, periodic review and offboarding are multi-step workflows whose steps are emitted by different services, so a correlation identifier is what turns a scattering of events back into one case. These are recommended rather than required because a conforming producer may have no correlation mechanism at all, because a classification scheme is an organization's decision rather than this profile's, and because a missing justification on a routine update should prompt a question rather than fail a build. Where a justification is genuinely load-bearing, a separate rule requires it.",
38
+ "severity": "warning",
39
+ "events": [
40
+ "customer.create",
41
+ "customer.update",
42
+ "customer.merge",
43
+ "customer.close",
44
+ "customer.delete",
45
+ "customer.restrict",
46
+ "customer.restore",
47
+ "account.open",
48
+ "account.update",
49
+ "account.close",
50
+ "account.reopen",
51
+ "account.freeze",
52
+ "account.unfreeze",
53
+ "account.restrict"
54
+ ],
55
+ "eventPrefixes": ["account.limit.", "account.status."],
56
+ "recommendedPaths": ["/reason", "/request/correlationId", "/resource/classification"]
57
+ },
58
+ {
59
+ "id": "CUSTOMER-ACCOUNT-001",
60
+ "description": "An account operation records the kind of account and the customer the account belongs to.",
61
+ "rationale": "An account event that does not name its owning customer cannot be joined to the party it affects, so a reviewer asking which parties were frozen last quarter has to reconstruct ownership from a system whose current state may no longer match what was true at the time. The account kind matters for the same reason the party type does: freezing a dormant internal settlement account and freezing a customer's only operating account are different acts that otherwise look identical in the trail.",
62
+ "severity": "error",
63
+ "events": [
64
+ "account.open",
65
+ "account.update",
66
+ "account.close",
67
+ "account.reopen",
68
+ "account.freeze",
69
+ "account.unfreeze",
70
+ "account.restrict"
71
+ ],
72
+ "eventPrefixes": ["account.limit.", "account.status."],
73
+ "requiredPaths": ["/resource/ownerId"],
74
+ "requiredMetadata": [{ "path": "/customer/accountType", "type": "string" }]
75
+ },
76
+ {
77
+ "id": "CUSTOMER-CONTROL-001",
78
+ "description": "An operation that restricts, closes, restores, merges, deletes or re-limits a customer or an account records the authorization decision that permitted it and why it was performed.",
79
+ "rationale": "These are the operations that take a customer's ability to transact away or give it back, and they are the ones a complaint, a dispute or a supervisory question later turns on. Without a recorded decision nothing distinguishes an action policy allowed from one that bypassed policy; without a stated justification the trail cannot say whether a freeze was a fraud control, a billing dispute or a mistake, and the person who knew will not be reachable when the question is asked. Approval is recommended rather than required because many organizations legitimately let a single trained operator freeze an account on suspicion; a rule that forbade it would be switched off rather than met.",
80
+ "severity": "error",
81
+ "events": [
82
+ "customer.merge",
83
+ "customer.close",
84
+ "customer.delete",
85
+ "customer.restrict",
86
+ "customer.restore",
87
+ "account.close",
88
+ "account.reopen",
89
+ "account.freeze",
90
+ "account.unfreeze",
91
+ "account.restrict"
92
+ ],
93
+ "eventPrefixes": ["account.limit.", "account.status."],
94
+ "requiredPaths": ["/authorization", "/reason"],
95
+ "recommendedPaths": ["/approval"]
96
+ },
97
+ {
98
+ "id": "CUSTOMER-UPDATE-001",
99
+ "description": "An update to a customer record or an account records what changed.",
100
+ "rationale": "An update event without a change context records only that something was altered, which is the least useful thing an audit trail can say about a mutation. The profile requires the change object and nothing about its shape: the core already accepts changed field names, sanitized state, hashes or references, and a customer record is exactly the kind of data where recording field names instead of values is the right answer.",
101
+ "severity": "error",
102
+ "events": ["customer.update", "account.update"],
103
+ "requiredPaths": ["/change"],
104
+ "recommendedPaths": ["/change/changedFields"]
105
+ },
106
+ {
107
+ "id": "CUSTOMER-STATE-001",
108
+ "description": "A status transition on a customer or an account records the resulting status and the transition itself.",
109
+ "rationale": "A closure, freeze, restriction or reinstatement is meaningful only as a move between two states. An event that names the action but not the resulting status forces every consumer to model the producer's state machine correctly in order to know where the record ended up, and consumers get that wrong. Recording the resulting status makes the trail replayable: the state after any transition can be read directly instead of inferred from the event name.",
110
+ "severity": "error",
111
+ "events": [
112
+ "customer.close",
113
+ "customer.restrict",
114
+ "customer.restore",
115
+ "account.close",
116
+ "account.reopen",
117
+ "account.freeze",
118
+ "account.unfreeze",
119
+ "account.restrict"
120
+ ],
121
+ "eventPrefixes": ["account.status."],
122
+ "requiredPaths": ["/change"],
123
+ "requiredMetadata": [{ "path": "/customer/status", "type": "string" }]
124
+ },
125
+ {
126
+ "id": "CUSTOMER-RESTRICT-001",
127
+ "description": "A restriction or freeze records what the restriction actually prevents.",
128
+ "rationale": "Restriction is not one control. Blocking outgoing payments, blocking new orders, blocking sign-in and blocking the whole relationship have very different consequences for the customer, and they are routinely emitted under the same event name. Without the scope, nobody can later tell whether a restriction was proportionate, nor lift it safely, and a customer complaint about being cut off cannot be answered from the trail at all. The review that triggered the restriction is recommended rather than required, because a restriction may equally be an immediate operational decision with no review behind it.",
129
+ "severity": "error",
130
+ "events": ["customer.restrict", "account.restrict", "account.freeze"],
131
+ "requiredMetadata": [{ "path": "/customer/restrictionScope", "type": "string" }],
132
+ "recommendedPaths": ["/approval", "/metadata/customer/reviewId"]
133
+ },
134
+ {
135
+ "id": "CUSTOMER-LIMIT-001",
136
+ "description": "A limit change records which limit changed and both the prior and the resulting value.",
137
+ "rationale": "A limit change is only reviewable as a pair of numbers: raising a transfer ceiling from a small figure to a very large one and lowering it are the same event name with opposite risk. Recording only the new value hides the direction and the magnitude of the change. The profile requires which limit was touched because an account carries several, and it requires both states rather than a computed delta because the rule language compares nothing: the engine can check that both values were recorded and must leave the comparison, and any threshold, to the reviewer.",
138
+ "severity": "error",
139
+ "eventPrefixes": ["account.limit."],
140
+ "requiredPaths": ["/change/before", "/change/after"],
141
+ "requiredMetadata": [{ "path": "/customer/limitType", "type": "string" }]
142
+ },
143
+ {
144
+ "id": "CUSTOMER-MERGE-001",
145
+ "description": "A customer merge names the record that was retired and the records the merge related.",
146
+ "rationale": "A merge collapses two parties into one and is the hardest customer operation to undo or explain. Every identifier that pointed at the retired record becomes unresolvable at the moment of the merge, so an event that does not name that record breaks the trail of everything that referenced it, and the surviving record appears to acquire history from nowhere. Requiring the related resources as well keeps both sides of the merge on the event, so that a reviewer never has to guess which direction it ran.",
147
+ "severity": "error",
148
+ "events": ["customer.merge"],
149
+ "requiredPaths": ["/relatedResources"],
150
+ "requiredMetadata": [{ "path": "/customer/mergedFromId", "type": "string" }]
151
+ },
152
+ {
153
+ "id": "CUSTOMER-DELETE-001",
154
+ "description": "Deleting a customer record records how far the deletion reached.",
155
+ "rationale": "Deletion is the operation an audit trail is least able to reconstruct afterwards, because the evidence is precisely what was removed. Erasing a party's personal data while retaining the transactional record is a different act from removing the record entirely, and the two are habitually emitted under one name. Recording the scope is what lets a later reader answer what still exists, and it is the only honest way to distinguish an erasure that was performed from one that was claimed. The privacy context and an approval are recommended rather than required because neither is universal.",
156
+ "severity": "error",
157
+ "events": ["customer.delete"],
158
+ "requiredMetadata": [{ "path": "/customer/deletionScope", "type": "string" }],
159
+ "recommendedPaths": ["/approval", "/privacy"]
160
+ },
161
+ {
162
+ "id": "CUSTOMER-APPROVAL-001",
163
+ "description": "When the producer declares that an operation required approval, the state of that approval is recorded.",
164
+ "rationale": "This profile does not decide which customer or account operations need a second person, because that varies by organization, product and jurisdiction and no vendor-neutral answer exists. It enforces the consequence of the producer's own declaration instead: an operation the system itself said required approval, carried out with no approval state on the event, is either an unapproved change or an unrecorded one, and the trail cannot tell which. The rule requires the status rather than the approval object so that the finding points at the fact that is missing rather than at its container.",
165
+ "severity": "error",
166
+ "events": [
167
+ "customer.create",
168
+ "customer.update",
169
+ "customer.merge",
170
+ "customer.close",
171
+ "customer.delete",
172
+ "customer.restrict",
173
+ "customer.restore",
174
+ "account.open",
175
+ "account.update",
176
+ "account.close",
177
+ "account.reopen",
178
+ "account.freeze",
179
+ "account.unfreeze",
180
+ "account.restrict"
181
+ ],
182
+ "eventPrefixes": ["account.limit.", "account.status."],
183
+ "when": { "path": "/metadata/customer/approvalRequired", "equals": true },
184
+ "requiredPaths": ["/approval/status"]
185
+ },
186
+ {
187
+ "id": "CUSTOMER-SUBJECT-001",
188
+ "description": "An operation the producer declares was carried out on behalf of another party names that party.",
189
+ "rationale": "Customer and account operations are frequently performed by an agent, a branch operator or a support engineer for someone else, and in that case the actor is not the party the operation was about. The core already requires a subject when a delegation object is present, but a great many systems record acting-for relationships without modelling delegation at all, and then the trail attributes a closure to the operator who typed it. This rule fires only on the producer's own declaration, so the profile does not have to decide what acting on behalf of means in a given deployment.",
190
+ "severity": "error",
191
+ "events": [
192
+ "customer.create",
193
+ "customer.update",
194
+ "customer.merge",
195
+ "customer.close",
196
+ "customer.delete",
197
+ "customer.restrict",
198
+ "customer.restore",
199
+ "account.open",
200
+ "account.update",
201
+ "account.close",
202
+ "account.reopen",
203
+ "account.freeze",
204
+ "account.unfreeze",
205
+ "account.restrict"
206
+ ],
207
+ "eventPrefixes": ["account.limit.", "account.status."],
208
+ "when": { "path": "/metadata/customer/onBehalfOf", "equals": true },
209
+ "requiredPaths": ["/subject"]
210
+ },
211
+ {
212
+ "id": "CUSTOMER-OVERRIDE-001",
213
+ "description": "An operation the producer declares was a manual override records the authorization decision it was recorded against and why the override was made.",
214
+ "rationale": "A manual override is a person setting aside what the system would otherwise have done, and it is the single most reviewable act in customer administration: overrides are where fraud, favouritism and honest emergency handling all look alike. Without a justification the trail cannot separate them, and without the recorded decision it cannot even show what was overridden. The rule extends this to ordinary updates and openings, which no other rule in this profile requires a justification for, because an overridden routine change is not a routine change.",
215
+ "severity": "error",
216
+ "events": [
217
+ "customer.create",
218
+ "customer.update",
219
+ "customer.merge",
220
+ "customer.close",
221
+ "customer.delete",
222
+ "customer.restrict",
223
+ "customer.restore",
224
+ "account.open",
225
+ "account.update",
226
+ "account.close",
227
+ "account.reopen",
228
+ "account.freeze",
229
+ "account.unfreeze",
230
+ "account.restrict"
231
+ ],
232
+ "eventPrefixes": ["account.limit.", "account.status."],
233
+ "when": { "path": "/metadata/customer/manualOverride", "equals": true },
234
+ "requiredPaths": ["/authorization", "/reason"]
235
+ }
236
+ ]
237
+ }