Skip to content

ci: install cargo-make as a prebuilt binary - #794

Merged
sanity merged 2 commits into
mainfrom
fix/ci-cargo-make-prebuilt
Oct 16, 2026
Merged

sanity merged 2 commits into
mainfrom
fix/ci-cargo-make-prebuilt

Conversation

@sanity

@sanity sanity commented Oct 16, 2026

Copy link
Copy Markdown
Contributor

cargo install --force cargo-make now fails on every run: all zip 2.x releases, which cargo-make 0.37.24 requires (zip = "^2"), have been yanked from crates.io. build and ui-playwright-tests stop at "Install cargo-make" before touching any code (e.g. run 38869289042 on #793). Because of --force, the cache never prevented the recompile.

This switches both jobs to taiki-e/install-action with cargo-make@0.37.24, which downloads the released binary, and drops the now-pointless cache step. Same version, no compile.

Found while reviewing #793.

https://claude.ai/code/session_019J28r7v39Pf1XZDNUUAidG

[AI-assisted - Claude]

Compiling cargo-make 0.37.24 from source now fails: every zip 2.x release
it depends on (zip = "^2") has been yanked from crates.io. The step used
--force, so it recompiled on every run and the cache never helped; build
and ui-playwright-tests failed at 'Install cargo-make' before reaching
any code (e.g. run 38869289042).

taiki-e/install-action fetches the released binary, pinned to the same
version, so nothing is compiled and the cache step is dropped.

Claude-Session: https://claude.ai/code/session_019J28r7v39Pf1XZDNUUAidG
…ocked

Review follow-ups on #794. fallback: none stops install-action from
silently dropping back to the source build this change exists to avoid.
The README's plain `cargo install cargo-make` fails locally for the same
yanked-zip reason; --locked uses cargo-make's own lockfile and installs
cleanly (verified with 0.37.24).

Claude-Session: https://claude.ai/code/session_019J28r7v39Pf1XZDNUUAidG

@sanity sanity left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: #794 (Full tier: CI configuration)

Reviewers run: code-first, skeptical and big-picture Claude lenses, plus Codex (codex review --base origin/main). All ran on 2ec7939.

Findings, all addressed in a549422:

  • README told contributors to run cargo install cargo-make, which fails locally for the same yanked-zip reason. It now uses --locked, verified to install 0.37.24 cleanly.
  • install-action's default fallback would silently drop back to a source build if no prebuilt binary matched the runner. It is now fallback: none.

Considered, not changed:

  • SHA-pinning install-action: the file uses floating tags for every third-party action, including the existing taiki-e/install-action@cargo-binstall, so this keeps the convention.
  • The unpinned dioxus-cli binstall is the same failure class but a separate step; filed as #795.

Verified: the install-action manifest carries 0.37.24 with an x86_64 musl binary; no other workflow compiles cargo-make. CI is green on a549422, including build and ui-playwright-tests, which previously died at the install step.

Verdict: ready to merge. The follow-up commit is review-driven and small; the diff was re-checked.

[AI-assisted - Claude]

@sanity
sanity merged commit 118d64a into main Oct 16, 2026
7 checks passed
@sanity
sanity deleted the fix/ci-cargo-make-prebuilt branch October 16, 2026 08:13
sanity added a commit that referenced this pull request Oct 16, 2026
main fixed the same breakage in #794 (cargo-make as a prebuilt binary),
which conflicts with this. Drop ours and take main's.

Claude-Session: https://claude.ai/code/session_01SzES9BQh5PhrJhHdh9FSFw
sanity added a commit that referenced this pull request Oct 17, 2026
…-keys (#580) (#792)

* fix(ui): reload an open tab when River's room contract or delegate re-keys (#580)

An open tab runs the build it loaded, with the room contract and chat
delegate WASM compiled in. After a re-key it stays on the old contract key,
so its rooms go quiet and what it posts reaches nobody, and it keeps writing
to the old delegate, whose changes the new build no longer imports.

The UI now watches River's two pointer records (100 bytes each, at fixed
addresses): GET + subscribe on every connect, with a 15-minute GET backstop.
build.rs bakes in which pointer version this build is, from
pointer-records.toml; a PointerResolver seeded with that floor reports a
newer generation only for a signature-verified record above it.

On a newer generation the tab reloads its iframe into the new build, keeping
the ?__sandbox=1 query. Loop bounds live in the URL, not storage: at most 4
tries per target with 2/5/15-minute backoff (the 1.2 MB web container can
reach a node after the pointer does), at most 6 automatic reloads per hour,
then a banner with a manual Reload. A visible tab gets a 15 s countdown with
"Not now"; a hidden tab waits 30-120 s; nothing reloads over unsent text.

Docs: river-publish.md step 7 now says the pointer publish is what reloads
open tabs, so it must follow the UI publish (already enforced by its
pre-flight) and should wait ~10 minutes; publish-river prints a reminder.

Claude-Session: https://claude.ai/code/session_01SzES9BQh5PhrJhHdh9FSFw

* fix(ui): re-plan the reload when the second pointer answer arrives (#580)

End-to-end on a local node: when both pointers move, the room pointer's
answer lands ~7 ms before the delegate's. The first names a partial target
(r2d1) that has spent no tries, so the plan started with no delay and kept
it when the real target (r2d2) arrived. A tab whose node lacked the new
bundle then retried every 15 s and spent all four tries in under a minute.

replan() now runs on every target move: refused -> manual-only, otherwise
the running plan takes the later of its wait and the new target's backoff,
and never gets shorter. Regression test replays the sequence; it fails with
the old keep-the-plan behaviour.

Also: log the cap decision when a plan is made, move the banner below the
conversation header (it covered the room title), and refuse publish-river
when RIVER_POINTER_RECORDS is set (dx build hides the cargo warning).

Claude-Session: https://claude.ai/code/session_01SzES9BQh5PhrJhHdh9FSFw

* fix(ui): size the reload retries and publish gap from measured propagation (#580)

Production telemetry over River UI publishes 30000397-403 (41 sampled
deliveries of the ~1.2 MB web container): median ~20 s, 71% within 60 s,
80% within 5 min, p90 ~10 min, max ~2 h; the 100-byte pointer reaches
subscribers in under 0.5 s.

So the guessed numbers change: the publish gap before
publish-pointer-records goes from ~10 to ~2 minutes (clears the fast
cluster; longer buys little), and the retries gain a fifth try at +45 min,
spanning ~67 minutes instead of ~22, past the p90 with margin.

Claude-Session: https://claude.ai/code/session_01SzES9BQh5PhrJhHdh9FSFw

* fix(ui): address review of the stale-tab reload (#580)

Must-fix findings from the Full-tier review of #792:

- One reload per page. The driver re-issued location.replace every tick
  until the page went, each aborting the last. A reload now moves the page
  to Phase::Reloading, which never issues another; one that has not landed
  after 20 s falls back to the manual banner.
- Reload through the shell's same-contract `navigate`. A self-navigating
  iframe left its proxied WebSocket open in the shell, and the shim numbers
  sockets from 1 per document, so the new page's socket collided with it.
  The shell closes every proxied socket before replacing the frame. The
  synchronizer does not reconnect while a reload is under way.
- Rebuild the fragment from PROCESSED_CACHE. The iframe's own hash is stale
  after an invitation is processed; reloading with it replayed the invitation.
- Compare reload targets per artifact. Tries were keyed on the exact target
  string, so a page that had heard one pointer (r2d1) reset the count for
  r2d2 and only the hourly ceiling held. A target no newer in any artifact
  carries the tries; a newer one starts afresh with the max recorded.

Should-fix:

- Busy check covers any visible `fixed inset-0` overlay (every modal), a
  focused field with text, pending invitation accepts, the composer and an
  edit in progress, plus a 5 s settle; pending syncs and the delegate flush
  get up to 5 s before navigating.
- No reload before the page is 35 s old (the node's 30 s bundle-refresh
  window would serve this page's bundle again).
- "Not now" sticks across tab switches.
- A competing record at the build's own version is not "newer".
- The test-pointer guard moved from publish-time env to the built archive,
  checked in sign-webapp before the counter bump (scripts/
  check-webapp-pointer-author.sh, tested in publish-guards-test.sh, and
  verified on a real release build and on the E2E test-key archive).
- Banner explains a long retry wait; docs no longer say pointers serve only
  third parties.

Routing is now a pure function (on_pointer_state) so the response-to-plan
path is tested; wiring pins check each hook returns; selector pins cover the
busy detection. Mutations of the per-artifact compare, the call-site
re-plan, a hook's return and the reconnect guard each turn a test red.

Claude-Session: https://claude.ai/code/session_01SzES9BQh5PhrJhHdh9FSFw

* ci: install cargo-make --locked (every zip 2.x was yanked)

`cargo install cargo-make` now fails on every job: cargo-make 0.37.24
requires `zip = "^2"` and every zip 2.x release was yanked on 2026-10-16,
so the unlocked install cannot resolve. --locked builds from cargo-make's
own Cargo.lock (zip 2.2.2), which cargo still accepts. Verified locally.

main is affected the same way on its next run.

Claude-Session: https://claude.ai/code/session_01SzES9BQh5PhrJhHdh9FSFw

* Revert "ci: install cargo-make --locked (every zip 2.x was yanked)"

main fixed the same breakage in #794 (cargo-make as a prebuilt binary),
which conflicts with this. Drop ours and take main's.

Claude-Session: https://claude.ai/code/session_01SzES9BQh5PhrJhHdh9FSFw

* fix(ui): address re-review of the stale-tab reload (#580)

Should-fix findings from the second Full-tier review of #792:

- A Connect skipped during a reload now still clears the reconnect latch,
  and a reload that does not happen re-sends Connect. Before, a socket
  lost during a reload that then failed left the tab disconnected.
- The stuck clock starts when the reload is posted, not before the flush,
  and a flush that outlives the plan never posts a stray navigate.
- A posted reload that never lands ends in ReloadFailed: the banner asks
  the reader to refresh the tab instead of offering the same reload again.
- Reload/Not now cannot start a second reload or show a Reload button
  mid-reload.
- A user action that queues a sync (mark_needs_sync) starts the settle
  period at once, so a quick send between ticks is not reloaded over; no
  automatic reload while disconnected; only in-flight invitation accepts
  count as busy.

Tests: the driver's reload-due transition is the pure on_reload_due; the
deliver_* return contracts are pinned; wiring pins are scoped to each
hook's own block (the NotFound pin could be satisfied by the next
statement), including the latch clear; all 13 overlay sites and the
mark_needs_sync call are pinned; the archive guard test asserts exit 2 for
an unreadable archive and that the call keeps `|| exit 1`.

Docs: the remaining "pointers serve third parties only" lines; the history
entry each reload adds is freenet/freenet-core#5910.

Claude-Session: https://claude.ai/code/session_01SzES9BQh5PhrJhHdh9FSFw

* fix(ui): only hold a generation reload while actually disconnected (#580)

SYNC_STATUS is also set to Error(..) on a live socket (an unsupported
operation), which would have held every reload forever. Wait only on
Disconnected / Connecting.

Claude-Session: https://claude.ai/code/session_01SzES9BQh5PhrJhHdh9FSFw

* fix(ui): never reconnect a page the shell is replacing; hold reloads offline (#580)

Must-fix findings from the third Full-tier review of #792:

- "Offline" now means no live socket (WEB_API unset), not a status value.
  SYNC_STATUS reads Error through most of an outage, so a tab could reload
  into a dead node and land on the browser's error page. Nothing reloads
  while disconnected; the banner says it will reload once it reconnects, and
  the manual Reload is withheld.
- A posted reload never reconnects. The shell closes the old page's sockets
  when it accepts `navigate`, and the new frame can take a 30 s container
  GET to arrive; a page that gave up at 20 s and reconnected leaked a socket
  into the next one. RELOAD_STUCK_MS is now 60 s, and ReloadFailed carries
  `reconnect`: true only if the socket is still open (the shell ignored the
  request). Only an unposted reload, which times out on its own after 30 s,
  calls abandon_reload.

Should-fix: RELOADING is set synchronously with the phase change; busy and
connection are re-checked just before posting (the plan is put back if
not); abandon_reload retries reaching the synchronizer instead of dropping
the Connect; the driver ticks every 5 s unless a reload is due within a
minute; the hook is renamed note_sync_queued (network callers trigger it
too); the NEEDS_SYNC wait is documented as the cheap check it is.

Tests and pins: disconnected never reloads; a page being replaced never
reconnects; unposted vs posted timeouts; drive() may call abandon_reload
only for an unposted reload; the latch clear precedes the skip; the
subscribe hook returns false; the guard script's exit-2 branches. Each new
guard was mutated and a test went red.

Claude-Session: https://claude.ai/code/session_01SzES9BQh5PhrJhHdh9FSFw

* fix(ui): a posted reload never reconnects; let a Reload click through (#580)

Findings from the fourth Full-tier review of #792:

- A posted reload now never reconnects, failed or not. The "socket still
  open means the shell ignored it" escape only helped shells without
  `navigate`, which no compatible node serves, while a slow closing
  handshake can make a closed socket read open for up to a minute and
  reopen the socket leak. ReloadFailed loses its `reconnect` field; drive()
  never clears RELOADING.
- The pre-post check is the pure `ready_to_post`, tagged with the attempt's
  start so a stale flush task never posts, and it lets a manual Reload
  through a busy reader (it used to bounce the click back to a countdown).
  Offline still waits.
- One Offline banner (no buttons) for every state that would otherwise offer
  a Reload that cannot work while disconnected.
- ConnectionLost with a reconnect already pending now also drops WEB_API: a
  failed attempt left the dead socket there, so "WEB_API set" did not mean
  connected.
- The refresh banner warns that messages sent before refreshing may not be
  delivered (the page no longer reconnects).
- UNPOSTED_TIMEOUT_MS 3 min: a long-hidden tab's timers run about once a
  minute. The driver no longer ticks every second past a due time.

Tests: ready_to_post (stale tags, click through busy, offline waits);
offline banners carry no Reload; a posted reload fails without reconnecting
whether or not the socket looks open; drive() never clears RELOADING.
Mutations of the manual exemption and the attempt tag each go red.

Docs: "Reload now" -> "Reload"; river-publish no longer duplicates the
telemetry figures (they live with RETRY_DELAYS_MS). The shell's late-close
id race is freenet/freenet-core#5912.

Claude-Session: https://claude.ai/code/session_01SzES9BQh5PhrJhHdh9FSFw

* fix(ui): keep the update banner honest about the connection (#580)

Findings from the fifth Full-tier review of #792 (no Must Fix):

- The banner now follows the connection: the synchronizer calls
  generation_watch::on_connection_changed on ConnectionLost and on a failed
  Connect, and on_connected does too. A manual-only banner no longer keeps a
  Reload button after the node drops (the driver has stopped there), and an
  offline banner no longer outlives the outage. A click while offline shows
  the offline banner instead of doing nothing.
- The offline banner is now "Waiting to reconnect to your node.", true for
  every state that shows it (Not now and a refused cap do not reload on
  their own).
- A manual click that finds the node gone mid-flush goes back to the manual
  banner rather than becoming an automatic countdown.
- A manual click waits out the settle period after a send before flushing.
- A reload URL over the shell's 4096-byte limit counts as never posted
  (the shell would ignore it silently, and the page would never reconnect).
- connected() treats a contended WEB_API borrow as connected (a send holds
  the socket).
- Reverted the previous round's WEB_API take() in the ConnectionLost "reconnect
  pending" arm: it could close a healthy socket in a narrow sequence, a
  regression for every user, to avoid a rare reload into a dead node.

Pins: RELOADING is cleared in one place, reached only before a post; nothing
reconnects after a successful post; drive() passes manual=false with its
attempt tag and reload_now passes manual=true; the banner hooks are wired.

Claude-Session: https://claude.ai/code/session_01SzES9BQh5PhrJhHdh9FSFw

* fix(ui): reload only once a reconnect has caught up; keep a clicked plan (#580)

Findings from the sixth Full-tier review of #792 (no Must Fix):

- "Connected" now also requires READY, set by on_connected (called after the
  synchronizer re-registers the delegate and processes the rooms) and
  cleared by on_disconnected (ConnectionLost, failed Connect). Before, a
  due reload could fire in the up-to-15 s after a socket opened but before
  what the reader sent offline had gone out.
- A Reload click that finds the node gone mid-flush puts back exactly the
  phase it was clicked from: an automatic countdown stays automatic, the
  manual banner stays manual (reload_after_flush takes `clicked_from`).
- A countdown that runs out while offline keeps a full countdown in hand,
  so reconnecting shows 15 s rather than reloading at once.
- ReloadFailed carries `posted`: only a posted reload stops the page
  reconnecting, so only then does the banner warn that messages may not be
  delivered (a URL too long for the shell was never posted).
- The shell-href limit applies only to the framed path, in UTF-16 terms.
- A put-back refreshes the banner at once; comments no longer claim WEB_API
  is always cleared on loss.

Pins: Connect is sent from abandon_reload only; the failed-Connect arm and
ConnectionLost mark not-ready; READY is set only in on_connected, which runs
after process_rooms; call sites pass None / Some(before).

Claude-Session: https://claude.ai/code/session_01SzES9BQh5PhrJhHdh9FSFw

* fix(ui): READY follows every connect and teardown, not duplicate losses (#580)

Findings from the seventh Full-tier review of #792 (no Must Fix):

- on_disconnected moved from the top of ConnectionLost into the teardown
  (armed) arm: a coalesced duplicate loss tore nothing down, but cleared
  READY, and a following Connect skipped as "already connected" never set
  it again, leaving the Offline banner on a live socket.
- That skip path now calls mark_ready (the connect that made the live
  connection has finished; the synchronizer handles one message at a time).
- A Connect that proceeds marks the page not ready before it starts: with no
  loss before it (the status can read Error on a live socket) it replaces
  the socket and spends up to 15 s catching up, and READY stayed true.
- The unposted refresh banner says "Refresh this tab to get the update" (the
  page keeps working).

Pins: on_disconnected is called in the teardown arm, before a Connect
proceeds and on a failed Connect, never in the duplicate-loss arm; mark_ready
in the skip path; on_disconnected clears READY; on_connected sets it before
any early return; connected() requires READY && a socket; reload_now
captures the pre-click phase before replacing it. The offline-hold test
again pins non-countdown phases as unchanged.

Claude-Session: https://claude.ai/code/session_01SzES9BQh5PhrJhHdh9FSFw

* test(ui): scope the duplicate-loss pin to the ConnectionLost arm (#580)

Round-8 review of #792: hook_block(&sync, "None =>") matched the first
`None =>` in freenet_synchronizer.rs (an unrelated function), so the
"a coalesced duplicate loss must not clear READY" assertion could never
fail. Scoped to the ConnectionLost arm and checked it is the right arm;
moving on_disconnected into that arm now turns the test red. The
Connect-proceeds pin is now whitespace-insensitive.

Claude-Session: https://claude.ai/code/session_01SzES9BQh5PhrJhHdh9FSFw
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.

1 participant