Automations and webhooks for the AT Protocol airglow.run

No guidance for triggers whose record lives in the other person's repo #39

Closed tyb.there-hugo.dev
Closed opened by tyb.there-hugo.dev

Problem #

Some lexicons record an interaction between two people, but the record is written to the repo of the person who acts. For example, scanning someone's You and Me QR code writes at.youandme.connection to the scanner's repo, with subject set to the person whose code was scanned.

The natural way to build "when someone scans my code, add them to my list" is condition event.did == <my DID> and listitem subject: {{event.commit.record.subject}}. That never matches, and if it did it would add the owner to their own list. The working shape is the reverse:

  • condition event.commit.record.subject == {{self}}
  • listitem subject: {{event.did}}

Nothing in the form hints at this, and condition misses leave no trace (https://tangled.org/exosphere.site/airglow/issues/14), so the automation is silently inert.

Seen in the session #

In https://www.youtube.com/watch?v=5MnT-ZgbBIc, the user's iosp-conf-youandme automation used the first shape and never fired. The scan event did reach Airglow: an older automation on the same account using subject == {{self}} handled it correctly within the same second.

Fix direction #

  • Near the trigger, say whose repo the record comes from ("fires when anyone creates this record; event.did is who created it").
  • Offer a "when someone does X to me" starting point that pre-fills event.commit.record.subject == {{self}}.
  • Warn when a condition requires event.did to equal the owner's DID while an action uses a DID from the record, since that is usually the inverted pattern.
  • The list action in https://tangled.org/exosphere.site/airglow/issues/29 could make "who to add" an explicit choice.

Details: docs/brainstorms/2026-10-17-graze-conference-automation-feedback.md ("Resolved: You and Me never fired").

Queued: the loop will work on this version of the issue.

loop:queued cid=bafyreihda4wc7gzce3rr57yhsyvcq2on56vwkxw45me7q42bzpmwjfooy4

Working on this, attempt 1.

loop:claim run=contribute-20261024-142846-3my33bvtvxx22 branch=loop/3my33bvtvxx22 attempt=1 issue=39

feat(automations): guide triggers whose record lives in the other person's repo

The automation form now explains that a trigger fires on records written to the repo of whoever acts, so event.did is that person. It also offers a one-click "Only when subject is me" gate (event.commit.record.subject == {{self}}) for lexicons whose records name a person. And it warns when a condition requires event.did to be the owner while an action uses a person taken from the record. That last shape is the inverted pattern that left iosp-conf-youandme silently inert.

What changed #

  • app/islands/step-drafts/self-pattern.ts (new, pure): recordPersonPaths lists the trigger record fields that hold a person: strings with a did/at-identifier format, nested objects included, plus a top-level string subject. It also has selfTargetGate, hasSelfTargetCheck and findInvertedSelfPattern.
  • AutomationForm.tsx:
    • A hint under the Lexicon NSID field says whose repo the record comes from.
    • The starter hint and buttons sit at the top of Steps. Clicking one puts a gate step first, so it also gates the trigger at the Jetstream level.
    • The inverted-pattern warning appears under the offending check. It is advisory and does not block submit.
    • The event.did descriptions now read "DID of whoever wrote the record (the repo it lives in)".
  • formConfig.viewerDid is passed from the new, edit and duplicate routes, so the warning also catches a literal owner DID, not just {{self}}. That literal DID is what the session's automation used.

Verification #

  • pnpm run check passes: format, lint and types.
  • pnpm run test passes: 162 files, 2549 tests. The islands suite was run again after a final wording tweak.
  • New unit tests in self-pattern.test.ts cover:
    • the You and Me shape gets flagged, both with {{self}} and with the owner's literal DID and didToHandle(...);
    • the working shape and an unrelated event.did stay quiet;
    • a strongRef subject is not treated as a person.
  • New form tests in AutomationForm.test.tsx:
    • the trigger hint renders;
    • the starter prepends a gate that counts toward the trigger-gate prefix, then goes away;
    • the warning renders without disabling submit;
    • the working shape shows neither the warning nor the starter.
  • I did not check the form in a browser. This container has no PDS (or curl), so the dev server's OAuth sign-in cannot run.

Acceptance criteria #

Decisions the owner can reverse #

  • The starter only appears when the loaded schema has a person field. I did not check whether at.youandme.connection marks subject as format: did, so a top-level string named subject also counts, whatever its format. When the schema is unresolved, nothing is offered.
  • The starter hides once any condition checks event.did, or once a person field is already compared to the owner. In the event.did case the warning takes over.
  • The warning only fires for record paths the schema says hold a person. That way "when I post, use my post's text" never triggers it.
  • The starter adds a gate at the top, not a then/else branch.

Review: https://tangled.org/exosphere.site/airglow/compare/main...loop/3my33bvtvxx22

loop:pushed run=contribute-20261024-142846-3my33bvtvxx22 sha=191c43ec757a8620f7f79d0049d7b4b967badee4

Merge. The branch does the three things the issue asks for: a hint near the trigger saying whose repo the record comes from, a one-click "Only when subject is me" starter that adds event.commit.record.subject == {{self}} as the first step, and an advisory warning when event.did must equal the owner while an action reads a person field from the record. The fourth bullet (an explicit "who to add" choice in the list action) is rightly left to #29. Check and tests both pass on 191c43e.

What I checked and how:

  • pnpm run check: format, lint and types all pass. pnpm run test: 162 files and 2549 tests pass.
  • I read every changed line in self-pattern.ts, the AutomationForm.tsx hunks and the three routes.
  • {{self}} resolves at runtime (lib/actions/template.ts) and counts as event-only in isEventOnlyAssertion, so the prepended gate really gates at the Jetstream level, as the form test asserts through triggerGatePrefixLength.
  • viewerDid is only passed where the viewer is or becomes the owner. The edit route is behind isOwner, which is strict DID equality with no admin bypass. New and duplicate both create under the viewer.
  • PLACEHOLDER_RE is global, so matchAll is safe. The warning only fires for schema-declared person paths, so the plain "when I post" case stays quiet. Nothing blocks submit.
  • Tests cover the You and Me shape with both {{self}} and a literal owner DID, the working shape staying quiet, a strongRef subject not being taken for a person, and the starter going away once used.

A non-blocking note for the owner: the new trigger hint says the trigger "fires when anyone writes a record". That is not true when Watched repos (wantedDids) limits which repos are read. That section has its own hint just below ("Events from other repos are not received"), so I did not count it against the branch. I did not check the form in a browser.

loop:verdict run=review-20261024-144021-3my33bvtvxx22 sha=191c43ec757a8620f7f79d0049d7b4b967badee4 verdict=merge

Merged onto main as 1eee353bb7a0147cf38f113b6ef3cfa53fec0dd7.

loop:merged sha=1eee353bb7a0147cf38f113b6ef3cfa53fec0dd7 head=191c43ec757a8620f7f79d0049d7b4b967badee4

Sign up or Sign in to add to the discussion.