Data Model
Core types and structures used in the SIG protocol.
This page defines the core types and structures that make up the SIG protocol. All JSON field names use snake_case.
OreMetadata
The issuer’s metadata object, served at /.well-known/sig-metadata.json. This is the entry point for discovery.
| Field | Type | Required | Description |
|---|---|---|---|
spec_version | string | Yes | Protocol version. Must be "sig/0.1". |
issuer | string | Yes | The issuer’s canonical DID, e.g. "did:web:acme.example". |
jwks_uri | string | Yes | Absolute URL to the issuer’s JWKS endpoint. |
events_uri | string | Yes | Absolute URL to the NDJSON event feed. |
public_only | boolean | Yes | If true, all events in the feed are public. If false, the feed may contain events with "private" visibility. |
algorithms_supported | string[] | Yes | List of supported JWS algorithms. Must include "EdDSA". |
event_serialization | string | No | Serialization format for events. Defaults to "jws-flattened" if omitted. |
| Example: |
{
"spec_version": "sig/0.1",
"issuer": "did:web:acme.example",
"jwks_uri": "https://acme.example/.well-known/jwks.json",
"events_uri": "https://acme.example/.well-known/sig-events.ndjson",
"public_only": true,
"algorithms_supported": ["EdDSA"]
}
CommonEventFields
Every event in the SIG protocol contains these fields in its payload.
| Field | Type | Required | Description |
|---|---|---|---|
spec_version | string | Yes | Must be "sig/0.1". |
event_id | string | Yes | Unique identifier for this event. ULID or UUIDv7 recommended for natural ordering. |
event_type | string | Yes | The event type: "relationship.upsert" or "relationship.revoke". |
issuer | string | Yes | The issuer’s did:web identifier. Must match the DID in the metadata. |
issued_at | string | Yes | Timestamp of when the event was created. RFC 3339 format, UTC (e.g. "2026-02-27T15:30:00Z"). |
sequence | integer | Yes | Monotonically increasing sequence number. Starts at 1 for the first event. |
relationship_id | string | Yes | Identifier for the relationship this event pertains to. Stable across upserts and revocations of the same relationship. |
subject | string | Yes | The subject’s did:web identifier (the entity the relationship is about). |
visibility | string | Yes | Either "public" or "private". Determines whether this event is intended for public consumption. |
JWS Flattened Envelope
Each event is wrapped in a JWS JSON Flattened Serialization envelope. This is the on-wire format: each line in the NDJSON feed is one of these objects.
| Field | Type | Description |
|---|---|---|
protected | string | Base64url-encoded protected header (JSON object). |
payload | string | Base64url-encoded event payload (JSON object containing CommonEventFields + type-specific fields). |
signature | string | Base64url-encoded Ed25519 signature computed over ASCII(protected).ASCII(payload). |
Example:
{
"protected": "eyJhbGciOiJFZERTQSIsImtpZCI6ImtleS0xIiwidHlwIjoib3JlLWV2ZW50K2p3cyJ9",
"payload": "eyJzcGVjX3ZlcnNpb24iOiJvcmUvMC4xIiwiZXZlbnRfaWQiOiIwMUo...",
"signature": "5Oj2eA3J7JzG..."
}
To verify, the consumer:
- Base64url-decodes
protectedandpayload. - Looks up the signing key using the
kidfrom the decoded protected header. - Verifies the signature over the concatenation
ASCII(protected) || '.' || ASCII(payload).
Protected Header
The decoded protected header contains:
| Field | Type | Required | Description |
|---|---|---|---|
alg | string | Yes | Signing algorithm. Must be "EdDSA". |
kid | string | Yes | Key ID referencing a key in the issuer’s JWKS. |
typ | string | Yes | Media type. Must be "sig-event+jws". |
Example (decoded):
{
"alg": "EdDSA",
"kid": "key-1",
"typ": "sig-event+jws"
}
DerivedRelationshipState
This is not a wire type. It is the computed state of a relationship after replaying the event log. Verifiers and consumers build this structure locally.
| Field | Type | Description |
|---|---|---|
issuer | string | The issuer’s did:web identifier. |
relationship_id | string | The stable identifier for this relationship. |
subject | string | The subject’s did:web identifier. |
relationship_type | string | The type of relationship (e.g. "employee", "contractor"). |
roles | string[] | Current roles assigned to the subject. |
valid_from | string | null | Start of the validity window (RFC 3339 UTC), or null if unbounded. |
valid_until | string | null | End of the validity window (RFC 3339 UTC), or null if unbounded. |
status | string | One of "active", "revoked", or "expired". |
revoked_reason_code | string | null | If revoked, the reason code from the revocation event. |
revoked_effective_at | string | null | If revoked, the effective timestamp of the revocation (RFC 3339 UTC). |
last_sequence | integer | The sequence number of the most recent event affecting this relationship. |
Status is derived as follows:
active— The most recent event is arelationship.upsertand the current time is within the validity window (or the window is unbounded).expired— The most recent event is arelationship.upsertbut the current time is pastvalid_until.revoked— The most recent event affecting this relationship is arelationship.revoke.
FeedState
The aggregate state derived from replaying an entire event feed.
| Field | Type | Description |
|---|---|---|
by_relationship_id | HashMap<string, DerivedRelationshipState> | Map from relationship_id to the derived state for that relationship. |
last_sequence | integer | The highest sequence number seen in the feed. |
Consumers build a FeedState by iterating through the NDJSON feed in order, verifying each event’s signature, and applying each event to the appropriate entry in by_relationship_id. After processing, the map contains the current state of every relationship the issuer has attested to. |