Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 22 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ func main() {
data := events.EvidenceIngestedData{
ContentDigest: "sha256:abc123...",
ArtifactType: "application/vnd.gemara.evaluation-log+json",
StorageRef: "s3://evidence-bucket/my-app-v1/evaluation-log.json",
SubjectID: "my-app-v1",
}

Expand All @@ -46,7 +47,23 @@ func main() {

| Type | Constant | Description |
|------|----------|-------------|
| `dev.complytime.evidence.ingested` | `events.TypeEvidenceIngested` | Evidence accepted for processing |
| `dev.complytime.evidence.ingested` | `events.TypeEvidenceIngested` | Evidence accepted for processing, before validation |
| `dev.complytime.evidence.sealed` | `events.TypeEvidenceSealed` | Evidence validated and sealed into a unit of work |
| `dev.complytime.evidence.quarantined` | `events.TypeEvidenceQuarantined` | Evidence failed validation and was quarantined |

### Correlation

Events do not carry a dedicated correlation attribute.

- **Join an artifact's lifecycle events** (`ingested` →
`sealed`|`quarantined`) on the shared `contentDigest`. Caveat: a
`quarantined` event whose `reason` is a content-digest mismatch is the one case
where the digest is itself in doubt.

- **Trace across services** using the
[CloudEvents Distributed Tracing extension](https://github.com/cloudevents/spec/blob/main/cloudevents/extensions/distributed-tracing.md)
(`traceparent`/`tracestate`, W3C Trace Context). This is the observability
plane, set and propagated by the producer's tracing SDK, not by this library.

### Payload Examples

Expand All @@ -55,9 +72,9 @@ Example CloudEvents JSON payloads are in

| File | Description |
|------|-------------|
| [`evidence-ingested.json`](api/events/examples/evidence-ingested.json) | Common payload with required fields and `storageRef` |
| [`evidence-ingested-minimal.json`](api/events/examples/evidence-ingested-minimal.json) | Required data fields only (no optional fields) |
| [`evidence-ingested-with-shard.json`](api/events/examples/evidence-ingested-with-shard.json) | All fields including optional `shardId` |
| [`evidence-ingested.json`](api/events/examples/evidence-ingested.json) | Ingested outcome; all data fields including `storageRef` |
| [`evidence-sealed.json`](api/events/examples/evidence-sealed.json) | Sealed outcome after successful validation |
| [`evidence-quarantined.json`](api/events/examples/evidence-quarantined.json) | Quarantined outcome after failed validation, with `reason` |

These examples conform to the [JSON Schema](api/events/schemas/) and
[AsyncAPI spec](api/events/asyncapi.yaml). They are hand-maintained
Expand Down Expand Up @@ -114,7 +131,7 @@ task check

Example sentinel field:
```go
_ struct{} `asyncapi:"channel:core.widget.created.{ownerId},param:ownerId=The widget owner,stream:WIDGETS,type:dev.complytime.widget.created,send:Published when a widget is created,receive:Consume widget-created events,description:Widget creation pipeline"`
_ struct{} `asyncapi:"channel:complyapi.widget.created.{ownerId},param:ownerId=The widget owner,stream:WIDGETS,type:dev.complytime.widget.created,send:Published when a widget is created,receive:Consume widget-created events,description:Widget creation pipeline"`
```

2. Add `asyncapi-field:"description:..."` tags on each struct field for
Expand Down
187 changes: 182 additions & 5 deletions api/events/asyncapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
asyncapi: 3.0.0
info:
title: ComplyTime API Events
version: 0.1.0
version: 0.2.0
description: |-
Event contract for the ComplyTime evidence lifecycle.

Expand All @@ -21,20 +21,48 @@ servers:
protocol: nats
channels:
evidenceIngested:
address: core.evidence.ingested.{subjectId}
address: complyapi.evidence.ingested.{subjectId}
description: Evidence ingestion pipeline for compliance artifacts
parameters:
subjectId:
description: The compliance subject identifier
messages:
EvidenceIngested:
$ref: '#/components/messages/EvidenceIngested'
evidenceQuarantined:
address: complyapi.evidence.quarantined.{subjectId}
description: Evidence quarantine pipeline for compliance artifacts
parameters:
subjectId:
description: The compliance subject identifier
messages:
EvidenceQuarantined:
$ref: '#/components/messages/EvidenceQuarantined'
evidenceSealed:
address: complyapi.evidence.sealed.{subjectId}
description: Evidence sealing pipeline for compliance artifacts
parameters:
subjectId:
description: The compliance subject identifier
messages:
EvidenceSealed:
$ref: '#/components/messages/EvidenceSealed'
operations:
consumeEvidenceIngested:
action: receive
summary: Consume evidence-ingested events
channel:
$ref: '#/channels/evidenceIngested'
consumeEvidenceQuarantined:
action: receive
summary: Consume evidence-quarantined events
channel:
$ref: '#/channels/evidenceQuarantined'
consumeEvidenceSealed:
action: receive
summary: Consume evidence-sealed events
channel:
$ref: '#/channels/evidenceSealed'
publishEvidenceIngested:
action: send
summary: Published when evidence is accepted for processing; before sealing
Expand All @@ -44,6 +72,24 @@ operations:
nats:
x-stream: EVIDENCE
bindingVersion: 0.1.0
publishEvidenceQuarantined:
action: send
summary: Published when a worker fails to validate evidence and quarantines it
channel:
$ref: '#/channels/evidenceQuarantined'
bindings:
nats:
x-stream: EVIDENCE
bindingVersion: 0.1.0
publishEvidenceSealed:
action: send
summary: Published when a worker validates and seals evidence into a unit of work
channel:
$ref: '#/channels/evidenceSealed'
bindings:
nats:
x-stream: EVIDENCE
bindingVersion: 0.1.0
components:
messages:
EvidenceIngested:
Expand All @@ -52,6 +98,18 @@ components:
contentType: application/cloudevents+json
payload:
$ref: '#/components/schemas/EvidenceIngestedCloudEvent'
EvidenceQuarantined:
name: EvidenceQuarantined
title: Evidence Quarantined
contentType: application/cloudevents+json
payload:
$ref: '#/components/schemas/EvidenceQuarantinedCloudEvent'
EvidenceSealed:
name: EvidenceSealed
title: Evidence Sealed
contentType: application/cloudevents+json
payload:
$ref: '#/components/schemas/EvidenceSealedCloudEvent'
schemas:
EvidenceIngestedCloudEvent:
type: object
Expand Down Expand Up @@ -94,23 +152,142 @@ components:
description: |-
EvidenceIngestedData is the CloudEvents data payload for
evidence.ingested events.
required:
- contentDigest
- artifactType
- storageRef
- subjectId
properties:
artifactType:
type: string
description: Gemara artifact type
contentDigest:
type: string
description: SHA-256 digest of the evidence artifact
storageRef:
type: string
description: URI-style storage reference consumers use to fetch the evidence artifact (must include a scheme prefix, e.g. s3://, gcs://, locker://)
subjectId:
type: string
description: Compliance subject identifier
EvidenceQuarantinedCloudEvent:
type: object
description: CloudEvents v1.0 envelope for dev.complytime.evidence.quarantined
required:
- specversion
- id
- type
- source
- subject
- time
- datacontenttype
- data
properties:
data:
$ref: '#/components/schemas/EvidenceQuarantinedData'
datacontenttype:
type: string
const: application/json
id:
type: string
format: uuid
source:
type: string
description: URI identifying the producing service
specversion:
type: string
const: "1.0"
subject:
type: string
description: The compliance subject identifier
time:
type: string
format: date-time
type:
type: string
const: dev.complytime.evidence.quarantined
EvidenceQuarantinedData:
type: object
description: |-
EvidenceQuarantinedData is the CloudEvents data payload for
evidence.quarantined events, published when a worker fails to validate
an ingested artifact.
required:
- contentDigest
- artifactType
- subjectId
- reason
properties:
artifactType:
type: string
description: Gemara artifact type
contentDigest:
type: string
description: SHA-256 digest of the evidence artifact
shardId:
reason:
type: string
description: Why validation failed
subjectId:
type: string
description: Compliance subject identifier
EvidenceSealedCloudEvent:
type: object
description: CloudEvents v1.0 envelope for dev.complytime.evidence.sealed
required:
- specversion
- id
- type
- source
- subject
- time
- datacontenttype
- data
properties:
data:
$ref: '#/components/schemas/EvidenceSealedData'
datacontenttype:
type: string
description: Subject shard identifier (null when sharding is not configured)
const: application/json
id:
type: string
format: uuid
source:
type: string
description: URI identifying the producing service
specversion:
type: string
const: "1.0"
subject:
type: string
description: The compliance subject identifier
time:
type: string
format: date-time
type:
type: string
const: dev.complytime.evidence.sealed
EvidenceSealedData:
type: object
description: |-
EvidenceSealedData is the CloudEvents data payload for evidence.sealed
events. It carries the same evidence identity as EvidenceIngestedData
but signals that a worker has validated the artifact and sealed it into
a unit of work.
required:
- contentDigest
- artifactType
- storageRef
- subjectId
properties:
artifactType:
type: string
description: Gemara artifact type
contentDigest:
type: string
description: SHA-256 digest of the evidence artifact
storageRef:
type: string
description: Internal storage reference
description: URI-style storage reference to the sealed WORM evidence object consumers fetch (must include a scheme prefix, e.g. s3://, gcs://, locker://)
subjectId:
type: string
description: Compliance subject identifier
14 changes: 0 additions & 14 deletions api/events/examples/evidence-ingested-minimal.json

This file was deleted.

16 changes: 0 additions & 16 deletions api/events/examples/evidence-ingested-with-shard.json

This file was deleted.

2 changes: 1 addition & 1 deletion api/events/examples/evidence-ingested.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"data": {
"contentDigest": "sha256:9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
"artifactType": "application/vnd.gemara.evaluation-log+json",
"storageRef": "store/evidence/2026/08/20/a1b2c3d4",
"storageRef": "locker://store/evidence/2026/08/20/a1b2c3d4",
"subjectId": "my-app-v1"
}
}
15 changes: 15 additions & 0 deletions api/events/examples/evidence-quarantined.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"specversion": "1.0",
"id": "c3d4e5f6-a7b8-9012-cdef-123456789012",
"type": "dev.complytime.evidence.quarantined",
"source": "complytime-worker",
"subject": "my-app-v1",
"time": "2026-08-20T14:32:00Z",
"datacontenttype": "application/json",
"data": {
"contentDigest": "sha256:9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
"artifactType": "application/vnd.gemara.evaluation-log+json",
"subjectId": "my-app-v1",
"reason": "content digest mismatch"
}
}
15 changes: 15 additions & 0 deletions api/events/examples/evidence-sealed.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"specversion": "1.0",
"id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
"type": "dev.complytime.evidence.sealed",
"source": "complytime-worker",
"subject": "my-app-v1",
"time": "2026-08-20T14:31:00Z",
"datacontenttype": "application/json",
"data": {
"contentDigest": "sha256:9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
"artifactType": "application/vnd.gemara.evaluation-log+json",
"storageRef": "s3://evidence/9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
"subjectId": "my-app-v1"
}
}
7 changes: 2 additions & 5 deletions api/events/schemas/EvidenceIngestedData.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,8 @@
"description": "SHA-256 digest of the evidence artifact",
"type": "string"
},
"shardId": {
"description": "Subject shard identifier (null when sharding is not configured)",
"type": "string"
},
"storageRef": {
"description": "Internal storage reference",
"description": "URI-style storage reference consumers use to fetch the evidence artifact (must include a scheme prefix, e.g. s3://, gcs://, locker://)",
"type": "string"
},
"subjectId": {
Expand All @@ -28,6 +24,7 @@
"required": [
"contentDigest",
"artifactType",
"storageRef",
"subjectId"
],
"type": "object"
Expand Down
Loading
Loading