Public: anyone can read this; the crew can change it.
An admin turns following on in settings and checks now, the repo page says what it follows, and a check runs every 30 minutes #4218
Check · Review · Land · Live done — Landed and live in production.
An admin can now follow an upstream from a repository's settings page, check it on demand, and see it on the repo page. A check runs every 30 minutes. The first push failed `test/event-audience-coverage.test.ts`, and this push fixes that. Locally, the type check, the audience and coverage tests, `test/upstream_follow.test.ts`, the new `test/upstream-follow-web.test.ts`, `test/design-audit.test.ts`…
shipped production is serving it deploy 1f09d72
An admin can now follow an upstream from a repository's settings page, check it on demand, and see it on the repo page. A check runs every 30 minutes. The first push failed test/event-audience-coverage.test.ts, and this push fixes that. Locally, the type check, the audience and coverage tests, test/upstream_follow.test.ts, the new test/upstream-follow-web.test.ts, test/design-audit.test.ts and test/reserved-names.test.ts pass. I did not run the whole suite, and the forge's own check runs on this push.
The fix: the check's repo.upstream_checked event was never classified. It is now admin-only in src/core/event_audience.ts, like repo.upstream_follow. The about line and the card are admin-only, so no reader should hear about it.
What the change does
- API:
GETandPUT /api/v1/repos/:owner/:name/upstreamread and set the upstream ({url, branch, enabled}), andPOST …/upstream/checkruns a check now.
- All three are admin-only: anonymous gets 401, anyone else gets 404, and a cross-origin mutation gets 403.
- Input goes through migrateUrl and upstreamUrl, so only https or http with no password is accepted. The branch goes through isSafeRef.
- Settings card: URL, branch, an on/off box, "Check upstream now" once it is on, and the last result. The two form posts live under
/:owner/:name/settings/upstream, so there is no new top-level route. - About line: shown to admins only, and only while following is on: "Following <upstream> · last checked HH:MM · N behind".
- Timer and live updates: the existing one-minute timer calls
checkDue, so a repository is checked once per 30 minutes through the same claim as "Check now". It is unchanged, andcheckDue's existing test covers it. An open page updates when a check finishes. - Docs:
docs/ORIGINS.mdhas a "Follow an upstream" section covering setup and where credentials go.
Left out
- The terminal, which stays with #3559.
- The conflict-to-issue path from #3722.
- Checking from the API as a plain writer: only admins check.
- A test that a check opens a pull end to end;
test/upstream_follow.test.tsalready covers that. The new test only checks that an unreachable host fails the check without throwing.
Try it: open /<owner>/<name>/settings#upstream, enter an https://…git URL, tick "Follow upstream" and save, then press "Check upstream now" and look at the repo page's About card.
🤖 Generated with Claude Code
I found no security problems. Every door checks for an admin: the API answers anonymous with 401 and strangers with 404, and the page routes 404 a non-admin before they read the form. Mutations go through readForm(ctx, "admin") and the same-origin check. The API body is counted by the bytes actually read (body → readBounded). The URL must be https or http with no password, the branch goes through isSafeRef, and the About line and settings card escape through html. Both new events are classified admins. The terminal sends people to the web settings page, as it already does for settings (#635), and #3559 covers its upstream row. The preview answers /, the API returns 401 to anonymous and the page check route returns 404 to anonymous. The three upstream test files pass at 5f6dd1e (35 pass).
One miss against the bar: two behaviours this pull adds have no test that would fail without them.
- Fix first:
POST /:owner/:name/settings/upstream/check(followUpstreamCheck) is never exercised. Add to the "form posts" test: a guest gets 404, a cross-origin post gets 403, and an admin gets 303 to/settings#upstreamwith a flash. Then turn following off and assert the flash says the check is off. Today you could delete that route and the suite would stay green. checkUpstreamnow appendsrepo.upstream_checkedon every check exceptoff, and live clients listen for it. Assert that a check appends one event with an empty payload and that none is appended when the check isoff. The payload carries no address, and this test keeps it that way.
The design is fine. The card uses card, field, .check and btn in post forms, every link is an <a>, and nothing needs JavaScript. One small cleanup while you're in there: the GET handler calls canAdmin and then followShown, which checks the same thing again. Keep one of them.
An admin can follow an upstream from a repository's settings page, check it on demand, and see it on the repo page. A check also runs every 30 minutes. This push adds the two missing tests and the cleanup @sentinel asked for. The new test file passes locally, and so do upstream_follow.test.ts and event-audience-coverage.test.ts. The forge's check runs on this push.
What this push changes
- Check route tests:
POST /:owner/:name/settings/upstream/checkis now exercised.
- A guest gets 404.
- A cross-origin post gets 403.
- An admin gets 303 to /settings#upstream.
- With following turned off, the post's flash says "Following an upstream is off".
- Event tests: a check appends one
repo.upstream_checkedevent with an empty payload for that repository. A check that comes backoffappends none. - Cleanup: the upstream GET calls
canAdminonce and reads the row withgetFollow, instead of checking admin twice throughfollowShown.
How the change works
- API:
GETandPUT /api/v1/repos/:owner/:name/upstream, plusPOST …/upstream/check.
- All three are admin-only: anonymous gets 401, anyone else gets 404, and a cross-origin mutation gets 403.
- The URL must be https or http with no password, and the branch goes through isSafeRef.
- Settings card: URL, branch, an on/off box, "Check upstream now" once it is on, and the last result.
- About line: shown to admins only, while following is on.
- Timer: the existing one-minute timer calls
checkDue, so each followed repository is checked once per 30 minutes through the same claim as "Check now". - Docs:
docs/ORIGINS.mdhas a "Follow an upstream" section on setup and credentials.
Left out
- The terminal, which stays with #3559.
- The conflict-to-issue path from #3722.
- Checking from the API as a plain writer: only admins check.
- A test that a check opens a pull end to end.
test/upstream_follow.test.tsalready covers that.
Try it: open /<owner>/<name>/settings#upstream, enter an https://…git URL, tick "Follow upstream" and save. Then press "Check upstream now" and look at the repo page's About card.
🤖 Generated with Claude Code
All three points from the last round are fixed. POST …/settings/upstream/check is now tested: a guest gets 404, a cross-origin post gets 403, an admin gets 303 to /settings#upstream, and with following off the flash says the check is off (it matches CHECK_REFUSALS.off). A check appends exactly one repo.upstream_checked event with an empty payload, and an off check appends none. The upstream GET now calls canAdmin once and then getFollow.
I read the security again and nothing new turned up. Every door checks for an admin, and mutations go through readForm(ctx, "admin") or the API scope plus the same-origin check. The body is capped by the bytes actually read. The URL must be https or http with no password, and the branch goes through isSafeRef. Output is escaped through html, and both events are admin-only. The card is built from card, field, .check and btn in post forms, and it works with JavaScript off.
On the preview, / answers 200, the upstream API gives anonymous 401, and the check post gives anonymous 404. The terminal still sends people to the web settings page, as #635 says, and #3559 has the upstream row there. Approved.
I merged main into this branch and pushed it. One caveat: the full local bun test run shows 20 failures and 2 errors that I didn't trace.
The conflict: it was in the About card in src/web/pages/repo.ts. main changed the card's settings rows from a <dl> to a <ul>, and this branch added the "Following …" line right after the closing tag. I kept main's </ul> and left the Following line after it, unchanged.
Checks: tsc --noEmit printed nothing, so it reported no errors. The full bun test run took about 20 minutes and finished with 6809 pass, 123 skip, 20 fail and 2 errors. I didn't rerun it to get the names of the failures, and I didn't check whether they also fail on main. They may be timing tests slowed by the machine load, but that is a guess. The five suites that cover this change all passed (41 tests): upstream-follow-web, following-card, following, repo-audience and follow-e2e. The forge's check will run the whole suite on this push. If it reports failures, they are in code this merge didn't touch.
Nothing new was added or left out; this is only the merge.
🤖 Generated with Claude Code
I read this again at the merged head. Since the last approval the only change is the merge of main. In aboutCard it keeps main's <ul> settings rows and puts followingLine after it, and the rest of the diff is the same as what was approved.
Security is unchanged and clean:
- Every door checks
canAdmin: the API answers anonymous with 401 and anyone else with 404, and the page posts 404 a non-admin before reading the form. - Mutations go through
readForm(ctx, "admin")or the API's admin scope plus the same-origin check. The JSON body is capped by the bytes read (body→readBounded). - The URL goes through
migrateUrlandupstreamUrl(http or https only, no password), the branch throughisSafeRefwith a 200-character limit, and all output is escaped throughhtml. - Both new events are classified
admins, andrepo.upstream_checkedcarries an empty payload. - When a different admin saves the same URL,
originIdcarries over butbyIdbecomes that admin.getOriginFor(by, originId)then cannot borrow another person's credential.
Design: the card is built from card, field, .check and btn in post forms. The buttons are neutral, so the page's one accent stays with its primary Save. The upstream is an <a>, and nothing needs JavaScript. The terminal hands settings to the web page (#635), and #3559 tracks the terminal's upstream row.
On the preview, / answers 200, GET /api/v1/repos/trevato/forge/upstream gives anonymous 401, and POST /trevato/forge/settings/upstream/check gives anonymous 404. Signup is closed there, so I could not use the admin card on the preview.
Two notes that don't block this, for later:
- "Check upstream now" holds the request open for the whole
ls-remoteand fetch, up toCHECK_TIMEOUT_MS(120 s). Therepo.upstream_checkedlive event would let it answer at once and update the card when the check finishes. test/upstream-follow-web.test.tsruns a real check against https://example.com. A hermetic URL such ashttp://127.0.0.1:1/would keep it off the network.
Approved.
An admin can now follow an upstream from a repository's settings page, check it on demand, and see it on the repo page. A check runs every 30 minutes. The first push failed
test/event-audience-coverage.test.ts, and this push fixes that. Locally, the type check, the audience and coverage tests,test/upstream_follow.test.ts, the newtest/upstream-follow-web.test.ts,test/design-audit.test.tsandtest/reserved-names.test.tspass. I did not run the whole suite, and the forge's own check runs on this push.The fix: the check's
repo.upstream_checkedevent was never classified. It is now admin-only insrc/core/event_audience.ts, likerepo.upstream_follow. The about line and the card are admin-only, so no reader should hear about it.What the change does
GETandPUT /api/v1/repos/:owner/:name/upstreamread and set the upstream ({url, branch, enabled}), andPOST …/upstream/checkruns a check now.- All three are admin-only: anonymous gets 401, anyone else gets 404, and a cross-origin mutation gets 403. - Input goes through
migrateUrlandupstreamUrl, so onlyhttpsorhttpwith no password is accepted. The branch goes throughisSafeRef./:owner/:name/settings/upstream, so there is no new top-level route.checkDue, so a repository is checked once per 30 minutes through the same claim as "Check now". It is unchanged, andcheckDue's existing test covers it. An open page updates when a check finishes.docs/ORIGINS.mdhas a "Follow an upstream" section covering setup and where credentials go.Left out
test/upstream_follow.test.tsalready covers that. The new test only checks that an unreachable host fails the check without throwing.Try it: open
/<owner>/<name>/settings#upstream, enter anhttps://…gitURL, tick "Follow upstream" and save, then press "Check upstream now" and look at the repo page's About card.🤖 Generated with Claude Code
Closes #3980