Skip to content

iamroylim/event-contracts

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

event-contracts

Analytics schemas usually drift silently: an event is renamed in one client, a required property disappears, or a campaign field changes type before anyone notices the broken dashboard. event-contracts gives growth engineers one versioned tracking plan they can enforce locally or in CI.

It validates local JSON only. Runtime code uses the Python standard library and has no registry, network call, telemetry, event storage, or analytics property-value echoing in reports.

What it checks

  • Strict lower_snake_case event names that exist in the plan
  • Required and optional string, number, boolean, array, and object properties
  • Scalar enums that match the declared type
  • Explicit allow/reject policy for additional event properties
  • Unknown or misspelled tracking-plan keys
  • Direct-identifier classification, including identifiers nested in objects and arrays
  • Exact event envelopes containing only event and properties
  • Stable JSON paths and deterministic issue ordering

Install and run

Python 3.12 or newer is required.

This project is not published to PyPI. Install the v0.1.0 wheel from its GitHub release:

python -m pip install \
  https://github.com/iamroylim/event-contracts/releases/download/v0.1.0/event_contracts-0.1.0-py3-none-any.whl
event-contracts validate \
  --plan tracking-plan.json \
  --events events.jsonl \
  --format jsonl \
  --pretty

For development from a source checkout:

python -m pip install -e ".[dev]"
event-contracts validate \
  --plan examples/tracking-plan.json \
  --events examples/events.jsonl \
  --format jsonl

Or install the tagged source directly:

python -m pip install \
  "event-contracts @ git+https://github.com/iamroylim/[email protected]"

The input format is explicit rather than inferred from a filename:

  • json requires one JSON array of event objects.
  • jsonl requires one JSON event object per non-empty line.
  • Every event has exactly {"event":"event_name","properties":{...}}.

The CLI prints one JSON report to stdout:

Exit Meaning
0 All events satisfy the tracking plan
1 Inputs are readable, but contract issues were found
2 Usage, file, encoding, or JSON parsing failed

Parse errors report only a filename and, for JSONL, a line number. Validation issues include schema names and paths but never analytics property values. The CLI caps JSON nesting at 512 levels so behavior remains predictable across supported Python versions; the public API's iterative privacy scan remains safe for deeper in-memory values.

Tracking plan

examples/tracking-plan.json is complete:

{
  "version": "1.0.0",
  "events": {
    "signup_completed": {
      "additionalProperties": false,
      "properties": {
        "plan": {
          "type": "string",
          "required": true,
          "enum": ["free", "pro"],
          "privacy": "non_personal"
        }
      }
    }
  }
}

Versions use MAJOR.MINOR.PATCH. Each event must declare additionalProperties; omitted required defaults to optional. Enums support string, number, and boolean.

Privacy classifications are non_personal, pseudonymous, sensitive, and direct_identifier. Names that normalize exactly to email, emailAddress, phone, phoneNumber, ip, ipAddress, or fullName must use direct_identifier. Normalization ignores case, underscores, hyphens, and spaces.

The privacy scan follows nested objects and arrays with an iterative, cycle-safe traversal. A nested identifier is allowed only when its top-level contracted property is classified direct_identifier. The rule deliberately does not inspect scalar contents, so classify a scalar array as direct_identifier when it intentionally carries direct identifiers.

Python API

from event_contracts import ValidationReport, validate_events

plan = {
    "version": "1.0.0",
    "events": {
        "checkout_completed": {
            "additionalProperties": False,
            "properties": {
                "order_id": {
                    "type": "string",
                    "required": True,
                    "privacy": "pseudonymous",
                }
            },
        }
    },
}

report: ValidationReport = validate_events(
    plan,
    [
        {
            "event": "checkout_completed",
            "properties": {"order_id": "ord_123"},
        }
    ],
)

validate_tracking_plan(plan) accepts an untrusted parsed object and returns a tuple of typed ValidationIssue values. validate_events(plan, events) returns an immutable typed ValidationReport; report.to_dict() produces the CLI shape.

Development

python -m pytest
ruff check .
ruff format --check .
mypy src
python -m build
pip-audit --local

License

MIT

About

Offline Python CLI and typed API for catching analytics schema and privacy drift.

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages