Automations and webhooks for the AT Protocol airglow.run

fix(automations): ignore unfinished watched repos in the event.did warning and show a gated automation with none as not running #67

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

Problem #

The watched-repos work in #22 (fe70e1f) has two places where it tells the user something false:

  • The event.did conflict warning treats a half-typed or invalid watched entry as a real DID, so "This check can never pass" flashes while the user is typing the very repo that would satisfy it.
  • The automation page shows "All repos" for a gated NSID with no watched repos, which the consumer actually skips.

Evidence #

  • app/islands/step-drafts/watched-repos.ts:22-27: resolveDid returns null only for empty or {{…}} input, so did:plc:fri or alice.bsky.social count as concrete DIDs. The comment at line 49 says unfinished entries are skipped; they are not. watched-repos.test.ts, "unfinished watched entries", only tests {{other}}.
  • lib/actions/validation.ts:182: DID_RE is the server's own test, and it would reject these entries.
  • app/routes/u/[handle]/[rkey]/index.tsx:363-375 renders "All repos" whenever wantedDids is empty. lib/jetstream/consumer.ts:1087-1094 skips such an automation when its NSID is in NSID_REQUIRES_DIDS.

Expected #

  • A watched entry that is neither {{self}} nor a full DID (per DID_RE) makes the finder return no conflicts, as it already does for templates.
  • On the automation page, a gated NSID with no watched repos says something like "None: this automation receives no events until a watched repo is added" (and/or shows a warning), not "All repos".
  • Optional: one sentence in the form hint noting that for low-volume lexicons an event.did condition avoids a separate Jetstream connection (docs/wanted-dids.md).

Acceptance criteria #

Out of scope #

  • Flagging other operators (in, startsWith…) or match: "any" rollups.
  • A cap on Jetstream subscriptions.
  • Changing which NSIDs allow watched repos.

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

loop:queued cid=bafyreidy5t2dp4xntipamedfqsvpdsm7eyu5m474g65py2cnig6a6uuwim

Working on this, attempt 1.

loop:claim run=contribute-20261030-101329-67 branch=loop/67 attempt=1 issue=67

fix(automations): ignore unfinished watched repos in the event.did warning and show a gated automation with none as not running

The event.did conflict finder now counts a watched entry only when it is the exact {{self}} token or a full DID that DID_RE accepts, the same rule validateWantedDids uses on save. A half-typed DID or a handle makes the finder return no conflicts, so "This check can never pass" no longer flashes while you type the repo that would satisfy the check. The automation page now says "None: this automation receives no events until a watched repo is added." instead of "All repos" when the NSID is in NSID_REQUIRES_DIDS and no repo is watched. The consumer skips such an automation, so "All repos" was wrong.

What changed #

  • lib/actions/did.ts (new) holds DID_RE. lib/actions/validation.ts imports and re-exports it, so the existing @/actions/validation.js imports keep working. It had to move because the island cannot import validation.ts: that file pulls in the DB and secrets modules.
  • app/islands/step-drafts/watched-repos.ts: new resolveWatchedDid for watched entries. Condition values still go through the old resolveDid.
  • app/routes/u/[handle]/[rkey]/index.tsx: a third branch for the "Watched repos" row, using nsidRequiresWantedDids(auto.lexicon, config.nsidRequireDids).
  • I also did the optional hint. In the watched-repos block, for NSIDs that do not require watched repos, the form now says: "For a low-volume lexicon, a check on event.did in the steps filters the same way without opening a separate Jetstream connection."

Verification #

  • pnpm run check and pnpm run test pass (181 files, 2791 tests).
  • With the finder change stashed, the new unit test and the new form test both fail. With it in place, both pass.
  • Test fixtures that used short fake DIDs as watched entries (did:plc:owner, did:plc:alice, did:plc:friend) now use valid 24-character ones. Otherwise those tests would pass only because the entries count as unfinished.

Acceptance criteria #

Decisions you can reverse #

  • A watched entry counts as self only when it is exactly {{self}} (trimmed), matching the server. {{ self }} counts as unfinished. Condition values still accept {{ self }}, as before.
  • A partial did:web:exa still matches DID_RE, so it counts as a complete DID while you type. The issue asked for DID_RE, so I did not add a stricter rule.
  • The page shows only the text in the "Watched repos" row, with no separate warning banner.
  • The new form hint appears only when watched repos are optional. A gated NSID keeps its "requires at least one watched repo" hint.
  • lib/sync/targets.ts still has its own copy of the same regex. I left it alone because it is outside this issue.

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

loop:pushed run=contribute-20261030-101329-67 sha=4cd52bfdd8a7a6a101f00f58cc86c345be8b58be

Merge. The branch meets every acceptance criterion in #67. A watched entry now counts toward the event.did conflict warning only when it is the exact {{self}} token or passes DID_RE, the same rule validateWantedDids applies on save. The automation page says "None: this automation receives no events until a watched repo is added." for a gated NSID with no watched repos, which matches the consumer's skip condition. pnpm run check and pnpm run test (181 files, 2791 tests) both pass on 4cd52bf.

What I checked and how:

  • Finder: read resolveWatchedDid against validateWantedDids (lib/actions/validation.ts). I ran a throwaway test through findWatchedRepoConflicts with real-world inputs: @alice.bsky.social, a DID with surrounding spaces, {{ self }}, DID:PLC:X, did:web:exa, {{self}} with and without the owner's DID, and a partial condition value.
    • Handles, partial plc DIDs and {{ self }} give no conflicts.
    • A full DID with spaces is trimmed and judged.
    • did:web:exa is judged, because DID_RE accepts it. The PR lists this as a reversible decision, and it follows the issue's "per DID_RE".
  • Shared regex: DID_RE moved to lib/actions/did.ts, which has no imports, so the island can load it. validation.ts re-exports it, and every importer (follow.ts, bsky-list-add.ts, api/admin/partners.ts) still resolves. No new regex was added. lib/sync/targets.ts keeps its own copy, as before; that is outside this issue.
  • Page row: the new branch uses nsidRequiresWantedDids(auto.lexicon, config.nsidRequireDids) with empty wantedDids. This is the same test consumer.ts _doRefresh uses to skip a row, because non-empty wantedDids always resolves to a non-empty set. "All repos" appears in only this one place in app/ and lib/. The form's "Watching all repos" notice is already hidden when the NSID is gated.
  • Tests:
    • The unit test covers did:plc:fri and alice.bsky.social.
    • The form test types a valid 24-character plc DID four characters at a time. It checks that the warning stays hidden throughout, then that a complete DID for another repo brings it back.
    • The page test makes the config mock mutable and resets it in a finally block.
    • Fixtures that used short fake DIDs were made valid, so those tests do not pass merely because the entries count as unfinished.
  • Optional hint: the extra form hint is static text and adds no control, so there is nothing to check for keyboard or focus.

Something I noticed but am not asking for: a half-typed condition value such as did:plc:al against a full watched DID still shows the warning. That is out of the issue's scope, which is about watched entries. The warning is also accurate there, since such a value could be saved and would never match.

loop:verdict run=review-20261030-102438-67 sha=4cd52bfdd8a7a6a101f00f58cc86c345be8b58be verdict=merge

Merged onto main as d6a11d6f5ac2486a39f0b7a6ff9c95e14b4917be.

loop:merged sha=d6a11d6f5ac2486a39f0b7a6ff9c95e14b4917be head=4cd52bfdd8a7a6a101f00f58cc86c345be8b58be

Sign up or Sign in to add to the discussion.