Semantic Events as Platform Primitive
A platform contract for declared events. Less analytics, more nervous system.
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 explain endpoint. It returns a human-readable explanation of the event's decision, using only evidence the requester is allowed to see.
It is the difference between "denied" and "denied because the policy requires two approvers for changes over X, and the second approver is missing."
This endpoint is where trust gets rebuilt. It is also where third parties stop reinventing your business logic in their UI.
Answers: "What is the latest decision state for this target?"
Lets a consumer avoid replaying the whole event stream just to know where things stand. For platforms, a gift to every integration. For users, a gift to every screen.
The clean separation between history and current truth.
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.