@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,109 @@
|
|
|
1
|
+
# Evidence Model
|
|
2
|
+
|
|
3
|
+
**Specification version: 0.1 · Status: Experimental · This document: Normative**
|
|
4
|
+
|
|
5
|
+
## 1. Purpose
|
|
6
|
+
|
|
7
|
+
`evidence` points to material that supports the event: a signed document, an approval record, a
|
|
8
|
+
screenshot, a trace, a ticket. It is OPTIONAL.
|
|
9
|
+
|
|
10
|
+
The single design rule is that **evidence is referenced, never embedded**.
|
|
11
|
+
|
|
12
|
+
## 2. Structure
|
|
13
|
+
|
|
14
|
+
`evidence` is an array. When present it MUST contain at least one entry. Each entry is an object.
|
|
15
|
+
|
|
16
|
+
### 2.1 Required
|
|
17
|
+
|
|
18
|
+
| Field | Meaning |
|
|
19
|
+
| ----------- | -------------------------------------------------------- |
|
|
20
|
+
| `type` | Kind of evidence being referenced. |
|
|
21
|
+
| `reference` | Locator or identifier of the evidence in its own system. |
|
|
22
|
+
|
|
23
|
+
### 2.2 Optional
|
|
24
|
+
|
|
25
|
+
| Field | Meaning |
|
|
26
|
+
| ---------------- | ----------------------------------------- |
|
|
27
|
+
| `hash` | Digest of the referenced evidence. |
|
|
28
|
+
| `contentType` | Media type of the referenced evidence. |
|
|
29
|
+
| `createdAt` | When the evidence was created. |
|
|
30
|
+
| `retentionUntil` | When the owning system may dispose of it. |
|
|
31
|
+
| `legalHold` | Whether disposal is currently suspended. |
|
|
32
|
+
|
|
33
|
+
## 3. Evidence types
|
|
34
|
+
|
|
35
|
+
`type` MUST be one of:
|
|
36
|
+
|
|
37
|
+
```text
|
|
38
|
+
document log metric trace screenshot
|
|
39
|
+
ticket approval signature external-record other
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## 4. Normative rules
|
|
43
|
+
|
|
44
|
+
1. Large binary documents MUST NOT be embedded in an audit event. An audit event is a record of an
|
|
45
|
+
operation, not a container for the artifacts of that operation.
|
|
46
|
+
2. OpenAuditModel does **not** define evidence storage. Where the evidence lives, how it is retained
|
|
47
|
+
and how it is retrieved are outside this specification entirely.
|
|
48
|
+
3. Evidence references MUST point to access-controlled locations. The reference itself SHOULD be
|
|
49
|
+
treated as sensitive: the existence and identifier of a document can disclose information even
|
|
50
|
+
when its content is protected.
|
|
51
|
+
4. A reference MUST NOT expose credentials. It MUST NOT contain a username and password, an access
|
|
52
|
+
token, a shared access signature, a pre-signed URL, an API key, or any other value that grants
|
|
53
|
+
access to the evidence by possession of the audit event.
|
|
54
|
+
5. A consumer MUST NOT assume a reference is resolvable. References may point to systems the consumer
|
|
55
|
+
cannot reach, or to material that has since been disposed of under its retention policy.
|
|
56
|
+
6. `hash`, where present, allows a reviewer to verify that retrieved evidence is the material the
|
|
57
|
+
event referred to. Producers SHOULD record it when the evidence is immutable in its own system.
|
|
58
|
+
|
|
59
|
+
## 5. Reference forms
|
|
60
|
+
|
|
61
|
+
The specification does not mandate a reference syntax. Any of these are acceptable, provided rule 4
|
|
62
|
+
holds:
|
|
63
|
+
|
|
64
|
+
```text
|
|
65
|
+
records/root-cause-analysis/rca-2026-0418 internal path
|
|
66
|
+
urn:example:incident:rca-2026-0418 URN
|
|
67
|
+
ticket-8123 opaque identifier in a known system
|
|
68
|
+
workflow-incident-closure/approval-request-9921 compound identifier
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Producers SHOULD choose a form that remains resolvable after the referencing system changes hosting,
|
|
72
|
+
and SHOULD document how references are interpreted.
|
|
73
|
+
|
|
74
|
+
Producers SHOULD NOT use a URL that embeds a query string containing identifiers or search terms, for
|
|
75
|
+
the same reasons `request.route` rejects query strings. See [privacy.md](privacy.md).
|
|
76
|
+
|
|
77
|
+
## 6. Retention and legal hold
|
|
78
|
+
|
|
79
|
+
`retentionUntil` and `legalHold` describe the state of the **referenced evidence in its own system**,
|
|
80
|
+
as known to the producer at the time of the event. They are informational.
|
|
81
|
+
|
|
82
|
+
They MUST NOT be interpreted as instructions to the audit pipeline, and they MUST NOT be interpreted
|
|
83
|
+
as a guarantee that the evidence still exists. Retention of the audit event itself is a separate
|
|
84
|
+
concern, described by `privacy.retentionClass` and governed by the operator.
|
|
85
|
+
|
|
86
|
+
The specification defines no legal hold process, no disposal workflow and no retention enforcement.
|
|
87
|
+
Recording that something is under hold is not the same as holding it.
|
|
88
|
+
|
|
89
|
+
## 7. Example
|
|
90
|
+
|
|
91
|
+
```json
|
|
92
|
+
{
|
|
93
|
+
"evidence": [
|
|
94
|
+
{
|
|
95
|
+
"type": "document",
|
|
96
|
+
"reference": "records/root-cause-analysis/rca-2026-0418",
|
|
97
|
+
"hash": "9f2a4c1d5e6b7a8c9d0e1f2a3b4c5d6e7f8091a2b3c4d5e6f708192a3b4c5d6e",
|
|
98
|
+
"contentType": "application/pdf",
|
|
99
|
+
"createdAt": "2026-03-14T17:30:00Z",
|
|
100
|
+
"retentionUntil": "2033-03-14T00:00:00Z",
|
|
101
|
+
"legalHold": false
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
"type": "approval",
|
|
105
|
+
"reference": "workflow-incident-closure/approval-request-9921"
|
|
106
|
+
}
|
|
107
|
+
]
|
|
108
|
+
}
|
|
109
|
+
```
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# Extension Model
|
|
2
|
+
|
|
3
|
+
**Specification version: 0.1 · Status: Experimental · This document: Normative**
|
|
4
|
+
|
|
5
|
+
## 1. Four places data can go
|
|
6
|
+
|
|
7
|
+
| Mechanism | For | Key form | Collision risk |
|
|
8
|
+
| ---------------- | ------------------------------------------------------ | ---------------------------- | --------------- |
|
|
9
|
+
| **Core fields** | Concepts universal to all business applications | Defined by the specification | None |
|
|
10
|
+
| **`attributes`** | Producer detail about a specific principal or resource | Plain names | Local only |
|
|
11
|
+
| **`metadata`** | Domain-specific audit interpretation data | Plain names | Within a domain |
|
|
12
|
+
| **`extensions`** | Vendor or product specific data | Reverse-domain namespaced | None |
|
|
13
|
+
|
|
14
|
+
Choosing correctly matters, because these have different stability expectations and different
|
|
15
|
+
audiences. A field in `metadata` is expected to be read by anyone working in that domain; a field in
|
|
16
|
+
`extensions` is expected to be read by whoever owns the namespace.
|
|
17
|
+
|
|
18
|
+
## 2. Core objects are closed
|
|
19
|
+
|
|
20
|
+
Every core object rejects unknown properties, and domain-specific data MUST NOT be added as a new
|
|
21
|
+
top-level property.
|
|
22
|
+
|
|
23
|
+
This is a deliberate trade-off. A closed model means a producer that adds `documentClassification`
|
|
24
|
+
at the top level gets a validation error instead of silently emitting a field no consumer will read.
|
|
25
|
+
|
|
26
|
+
## 3. Metadata
|
|
27
|
+
|
|
28
|
+
`metadata` carries domain-specific audit interpretation data that has no core representation: the
|
|
29
|
+
role that was assigned, the partition that was reset, the export format that was produced.
|
|
30
|
+
|
|
31
|
+
### 3.1 Rules
|
|
32
|
+
|
|
33
|
+
1. `metadata` MUST be a JSON object. Values MAY be any JSON value.
|
|
34
|
+
2. Metadata keys do NOT need a reverse-domain namespace. They are plain names.
|
|
35
|
+
3. Metadata MUST NOT contain any value prohibited by [privacy.md](privacy.md).
|
|
36
|
+
4. Metadata SHOULD contain small, structured values. It is not a place for payloads, documents,
|
|
37
|
+
serialized objects or blobs.
|
|
38
|
+
5. A metadata field's meaning MUST be stable once used, in the same way an event name is stable. A
|
|
39
|
+
key that means one thing in one release and something else in the next silently corrupts
|
|
40
|
+
historical analysis.
|
|
41
|
+
6. Metadata MUST NOT duplicate core fields. Recording `metadata.userId` when `actor.id` exists creates
|
|
42
|
+
two sources of truth that will eventually disagree.
|
|
43
|
+
7. Metadata SHOULD NOT be used to work around a core constraint. A producer that puts an approval
|
|
44
|
+
status in `metadata` because it did not want to populate `approval` has made its data
|
|
45
|
+
uncomparable for no benefit.
|
|
46
|
+
|
|
47
|
+
### 3.2 Profiles and metadata
|
|
48
|
+
|
|
49
|
+
Profiles MAY define expected metadata fields for their domain, and MAY require them for specific
|
|
50
|
+
event classes. When a profile defines a metadata field, producers implementing that profile SHOULD
|
|
51
|
+
use the profile's name and meaning rather than inventing their own. See [profiles/](../profiles/).
|
|
52
|
+
|
|
53
|
+
No profile defines metadata fields normatively in v0.1.
|
|
54
|
+
|
|
55
|
+
### 3.3 Example
|
|
56
|
+
|
|
57
|
+
```json
|
|
58
|
+
{
|
|
59
|
+
"metadata": {
|
|
60
|
+
"assignedRole": "support-agent",
|
|
61
|
+
"assignmentScope": "tenant",
|
|
62
|
+
"effectiveFrom": "2026-03-16T00:00:00Z",
|
|
63
|
+
"expiresAt": "2026-06-16T00:00:00Z"
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## 4. Extensions
|
|
69
|
+
|
|
70
|
+
`extensions` carries vendor-specific or domain-specific data whose meaning is owned by an
|
|
71
|
+
identifiable party.
|
|
72
|
+
|
|
73
|
+
### 4.1 Key form
|
|
74
|
+
|
|
75
|
+
Every extension key MUST be a reverse-domain namespaced name of **at least three** dot-separated,
|
|
76
|
+
lower-case segments. Each segment starts with a letter and may contain digits and hyphenated words.
|
|
77
|
+
|
|
78
|
+
Valid:
|
|
79
|
+
|
|
80
|
+
```json
|
|
81
|
+
{
|
|
82
|
+
"extensions": {
|
|
83
|
+
"com.example.identity.directory.id": "directory-1",
|
|
84
|
+
"io.vendor.product.feature.enabled": true,
|
|
85
|
+
"org.example.workflow.stage": "legal-review"
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Invalid:
|
|
91
|
+
|
|
92
|
+
```json
|
|
93
|
+
{
|
|
94
|
+
"extensions": {
|
|
95
|
+
"clusterId": "production",
|
|
96
|
+
"customValue": true
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
The canonical schema rejects keys that do not follow the convention. Three segments is the minimum
|
|
102
|
+
because a reverse domain needs at least two labels — `com.example` — plus at least one segment naming
|
|
103
|
+
the field.
|
|
104
|
+
|
|
105
|
+
The namespace SHOULD be a domain the extension's author controls. This is a convention for avoiding
|
|
106
|
+
collisions, not an ownership claim the specification can verify: nothing prevents a producer from
|
|
107
|
+
using a namespace it does not own, and consumers MUST NOT treat a namespace as an authenticity
|
|
108
|
+
signal.
|
|
109
|
+
|
|
110
|
+
### 4.2 Rules
|
|
111
|
+
|
|
112
|
+
1. Extension keys MUST use a reverse-domain namespace as defined above.
|
|
113
|
+
2. Extension values MAY be any JSON value, including nested objects and arrays.
|
|
114
|
+
3. Extensions MUST NOT weaken required core fields. An extension cannot make a required field
|
|
115
|
+
optional, relax a constraint, or substitute for a core field that should have been populated.
|
|
116
|
+
4. Extensions MUST NOT change the meaning of existing core fields. If an extension says
|
|
117
|
+
`com.example.audit.actor-is-really-the-subject`, the model has been misused.
|
|
118
|
+
5. Extensions MUST NOT contain any value prohibited by [privacy.md](privacy.md).
|
|
119
|
+
6. A consumer MUST NOT reject an event because it carries extensions the consumer does not
|
|
120
|
+
understand. Ignoring unknown extensions is the required behaviour.
|
|
121
|
+
7. A consumer MUST NOT derive core semantics from an extension it does not own.
|
|
122
|
+
|
|
123
|
+
### 4.3 Enrichment by pipeline components
|
|
124
|
+
|
|
125
|
+
Collectors and forwarders MAY add extensions under their own namespace, subject to the rules in
|
|
126
|
+
[delivery.md](delivery.md) — in particular, they MUST NOT modify an event carrying integrity
|
|
127
|
+
material, and MUST NOT alter fields that describe what the producer observed.
|
|
128
|
+
|
|
129
|
+
## 5. Metadata or extension?
|
|
130
|
+
|
|
131
|
+
Ask who owns the meaning of the field.
|
|
132
|
+
|
|
133
|
+
| Question | Use |
|
|
134
|
+
| ------------------------------------------------------------------- | -------------------- |
|
|
135
|
+
| Would every application in this domain use this field the same way? | `metadata` |
|
|
136
|
+
| Is the meaning defined by one vendor, product or internal platform? | `extensions` |
|
|
137
|
+
| Does the field describe one principal or one resource specifically? | `attributes` |
|
|
138
|
+
| Is the concept universal across all business applications? | Propose a core field |
|
|
139
|
+
|
|
140
|
+
When genuinely unsure, prefer `extensions`. A namespaced key can be promoted to `metadata` or to a
|
|
141
|
+
core field later; a plain key that turns out to mean different things in different products cannot be
|
|
142
|
+
untangled.
|
|
143
|
+
|
|
144
|
+
## 6. Promoting an extension
|
|
145
|
+
|
|
146
|
+
An extension that proves general may be proposed as a semantic convention, a profile field, or a core
|
|
147
|
+
field. The process is described in [CONTRIBUTING.md](../CONTRIBUTING.md).
|
|
148
|
+
|
|
149
|
+
Promotion requires evidence of independent use across unrelated applications. A field used by one
|
|
150
|
+
product is a product field, however well designed.
|
|
151
|
+
|
|
152
|
+
## 7. Free-form values
|
|
153
|
+
|
|
154
|
+
`metadata`, `extensions` and `attributes` all accept the full JSON value space: strings, numbers,
|
|
155
|
+
integers, booleans, null, arrays and objects, nested to any depth. The canonical schema defines this
|
|
156
|
+
recursively, and recursion is exercised by the conformance test suite.
|
|
157
|
+
|
|
158
|
+
Producers SHOULD nonetheless keep these values shallow and small. Deep structures in audit data are
|
|
159
|
+
hard to review, hard to index and hard to redact.
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
# Integrity
|
|
2
|
+
|
|
3
|
+
**Specification version: 0.1 · Status: Experimental · This document: Normative**
|
|
4
|
+
|
|
5
|
+
## 1. Tamper-evident, not immutable
|
|
6
|
+
|
|
7
|
+
This specification uses the term **tamper-evident** and only that term.
|
|
8
|
+
|
|
9
|
+
An audit event model can make alteration **detectable**. It cannot make anything immutable. Nothing an
|
|
10
|
+
SDK does to a JSON object prevents that object from being deleted, overwritten or never written at
|
|
11
|
+
all. The words `immutable`, `tamper-proof`, `legally binding` and `non-repudiable` MUST NOT be used to
|
|
12
|
+
describe event-level or SDK-level guarantees in this project, in its documentation, or in tooling
|
|
13
|
+
built on it.
|
|
14
|
+
|
|
15
|
+
Storage immutability is a property of a storage system — write-once media, append-only ledgers,
|
|
16
|
+
retention locks — and is outside this specification.
|
|
17
|
+
|
|
18
|
+
## 2. The integrity object
|
|
19
|
+
|
|
20
|
+
`integrity` is OPTIONAL. Integrity information is not required of a conforming event, and an event
|
|
21
|
+
without it is fully conforming. When the object is present it MUST contain at least one property.
|
|
22
|
+
|
|
23
|
+
| Field | Meaning |
|
|
24
|
+
| ------------------ | ------------------------------------------------------------------------------ |
|
|
25
|
+
| `canonicalization` | Canonicalization applied before hashing. REQUIRED when `hash` is present. |
|
|
26
|
+
| `hashAlgorithm` | Algorithm used for `hash` and `previousHash`. REQUIRED when either is present. |
|
|
27
|
+
| `hash` | Digest of the canonicalized event. |
|
|
28
|
+
| `previousHash` | Digest of the preceding event in the same chain. |
|
|
29
|
+
| `chainId` | Identifier of the chain this event belongs to. |
|
|
30
|
+
| `batchId` | Identifier of the sealing or verification batch. See §2.1. |
|
|
31
|
+
| `signature` | Digital signature over the canonicalized event. |
|
|
32
|
+
|
|
33
|
+
`signature` is an object requiring `algorithm` and `value`, and optionally `keyId`. `keyId` MUST NOT
|
|
34
|
+
contain key material. Signature verification is **not** part of v0.1; see §9.
|
|
35
|
+
|
|
36
|
+
### 2.1 What `batchId` is not
|
|
37
|
+
|
|
38
|
+
`batchId` identifies an **integrity sealing or verification batch**: the group of events whose
|
|
39
|
+
digests were computed, or whose chain was verified, together. Its boundaries are set by the sealing
|
|
40
|
+
process.
|
|
41
|
+
|
|
42
|
+
It does NOT identify a job run, a processing batch, an import batch or any business operation, and a
|
|
43
|
+
consumer MUST NOT read it as one. Two events in the same sealing batch need not be related in any
|
|
44
|
+
other way, and events from one job run may be sealed across several batches.
|
|
45
|
+
|
|
46
|
+
`request.correlationId` is the field for job runs, processing batches and logical operations. See
|
|
47
|
+
[event-model.md §10.1](event-model.md) and
|
|
48
|
+
[semantic-conventions/correlation-and-tracing.md](../semantic-conventions/correlation-and-tracing.md).
|
|
49
|
+
|
|
50
|
+
Likewise `chainId` identifies a tamper-evidence chain, not a business grouping.
|
|
51
|
+
|
|
52
|
+
## 3. Canonicalization
|
|
53
|
+
|
|
54
|
+
A digest over JSON is meaningless unless the bytes are reproducible. Property order, whitespace,
|
|
55
|
+
number formatting and Unicode escaping all vary between serializers, so two implementations
|
|
56
|
+
serializing the same event will otherwise disagree on its digest.
|
|
57
|
+
|
|
58
|
+
- A producer that populates `hash`, `previousHash` or `signature` MUST canonicalize the event first,
|
|
59
|
+
and MUST declare which canonicalization it used.
|
|
60
|
+
- **RFC 8785, the JSON Canonicalization Scheme, is the canonicalization of v0.1**, declared as the
|
|
61
|
+
identifier `RFC8785`. Identifiers are matched **case-sensitively**: `rfc8785` and `JCS-RFC8785` are
|
|
62
|
+
not `RFC8785`.
|
|
63
|
+
- `canonicalization` is an open vocabulary in the schema so that a future scheme can be adopted
|
|
64
|
+
without a specification change. A producer MAY declare another identifier; conforming v0.1 tooling
|
|
65
|
+
will report it as unverifiable rather than guess.
|
|
66
|
+
|
|
67
|
+
RFC 8785 was chosen because it is a published standard with independent implementations, it produces
|
|
68
|
+
plain UTF-8 JSON rather than a bespoke encoding, and its number and string rules are already the
|
|
69
|
+
behaviour of every JSON serializer built on ECMAScript semantics. See
|
|
70
|
+
[ADR 0006](../decisions/0006-event-digest-and-chain-verification.md).
|
|
71
|
+
|
|
72
|
+
## 4. The digest procedure
|
|
73
|
+
|
|
74
|
+
This procedure is **normative**. An implementation that deviates from it in any step produces a
|
|
75
|
+
different digest and is not interoperable.
|
|
76
|
+
|
|
77
|
+
Given an event and the algorithm named by `integrity.hashAlgorithm`:
|
|
78
|
+
|
|
79
|
+
1. The input MUST be a conforming OpenAuditModel event. A verifier MUST validate it against the
|
|
80
|
+
canonical schema before calculating a digest.
|
|
81
|
+
2. The event MUST be deep-cloned. The digest procedure MUST NOT modify the input event.
|
|
82
|
+
3. Exactly the following JSON Pointers MUST be removed from the clone, when present:
|
|
83
|
+
|
|
84
|
+
```text
|
|
85
|
+
/integrity/hash
|
|
86
|
+
/integrity/signature
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
No other member is removed. **No empty container is pruned**: an `integrity` object left with no
|
|
90
|
+
members MUST be serialized as `{}`. This rule is arbitrary, and it is fixed precisely because it
|
|
91
|
+
is arbitrary — a producer that pruned the empty object and a verifier that did not would compute
|
|
92
|
+
different digests for the same event.
|
|
93
|
+
|
|
94
|
+
4. The resulting value MUST be serialized with RFC 8785.
|
|
95
|
+
5. The canonical form MUST be encoded as UTF-8.
|
|
96
|
+
6. The digest MUST be calculated over those bytes with the declared algorithm.
|
|
97
|
+
7. The digest MUST be encoded as described in §5.
|
|
98
|
+
8. The result MUST be compared with `integrity.hash` as **bytes**, not as text. A malformed encoding
|
|
99
|
+
MUST be rejected; it MUST NOT be reinterpreted, coerced or truncated to make a comparison possible.
|
|
100
|
+
|
|
101
|
+
### 4.1 What is in the digest
|
|
102
|
+
|
|
103
|
+
| Field | In the digest | Why |
|
|
104
|
+
| ---------------------------- | ------------- | ------------------------------------------------------------------------ |
|
|
105
|
+
| Every field not listed below | **Included** | The digest attests to the event |
|
|
106
|
+
| `sequence` | **Included** | Position in a chain is an integrity claim; reordering must be detectable |
|
|
107
|
+
| `integrity.previousHash` | **Included** | The link is an assertion; re-linking must invalidate the event |
|
|
108
|
+
| `integrity.chainId` | **Included** | Chain membership is an assertion; moving an event must be detectable |
|
|
109
|
+
| `integrity.batchId` | **Included** | Batch membership is an assertion |
|
|
110
|
+
| `integrity.hashAlgorithm` | **Included** | Prevents silently downgrading the declared algorithm |
|
|
111
|
+
| `integrity.canonicalization` | **Included** | Prevents silently changing the declared canonicalization |
|
|
112
|
+
| `integrity.hash` | **Excluded** | Self-referential: it cannot be an input to its own calculation |
|
|
113
|
+
| `integrity.signature` | **Excluded** | Belongs to a later verification layer, over the same digest input |
|
|
114
|
+
|
|
115
|
+
The inclusions matter as much as the exclusions. If chain metadata were excluded, an attacker could
|
|
116
|
+
rewrite `previousHash`, `chainId` and `sequence` freely while every event's hash still verified — the
|
|
117
|
+
chain would become decoration. Because they are included, altering any of them invalidates the event
|
|
118
|
+
that carries them.
|
|
119
|
+
|
|
120
|
+
### 4.2 Consequence for collectors
|
|
121
|
+
|
|
122
|
+
Every field except the two excluded pointers is part of the digest, including `observedTime`. A
|
|
123
|
+
collector that adds `observedTime` to an event that already carries `integrity.hash` therefore
|
|
124
|
+
invalidates it. This is not a special case: a component that is not the producer MUST NOT modify a
|
|
125
|
+
sealed event at all, as required by [delivery.md](delivery.md) §7. Enrichment of a sealed event MUST
|
|
126
|
+
be carried alongside it, not inside it.
|
|
127
|
+
|
|
128
|
+
## 5. Digest encoding
|
|
129
|
+
|
|
130
|
+
`integrity.hash` and `integrity.previousHash` MUST be encoded as **lower-case hexadecimal** with an
|
|
131
|
+
even number of digits. The canonical schema enforces this.
|
|
132
|
+
|
|
133
|
+
A single encoding is mandated because a digest that might be hexadecimal, base64 or base64url cannot
|
|
134
|
+
be compared without guessing which: a 64-character hexadecimal digest is also a well-formed base64
|
|
135
|
+
character sequence, so no verifier can distinguish them reliably. Hexadecimal was chosen over
|
|
136
|
+
base64url because it is fixed-length per algorithm, has no padding or alphabet variants, and can be
|
|
137
|
+
compared by eye in a report. Its 33% size cost on a 32-byte digest is not material.
|
|
138
|
+
|
|
139
|
+
`integrity.signature.value`, `change.beforeHash`, `change.afterHash` and `evidence[].hash` are **not**
|
|
140
|
+
restricted to hexadecimal. Those values are frequently echoed from the system that produced them, and
|
|
141
|
+
OpenAuditModel tooling does not recalculate them.
|
|
142
|
+
|
|
143
|
+
## 6. Hash algorithms
|
|
144
|
+
|
|
145
|
+
`hashAlgorithm` is an open vocabulary, matched case-sensitively, so that a new algorithm can be
|
|
146
|
+
adopted without a specification change.
|
|
147
|
+
|
|
148
|
+
Conforming v0.1 tooling MUST implement:
|
|
149
|
+
|
|
150
|
+
```text
|
|
151
|
+
SHA-256 SHA-384 SHA-512
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Producers SHOULD use one of those three. A producer MAY declare another identifier — `BLAKE3`, for
|
|
155
|
+
example — and a verifier that does not implement it MUST report the event as unverifiable rather than
|
|
156
|
+
as verified. **Acceptance by the schema is not a claim of verifier support**, and tooling MUST NOT
|
|
157
|
+
present it as one.
|
|
158
|
+
|
|
159
|
+
A verifier MUST reject a declared hash whose length disagrees with the declared algorithm.
|
|
160
|
+
|
|
161
|
+
## 7. Chains
|
|
162
|
+
|
|
163
|
+
`previousHash` links an event to its predecessor, so that removing or altering a member of the
|
|
164
|
+
sequence breaks the chain and becomes detectable.
|
|
165
|
+
|
|
166
|
+
### 7.1 Chain rules
|
|
167
|
+
|
|
168
|
+
For an event to participate in chain verification:
|
|
169
|
+
|
|
170
|
+
1. It MUST declare `integrity.chainId`. Chain membership is explicit; it is never inferred.
|
|
171
|
+
2. It MUST declare `integrity.hash`.
|
|
172
|
+
3. It MUST declare `sequence`. Ordering is by `sequence`; there is no other deterministic order.
|
|
173
|
+
4. Every event in one chain MUST declare the **same** `hashAlgorithm` and the **same**
|
|
174
|
+
`canonicalization`. A `previousHash` produced under a different algorithm can never equal the
|
|
175
|
+
predecessor's `hash`.
|
|
176
|
+
5. Two events in one chain MUST NOT declare the same `sequence`.
|
|
177
|
+
6. Sequence numbers MAY be non-contiguous. The core model permits gaps, and a genuinely removed event
|
|
178
|
+
breaks a link rather than only leaving a gap.
|
|
179
|
+
|
|
180
|
+
### 7.2 The first event
|
|
181
|
+
|
|
182
|
+
**The first event in a chain MUST omit `previousHash`.**
|
|
183
|
+
|
|
184
|
+
No genesis constant is defined. A magic value would have to be agreed, encoded, and then distinguished
|
|
185
|
+
from a real digest by every verifier; omission is unambiguous and needs no agreement.
|
|
186
|
+
|
|
187
|
+
A verifier given a set whose lowest-sequence event _does_ declare `previousHash` MUST report that the
|
|
188
|
+
supplied set is a **segment** of a chain rather than a chain from its beginning. That is not a
|
|
189
|
+
failure — verifying a window is legitimate — but it MUST be visible in the report, because a segment
|
|
190
|
+
that verifies says nothing about the events before it.
|
|
191
|
+
|
|
192
|
+
### 7.3 Linking
|
|
193
|
+
|
|
194
|
+
For every event after the first, `integrity.previousHash` MUST equal the **declared**
|
|
195
|
+
`integrity.hash` of the preceding event in sequence order.
|
|
196
|
+
|
|
197
|
+
Comparison is against the predecessor's declared hash rather than a recalculation. This is not weaker:
|
|
198
|
+
every event's declared hash is independently verified against its own recalculated digest, so the two
|
|
199
|
+
formulations are equivalent whenever verification passes. It is more useful when verification fails,
|
|
200
|
+
because a modified event and a broken link are then reported as separate, individually locatable
|
|
201
|
+
problems instead of one smearing into the other.
|
|
202
|
+
|
|
203
|
+
### 7.4 Chain scope
|
|
204
|
+
|
|
205
|
+
Chains MAY be **instance-level**, **partition-level** or **batch-level**. A single global hash chain
|
|
206
|
+
is **NOT** required, and for most distributed systems is not achievable without a serialization
|
|
207
|
+
bottleneck the audit model has no business imposing. A set of events containing several chains is
|
|
208
|
+
verified as several independent chains.
|
|
209
|
+
|
|
210
|
+
## 8. What verification does not prove
|
|
211
|
+
|
|
212
|
+
This section is normative in the sense that documentation and tooling MUST NOT claim otherwise.
|
|
213
|
+
|
|
214
|
+
1. **An event hash does not prevent deletion.** A verifier can detect that an event is missing from a
|
|
215
|
+
chain; nothing stops it being removed.
|
|
216
|
+
2. **An event hash does not provide storage immutability.** It detects change; it does not prevent it.
|
|
217
|
+
3. **Chain verification proves consistency of the supplied set only.** It cannot prove that the whole
|
|
218
|
+
historical chain was supplied.
|
|
219
|
+
4. **Tail truncation may be undetectable.** An attacker who removes the most recent events leaves a
|
|
220
|
+
shorter chain that is internally perfectly consistent. Detecting this requires an external
|
|
221
|
+
checkpoint — a chain head published somewhere the attacker does not control — which is out of
|
|
222
|
+
scope for v0.1.
|
|
223
|
+
5. **Head truncation is reported, not prevented.** A segment that does not begin at a genesis event is
|
|
224
|
+
flagged, but a verifier cannot know what preceded it.
|
|
225
|
+
6. **Hash chaining does not replace WORM storage.** A party that can rewrite the store can also
|
|
226
|
+
recompute the chain, unless the chain is anchored somewhere they do not control.
|
|
227
|
+
7. **Hash chaining does not guarantee completeness across distributed producers.** Each producer
|
|
228
|
+
attests to its own sequence. Events a producer never emitted, or a producer that never started,
|
|
229
|
+
leave no gap to detect.
|
|
230
|
+
8. **Distributed ordering is outside the core model.** `sequence` orders events within one chain, not
|
|
231
|
+
across producers.
|
|
232
|
+
9. **A digital signature does not automatically create legal evidentiary status.** Whether a signature
|
|
233
|
+
is admissible or probative is a legal question depending on jurisdiction, process, key custody and
|
|
234
|
+
the circumstances of the dispute. This specification makes no claim about it.
|
|
235
|
+
10. **Key management is outside the core specification.** Key generation, storage, rotation,
|
|
236
|
+
revocation, distribution and custody are all out of scope. `keyId` identifies a key; it says
|
|
237
|
+
nothing about how that key is protected.
|
|
238
|
+
11. **Verification is only as good as its reference.** A verifier that obtains both the events and the
|
|
239
|
+
expected digests from the same untrusted store has verified nothing.
|
|
240
|
+
|
|
241
|
+
## 9. Verifying with the conformance tooling
|
|
242
|
+
|
|
243
|
+
Two commands are implemented in v0.1. Both are offline: they resolve no remote reference, fetch no
|
|
244
|
+
evidence URL and execute nothing contained in an event.
|
|
245
|
+
|
|
246
|
+
```bash
|
|
247
|
+
auditmodel verify-integrity examples/integrity/valid/single-event-sha256.json
|
|
248
|
+
auditmodel verify-chain examples/integrity/valid/three-event-chain
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
`verify-integrity` validates the event against the canonical schema, confirms the declared
|
|
252
|
+
canonicalization and algorithm are implemented, recalculates the digest and compares it with
|
|
253
|
+
`integrity.hash`.
|
|
254
|
+
|
|
255
|
+
`verify-chain` additionally groups events by `chainId`, orders them by `sequence`, and checks every
|
|
256
|
+
link. It detects broken links, modified events, reordering, duplicate sequences, missing sequences,
|
|
257
|
+
mixed algorithms and unsupported algorithms.
|
|
258
|
+
|
|
259
|
+
Exit codes are `0` verified, `1` a verification failed, `2` a usage, read or parse error.
|
|
260
|
+
|
|
261
|
+
**Not implemented in v0.1**, and not to be inferred from the presence of the fields that would support
|
|
262
|
+
them: signing, signature verification, key generation, key storage, key management integrations,
|
|
263
|
+
certificate parsing, trust stores, transparency logs, timestamp authorities, WORM storage and remote
|
|
264
|
+
verification services.
|
|
265
|
+
|
|
266
|
+
## 10. Practical guidance
|
|
267
|
+
|
|
268
|
+
- Integrity metadata is OPTIONAL. An application without it is fully conforming. It SHOULD NOT be
|
|
269
|
+
added because it sounds reassuring; it is worth adding when there is a verifier that will actually
|
|
270
|
+
check it.
|
|
271
|
+
- Producers SHOULD start with per-instance chains. They require no coordination and detect the most
|
|
272
|
+
common failure mode: selective deletion from a single store.
|
|
273
|
+
- Producers SHOULD periodically publish chain heads to a system under different administrative
|
|
274
|
+
control. That is the difference between a chain that detects tampering by an outsider and one that
|
|
275
|
+
detects tampering by anyone.
|
|
276
|
+
- Verifiers SHOULD treat a broken chain as a signal to investigate, not as proof of misconduct.
|
|
277
|
+
Crashes, replays and misconfigured batching break chains routinely.
|
|
278
|
+
- A producer MUST NOT re-seal an event to repair a failed verification. The correct response to a
|
|
279
|
+
mismatch is investigation.
|
|
280
|
+
|
|
281
|
+
## 11. Examples
|
|
282
|
+
|
|
283
|
+
An event in an instance-level chain:
|
|
284
|
+
|
|
285
|
+
```json
|
|
286
|
+
{
|
|
287
|
+
"sequence": 2,
|
|
288
|
+
"integrity": {
|
|
289
|
+
"canonicalization": "RFC8785",
|
|
290
|
+
"hashAlgorithm": "SHA-256",
|
|
291
|
+
"hash": "f7fbea247bdde9c24f07d81cb6ba82b60372f293b2d618066b065bac880dcbf1",
|
|
292
|
+
"previousHash": "3e9462f941036e9b676694cf80fcf17bb438ddff9fb4fb6f67220eca3399ba1b",
|
|
293
|
+
"chainId": "chain-platform-control-service-instance-7c1a"
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
A signed event with no chain. The signature is recorded but is not verified by v0.1 tooling:
|
|
299
|
+
|
|
300
|
+
```json
|
|
301
|
+
{
|
|
302
|
+
"integrity": {
|
|
303
|
+
"canonicalization": "RFC8785",
|
|
304
|
+
"signature": {
|
|
305
|
+
"algorithm": "Ed25519",
|
|
306
|
+
"value": "3045022100c0ffee1234567890abcdef1234567890abcdef1234567890abcdef1234567890",
|
|
307
|
+
"keyId": "key-2026-03"
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
Complete, verifiable fixtures are published under
|
|
314
|
+
[examples/integrity/](../examples/integrity/).
|