N-ary Event Objects: Alice introduced Bob to Carol

A31Prose proofEvents involving more than two parties, formalized as witnessed structures.

A sign is something, A, which brings something, B, its interpretant sign determined or created by it, into the same sort of correspondence with something, C, its object, as that in which itself stands to C.

Charles Sanders Peirce, Collected Papers (1902)

This chapter formalizes n-ary event objects as the minimum structure required to preserve meaning under mutation, defining Anchor A31 (N-ary Event Object). The central result is the No Lossless Binarization Theorem, which establishes that any encoding of multi-participant events without an event identifier loses at least one of instance binding, atomicity, or constraint scope. A31 specifies event types with role schemas, typed constraints, and context requirements, integrating with A22 (Context Graph) for structural embedding and A26 (Version Compatibility) for event type evolution. The narrative motivation appears in Volume I, Chapter 8, where naive binarization is identified as a fundamental information-loss pathology.

The Sentence That Won't Flatten

"Alice introduced Bob to Carol at the conference."

One sentence. Three people. One event. Try to store it in a system that only knows binary relations.

First attempt: two edges.

(Alice, introduced, Bob)
(Alice, introduced_to, Carol)

This preserves something. Alice is the subject of both edges; she must be the introducer. Bob appears with "introduced"; Carol with "introduced_to." The structure seems to encode the sentence.

Now add a second introduction at the same conference.

e1: Alice introduced Bob to Carol
e2: Alice introduced Dave to Carol

The graph gains two more edges:

(Alice, introduced, Bob)
(Alice, introduced_to, Carol)
(Alice, introduced, Dave)
(Alice, introduced_to, Carol)

Query: "Whom did Alice introduce to Carol?"

The graph cannot answer. Bob? Dave? Both? Neither? Which "introduced" edge belongs to which "introduced_to" edge? The edges for e1 and e2 are indistinguishable. Without knowing which edges belong to the same event, the question is unanswerable.

In the real world, this is where systems start lying. Not by returning an empty set, but by returning a plausible one. The query "whom did Alice introduce to Carol?" returns Bob and Dave, but it cannot tell you which introduction happened when, which witness supports which, and which correction retracts which event. Meaning becomes unstable under correction. A user asks "did Bob meet Carol through Alice?" and the system says yes, based on edges that may or may not constitute a single event. The lie is not in the data. The lie is in the structure that cannot represent what the data asserts.

This is T10, the tenth touchstone. We named it in Chapter 5: "Information loss under naive binarization; no event object." The pathology is not missing data. It is missing structure. The sentence asserts one event with three typed participants. The binary representation fragments that event into edges that cannot be reassembled.

A31 solves this by refusing to fragment in the first place.

Events as Objects

The root cause is structural: without event_id, the representation cannot express "these role fillers belong to the same event instance." Grouping, atomicity, and cross-role constraints all become underspecified. An event_id is not a database trick but the name of the claim's atomic unit. If you can't name the event, you can't retract the claim.

The fix is not to encode more carefully. The fix is to recognize that events are objects(Davidson 1967)Donald Davidson, "The Logical Form of Action Sentences," in The Logic of Decision and Action, ed. Nicholas Rescher (University of Pittsburgh Press, 1967), 81–95.View in bibliography(Parsons 1990)Terence Parsons, Events in the Semantics of English: A Study in Subatomic Semantics (MIT Press, 1990).View in bibliography. An event is like a database row with a primary key, or a function call frame with named arguments: a single identity that binds its parts. A31 is not a modeling preference but the price of keeping meaning stable under mutation.

Event vs Edges

An edge connects two entities: (subject, predicate, object). Edges are independent facts.

An event binds multiple entities into typed roles under a single identity. Events are atomic units: they are created together, deleted together, and constrained together.

The introduction sentence is not two facts about Alice. It is one event with three roles:

RoleFiller
introducerAlice
introducedBob
toCarol

The event has an identity. Deleting the event deletes all role bindings atomically. The constraint "introducer ≠ introduced" is enforced on the event, not scattered across edges.

Anchor A31: N-ary Event Object

A31
A31: N-ary Event Object

