Contracts
event-envelope.md
Event Envelope Contract
Feature: 032-identity-aware-cli-event-sync Date: 2026-02-07
Updated Event Envelope Schema
This document describes the changes to the event envelope schema.
Before (Current)
{
"event_id": "01HQXYZ...",
"event_type": "WPStatusChanged",
"aggregate_id": "WP01",
"aggregate_type": "WorkPackage",
"payload": { ... },
"timestamp": "2026-02-07T12:00:00Z",
"node_id": "node-abc123",
"lamport_clock": 42,
"causation_id": null,
"team_slug": "my-team"
}
After (This Feature)
{
"event_id": "01HQXYZ...",
"event_type": "WPStatusChanged",
"aggregate_id": "WP01",
"aggregate_type": "WorkPackage",
"payload": { ... },
"timestamp": "2026-02-07T12:00:00Z",
"node_id": "node-abc123",
"lamport_clock": 42,
"causation_id": null,
"team_slug": "my-team",
// NEW FIELDS
"project_uuid": "550e8400-e29b-41d4-a716-446655440000",
"project_slug": "my-project"
}
Field Definitions
project_uuid (NEW, REQUIRED for WebSocket)
- Type:
string(UUID4 format) - Description: Unique identifier for the project that emitted this event
- Format:
xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx - Generation: Created once during
spec-kitty initor on first event emission - Persistence: Stored in
.kittify/config.yaml
Validation:
- MUST be present for WebSocket transmission
- If missing, event is queued locally only (not sent via WebSocket)
- Warning logged when event lacks project_uuid
project_slug (NEW, OPTIONAL)
- Type:
string | null - Description: Human-readable identifier for the project
- Format: Kebab-case, derived from directory name or git remote
- Example:
"my-awesome-project","spec-kitty"
Derivation Logic: 1. If git remote origin exists: Extract repo name from URL 2. Otherwise: Use directory name, converted to kebab-case
Backward Compatibility
CLI → SaaS
- Old CLI (without project_uuid): Events will be stored but not attributed to a project
- New CLI: Events include project_uuid, SaaS can materialize Projects
SaaS Processing
The SaaS should handle events with/without project_uuid:
# In batch sync handler
if event.get("project_uuid"):
project = get_or_create_project(event["project_uuid"], event.get("project_slug"))
event.project = project
else:
logger.warning(f"Event {event['event_id']} missing project_uuid")
# Store event but don't attribute to project
Migration
No migration needed. New fields are:
- Generated on first CLI access (graceful backfill)
- Optional in existing events (backward compatible)
- Required only for new WebSocket transmissions