Studio · Principle

Semantic Events as Platform Primitive

A platform contract for declared events. Less analytics, more nervous system.

declared composition · infrastructure · platform-design
Two specimen jars on a shelf. The left jar carries a handwritten paper label reading 'decision'. The right jar is identical in shape but unlabeled.
Name it and you can trace it. Leave it unlabeled and you are always inferring.

Every serious platform has a quiet superpower: years of declared relationships sitting in tables.

Who can approve what. Which thing belongs to which project. What counts as a valid state transition. What depends on what.

Then we bolt on AI, and we do something strange. We dump documents into text search, we ask a model to infer the world from prose, and we ignore the declarations we already paid for.

That is the mistake.

If you want trustworthy answers, you need a trustworthy substrate. Not vibes. Not "it probably means." A contract.

Here is the primitive: declared events as a platform contract.

The primitive: an event with evidence

An event is a durable record of something that happened in the system.

Not a log line. Not a screenshot. Not a support ticket.

A platform event has a stable ID and a shape that third parties can rely on.

In plain language, the fields are:

event_id The permanent receipt.

The permanent receipt. A stable, globally unique identifier. This is what you reference when you want to trace a chain of causality. Without it, you have logs. With it, you have provenance.

event_type The vocabulary of your platform.

A noun for a verb your system performs: "permission_checked", "approval_submitted", "rollup_computed", "integration_applied". This is what makes events searchable and categorizable across the whole system.

timestamp When it happened.

Always UTC. Without this, causality analysis breaks down entirely.

actor Who did it.

Not just a user ID, a typed entity. The type field distinguishes a human user from an automated service, an AI agent, or a scheduled job. This matters when something goes wrong and you need to know whether a human or a system made the decision.

target What it happened to.

Same pattern as actor: a typed entity with a stable ID. The target is what you retrieve when you ask "show me everything that ever touched this record."

decision What the system concluded.

Three possible outcomes: allow, deny, mutate. The policy_fingerprint is the key insight, a hash of the exact rule set version that was evaluated. If the policy changes tomorrow, events from today still point to the policy that was in force when they happened. This is what makes audits possible years later.

evidence What the system looked at.

The rules it evaluated and the relationship graph it traversed. This is the difference between "denied" and "denied because of these specific rules, applied to these specific relationships."

causality The chain.

Every event points to what triggered it and what it triggered in turn. This is how you reconstruct sequences, not by inferring from logs, but by following declared links.

access_boundary The permission context.

The permission context. Two fields: principal and scopes.

Here is what that contract looks like in practice:

{
  "event_id": "evt_01HZ9K3MXPQ7R",
  "event_type": "approval_submitted",
  "timestamp": "2026-04-30T06:30:00Z",
  "actor": { "id": "usr_tobi", "type": "user" },
  "target": { "id": "doc_9x2k", "type": "document" },
  "decision": {
    "outcome": "allow",
    "policy_fingerprint": "pol_v3_sha256_a1b2c3"
  },
  "evidence": {
    "rules_evaluated": ["require_two_approvers", "budget_threshold_check"],
    "relationships_traversed": ["doc_9x2k -> project_42 -> approver_list"]
  },
  "causality": {
    "parent_event_id": "evt_01HZ9K2PREV1A",
    "child_event_ids": []
  },
  "access_boundary": {
    "principal": "usr_tobi",
    "scopes": ["read:documents", "submit:approvals"]
  }
}

The access_boundary field deserves its own paragraph.

principal names the authority under whose identity this request is made, not just who sent it, but whose authority legitimizes it. An AI agent can send a request. But the principal behind that agent is the human whose authority the agent is acting under. A scheduled job can trigger an event. But the principal is the service account whose permissions were explicitly granted.

scopes are what that principal is allowed to do in this specific context. They are not permanent. They are what was active for this event. A broad permission set can issue a narrow-scoped action. The scopes field captures that.

The word "principal" comes from two traditions simultaneously. In IAM systems (AWS, GCP, and every serious access control implementation), a principal is any entity that can be authenticated and granted permissions, the unit of authority. In AI alignment work, Amanda Askell's Claude's Constitution (Anthropic, 2026, CC0) introduces the principal hierarchy as the governance structure for Claude's behavior: Anthropic sets the constitutional layer, operators customize within those bounds, users adjust within what operators allow. Authority is structured and declared at each layer, not derived from proximity or volume of instruction. The access_boundary.principal field in this contract is the platform-engineering instantiation of that same insight.

This is not about observability for engineers.

This is about making the platform's reality legible to humans and safe for software to consume.

Three endpoints that make the contract usable

Once events exist as first-class entities, the platform can expose a tiny API surface that unlocks an ecosystem.

Search over the platform's declared past. A consumer can ask: "Show me everything that touched this record." Or: "Show me all permission denials for this user this week." Or: "Show me the chain of events that changed this number."

This endpoint turns debugging into retrieval. It turns investigations into queries.

It also becomes the foundation for AI that can answer questions without hallucinating, because the model is not inventing a sequence. It is traversing one.

The permission problem, solved once

If you have ever shipped integrations, you know the painful truth: every consumer reimplements permissions.

They do it badly. They do it inconsistently. They do it with gaps.

Then you get the worst kind of bug: one experience says "you can", another says "you can't", and nobody can prove which is right.

The fix is not to document permissions harder.

The fix is a Policy Resolver: a single source of truth for permission predicates. The part of the platform that answers: "Given this principal and this target, is this action allowed?"

It returns two things: the decision (allow, deny, mutate) and the proof (a policy fingerprint and the evaluated predicate, so the decision can be audited).

Now every surface uses the same resolver. UI reads. Event emission. why explanations. Third-party integrations. The platform stops being a collection of permission implementations and becomes one permission system.

This matters even more when AI enters the room, because models will always produce an answer. They need a boundary that can say "no", and a proof that can say "here is why."

Why this compounds, and why the shortcut decays

There are two ways to build "intelligence" on a platform.

One way is to infer structure from text. You scan documents, embed them, ask a model to guess the relationships. It looks fast, then it gets expensive. Hard to audit. Hard to govern. Breaks silently when the model changes. Becomes a probability cloud you cannot defend.

The other way is to declare the structure as it happens.

Events are declarations. Evidence is a declaration. Causality is a declaration. Permission scope is a declaration.

Once shipped, this substrate compounds: each new event type makes the system more explainable. Each new relationship traversed becomes part of the platform's graph. Each new why response becomes a reusable pattern. Each policy fingerprint makes audits cheaper.

The platform becomes more portable across time.

The shortcut decays because it keeps asking a model to rederive what the humans already knew.

That gap has a name: context debt.

Context debt is the distance between what your platform has already declared and what your model is forced to guess. If you want less debt, you need more declarations.

The portable rule

If you run a platform, here is the rule you can take home.

Traverse what was declared before you generate from what was inferred.

Start from tables, not PDFs. Start from event IDs, not screenshots. Start from a permission resolver, not a dozen permission copies.

Then, and only then, use a model to fill the genuinely missing parts.

If you ship declared events as a platform contract, you get an irreversible advantage:

You can explain yourself. You can audit yourself. You can let others build on you without rebuilding you.

And when AI asks "what happened", you can answer with receipts.

Ready to declare your first event? Build your own platform primitive.