RoleSpec defines a role slot:

RoleSpec := { type: EntityType, cardinality: single | multi }

EventType defines the schema:

EventType := {
  name: TypeName,
  role_schema: { role_name: RoleSpec }...,
  constraints: [Constraint...],
  context_requirements: ContextSpec
}

Event is an instance:

Event := {
  id: EventID,               // the event_id (join key)
  type: EventType,
  roles: { role_name: Entity }...,
  context: Context,
  witness: EventWitness
}

Role projection: πr:EventEntity\pi_r : \text{Event} \to \text{Entity}

For each role rr in type.role_schema, πr(e)=e.roles[r]\pi_r(e) = e.\text{roles}[r].

Invariants:

  1. COMPLETENESS: All roles must be filled.
  2. TYPE_CONFORMANCE: Each filler matches its role's declared type (e.roles[r]:role_schema[r].typee.\text{roles}[r] : \text{role\_schema}[r].\text{type}).
  3. ROLE_FUNCTIONALITY: Each role is single-valued unless declared multi.
  4. CONSTRAINT_SATISFACTION: All type constraints hold at instantiation.
  5. CONTEXT_BINDING: Event is anchored in declared context.

The introduction event:

EventType: Introduction = {
  name: "Introduction",
  role_schema: {
    introducer: { type: Person, cardinality: single },
    introduced: { type: Person, cardinality: single },
    to: { type: Person, cardinality: single }
  },
  constraints: [
    introducer ≠ introduced,
    introduced ≠ to,
    introducer ≠ to
  ],
  context_requirements: { has_location: true, has_time: true }
}

Event: e1 = {
  id: "intro_001",
  type: Introduction,
  roles: {
    introducer: Alice,
    introduced: Bob,
    to: Carol
  },
  context: conference_2024,
  witness: ref(W-7234)
}

Projections:

  • πintroducer(e1)=Alice\pi_{\text{introducer}}(e1) = \text{Alice}
  • πintroduced(e1)=Bob\pi_{\text{introduced}}(e1) = \text{Bob}
  • πto(e1)=Carol\pi_{\text{to}}(e1) = \text{Carol}

Constraints checked at creation:

  • Alice ≠ Bob ✓
  • Bob ≠ Carol ✓
  • Alice ≠ Carol ✓

The event exists or it does not. There are no orphan edges.

What Binarization Loses

Binarization

Binarization: An encoding that introduces no fresh event_id (join key) beyond the domain entities. No new identifier—node, edge label, or synthetic "pairing" relation—may serve as a stable grouping key across mutations.

Remark: Blank nodes, statement IDs, named graphs, quads, RDF*, etc. are all ways of introducing event_id; they therefore fall on the "event object" side of the boundary.

We care about encodings that support (i) querying role-bound events, (ii) atomic create/delete, and (iii) per-instance constraint checks.

No Lossless Binarization Theorem

Let E1,E2E_1, E_2 be distinct events of the same type with overlapping participants (i.e., multiple instances can produce the same edge set).
Let B(E1),B(E2)B(E_1), B(E_2) be any binarization without event identifiers.

Then B(E1)B(E2)B(E_1) \cup B(E_2) loses at least one of:

  1. Instance binding: which entity plays which role in which event
  2. Atomicity: atomic create/delete of one event
  3. Constraint scope: constraints enforced per event instance

Definition of "loses": There exists a query, a mutation, or an integrity check expressible over Event objects that cannot be answered or enforced correctly from the binarized representation without additional identity structure.

Corollary: Any lossless encoding introduces an event identifier. Once introduced, we have an event object.

All three losses reduce to the same root cause: without event_id, there is no principled way to group related edges into a coherent unit. Instance binding fails because edges cannot identify their event. Atomicity fails because there is no unit to delete. Constraint scope fails because there is no object to validate. One missing join key produces three symptoms.

Loss 1: Instance Binding

This is the collision from the opening scene. Two events share participants; binarization cannot express which edges belong to which event. The query "whom did Alice introduce to Carol?" becomes unanswerable because role-binding is undefinable without an event identifier.

The only fix is to add event identifiers:

