@openauditmodel/cli 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +215 -0
- package/README.md +647 -0
- package/dist/conformance/src/cli.d.ts +20 -0
- package/dist/conformance/src/cli.js +560 -0
- package/dist/conformance/src/cli.js.map +1 -0
- package/dist/conformance/src/format-errors.d.ts +20 -0
- package/dist/conformance/src/format-errors.js +116 -0
- package/dist/conformance/src/format-errors.js.map +1 -0
- package/dist/conformance/src/integrity/canonicalize.d.ts +25 -0
- package/dist/conformance/src/integrity/canonicalize.js +100 -0
- package/dist/conformance/src/integrity/canonicalize.js.map +1 -0
- package/dist/conformance/src/integrity/digest.d.ts +54 -0
- package/dist/conformance/src/integrity/digest.js +126 -0
- package/dist/conformance/src/integrity/digest.js.map +1 -0
- package/dist/conformance/src/integrity/types.d.ts +86 -0
- package/dist/conformance/src/integrity/types.js +36 -0
- package/dist/conformance/src/integrity/types.js.map +1 -0
- package/dist/conformance/src/integrity/verify-chain.d.ts +23 -0
- package/dist/conformance/src/integrity/verify-chain.js +235 -0
- package/dist/conformance/src/integrity/verify-chain.js.map +1 -0
- package/dist/conformance/src/integrity/verify-event.d.ts +29 -0
- package/dist/conformance/src/integrity/verify-event.js +140 -0
- package/dist/conformance/src/integrity/verify-event.js.map +1 -0
- package/dist/conformance/src/privacy/entropy.d.ts +14 -0
- package/dist/conformance/src/privacy/entropy.js +86 -0
- package/dist/conformance/src/privacy/entropy.js.map +1 -0
- package/dist/conformance/src/privacy/field-names.d.ts +36 -0
- package/dist/conformance/src/privacy/field-names.js +121 -0
- package/dist/conformance/src/privacy/field-names.js.map +1 -0
- package/dist/conformance/src/privacy/lint-event.d.ts +18 -0
- package/dist/conformance/src/privacy/lint-event.js +272 -0
- package/dist/conformance/src/privacy/lint-event.js.map +1 -0
- package/dist/conformance/src/privacy/rules.d.ts +44 -0
- package/dist/conformance/src/privacy/rules.js +161 -0
- package/dist/conformance/src/privacy/rules.js.map +1 -0
- package/dist/conformance/src/privacy/safe-formats.d.ts +32 -0
- package/dist/conformance/src/privacy/safe-formats.js +118 -0
- package/dist/conformance/src/privacy/safe-formats.js.map +1 -0
- package/dist/conformance/src/privacy/size-analysis.d.ts +36 -0
- package/dist/conformance/src/privacy/size-analysis.js +87 -0
- package/dist/conformance/src/privacy/size-analysis.js.map +1 -0
- package/dist/conformance/src/privacy/token-patterns.d.ts +24 -0
- package/dist/conformance/src/privacy/token-patterns.js +126 -0
- package/dist/conformance/src/privacy/token-patterns.js.map +1 -0
- package/dist/conformance/src/privacy/traverse.d.ts +33 -0
- package/dist/conformance/src/privacy/traverse.js +61 -0
- package/dist/conformance/src/privacy/traverse.js.map +1 -0
- package/dist/conformance/src/privacy/types.d.ts +58 -0
- package/dist/conformance/src/privacy/types.js +32 -0
- package/dist/conformance/src/privacy/types.js.map +1 -0
- package/dist/conformance/src/privacy/url-analysis.d.ts +40 -0
- package/dist/conformance/src/privacy/url-analysis.js +148 -0
- package/dist/conformance/src/privacy/url-analysis.js.map +1 -0
- package/dist/conformance/src/profiles/check-profile.d.ts +22 -0
- package/dist/conformance/src/profiles/check-profile.js +72 -0
- package/dist/conformance/src/profiles/check-profile.js.map +1 -0
- package/dist/conformance/src/profiles/evaluate-rule.d.ts +27 -0
- package/dist/conformance/src/profiles/evaluate-rule.js +87 -0
- package/dist/conformance/src/profiles/evaluate-rule.js.map +1 -0
- package/dist/conformance/src/profiles/load-profile.d.ts +30 -0
- package/dist/conformance/src/profiles/load-profile.js +95 -0
- package/dist/conformance/src/profiles/load-profile.js.map +1 -0
- package/dist/conformance/src/profiles/resolve-pointer.d.ts +44 -0
- package/dist/conformance/src/profiles/resolve-pointer.js +123 -0
- package/dist/conformance/src/profiles/resolve-pointer.js.map +1 -0
- package/dist/conformance/src/profiles/select-rules.d.ts +13 -0
- package/dist/conformance/src/profiles/select-rules.js +42 -0
- package/dist/conformance/src/profiles/select-rules.js.map +1 -0
- package/dist/conformance/src/profiles/types.d.ts +101 -0
- package/dist/conformance/src/profiles/types.js +31 -0
- package/dist/conformance/src/profiles/types.js.map +1 -0
- package/dist/conformance/src/profiles/validate-profile-definition.d.ts +12 -0
- package/dist/conformance/src/profiles/validate-profile-definition.js +47 -0
- package/dist/conformance/src/profiles/validate-profile-definition.js.map +1 -0
- package/dist/conformance/src/sources.d.ts +53 -0
- package/dist/conformance/src/sources.js +153 -0
- package/dist/conformance/src/sources.js.map +1 -0
- package/dist/conformance/src/validate-core.d.ts +30 -0
- package/dist/conformance/src/validate-core.js +62 -0
- package/dist/conformance/src/validate-core.js.map +1 -0
- package/dist/conformance/src/validate.d.ts +34 -0
- package/dist/conformance/src/validate.js +62 -0
- package/dist/conformance/src/validate.js.map +1 -0
- package/dist/conformance/src/validator-interface.d.ts +35 -0
- package/dist/conformance/src/validator-interface.js +19 -0
- package/dist/conformance/src/validator-interface.js.map +1 -0
- package/package.json +85 -0
- package/profiles/README.md +201 -0
- package/profiles/api-and-integration-management/README.md +271 -0
- package/profiles/api-and-integration-management/profile.json +258 -0
- package/profiles/backup-and-recovery/README.md +318 -0
- package/profiles/backup-and-recovery/profile.json +178 -0
- package/profiles/customer-and-account-management/README.md +340 -0
- package/profiles/customer-and-account-management/profile.json +237 -0
- package/profiles/deployment-and-change-management/README.md +359 -0
- package/profiles/deployment-and-change-management/profile.json +298 -0
- package/profiles/document-management/README.md +120 -0
- package/profiles/document-management/profile.json +170 -0
- package/profiles/financial-transaction-management/README.md +339 -0
- package/profiles/financial-transaction-management/profile.json +247 -0
- package/profiles/identity-and-access-management/README.md +112 -0
- package/profiles/identity-and-access-management/profile.json +120 -0
- package/profiles/incident-management/README.md +338 -0
- package/profiles/incident-management/profile.json +256 -0
- package/profiles/message-broker-management/README.md +344 -0
- package/profiles/message-broker-management/profile.json +399 -0
- package/profiles/profile-definition.schema.json +213 -0
- package/profiles/secrets-and-key-management/README.md +331 -0
- package/profiles/secrets-and-key-management/profile.json +219 -0
- package/schemas/v0.1/audit-event.schema.json +911 -0
- package/semantic-conventions/README.md +52 -0
- package/semantic-conventions/authentication.md +111 -0
- package/semantic-conventions/configuration-and-change.md +122 -0
- package/semantic-conventions/correlation-and-tracing.md +387 -0
- package/semantic-conventions/data-access.md +115 -0
- package/semantic-conventions/event-naming.md +114 -0
- package/semantic-conventions/identity-and-access.md +112 -0
- package/semantic-conventions/privileged-operations.md +110 -0
- package/semantic-conventions/workflow-and-approval.md +109 -0
- package/specification/actor-model.md +135 -0
- package/specification/approval-and-delegation.md +150 -0
- package/specification/authentication.md +116 -0
- package/specification/authorization.md +117 -0
- package/specification/change-model.md +134 -0
- package/specification/delivery.md +136 -0
- package/specification/design-principles.md +137 -0
- package/specification/event-model.md +387 -0
- package/specification/evidence-model.md +109 -0
- package/specification/extension-model.md +159 -0
- package/specification/integrity.md +314 -0
- package/specification/overview.md +172 -0
- package/specification/privacy.md +386 -0
- package/specification/resource-model.md +116 -0
- package/specification/terminology.md +135 -0
|
@@ -0,0 +1,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
|
+
}
|