Skip to content

feat(upstream-watch): ledger on an orphan branch, commit-comment notices, on-demand dispatch - #253

Merged
pacphi merged 21 commits into
mainfrom
feat/upstream-watch-ledger-ref
Sep 28, 2026
Merged

pacphi merged 21 commits into
mainfrom
feat/upstream-watch-ledger-ref

Conversation

@pacphi

@pacphi pacphi commented Sep 28, 2026

Copy link
Copy Markdown
Owner

Summary

The upstream watch's first scheduled run (36432957846) read every thread, then failed to post: the Actions token cannot comment on the locked ledger issue #243. This replaces the issue-comment ledger with a git ledger, commit-comment notices and on-demand dispatch (audit decision 15; ADR-0041 §7 updated).

  • Record: events.ndjson on the orphan branch upstream-watch-ledger, committed only on days with new records, built with git plumbing (scripts/upstream-watch/ledger-branch.mjs). The next window starts at the newest commit's Checked-At trailer, which holds when a read failed; no fixed lookback and no heartbeat commit.
  • Notifications: when a new record needs the maintainer, github-actions[bot] comments on that day's ledger commit and mentions watchPolicy.notify.mention. A throwaway-repository probe showed this reaches the inbox and email (reason mention). No issue is involved.
  • Dispatch: record fires the claude.ai routine's API trigger for a released fix whose upstream/<id> branch does not exist yet, with the thread in the payload; at most two firings, three days apart. Each firing and the resulting draft pull request are recorded (fired, dispatch-pr); pull requests from forks are ignored. The routine no longer runs on a schedule.
  • Queries and liveness: ledger [--id] [--event] [--since] [--recorded-since]; report shows the last successful scheduled run and warns after 48 hours. record --dry-run lists wouldFire without calling the trigger.
  • Workflow: daily at 14:17 UTC; Record → Push the ledger commit → Notify (commit comment) → Verdict (fails on residual read or dispatch errors). The trigger token reaches only the Record step, and inputs pass through env. Permissions: contents: write, actions: read, pull-requests: read; no issues.
  • Registry: watchPolicy.repo (home repository), ledger: { branch, sentinel }, notify: { mention }; the comment-ledger fields are removed (schemaVersion stays 6).
  • Removed: the comment subcommand, check --ledger, the ledger-authors rules, the upstream-dispatch label and its steps, and their tests.
  • Docs: docs/UPSTREAM-WATCH.md, ADR-0041 §7, audit decision 15, the glossary, MAINTAINER.md and the upstream-status skill describe only the new design.
  • Ledger commit messages write thread ids as owner/repo no. n, so no ledger commit adds a "referenced this issue" entry to an upstream thread.

Design: docs/superpowers/specs/2026-09-28-upstream-watch-ledger-branch-design.md. Plan: docs/superpowers/plans/2026-09-28-upstream-watch-ledger-branch.md.

After merge (maintainer)

  • Before 14:17 UTC: gh workflow run upstream-watch.yml -f record=false -f since=2026-09-21T00:00:00Z, then read wouldFire in the job summary.
  • If anything would fire: create the routine's API trigger token in claude.ai, run gh secret set UPSTREAM_DISPATCH_TOKEN, and give the routine its new prompt (in docs/UPSTREAM-WATCH.md) and API trigger. Its schedule stays off (it is paused now).
  • gh workflow run upstream-watch.yml -f since=2026-09-21T00:00:00Z: the ledger branch appears, and a notice arrives if something needs you.
  • Close Upstream watch #243 with a pointer to the ledger branch; delete the probe repository; decide on a rule for main (the job token can push branches).

Test plan

  • node scripts/run-tests.mjs unit on Node 26.4.0: 5638 pass, 0 fail, 6 skipped (Windows-only)
  • tsc -p tsconfig.json, eslint . (0 errors; warnings unchanged vs main), eslint src bin --rule 'complexity: [2, 50]', markdownlint-cli2, lychee --offline, node scripts/build-check.mjs
  • Real git round trip against a bare repository in the tests (sandboxed environment); the final reviewer also ran the store against a shallow clone (absent branch, two commits, Checked-At, a stale-parent push rejected)
  • Probe repository run 36451224053: the job token pushes an orphan branch, and a bot commit comment mentioning the owner notifies them (inbox and email)
  • Nine tasks, each reviewed; the final whole-branch review's findings (5 Important, 10 Minor) fixed in one wave and re-reviewed clean
  • UI suite not run: no UI files changed
  • This pull request's preview job runs record --dry-run against the real threads