(e1, introduced, Bob)
(e1, introduced_to, Carol)
(e2, introduced, Dave)
(e2, introduced_to, Carol)

But now we have event nodes (e1, e2). The "binary" edges are projections from an implicit n-ary object. We have reinvented A31 with less structure.

Loss 2: Atomicity

In A31, an event has a single identifier. Operations are atomic in that identifier:

  • CreateEvent(e1, ...) creates all role bindings together.
  • DeleteEvent(e1) deletes all role bindings together.
  • No orphan bindings. No partial events.

In binarization without event identifiers:

DELETE (Alice, introduced, Bob)

Does this delete event e1? Or just one "fact"? If we delete one edge, others remain:

(Alice, introduced_to, Carol)  // orphan

We now have "Alice introduced someone to Carol" with no "introduced" binding. The event is fragmented.

To restore atomicity, we need transaction grouping: "Delete all edges that belong to event e1." But this requires knowing which edges belong to e1. Which requires an event identifier.

"Use database transactions." Yes, but a transaction must specify what to delete. To delete "all edges belonging to event e1," you must specify the set. Any specification that correctly selects the set is an event identifier in disguise: a join key, a blank node, a statement ID, a named graph. The transaction does not avoid the need for event identity; it assumes it.

Loss 3: Constraint Scope

The constraint "introducer ≠ introduced ≠ to" must be enforced per-event-instance.

In A31: the constraint is checked at event creation time, on the event object's role bindings.

In binarization, how do we enforce this?

Attempt 1: Per-edge constraint

(Alice, introduced, Alice)  // reject: subject = object

This catches the degenerate case. But "introducer ≠ to" spans different edges:

(Alice, introduced, Bob)
(Alice, introduced_to, Alice)  // should reject: Alice is both introducer and recipient

No per-edge rule catches this. The constraint spans edges.

Attempt 2: External constraint engine

CONSTRAINT: ∀ edges e1, e2:
  if e1 = (x, introduced, y) and e2 = (x, introduced_to, z)
  then x ≠ z

But which e1 pairs with which e2? If Alice has multiple introductions (e1 and e2 from our example), the constraint must apply per-event, not globally. We need to know which edges constitute one event instance.

The constraint engine must query "all edges belonging to event e1" and check constraints on that set. This requires an event_id to delimit the instance. Without it, the engine cannot know which edge-set to validate.

The Event_id Necessity

