Skip to content

fix(stream): retire the frozen-channel ledger on the union path too - #1308

Merged
teetangh merged 1 commit into
devfrom
fix/drain-ledger-retire-gate
Sep 1, 2026
Merged

fix(stream): retire the frozen-channel ledger on the union path too#1308
teetangh merged 1 commit into
devfrom
fix/drain-ledger-retire-gate

Conversation

@teetangh

@teetangh teetangh commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Follow-up to #1302, from its own unresolved review threads. Two findings, one of them a real cross-subsystem bug.

1 · The frozen-channel ledger never shrank on the union path (🟠 Major)

unfreezeChannelsAfterMaintenance used result.source for two different jobs — reporting how the unfreeze set was determined, and gating ledger retirement:

if (result.source === "ledger") {
  await retireFrozenChannels(unfrozenInBatch, result);
}

On the union path (ledger.length > 0 && incomplete) source is set to "derived", because the set is only best-effort complete. But the returned set still contains every ledger entry. So retirement was skipped and FROZEN_CHANNELS never shrank.

Why this is more than untidy. The stale set is re-unfrozen on every later OFF transition, which:

Fix. Split the two concerns: usedLedger tracks participation, source keeps reporting provenance. Both ledger-reading paths set it. srem on an id the set never held is a no-op, so passing the derived ids through alongside the ledger ids costs nothing.

2 · Two breaker fixtures asserted a message their path cannot emit (🔵 Trivial)

Since the breaker split, createCircuitBreaker("stream") throws "stream circuit breaker is OPEN - service unavailable". Both withStreamCircuitBreaker fixtures still simulated "Redis ...". They passed only because the guard matches the substring "circuit breaker is OPEN" — so they were green against a string this code path can never produce.

Verification

Proof-by-revert on the new assertion — restore the result.source === "ledger" gate and it goes red on exactly the right thing:

● UNIONS ledger and derived when a ledger write failed mid-drain
    Expected value: "webinar-recorded"
    Received array: []
Tests: 1 failed, 12 passed, 13 total

Restored: 13 passed.

  • npx tsc --noEmit → exit 0
  • npx eslint on the three changed files → exit 0
  • npx jest __tests__/stream41 suites, 514 tests, all pass

Threads deliberately not actioned

PR Thread Why
#1302 narrow the marker-clearing condition to unfreeze failures Every error that reaches result.errors — ledger read, unfreeze, retire — genuinely does make the ledger suspect, which is what the marker means. The condition is already the intended one.
#1303 loadOrgChatRetention should filter to seen orgs Deliberate, and documented in its docblock: the scan window derives from the largest configured chatRetentionDays, so narrowing the query would make the window depend on which page loaded — the same incomplete-input-drives-a-destructive-decision shape the truncation guard exists to prevent.
#1303 deleted DMs re-deleted, deletedDms inflated Already addressed in the merged code. The metric is dmDeleteRequests and its docblock says it counts requests, not deletions; the re-send is bounded by the scan window (largest retention + 60 days).
#1304 doc correction is future-dated 2026-09-01 It is 2026-09-01. The Last Updated: 2025-01-22 the comment cites is not in the file.

🤖 Generated with Claude Code

@netlify

netlify Bot commented Sep 1, 2026

Copy link
Copy Markdown

Deploy Preview for familiarise ready!

Name Link
🔨 Latest commit 7bc6c3e
🔍 Latest deploy log https://app.netlify.com/projects/familiarise/deploys/6a96874195c61f000866c3fc
😎 Deploy Preview https://deploy-preview-1308--familiarise.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
Lighthouse
Lighthouse
1 paths audited
Performance: 29 (🔴 down 9 from production)
Accessibility: 90 (no change from production)
Best Practices: 83 (no change from production)
SEO: 90 (no change from production)
PWA: -
View the detailed breakdown and full score reports

To edit notification comments on pull requests, go to your Netlify project configuration.

@coderabbitai

coderabbitai Bot commented Sep 1, 2026

Copy link
Copy Markdown

Warning

Review limit reached

Next included review available in 19 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used all 2 included reviews currently available. Your 53 included PR review attempts over the past 7 days set your current allowance at 2 reviews per hour.

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Essentials

Run ID: 421b42d4-2990-4341-b38e-c22f34964568

📥 Commits

Reviewing files that changed from the base of the PR and between 9942b71 and 7bc6c3e.

📒 Files selected for processing (3)
  • __tests__/stream/maintenance-drain.test.ts
  • __tests__/stream/stream-client.test.ts
  • actions/maintenance/drain-sessions.ts

Comment @coderabbitai help to get the list of available commands.

@teetangh teetangh assigned teetangh and unassigned teetangh Sep 1, 2026
#1302 review. `source` was doing two jobs: reporting how the unfreeze set was
determined, and gating ledger retirement. On the union path it reads "derived"
because the set is only best-effort complete — but the set still CONTAINS the
ledger entries, so `result.source === "ledger"` skipped `retireFrozenChannels`
and `FROZEN_CHANNELS` never shrank.

Left alone that is not just untidy. The stale set is re-unfrozen on every later
OFF transition, spending the 300/min UpdateChannelPartial budget on it, and —
since #1303 landed — reopening DM channels the dormancy sweep froze on purpose.
Maintenance would silently undo another subsystem's decision.

Split the two: `usedLedger` tracks participation, `source` keeps reporting
provenance. `srem` on an id the set never held is a no-op, so passing the
derived ids through with the ledger ids costs nothing.

Also corrects two `withStreamCircuitBreaker` fixtures that simulated Redis's
breaker message. They passed only because the guard matches the substring
"circuit breaker is OPEN", so they were asserting against a string this path
cannot emit.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@teetangh
teetangh force-pushed the fix/drain-ledger-retire-gate branch from 7bd595e to 7bc6c3e Compare September 1, 2026 08:05
@sonarqubecloud

sonarqubecloud Bot commented Sep 1, 2026

Copy link
Copy Markdown

@teetangh
teetangh merged commit 5d7813e into dev Sep 1, 2026
8 checks passed
@teetangh
teetangh deleted the fix/drain-ledger-retire-gate branch September 1, 2026 08:21
@teetangh teetangh mentioned this pull request Sep 1, 2026
6 tasks
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