ADR-0018: Bridged events, their fields, ids and projections¶
Status: Accepted, amended by ADR-0036, and the amendment below Date: 2026-10-12 Deciders: Alex Nodeland
Context¶
ADR-0006 names the bridged types, and stackr RFC-0002 lists what each carries, that an application's app_events and artifact kinds are bridged by "a mapping function per type", and that "the bridged event id comes from the artifactr envelope id". It leaves open what every type has in common, how an id is derived when one envelope could become several events, and whether an application's mapping for an artifact kind adds to the default set's events or takes their place.
Decision¶
- Every bridged type carries where it came from:
author, the artifactr actor;thread_id; andturn_id, artifactr's run id. A run is reflexr's word, so artifactr's runs are turns, asrun_endedis bridged asartifactr:turn_ended.proposal_createdcarriesproposed_byas well asauthor, so a rule reads the proposer the same way on both proposal events. - The fields keep artifactr's types: a patch is artifactr's
Patch, a proposed change itsProposedChange, a feedback target itsFeedbackTarget, and an author itsActor. Rules read them as JSON, such asF.author.kind. - An envelope becomes at most one event, whose id is the envelope's id, unchanged.
Projectionsholds the application's mappings: one perapp_eventname, and one per artifact kind, which takes the place of the default set'sartifact_created,artifact_changedandartifact_archivedfor that kind. Proposals on the kind are bridged as the default set's. Anapp_eventwith no mapping stays in artifactr. A mapping returns its own event, in the application's namespace, or None.thread_createdisn't in the default set, which RFC-0002 leaves as an open question.BRIDGEDlists the bridged types, for reflexr'sWorkspaces(events=...); a type it doesn't accept stops the follower (ADR-0015).
Options considered¶
| Option | For | Against |
|---|---|---|
| A kind's mapping replaces its artifact events (chosen) | One event per envelope, so the id is the envelope's | A rule on artifactr:artifact_changed doesn't see that kind |
| A kind's mapping adds an event beside them | Both kinds of rule see it | Two events from one envelope need derived ids, and the bridged log doubles for that kind |
| The envelope's id, unchanged (chosen) | What RFC-0002 says; an operator finds one from the other | Ids are shared across the two logs, which use different random ids by default |
| An id derived from the envelope's | Room for several events per envelope | A mapping to explain, for nothing phase 1 needs |
Consequences¶
- Easier: a bridged event names its artifactr envelope, and a rule can filter on who acted, in which thread and turn.
- Harder: a redelivered envelope whose projection now gives a different event publishes nothing: reflexr keeps the id's first event. A changed projection applies to envelopes it hasn't bridged yet.
- Harder: an application that wants both its typed event and the default set's for an artifact kind can't have both; its mapping's event has to carry what its rules need.
Amendment (2026-10-17): answers, and a projection per deferred tool¶
lattice RFC-0005 bridges answers to paused turns, and lets a form's answer and a form's submission reach a rule as one event.
artifactr:deferred_answeredcarriestool_call_id,tool_name,answerandapproved, besideauthor, who answered, andthread_id. Itsturn_idis the turn that asked, from the event'srun_id: the envelope names that run, but a person wrote it, not the turn.tool_nameis None only for answers artifactr recorded before it named the tool. A chat message that answers is bridged as itsartifactr:message_postedtoo.Projectionsgains a third kind of mapping, beside those perapp_eventname and per artifact kind: one per deferred tool's name,Projections(deferred={"ask_form": ...}), which takes the place ofartifactr:deferred_answeredfor answers to that tool, as a kind's mapping takes the place of its artifact events. An answer whose tool has no mapping, or that names none, is bridged as the default set's.relayr.ui, theformsextra (ADR-0038), uses it to bridge anask_formanswer and a form's submission both asui:form_submitted.- An answer to a rule's paused turn joins the turn's chain without going deeper, as a person's answer to a rule's proposal joins the proposal's (ADR-0021): the follower finds the turn's cause in the ledger by the answer's run, and publishes with its correlation id and no causation. An answer to any other turn starts a chain of its own.