Skip to content

The CI watch owns its rules, tested in-process behind a seam - #399

Merged
JacobStephens2 merged 3 commits into
mainfrom
issue-390
Oct 5, 2026
Merged

JacobStephens2 merged 3 commits into
mainfrom
issue-390

Conversation

@JacobStephens2

@JacobStephens2 JacobStephens2 commented Oct 5, 2026 •

Copy link
Copy Markdown
Owner

Summary

The CI watch's rules now reach GitHub, the poll interval, the grace deadline and progress lines only through a private Outside, the way the Claim, the Base fix and the Delivery already do. Callers see no change.

 ci::watch(issue, sha, base_commit)          # signature unchanged
-  github::checks_on / poll::within / progress::step
+  watch_through(&mut OnGitHub { issue }, …)
 ci::rerun(issue, sha, base_commit, failed)  # signature unchanged
-  github::rerun_failed_jobs / poll::within / …
+  rerun_through(&mut OnGitHub { issue }, …)
trait Outside {
    type Deadline;                                   // Instant for gh, a pause count in memory
    fn checks_on(&mut self, sha: &str) -> Result<Vec<Check>>;
    fn rerun_failed_jobs(&mut self, workflow_run: u64) -> Result<()>;
    fn pause(&mut self) -> Result<()>;               // fails with `interrupted`
    fn start_grace(&mut self) -> (Duration, Self::Deadline);
    fn passed(&mut self, deadline: &Self::Deadline) -> bool;
    fn step(&mut self, line: String);
}

The split, de-duplicating workflow runs, spotting the new attempt and the running-count lines all stay on the module's side. The module runs its own small until/within loops over the seam, and poll.rs is untouched. The only change outside src/ci.rs is #[derive(Clone)] on github::Check, which the in-memory adapter needs.

Evidence

  • Before: only the pure split had tests (5 direct tests). Nothing tested the polling, the grace period or ADR 0010's Check re-run.
    After: 14 in-process tests run through watch_through/rerun_through against InMemory. They never sleep and don't read environment variables. They cover every acceptance-criteria case, and the five split cases now run through the watch. They assert on the result, the re-run requests and the progress lines.
a_check_rerun_waits_for_an_inherited_failure_sharing_a_workflow_run_to_be_rerun_too
  own: test (check run 11, workflow run 1); inherited: lint (check run 12, workflow run 1)
  HEAD listings: [test new pending, lint OLD red] → [both new pending] → [test green, lint red]
  ⇒ Some(Failed { own: [], inherited: [lint] }), requests == [1], no "still running" line

I checked that this test catches the bug it is meant for: when I dropped the Inherited failures from the attempt before, the test failed.

cargo test passes in full, and so do cargo clippy --all-targets -- -D warnings and cargo fmt --check. One full run had a single failure in tests/architect_run.rs, which doesn't touch the CI watch. It passed on the four full runs after that, so I take it to be a timing flake on a busy machine.

Merge Danger

Door: two-way

A refactor plus tests, which reverts cleanly.

Blast Radius: CI-watch

The watch and Check re-run behave as before: the same progress lines, the same polling order (probe, then check the deadline, then pause), and the same gh calls.

Unaddressed findings

Standards

  • Duplicated Code: until/within copy poll::until/poll::within. Skipped: the issue's Decisions call for this. The poll helpers would hold the adapter borrowed while the probe needs it, and changing poll.rs is out of scope.
  • Test hedging: tests sort the re-run requests, or refuse both workflow runs. Skipped: this is on purpose, so the tests don't depend on the order of internal calls. The reviewer also judged it acceptable.
  • #[derive(Clone)] on Check widens a production type for tests. Skipped: the reviewer judged it acceptable. It's harmless, and the in-memory adapter needs it.

Spec

  • The inherited-shared-workflow-run test catches a missing wait only through the progress lines; asserting HEAD read counts would be more direct. Skipped: the issue asks tests to assert on results, requests and progress lines, not internal calls. I added a comment explaining what the test catches, and confirmed by mutation that it fails without the wait.
  • Asserting that the Base branch commit is the last read is an order check. Skipped: the acceptance criteria ask for "read once, after every check has finished", and this is the direct way to assert it.

Closes #390

🤖 Generated with Claude Code

JacobStephens2 and others added 3 commits October 4, 2026 22:32
The CI watch reaches GitHub, the poll interval, the grace deadline and
progress lines only through a private Outside, with a gh adapter and an
in-memory one. Its rules, the Check re-run's included, are tested through
the watch; the direct tests of the split are replaced by tests through it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
One start_grace gives the grace period and its deadline, so the gh
adapter reads the grace period once and the seam has one method fewer;
test helpers named for what they return.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@JacobStephens2
JacobStephens2 merged commit 79a6ee8 into main Oct 5, 2026
13 checks passed
@JacobStephens2
JacobStephens2 deleted the issue-390 branch October 5, 2026 02:59
@JacobStephens2 JacobStephens2 mentioned this pull request Oct 5, 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.

The CI watch owns its rules, tested in-process behind a seam

1 participant