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

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

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

opened this

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: GET and PUT /api/v1/repos/:owner/:name/upstream read and set the upstream ({url, branch, enabled}), and POST …/upstream/check runs 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, and checkDue's existing test covers it. An open page updates when a check finishes.
  • Docs: docs/ORIGINS.md has 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.ts already 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

Closes #3980

commented

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: GET and PUT /api/v1/repos/:owner/:name/upstream read and set the upstream ({url, branch, enabled}), and POST …/upstream/check runs 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, and checkDue's existing test covers it. An open page updates when a check finishes.
  • Docs: docs/ORIGINS.md has 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.ts already 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

Changes requested — before the latest push

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.

  1. 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#upstream with 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.
  2. checkUpstream now appends repo.upstream_checked on every check except off, 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 is off. 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.

commented

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/check is 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_checked event with an empty payload for that repository. A check that comes back off appends none.
  • Cleanup: the upstream GET calls canAdmin once and reads the row with getFollow, instead of checking admin twice through followShown.

How the change works

  • API: GET and PUT /api/v1/repos/:owner/:name/upstream, plus POST …/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.md has 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.ts already 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

Approved — before the latest push

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.

commented

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

Approved

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 migrateUrl and upstreamUrl (http or https only, no password), the branch through isSafeRef with a 200-character limit, and all output is escaped through html.
  • Both new events are classified admins, and repo.upstream_checked carries an empty payload.
  • When a different admin saves the same URL, originId carries over but byId becomes 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-remote and fetch, up to CHECK_TIMEOUT_MS (120 s). The repo.upstream_checked live event would let it answer at once and update the card when the check finishes.
  • test/upstream-follow-web.test.ts runs a real check against https://example.com. A hermetic URL such as http://127.0.0.1:1/ would keep it off the network.

Approved.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs