Repository navigation
fix(app): let an explicit refresh bypass an in-flight api() request (#243) - #263
Conversation
…pi() request (#243) - test-app-api-bust-inflight-243.js: api(path, { bust: true }) during an in-flight request must fetch again, take the in-flight slot, and keep its newer data in the TTL cache when the older answer lands last. - test-channels-client-state-152.js: the real channels.js with the real app.js api(): an approval while a region load is in flight must list the approved channel, in both answer orders; nothing in flight makes exactly one request. - test-channel-proposals.js: ChannelProposals.unmount() cancels the suggest poller (no scheduled poll, no status request afterwards). Red on master: 4 api() tests and the 2 in-flight channel tests. The unmount test is green on master and red without suggestPoller.cancel(). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…243) api(path, { bust: true }) skipped only the TTL cache. A refresh asked for while the same request was in flight joined that older request, whose answer may predate the change (an approval), so the approved channel was missing until the next refresh. - api(): a bust never joins _inflight; it fetches and takes the in-flight slot, so later plain callers join the newer request. A superseded request neither writes the TTL cache nor removes the newer slot entry. Calls without bust are unchanged. - channels.js: onApproved refreshes with loadChannels(true, { bust: true }). The #225 request token still drops the older response. The only other bust caller, the observers manual refresh, now gets fresh data instead of joining an in-flight auto-refresh, as its Kpa-clawbot#1563 request guard already expects. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Rapport — CS-pve-agent2 PR#263 #243 — head 601bd00Status: fix and tests done; local suites and CI green (no re-runs). Evidence tags: [T] = test or command run in this session, [A] = analysis/reading of code, [K] = existing knowledge, not re-checked here. Requirements
Local runs [T]
CI (run 37325629702)
Remaining
|
CI update — CS-pve-agent2 PR#263 #243 — head 601bd00Status: CI green, no re-runs needed. Run 37325629702 [T]:
What remains is the one-line overlap with #257 (see the report above). |
Review — CS-MacBook PR#263 — head 601bd00Dom: REQUEST CHANGES Evidence tags: [T] = test/command run in this session, [A] = analysis/reading of code, [K] = existing knowledge, not re-checked here. Finding table
No correctness defects found in the Point-by-point1. 2. #225 token ( 3. Tests.
4. Conflicts (#257). #257 ("hide revoked shared channels," 5. Channel E2Es +
Tests and mutants run this session
Not verified
|
…-inflight-refresh # Conflicts: # public/channels.js
Rapport — CS-MacBook PR#263 runde 2 — head 59e398cEvidence tags: [T] = test/command run in this session, [A] = analysis/reading of code, [K] = existing knowledge, not re-checked here. Review feedback addressed (commit
|
| Test | Result |
|---|---|
node test-app-api-bust-inflight-243.js |
7/0 |
node test-channels-client-state-152.js |
70/0 (3 more than the PR's original 67 — extra assertions from master's #257/other merges) |
node test-channel-proposals.js |
40/0 |
sh test-all.sh |
220/220 files |
node test-frontend-helpers.js |
707/0 |
cd cmd/server && go test ./... |
pass (40.5s) |
test-channel-proposals-e2e.js (self-starting server+ingestor, auto-assigned port) |
26/0 |
test-channels-client-state-152-decrypt-e2e.js (self-starting) |
18/0 |
Channel E2Es against a shared local Go server (built corescope-server/corescope-migrate/corescope-ingestor from the merged tree, port 13581, fixture freshened + Kpa-clawbot#1486/Kpa-clawbot#1791 seeded + migrated + Kpa-clawbot#2073/#199/#245 seeded, exactly as deploy.yml's Playwright job does):
test-channel-issue-1087-e2e.js 3/0, test-channel-issue-1111-e2e.js 2/0, test-channels-client-state-152-e2e.js 6/0, test-channel-fluid-e2e.js 15/0, test-channel-decrypt-e2e.js 15/0, test-channel-qr-e2e.js 11/0, test-channel-color-picker-e2e.js 10/0, test-channel-modal-e2e.js 15/0, test-channels-154-155-e2e.js 8/0, test-issue-1657-analytics-channels-group-sprites-e2e.js 5/0, test-issue-1224-channels-mobile-ux-e2e.js 16/0, test-issue-1367-channels-chat-app-e2e.js 9/0, test-channels-list-render-e2e.js 8/0, test-channels-selection-flow-e2e.js 8/0, test-channels-add-modal-e2e.js 8/0, test-channels-share-color-e2e.js 6/0, test-channels-ws-batch-e2e.js 6/0, test-channels-ws-race-1498-e2e.js 5/0, test-channels-observed-path-hash-size-e2e.js 7/0. All pass, nothing skipped.
Fixture DB and build artifacts were reverted after the run (e2e-fixture.db.orig restored, binaries and the generated mbcap-snapshot snapshot removed) — git status on the merged tree is clean before the push.
Mutants [T]
Both applied to the resolved public/channels.js, run, then reverted (confirmed clean via git diff/git status afterward):
| Mutant | Killed by |
|---|---|
bust ignored: api('/channels' + qs, { ttl: CLIENT_TTL.channels, bust: false }) in loadChannelsFor |
test-channels-client-state-152.js: both #243 in-flight-approval tests fail (68/2) |
hiddenChannels guard removed: } else if (isFirstObservation) { (dropped && !hiddenChannelNames.has(channelKey)) in processWSBatch |
test-channels-client-state-152.js: "a live CHAN packet does not re-create the row of a hidden channel (#251)" fails (69/1) |
Both mutants died as required.
CI (run 37360022795, head 59e398cd)
| Job | Result |
|---|---|
| Go Build & Test | pass (22m27s) |
| Playwright E2E Tests | pass (24m33s) — first attempt, #267/#271 did not flake |
| Build & Publish Docker Image | pass (45s) |
| Release Artifacts | skipping (PR event) |
| Deploy Staging | skipping (PR event) |
| Publish Badges & Summary | skipping (PR event) |
Not done / out of scope
- No merge, ready-for-review flip, approval, or revert — the PR remains draft, per the task's constraints.
- No stg/prod access, no upstream writes, no other branches touched. All work happened in a scratch worktree (
/Users/dborup/CoreScope-worktree-pr263), now safe to remove. - Did not re-run the broader
test-e2e-playwright.jsbattery — out of scope for this round (only the channel E2Es and the specific test files listed in the task were requested).
Review — CS-Macmini PR#263 — head 59e398cDom: APPROVE med nits Independent read-only re-review. Evidence tags: [T] = run in this session, [A] = code reading/analysis, [K] = taken from existing context, not re-checked. Scope per the round-1 outcome: the round-1 fix was already found correct, and REQUEST CHANGES was raised only for the #257 merge conflict. This round therefore concentrates on the conflict resolution and on both PRs' behaviour surviving it. Findings
No blocking findings. The conflict resolution matches what the PR description predicted and what the round-1 review asked for. Answers to the review points1. Conflict resolution in
Both hooks now point at one function, so approval and revocation bust — strictly a superset of each PR's own behaviour, and the resolution the PR description predicted. 2. Mutants — all three required ones die [T]. See the mutant table below (M1, M2, M3). 3. No changes beyond the conflict resolution [T]. 4. Tests green [T]. Listed below; Red before / green after [T]. On a tree of No behaviour change beyond the purpose [A]. The one deliberate ripple is disclosed in the PR body and holds up: I also checked the two ownership checks for leaks and ordering [A]: whichever order the superseded and superseding requests settle in, exactly one Tests I ran [T]All against the resolved merge tree (
E2Es against a local Go server built from the merged tree, port 13800+,
Self-starting (own server + ingestor, OS-assigned ports): Mutants I ran [T]Ten, each applied to the resolved merge tree and reverted afterwards. Eight die.
Guardrails [T]
CI, checked per job [T]Run Not verified
|
Relates to #243
Problem
api(path, { bust: true })skipped only the TTL cache, not the_inflightmap.invalidateApiCache('/channels')does not clear_inflighteither.When a channel is approved (admin decision, or auto-approve since #238),
onApprovedrefreshes the list. If a/channelsrequest is already in flight at that moment, the refresh joins it. That can happen after a region change, the encrypted toggle, or a second approval. The older request's answer can predate the approval, so the approved channel stays missing until the next refresh.Fix
public/app.jsapi()bust: truenever joins an in-flight request. It always fetches, and it takes the in-flight slot for its key, so later callers withoutbustjoin the newer request..finally()removes the_inflightentry only if that entry is still its own.bustare unchanged. For them the slot is always their own promise, so both checks are always true.public/channels.js(kept minimal; #257 also edits this file)loadChannels(silent, opts)passesopts.buston toapi().onApprovedcallsloadChannels(true, { bust: true }).invalidateApiCache('/channels')stays, so the cached lists for other region andincludeEncryptedvariants are still cleared.channelsRequestIdtoken from fix(channels): drop stale channel-list responses and show the unread badge on mobile (#154, #155) #225 is unchanged, so an older response is still dropped without a render or a reconcile.Other
api()callers/channelslist endpoint inchannels.jspassesbust: region change, the encrypted toggle andinit()are unchanged.channels.jsalready has abust: !!opts.forceNoCachepassthrough, but it is on a different endpoint and pre-dates this PR:refreshMessages()'s fetch of/channels/{hash}/messages(region-switch handler, from test(server): data race between background index builds and the hash-migrate log capture (#301) #307). That is the messages endpoint for the open conversation, not the/channelslist this PR's race is about, and it is unaffected by this change.bustcaller of a list-shaped endpoint is the manual refresh inobservers.js. It now gets fresh data instead of joining an in-flight auto-refresh. This only happens when the two overlap, and it is what its fix(observers): show "Last updated" timestamp on aggregate header (closes #1562) Kpa-clawbot/CoreScope#1563 request-id guard already expects. The observers unit tests and E2Es are green.Normal case
With nothing in flight, an approval still makes exactly one
/channelsrequest. A test pins this.Overlap with #257
#257 moves the
onApprovedbody intorefreshChannelList(). Whichever PR merges second needs a one-line resolution:loadChannels(true, { bust: true })goes intorefreshChannelList(). The new tests sit away from #257's test hunks.Update (round 2): #257 merged first.
masterwas merged into this branch and the conflict was resolved exactly as predicted above —refreshChannelList()now doesinvalidateApiCache('/channels'); loadChannels(true, { bust: true });, and bothonApprovedandonRevokedwire to it. See the PR comment for the merge-resolution writeup.Tests
The tests were committed first (
899e9232) and are red on master. The fix is in601bd000.test-app-api-bust-inflight-243.js(new, 7)app.jswith parked fetches. A bust during an in-flight request fetches again and gets the newer data. A later plain call joins the newer request. The older request settling first does not remove the newer entry. An older answer landing last does not overwrite the cache. A bust with nothing in flight makes one fetch. Guards: plain dedup and TTL, a failed bust leaves no entrytest-channels-client-state-152.js(+3)channels.jswith the realapp.jsapi(). An approval while a region load is in flight lists and renders the approved channel, in both answer orders, with one request for each load. The next refresh within the TTL still has the channel, with no request. Nothing in flight → exactly one requesttest-channel-proposals.js(+1)ChannelProposals.unmount()cancels the suggest poller: no poll stays scheduled, and there is no status request afterwardsMutants
Each mutant was applied to the fixed code and run against the three files.
api()ignoresbustfor_inflightonApprovedwithoutbustloadChannelsFordoes not passbuston.finally()ownership check removedstate.suggestPoller.cancel()removedLocal runs (head
601bd000)sh test-all.sh: 220 / 220 files.node test-frontend-helpers.js: 707 / 0.node test-channel-proposals.js: 37 / 0.node test-channels-client-state-152.js: 67 / 0.e2e-fixture.db, prepared as in CI (freshen, the Packets page collapse button in the left column of the table opens the dialog. Kpa-clawbot/CoreScope#1486 seed, migrate, the [feat] node detail, separate flood-adv from zero-hop adv Kpa-clawbot/CoreScope#2073 and Active observers vanish as nodes after retention.nodeDays without an advert; observer link dead-ends on 'Node not found' #199 seeds):test-e2e-playwright.js: 132 / 135, 3 skipped (built-in skips).test-channel-proposals-e2e.js(24 / 0) andtest-channels-client-state-152-decrypt-e2e.js(18 / 0).scripts/check-xss-sinks.sh --diff origin/master: clean.deploy.ymland 1 inrelease-fast-path.yml.Perf
Not a hot path.
api()adds one boolean test and, per response, oneMap.get. An extra request only happens when an explicit refresh overlaps an in-flight request for the same path. That is the request the refresh asked for.Not verified
🤖 Generated with Claude Code