Skip to content

spanforge.event

Core event envelope and tag container for spanforge.

This module provides the Event class (the immutable event envelope) and the Tags class (an immutable str → str mapping for arbitrary metadata).

See the Events User Guide for full usage examples.


Tags

class Tags(**kwargs: str)

An immutable, validated str → str mapping attached to an event.

All keys and values must be non-empty strings. Tags is frozen after construction — there are no mutation methods.

Args:

ParameterTypeDescription
**kwargsstrArbitrary key-value string pairs. Both key and value must be non-empty strings.

Raises: SchemaValidationError — if any key or value is not a non-empty string.

Example:

tags = Tags(env="production", model="gpt-4o")
tags.get("env")       # "production"
tags.to_dict()        # {"env": "production", "model": "gpt-4o"}

Methods

get(key: str, default: Optional[str] = None) -> Optional[str]

Return the value for key, or default if the key is absent.

keys() -> KeysView[str]

Return a view of all tag keys.

values() -> ValuesView[str]

Return a view of all tag values.

items() -> ItemsView[str, str]

Return a view of all (key, value) pairs.

to_dict() -> Dict[str, str]

Return a plain dict copy of the tags.


Event

class Event(
    *,
    event_type: str,
    source: str,
    payload: Dict[str, Any],
    schema_version: str = "2.0",
    event_id: Optional[str] = None,
    timestamp: Optional[str] = None,
    trace_id: Optional[str] = None,
    span_id: Optional[str] = None,
    parent_span_id: Optional[str] = None,
    org_id: Optional[str] = None,
    team_id: Optional[str] = None,
    actor_id: Optional[str] = None,
    session_id: Optional[str] = None,
    tags: Optional[Tags] = None,
    checksum: Optional[str] = None,
    signature: Optional[str] = None,
    prev_id: Optional[str] = None,
)

Immutable, validated event envelope.

All fields are read-only properties after construction. event_id and timestamp are auto-generated (ULID and UTC ISO-8601 respectively) if not provided.

Args:

ParameterTypeDefaultDescription
event_typestrEvent type string. Must be either a registered first-party EventType value (RFC Appendix B) or a valid reverse-domain custom type outside llm.* (e.g. x.company.entity.action).
sourcestrTool name + full semver, e.g. "llm-trace@0.3.1".
payloadDict[str, Any]Non-empty dict of event-type-specific data.
schema_versionstr"2.0"Schema version. Allowed values: "1.0" and "2.0" only.
event_idstr | NoneNone26-character ULID. Auto-generated if None.
timestampstr | NoneNoneUTC ISO-8601 timestamp. Auto-generated if None.
trace_idstr | NoneNoneOpenTelemetry trace ID — 32 lowercase hex chars.
span_idstr | NoneNoneOpenTelemetry span ID — 16 lowercase hex chars.
parent_span_idstr | NoneNoneParent span ID — 16 lowercase hex chars.
org_idstr | NoneNoneOrganisation identifier.
team_idstr | NoneNoneTeam identifier within the organisation.
actor_idstr | NoneNoneUser or service actor identifier.
session_idstr | NoneNoneSession or conversation identifier.
tagsTags | NoneNoneArbitrary string key-value metadata.
checksumstr | NoneNoneSHA-256 payload checksum (set by signing.sign()).
signaturestr | NoneNoneHMAC-SHA256 audit chain signature (set by signing.sign()).
prev_idstr | NoneNoneULID of the preceding event in the audit chain.

Raises: SchemaValidationError — if any supplied field value is invalid.

Example:

from spanforge import Event, EventType

event = Event(
    event_type=EventType.TRACE_SPAN_COMPLETED,
    source="llm-trace@0.3.1",
    payload={"span_name": "run", "status": "ok"},
    org_id="acme",
    tags=Tags(env="production"),
)

Properties

All properties are read-only.

