@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,52 @@
|
|
|
1
|
+
# Semantic Conventions
|
|
2
|
+
|
|
3
|
+
**Specification version: 0.1 · Status: Experimental**
|
|
4
|
+
|
|
5
|
+
Semantic conventions are the layer between the schema and a useful audit trail. The schema says
|
|
6
|
+
`event.name` must be a lower-case dotted name; the conventions say that a sign-in is
|
|
7
|
+
`authentication.login` everywhere, so that two applications written by two teams produce data that
|
|
8
|
+
can be read together.
|
|
9
|
+
|
|
10
|
+
## Status of these documents
|
|
11
|
+
|
|
12
|
+
Conventions are **SHOULD-level guidance** unless a section explicitly states otherwise.
|
|
13
|
+
|
|
14
|
+
A producer that uses a name outside these conventions is still conforming. It is simply not
|
|
15
|
+
comparable with other producers, which is the entire benefit on offer. Where an application's domain
|
|
16
|
+
is not covered here, producers SHOULD follow the naming rule in
|
|
17
|
+
[event-naming.md](event-naming.md) and propose a convention.
|
|
18
|
+
|
|
19
|
+
The vocabularies here are **open**. The core schema constrains their form, not their membership. See
|
|
20
|
+
[design-principles.md](../specification/design-principles.md).
|
|
21
|
+
|
|
22
|
+
## Documents
|
|
23
|
+
|
|
24
|
+
| Document | Covers |
|
|
25
|
+
| ---------------------------------------------------------- | ------------------------------------------------- |
|
|
26
|
+
| [event-naming.md](event-naming.md) | The naming rule, categories, activity types |
|
|
27
|
+
| [authentication.md](authentication.md) | Sign-in, sign-out, sessions, credentials, factors |
|
|
28
|
+
| [identity-and-access.md](identity-and-access.md) | Users, roles, permissions, service accounts |
|
|
29
|
+
| [data-access.md](data-access.md) | Reading, exporting, sharing and modifying data |
|
|
30
|
+
| [configuration-and-change.md](configuration-and-change.md) | Settings, secrets, deployments, releases |
|
|
31
|
+
| [workflow-and-approval.md](workflow-and-approval.md) | Requests, approvals, workflow state, incidents |
|
|
32
|
+
| [privileged-operations.md](privileged-operations.md) | Administrative and break-glass operations |
|
|
33
|
+
| [correlation-and-tracing.md](correlation-and-tracing.md) | Request, trace, span and correlation identifiers |
|
|
34
|
+
|
|
35
|
+
## Relationship to profiles
|
|
36
|
+
|
|
37
|
+
Conventions are horizontal: they describe how to name and categorise the operations that appear in
|
|
38
|
+
almost every application. [Profiles](../profiles/) are vertical: they add stricter requirements for a
|
|
39
|
+
specific domain, and may require fields the core model leaves optional.
|
|
40
|
+
|
|
41
|
+
A convention never adds a requirement. A profile may.
|
|
42
|
+
|
|
43
|
+
## Proposing a convention
|
|
44
|
+
|
|
45
|
+
Open a specification change issue describing:
|
|
46
|
+
|
|
47
|
+
1. The operations the convention covers.
|
|
48
|
+
2. The proposed names, and why they follow the naming rule.
|
|
49
|
+
3. At least two independent applications that would emit them.
|
|
50
|
+
4. What breaks if the convention is not adopted.
|
|
51
|
+
|
|
52
|
+
See [CONTRIBUTING.md](../CONTRIBUTING.md).
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# Authentication Events
|
|
2
|
+
|
|
3
|
+
**Specification version: 0.1 · Status: Experimental**
|
|
4
|
+
|
|
5
|
+
Category: `authentication`
|
|
6
|
+
|
|
7
|
+
## 1. Recommended event names
|
|
8
|
+
|
|
9
|
+
| Name | Operation |
|
|
10
|
+
| ---------------------------------- | ----------------------------------------------------------- |
|
|
11
|
+
| `authentication.login` | A principal established an authenticated session |
|
|
12
|
+
| `authentication.logout` | A principal ended a session deliberately |
|
|
13
|
+
| `authentication.session.expire` | A session ended by policy rather than by request |
|
|
14
|
+
| `authentication.session.revoke` | A session was terminated by an administrator or by policy |
|
|
15
|
+
| `authentication.factor.enroll` | A principal registered an additional authentication factor |
|
|
16
|
+
| `authentication.factor.remove` | A factor was removed |
|
|
17
|
+
| `authentication.factor.challenge` | An additional factor was requested and answered |
|
|
18
|
+
| `authentication.credential.create` | A credential was issued |
|
|
19
|
+
| `authentication.credential.rotate` | A credential was replaced |
|
|
20
|
+
| `authentication.credential.revoke` | A credential was invalidated |
|
|
21
|
+
| `authentication.password.change` | A principal changed its own password |
|
|
22
|
+
| `authentication.password.reset` | A password was reset, typically by an administrator or flow |
|
|
23
|
+
| `authentication.lockout.apply` | An account was locked after repeated failures |
|
|
24
|
+
| `authentication.lockout.release` | A lock was lifted |
|
|
25
|
+
|
|
26
|
+
Failed sign-ins use `authentication.login` with `outcome: failure`, never a separate name.
|
|
27
|
+
|
|
28
|
+
## 2. Context to populate
|
|
29
|
+
|
|
30
|
+
| Field | Guidance |
|
|
31
|
+
| -------------------------------- | ------------------------------------------------------------------------------------------ |
|
|
32
|
+
| `actor` | The principal that authenticated. For a failed attempt where identity is unproven, see §4. |
|
|
33
|
+
| `resource` | The session, credential or account that the operation concerned |
|
|
34
|
+
| `authentication.method` | The primary factor used |
|
|
35
|
+
| `authentication.mfa` | Whether a second factor was satisfied |
|
|
36
|
+
| `authentication.provider` | The identity provider that asserted the identity |
|
|
37
|
+
| `authentication.sessionId` | A correlation identifier, never a usable token |
|
|
38
|
+
| `request.ipAddress`, `userAgent` | Only where the audit purpose requires it; both are personal data in context |
|
|
39
|
+
| `controlCategories` | `authentication-logging` |
|
|
40
|
+
|
|
41
|
+
## 3. Outcomes and errors
|
|
42
|
+
|
|
43
|
+
A failed authentication MUST carry `event.error`. Recommended codes:
|
|
44
|
+
|
|
45
|
+
```text
|
|
46
|
+
invalid-credentials unknown-principal account-locked
|
|
47
|
+
account-disabled factor-required factor-invalid
|
|
48
|
+
session-expired provider-unavailable policy-denied
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
`event.error.message` MUST NOT disclose which part of a credential was wrong, whether an account
|
|
52
|
+
exists, or any value the principal submitted. `invalid-credentials` is the correct code for both a
|
|
53
|
+
wrong password and an unknown user, unless the operator has decided otherwise for their threat model.
|
|
54
|
+
|
|
55
|
+
## 4. Identifying the actor on a failed sign-in
|
|
56
|
+
|
|
57
|
+
A failed sign-in has no proven identity. Recommended handling:
|
|
58
|
+
|
|
59
|
+
- Where the submitted identifier resolves to a known principal, record that principal with the
|
|
60
|
+
identifier the system already uses. Do not record the submitted string.
|
|
61
|
+
- Where it does not resolve, record `actor` as `{"type": "unknown", "id": "..."}` with a producer-chosen
|
|
62
|
+
non-personal placeholder, such as a hash of the attempt or an attempt identifier.
|
|
63
|
+
- Never record the submitted username verbatim when it may be an email address, and never record the
|
|
64
|
+
submitted password in any form. See [privacy.md](../specification/privacy.md).
|
|
65
|
+
|
|
66
|
+
## 5. Credentials
|
|
67
|
+
|
|
68
|
+
Credential events record the **fact** of a credential operation. They MUST NOT record the credential.
|
|
69
|
+
|
|
70
|
+
```json
|
|
71
|
+
{
|
|
72
|
+
"event": {
|
|
73
|
+
"name": "authentication.credential.rotate",
|
|
74
|
+
"category": "authentication",
|
|
75
|
+
"outcome": "success"
|
|
76
|
+
},
|
|
77
|
+
"actor": { "type": "service", "id": "service-account-rotation-worker" },
|
|
78
|
+
"resource": { "type": "api-key", "id": "api-key-4471" },
|
|
79
|
+
"change": { "type": "update", "changedFields": ["secret"], "ticketId": "change-8812" },
|
|
80
|
+
"controlCategories": ["authentication-logging", "privileged-access"]
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## 6. Absence of authentication context
|
|
85
|
+
|
|
86
|
+
Background jobs, system operations and imported events legitimately have no `authentication` object.
|
|
87
|
+
Omitting it is correct; `method: anonymous` means something different. See
|
|
88
|
+
[authentication.md](../specification/authentication.md) §4.
|
|
89
|
+
|
|
90
|
+
## 7. Example
|
|
91
|
+
|
|
92
|
+
```json
|
|
93
|
+
{
|
|
94
|
+
"specVersion": "0.1",
|
|
95
|
+
"id": "018f1c40-1111-7222-8333-444455556666",
|
|
96
|
+
"time": "2026-03-20T08:12:04Z",
|
|
97
|
+
"event": {
|
|
98
|
+
"name": "authentication.login",
|
|
99
|
+
"category": "authentication",
|
|
100
|
+
"type": "login",
|
|
101
|
+
"outcome": "failure",
|
|
102
|
+
"severity": "medium",
|
|
103
|
+
"error": { "code": "factor-required", "type": "authentication", "retryable": true }
|
|
104
|
+
},
|
|
105
|
+
"actor": { "type": "user", "id": "user-2211" },
|
|
106
|
+
"resource": { "type": "session", "id": "session-attempt-88f1" },
|
|
107
|
+
"application": { "name": "identity-service", "environment": "production" },
|
|
108
|
+
"authentication": { "method": "password", "provider": "corporate-idp", "mfa": false },
|
|
109
|
+
"controlCategories": ["authentication-logging"]
|
|
110
|
+
}
|
|
111
|
+
```
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# Configuration and Change Events
|
|
2
|
+
|
|
3
|
+
**Specification version: 0.1 · Status: Experimental**
|
|
4
|
+
|
|
5
|
+
Categories: `configuration`, `deployment`
|
|
6
|
+
|
|
7
|
+
## 1. Recommended event names
|
|
8
|
+
|
|
9
|
+
### Configuration
|
|
10
|
+
|
|
11
|
+
| Name | Operation |
|
|
12
|
+
| -------------------------------- | -------------------------------- |
|
|
13
|
+
| `configuration.setting.create` | A setting was introduced |
|
|
14
|
+
| `configuration.setting.update` | A setting was changed |
|
|
15
|
+
| `configuration.setting.delete` | A setting was removed |
|
|
16
|
+
| `configuration.secret.rotate` | A secret was replaced |
|
|
17
|
+
| `configuration.secret.access` | A secret was read by a principal |
|
|
18
|
+
| `configuration.feature.toggle` | A feature flag was switched |
|
|
19
|
+
| `configuration.policy.update` | A policy definition was changed |
|
|
20
|
+
| `configuration.retention.update` | A retention rule was changed |
|
|
21
|
+
|
|
22
|
+
### Deployment and release
|
|
23
|
+
|
|
24
|
+
| Name | Operation |
|
|
25
|
+
| --------------------------------- | ------------------------------------- |
|
|
26
|
+
| `deployment.release.create` | A release was prepared |
|
|
27
|
+
| `deployment.release.approve` | A release was approved for deployment |
|
|
28
|
+
| `deployment.release.deploy` | A release was deployed |
|
|
29
|
+
| `deployment.release.rollback` | A deployment was reverted |
|
|
30
|
+
| `deployment.infrastructure.apply` | An infrastructure change was applied |
|
|
31
|
+
|
|
32
|
+
### Change management
|
|
33
|
+
|
|
34
|
+
| Name | Operation |
|
|
35
|
+
| ------------------------ | ----------------------------- |
|
|
36
|
+
| `change.request.create` | A change request was raised |
|
|
37
|
+
| `change.request.approve` | A change request was approved |
|
|
38
|
+
| `change.request.reject` | A change request was refused |
|
|
39
|
+
| `change.request.close` | A change request was closed |
|
|
40
|
+
|
|
41
|
+
## 2. Configuration changes are high-value audit events
|
|
42
|
+
|
|
43
|
+
Configuration is where controls are switched off. A change to session lifetime, a retention rule, an
|
|
44
|
+
audit destination or a policy version can undo every other control in the system, and it usually
|
|
45
|
+
leaves no trace in business data.
|
|
46
|
+
|
|
47
|
+
Producers SHOULD:
|
|
48
|
+
|
|
49
|
+
- Record every change to security-relevant configuration, including failed attempts.
|
|
50
|
+
- Set `event.severity` to `high` or `critical` for production security configuration.
|
|
51
|
+
- Include `configuration-integrity` in `controlCategories`, and `change-approval` where approval
|
|
52
|
+
applies.
|
|
53
|
+
- Record `application.environment` accurately. The same change is routine in `development` and
|
|
54
|
+
critical in `production`.
|
|
55
|
+
|
|
56
|
+
## 3. Before and after
|
|
57
|
+
|
|
58
|
+
Configuration is the case where before and after values are usually both safe and necessary — a
|
|
59
|
+
reviewer needs to know that session lifetime went from 720 minutes to 60, not merely that it changed.
|
|
60
|
+
|
|
61
|
+
Nonetheless:
|
|
62
|
+
|
|
63
|
+
- Record only the settings that changed, named in `change.changedFields`.
|
|
64
|
+
- MUST NOT record secret values. A secret rotation records `changedFields: ["secret"]` and nothing
|
|
65
|
+
more. See [change-model.md](../specification/change-model.md) §5.
|
|
66
|
+
- Where a setting's value is itself sensitive — an allowlist of addresses, an internal endpoint —
|
|
67
|
+
record a hash or omit the value.
|
|
68
|
+
|
|
69
|
+
## 4. Correlating change to authorization
|
|
70
|
+
|
|
71
|
+
The three questions matter most here:
|
|
72
|
+
|
|
73
|
+
| Field | Records |
|
|
74
|
+
| ----------------- | --------------------------------------------- |
|
|
75
|
+
| `authorization` | The policy decision that permitted the change |
|
|
76
|
+
| `approval` | The human decision that sanctioned it |
|
|
77
|
+
| `change.ticketId` | The change record it was performed under |
|
|
78
|
+
| `reason` | Why it was needed |
|
|
79
|
+
|
|
80
|
+
An emergency change with `approval.status` of `pending` and a successful outcome is a control bypass.
|
|
81
|
+
Producers MUST record it accurately rather than suppressing it, and SHOULD record the justification in
|
|
82
|
+
`reason`. That combination is exactly what a post-incident review looks for.
|
|
83
|
+
|
|
84
|
+
## 5. Deployments
|
|
85
|
+
|
|
86
|
+
`deployment.release.deploy` describes what was deployed, where, and from which source:
|
|
87
|
+
|
|
88
|
+
```json
|
|
89
|
+
{
|
|
90
|
+
"resource": { "type": "deployment", "id": "deployment-2026-03-16-3" },
|
|
91
|
+
"relatedResources": [{ "type": "service", "id": "service-checkout" }],
|
|
92
|
+
"application": { "name": "delivery-service", "environment": "production" },
|
|
93
|
+
"change": {
|
|
94
|
+
"type": "execute",
|
|
95
|
+
"deploymentId": "deployment-2026-03-16-3",
|
|
96
|
+
"ticketId": "change-9910"
|
|
97
|
+
},
|
|
98
|
+
"metadata": { "sourceRevision": "9f2a4c1", "releaseVersion": "9.0.3", "strategy": "rolling" }
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
`application` describes the system that **performed** the deployment. The deployed service is a
|
|
103
|
+
resource. Producers routinely get this backwards.
|
|
104
|
+
|
|
105
|
+
Rollbacks SHOULD reference the deployment being reverted in `metadata`, so that the pair can be found
|
|
106
|
+
together.
|
|
107
|
+
|
|
108
|
+
## 6. Secret access
|
|
109
|
+
|
|
110
|
+
`configuration.secret.access` records that a principal read a secret. It MUST NOT record the secret,
|
|
111
|
+
any part of it, or a hash of it that would allow verification against a guess.
|
|
112
|
+
|
|
113
|
+
Where secret access is routine for a workload, producers SHOULD consider whether recording every read
|
|
114
|
+
is useful, or whether recording grants, rotations and out-of-pattern access is more valuable. Volume
|
|
115
|
+
that nobody reviews is not a control.
|
|
116
|
+
|
|
117
|
+
## 7. Example
|
|
118
|
+
|
|
119
|
+
See
|
|
120
|
+
[examples/valid/privileged-configuration-change.json](../examples/valid/privileged-configuration-change.json)
|
|
121
|
+
for a complete privileged configuration change with approval, sanitized before and after values, and
|
|
122
|
+
an integrity chain entry.
|
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
# Semantic Conventions: Correlation and Tracing
|
|
2
|
+
|
|
3
|
+
**Specification version: 0.1 · Status: Experimental · This document: Informative**
|
|
4
|
+
|
|
5
|
+
> How to populate the identifiers that let an operator find every audit event, application log and
|
|
6
|
+
> error belonging to one operation — across services, messages and trace boundaries.
|
|
7
|
+
|
|
8
|
+
The normative rules referenced here live in
|
|
9
|
+
[specification/event-model.md §10](../specification/event-model.md). This document explains which
|
|
10
|
+
identifier to use, and why the distinctions matter.
|
|
11
|
+
|
|
12
|
+
**OpenAuditModel is not a tracing system.** It records no spans, builds no trace tree and stores no
|
|
13
|
+
timing. It carries the identifiers a tracing system already produced, so that an audit event can be
|
|
14
|
+
joined to whatever the operator already runs — and so that correlation still works when nothing is
|
|
15
|
+
running at all.
|
|
16
|
+
|
|
17
|
+
## 1. The five identifiers
|
|
18
|
+
|
|
19
|
+
| Field | Identifies | Stable across services? | Stable across messages? |
|
|
20
|
+
| ------------------------ | --------------------------------- | ----------------------- | ----------------------- |
|
|
21
|
+
| `/id` | one audit event | no — unique per event | no |
|
|
22
|
+
| `/request/requestId` | one inbound request | usually no | not applicable |
|
|
23
|
+
| `/request/traceId` | one distributed execution | **yes** | yes, if continued |
|
|
24
|
+
| `/request/spanId` | one operation inside that trace | no — new per operation | no |
|
|
25
|
+
| `/request/correlationId` | one logical or business operation | **yes** | **yes** |
|
|
26
|
+
|
|
27
|
+
### 1.1 `/id` — the audit event identifier
|
|
28
|
+
|
|
29
|
+
Unique to a single audit record. Two events never share it, and it is the field a consumer
|
|
30
|
+
deduplicates on ([delivery.md](../specification/delivery.md)). It identifies the _record_, not the
|
|
31
|
+
operation the record describes.
|
|
32
|
+
|
|
33
|
+
### 1.2 `/request/requestId` — the current inbound request
|
|
34
|
+
|
|
35
|
+
The request currently being served. Scoped to one service handling one call: a downstream service
|
|
36
|
+
generally has its own, and a background job or message consumer has none at all.
|
|
37
|
+
|
|
38
|
+
`requestId` SHOULD NOT be propagated into asynchronous messages. A message consumer is not serving
|
|
39
|
+
the request that produced the message, and copying the value there makes two unrelated units of work
|
|
40
|
+
look like one.
|
|
41
|
+
|
|
42
|
+
Where the value comes from a caller-supplied header such as `X-Request-ID`, see §5.3.
|
|
43
|
+
|
|
44
|
+
### 1.3 `/request/traceId` — the distributed technical execution
|
|
45
|
+
|
|
46
|
+
The end-to-end technical execution, in the W3C Trace Context sense. It stays the same across every
|
|
47
|
+
service that participates in one execution, which is what makes it the primary join key between an
|
|
48
|
+
audit event and application logs.
|
|
49
|
+
|
|
50
|
+
A trace ends when the execution ends. It does not span a business process that pauses for an
|
|
51
|
+
overnight batch or a human approval — that is what `correlationId` is for.
|
|
52
|
+
|
|
53
|
+
### 1.4 `/request/spanId` — the active operation
|
|
54
|
+
|
|
55
|
+
The specific operation within the trace that produced this event. New for every operation, so it is a
|
|
56
|
+
locator inside a trace rather than a grouping key.
|
|
57
|
+
|
|
58
|
+
`spanId` SHOULD NOT be recorded without `traceId`. A span identifier alone cannot be resolved: there
|
|
59
|
+
is nothing to look it up in. This matches the OpenTelemetry log data model, which states that if
|
|
60
|
+
SpanId is present TraceId should be too. The schema does not enforce it, because a producer that has
|
|
61
|
+
only half the context is better served by recording what it has than by dropping the event.
|
|
62
|
+
|
|
63
|
+
### 1.5 `/request/correlationId` — the logical operation
|
|
64
|
+
|
|
65
|
+
The logical operation, workflow, job, conversation or business process the event belongs to. It may
|
|
66
|
+
span services, messages, trace boundaries and time.
|
|
67
|
+
|
|
68
|
+
This is the field that survives when nothing else does. When a consumer starts a fresh trace, the
|
|
69
|
+
`traceId` changes and the `requestId` is absent; `correlationId` is what still says _this belongs to
|
|
70
|
+
order 2026-004418_. A system with no tracing at all can populate `correlationId` alone and still get
|
|
71
|
+
useful correlation.
|
|
72
|
+
|
|
73
|
+
Because it is the identifier most likely to be set from business data, it is also the one most likely
|
|
74
|
+
to leak personal data. See §5.4.
|
|
75
|
+
|
|
76
|
+
## 2. Where the values come from
|
|
77
|
+
|
|
78
|
+
Obtain `traceId` and `spanId` from the **active trace context**, not by generating them for the audit
|
|
79
|
+
event. An identifier minted for the audit record correlates the record with nothing: it is
|
|
80
|
+
well-formed, passes validation, and matches no span in any backend.
|
|
81
|
+
|
|
82
|
+
| Situation | `traceId` | `spanId` |
|
|
83
|
+
| ---------------------------------- | ------------------- | ---------------- |
|
|
84
|
+
| Tracing active | from active context | from active span |
|
|
85
|
+
| Trace context received, not active | from `traceparent` | omit |
|
|
86
|
+
| No tracing | omit | omit |
|
|
87
|
+
|
|
88
|
+
Omitting a field is always correct. Inventing one is not.
|
|
89
|
+
|
|
90
|
+
### 2.1 Do not store the raw headers
|
|
91
|
+
|
|
92
|
+
Parse `traceparent`, extract the trace and span identifiers, and discard the rest. The version byte
|
|
93
|
+
and the sampled flag describe the tracing system's own decisions, not the audited operation.
|
|
94
|
+
|
|
95
|
+
`tracestate` SHOULD NOT be stored at all. It is vendor-specific, may carry tenant or account
|
|
96
|
+
identifiers, and is the only unbounded free-text value in the set. W3C Trace Context forbids putting
|
|
97
|
+
personal data in it, but that obligation binds the system that _writes_ the header — an audit
|
|
98
|
+
producer reading it cannot assume it was honoured.
|
|
99
|
+
|
|
100
|
+
The schema deliberately provides nowhere to put either header.
|
|
101
|
+
|
|
102
|
+
## 3. Correlation without tracing
|
|
103
|
+
|
|
104
|
+
None of this requires OpenTelemetry, or any tracing at all.
|
|
105
|
+
|
|
106
|
+
A system with no tracer populates `correlationId` — a job run identifier, a workflow instance, an
|
|
107
|
+
order number's non-reversible surrogate — and omits `traceId` and `spanId`. Correlation across
|
|
108
|
+
services still works, because `correlationId` is propagated by the application rather than by a
|
|
109
|
+
tracing library.
|
|
110
|
+
|
|
111
|
+
Where even that is unavailable, `/application` still identifies the producing service and
|
|
112
|
+
`/organization` its tenant, which is the same resource-level fallback OpenTelemetry uses for logs
|
|
113
|
+
with no trace context.
|
|
114
|
+
|
|
115
|
+
## 4. Scenarios
|
|
116
|
+
|
|
117
|
+
### 4.1 HTTP request
|
|
118
|
+
|
|
119
|
+
```json
|
|
120
|
+
{
|
|
121
|
+
"id": "01J8ZC7Q2E4N6R8T0V2X4Z6A80",
|
|
122
|
+
"request": {
|
|
123
|
+
"requestId": "req-8f2c41",
|
|
124
|
+
"traceId": "4bf92f3577b34da6a3ce929d0e0e4736",
|
|
125
|
+
"spanId": "00f067aa0ba902b7"
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### 4.2 HTTP request that publishes a message
|
|
131
|
+
|
|
132
|
+
The same trace continues into the producer; `correlationId` is established here and travels with the
|
|
133
|
+
message.
|
|
134
|
+
|
|
135
|
+
```json
|
|
136
|
+
{
|
|
137
|
+
"id": "01J8ZC7Q2E4N6R8T0V2X4Z6A81",
|
|
138
|
+
"request": {
|
|
139
|
+
"requestId": "req-8f2c41",
|
|
140
|
+
"correlationId": "order-2026-004418",
|
|
141
|
+
"traceId": "4bf92f3577b34da6a3ce929d0e0e4736",
|
|
142
|
+
"spanId": "1a2b3c4d5e6f7081"
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Propagate `traceparent` and the business `correlationId` in the message headers. Do not propagate
|
|
148
|
+
`requestId`.
|
|
149
|
+
|
|
150
|
+
### 4.3 Consumer that continues the trace
|
|
151
|
+
|
|
152
|
+
The trace and the correlation both survive. There is no `requestId`: no request is being served.
|
|
153
|
+
|
|
154
|
+
```json
|
|
155
|
+
{
|
|
156
|
+
"id": "01J8ZC7Q2E4N6R8T0V2X4Z6A82",
|
|
157
|
+
"request": {
|
|
158
|
+
"correlationId": "order-2026-004418",
|
|
159
|
+
"traceId": "4bf92f3577b34da6a3ce929d0e0e4736",
|
|
160
|
+
"spanId": "2b3c4d5e6f708192"
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### 4.4 Consumer that starts a new trace
|
|
166
|
+
|
|
167
|
+
Some consumers deliberately begin a new trace — long-running processors, or systems where the
|
|
168
|
+
producing trace has already been closed. The technical execution is new; the business operation is
|
|
169
|
+
not.
|
|
170
|
+
|
|
171
|
+
```json
|
|
172
|
+
{
|
|
173
|
+
"id": "01J8ZC7Q2E4N6R8T0V2X4Z6A83",
|
|
174
|
+
"request": {
|
|
175
|
+
"correlationId": "order-2026-004418",
|
|
176
|
+
"traceId": "9f8e7d6c5b4a39281706f5e4d3c2b1a0",
|
|
177
|
+
"spanId": "3c4d5e6f708192a3"
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
`correlationId` is the only identifier shared with §4.2. Without it, the business operation cannot be
|
|
183
|
+
reconstructed at all — which is the reason the field exists separately from `traceId`.
|
|
184
|
+
|
|
185
|
+
### 4.5 Workflow spanning several services and days
|
|
186
|
+
|
|
187
|
+
An approval that waits for a human outlives any trace. `correlationId` carries the process;
|
|
188
|
+
`/approval/workflowId` identifies the workflow definition or instance.
|
|
189
|
+
|
|
190
|
+
```json
|
|
191
|
+
{
|
|
192
|
+
"id": "01J8ZC7Q2E4N6R8T0V2X4Z6A84",
|
|
193
|
+
"request": {
|
|
194
|
+
"requestId": "req-c41f09",
|
|
195
|
+
"correlationId": "access-request-2026-0912",
|
|
196
|
+
"traceId": "5c6d7e8f90a1b2c3d4e5f60718293a4b",
|
|
197
|
+
"spanId": "4d5e6f7081920304"
|
|
198
|
+
},
|
|
199
|
+
"approval": {
|
|
200
|
+
"workflowId": "wf-access-review-v3",
|
|
201
|
+
"requestId": "ar-2026-0912"
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
Both `requestId` fields are correct and mean different things: `/request/requestId` is the HTTP call
|
|
207
|
+
being served, `/approval/requestId` is the approval record. See §6.1.
|
|
208
|
+
|
|
209
|
+
### 4.6 Scheduled background job
|
|
210
|
+
|
|
211
|
+
No request, so no `requestId`. The job run is the logical operation.
|
|
212
|
+
|
|
213
|
+
```json
|
|
214
|
+
{
|
|
215
|
+
"id": "01J8ZC7Q2E4N6R8T0V2X4Z6A85",
|
|
216
|
+
"request": {
|
|
217
|
+
"correlationId": "nightly-reconciliation-2026-07-30"
|
|
218
|
+
},
|
|
219
|
+
"metadata": {
|
|
220
|
+
"recordsProcessed": 4187
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
Every event emitted by one job run SHOULD share that `correlationId`. Do not use
|
|
226
|
+
`/integrity/batchId` for this; see §6.2.
|
|
227
|
+
|
|
228
|
+
### 4.7 Failed downstream operation
|
|
229
|
+
|
|
230
|
+
A failure belongs to the same execution as the work that failed, so the trace context is preserved.
|
|
231
|
+
|
|
232
|
+
```json
|
|
233
|
+
{
|
|
234
|
+
"id": "01J8ZC7Q2E4N6R8T0V2X4Z6A87",
|
|
235
|
+
"event": {
|
|
236
|
+
"outcome": "failure",
|
|
237
|
+
"error": { "code": "upstream-timeout" }
|
|
238
|
+
},
|
|
239
|
+
"request": {
|
|
240
|
+
"correlationId": "order-2026-004418",
|
|
241
|
+
"traceId": "4bf92f3577b34da6a3ce929d0e0e4736",
|
|
242
|
+
"spanId": "6f70819203040506"
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
## 5. Trust and privacy
|
|
248
|
+
|
|
249
|
+
### 5.1 Correlation is not authorization
|
|
250
|
+
|
|
251
|
+
Correlation identifiers are **observational metadata**. They MUST NOT be treated as proof of
|
|
252
|
+
identity, authorization, authenticity, integrity or tenant isolation
|
|
253
|
+
([event-model.md §10.3](../specification/event-model.md)).
|
|
254
|
+
|
|
255
|
+
Every one of them can be supplied by a caller, guessed, replayed or copied between tenants. The model
|
|
256
|
+
has dedicated fields that do carry those guarantees — `/authentication`, `/authorization` and
|
|
257
|
+
`/integrity` — and a correlation identifier is not a substitute for any of them. Two events sharing a
|
|
258
|
+
`traceId` is evidence that someone propagated a header, not that they belong to the same principal.
|
|
259
|
+
|
|
260
|
+
### 5.2 Trace context is not causality
|
|
261
|
+
|
|
262
|
+
A shared `traceId` means two events belong to the same execution. It does not say which caused which,
|
|
263
|
+
in what order, or through what path. Ordering within a producer comes from `/sequence`; causal
|
|
264
|
+
relationships between messages are not modelled in the core schema at all (§7).
|
|
265
|
+
|
|
266
|
+
### 5.3 Inbound identifiers are untrusted input
|
|
267
|
+
|
|
268
|
+
A `correlationId` or `requestId` taken from a request header is caller-controlled. A producer SHOULD
|
|
269
|
+
either generate its own value, or record the supplied one while never relying on it — an attacker who
|
|
270
|
+
can set `X-Correlation-ID` can attach their own activity to another operation's identifier.
|
|
271
|
+
|
|
272
|
+
The schema bounds the damage: `identifier` caps at 256 characters and rejects leading and trailing
|
|
273
|
+
whitespace, so an identifier cannot become a log-injection payload or an unbounded index key.
|
|
274
|
+
|
|
275
|
+
### 5.4 Do not put sensitive values in identifiers
|
|
276
|
+
|
|
277
|
+
Personal data, credentials, account numbers and sensitive business values SHOULD NOT be used as
|
|
278
|
+
correlation identifiers. An email address used as a `correlationId` is personal data in the audit
|
|
279
|
+
store, in every application log that echoes it, and in every index built over it.
|
|
280
|
+
|
|
281
|
+
Use a non-reversible surrogate — a random identifier, or a keyed hash of the business key — and keep
|
|
282
|
+
the business value in the field that was designed for it, where it can be redacted independently.
|
|
283
|
+
|
|
284
|
+
The privacy linter does **not** inspect `/request/correlationId` or `/request/requestId`
|
|
285
|
+
([privacy.md §6.6](../specification/privacy.md)). These fields hold high-entropy values by design and
|
|
286
|
+
scanning them would report a finding on every well-formed event. Nothing will warn a producer that
|
|
287
|
+
put an email address there.
|
|
288
|
+
|
|
289
|
+
### 5.5 Cardinality
|
|
290
|
+
|
|
291
|
+
Every distinct correlation identifier is an index entry. Deriving one per record in a large batch, or
|
|
292
|
+
per retry, produces indexes that cost more than the queries they serve. Prefer one identifier per
|
|
293
|
+
logical operation.
|
|
294
|
+
|
|
295
|
+
## 6. Identifiers that are not correlation identifiers
|
|
296
|
+
|
|
297
|
+
### 6.1 `/approval/requestId` is not `/request/requestId`
|
|
298
|
+
|
|
299
|
+
`/request/requestId` identifies the inbound call being served. `/approval/requestId` identifies an
|
|
300
|
+
approval record that may exist for days and be referenced by many calls. An event may carry both; see
|
|
301
|
+
§4.5.
|
|
302
|
+
|
|
303
|
+
### 6.2 `/integrity/batchId` is not a job or processing batch
|
|
304
|
+
|
|
305
|
+
`/integrity/batchId` identifies an **integrity sealing or verification batch** — the group of events
|
|
306
|
+
sealed together when digests were computed. It does **not** identify a job run, a processing batch, an
|
|
307
|
+
import batch or any business operation, and its boundaries are set by the sealing process rather than
|
|
308
|
+
by the work.
|
|
309
|
+
|
|
310
|
+
Use `/request/correlationId` for job runs, processing batches and imports (§4.6).
|
|
311
|
+
|
|
312
|
+
Likewise `/integrity/chainId` identifies a tamper-evidence chain, and `/sequence` orders events within
|
|
313
|
+
one producer, partition or chain. Neither is a correlation identifier.
|
|
314
|
+
|
|
315
|
+
### 6.3 `/authentication/sessionId`
|
|
316
|
+
|
|
317
|
+
A session correlation identifier, scoped to one authenticated session. It is not an operation
|
|
318
|
+
identifier, and it must never be a usable session token or cookie value.
|
|
319
|
+
|
|
320
|
+
## 7. Experimental: messaging causation
|
|
321
|
+
|
|
322
|
+
**Experimental. Not a stable core field, not a required convention, and not part of the v0.1
|
|
323
|
+
conformance surface.** Nothing validates it, and it may change or be withdrawn.
|
|
324
|
+
|
|
325
|
+
The core schema deliberately has no `causationId`. A shared `traceId` groups events; it does not say
|
|
326
|
+
which event caused which. Where that relationship must be recorded, producers MAY use the reserved
|
|
327
|
+
extension:
|
|
328
|
+
|
|
329
|
+
```json
|
|
330
|
+
{
|
|
331
|
+
"extensions": {
|
|
332
|
+
"org.openauditmodel.correlation": {
|
|
333
|
+
"causes": [{ "type": "audit-event", "id": "01J8ZC7Q2E4N6R8T0V2X4Z6A81" }]
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
`causes` is an **array** because a single scalar cannot express fan-in — an operation triggered by
|
|
340
|
+
several messages has several causes, and OpenTelemetry reached the same conclusion when it chose span
|
|
341
|
+
links over a single parent for messaging. `type` names the kind of cause (`audit-event`, `message`);
|
|
342
|
+
`id` is the identifier of that cause.
|
|
343
|
+
|
|
344
|
+
It stays an extension rather than a core field for two reasons. There is not enough production
|
|
345
|
+
adoption evidence to freeze its shape, and a field whose shape later has to change would be a
|
|
346
|
+
breaking change to a published identifier — which the project does not permit. An extension can
|
|
347
|
+
evolve; `/request` cannot.
|
|
348
|
+
|
|
349
|
+
Extension keys are inspected by the privacy linter, so ordinary care about their contents applies.
|
|
350
|
+
|
|
351
|
+
## 8. Logging interoperability
|
|
352
|
+
|
|
353
|
+
The point of all of this is that one query returns everything. For that, application logs must carry
|
|
354
|
+
the **same values** under whatever names their stack already uses.
|
|
355
|
+
|
|
356
|
+
| OpenAuditModel | OpenTelemetry | ECS |
|
|
357
|
+
| ------------------------ | ------------- | ---------- |
|
|
358
|
+
| `/request/traceId` | `trace_id` | `trace.id` |
|
|
359
|
+
| `/request/spanId` | `span_id` | `span.id` |
|
|
360
|
+
| `/request/correlationId` | attribute | label |
|
|
361
|
+
|
|
362
|
+
The values are byte-identical across all three, so a log backend needs a field alias, not a
|
|
363
|
+
transformation. Emit `traceId` and `spanId` into application logs, error logs, producer logs and
|
|
364
|
+
consumer logs, and a search for one trace identifier returns the audit events alongside them.
|
|
365
|
+
|
|
366
|
+
OpenAuditModel keeps its own `camelCase` naming rather than adopting `trace.id`, because that naming
|
|
367
|
+
is used consistently throughout the schema and correlation is not a good place to make it
|
|
368
|
+
inconsistent. See [mappings/](../mappings/) for the full informative mappings.
|
|
369
|
+
|
|
370
|
+
## 9. Sampling independence
|
|
371
|
+
|
|
372
|
+
**Audit event generation MUST NOT depend on trace sampling**
|
|
373
|
+
([event-model.md §10.3](../specification/event-model.md)).
|
|
374
|
+
|
|
375
|
+
Tracing is a sampled diagnostic; auditing is a complete record. An audit event emitted only when a
|
|
376
|
+
span is recording is a sampled audit log, and the gap is invisible until the missing event is the one
|
|
377
|
+
that matters.
|
|
378
|
+
|
|
379
|
+
| Situation | Behaviour |
|
|
380
|
+
| -------------------------------------- | ------------------------------------------------ |
|
|
381
|
+
| Trace sampled and stored | Record the identifiers; they resolve. |
|
|
382
|
+
| Trace exists but is not sampled | Record the identifiers anyway. |
|
|
383
|
+
| No tracing backend retains the trace | Record them anyway. The audit event is complete. |
|
|
384
|
+
| Identifier resolves to no stored trace | Expected. Not an error, and not a defect. |
|
|
385
|
+
|
|
386
|
+
A `traceId` that leads nowhere is a normal outcome of sampling, and it still correlates the audit
|
|
387
|
+
event with the application logs of the same execution.
|