Any lossless encoding introduces event_id. Once introduced, the encoding becomes:

  • Event node + role projections (A31's structure)
  • With typed roles (A31's role_schema)
  • With constraint enforcement (A31's constraints)

A31 is not an alternative to binarization. It is what binarization converges to when you fix its defects.

The Recommendation Event

T10's running example: "User A recommended Dress X to User B during the trunk show."

Naive binarization:

(UserA, recommended, DressX)
(DressX, recommended_to, UserB)

Who recommended to whom? UserA → DressX → UserB could be parsed wrong. Is UserA recommending, or receiving? The edges do not say. Add a second recommendation (DressY) and the collision from the opening scene recurs: which recommendation happened when? Which deletion retracts which? Which "occasion" binds to which item? The edges cannot answer.

A31 representation:

EventType: ProductRecommendation = {
  name: "ProductRecommendation",
  role_schema: {
    recommender: { type: User, cardinality: single },
    item: { type: Product, cardinality: single },
    recipient: { type: User, cardinality: single },
    occasion: { type: EventContext, cardinality: single }
  },
  constraints: [
    recommender ≠ recipient,
    item.available_in(context)
  ],
  context_requirements: { has_time: true }
}

Event: rec_001 = {
  id: "rec_001",
  type: ProductRecommendation,
  roles: {
    recommender: UserA,
    item: DressX,
    recipient: UserB,
    occasion: trunk_show_2024
  },
  context: { time: "2024-03-15T14:30:00Z", channel: "in_person" },
  witness: ref(W-8891)
}
Example(What the System Can Now Do)

Query by role:

"Who recommended DressX?"
  → π_recommender(rec_001) = UserA

"What was recommended to UserB at trunk shows?"
  → Filter events: π_recipient = UserB AND occasion.type = trunk_show
  → Return π_item for each match
  → Result: [DressX, DressY, ...]

Enforce constraints:

Attempted: UserA recommends DressX to UserA
  → REJECTED: recommender ≠ recipient violated
  → ConstraintViolation { event_type: ProductRecommendation, 
                          constraint: "recommender ≠ recipient",
                          values: { recommender: UserA, recipient: UserA } }

Preserve event identity:

Delete recommendation rec_001:
  → All role bindings deleted atomically
  → No orphan edge "DressX recommended_to UserB" remains
  → EventDeletionReceipt { id: rec_001, roles_deleted: 4 }

The system knows what it knows. Queries are answered from structure, not inference. Constraints are enforced at creation, not discovered later. Deletions are atomic, not fragmenting.

Integration with Context Graph

Events are nodes in the context graph (A22):

Event nodes:
  NodeType: Event
  Content: { type, roles, witness }

Event → Entity edges:
  EdgeType: RoleFiller
  Payload: { role_name, constraint_status }

Event → Context edges:
  EdgeType: OccurredIn
  Payload: { time, location, attestation }

CreateEvent(type, roles, context, witness):

  1. CHECK: All roles filled (COMPLETENESS)
  2. CHECK: All fillers match types (TYPE_CONFORMANCE)
  3. CHECK: Cardinality respected (ROLE_FUNCTIONALITY)
  4. CHECK: All constraints satisfied (CONSTRAINT_SATISFACTION)
  5. CHECK: Context requirements met (CONTEXT_BINDING)
  6. CREATE: Event node
  7. CREATE: RoleFiller edges to each entity
  8. CREATE: OccurredIn edge to context
  9. RETURN: EventReceipt

DeleteEvent(event_id):

  1. DELETE: Event node (cascades to all role edges)
  2. Atomic: Either all edges deleted or none
  3. RETURN: DeletionReceipt

Event identity is preserved. No orphan edges. No partial states.

Event Evolution

Event types evolve (A26 integration):

Role addition (conservative if nullable):

ProductRecommendation_v2 adds:
  { recommendation_reason: ReasonCode? }

Migration:
  - Existing events get recommendation_reason = unknown
  - CompatibilityWitness(conservative, nullable_addition)

Role removal (breaking):

Removing a role is a breaking change. Existing events reference that role. Migration requires explicit handling:

Breaking change manifest:
  - Affected events: [rec_001, rec_002, ...]
  - Migration plan: archive role values, then remove
  - Compatibility: v2 cannot read v1 events without migration

Constraint tightening (breaking):

Adding "recommender.reputation > 3" to ProductRecommendation is breaking. Existing events may violate. Requires validation pass over all instances.

Constraint relaxation (conservative):

Removing a constraint is conservative. All existing events remain valid. No migration needed.

The versioning discipline from A26 applies: conservative extensions preserve old truths; breaking changes require explicit migration.

Reification Is Not Enough

A common objection: "Reification already solves this. RDF can add an event node and attach properties."

True. Reification introduces an event identifier. That is necessary. But reification alone lacks:

  • Type system for roles: Any predicate can attach to the event node. Nothing prevents _:e1 foo bar.
  • Constraint enforcement at creation: Constraints are external, checked post-hoc or never.
  • Versioning semantics: No evolution rules for event types.

"But we can add SHACL/OWL constraints + shapes + governance."

Yes. Reification + SHACL + governance can approximate A31, but at the cost of dispersing the invariant into tooling rather than the object. If you add shapes (role typing), constraints (cross-role validation), and versioning (evolution semantics) to reification, you have rebuilt A31 piecemeal. The structure is the same; the coherence is scattered.

A31 is reification done right: typed, constrained, versioned, in one coherent structure.

Consequence

Higher-arity events are not a modeling flourish but the minimum structure required to preserve meaning under mutation. If you want queries to answer "who introduced whom," you must name the event. If you want deletions to be atomic, you must name the event. If you want constraints to be enforceable, you must name the event.

T10 is resolved. Higher-arity events are meaning atoms. Binarization destroys them. A31 preserves them.

Store structure, enforce constraints, produce receipts.