🤖 Generated with Claude Code

The first live runs on 2026-09-28 showed the comment ledger cannot work:
the workflow token cannot comment on the locked #243, the routine reads
one page of comments, and GitHub stops comments at 2,500. This design
moves the record to refs/upstream-watch/ledger, notifies through an
action digest on #243, fires the dispatch routine on demand, and removes
the comment-ledger artifacts in the same change (decisions L1-L6).
Revision 2 after adversarial review by Claude and Codex and a live probe
(pacphi/upstream-watch-probe-20260928, run 36451224053): the record moves
to the orphan branch upstream-watch-ledger, committed only on days with
new records; notification is a github-actions[bot] commit comment that
mentions the maintainer (probe: reason mention); dispatch fires the
routine with the thread in the payload and records each firing; liveness
comes from the Actions API instead of heartbeat commits; #243 closes.
Nine tasks: the ledger store, the dispatcher, the notice, the registry's
new watch policy with the comment ledger removed, record, ledger and the
last-run check, the workflow, the docs (decision 15, ADR-0041 §7), and
the gate. The spec now names watchPolicy.repo, the home repository that
ledger.repo used to carry.
…ger branch and notice; the comment ledger is gone
…a dry run would fire, times out the trigger

- openPullRequest asks gh for isCrossRepository and takes the first pull
  request from this repository: `--head` matches the branch name in any fork.
- dispatch() takes dryRun: where it would fire it adds { id, version, branch }
  to wouldFire; the branch checks, firing limits and pull request lookups run
  as before and only read.
- A fired record without fields is an error for its id, not a crash: the
  dispatch-pr branch lookup is inside its try.
- The trigger call carries AbortSignal.timeout(30 s); a timeout is an error
  like any other.
…nch by exit code

- parseRecords requires string id, event and date, an object of fields and a
  UTC recordedAt; anything else is "events.ndjson line N is not a ledger record".
- read() asks `git ls-remote --exit-code --heads` first: exit 2 is an empty
  ledger with no fetch, 0 fetches as before, anything else throws. git's
  message, which a translated git changes, is no longer matched.
…nce nothing, notice hint finds the run

- report's last run counts scheduled runs only (event=schedule): pull request
  previews and manual dry runs also succeed. No scheduled run found is a
  warning in the text report.
- ledger --recorded-since <time> selects by recordedAt; --since still selects
  by the event's date. The notice ends with --recorded-since and the run's
  time, so the hint shows every record of that run.
- Ledger commit sentences go through commitSafe: `owner/repo no. n` and
  `no. n`, because GitHub turns ids in a commit message into references on
  those threads. The notice keeps its code-span ids.
- record --dry-run runs dispatch with dryRun and prints wouldFire (always
  present; [] when nothing would fire, and on blind output). Text mode adds
  one "Would fire" line each.
- A ledger commit that cannot be built after a firing keeps the fired records
  in the blind JSON and prints each session link to stderr.
- The preview and Record step summaries add "would fire N" and one line per
  entry of wouldFire.
- The exit-3 comment names a ledger commit that could not be built.
- A static test pins that the Verdict step fails on fetchErrors plus
  dispatchErrors.
…s, wouldFire and the rollout

- UPSTREAM-WATCH.md: the last successful scheduled run; `ledger
  --recorded-since`; commit messages write ids as `owner/repo no. n`;
  `wouldFire` in record and the job summaries; exit 3 also covers a ledger
  commit that could not be built; clearing a dispatch stuck after two firings.
- Both upstream-status skills: `report`, `check` and `ledger` only read;
  `record` fires the dispatch routine and builds a ledger commit, so an agent
  runs it only with --dry-run.
- MAINTAINER.md and the glossary (`idle` in the notice row).
- Spec and plan: the components, notice hint and Commits section match the
  code; rollout steps 3 and 4 start with a record=false run that reads
  wouldFire, and the rehearsal is its first entry (the agentic-qe entry the
  old step named is retired).
@pacphi
pacphi merged commit 82d1211 into main Sep 28, 2026
17 of 18 checks passed
@pacphi
pacphi deleted the feat/upstream-watch-ledger-ref branch September 28, 2026 19:46
@pacphi pacphi mentioned this pull request Sep 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Upstream watch

1 participant