@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,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/).