Search by

artisan-build / telltale-contracts

edgrosvenor

Versioned wire contracts shared by the Telltale client and server.

Package info

github.com/artisan-build/telltale-contracts

pkg:composer/artisan-build/telltale-contracts

Statistics

Installs: 113

Dependents: 2

Suggesters: 0

Stars: 0

v1.2.0 2026-10-30 10:17 UTC

This package is auto-updated.

Last update: 2026-10-30 10:18:28 UTC


README

Versioned wire contracts shared by the Telltale server and NativePHP client.

Envelope v1

EnvelopeV1 serializes this JSON object:

{
  "envelope_version": 1,
  "client_version": "1.0.0",
  "dropped_events_total": 0,
  "events": [
    {
      "event_id": "01ARZ3NDEKTSV4RRFFQ69G5FAV",
      "name": "checkout.completed",
      "type": "event",
      "ts": "2026-10-03T12:34:56.123Z",
      "session_id": "session-1",
      "props": {}
    }
  ]
}

The envelope version is exactly 1, client_version is non-empty, dropped_events_total is a non-negative integer, and events is a list. The drop count is cumulative per install rather than per batch. A server can therefore persist the latest count without counting the same drops again when a batch is replayed.

Each event has a canonical uppercase ULID event_id, a non-empty name, one of the exact event types screen, event, error, session_start, session_end, or context, an RFC3339 ts, a non-empty session_id, and a JSON-object props. An error object is required only when type is error; other event types reject it. Error details contain a non-empty class, message, non-empty file, positive integer line, list of string stack frames, and optional non-empty string fingerprint.

Limits

Limits are measured in bytes and are fixed for v1:

Value Maximum
Every property-object key, recursively 128 bytes
Every property string value, recursively 4,096 bytes
Error message 4,096 bytes
Error file 1,024 bytes
Error stack frames 100
Each error stack frame 2,048 bytes

Properties may otherwise contain JSON objects, lists, strings, finite numbers, booleans, and null. Non-finite numbers and PHP values that JSON cannot represent are rejected. Empty props always serializes as {}.

Compatibility And Identity

Decoding ignores unknown object fields so producers can add fields without breaking a v1 consumer. Malformed JSON, invalid shapes or values, and unsupported versions throw ContractException. EnvelopeV1::versionFrom() reads the declared positive version without requiring v1 decoding, so a server must inspect the version first and can return an actionable response for a newer producer.

Install tokens are header-only credentials. Ingest values or keys, app IDs, and install IDs are not members of the envelope or event body. Unknown fields with those names are ignored during decoding and are never emitted during encoding. Runtime transport, authentication, and persistence are outside this package.

Envelope v2 and mobile crashes

EnvelopeV2 uses the same headers and ordinary event fields as v1. It adds type: "crash" with required typed CrashDetails in the event's crash field. A batch may mix ordinary and crash events. Error and crash details are mutually exclusive. V1 rejects crashes, and its ordinary JSON encoding is unchanged. Existing Event constructor calls still work; crash is an optional final argument.

use ArtisanBuild\TelltaleContracts\EnvelopeDecoder;
use ArtisanBuild\TelltaleContracts\EnvelopeV2;

$envelope = EnvelopeDecoder::decode($json); // EnvelopeV1 or EnvelopeV2
if ($envelope instanceof EnvelopeV2) {
    foreach ($envelope->events as $event) {
        if ($event->crash !== null) {
            $fingerprint = $event->crash->fingerprint(); // Recompute on the server.
            $retryEventId = $event->crash->eventId();
        }
    }
}

Crash schema 1 requires a lowercase SHA-256 report_id, platform, kind, source, machine reason, fatal flag, occurrence timestamp, timestamp precision and positive occurrence count (at most 1,000,000). Event ts equals occurred_at. Window-end timestamps require both reporting-window endpoints and equal the end. Optional app version/build describe the crashed build; unknown values are omitted. MetricKit hangs are nonfatal, while Android ApplicationExitInfo ANR deaths are fatal. Other accepted crash, memory-exit and watchdog-exit reports are fatal.

CrashFrame retains binary UUID/build ID, name, address, offset, load address and symbol inputs. Addresses are canonical unsigned 64-bit hex strings, with precision-safe subtraction to derive relative/load addresses where inputs permit. Fingerprints use platform/kind/reason and up to five frames, preferring UUID plus offset for native frames and class/method symbols for JVM frames. They exclude absolute ASLR addresses and trace/exception messages. Empty frames use a platform/kind/reason fallback. eventId() converts the first 128 digest bits to a stable canonical ULID for retries; it does not encode the occurrence time.

The limits are 64 frames, 255-byte binary text, 1,024-byte symbols, 16,384-byte sanitized UTF-8 trace text and 16 scalar metadata keys (strings at most 1,024 bytes). Unknown crash/frame keys, opaque tombstone/base64 fields, binary text, invalid types and contradictory values throw ContractException. Native projection and privacy callbacks must remove sensitive text before import. This package does not parse native tombstones, upload reports, or trust body app identity.

The mobile crash contract specifies the field rules, native handoff/acknowledgment, retention, byte-bounded delivery and planned server/MCP behavior. Those delivery paths are not implemented by the contracts package.