@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,339 @@
1
+ # Financial Transaction Management Profile
2
+
3
+ **Status: Experimental. Implemented in v0.1, 12 rules (11 enforceable, 1 advisory).**
4
+
5
+ The enforceable rules are in [profile.json](profile.json).
6
+
7
+ ```bash
8
+ auditmodel check-profile examples/profiles/financial-transaction-management/valid --profile financial-transaction-management
9
+ ```
10
+
11
+ ## Purpose
12
+
13
+ An audit trail over money has to answer four questions long after everyone involved has moved on:
14
+ **who moved what, from where to where, on whose authority, and does it still reconcile.** The core
15
+ model carries the actor, the resource and the outcome. It has nowhere to put the amount, the currency,
16
+ the direction, the transaction reference or the business status of the movement, and no way to insist
17
+ that the operations which unwind value are justified. This profile adds exactly those requirements and
18
+ nothing else.
19
+
20
+ The profile is about **audit fields**, not about money. It defines no accounting model, mandates no
21
+ control thresholds, and takes no position on what any organization's approval limits should be.
22
+
23
+ ## Scope
24
+
25
+ Material financial and monetary operations, in any application that performs them:
26
+
27
+ - **Payment platforms** — authorization, capture, refund, dispute.
28
+ - **Marketplaces** — seller balances, scheduled payouts, buyer refunds.
29
+ - **Brokerages** — client cash movement, withdrawal under dual control, limit administration.
30
+ - **Treasury and internal financial applications** — inter-account transfers, net settlement,
31
+ reconciliation and adjustment.
32
+
33
+ Nothing here is specific to banking, to a payment scheme, to a card network, to a ledger technology or
34
+ to a jurisdiction. The profile never names a currency, a scheme, a message format or a regulation.
35
+
36
+ ## Event families
37
+
38
+ | Event family | Governed | Requirement group |
39
+ | ---------------------------- | -------- | ----------------------------- |
40
+ | `financial.transfer.*` | yes | movement, two-sided |
41
+ | `financial.payment.*` | yes | movement |
42
+ | `financial.withdrawal.*` | yes | movement |
43
+ | `financial.deposit.*` | yes | movement |
44
+ | `financial.refund.*` | yes | movement, justified |
45
+ | `financial.reversal.*` | yes | movement, justified, unwinds |
46
+ | `financial.payout.*` | yes | movement |
47
+ | `financial.settlement.*` | yes | movement, two-sided |
48
+ | `financial.chargeback.*` | yes | movement, justified, unwinds |
49
+ | `financial.reconciliation.*` | yes | reconciliation |
50
+ | `financial.limit.*` | yes | preventive-control change |
51
+ | `financial.balance.*` | **no** | not governed — see exclusions |
52
+ | `financial.quote.*` | **no** | not governed — see exclusions |
53
+ | `financial.report.*` | **no** | not governed — see exclusions |
54
+
55
+ The nine **movement** families are the ones where value actually changes hands. They carry the
56
+ transaction requirements (`FIN-TXN-001`, `FIN-TXN-002`). Reconciliation and limit events are governed
57
+ but are not movements, so they are not required to carry a direction or a transaction reference they
58
+ do not have.
59
+
60
+ ## Explicit exclusions
61
+
62
+ **Non-mutating financial reads are deliberately ungoverned**: balance enquiries, price and rate quotes,
63
+ statement views and routine reporting. A retail application emits a balance-view event every time
64
+ anyone opens the app. Requiring an authorization decision, an amount, a direction and a correlation
65
+ identifier on each of them would put the profile's heaviest requirements on the highest-volume event in
66
+ the system, in exchange for almost no review value — and the requirement would be switched off rather
67
+ than met.
68
+
69
+ The exclusion is **structural, not a matter of discipline**: no selector in this profile uses a bare
70
+ `financial.` prefix, so `financial.balance.view`, `financial.quote.create` and
71
+ `financial.report.generate` match no rule at all and `check-profile` reports them as not applicable.
72
+ A test asserts it, because widening one prefix later would silently start governing every balance
73
+ lookup in a deployment.
74
+
75
+ Also out of scope, and deliberately so:
76
+
77
+ - **Order, trade and position events.** They are commercial events that may or may not result in money
78
+ moving. Governing them would make this a trading profile.
79
+ - **Fee and interest calculation.** A computation is not a movement; the resulting posting is, and the
80
+ posting is governed.
81
+ - **Ledger reads and exports.** Covered by
82
+ [data-access.md](../../semantic-conventions/data-access.md).
83
+
84
+ Excluded does not mean unaudited. Every excluded event is still a conforming OpenAuditModel event.
85
+
86
+ ## Rules
87
+
88
+ | Rule | Applies to | Requires |
89
+ | ------------------ | ------------------------------------------------------- | ---------------------------------------------------------------------------- |
90
+ | `FIN-CORE-001` | every governed event | `/authorization` |
91
+ | `FIN-CORE-002` | every governed event | _recommends_ `/reason`, `/authentication`, `/relatedResources` |
92
+ | `FIN-TXN-001` | the nine movement families | `/request/correlationId`, `transactionId`, `amount` (number), `currency` |
93
+ | `FIN-TXN-002` | the nine movement families | `direction`, `status` |
94
+ | `FIN-LINK-001` | `financial.transfer.*`, `financial.settlement.*` | `/relatedResources` |
95
+ | `FIN-REASON-001` | reversals, chargebacks, refunds, rejections, cancels | `/reason` |
96
+ | `FIN-REVERSAL-001` | `financial.reversal.*`, `financial.chargeback.*` | `originalTransactionId`; recommends `/approval` |
97
+ | `FIN-APPROVAL-001` | every governed event, when `approvalRequired` is `true` | `/approval/status`; recommends `/approval/workflowId`, `/approval/approvers` |
98
+ | `FIN-MANUAL-001` | every governed event, when `manual` is `true` | `/reason`, `/change`; recommends `/approval` |
99
+ | `FIN-RECON-001` | `financial.reconciliation.*` | `reconciliationId`; recommends `/request/correlationId` |
100
+ | `FIN-RECON-002` | `financial.reconciliation.adjust` | `/change`, `/reason`, `amount` (number), `currency`; recommends `/approval` |
101
+ | `FIN-LIMIT-001` | `financial.limit.*` | `/change`, `/reason`, `limitType`; recommends `/approval` |
102
+
103
+ Unqualified names in the "Requires" column are metadata fields under `/metadata/financial/`. Each
104
+ rule's full text and rationale is in [profile.json](profile.json).
105
+
106
+ The "Applies to" column is a summary; the selectors are exact. `FIN-REASON-001` in particular selects
107
+ the whole of `financial.reversal.*` and `financial.chargeback.*`, but only the named events
108
+ `financial.payment.reject`, `financial.payment.cancel`, `financial.transfer.cancel`,
109
+ `financial.payout.cancel`, `financial.settlement.cancel`, `financial.withdrawal.reject` and
110
+ `financial.refund.create` — **not** every event in the refund, payment or payout families. A refund
111
+ that is later settled or cancelled is governed by the movement rules, not by this one.
112
+
113
+ ## Metadata namespace
114
+
115
+ Every metadata requirement in this profile lives under **`/metadata/financial/`**. Nothing is required
116
+ at the root of `metadata`: a key like `/metadata/amount` or `/metadata/status` would collide with any
117
+ other profile that governs the same event, and `status` in particular means something different to a
118
+ payment, an incident and a deployment.
119
+
120
+ | Field | Type | Required for | Meaning |
121
+ | ----------------------- | ------- | ------------------------------ | ---------------------------------------------------------- |
122
+ | `transactionId` | string | movement families | The transaction this event is one step of |
123
+ | `amount` | number | movement families, adjustments | Magnitude of the movement, in the unit the producer states |
124
+ | `currency` | string | movement families, adjustments | Unit `amount` is expressed in |
125
+ | `direction` | string | movement families | Which way value moved relative to `/resource` |
126
+ | `status` | string | movement families | Business state of the transaction after this event |
127
+ | `originalTransactionId` | string | reversals, chargebacks | The transaction this event acts against |
128
+ | `reconciliationId` | string | reconciliation | The reconciliation run this event belongs to |
129
+ | `limitType` | string | limit changes | Which control changed |
130
+ | `approvalRequired` | boolean | never required | Producer's declaration that this operation needed approval |
131
+ | `manual` | boolean | never required | Producer's declaration that this was a manual intervention |
132
+
133
+ **Vocabularies are open.** The profile requires `direction` and `status` to be present and to be
134
+ strings. It does not enumerate their values, because `debit`/`credit`, `inbound`/`outbound` and
135
+ `pending`/`settled`/`reversed` are all in honest use and a closed list would simply exclude a
136
+ conforming producer. The same applies to `currency`: the profile never enumerates currencies, so a
137
+ minor-unit ledger, a multi-currency book and a system that settles in something other than a national
138
+ currency are all expressible.
139
+
140
+ ## Conditional-policy fields
141
+
142
+ Two fields are **declarations by the producer**, never requirements in themselves:
143
+
144
+ - **`approvalRequired`** — "this deployment's policy said this operation needed approval."
145
+ - **`manual`** — "this event came from the manual code path, not the automated one."
146
+
147
+ Both are used only as `when` conditions. Neither is required by any rule, and setting either to
148
+ `false` — or omitting it — is fully conforming. The reason is that only the producer knows which of
149
+ its code paths is the manual one, and only the operator knows what their approval policy is. The
150
+ profile does not guess; it enforces the consequence of whatever the producer declared.
151
+
152
+ This is the same shape as the IAM profile's `privileged` flag and the document profile's
153
+ `recipientType`, and it is the only conditional mechanism v0.1 offers: **one path compared for
154
+ equality against one scalar**. When the path is absent the condition does not hold and the rule
155
+ contributes nothing.
156
+
157
+ ## Approval model
158
+
159
+ **Approval is never required by default, and that is a deliberate design decision.**
160
+
161
+ A marketplace releases thousands of seller payouts an hour with no human in the loop and is right to.
162
+ A low-value refund issued by a support agent under a standing policy needs no second signature. A
163
+ scheduled net settlement runs at a fixed time against a fixed instruction. A profile that demanded
164
+ `/approval` on every monetary operation would describe one treasury department's process, would be
165
+ false for most of the domain, and would be disabled rather than adopted.
166
+
167
+ What the profile requires instead is **consistency with the producer's own declaration**:
168
+
169
+ 1. `/approval` is _recommended_ on the four operations where its absence is worth a question, and
170
+ nowhere else: `FIN-REVERSAL-001` (reversals and chargebacks), `FIN-MANUAL-001` (manual
171
+ interventions), `FIN-RECON-002` (reconciliation adjustments) and `FIN-LIMIT-001` (limit changes).
172
+ `FIN-CORE-002` deliberately does not name it, so a routine capture, transfer or scheduled payout
173
+ is never asked for an approval it does not have and produces no warning for lacking one.
174
+ 2. When the producer sets `/metadata/financial/approvalRequired` to `true`, `FIN-APPROVAL-001`
175
+ requires `/approval/status` — the decision, not merely the existence of a workflow.
176
+
177
+ `/approval/status` is required rather than `/approval` because an approval object that names only a
178
+ workflow records that a control exists without recording whether it was satisfied. Note that the
179
+ profile requires the status to be **present**, not to equal `approved`: an event recording a
180
+ `rejected` or `pending` approval is a legitimate and important audit event, and the rule language
181
+ cannot express "approved _when the outcome was success_" because it has no boolean combination.
182
+
183
+ ## Privacy considerations
184
+
185
+ Financial events are the events most likely to carry material that must never enter an audit trail.
186
+ The core model already forbids it; this profile adds emphasis because the temptation here is specific
187
+ and strong.
188
+
189
+ **Never record**, in any field, under any name:
190
+
191
+ - Full account numbers, card numbers, IBANs or any other payment instrument identifier that could be
192
+ used to initiate a movement.
193
+ - Card verification values, PINs, one-time codes, banking credentials or scheme API keys.
194
+ - Full payment instructions or remittance payloads copied wholesale into `metadata`, `change.before`
195
+ or `change.after`.
196
+ - Counterparty personal data — names, addresses, dates of birth — that the audit purpose does not
197
+ require.
198
+
199
+ **Record instead**: opaque, stable references issued by the producer, such as `account-ref-781`,
200
+ `counterparty-ref-44` or `txn-2026-0314-0091`. Every fixture in this profile uses them, and a
201
+ reviewer loses nothing: the reference resolves inside the producing system, under that system's own
202
+ access controls, for the people entitled to resolve it.
203
+
204
+ `amount` and `currency` are required because they are the audit facts. A **balance** is not, and no
205
+ rule in this profile asks for one: an amount describes the operation, a balance describes the
206
+ customer. Where a balance does appear — `reconciliation-adjust.json` records a ledger balance in
207
+ `change.before` and `change.after` — it is there because the operation moved that specific value and
208
+ `FIN-RECON-002` requires the transition to be recorded. `/change` is a requirement to record **what
209
+ this operation altered**, never a licence to snapshot an account.
210
+
211
+ `auditmodel lint-privacy` runs over every fixture in this profile and must report zero findings; a
212
+ test enforces it. Note that the linter checks values, not judgement — a profile-conforming event can
213
+ still disclose something it should not, and a clean lint is not a review.
214
+
215
+ ## Known rule-language limitations
216
+
217
+ The v0.1 rule language checks presence, JSON type and scalar equality. These are the things this
218
+ profile would assert and cannot:
219
+
220
+ - **Amount is type-checked, never range-checked.** `FIN-TXN-001` requires `/metadata/financial/amount`
221
+ to be a `number`. The engine cannot require it to be positive, non-zero, finite, within a limit, or
222
+ consistent with `direction`. `integer` is a subset of `number`, so a minor-unit ledger recording
223
+ `14850` and a decimal ledger recording `148.50` both satisfy the rule — which is intended, since
224
+ the profile does not dictate the unit. **A conforming event may carry a nonsensical amount**, and a
225
+ producer that wants a range check needs one outside the profile.
226
+ - **No cross-field comparison.** The profile cannot require that a reversal's amount equals its
227
+ original's, that `direction` agrees with the sign of `amount`, that `status` agrees with
228
+ `event.outcome`, or that `approval.receivedApprovals` reaches `requiredApprovals`.
229
+ - **No boolean combination.** "Approved _when_ the outcome was success" and "reason required _unless_
230
+ the actor is a service" are both inexpressible. Every rule has at most one condition.
231
+ - **No array-content predicates.** `FIN-LINK-001` requires `/relatedResources` to be non-empty. It
232
+ cannot require that one of the entries is an account, or that a transfer names exactly two sides.
233
+ - **No value vocabularies.** `direction`, `status` and `currency` are required to be strings and
234
+ nothing more. A typo is conforming.
235
+ - **No numeric or temporal ordering.** The profile cannot require `approvedAt` to precede `time`, nor
236
+ a limit's `after` value to be compared with its `before` value.
237
+ - **`when` cannot be negated.** There is no way to write "require X when `manual` is _not_ true".
238
+
239
+ Where a requirement could not be expressed, it is stated here as guidance and enforced nowhere. The
240
+ profile does not pretend otherwise.
241
+
242
+ ## Cross-profile overlaps
243
+
244
+ - **Identity and access management.** Changing who may move money is an identity operation
245
+ (`identity.role.*`, `identity.permission.*`) and is governed there, not here. This profile governs
246
+ the movement, not the entitlement. An event is never governed by both, because the event families
247
+ do not intersect.
248
+ - **Document management.** A settlement statement or a reconciliation report stored as a document is a
249
+ `document.*` event. The financial event references it through `/relatedResources` or
250
+ `/reason/reference`; it does not embed it.
251
+ - **Deployment and change management.** `/change` is used by several profiles. Here it always means
252
+ _what this operation altered in the financial record_ — a limit, a posted amount, a ledger balance —
253
+ never a code or configuration release. `change.deploymentId` remains available for correlation and
254
+ is never required by this profile.
255
+ - **Incident management.** A financial break usually becomes an incident. The link belongs in
256
+ `change.incidentId` or `reason.reference`; this profile requires neither, because a correction made
257
+ before anyone raised an incident is still a correction.
258
+ - **Metadata namespacing** is what keeps these apart in practice. Every field this profile requires is
259
+ under `/metadata/financial/`, so an event governed by two profiles never has two meanings for one
260
+ key.
261
+
262
+ ## Not required, and why
263
+
264
+ - **`/event/error` on failures.** The core schema already requires an error descriptor whenever
265
+ `outcome` is `failure`, and `errorDescriptor` already requires a `code`. A profile rule restating
266
+ that would add a second place for the requirement to drift.
267
+ - **Trace identifiers.** `/request/traceId` and `/request/spanId` are never required. Distributed
268
+ tracing may not exist in a batch settlement runner or a mainframe-adjacent ledger, and an audit
269
+ requirement that assumes an observability stack excludes conforming producers.
270
+ `/request/correlationId` **is** required for movement families, because correlating the steps of one
271
+ transaction is a business need that exists whether or not tracing does.
272
+ - **`/request/protocol`, `/request/ipAddress`, `/request/userAgent`.** A payout released by a
273
+ scheduler has no protocol worth recording and no client at all.
274
+ - **`/authentication`.** Recommended by `FIN-CORE-002`, never required: batch and scheduled
275
+ operations legitimately have no interactive authentication context. **`/authentication/mfa`** is
276
+ neither required nor recommended by any rule, and no rule pins it to `true`. A deployment that
277
+ wants step-up authentication on high-value movement can express it with `requiredValues` in a local
278
+ profile once it has a producer-set discriminator to condition on.
279
+ - **A balance, before or after.** See privacy considerations.
280
+ - **A financial approval event family.** Approval is already modelled by `workflow.approval.*` in
281
+ [workflow-and-approval.md](../../semantic-conventions/workflow-and-approval.md) and by the core
282
+ `/approval` object.
283
+
284
+ ## Fixture matrix
285
+
286
+ [examples/profiles/financial-transaction-management/](../../examples/profiles/financial-transaction-management/)
287
+ — twelve valid, twelve invalid, three not-applicable.
288
+
289
+ | Rule | Valid fixture | Invalid fixture |
290
+ | ------------------ | -------------------------------- | ---------------------------------------------------------------------------- |
291
+ | `FIN-CORE-001` | all twelve | `payment-capture-missing-authorization.json` |
292
+ | `FIN-CORE-002` | warns on payout and settlement | none — a warning cannot fail |
293
+ | `FIN-TXN-001` | `payment-capture.json` | `payment-capture-missing-amount.json`, `withdrawal-missing-correlation.json` |
294
+ | `FIN-TXN-002` | `payout-execute.json` | `payout-missing-direction.json` |
295
+ | `FIN-LINK-001` | `transfer-execute.json` | `transfer-missing-related-resources.json` |
296
+ | `FIN-REASON-001` | `refund-create.json` | `refund-missing-reason.json` |
297
+ | `FIN-REVERSAL-001` | `reversal-execute.json` | `reversal-missing-original-transaction.json` |
298
+ | `FIN-APPROVAL-001` | `withdrawal-execute.json` | `withdrawal-missing-approval-status.json` |
299
+ | `FIN-MANUAL-001` | `deposit-manual-correction.json` | `manual-deposit-missing-change.json` |
300
+ | `FIN-RECON-001` | `reconciliation-adjust.json` | `reconciliation-missing-identifier.json` |
301
+ | `FIN-RECON-002` | `reconciliation-adjust.json` | `reconciliation-adjust-missing-change.json` |
302
+ | `FIN-LIMIT-001` | `limit-update.json` | `limit-update-missing-limit-type.json` |
303
+
304
+ Every fixture — valid, invalid and not-applicable alike — is core-conforming and privacy-clean. Each
305
+ invalid fixture removes exactly one profile-required value from a valid one and fails exactly one rule
306
+ with exactly one error.
307
+
308
+ ## Not-applicable rationale
309
+
310
+ Three fixtures exist to hold the exclusion in place:
311
+
312
+ | Fixture | Event | Why it is ungoverned |
313
+ | ---------------------- | --------------------------- | ----------------------------------------------------------------------- |
314
+ | `balance-view.json` | `financial.balance.view` | A read. No value moves and the event is emitted at page-view volume. |
315
+ | `quote-create.json` | `financial.quote.create` | A price is computed and recorded. Nothing is owed, paid or transferred. |
316
+ | `report-generate.json` | `financial.report.generate` | Routine reporting over movements already audited individually. |
317
+
318
+ `check-profile` reports all three as not applicable and exits `3`. **Not applicable is not
319
+ conformance** — the profile says nothing about these events rather than blessing them.
320
+
321
+ If a future edit widened a selector to a bare `financial.` prefix, these fixtures would start
322
+ conforming instead of being skipped and the test would fail, which is the point: that edit would
323
+ silently impose the profile's heaviest requirements on the highest-volume events in every financial
324
+ system that adopted it.
325
+
326
+ ## Open questions
327
+
328
+ - Should `direction` have a recommended vocabulary in the semantic conventions, given that
329
+ `debit`/`credit` and `inbound`/`outbound` describe the same fact from an accounting and an
330
+ operational point of view? The profile currently requires only that one be recorded.
331
+ - Is a partial refund meaningfully different from a reversal for audit purposes? The profile treats
332
+ them as separate families and requires an original transaction reference only from the reversal, on
333
+ the grounds that a goodwill credit has no original.
334
+ - Should a settlement be required to name the reconciliation that will check it? It is recommended
335
+ today, because in many deployments the reconciliation identifier is not known when the settlement
336
+ is released.
337
+ - What would a producer-set discriminator for high-value movement be called, so that a rule could
338
+ condition stronger evidence on it the way `FIN-APPROVAL-001` conditions on `approvalRequired`?
339
+ There is no adoption evidence yet, and thresholds are not something this specification should set.
@@ -0,0 +1,247 @@
1
+ {
2
+ "profileVersion": "0.1",
3
+ "name": "financial-transaction-management",
4
+ "version": "0.1",
5
+ "status": "experimental",
6
+ "coreVersions": ["0.1"],
7
+ "title": "Financial Transaction Management Profile",
8
+ "description": "Additional conformance requirements for audit events that record material financial operations: transfers, payments, withdrawals, deposits, refunds, reversals, payouts, settlements, chargebacks, reconciliation adjustments and transaction limit changes. Every requirement adds to the OpenAuditModel Core Specification; none relaxes it. Non-mutating financial reads such as balance enquiries, price quotes and routine reporting are deliberately not governed.",
9
+ "rules": [
10
+ {
11
+ "id": "FIN-CORE-001",
12
+ "description": "Every governed financial operation records the authorization decision that permitted it.",
13
+ "rationale": "Money movement is the operation an attacker and a mistaken operator both aim for, and after the fact the two look identical. Without a recorded decision nothing separates a transfer that policy permitted from one that bypassed policy entirely, and a reviewer is left inferring the control state of the system from the fact that the transaction completed. The profile records the decision; it does not evaluate it.",
14
+ "severity": "error",
15
+ "events": ["financial.limit.create", "financial.limit.delete", "financial.limit.update"],
16
+ "eventPrefixes": [
17
+ "financial.transfer.",
18
+ "financial.payment.",
19
+ "financial.withdrawal.",
20
+ "financial.deposit.",
21
+ "financial.refund.",
22
+ "financial.reversal.",
23
+ "financial.payout.",
24
+ "financial.settlement.",
25
+ "financial.chargeback.",
26
+ "financial.reconciliation."
27
+ ],
28
+ "requiredPaths": ["/authorization"]
29
+ },
30
+ {
31
+ "id": "FIN-CORE-002",
32
+ "description": "A governed financial operation should record why it happened, how the actor was authenticated, and the other financial resources the same operation touched.",
33
+ "rationale": "These are the three fields a financial reviewer reaches for first and a producer omits most often. They are recommended rather than required because each has a legitimate absence: a scheduled settlement has no per-event business justification, an internal batch process has no interactive authentication context, and a single-sided deposit has no counterparty resource to name. A missing one should prompt a question rather than fail a pipeline.",
34
+ "severity": "warning",
35
+ "events": ["financial.limit.create", "financial.limit.delete", "financial.limit.update"],
36
+ "eventPrefixes": [
37
+ "financial.transfer.",
38
+ "financial.payment.",
39
+ "financial.withdrawal.",
40
+ "financial.deposit.",
41
+ "financial.refund.",
42
+ "financial.reversal.",
43
+ "financial.payout.",
44
+ "financial.settlement.",
45
+ "financial.chargeback.",
46
+ "financial.reconciliation."
47
+ ],
48
+ "recommendedPaths": ["/reason", "/authentication", "/relatedResources"]
49
+ },
50
+ {
51
+ "id": "FIN-TXN-001",
52
+ "description": "A monetary movement records the transaction it belongs to, the identifier that correlates the whole transaction lifecycle, and the amount and currency that moved.",
53
+ "rationale": "A financial transaction is almost never one event. An authorization, a capture, a settlement, a refund and a reversal are separate operations against the same money, often minutes or months apart and often produced by different services. Without a transaction reference the events cannot be assembled into a transaction, and without a correlation identifier the transaction cannot be assembled into a case. Amount and currency are what make the trail answerable at all: an event that records that value moved without recording how much and in what unit cannot be reconciled against any ledger, and a bare number without its currency is not an amount.",
54
+ "severity": "error",
55
+ "eventPrefixes": [
56
+ "financial.transfer.",
57
+ "financial.payment.",
58
+ "financial.withdrawal.",
59
+ "financial.deposit.",
60
+ "financial.refund.",
61
+ "financial.reversal.",
62
+ "financial.payout.",
63
+ "financial.settlement.",
64
+ "financial.chargeback."
65
+ ],
66
+ "requiredPaths": ["/request/correlationId"],
67
+ "requiredMetadata": [
68
+ {
69
+ "path": "/financial/transactionId",
70
+ "type": "string"
71
+ },
72
+ {
73
+ "path": "/financial/amount",
74
+ "type": "number"
75
+ },
76
+ {
77
+ "path": "/financial/currency",
78
+ "type": "string"
79
+ }
80
+ ]
81
+ },
82
+ {
83
+ "id": "FIN-TXN-002",
84
+ "description": "A monetary movement records which way value moved and the state the transaction was left in.",
85
+ "rationale": "The same amount against the same account is a credit or a debit, and the difference is the whole meaning of the event. Direction cannot be reconstructed from the event name because one deployment models a refund as an outbound payment and another as a negative capture. Business status is equally irreplaceable: `event.outcome` says whether the operation the application attempted succeeded, which is not the same question as whether the money has actually moved. A capture can succeed while the transaction remains pending settlement, and a reviewer who conflates the two will report money as final that a counterparty can still recall.",
86
+ "severity": "error",
87
+ "eventPrefixes": [
88
+ "financial.transfer.",
89
+ "financial.payment.",
90
+ "financial.withdrawal.",
91
+ "financial.deposit.",
92
+ "financial.refund.",
93
+ "financial.reversal.",
94
+ "financial.payout.",
95
+ "financial.settlement.",
96
+ "financial.chargeback."
97
+ ],
98
+ "requiredMetadata": [
99
+ {
100
+ "path": "/financial/direction",
101
+ "type": "string"
102
+ },
103
+ {
104
+ "path": "/financial/status",
105
+ "type": "string"
106
+ }
107
+ ]
108
+ },
109
+ {
110
+ "id": "FIN-LINK-001",
111
+ "description": "A transfer or a settlement names the other financial resources it moved value between.",
112
+ "rationale": "A transfer and a settlement are two-sided by definition. The core model gives one resource the primary position, so an event that stops there records exactly half the operation: the money left somewhere and the trail does not say where it went. `relatedResources` is the core field for this and needs no new vocabulary, and a counterparty outside the producer's control can still be named by an opaque reference. This is a strengthening of the general `FIN-CORE-002` recommendation for the two families where a single-sided record is not merely thin but wrong.",
113
+ "severity": "error",
114
+ "eventPrefixes": ["financial.transfer.", "financial.settlement."],
115
+ "requiredPaths": ["/relatedResources"]
116
+ },
117
+ {
118
+ "id": "FIN-REASON-001",
119
+ "description": "An operation that refuses, withdraws or unwinds value records why.",
120
+ "rationale": "A completed payment explains itself; a rejected, cancelled, refunded or reversed one does not. These are the operations where the customer disputes the outcome, where the loss is booked, and where the difference between a policy decision, an operational error and a deliberate write-off decides who is accountable. That difference exists only in the justification, and it is knowable only at the moment of the decision. A stated reason also separates a refund issued because goods were returned from one issued to move money to a chosen destination, which is the shape financial abuse takes when it is committed from inside.",
121
+ "severity": "error",
122
+ "events": [
123
+ "financial.payment.reject",
124
+ "financial.payment.cancel",
125
+ "financial.transfer.cancel",
126
+ "financial.payout.cancel",
127
+ "financial.settlement.cancel",
128
+ "financial.withdrawal.reject",
129
+ "financial.refund.create"
130
+ ],
131
+ "eventPrefixes": ["financial.reversal.", "financial.chargeback."],
132
+ "requiredPaths": ["/reason"]
133
+ },
134
+ {
135
+ "id": "FIN-REVERSAL-001",
136
+ "description": "A reversal or a chargeback names the transaction it acts against.",
137
+ "rationale": "A reversal has no independent existence: it is defined entirely by the movement it undoes, and an event that does not name that movement records value appearing from nowhere. Matching a reversal to its original by amount and timing is guesswork that fails precisely where it matters, on the accounts with many similar transactions. The same holds for a chargeback, which is raised by a counterparty against a specific earlier payment and whose whole review turns on which one.",
138
+ "severity": "error",
139
+ "eventPrefixes": ["financial.reversal.", "financial.chargeback."],
140
+ "requiredMetadata": [
141
+ {
142
+ "path": "/financial/originalTransactionId",
143
+ "type": "string"
144
+ }
145
+ ],
146
+ "recommendedPaths": ["/approval"]
147
+ },
148
+ {
149
+ "id": "FIN-APPROVAL-001",
150
+ "description": "When the producer declares that an operation required approval, the event records the approval decision.",
151
+ "rationale": "Approval thresholds are a local policy decision that this profile deliberately does not make. A marketplace pays out thousands of small balances an hour with no human involvement and is right to; a treasury team moves one amount a week under dual control and is right to. What the profile can require is consistency: once a producer has declared that this operation required approval, an event that does not carry the resulting decision has recorded the control as applying without recording whether it was satisfied, which is worse than not claiming the control at all. The status is required rather than the object alone because an approval record naming only a workflow does not say whether anyone approved anything.",
152
+ "severity": "error",
153
+ "events": ["financial.limit.create", "financial.limit.delete", "financial.limit.update"],
154
+ "eventPrefixes": [
155
+ "financial.transfer.",
156
+ "financial.payment.",
157
+ "financial.withdrawal.",
158
+ "financial.deposit.",
159
+ "financial.refund.",
160
+ "financial.reversal.",
161
+ "financial.payout.",
162
+ "financial.settlement.",
163
+ "financial.chargeback.",
164
+ "financial.reconciliation."
165
+ ],
166
+ "when": {
167
+ "path": "/metadata/financial/approvalRequired",
168
+ "equals": true
169
+ },
170
+ "requiredPaths": ["/approval/status"],
171
+ "recommendedPaths": ["/approval/workflowId", "/approval/approvers"]
172
+ },
173
+ {
174
+ "id": "FIN-MANUAL-001",
175
+ "description": "When the producer declares an operation to be a manual intervention, the event records what was altered and why.",
176
+ "rationale": "A manual intervention is an operator substituting their own judgement for the system's, and it is the single most sensitive class of financial event because it leaves no upstream business record to reconcile against. The automated path has an order, an instruction or a schedule behind it; the manual path has a person. Recording the change makes the intervention measurable against the state it replaced, and recording the justification is the only chance to capture the operator's intent, which no later reviewer can reconstruct. The flag is set by the producer because only the producer knows which of its code paths is the manual one.",
177
+ "severity": "error",
178
+ "events": ["financial.limit.create", "financial.limit.delete", "financial.limit.update"],
179
+ "eventPrefixes": [
180
+ "financial.transfer.",
181
+ "financial.payment.",
182
+ "financial.withdrawal.",
183
+ "financial.deposit.",
184
+ "financial.refund.",
185
+ "financial.reversal.",
186
+ "financial.payout.",
187
+ "financial.settlement.",
188
+ "financial.chargeback.",
189
+ "financial.reconciliation."
190
+ ],
191
+ "when": {
192
+ "path": "/metadata/financial/manual",
193
+ "equals": true
194
+ },
195
+ "requiredPaths": ["/reason", "/change"],
196
+ "recommendedPaths": ["/approval"]
197
+ },
198
+ {
199
+ "id": "FIN-RECON-001",
200
+ "description": "A reconciliation event names the reconciliation it belongs to.",
201
+ "rationale": "Reconciliation is the control that proves the producer's own record of money agrees with somebody else's. Its events are only meaningful as a set: a start, a set of findings and a completion together state what was compared and what the comparison concluded. An event that does not name its run cannot be placed in that set, so the trail records that reconciliation happened without recording what any of it decided, and a break found in one period becomes indistinguishable from a break found in another.",
202
+ "severity": "error",
203
+ "eventPrefixes": ["financial.reconciliation."],
204
+ "requiredMetadata": [
205
+ {
206
+ "path": "/financial/reconciliationId",
207
+ "type": "string"
208
+ }
209
+ ],
210
+ "recommendedPaths": ["/request/correlationId"]
211
+ },
212
+ {
213
+ "id": "FIN-RECON-002",
214
+ "description": "A reconciliation adjustment records the change it made to the books, the amount and currency involved, and why the correction was accepted.",
215
+ "rationale": "An adjustment is the one reconciliation operation that moves recorded value without a business transaction behind it: the books are altered to agree with an external record. That makes it the natural hiding place for a loss nobody wants to explain, and the natural place for a genuine correction to be mistaken for one. The before and after state, the size of the correction and the stated justification are what let a later reviewer tell those two apart, and none of them can be recovered once the balances have moved on.",
216
+ "severity": "error",
217
+ "events": ["financial.reconciliation.adjust"],
218
+ "requiredPaths": ["/change", "/reason"],
219
+ "requiredMetadata": [
220
+ {
221
+ "path": "/financial/amount",
222
+ "type": "number"
223
+ },
224
+ {
225
+ "path": "/financial/currency",
226
+ "type": "string"
227
+ }
228
+ ],
229
+ "recommendedPaths": ["/approval"]
230
+ },
231
+ {
232
+ "id": "FIN-LIMIT-001",
233
+ "description": "A change to a transaction limit records the kind of limit, its before and after state, and why it was changed.",
234
+ "rationale": "A limit is a preventive control, so changing one changes what every future transaction is allowed to do. Raising a ceiling shortly before a large movement and lowering it afterwards is the classic shape of a limit abused rather than administered, and it is visible only when the transition itself is recorded: an event stating the resulting value alone cannot show how far the control moved or that it moved back. The kind of limit is required because a daily aggregate ceiling and a single-transaction ceiling are different controls that deployments name differently, and a reviewer cannot infer which one changed from a number. The rule selects the three mutating operations by name rather than the whole `financial.limit.` family, because a limit check runs on every transaction: governing `financial.limit.check` would put the profile's heaviest requirements on its highest-volume event, and the requirement would be switched off rather than met.",
235
+ "severity": "error",
236
+ "events": ["financial.limit.create", "financial.limit.update", "financial.limit.delete"],
237
+ "requiredPaths": ["/change", "/reason"],
238
+ "requiredMetadata": [
239
+ {
240
+ "path": "/financial/limitType",
241
+ "type": "string"
242
+ }
243
+ ],
244
+ "recommendedPaths": ["/approval"]
245
+ }
246
+ ]
247
+ }