PropertyTypeDescription
schema_versionstrSchema version string ("1.0" or "2.0").
event_idstr26-character ULID event identifier.
event_typestrNamespaced event type string.
timestampstrUTC ISO-8601 timestamp string.
sourcestrSource tool and version string.
payloadDict[str, Any]Read-only mapping view (MappingProxyType) of the payload.
trace_idstr | None32-char hex OpenTelemetry trace ID.
span_idstr | None16-char hex OpenTelemetry span ID.
parent_span_idstr | None16-char hex parent span ID.
org_idstr | NoneOrganisation identifier.
team_idstr | NoneTeam identifier.
actor_idstr | NoneActor identifier.
session_idstr | NoneSession identifier.
tagsTags | NoneImmutable tag mapping.
checksumstr | NoneSHA-256 payload checksum.
signaturestr | NoneHMAC-SHA256 audit signature.
prev_idstr | NoneULID of the previous event in the chain.
unknown_fieldsDict[str, Any](read-only) Unrecognised fields preserved from from_dict() round-trips. Empty dict if none.

Methods

validate() -> None

Validate all fields against the schema rules.

Checks every field in order: schema_version, event_id, event_type, timestamp, source, payload, optional tracing IDs, optional context strings, optional integrity fields, and tags.

Raises: SchemaValidationError — on the first invalid field, with .field, .received, and .reason attributes describing the problem.


to_dict(*, omit_none: bool = True) -> Dict[str, Any]

Serialise to a plain Python dictionary.

Args:

ParameterTypeDefaultDescription
omit_noneboolTrueWhen True, fields with None values are excluded from the result.

Returns: Dict[str, Any] — dictionary representation of the event.


to_json() -> str

Serialise to a canonical, deterministic JSON string.

  • Keys are sorted alphabetically at every nesting level.
  • None values are omitted.
  • Uses compact separators (no whitespace).
  • Byte-for-byte identical for the same event on any platform.

Returns: str — compact canonical JSON string.

Raises: SerializationError — if the payload contains a non-JSON-serialisable value.


payload_checksum() -> str

Compute the SHA-256 digest of the canonical JSON payload.

Returns: str — hex-encoded SHA-256 prefixed with "sha256:".


from_dict(data: Dict[str, Any], *, max_size_bytes: int = 1_048_576, max_payload_depth: int = 10, max_tags: int = 50, source_hint: str = "<dict>") -> Event (classmethod)

Construct an Event from a plain dictionary.

The dictionary shape matches to_dict() output. The returned event is not yet validated — call validate() separately if needed.

Unrecognised keys in data are preserved in event.unknown_fields and included in to_dict() output, enabling lossless round-tripping of events that contain extension or future-version fields.

DoS limits are enforced before any field parsing (RFC §19.4). Pass 0 to disable an individual limit.

Args:

ParameterTypeDefaultDescription
dataDict[str, Any]Dictionary with event fields.
max_size_bytesint1_048_576Maximum serialised size in bytes (1 MiB).
max_payload_depthint10Maximum nesting depth of the payload object.
max_tagsint50Maximum number of keys in the tags object.
source_hintstr"<dict>"Short label used in error messages (e.g. a filename).

Returns: Event

Raises: DeserializationError — if a required field is missing, has an unexpected type, or any DoS limit is exceeded.


from_json(json_str: str, *, max_size_bytes: int = 1_048_576, max_payload_depth: int = 10, max_tags: int = 50, source_hint: str = "<json>") -> Event (classmethod)

Construct an Event from a JSON string (as produced by to_json()).

The returned event is not yet validated — call validate() if needed.

The byte-length of the UTF-8-encoded string is checked before parsing to guard against parse-bomb attacks (RFC §19.4). Pass max_size_bytes=0 to disable the pre-parse check.

Args:

ParameterTypeDefaultDescription
json_strstrA JSON string in the format produced by to_json().
max_size_bytesint1_048_576Maximum string size in UTF-8 bytes (1 MiB).
max_payload_depthint10Maximum nesting depth of the payload object.
max_tagsint50Maximum number of keys in the tags object.
source_hintstr"<json>"Short label used in error messages.

Returns: Event

Raises: DeserializationError — if json_str is not valid JSON, is missing required fields, or any DoS limit is exceeded.