Automations and webhooks for the AT Protocol airglow.run

Replayed events can make a new automation look like it works when it never fires live #38

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

Problem #

Replayed events (https://tangled.org/exosphere.site/airglow/issues/8) produce real records, so a new automation can look like it works when it never fires on a live event. Combined with condition misses leaving no trace (https://tangled.org/exosphere.site/airglow/issues/14), the user has no way to tell the difference.

Seen in the session #

In https://www.youtube.com/watch?v=5MnT-ZgbBIc, at 1:39 the user found themselves on the conference list and concluded the You and Me automation worked. The public repo data shows otherwise:

  • The two listitems for the user were created 3 seconds after iosp-conf-sifa was created, a minute before iosp-conf-youandme existed and before any scan. They came from iosp-conf-sifa replaying earlier test records, and the user was added twice.
  • iosp-conf-youandme could never match (https://tangled.org/exosphere.site/airglow/issues/39). Its detail page correctly said "No deliveries yet", but that went unnoticed next to a list that seemed to prove success.

Fix direction #

  • Fixing https://tangled.org/exosphere.site/airglow/issues/8 removes the cause.
  • Until then, and for any intentional catch-up: mark deliveries whose source event is older than the automation ("event from before this automation existed") and show the source event time on each delivery.

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

#8 is merged, but it might still be a good a idea to show that an automation was updated in the logs

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

loop:queued cid=bafyreielfb7thrnkeag5ejmevj44pilyjytbqgzd25hu6oirplpok2ubte

Working on this, attempt 1.

loop:claim run=contribute-20261026-223139-3my33bvptg222 branch=loop/3my33bvptg222 attempt=1 issue=38

feat(logs): mark automation updates and pre-creation replays in the delivery log

Saving a change to an automation now writes an "update marker" row to its delivery log ("Automation updated: steps changed, turned on."), so runs above it read as the new version and runs below it as an older one, as the owner asked once #8 was merged. Runs whose source event is older than the automation itself (the creation time in its TID rkey) are labelled "event from before this automation existed" next to the event time, so a replay can no longer pass for a live trigger.

What changed #

  • delivery_logs.kind (new nullable column, migration 0003_known_princess_powerful.sql from pnpm db:generate: ALTER TABLE "delivery_logs" ADD COLUMN "kind" text;). "automation-updated" marks the update rows; NULL on every delivery and on the older system rows.
  • lib/automations/update-log.ts: works out what changed (renamed, description edited, trigger operations, watched accounts, steps compared by behavior hash, once per record, Blueprint, dry run on/off, turned on/off) and writes one marker row. A save that changes nothing writes nothing. The row has countsTowardRateLimit: false, and a failed write is logged, never fails the save.
  • Written by PATCH /api/automations/:rkey and by the sync's "deactivate redundant automations" action (the other path where the owner changes an automation).
  • The activity summary on the automation page (Last delivery and Last 7 days) leaves marker rows out, so an edit never shows as a "Success" delivery.
  • The per-automation log, the account log browser and the admin log browser show markers as one full-width line, add the pre-creation label to run headers (and to flat legacy rows), and give flat rows the event time as a tooltip. The admin run header now shows the event time too, as the other two already did. When the log holds only markers, "No deliveries yet." still shows.
  • CONCEPTS.md: new "Update marker" entry.

Verified #

  • pnpm run check passes; pnpm run test passes (172 files, 2664 tests).
  • New tests: PATCH writes the marker with the right message, kind and rate-limit flag, dated with the save, and returned by GET; a no-change save writes none; a step change plus a toggle is named ([rkey].test.ts); a redundant deactivation writes "turned off" (redundant-automations.test.ts); the activity summary ignores markers (activity-summary.test.ts); DeliveryLog renders markers full-width, keeps "No deliveries yet." when only markers exist, and labels only the replayed run (DeliveryLog.test.tsx).
  • pnpm db:generate run again reports no schema changes.
  • I did not test it in a browser: this container has no dev server or PDS.

Acceptance criteria #

Decisions you can reverse #

  • Where the markers live. They are rows in delivery_logs with a new kind column, not a separate table, so they page and sort with the runs. The column needs the migration above.
  • "Before it existed" is judged against creation time, not the last edit. We only store the latest indexedAt, so we can't tell which version a past run ran on. The update markers show that history instead.
  • Steps changes are compared by behavior. A comment-only edit or a webhook re-verification does not count as "steps changed", and a save that changes nothing writes no marker.
  • Older system rows are unchanged. The rows for rate-limit disables, session loss and re-enabling after re-auth keep kind NULL.
  • The pre-creation label uses the warning colour inside the run header, which is uppercase like the rest of the header.

Follow-up, filed as a new issue on merge if the review agrees:

fix(activity): stop counting the "Re-enabled after re-authorization" system row as a successful delivery

reactivateSessionLostAutomations (lib/jetstream/session-loss.ts) writes a delivery_logs row with statusCode: null, error: null and the message "Re-enabled after re-authorization." getActivitySummary (lib/actions/activity-summary.ts) takes the newest resolved row as "Last delivery" and counts every row in "Last 7 days". activityBadge in app/routes/u/[handle]/[rkey]/index.tsx falls through to the "Success" badge for a row with no error and no status code. So after a re-auth, the automation page reports a successful delivery that never happened. That is the false "it works" signal #38 is about. I found this by reading those three functions while making the activity summary skip the new update markers (kind IS NULL).

Where to start: give these system rows (and the session-loss and rate-limit disable rows) a delivery_logs.kind value, as the update markers now have, so the summary and the log views can treat them as markers. Or exclude statusCode IS NULL AND error IS NULL AND run_id IS NULL from the summary.

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

loop:pushed run=contribute-20261026-223139-3my33bvptg222 sha=6e2226ee60849c4e8639fe506effb3d978ff7654

The migration on 6e2226ee6084 is what pnpm db:generate writes from the schema, and it only adds. Only the owner merges a branch with a migration: loop merge 38, once the gate passes.

0003_known_princess_powerful.sql:

ALTER TABLE "delivery_logs" ADD COLUMN "kind" text;

loop:migration run=verify-20261026-224132-3my33bvptg222 sha=6e2226ee60849c4e8639fe506effb3d978ff7654 result=ok

Verdict: changes-requested. The branch does what #38 and the owner's comment ask: update markers on save, the "event from before this automation existed" label, and the event time on run headers in all three log views. pnpm run check and pnpm run test pass (172 files, 2664 tests). One real regression blocks it: the public stats page counts the new marker rows as successful actions. Every save of an automation adds one "successful action" on /stats and makes its owner an "active user".

What I checked and how:

  • Read every changed line against the merge base (3671815). main has since moved to 5e1491f, which adds no migration, so 0003 does not clash.
  • Ran check and test from run.json. Both are green.
  • Followed every reader of delivery_logs: activity summary, rate limit (countsTowardRateLimit: false keeps markers out), session-loss streak, run-steps dry-run counters (these filter on dry_run/step_id, so markers are excluded), the log status filters (the success and error filters already drop rows with a NULL status), cleanup, and lib/stats/compute.ts.
  • Wrote a throwaway PGlite test (since deleted): insert an automation, call logAutomationUpdate(...) with active: false, then compare computeStats() before and after. After the marker, actions.successTotal and successWebhook went from 0 to 1, and users.withSuccessfulAction30d and withSuccessfulAction30dInclSync went from 0 to 1. The cause is that compute.ts treats error IS NULL AND (status_code IS NULL OR status_code < 400) as success, and every marker row matches that.
  • Replay label: run events carry Jetstream's time_us, so a cursor replay has an old eventTimeUs. tidTimestampUs reads the rkey's TID with no server imports, so it is safe in islands.
  • Migration 0003_known_princess_powerful.sql: ALTER TABLE "delivery_logs" ADD COLUMN "kind" text; is a nullable column with no default. Existing rows get NULL, which the code reads as an ordinary delivery or an older system row. In Postgres this changes only metadata: no table rewrite, and no change to any existing row's data. The owner merges migrations.
  • UI: marker rows are static text and add no new interactive control, so there is no keyboard or focus change to check.

Changes:

  1. lib/stats/compute.ts: leave marker rows out of the stats. Add l.kind IS NULL to the successfulAction fragment (it drives both active-user counts) and to the WHERE of the action-bucket query (the l.dry_run OR (...) one), so markers never count as webhook/PDS successes or dry-run actions. Add a case to lib/stats/compute.test.ts: insert a kind: "automation-updated" row and assert it changes no number in computeStats().

Follow-up (followup.md): file it. I checked it against the code. reactivateSessionLostAutomations (lib/jetstream/session-loss.ts) writes a row with statusCode: null, error: null, kind NULL. getActivitySummary picks it up as the newest resolved row, and activityBadge in app/routes/u/[handle]/[rkey]/index.tsx falls through to "Success". So a re-auth shows a delivery that never happened. This is a separate, older system row that this branch does not touch, so it is outside this issue. The branch's new kind column makes it simple to fix, and it would also take those rows out of the stats.

loop:verdict run=review-20261026-224631-3my33bvptg222 sha=6e2226ee60849c4e8639fe506effb3d978ff7654 verdict=changes-requested

Fix round 1: the review asked for changes.

loop:fix run=contribute-20261026-224943-3my33bvptg222 round=1 issue=38

feat(logs): mark automation updates and pre-creation replays in the delivery log

Saving a change to an automation now writes an "update marker" row to its delivery log ("Automation updated: steps changed, turned on."), so runs above it read as the new version and runs below it as an older one, as the owner asked once #8 was merged. Runs whose source event is older than the automation itself (the creation time in its TID rkey) are labelled "event from before this automation existed" next to the event time, so a replay can no longer pass for a live trigger.

What changed #

  • delivery_logs.kind (new nullable column, migration 0003_known_princess_powerful.sql from pnpm db:generate: ALTER TABLE "delivery_logs" ADD COLUMN "kind" text;). "automation-updated" marks the update rows; NULL on every delivery and on the older system rows.
  • lib/automations/update-log.ts: works out what changed (renamed, description edited, trigger operations, watched accounts, steps compared by behavior hash, once per record, Blueprint, dry run on/off, turned on/off) and writes one marker row. A save that changes nothing writes nothing. The row has countsTowardRateLimit: false, and a failed write is logged, never fails the save.
  • Written by PATCH /api/automations/:rkey and by the sync's "deactivate redundant automations" action (the other path where the owner changes an automation).
  • The activity summary on the automation page (Last delivery and Last 7 days) leaves marker rows out, so an edit never shows as a "Success" delivery.
  • The public stats (lib/stats/compute.ts) leave marker rows out too: l.kind IS NULL is in the shared successful-action filter (both active-user counts) and in the action-bucket query, so a save never adds a successful action or makes its owner an active user.
  • The per-automation log, the account log browser and the admin log browser show markers as one full-width line, add the pre-creation label to run headers (and to flat legacy rows), and give flat rows the event time as a tooltip. The admin run header now shows the event time too, as the other two already did. When the log holds only markers, "No deliveries yet." still shows.
  • CONCEPTS.md: new "Update marker" entry.

Verified #

  • pnpm run check passes; pnpm run test passes (172 files, 2665 tests).
  • New tests: PATCH writes the marker with the right message, kind and rate-limit flag, dated with the save, and returned by GET; a no-change save writes none; a step change plus a toggle is named ([rkey].test.ts); a redundant deactivation writes "turned off" (redundant-automations.test.ts); the activity summary ignores markers (activity-summary.test.ts); DeliveryLog renders markers full-width, keeps "No deliveries yet." when only markers exist, and labels only the replayed run (DeliveryLog.test.tsx); two marker rows (dry run off and on) change no number in computeStats() (lib/stats/compute.test.ts, which fails without the stats fix).
  • pnpm db:generate run again reports no schema changes.
  • I did not test it in a browser: this container has no dev server or PDS.

Acceptance criteria #

Decisions you can reverse #

  • Where the markers live. They are rows in delivery_logs with a new kind column, not a separate table, so they page and sort with the runs. The column needs the migration above.
  • "Before it existed" is judged against creation time, not the last edit. We only store the latest indexedAt, so we can't tell which version a past run ran on. The update markers show that history instead.
  • Steps changes are compared by behavior. A comment-only edit or a webhook re-verification does not count as "steps changed", and a save that changes nothing writes no marker.
  • Older system rows are unchanged. The rows for rate-limit disables, session loss and re-enabling after re-auth keep kind NULL.
  • The pre-creation label uses the warning colour inside the run header, which is uppercase like the rest of the header.

This round #

The review found that marker rows counted as successful actions on /stats. Round 1 adds l.kind IS NULL to the stats queries and a test that markers move no stats number.

Follow-up, filed as a new issue on merge if the review agrees:

fix(activity): stop counting the "Re-enabled after re-authorization" system row as a successful delivery

reactivateSessionLostAutomations (lib/jetstream/session-loss.ts) writes a delivery_logs row with statusCode: null, error: null and the message "Re-enabled after re-authorization." getActivitySummary (lib/actions/activity-summary.ts) takes the newest resolved row as "Last delivery" and counts every row in "Last 7 days". activityBadge in app/routes/u/[handle]/[rkey]/index.tsx falls through to the "Success" badge for a row with no error and no status code. So after a re-auth, the automation page reports a successful delivery that never happened. That is the false "it works" signal #38 is about. I found this by reading those three functions while making the activity summary skip the new update markers (kind IS NULL).

Where to start: give these system rows (and the session-loss and rate-limit disable rows) a delivery_logs.kind value, as the update markers now have, so the summary and the log views can treat them as markers. Or exclude statusCode IS NULL AND error IS NULL AND run_id IS NULL from the summary.

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

loop:pushed run=contribute-20261026-224943-3my33bvptg222 sha=94e2a89f508330ffe1995ed03ea600e2ee012424

The migration on 94e2a89f5083 is what pnpm db:generate writes from the schema, and it only adds. Only the owner merges a branch with a migration: loop merge 38, once the gate passes.

0003_known_princess_powerful.sql:

ALTER TABLE "delivery_logs" ADD COLUMN "kind" text;

loop:migration run=verify-20261026-225209-3my33bvptg222 sha=94e2a89f508330ffe1995ed03ea600e2ee012424 result=ok

Verdict: changes-requested. The branch does what #38 and the owner's comment ask, round 1's stats fix is in, and pnpm run check and pnpm run test pass on 94e2a89 (172 files, 2665 tests). But the branch no longer merges with main (acb8f87): #65 and #70 landed since, and four files conflict. With those conflicts resolved the obvious way, vp check goes red and two tests fail, so it needs a rebase before it can merge.

What I checked and how:

  • Round 1's point: lib/stats/compute.ts now has l.kind IS NULL in the shared successfulAction fragment and in the action-bucket WHERE. lib/stats/compute.test.ts asserts that two marker rows change no number in computeStats(). Fixed.
  • Ran check and test from run.json on the head: both green.
  • Read every changed line against the merge base 3671815. I listed every reader of delivery_logs. Rate limit: excluded by countsTowardRateLimit: false. Status filters: success and error need a status code, so markers drop out. Dry-run counters and run lookups filter on dry_run, step_id or run_id. Cleanup goes by age. Activity summary and stats now use kind IS NULL. Session-loss streak: a marker could only interrupt a run of 401s if a save got through to the PDS mid-streak, and a save can't do that while the session is lost. The only paths where the owner changes an automation are PATCH and the redundant deactivation, and both write the marker. Nothing indexes automation edits made on the PDS. The other .update(automations) callers are system or admin flips, not owner edits.
  • Replay label: eventPredatesAutomation compares the Jetstream event time with the TID time in the rkey, and markers have a null run_id, so each one is its own group.
  • Merge with main: in a scratch worktree I merged acb8f87 into the head. Conflicts: app/islands/DeliveryLog.tsx, app/routes/api/automations/[rkey].ts, app/routes/api/automations/[rkey]/logs.ts and app/routes/u/[handle]/[rkey]/logs.tsx. I resolved them by keeping both sides' imports and using main's serializeDeliveryLogRow, which already carries kind from the auto-merged lib/actions/logs-query.ts. Then vp check failed with TS2741 (kind missing from ROW in lib/actions/logs-query.test.ts), and both toStrictEqual tests in that file failed. I added kind: null to ROW and ROW_JSON, and then vp check passed and vp test run passed in full (179 files, 2747 tests). I deleted the worktree afterwards.
  • Migration 0003_known_princess_powerful.sql (ALTER TABLE "delivery_logs" ADD COLUMN "kind" text;): main added no migration, so 0003 does not clash. It adds a nullable column with no default. Existing rows get NULL, which the code reads as a delivery or an older system row. In Postgres this changes only metadata, with no table rewrite. The owner merges migrations.
  • UI: marker rows and the pre-creation note are static text. They add no control, so there is no keyboard or focus change.

Changes:

  1. Rebase loop/38 onto current main (acb8f87) and resolve the four conflicts:
    • In app/routes/api/automations/[rkey].ts, app/routes/api/automations/[rkey]/logs.ts and app/routes/u/[handle]/[rkey]/logs.tsx, take main's logs.map(serializeDeliveryLogRow) (and main's filtered/dryRun props on DeliveryLog). Drop the branch's hand-written row maps: kind already comes from serializeDeliveryLogRow. Keep the logAutomationUpdate import next to main's getFilterSummary/hasFilterGates imports.
    • In app/islands/DeliveryLog.tsx, keep main's Alert, filter-summary and LocalTime imports and the branch's longer log-run-grouping.js import list.
    • In lib/actions/logs-query.test.ts (added by #70), add kind: null to both the ROW and ROW_JSON fixtures. Optionally also add a case showing that a kind: "automation-updated" row serializes with its kind. Then run pnpm run check and pnpm run test. With exactly these resolutions, both are green.

Follow-up (followup.md): file it. It is unchanged from round 1, and I checked it against the code again. reactivateSessionLostAutomations in lib/jetstream/session-loss.ts still writes a row with statusCode: null, error: null and kind NULL. getActivitySummary takes it as the newest resolved row, and activityBadge shows "Success" for it. That is an older system row this branch does not touch, so it is outside this issue, and the new kind column makes it a small fix.

loop:verdict run=review-20261030-091114-38 sha=94e2a89f508330ffe1995ed03ea600e2ee012424 verdict=changes-requested

Fix round 2: the review asked for changes.

loop:fix run=contribute-20261030-091653-38 round=2 issue=38

feat(logs): mark automation updates and pre-creation replays in the delivery log

Saving a change to an automation now writes an "update marker" row to its delivery log ("Automation updated: steps changed, turned on."), so runs above it read as the new version and runs below it as an older one, as the owner asked once #8 was merged. Runs whose source event is older than the automation itself (the creation time in its TID rkey) are labelled "event from before this automation existed" next to the event time, so a replay can no longer pass for a live trigger.

What changed #

  • delivery_logs.kind (new nullable column, migration 0003_known_princess_powerful.sql from pnpm db:generate: ALTER TABLE "delivery_logs" ADD COLUMN "kind" text;). "automation-updated" marks the update rows; NULL on every delivery and on the older system rows.
  • lib/automations/update-log.ts: works out what changed (renamed, description edited, trigger operations, watched accounts, steps compared by behavior hash, once per record, Blueprint, dry run on/off, turned on/off) and writes one marker row. A save that changes nothing writes nothing. The row has countsTowardRateLimit: false, and a failed write is logged, never fails the save.
  • Written by PATCH /api/automations/:rkey and by the sync's "deactivate redundant automations" action (the other path where the owner changes an automation).
  • The activity summary on the automation page (Last delivery and Last 7 days) leaves marker rows out, so an edit never shows as a "Success" delivery.
  • The public stats (lib/stats/compute.ts) leave marker rows out too: l.kind IS NULL is in the shared successful-action filter (both active-user counts) and in the action-bucket query, so a save never adds a successful action or makes its owner an active user.
  • The per-automation log, the account log browser and the admin log browser show markers as one full-width line, add the pre-creation label to run headers (and to flat legacy rows), and give flat rows the event time as a tooltip. The admin run header now shows the event time too, as the other two already did. When the log holds only markers, "No deliveries yet." still shows.
  • CONCEPTS.md: new "Update marker" entry.

Verified #

  • pnpm run check passes; pnpm run test passes (179 files, 2748 tests) after merging main (acb8f87).
  • New tests: PATCH writes the marker with the right message, kind and rate-limit flag, dated with the save, and returned by GET; a no-change save writes none; a step change plus a toggle is named ([rkey].test.ts); a redundant deactivation writes "turned off" (redundant-automations.test.ts); the activity summary ignores markers (activity-summary.test.ts); DeliveryLog renders markers full-width, keeps "No deliveries yet." when only markers exist, and labels only the replayed run (DeliveryLog.test.tsx); two marker rows (dry run off and on) change no number in computeStats() (lib/stats/compute.test.ts, which fails without the stats fix).
  • pnpm db:generate run again after the merge reports no schema changes; main added no migration, so 0003 does not clash.
  • I did not test it in a browser: this container has no dev server or PDS.

Acceptance criteria #

Decisions you can reverse #

  • Where the markers live. They are rows in delivery_logs with a new kind column, not a separate table, so they page and sort with the runs. The column needs the migration above.
  • "Before it existed" is judged against creation time, not the last edit. We only store the latest indexedAt, so we can't tell which version a past run ran on. The update markers show that history instead.
  • Steps changes are compared by behavior. A comment-only edit or a webhook re-verification does not count as "steps changed", and a save that changes nothing writes no marker.
  • Older system rows are unchanged. The rows for rate-limit disables, session loss and re-enabling after re-auth keep kind NULL.
  • The pre-creation label uses the warning colour inside the run header, which is uppercase like the rest of the header.

This round #

  • Round 1: the review found that marker rows counted as successful actions on /stats. Round 1 adds l.kind IS NULL to the stats queries and a test that markers move no stats number.
  • Round 2: the branch no longer merged with main after #65 and #70. The review asked for a rebase, but my rules forbid rewriting the branch's history, so I merged main (acb8f87) instead and resolved the four conflicts as the review described. The three routes now use main's serializeDeliveryLogRow, which already carries kind, plus main's filtered/dryRun props. DeliveryLog.tsx keeps main's imports and the branch's log-run-grouping.js imports. lib/actions/logs-query.test.ts gets kind: null in ROW and ROW_JSON, and a new case checks that an automation-updated row serializes with its kind.

Follow-up, filed as a new issue on merge if the review agrees:

fix(activity): stop counting the "Re-enabled after re-authorization" system row as a successful delivery

reactivateSessionLostAutomations (lib/jetstream/session-loss.ts) writes a delivery_logs row with statusCode: null, error: null and the message "Re-enabled after re-authorization." getActivitySummary (lib/actions/activity-summary.ts) takes the newest resolved row as "Last delivery" and counts every row in "Last 7 days". activityBadge in app/routes/u/[handle]/[rkey]/index.tsx falls through to the "Success" badge for a row with no error and no status code. So after a re-auth, the automation page reports a successful delivery that never happened. That is the false "it works" signal #38 is about. I found this by reading those three functions while making the activity summary skip the new update markers (kind IS NULL).

Where to start: give these system rows (and the session-loss and rate-limit disable rows) a delivery_logs.kind value, as the update markers now have, so the summary and the log views can treat them as markers. Or exclude statusCode IS NULL AND error IS NULL AND run_id IS NULL from the summary.

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

loop:pushed run=contribute-20261030-091653-38 sha=61b5ca572740574a0fd9cee21ffa11d91ddac474

The migration on 61b5ca572740 is what pnpm db:generate writes from the schema, and it only adds. Only the owner merges a branch with a migration: loop merge 38, once the gate passes.

0003_known_princess_powerful.sql:

ALTER TABLE "delivery_logs" ADD COLUMN "kind" text;

loop:migration run=verify-20261030-091926-38 sha=61b5ca572740574a0fd9cee21ffa11d91ddac474 result=ok

Verdict: merge. On 61b5ca5 the branch does what #38 and the owner's comment ask. A save writes an "Automation updated: ..." marker to the delivery log. Runs whose source event is older than the automation's TID creation time are labelled "event from before this automation existed". All three log views show the event time. Round 1's stats fix is still in, and round 2's merge with main was resolved exactly as that review described. pnpm run check and pnpm run test both pass (179 files, 2748 tests).

What I checked and how:

  • Earlier rounds:
    • Round 1: lib/stats/compute.ts still has l.kind IS NULL in the shared successfulAction fragment and in the action-bucket WHERE, and the compute.test.ts case still passes.
    • Round 2: main has not moved past acb8f87, so the branch merges cleanly. The merge commit 61b5ca5 takes main's serializeDeliveryLogRow in [rkey].ts, [rkey]/logs.ts and u/[handle]/[rkey]/logs.tsx, with no hand-written row maps left. kind flows through serializeDeliveryLogRow/serializeLogRow to all three islands, and DeliveryLog.tsx keeps main's imports. logs-query.test.ts has kind: null in both fixtures, plus a test that a marker row keeps its kind when serialized.
  • Issue criteria, checked against the code:
    • The pre-creation label (eventPredatesAutomation, which compares the event time with tidTimestampUs(rkey)) appears in the run headers and flat rows of DeliveryLog, LogsBrowser and AdminLogsBrowser.
    • The event time shows in every run header (the admin view gained it here) and as a tooltip on flat rows.
    • Markers are written by PATCH and by the redundant-automation deactivation. Those are the only paths where the owner changes an automation. The other .update(automations) callers are system or admin flips (rate limit, session loss, dry-run expiry, partner revoke), and those were already outside this issue.
  • Readers of delivery_logs:
    • Activity summary and stats exclude markers through kind IS NULL.
    • Markers never count toward the rate limit (countsTowardRateLimit: false).
    • The success and error filters require a status code or an error, so markers drop out of them.
    • The run-steps counters filter on run, step or dry run. Cleanup goes by age.
    • The session-loss streak cannot be broken by a marker in practice, because a save cannot reach the PDS while the session is lost.
  • Real-use throwaway test (in app/routes/api/automations/, deleted afterwards):
    • I seeded an automation with a webhook nested in a branch. I then did a form-style round trip: GET it, and PATCH back exactly what GET returned (webhook secret stripped). That wrote no marker, so an unchanged form save does not spam the log.
    • PATCH active: false and then active: true wrote "Automation updated: turned off." and "...turned on.", and GET returned both with kind: "automation-updated".
  • Migration 0003_known_princess_powerful.sql (ALTER TABLE "delivery_logs" ADD COLUMN "kind" text;):
    • It adds a nullable column with no default, and every existing row gets NULL. The code reads NULL as an ordinary delivery or one of the older system rows, so nothing already there changes meaning.
    • In Postgres this changes only metadata, with no table rewrite.
    • The owner merges migrations.
  • UI: the marker rows and the pre-creation note are static text. They add no control, so there is no keyboard or focus behaviour to check.

Follow-up (followup.md): file it. I checked it against the code again. reactivateSessionLostAutomations in lib/jetstream/session-loss.ts still inserts a "Re-enabled after re-authorization." row with statusCode: null, error: null and kind NULL. getActivitySummary takes it as the latest delivery, and activityBadge shows "Success" for it, which is a false "it works" signal. It is an older system row that this branch does not touch, so it falls outside this issue. The new kind column makes the fix small.

loop:verdict run=review-20261030-092436-38 sha=61b5ca572740574a0fd9cee21ffa11d91ddac474 verdict=merge followup=1

Merged main into the branch: 16a5e8b2fd53ed5167f8f70b326a5c98b28f9db4.

loop:carry from=61b5ca572740574a0fd9cee21ffa11d91ddac474 sha=16a5e8b2fd53ed5167f8f70b326a5c98b28f9db4

The migration on 16a5e8b2fd53 is what pnpm db:generate writes from the schema, and it only adds. Only the owner merges a branch with a migration: loop merge 38, once the gate passes.

0003_known_princess_powerful.sql:

ALTER TABLE "delivery_logs" ADD COLUMN "kind" text;

loop:migration run=verify-20261030-092901-38 sha=16a5e8b2fd53ed5167f8f70b326a5c98b28f9db4 result=ok

Merged main into the branch: 0cfb235be0054ef6d33ad1f34f83f67b74217649.

loop:carry from=16a5e8b2fd53ed5167f8f70b326a5c98b28f9db4 sha=0cfb235be0054ef6d33ad1f34f83f67b74217649

The migration on 0cfb235be005 is what pnpm db:generate writes from the schema, and it only adds. Only the owner merges a branch with a migration: loop merge 38, once the gate passes.

0003_known_princess_powerful.sql:

ALTER TABLE "delivery_logs" ADD COLUMN "kind" text;

loop:migration run=verify-20261030-094322-38 sha=0cfb235be0054ef6d33ad1f34f83f67b74217649 result=ok

Merged main into the branch: 45a97b76fad25dffac673e28e08b5664a502d6b0.

loop:carry from=0cfb235be0054ef6d33ad1f34f83f67b74217649 sha=45a97b76fad25dffac673e28e08b5664a502d6b0

The migration on 45a97b76fad2 is what pnpm db:generate writes from the schema, and it only adds. Only the owner merges a branch with a migration: loop merge 38, once the gate passes.

0003_known_princess_powerful.sql:

ALTER TABLE "delivery_logs" ADD COLUMN "kind" text;

loop:migration run=verify-20261030-095253-38 sha=45a97b76fad25dffac673e28e08b5664a502d6b0 result=ok

Merged main into the branch: 6fb66ce0972a7b09297b5c04f6026864937de879.

loop:carry from=45a97b76fad25dffac673e28e08b5664a502d6b0 sha=6fb66ce0972a7b09297b5c04f6026864937de879

The migration on 6fb66ce0972a is what pnpm db:generate writes from the schema, and it only adds. Only the owner merges a branch with a migration: loop merge 38, once the gate passes.

0003_known_princess_powerful.sql:

ALTER TABLE "delivery_logs" ADD COLUMN "kind" text;

loop:migration run=verify-20261030-100633-38 sha=6fb66ce0972a7b09297b5c04f6026864937de879 result=ok

Merged main into the branch: 088b816fed02fd2bbc886f24482aa6b42f06045b.

loop:carry from=6fb66ce0972a7b09297b5c04f6026864937de879 sha=088b816fed02fd2bbc886f24482aa6b42f06045b

The migration on 088b816fed02 is what pnpm db:generate writes from the schema, and it only adds. Only the owner merges a branch with a migration: loop merge 38, once the gate passes.

0003_known_princess_powerful.sql:

ALTER TABLE "delivery_logs" ADD COLUMN "kind" text;

loop:migration run=verify-20261030-101310-38 sha=088b816fed02fd2bbc886f24482aa6b42f06045b result=ok

Merged main into the branch: 21430195c55bc586972411583b68f615f518079f.

loop:carry from=088b816fed02fd2bbc886f24482aa6b42f06045b sha=21430195c55bc586972411583b68f615f518079f

The migration on 21430195c55b is what pnpm db:generate writes from the schema, and it only adds. Only the owner merges a branch with a migration: loop merge 38, once the gate passes.

0003_known_princess_powerful.sql:

ALTER TABLE "delivery_logs" ADD COLUMN "kind" text;

loop:migration run=verify-20261030-102922-38 sha=21430195c55bc586972411583b68f615f518079f result=ok

Merged main into the branch: c12da05ec89b30ac4a3206144797cd6d9864830f.

loop:carry from=21430195c55bc586972411583b68f615f518079f sha=c12da05ec89b30ac4a3206144797cd6d9864830f

The migration on c12da05ec89b is what pnpm db:generate writes from the schema, and it only adds. Only the owner merges a branch with a migration: loop merge 38, once the gate passes.

0003_known_princess_powerful.sql:

ALTER TABLE "delivery_logs" ADD COLUMN "kind" text;

loop:migration run=verify-20261030-104222-38 sha=c12da05ec89b30ac4a3206144797cd6d9864830f result=ok

Merged main into the branch: 4fba2b9ed693237e8501c06c4be631e24b11721d.

loop:carry from=c12da05ec89b30ac4a3206144797cd6d9864830f sha=4fba2b9ed693237e8501c06c4be631e24b11721d

The migration on 4fba2b9ed693 is what pnpm db:generate writes from the schema, and it only adds. Only the owner merges a branch with a migration: loop merge 38, once the gate passes.

0003_known_princess_powerful.sql:

ALTER TABLE "delivery_logs" ADD COLUMN "kind" text;

loop:migration run=verify-20261102-175359-38 sha=4fba2b9ed693237e8501c06c4be631e24b11721d result=ok

Merged onto main as 398d2142bd7fc1db4227851888ba6e55040bd98f.

loop:merged sha=398d2142bd7fc1db4227851888ba6e55040bd98f head=4fba2b9ed693237e8501c06c4be631e24b11721d

Filed the follow-up as #91, a new issue, not marked ready, from branch loop/38 (398d2142bd7f): fix(activity): stop counting the "Re-enabled after re-authorization" system row as a successful delivery

loop:filed issue=91

Sign up or Sign in to add to the discussion.