mirror of
https://github.com/J3vb/OwnCord.git
synced 2026-09-02 19:43:10 +03:00
main
859
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
3d3875f902 |
ci: carry nightly-docker-smoke.yml to main so its schedule can fire (#1468)
B3-6 item 8 (roadmap workstream 16) merged the workflow to dev in #1452, but a schedule only ever runs from the default branch, so it has been registered and dormant: zero scheduled runs, and no dispatch button. The file itself already says "this file does nothing until it reaches main" and checks out ref: dev — dev is the branch the nightly exists to smoke. Its job name matches no required context, so release gating on main is unaffected. Verbatim copy of dev's file; owner decision 2026-08-31 to activate now rather than wait for the next release merge. The item is operationally closed by the first observed green scheduled run, recorded in the B3 plan's evidence block. Claude-Session: https://claude.ai/code/session_01B8dwVLEihnGZYtH9X631F4 Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
874fc969a9 |
chore(deps): bump golang from 1.26-bookworm to 1.27-bookworm in /Server in the docker-dependencies group across 1 directory (#1427)
* ci(deps): bump anthropics/claude-code-action (#1404) Bumps the actions-dependencies group with 1 update: [anthropics/claude-code-action](https://github.com/anthropics/claude-code-action). Updates `anthropics/claude-code-action` from 1.0.193 to 1.0.199 - [Release notes](https://github.com/anthropics/claude-code-action/releases) - [Commits](https://github.com/anthropics/claude-code-action/compare/9d7150bc8a3dae8149739a88019d192b579ad90c...dcb57747bfceeaa1fa72638cae52295d1d853d4a) --- updated-dependencies: - dependency-name: anthropics/claude-code-action dependency-version: 1.0.199 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: actions-dependencies ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps): pin rfd to tauri-plugin-dialog's major to unblock the cargo group (#1406) The cargo-dependencies group PR (#1405) fails Rust Unit Tests on Linux: error: failed to run custom build command for `rfd v0.17.2` You need to choose at least one backend: `gtk3` or `xdg-portal` features for x86_64-linux rfd is not really ours. It arrives in the tree via tauri-plugin-dialog, which pins ^0.16; we declare it directly only for the fatal-startup message box in lib.rs, where the Tauri app never finished building and the plugin has no AppHandle to run a dialog through. Cargo unifies features only within a semver-compatible version group, so while both wanted ^0.16 there was a single rfd in the graph and the plugin's backend features covered our `default-features = false` declaration too. Bumping our direct dep to 0.17 forks rfd into two crates: the plugin keeps 0.16.0 with its features, ours resolves to 0.17.2 with none, and rfd 0.17 added a build.rs assertion that aborts the Linux build when no backend feature is set. Confirmed in the PR's lockfile, which carries both 0.16.0 and 0.17.2. Adding a Linux backend feature would be the wrong fix: it would paper over the fork and still build rfd twice on every platform for one error dialog. Our version has to track the plugin's instead, so ignore semver-minor rfd updates (0.16 -> 0.17 for a 0.x crate) until tauri-plugin-dialog moves. Patch updates inside 0.16.x still flow. The remaining five crates in the group are unaffected; `windows` in fact consolidates 3 versions down to 2. Cargo.toml is comment-only here - no dependency, feature, or lockfile change - so the build is untouched. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> * chore(deps): bump log (#1407) Bumps the cargo-dependencies group with 1 update in the /Client/tauri-client/src-tauri directory: [log](https://github.com/rust-lang/log). Updates `log` from 0.4.33 to 0.4.34 - [Release notes](https://github.com/rust-lang/log/releases) - [Changelog](https://github.com/rust-lang/log/blob/master/CHANGELOG.md) - [Commits](https://github.com/rust-lang/log/compare/0.4.33...0.4.34) --- updated-dependencies: - dependency-name: log dependency-version: 0.4.34 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: cargo-dependencies ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * ci(deps): bump anthropics/claude-code-action (#1408) Bumps the actions-dependencies group with 1 update: [anthropics/claude-code-action](https://github.com/anthropics/claude-code-action). Updates `anthropics/claude-code-action` from 1.0.199 to 1.0.200 - [Release notes](https://github.com/anthropics/claude-code-action/releases) - [Commits](https://github.com/anthropics/claude-code-action/compare/dcb57747bfceeaa1fa72638cae52295d1d853d4a...24dcd50c0568f0fc9e9211213a4fd2d9eb15c4e0) --- updated-dependencies: - dependency-name: anthropics/claude-code-action dependency-version: 1.0.200 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: actions-dependencies ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * release: v1.2.0-alpha.4 — 62 fixes plus the B0/B1 repository foundation (#1426) * Fix 27 findings from 2026-08-21 bug hunt (#1400) * chore(findings): record 2026-08-21 bug hunt (38 findings) * fix(api): 1 defect(s) (OC-0240) * fix(client): 1 defect(s) (OC-0241) * fix(plugin): 2 defect(s) (OC-0243, OC-0265) * fix(client): 3 defect(s) (OC-0244, OC-0256, OC-0259) * fix(client): 1 defect(s) (OC-0247) * fix(client): 2 defect(s) (OC-0248, OC-0258) * fix(identity): 1 defect(s) (OC-0250) * fix(ws): 3 defect(s) (OC-0252, OC-0269, OC-0272) * fix(admin): 1 defect(s) (OC-0253) * fix(client): 1 defect(s) (OC-0254) * fix(voice): 1 defect(s) (OC-0255) * fix(ws): 1 defect(s) (OC-0260) * fix(client): 1 defect(s) (OC-0261) * fix(client): 1 defect(s) (OC-0262) * fix(client): 1 defect(s) (OC-0263) * fix(client): 1 defect(s) (OC-0264) * fix(client): 1 defect(s) (OC-0268) * fix(ws): 1 defect(s) (OC-0273) * fix(service): 1 defect(s) (OC-0275) * style: satisfy golangci-lint and prettier on 2026-08-21 fix commits - drop ineffectual backupDir reset before return (registry.go, OC-0265) - reflow long boolean expression (attachments.ts, OC-0241) * fix(client): 4 defect(s) (OC-0242, OC-0246, OC-0249, OC-0251) * fix(voice): 1 defect(s) (OC-0267) * fix(admin): 1 defect(s) (OC-0274) * fix(voice): 1 defect(s) (OC-0245) * fix(ws): 1 defect(s) (OC-0271) * fix(voice): 2 defect(s) (OC-0239, OC-0257) * fix(ws): 1 defect(s) (OC-0266) * fix(voice): 1 defect(s) (OC-0270) * style: clear golangci-lint modernize and prettier nits from 2026-08-21 fixes - range-over-int and slices.Contains modernizations in new Go test files - prettier reflow in dispatcher.ts * chore(findings): mark 2026-08-21 hunt findings fixed/declined 37 fixed across the fix waves, OC-0238 declined (LiveKit webhook TLS requires a product decision, not a mechanical patch). --------- Co-authored-by: Claude <noreply@anthropic.com> * fix: 35 findings from the 2026-08-22 bug hunt (#1402) * fix(voice): 1 defect(s) (OC-0277) * fix(voice): 1 defect(s) (OC-0278) * fix(client): 1 defect(s) (OC-0280) refreshDmSidebar() rebuilds the entire DM sidebar subtree on every dmStore.channels change - which includes presence flips and new messages, not just DM list changes. The "Find a conversation" filter text and input focus live only in that destroyed subtree, so they were silently wiped mid-typing. Capture and restore both across the destroy+recreate cycle. * fix(ws): 1 defect(s) (OC-0285) * fix(client): 1 defect(s) (OC-0286) * fix(client): 1 defect(s) (OC-0288) Consume the legacy unscoped mute key after migrating it onto the first host, so a brand-new host with no scoped key of its own no longer reads through to the same legacy list and inherits another server's mutes. * fix(voice): 1 defect(s) (OC-0290) * fix(db): 1 defect(s) (OC-0293) DecrementMentionCounts reversed mention_count bumps that were never applied: message_mentions stores every resolved mention id including the author's blockers, while applyMentionCounts excludes blockers before incrementing. Deleting a blocked author's message therefore wiped an unrelated, genuine mention badge on the same read_states row. Mirror the block exclusion in the decrement UPDATE. * fix(db): 1 defect(s) (OC-0294) DeleteAccount soft-deletes the departing user's messages but never reversed the read_states.mention_count bumps those messages made, leaving phantom mention badges. Reverse them inline in the existing transaction, mirroring DecrementMentionCounts' guards. * fix(client): 1 defect(s) (OC-0295) MemberList rebuilt every row on any non-presence-only membersStore change and on every roles_update, but registered each row's click/contextmenu listeners on the component-lifetime disposable.signal, which only aborts at destroy(). Discarded rows therefore stayed reachable (and their listeners live) for the component's whole lifetime. Route per-row listeners through a per-render AbortController that is aborted and replaced at the top of every render, and aborted again in destroy(). * fix(identity): 1 defect(s) (OC-0297) UpdateProfile's post-commit re-read of the user row could fail for reasons unrelated to context cancellation (SQLITE_BUSY, I/O error, pool exhaustion) and was reported as ErrInternal even though UpdateUserProfile had already committed. Callers that treat any UpdateProfile error as proof the write never landed — handleUploadAvatar deletes the file it just stored — would delete a file the committed avatar column now points at, permanently breaking the avatar with no user_update broadcast. Since UpdateUserProfile only writes username/avatar/display_name/about, merge those four onto the pre-write snapshot to reconstruct the committed row without needing the re-read to succeed, and log the read failure. * fix(ws): 2 defect(s) (OC-0298, OC-0299) - OC-0298: applyConnectStatus stamped c.user.Status even when the UpdateUserStatus write failed, so auth_ok and the presence broadcast claimed a status users.status disagreed with, and buildReady's ListMembers read never self-corrected for the session. - OC-0299: refreshUserSnapshot silently fell back to roleName "member" when the new role lookup failed, pinning the session to a fabricated role on the wire. It now fails closed like the sibling lookups in upgradeAndAuth and handleFreshConnect. * fix(client): 1 defect(s) (OC-0300) * fix(client): 1 defect(s) (OC-0301) * fix(ws): 1 defect(s) (OC-0302) * fix(api): 1 defect(s) (OC-0305) handleDiagnosticsConnectivity used clientIP(r), ignoring cfg.Server.TrustedProxies, so behind a configured trusted reverse proxy the endpoint reported the proxy hop instead of the real client address. Use clientIPWithProxies with the parsed trusted-proxy nets, matching RateLimitMiddleware on the same route. * fix(client): 2 defect(s) (OC-0306, OC-0308) * fix(client): 1 defect(s) (OC-0307) QuickSwitcher registered a per-row click listener against the overlay-lifetime AbortSignal, but renderResults() rebuilds every row on each keystroke, arrow key, and store refresh. Discarded rows kept their listeners alive until the overlay closed. Replaced with one delegated click listener on the stable results container, keyed off the data-channelid each row already carries. * fix(client): 1 defect(s) (OC-0310) * fix(server): 3 defect(s) (OC-0279, OC-0291, OC-0292) Reap a soft-deleted message's attachment files, count lapsed temporary bans as active users in the require_2fa enrollment gate, and only apply the 2FA-enrollment precondition when require_2fa itself is being enabled. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SdkJRbjCtrG76jEnrhKbYo * test(api): sync apiTestSchema with the user_blocks migration DeleteAccount's mention-count reversal joins user_blocks; the api package's hand-rolled schema fixture predates migration 012. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SdkJRbjCtrG76jEnrhKbYo * fix(client): 3 defect(s) (OC-0281, OC-0282, OC-0296) Decouple the E2EE identity-mismatch modal and right-click popovers from the sidebar's per-render abort signal, and let global drag listeners survive a mid-drag re-render. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SdkJRbjCtrG76jEnrhKbYo * fix(voice): 2 defect(s) (OC-0283, OC-0287) Retire a departed peer's E2EE key unconditionally on leave, and surface a failed microphone unmute instead of reporting an unmuted state the room never saw. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SdkJRbjCtrG76jEnrhKbYo * fix(client): 3 defect(s) (OC-0289, OC-0303, OC-0309) Guard the DM call button against redialing the channel already joined, resolve the incoming-call banner's caller through the nickname-aware display name, and keep the DM profile sidebar subscribed to live member/status updates. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SdkJRbjCtrG76jEnrhKbYo * style(client): prettier-format the dm-store test Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SdkJRbjCtrG76jEnrhKbYo * fix(server): 1 defect(s) (OC-0284) Make message soft-delete a compare-and-set so a repeated chat_delete cannot reverse mention counts twice. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SdkJRbjCtrG76jEnrhKbYo * fix(server): 2 defect(s) (OC-0276, OC-0304) Re-sync a resumed connection's voice E2EE peer keys in registerNow (announce frames are unsequenced and cannot be replayed), and apply the live-connection presence rule to every DM payload DMService builds. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SdkJRbjCtrG76jEnrhKbYo * chore(ledger): record the 2026-08-21 hunt findings as fixed Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SdkJRbjCtrG76jEnrhKbYo * chore(ledger): independent revert-proof pass for OC-0276..OC-0310 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SdkJRbjCtrG76jEnrhKbYo * refactor(service): extract DeleteMessage authorization into a helper Keeps DeleteMessage under the cyclop complexity ceiling after the OC-0284 guard. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SdkJRbjCtrG76jEnrhKbYo --------- Co-authored-by: Claude <noreply@anthropic.com> * chore(ledger): record the 38 open findings from the 2026-08-22 hunt (#1403) Claude-Session: https://claude.ai/code/session_01SdkJRbjCtrG76jEnrhKbYo Co-authored-by: Claude <noreply@anthropic.com> * chore(graphify): refresh knowledge graph * fix: close the three B0 P0 gates and record a measured baseline (#1409) * chore(security): stop tracking the private security-finding reports docs/security-findings/ holds detailed reports for defects that are not yet fixed. The directory was untracked but not ignored, so any 'git add .' would have published seven unfixed vulnerability traces to a public repository. Findings are coordinated through private GitHub Security Advisories (docs/security.md); only opaque identifiers and safe status belong in tracked plans. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(client): repair the two red P0 unit contracts (G-01, G-02) G-02: noise-suppression-restart stubbed MediaStream with vi.fn().mockImplementation(arrow), which is not constructible. Vitest 4 threw 'is not a constructor' at the new MediaStream([inputTrack]) call in noise-suppression.ts before reaching any assertion. Replaced with a real class; the OC-0277 assertions are unchanged. G-01: message-list's OC-0217 guard was inverted, not merely stale. It spied on AbortSignal.prototype.addEventListener and asserted zero abort registrations, but the leak it names registered row listeners via element.addEventListener(..., { signal }) — a path that never calls that prototype method. Measured: the leak produces 0 registrations (test passes), while the OC-0286 fix rotates a per-window AbortSignal.any and produces 5 across 5 distinct signals (test fails). The guard passed on the bug and failed on the fix. It now captures the signal each window's row listeners register against and asserts the invariant its name always claimed: one signal per rendered window, a fresh signal per jump, and every superseded window already aborted with exactly one live. Verified both directions — green on the fix, and 'expected 1 to be 5' with beginRowRender() reverted to rowSignal = ac.signal. Client suite: 5257 passed, 0 failed (was 5255 passed, 2 failed). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(client): make the Playwright suite terminate The runner finished every test and then never exited, printing no summary — so the failure read as 'tests never finish' when it was 'process never exits'. getActiveResourcesInfo() at hang time showed a live ProcessWrap plus several PipeWrap: the Vite dev server was still running. Playwright's webServer teardown does not kill it here. Measured, full suite each time: npm run dev hangs, tests pass node node_modules/vite/bin/vite.js hangs, tests pass reuseExistingServer: false hangs, tests pass gracefulShutdown SIGTERM/3s hangs, tests pass npx vite exits, 290 of 293 FAIL no webServer (pre-started) exits, 293 pass in 33s npx only appears to fix it: npx exits once Vite is up, Playwright reads that as the server dying and tears the group down mid-run, so later tests get ERR_CONNECTION_REFUSED. globalTeardown now kills the process listening on the dev port, releasing the runner's handle. The webServer command spawns Vite's entry point directly so the listening process is Playwright's own child — via 'npm run dev' the npm process would still hold the handle open. It also reaps servers orphaned by an interrupted run, which reuseExistingServer would otherwise silently adopt. An earlier revision used netstat, which is not on PATH in every shell here; the swallowed ENOENT made the fix look applied while the hang persisted. It now uses PowerShell on Windows and lsof elsewhere, and warns on failure rather than failing silently. npm run test:e2e: exit 0, 293 passed, 37s, reproducible, no orphan listener. playwright.config.prod.ts carried the same npm-wrapper shape. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * chore(client): align .nvmrc with the Node version CI uses Three versions were in play, not two: .nvmrc said 20, CI pins 24, and the machine the audit was measured on runs 26. A baseline measured against .nvmrc is not the baseline CI produces, which defeats the point of B0. Scoped to .nvmrc only. The full single-source-of-truth work — package engines, contributor docs, release — stays in B1 (RL-17 / C-01). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs(plans): add the beta audit set and the B0 baseline The 2026-08-23 audit set has been sitting untracked: repository-health and repository-layout audits, beta product requirements, requirement traceability, the issue register, and the B0-B10 roadmap. They are the plan of record for beta and belong in the repository. Adds b0-baseline-2026-08-25.md, which supersedes the roadmap's 'current evidence snapshot'. Every row is marked measured or carried, so nothing is inherited silently. It also records three audit claims that did not survive verification: - G-01 was an inverted guard, not a stale assertion — it passed on the bug and failed on the fix. - The Playwright hang matched none of the three hypotheses; the runner could not kill its own dev server. - The golangci-lint toolchain failure is refuted: 19 linters run, 0 issues, verified with -v to rule out the known zero-linters false-green. Adds b0-dev-branch-protection.sh, which records the applied dev branch protection and the reasoning behind each setting. Security detail stays private: the register carries only opaque SEC-* families and safe closure criteria, per the roadmap's public/private handling policy. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * chore(graphify): refresh the knowledge graph Own commit, per CLAUDE.md — the graph payload does not belong in the diff of the changes that triggered it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs(plans): add the active-plan index and fix a stale status header (G-04) Planning documents had no recorded state, so a reader could not tell current guidance from shipped history. docs/plans/README.md now indexes every plan as active, partially implemented, design-only, or shipped, and names the source of truth for each concern so a defect count is never read out of a plan. Status is recorded in the index rather than by moving or rewriting the historical plans, so links from audits and commit messages keep resolving. One real stale claim found and fixed: audit-2026-08-19-remediation.md still read 'in progress 2026-08-19' while its own phase table showed phases 1-6 done 2026-08-20 (merged |
||
|
|
f47ac1b39a |
ci(deps): bump anthropics/claude-code-action (#1429)
Bumps the actions-dependencies group with 1 update in the / directory: [anthropics/claude-code-action](https://github.com/anthropics/claude-code-action). Updates `anthropics/claude-code-action` from 1.0.200 to 1.0.206 - [Release notes](https://github.com/anthropics/claude-code-action/releases) - [Commits](https://github.com/anthropics/claude-code-action/compare/24dcd50c0568f0fc9e9211213a4fd2d9eb15c4e0...1f291e1cfe0f5fc21db2aef19af844591600ade7) --- updated-dependencies: - dependency-name: anthropics/claude-code-action dependency-version: 1.0.206 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: actions-dependencies ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
dd5476f9e3 |
chore(deps): bump the npm-dependencies group across 1 directory with 3 updates (#1428)
Bumps the npm-dependencies group with 3 updates in the /Client directory: [eslint](https://github.com/eslint/eslint), [oxlint](https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint) and [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint). Updates `eslint` from 10.9.0 to 10.9.1 - [Release notes](https://github.com/eslint/eslint/releases) - [Commits](https://github.com/eslint/eslint/compare/v10.9.0...v10.9.1) Updates `oxlint` from 1.79.0 to 1.80.0 - [Release notes](https://github.com/oxc-project/oxc/releases) - [Changelog](https://github.com/oxc-project/oxc/blob/main/npm/oxlint/CHANGELOG.md) - [Commits](https://github.com/oxc-project/oxc/commits/oxlint_v1.80.0/npm/oxlint) Updates `typescript-eslint` from 8.67.0 to 8.68.0 - [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases) - [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md) - [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.68.0/packages/typescript-eslint) --- updated-dependencies: - dependency-name: eslint dependency-version: 10.9.1 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: npm-dependencies - dependency-name: oxlint dependency-version: 1.80.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: npm-dependencies - dependency-name: typescript-eslint dependency-version: 8.68.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: npm-dependencies ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
b7d388a39c |
release: v1.2.0-alpha.4 — 62 fixes plus the B0/B1 repository foundation (#1426)
* Fix 27 findings from 2026-08-21 bug hunt (#1400) * chore(findings): record 2026-08-21 bug hunt (38 findings) * fix(api): 1 defect(s) (OC-0240) * fix(client): 1 defect(s) (OC-0241) * fix(plugin): 2 defect(s) (OC-0243, OC-0265) * fix(client): 3 defect(s) (OC-0244, OC-0256, OC-0259) * fix(client): 1 defect(s) (OC-0247) * fix(client): 2 defect(s) (OC-0248, OC-0258) * fix(identity): 1 defect(s) (OC-0250) * fix(ws): 3 defect(s) (OC-0252, OC-0269, OC-0272) * fix(admin): 1 defect(s) (OC-0253) * fix(client): 1 defect(s) (OC-0254) * fix(voice): 1 defect(s) (OC-0255) * fix(ws): 1 defect(s) (OC-0260) * fix(client): 1 defect(s) (OC-0261) * fix(client): 1 defect(s) (OC-0262) * fix(client): 1 defect(s) (OC-0263) * fix(client): 1 defect(s) (OC-0264) * fix(client): 1 defect(s) (OC-0268) * fix(ws): 1 defect(s) (OC-0273) * fix(service): 1 defect(s) (OC-0275) * style: satisfy golangci-lint and prettier on 2026-08-21 fix commits - drop ineffectual backupDir reset before return (registry.go, OC-0265) - reflow long boolean expression (attachments.ts, OC-0241) * fix(client): 4 defect(s) (OC-0242, OC-0246, OC-0249, OC-0251) * fix(voice): 1 defect(s) (OC-0267) * fix(admin): 1 defect(s) (OC-0274) * fix(voice): 1 defect(s) (OC-0245) * fix(ws): 1 defect(s) (OC-0271) * fix(voice): 2 defect(s) (OC-0239, OC-0257) * fix(ws): 1 defect(s) (OC-0266) * fix(voice): 1 defect(s) (OC-0270) * style: clear golangci-lint modernize and prettier nits from 2026-08-21 fixes - range-over-int and slices.Contains modernizations in new Go test files - prettier reflow in dispatcher.ts * chore(findings): mark 2026-08-21 hunt findings fixed/declined 37 fixed across the fix waves, OC-0238 declined (LiveKit webhook TLS requires a product decision, not a mechanical patch). --------- Co-authored-by: Claude <noreply@anthropic.com> * fix: 35 findings from the 2026-08-22 bug hunt (#1402) * fix(voice): 1 defect(s) (OC-0277) * fix(voice): 1 defect(s) (OC-0278) * fix(client): 1 defect(s) (OC-0280) refreshDmSidebar() rebuilds the entire DM sidebar subtree on every dmStore.channels change - which includes presence flips and new messages, not just DM list changes. The "Find a conversation" filter text and input focus live only in that destroyed subtree, so they were silently wiped mid-typing. Capture and restore both across the destroy+recreate cycle. * fix(ws): 1 defect(s) (OC-0285) * fix(client): 1 defect(s) (OC-0286) * fix(client): 1 defect(s) (OC-0288) Consume the legacy unscoped mute key after migrating it onto the first host, so a brand-new host with no scoped key of its own no longer reads through to the same legacy list and inherits another server's mutes. * fix(voice): 1 defect(s) (OC-0290) * fix(db): 1 defect(s) (OC-0293) DecrementMentionCounts reversed mention_count bumps that were never applied: message_mentions stores every resolved mention id including the author's blockers, while applyMentionCounts excludes blockers before incrementing. Deleting a blocked author's message therefore wiped an unrelated, genuine mention badge on the same read_states row. Mirror the block exclusion in the decrement UPDATE. * fix(db): 1 defect(s) (OC-0294) DeleteAccount soft-deletes the departing user's messages but never reversed the read_states.mention_count bumps those messages made, leaving phantom mention badges. Reverse them inline in the existing transaction, mirroring DecrementMentionCounts' guards. * fix(client): 1 defect(s) (OC-0295) MemberList rebuilt every row on any non-presence-only membersStore change and on every roles_update, but registered each row's click/contextmenu listeners on the component-lifetime disposable.signal, which only aborts at destroy(). Discarded rows therefore stayed reachable (and their listeners live) for the component's whole lifetime. Route per-row listeners through a per-render AbortController that is aborted and replaced at the top of every render, and aborted again in destroy(). * fix(identity): 1 defect(s) (OC-0297) UpdateProfile's post-commit re-read of the user row could fail for reasons unrelated to context cancellation (SQLITE_BUSY, I/O error, pool exhaustion) and was reported as ErrInternal even though UpdateUserProfile had already committed. Callers that treat any UpdateProfile error as proof the write never landed — handleUploadAvatar deletes the file it just stored — would delete a file the committed avatar column now points at, permanently breaking the avatar with no user_update broadcast. Since UpdateUserProfile only writes username/avatar/display_name/about, merge those four onto the pre-write snapshot to reconstruct the committed row without needing the re-read to succeed, and log the read failure. * fix(ws): 2 defect(s) (OC-0298, OC-0299) - OC-0298: applyConnectStatus stamped c.user.Status even when the UpdateUserStatus write failed, so auth_ok and the presence broadcast claimed a status users.status disagreed with, and buildReady's ListMembers read never self-corrected for the session. - OC-0299: refreshUserSnapshot silently fell back to roleName "member" when the new role lookup failed, pinning the session to a fabricated role on the wire. It now fails closed like the sibling lookups in upgradeAndAuth and handleFreshConnect. * fix(client): 1 defect(s) (OC-0300) * fix(client): 1 defect(s) (OC-0301) * fix(ws): 1 defect(s) (OC-0302) * fix(api): 1 defect(s) (OC-0305) handleDiagnosticsConnectivity used clientIP(r), ignoring cfg.Server.TrustedProxies, so behind a configured trusted reverse proxy the endpoint reported the proxy hop instead of the real client address. Use clientIPWithProxies with the parsed trusted-proxy nets, matching RateLimitMiddleware on the same route. * fix(client): 2 defect(s) (OC-0306, OC-0308) * fix(client): 1 defect(s) (OC-0307) QuickSwitcher registered a per-row click listener against the overlay-lifetime AbortSignal, but renderResults() rebuilds every row on each keystroke, arrow key, and store refresh. Discarded rows kept their listeners alive until the overlay closed. Replaced with one delegated click listener on the stable results container, keyed off the data-channelid each row already carries. * fix(client): 1 defect(s) (OC-0310) * fix(server): 3 defect(s) (OC-0279, OC-0291, OC-0292) Reap a soft-deleted message's attachment files, count lapsed temporary bans as active users in the require_2fa enrollment gate, and only apply the 2FA-enrollment precondition when require_2fa itself is being enabled. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SdkJRbjCtrG76jEnrhKbYo * test(api): sync apiTestSchema with the user_blocks migration DeleteAccount's mention-count reversal joins user_blocks; the api package's hand-rolled schema fixture predates migration 012. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SdkJRbjCtrG76jEnrhKbYo * fix(client): 3 defect(s) (OC-0281, OC-0282, OC-0296) Decouple the E2EE identity-mismatch modal and right-click popovers from the sidebar's per-render abort signal, and let global drag listeners survive a mid-drag re-render. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SdkJRbjCtrG76jEnrhKbYo * fix(voice): 2 defect(s) (OC-0283, OC-0287) Retire a departed peer's E2EE key unconditionally on leave, and surface a failed microphone unmute instead of reporting an unmuted state the room never saw. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SdkJRbjCtrG76jEnrhKbYo * fix(client): 3 defect(s) (OC-0289, OC-0303, OC-0309) Guard the DM call button against redialing the channel already joined, resolve the incoming-call banner's caller through the nickname-aware display name, and keep the DM profile sidebar subscribed to live member/status updates. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SdkJRbjCtrG76jEnrhKbYo * style(client): prettier-format the dm-store test Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SdkJRbjCtrG76jEnrhKbYo * fix(server): 1 defect(s) (OC-0284) Make message soft-delete a compare-and-set so a repeated chat_delete cannot reverse mention counts twice. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SdkJRbjCtrG76jEnrhKbYo * fix(server): 2 defect(s) (OC-0276, OC-0304) Re-sync a resumed connection's voice E2EE peer keys in registerNow (announce frames are unsequenced and cannot be replayed), and apply the live-connection presence rule to every DM payload DMService builds. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SdkJRbjCtrG76jEnrhKbYo * chore(ledger): record the 2026-08-21 hunt findings as fixed Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SdkJRbjCtrG76jEnrhKbYo * chore(ledger): independent revert-proof pass for OC-0276..OC-0310 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SdkJRbjCtrG76jEnrhKbYo * refactor(service): extract DeleteMessage authorization into a helper Keeps DeleteMessage under the cyclop complexity ceiling after the OC-0284 guard. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SdkJRbjCtrG76jEnrhKbYo --------- Co-authored-by: Claude <noreply@anthropic.com> * chore(ledger): record the 38 open findings from the 2026-08-22 hunt (#1403) Claude-Session: https://claude.ai/code/session_01SdkJRbjCtrG76jEnrhKbYo Co-authored-by: Claude <noreply@anthropic.com> * chore(graphify): refresh knowledge graph * fix: close the three B0 P0 gates and record a measured baseline (#1409) * chore(security): stop tracking the private security-finding reports docs/security-findings/ holds detailed reports for defects that are not yet fixed. The directory was untracked but not ignored, so any 'git add .' would have published seven unfixed vulnerability traces to a public repository. Findings are coordinated through private GitHub Security Advisories (docs/security.md); only opaque identifiers and safe status belong in tracked plans. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(client): repair the two red P0 unit contracts (G-01, G-02) G-02: noise-suppression-restart stubbed MediaStream with vi.fn().mockImplementation(arrow), which is not constructible. Vitest 4 threw 'is not a constructor' at the new MediaStream([inputTrack]) call in noise-suppression.ts before reaching any assertion. Replaced with a real class; the OC-0277 assertions are unchanged. G-01: message-list's OC-0217 guard was inverted, not merely stale. It spied on AbortSignal.prototype.addEventListener and asserted zero abort registrations, but the leak it names registered row listeners via element.addEventListener(..., { signal }) — a path that never calls that prototype method. Measured: the leak produces 0 registrations (test passes), while the OC-0286 fix rotates a per-window AbortSignal.any and produces 5 across 5 distinct signals (test fails). The guard passed on the bug and failed on the fix. It now captures the signal each window's row listeners register against and asserts the invariant its name always claimed: one signal per rendered window, a fresh signal per jump, and every superseded window already aborted with exactly one live. Verified both directions — green on the fix, and 'expected 1 to be 5' with beginRowRender() reverted to rowSignal = ac.signal. Client suite: 5257 passed, 0 failed (was 5255 passed, 2 failed). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(client): make the Playwright suite terminate The runner finished every test and then never exited, printing no summary — so the failure read as 'tests never finish' when it was 'process never exits'. getActiveResourcesInfo() at hang time showed a live ProcessWrap plus several PipeWrap: the Vite dev server was still running. Playwright's webServer teardown does not kill it here. Measured, full suite each time: npm run dev hangs, tests pass node node_modules/vite/bin/vite.js hangs, tests pass reuseExistingServer: false hangs, tests pass gracefulShutdown SIGTERM/3s hangs, tests pass npx vite exits, 290 of 293 FAIL no webServer (pre-started) exits, 293 pass in 33s npx only appears to fix it: npx exits once Vite is up, Playwright reads that as the server dying and tears the group down mid-run, so later tests get ERR_CONNECTION_REFUSED. globalTeardown now kills the process listening on the dev port, releasing the runner's handle. The webServer command spawns Vite's entry point directly so the listening process is Playwright's own child — via 'npm run dev' the npm process would still hold the handle open. It also reaps servers orphaned by an interrupted run, which reuseExistingServer would otherwise silently adopt. An earlier revision used netstat, which is not on PATH in every shell here; the swallowed ENOENT made the fix look applied while the hang persisted. It now uses PowerShell on Windows and lsof elsewhere, and warns on failure rather than failing silently. npm run test:e2e: exit 0, 293 passed, 37s, reproducible, no orphan listener. playwright.config.prod.ts carried the same npm-wrapper shape. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * chore(client): align .nvmrc with the Node version CI uses Three versions were in play, not two: .nvmrc said 20, CI pins 24, and the machine the audit was measured on runs 26. A baseline measured against .nvmrc is not the baseline CI produces, which defeats the point of B0. Scoped to .nvmrc only. The full single-source-of-truth work — package engines, contributor docs, release — stays in B1 (RL-17 / C-01). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs(plans): add the beta audit set and the B0 baseline The 2026-08-23 audit set has been sitting untracked: repository-health and repository-layout audits, beta product requirements, requirement traceability, the issue register, and the B0-B10 roadmap. They are the plan of record for beta and belong in the repository. Adds b0-baseline-2026-08-25.md, which supersedes the roadmap's 'current evidence snapshot'. Every row is marked measured or carried, so nothing is inherited silently. It also records three audit claims that did not survive verification: - G-01 was an inverted guard, not a stale assertion — it passed on the bug and failed on the fix. - The Playwright hang matched none of the three hypotheses; the runner could not kill its own dev server. - The golangci-lint toolchain failure is refuted: 19 linters run, 0 issues, verified with -v to rule out the known zero-linters false-green. Adds b0-dev-branch-protection.sh, which records the applied dev branch protection and the reasoning behind each setting. Security detail stays private: the register carries only opaque SEC-* families and safe closure criteria, per the roadmap's public/private handling policy. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * chore(graphify): refresh the knowledge graph Own commit, per CLAUDE.md — the graph payload does not belong in the diff of the changes that triggered it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs(plans): add the active-plan index and fix a stale status header (G-04) Planning documents had no recorded state, so a reader could not tell current guidance from shipped history. docs/plans/README.md now indexes every plan as active, partially implemented, design-only, or shipped, and names the source of truth for each concern so a defect count is never read out of a plan. Status is recorded in the index rather than by moving or rewriting the historical plans, so links from audits and commit messages keep resolving. One real stale claim found and fixed: audit-2026-08-19-remediation.md still read 'in progress 2026-08-19' while its own phase table showed phases 1-6 done 2026-08-20 (mergedv1.2.0-alpha.4 |
||
|
|
259225ac61 |
ci(deps): bump anthropics/claude-code-action (#1408)
Bumps the actions-dependencies group with 1 update: [anthropics/claude-code-action](https://github.com/anthropics/claude-code-action). Updates `anthropics/claude-code-action` from 1.0.199 to 1.0.200 - [Release notes](https://github.com/anthropics/claude-code-action/releases) - [Commits](https://github.com/anthropics/claude-code-action/compare/dcb57747bfceeaa1fa72638cae52295d1d853d4a...24dcd50c0568f0fc9e9211213a4fd2d9eb15c4e0) --- updated-dependencies: - dependency-name: anthropics/claude-code-action dependency-version: 1.0.200 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: actions-dependencies ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
cb5953bbb8 |
chore(deps): bump log (#1407)
Bumps the cargo-dependencies group with 1 update in the /Client/tauri-client/src-tauri directory: [log](https://github.com/rust-lang/log). Updates `log` from 0.4.33 to 0.4.34 - [Release notes](https://github.com/rust-lang/log/releases) - [Changelog](https://github.com/rust-lang/log/blob/master/CHANGELOG.md) - [Commits](https://github.com/rust-lang/log/compare/0.4.33...0.4.34) --- updated-dependencies: - dependency-name: log dependency-version: 0.4.34 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: cargo-dependencies ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
0ccb42932c |
build(deps): pin rfd to tauri-plugin-dialog's major to unblock the cargo group (#1406)
The cargo-dependencies group PR (#1405) fails Rust Unit Tests on Linux: error: failed to run custom build command for `rfd v0.17.2` You need to choose at least one backend: `gtk3` or `xdg-portal` features for x86_64-linux rfd is not really ours. It arrives in the tree via tauri-plugin-dialog, which pins ^0.16; we declare it directly only for the fatal-startup message box in lib.rs, where the Tauri app never finished building and the plugin has no AppHandle to run a dialog through. Cargo unifies features only within a semver-compatible version group, so while both wanted ^0.16 there was a single rfd in the graph and the plugin's backend features covered our `default-features = false` declaration too. Bumping our direct dep to 0.17 forks rfd into two crates: the plugin keeps 0.16.0 with its features, ours resolves to 0.17.2 with none, and rfd 0.17 added a build.rs assertion that aborts the Linux build when no backend feature is set. Confirmed in the PR's lockfile, which carries both 0.16.0 and 0.17.2. Adding a Linux backend feature would be the wrong fix: it would paper over the fork and still build rfd twice on every platform for one error dialog. Our version has to track the plugin's instead, so ignore semver-minor rfd updates (0.16 -> 0.17 for a 0.x crate) until tauri-plugin-dialog moves. Patch updates inside 0.16.x still flow. The remaining five crates in the group are unaffected; `windows` in fact consolidates 3 versions down to 2. Cargo.toml is comment-only here - no dependency, feature, or lockfile change - so the build is untouched. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
b1bea96fc8 |
ci(deps): bump anthropics/claude-code-action (#1404)
Bumps the actions-dependencies group with 1 update: [anthropics/claude-code-action](https://github.com/anthropics/claude-code-action). Updates `anthropics/claude-code-action` from 1.0.193 to 1.0.199 - [Release notes](https://github.com/anthropics/claude-code-action/releases) - [Commits](https://github.com/anthropics/claude-code-action/compare/9d7150bc8a3dae8149739a88019d192b579ad90c...dcb57747bfceeaa1fa72638cae52295d1d853d4a) --- updated-dependencies: - dependency-name: anthropics/claude-code-action dependency-version: 1.0.199 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: actions-dependencies ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
9df0e63b5f |
build: toolchain and dependency upgrades (TypeScript 6, Node 24 CI, Vite 8, Vitest 4, Go/Rust deps) (#1401)
* build(client): upgrade TypeScript to 6.0.3 Staging step toward TypeScript 7 (the native compiler), which needs its 7.1 stable API before typescript-eslint and Stryker's typescript-checker can run on it. TS 6 is the JS-based bridge release that aligns config defaults with 7. Two fallout fixes: - tsconfig.e2e.json: TS 6 defaults "types" to [] instead of every installed @types package, so the Playwright layer's Node globals (process, Buffer) need an explicit "types": ["node"]. - media-visibility.test.ts: TS 6's DOM lib adds scrollMargin to IntersectionObserver, so the mock grows the property. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Lw5KEz6gdD816Wxmm4A3Bn * build(server): bump chi to 5.3.2, modernc.org/sqlite to 1.57.0, toolchain to go1.26.7 Go 1.27 deliberately deferred until 1.27.1 lands. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Lw5KEz6gdD816Wxmm4A3Bn * build(tauri): bump tokio-tungstenite to 0.30, refresh Cargo.lock In-range lockfile refresh via cargo update; tungstenite 0.29/0.30 changes are client-API-neutral (header handling, server-side handshake hardening). Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Lw5KEz6gdD816Wxmm4A3Bn * build(client): upgrade vite 8, vitest 4, jsdom 30, stryker 10 + minors - vite 6 -> 8: Rolldown requires the function form of manualChunks; __dirname -> import.meta.dirname in configs - vitest 3 -> 4: browser provider moved to @vitest/browser-playwright; vi.fn() mocks now need explicit signatures (typed throughout tests); constructor mocks use function impls; restoreAllMocks no longer resets vi.fn state; matchMedia spies replaced with vi.stubGlobal - jsdom 29 -> 30: one internal bookkeeping abort listener per signal, listener-count regression tests adjusted (leak detection retained) - stryker 9 -> 10, @types/node 20 -> 24, eslint/oxlint/livekit-client minors - tsconfigs: explicit "types" now that TS6/vitest4 stop injecting @types/node ambiently; build config keeps Node globals out of src/ Validated: tsc (main/build/e2e), eslint, oxlint, knip, prettier, unit+integration (5196 tests), browser suite, vite build, stryker dry run. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Lw5KEz6gdD816Wxmm4A3Bn * ci: move Node 20 (EOL 2026-04-30) to Node 24 LTS The jsdom suite runs on modern Node without --no-experimental-webstorage: tests/setup.ts already replaces the shadowed localStorage with an in-memory shim. Client CLAUDE.md gotcha updated accordingly. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Lw5KEz6gdD816Wxmm4A3Bn --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
c22bc14946 |
feat(bughunt): coverage-driven convergence (#1399)
* feat(bughunt): coverage-driven stop rule and directory-coherent sweep Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(bughunt): return uncredited explore draws to the pool An explore lens denied coverage credit (dead finder or unverified candidates) now un-consumes its draw so later rounds re-offer the files; consumed-but-uncovered files could otherwise pin uncoveredCount above zero and block convergence. Directory grouping reuses clusterOf(). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(bughunt): stalled-coverage guard, risky-file class sweep, exhausted-dry convergence Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(bughunt): stall guard never stops a still-confirming hunt A round with newConfirmed > 0 resets the coverage-stall counter instead of counting toward it; hotspot yield does not shrink the uncovered pool, and a stuck sweep must not cut off a hunt that is still finding bugs. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(bughunt): coverage telemetry in report and operator docs Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs(bughunt): scoped-hunt coverage trap and current cost estimate Final-review fixes: warn that args.lenses plus an examined-armed inventory still sweeps the whole pool (pass a filtered inventory or legacy rows to truly scope), and align the budget note with the coverage-run estimate. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
5202e3fe1e |
fix: correctness fixes from the 2026-08-20 bug hunt (#1398)
* fix(identity): 2 defect(s) (OC-0192, OC-0197)
OC-0192: bound raw display_name/about/avatar bytes before the quadratic
fixpoint sanitizer runs, in both the REST handler and UserService.UpdateProfile.
OC-0197: sanitize display_name before validateDisplayName so an
HTML-entity-encoded bidi override (e.g. "‮") can no longer pass
validation as ASCII and be decoded into the real character on the way to
storage.
* fix(ws): 1 defect(s) (OC-0196)
A transient DB error during WebSocket auth (session or user lookup) was
collapsed into the terminal auth_error frame, which the client treats as
non-recoverable: it stops reconnecting and clears stored credentials. A
sub-second SQLite hiccup therefore force-logged-out every reconnecting
client with a perfectly valid session. Send a non-terminal INTERNAL error
frame instead so normal backoff/reconnect retries.
* fix(api): 1 defect(s) (OC-0198)
* fix(ws): 1 defect(s) (OC-0200)
normalizeHostForCertCompare now unwraps a bracketed IPv6 literal after the
trailing-":443" strip and before lowercasing, matching tofu::cert_store_key's
normalization order. Without the unwrap, every cert-tofu host equality guard
took the "unrelated host" branch for bracketed-IPv6 servers.
* fix(api): 1 defect(s) (OC-0202)
* fix(admin): 1 defect(s) (OC-0203)
Channel permission override handlers applied requireGrantableOverride only
to the bits being written, so an all-zero PUT or a DELETE could clear a
deny bit the actor's own role does not hold — EffectivePerms =
(rolePerm &^ deny) | allow makes removing a deny an escalation. Both the
role-layer and per-user handlers now check the guard against the bits
already on the row.
* fix(client): 1 defect(s) (OC-0205)
* fix(client): 3 defect(s) (OC-0207, OC-0227, OC-0235)
* fix(client): 1 defect(s) (OC-0208)
* fix(voice): 3 defect(s) (OC-0209, OC-0212, OC-0213)
OC-0209: reject a replayed retired-key announce before verifyPeerAnnounce
runs, so the replay cannot overwrite the peer's displayed verification
status/session fingerprint with the retired key's before being rejected.
OC-0212: buffer an announce blocked as a TOFU pin mismatch and replay it
after a successful rePinPeerIdentity, so re-pinning actually restores the
peer for the live call instead of clearing the badge and leaving them
un-keyed (a mid-call peer never re-announces on its own).
OC-0213: skip retiring a departing peer's key when the local voice roster
still lists them as present — a rejoin announce published straight into
the send queue can overtake the buffered, stale voice_leave, and retiring
a still-live key would reject every later genuine re-announce as a replay.
* fix(ws): 1 defect(s) (OC-0211)
* fix(identity): 1 defect(s) (OC-0214)
The delete-account admin guard counted remaining admins with a raw
`banned = 0` filter, so an admin whose temporary ban had already lapsed
was treated as unusable. Use the shared notBannedClause, appended outside
the Sprintf format string because its strftime verbs (%Y, %H) would
otherwise be parsed as fmt directives.
* fix(client): 1 defect(s) (OC-0215)
* fix(voice): 1 defect(s) (OC-0216)
* fix(client): 1 defect(s) (OC-0217)
* fix(voice): 1 defect(s) (OC-0219)
rollbackVoiceJoin cleared the client's in-memory voiceChID but left its
VoiceTopic subscription in place, so a socket whose join failed after
voiceJoinComplete's Subscribe kept receiving that room's E2EE relays for
the rest of the connection. Use clearVoiceAndUnsubscribe instead, matching
every other path that takes a client out of voice while its WS stays up.
* fix(client): 2 defect(s) (OC-0220, OC-0224)
dmDisplayName: a group DM whose other members have all left keeps a live
is_group row, but the server leaves `recipient` zero-valued, so the empty
username fell through as a blank label. Fall back to a non-empty placeholder.
updateDmLastMessage: a queued chat_message redelivered for an id already
reflected in the `ready` snapshot double-counted the unread badge. Only
increment when the message id advances past lastMessageId.
* fix(client): 1 defect(s) (OC-0221)
Cap queued attachments at the server's 10-attachment limit in the message
composer. Past that the server rejects the whole chat_send frame as a
generic parse error, orphaning already-uploaded attachments; refusing
before the upload starts keeps composer state and the send in sync.
* fix(ws): 1 defect(s) (OC-0222)
handleReconnect built the resume auth_ok before applyConnectStatus settled
c.user.Status, so a resumed client was told its disconnect-time status
(routinely "offline") instead of the status it was coming online as.
Move applyConnectStatus ahead of reconnectWriteReplay, matching
handleFreshConnect's ordering.
* fix(mentions): 1 defect(s) (OC-0223)
* fix(admin): 1 defect(s) (OC-0225)
* fix(client): 1 defect(s) (OC-0226)
* fix(client): 1 defect(s) (OC-0228)
* fix(client): 1 defect(s) (OC-0230)
Route the Logs tab entry counter through renderLogEntries so every render path (filter change, Clear, Refresh, live entry) keeps the count in sync with the list.
* fix(voice): 1 defect(s) (OC-0231)
* fix(client): 1 defect(s) (OC-0232)
Reduce Motion toggle wrote the reduced-motion class directly, fighting the
OS-sync media-query listener that owns it when Sync with OS is on. Route the
side effect through syncOsMotionListener so whichever source owns the class
re-derives it.
* fix(client): 1 defect(s) (OC-0233)
notifyIncomingMessage titled the desktop notification with the raw
payload username, so the popup named the sender differently from the
message row it points at. Resolve the author the same way the message
list does (resolveAuthor over the live membersStore, then
resolveDisplayName).
* fix(client): 1 defect(s) (OC-0234)
* fix(client): 1 defect(s) (OC-0236)
* fix(ws): 1 defect(s) (OC-0237)
* fix(client): 4 defect(s) (OC-0193, OC-0201, OC-0204, OC-0218)
* fix(identity): 1 defect(s) (OC-0195)
Bound free-text profile fields by raw byte length before cleanText's
quadratic sanitizeToFixpoint pass runs, generalizing OC-0192's guard into
cleanTextBounded and applying it to HandlePresenceUpdate's custom_status,
SetCustomStatus, and group DM names.
* fix(dm): 1 defect(s) (OC-0199)
handleCreateDM now broadcasts dm_channel_open to the recipient when a 1:1 DM is newly created, matching handleCreateGroupDM. GetOrCreateDMChannel pre-seeds dm_open_state for both users, so the recipient's later OpenDM reported opened=false and nothing ever told them the DM existed.
* fix(voice): 1 defect(s) (OC-0206)
vad-worklet.js gate timing constants were copied from the setTimeout
fallback's ~16ms poll cadence, but AudioWorkletProcessor.process() runs
once per 128-sample render quantum (~2.667ms at the 48kHz AudioContext).
The mic gate therefore closed ~6x faster than intended (~32ms of silence
instead of ~200ms), with the startup grace and RMS post interval off by
the same factor. Scale the frame counts to render quanta.
* fix(client): 1 defect(s) (OC-0229)
* test(client): assert the real TOFU re-pin outcome and make the pin mock faithful
The e2e journey test asserted that "Trust New Key" makes the peer's verify
badge disappear. That is the behaviour OC-0212 identifies as the defect: a
mid-call peer never re-announces, so clearing the badge left the peer
un-keyed for the rest of the call with nothing on screen. Re-pinning now
replays the announce that was blocked as a mismatch and re-verifies it
against the pin just stored, so assert the peer actually lands verified.
The mock's store_identity_pin was a no-op recorder while get_identity_pin
served a static seed map, so the replayed announce re-read the stale pin and
re-failed — a mismatch the real keyring never produces. Back the pins with a
mutable map so a write is visible to the next read. The unreadable-store
(DC-08) and reject-keeps-blocked paths are unchanged and still pass.
* fix(dm): 1 defect(s) (OC-0194)
Add regression tests pinning the raw-byte bound on group DM names, for
both CreateGroupDM and RenameGroupDM.
The Server/service/dm.go source fix for OC-0194 already landed in
|
||
|
|
d880b64d64 |
test: audit 2026-08-19 — fix stale tests, close coverage gaps (#1397)
* test(server): admin/handlers/channels — test-audit 2026-08-19 fixes * test(server): api/constants — test-audit 2026-08-19 fixes * test(server): api/middleware — test-audit 2026-08-19 fixes * test(server): api/waf — test-audit 2026-08-19 fixes * test(server): auth/totp/encrypt — test-audit 2026-08-19 fixes * test(server): db/session/expiry/test — test-audit 2026-08-19 fixes * test(server): migrations/030/attachments/unlink/on/message/delete — test-audit 2026-08-19 fixes * test(server): updater/download — test-audit 2026-08-19 fixes * test(server): ws/handlers_command — test-audit 2026-08-19 fixes * test(server): ws/hub/broadcast — test-audit 2026-08-19 fixes * test(server): ws/hub/events — test-audit 2026-08-19 fixes * test(server): ws/livekit/webhook — test-audit 2026-08-19 fixes * test(server): ws/voice/controls — test-audit 2026-08-19 fixes * test(server): ws/voice/join — test-audit 2026-08-19 fixes * test(server): ws/voice/moderation — test-audit 2026-08-19 fixes * test(rust): src-tauri/src/commands.rs — test-audit 2026-08-19 fixes * test(rust): src-tauri/src/secret_store.rs — test-audit 2026-08-19 fixes * test(rust): src-tauri/src/update_commands.rs — test-audit 2026-08-19 fixes * test(client): src/components/ChannelSidebar.ts — test-audit 2026-08-19 fixes * test(client): src/lib/ws.ts — test-audit 2026-08-19 fixes * test(rust): src-tauri/src/credentials.rs — test-audit 2026-08-19 fixes * test(rust): src-tauri/src/tofu.rs — test-audit 2026-08-19 fixes * test(client): src/lib/hostValidation.ts — test-audit 2026-08-19 fixes * test(client): src/lib/rate-limiter.ts — test-audit 2026-08-19 fixes * test(client): src/pages/connect-page/LoginForm.ts — test-audit 2026-08-19 fixes * test(client): src/pages/main-page/SidebarArea.ts — test-audit 2026-08-19 fixes * test(client): src/stores/voice.store.ts — test-audit 2026-08-19 fixes * test(client): tests/browser/smoke.test.ts — test-audit 2026-08-19 fixes * test(client): tests/unit/media.test.ts — test-audit 2026-08-19 fixes * test(client): tests/unit/renderers.test.ts — test-audit 2026-08-19 fixes * test(client): src/components/UserProfilePopup.ts — test-audit 2026-08-19 fixes * test(client): src/lib/e2eeCrypto.ts — test-audit 2026-08-19 fixes * test(client): tests/unit/log-persistence.test.ts — test-audit 2026-08-19 fixes * test(client): keep tests/browser out of the jsdom suite and run it in CI * test(server): ws/hub_broadcast_test.go — bytes.Equal payload compare (gocritic) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test(client): src/lib/credentials.ts — test-audit 2026-08-19 round 2 (Stryker) * test(client): src/lib/dispatcher.ts — test-audit 2026-08-19 round 2 (Stryker) * test(client): src/lib/permissions.ts — test-audit 2026-08-19 round 2 (Stryker) * test(client): src/lib/rate-limiter.ts — test-audit 2026-08-19 round 2 (Stryker) * test(client): src/lib/hostValidation.ts — test-audit 2026-08-19 round 2 (Stryker) * test(client): src/stores/messages.store.ts — test-audit 2026-08-19 round 2 (Stryker) * test(client): src/lib/e2eeCrypto.ts — test-audit 2026-08-19 round 2 (Stryker) * test(client): src/lib/ws.ts — test-audit 2026-08-19 round 2 (Stryker) * test(client): src/lib/identity.ts — test-audit 2026-08-19 round 2 (Stryker) * test(client): src/lib/livekitE2EE.ts — test-audit 2026-08-19 round 2 (Stryker) * test(client): src/stores/auth.store.ts — test-audit 2026-08-19 round 2 (Stryker) * test(client): src/stores/voice.store.ts — test-audit 2026-08-19 round 2 (Stryker) * docs: test audit 2026-08-19 — findings, fixes, measured baselines Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * chore(graph): refresh the knowledge graph after the 2026-08-19 test audit Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
03fcb7d518 |
fix: execute the 2026-08-19 audit fix order (docs refresh + five FRAGILE fixes) (#1396)
* docs(plans): phased remediation plan for the 2026-08-19 audit Executes the audit's §8 MUST-fix verdict and §9.1 fix order: one phase per finding group, statuses updated in place as phases land. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HtkxwdqE4pUv82GQPRsTeQ * test(client): give the renderWindow-breaker test its own timeout (audit F-5) 30 synchronous 100-row jsdom rebuilds can exceed vitest's default 5s on a loaded runner; the test timed out once under CI-like load and passes in isolation, so it now carries an explicit 20s budget. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HtkxwdqE4pUv82GQPRsTeQ * docs: fix the ten wrong reference-doc statements from audit 2026-08-19 (B-01..B-10) schema.md: migrations 030/031 documented, attachments ON DELETE SET NULL (matching 030's rebuild), index inventory rewritten from cumulative migration state, writer/reader pool split described, default-roles table made a consistent post-migration snapshot, dbgen preamble updated. protocol.md: DM chat events documented as sequenced/ring-buffered/replayable (they are), plugin_broadcast seq flipped to Yes, retry_after claim removed (no WS error carries it), the five enforced-but-documented-as-None rate limits added (channel_focus, mark_read, call_decline, chat_command, ping), E2EE announce/offer budgets corrected incl. the per-target inner cap, BAD_PAYLOAD and NOT_KEY_HOLDER added to the error table, ready voice_states/ roles field lists completed, member_join top-level status documented. api.md: diagnostics endpoint is ADMINISTRATOR-only (H-8) with a per-IP limiter and host:port livekit_url, error-code table now matches emitted codes (INTERNAL_ERROR, STORAGE_ERROR 507; oversize upload is 400), body-cap exemptions listed, identity_public_key documented on PATCH /users/me, plugin endpoints' plain-text errors + X-Plugin-Runtime header documented, /health 503 degraded state documented, metrics/LiveKit CIDR keys named, updates/apply restart-conflict 409s added. Also folds in the audit's D-04/D-05 comment and plan-header staleness fixes (buildReady comment, e2e spec-count comments, logctx stray word, three plan status headers). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HtkxwdqE4pUv82GQPRsTeQ * fix(server): log the five silently-discarded persistence errors (audit F-3/F-4/D-16) Lockout Upsert/Delete/Cleanup failures (auth/ratelimit.go), the H-6 session-cap eviction failure in CreateSession (db/auth_queries.go), and the channel_focus read-state write failure (service/channel.go) all discarded their errors with no trace — a brute-force lockout could silently fail to survive a restart. In-memory behavior is unchanged (warn-and-continue); the lockout write paths are pinned by tests mirroring OC-0061's load-path test. The session-cap and read-state sites are log-only additions on seams the existing suites already exercise on the success path. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HtkxwdqE4pUv82GQPRsTeQ * fix(dm): blocking a user evicts them from the pair's live 1:1 DM voice call (audit F-1) The block gate ran only at voice_join and voluntary voice_token_refresh, so a blocked user already in the shared 1:1 DM call kept their session indefinitely — the same guard-asymmetry family as A-2026-08-03. handleBlockUser now severs the call through the dmVoiceEvictor capability handleCloseDM already exercises, using a new find-only FindDMChannelIDBetween lookup (sqlc-generated; mirrors GetOrCreateDMChannel's is_group=0 clause so group DM calls stay exempt, matching requireDMNotBlocked). Pinned by three handler tests: shared-DM eviction, no-DM no-op, group-only no-op. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HtkxwdqE4pUv82GQPRsTeQ * fix(ws): close the role-reassignment/handshake race (audit F-2) A role reassignment landing mid-handshake was invisible for the socket's whole life: both handshake paths resolved permissions from the auth-time c.user snapshot, revokeUnreadableChannels early-returns for a user not yet in h.clients, and its Unsubscribe no-ops on the pubsub identity guard once a reconnect replaced the client. Three coordinated fixes: (1) refreshUserSnapshot re-reads the user row (and role name) in reconnectPrecheck and handleFreshConnect, fail-closed; (2) the resume-fallback path re-reads the role once more after registerNow and runs the revocation pass when it moved, so the reassignment-vs-registration orderings meet in the middle; (3) revokeUnreadableChannels re-resolves the live client immediately before acting, mirroring RefreshChannelVisibility. Pinned by four tests driving real WS handshakes through the existing race hooks plus a new pre-register/pre-act hook pair; ws suite green under the default and deadlock builds. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HtkxwdqE4pUv82GQPRsTeQ * refactor(client): remove the inert replay-dedup machinery (audit F-6) The server writes auth_ok before the replay burst, so replayDedup — created on socket-open and cleared when auth_ok is processed — could never be active for a real replayed frame, and the dispatcher's isReplaying() unread gates never fired. Their no-op behavior is the correct behavior (a buffer/db resume has no ready payload, so replayed frames must count as unread), so the machinery, the gates, and the misleading comments are removed rather than repaired. The pinning tests injected replay frames in an order a spec-compliant server never produces; they are replaced by a test pinning the real contract (frames after auth_ok are dispatched verbatim; duplicate handling belongs to the stores). Client suite green: 5036/5036. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HtkxwdqE4pUv82GQPRsTeQ * docs(plans): mark remediation phases 1-6 done Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HtkxwdqE4pUv82GQPRsTeQ * fix(ws): nolint the context-less revoke call golangci-lint flags revokeUnreadableChannels takes no context by design (admin HubBroadcaster interface); annotate the one call site inside a ctx-taking function, matching the RefreshChannelVisibility precedent. golangci-lint v2.11.3: 0 issues. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HtkxwdqE4pUv82GQPRsTeQ --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
312ac4bbf4 |
docs: add the 2026-08-19 repo health audit (#1395)
Full-repo health check at
|
||
|
|
eacba10cff |
fix(e2ee): bind a key epoch into room-key offers and show a per-call session fingerprint (#1394)
* fix(e2ee): bind the key epoch into wrapped room-key offers The holder's rotation counter now rides inside encrypted_key as a versioned header and is bound as AES-GCM additional data, so a receiver can tell a current room key from a superseded one. Receivers keep a per-sender high-water mark and apply an offer only at or above it; the mark resets when that sender announces a fresh ephemeral key. Blobs in the pre-epoch layout are still accepted for holders on the older build (compat path, scheduled for removal next release). No server or schema change: the relay treats encrypted_key as opaque. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(e2ee): show a per-call session fingerprint for every voice peer A peer with no published identity key has no safety number, so the TOFU badge gave the user nothing to compare out of band. Every accepted announce now also carries a fingerprint of the peer's ephemeral session key, shown on the unverified badge and labelled as changing every call and not an identity; the local user's own session fingerprint is shown on their row so it can be read back. safetyNumber is unchanged and stays null for unverified peers. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * chore(ledger): mark OC-0001 and OC-0003 fixed Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
c86d803a18 |
fix: resolve the six blocked batch-4 ledger findings (#1393)
* fix(ws): resolve an empty READ audience for a channel whose row is gone channelReadAudience already failed closed on a GetChannel error; a deleted channel returns (nil, nil) and fell through to the role scan. Return nobody for a missing row too — voice teardown callers union the room's participants and the leaver back in, so their signals still land. Test locks both halves: the non-participant hears nothing, the leaver still gets voice_leave. (OC-0090) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(ws): re-elect the key holder in CleanupVoiceForChannel Every other voice-removal path re-elects (finishVoiceLeave, the LiveKit webhook, registerNow, rollbackVoiceJoin, sweepStaleVoiceStates); the channel delete/archive path did not, so a torn-down channel's voiceKeyHolders entry lived for the process lifetime. One updateKeyHolder call at the end of the teardown deletes it. (OC-0012) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(ws): implement BroadcastMemberUnban so unban reaches connected clients The admin unban path reaches the hub through an optional-capability type assertion that *ws.Hub never satisfied, so it always missed silently and clients connected during a ban kept the user missing from their member store. Implement the mirror of BroadcastMemberBan: fan out the same member_join a fresh connect sends (clients already map it to addMember), reporting offline since the unbanned user cannot be connected. A compile-time assertion in admin pins the wiring so the assertion can never silently miss again. (OC-0058) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(db): exclude the requester's own flag from the video-cap stream count EnableCameraIfUnderLimit and EnableScreenshareIfUnderLimit counted every stream in the channel including the very flag the UPDATE sets, so a user whose server-side flag was already 1 (client lost track and retried) was refused at the cap against their own stream, with no path out. Subtract the outer row's own bit from the correlated count: re-enable becomes idempotent while the requester's other stream and everyone else's still count. sqlc layer regenerated. (OC-0081) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * chore(ledger): resolve the six blocked batch-4 findings Four fixed in this branch (OC-0012, OC-0058, OC-0081, OC-0090), each with an independent revert-proof pass. Two were already fixed on main by later sibling fixes and are recorded as such: OC-0086 by the OC-0017 pre-delete re-check (#1374), OC-0101 by the OC-0206 early watermark bump (#1375). The ledger holds zero open and zero blocked findings. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
8cf019c03f |
fix: 40 correctness fixes from the 2026-08-19 bug hunt (#1392)
* fix(identity): 1 defect(s) (OC-0151)
* fix(ws): 1 defect(s) (OC-0152)
* fix(admin): 1 defect(s) (OC-0153)
* fix(admin): 1 defect(s) (OC-0154)
* fix(voice): 2 defect(s) (OC-0155, OC-0167)
Replace distributeRoomKey's per-call offer counter with an instance-level
sliding-window budget shared by every voice_e2ee_offer send path.
- OC-0155: back-to-back rotations (the second run immediately by
drainPendingRotationOrArmTimer) each got a fresh pacing budget, so their
combined sends could exceed the server's single per-second cap.
- OC-0167: handleAnnounceInner's drain-time offer send bypassed pacing
entirely, letting a key holder joining a large ongoing call burst every
queued announce's offer unpaced.
The shared budget is reset in clearState() since the server's limit is
scoped per (sender, channel).
* fix(client): 1 defect(s) (OC-0156)
createPresenceSender dropped a queued custom_status when a later plain
status change superseded the pending retry. The retry now carries the
last committed custom_status forward.
* fix(client): 2 defect(s) (OC-0160, OC-0163)
OC-0160: exempt the handshake frames (ready, auth_ok) from the ws message
size limit and run the guard after parsing. A 'ready' frame grows unbounded
with member/channel/DM counts and carries no seq, so dropping it left the
client on empty stores with no error and no recovery path.
OC-0163: bracket a bare IPv6 host when building the wss:// URL so the
authority parses, and collapse bracketed/bare IPv6 literals to the same
cert_store_key so one server is not pinned (and user-confirmed) twice.
* fix(voice): 1 defect(s) (OC-0162)
updatePttKey armed the Rust poller when a PTT key was bound mid-call but
never applied the gate. The poller only emits 'ptt-state' on a press/release
transition, so an idle key produced no event and the already-published mic
stayed hot until the user's first physical press+release. Mirror the join-time
gate computation in updatePttKey, guarded on being in a call, polling actually
being live, and the mic not already being gated.
* fix(client): 1 defect(s) (OC-0164)
* fix(plugin): 1 defect(s) (OC-0165)
scanPluginDirectory now skips a malformed plugin subdirectory and joins its
error instead of aborting the whole scan, and LoadAll logs-and-continues so
one bad plugin directory cannot disable every other plugin.
* fix(ws): 1 defect(s) (OC-0166)
Route PresenceSelfEvent onto the owner's normal-priority queue instead of
letting it fall through to the UserTargetedEvent high-priority case, so a
user's own presence frames all share one FIFO and cannot be delivered out
of order relative to the visible presence_update path.
* fix(db): 1 defect(s) (OC-0168)
* fix(client): 1 defect(s) (OC-0169)
* fix(client): 1 defect(s) (OC-0171)
addMessage appended a broadcast at the tail even when trailing optimistic
rows were still unreconciled, so a message that committed while our own
send was in flight ended up ordered behind the row confirmSend later
stamped with a higher server id/timestamp. Insert before the trailing
unreconciled run instead.
* fix(voice): 1 defect(s) (OC-0172)
* fix(client): 1 defect(s) (OC-0174)
* fix(ws): 1 defect(s) (OC-0175)
* fix(client): 1 defect(s) (OC-0177)
* fix(client): 1 defect(s) (OC-0178)
* fix(voice): 1 defect(s) (OC-0179)
Undeafening no longer sends a voice_mute{muted:false} the server will
refuse while a moderator-imposed mute stands, matching the localServerMuted
guard already present in onMuteToggle.
* fix(client): 1 defect(s) (OC-0182)
* fix(plugin): 1 defect(s) (OC-0183)
* fix(client): 1 defect(s) (OC-0184)
Treat a trailing underscore as an emphasis delimiter, not part of the URL,
when scanning for the end of an autolinked URL.
* fix(client): 1 defect(s) (OC-0185)
Reveal .msg-actions-bar on .message:focus-within, not only on hover, so
keyboard users can see the per-message action buttons they Tab into
instead of activating them at opacity: 0.
Claude-Session: https://claude.ai/code/session_01M6gVN2JM5wrduhkNaFCxdK
* fix(client): 1 defect(s) (OC-0186)
* fix(client): 1 defect(s) (OC-0187)
The Add Server modal validated addresses with its own narrower regex that
never gained IPv6 support when api.ts's validator did, so an IPv6 server
could be logged into but never saved as a profile. Extract the validator
into src/lib/hostValidation.ts and use it from both call sites.
* fix(client): 1 defect(s) (OC-0189)
DM sidebar rows dropped mention counts entirely and the header total
excluded muted conversations outright, so a direct mention in a muted DM
was invisible. Render a mention badge that outranks the plain unread
badge, and count a muted channel's mentionCount toward the header total.
* fix(client): 1 defect(s) (OC-0190)
* fix(client): 1 defect(s) (OC-0191)
* fix(client): 2 defect(s) (OC-0157, OC-0176)
* fix(client): 1 defect(s) (OC-0161)
confirmTotp answers 401 for a wrong enrollment code while the session is still valid; firing the global onUnauthorized sink signed the user out and deleted their stored credential. Opt that one call out via a skipUnauthorized flag on doFetch.
* fix(admin): 1 defect(s) (OC-0173)
* fix(identity): 1 defect(s) (OC-0180)
* fix(admin): archived channel PATCH skips voice eviction and fan-out (OC-0158)
handlePatchChannel commits the AdminUpdateChannel write, then re-reads the
channel to drive voice eviction and the visibility fan-out. When that
post-commit re-read failed, the handler returned early: the archive was
durable but connected clients were never told and voice members were never
evicted, leaving users talking in a channel that no longer exists for them.
Drive the post-commit work off the values already in hand rather than
abandoning it when the re-read fails.
Adds SetPatchChannelPostCommitHook so the test can land a cancellation in
that exact window deterministically instead of racing wall-clock timing.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M6gVN2JM5wrduhkNaFCxdK
* fix(admin): role changes commit with no client ever notified (OC-0170)
broadcastRoles derived its context from the inbound *http.Request, so the
roles_update fan-out was tied to the request lifetime. A role create,
update, or delete could commit to the database and then broadcast nothing
once that request context was done, leaving every connected client on a
stale role list until the next full resync.
Decouple the fan-out from the request context so the broadcast follows the
commit rather than the caller.
Adds BroadcastRolesForTest to reach broadcastRoles from the external test
package.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M6gVN2JM5wrduhkNaFCxdK
* fix(client): username rename stomps the profile card header (OC-0188)
The account profile card's header is a resolveDisplayName() slot, but the
username-rename save path wrote the raw username straight into it. A user
with a display name set would see the header switch from their display
name to their new username after a rename, disagreeing with every other
surface that renders the same identity.
Resolve the header through the same display-name path the initial render
uses, so a rename updates the username field without touching the header.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M6gVN2JM5wrduhkNaFCxdK
* fix(client): settings overlay never focuses when mounted already-open (OC-0181)
mount() synced initial state — including the show() that calls
focusDialog() — before appending root to the container. .focus() on a
still-detached subtree is a silent no-op, so a caller that mounts while
uiStore.settingsOpen is already true (ConnectPage's lazy first-open path)
got a visible overlay whose focus trap never captured focus: keyboard
users landed outside the dialog with Tab escaping to the page behind it.
Attach root before syncing initial state so focusDialog() runs against a
connected subtree.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M6gVN2JM5wrduhkNaFCxdK
* chore: satisfy the CI gates for this fix batch
The fix batch's own commits left three CI gates red. Nothing here changes
behaviour; every edit is a lint, type, or formatting correction to code
this batch introduced.
golangci-lint:
- OC-0153 and OC-0173 replaced the last two uses of admin's setupSanitizer,
and OC-0151 the last use of api's sanitizer, leaving both package-level
bluemonday vars unused. Remove them along with the now-unused imports,
and reword the comments that named them so they still explain why the
fixpoint sanitizer is the right one without pointing at deleted symbols.
- Modernize the new handshake-deadline test's loop to range-over-int.
tsc --noEmit:
- jsdom ships no types and @types/jsdom is not a dependency, so declare the
surface the new admin-panel test uses, following src/types/jitsi-rnnoise.d.ts.
- Narrow the last-call lookup instead of indexing under
noUncheckedIndexedAccess, with an explicit failure message.
- membersStore.setState replaces whole state, so the presence-sender mocks
must supply typingUsers.
prettier: reformat the five files this batch touched.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M6gVN2JM5wrduhkNaFCxdK
* chore(ledger): record the 2026-08-19 hunt and its fixes
Adds the 41 findings confirmed by the 2026-08-19 hunt and marks the 40
fixed on this branch, each with its commit, the test that pins it, and
revertProof "pass".
"pass" means an independent check, not the fixing agent's self-report:
every commit had its source diff reverted against the working tree, its
own test re-run and required to FAIL, then the source restored and the
test required to PASS. Commits whose tests live inline in Rust
#[cfg(test)] blocks were proven the same way at hunk level, splicing the
pre-fix source onto the post-fix test module.
OC-0159 is recorded as a duplicate of OC-0152: the flow-reconnect and
flow-message lenses independently found the same unbounded handshake
write and proposed the same helper over the same call sites.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M6gVN2JM5wrduhkNaFCxdK
* test(e2e): make the voice-roster join fixture self-consistent
The voice-widget join test emitted a voice_state for user_id 4 claiming
username "newvoiceuser", but id 4 is "member2" in MOCK_MEMBERS_MULTI_ROLE.
A real server never sends a voice_state whose username disagrees with the
member record for that id, and the same file's VOICE_STATE_EVENT already
pairs id 1 with "testuser" correctly — this one event was the outlier.
The contradiction was invisible while the roster rendered the payload's
raw username. OC-0177 makes it resolve identity through membersStore so a
nickname shows the same in voice as everywhere else, at which point the
fixture's own inconsistency surfaced as a failure.
Send id 4's real username and assert on it. The test still covers what it
did before — a genuine join by a user not previously in voice, asserted by
name and by roster count.
Verified against the app unchanged: with the old fixture the spec fails
1/5 (matching CI), with this one it passes 5/5.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M6gVN2JM5wrduhkNaFCxdK
---------
Co-authored-by: Claude <noreply@anthropic.com>
|
||
|
|
5d6167a4d3 |
fix(deps): bump h2 to 0.4.16 to clear RUSTSEC-2026-0258 (#1390)
* fix(deps): bump h2 to 0.4.16 to clear RUSTSEC-2026-0258 The Rust dependency audit step in Tauri Full Build fails on h2 0.4.13, which RustSec patches at >=0.4.16. h2 is transitive (hyper -> reqwest), so this is a lockfile-only bump. Edited the h2 stanza directly rather than running `cargo update -p h2 --precise`: that command also re-unified ten unrelated windows-sys references down a minor, churn this change has no reason to carry. `cargo metadata --locked` accepts the edited lockfile, which is the resolver confirming it is a valid resolution. reqwest (0.12.28, 0.13.2), hyper 1.8.1, hyper-rustls 0.27.7 and rustls 0.23.43 are all unchanged, so the preconfigured-ClientConfig seam that tauri-plugin-updater's minor pin protects is untouched. cargo audit now exits 0; the 19 remaining entries are unmaintained/yanked warnings (atk and the rest of the GTK3 tree under wry), which the audit does not fail on and which only Tauri upstream can retire. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * test(ws): join the load-soak drain goroutines instead of racing goleak TestTheLoadTest closed each anchor's stopDrain channel and then relied on a 300ms sleep for the drain goroutines to actually exit before the deferred goleak.VerifyNone ran. Closing the channel only makes those goroutines runnable — it does not wait for the scheduler to run them. On windows-latest the whole test takes ~126s under -race with 20 churn workers and 6 broadcasters saturating the runner, and goleak's bounded retry window can expire while all 8 drains are still sitting in state "runnable". CI then fails with "found unexpected goroutines" pointing at load_soak_test.go:127 even though nothing actually leaks. Track the drains on a WaitGroup and join them right after the stopDrain channels close. The wait happens in the test body, and goleak.VerifyNone is deferred, so the check can no longer observe a drain that has been signalled but not yet scheduled. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
21945fb809 |
chore(deps): consolidate the four open Dependabot groups into one PR (#1391)
* ci(deps): bump anthropics/claude-code-action Bumps the actions-dependencies group with 1 update: [anthropics/claude-code-action](https://github.com/anthropics/claude-code-action). Updates `anthropics/claude-code-action` from 1.0.189 to 1.0.193 - [Release notes](https://github.com/anthropics/claude-code-action/releases) - [Commits](https://github.com/anthropics/claude-code-action/compare/6b082c41935b4c8a3b8b0ef85ba4ba4d9eeb8975...9d7150bc8a3dae8149739a88019d192b579ad90c) --- updated-dependencies: - dependency-name: anthropics/claude-code-action dependency-version: 1.0.193 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: actions-dependencies ... Signed-off-by: dependabot[bot] <support@github.com> * chore(deps): bump the go-dependencies group in /Server with 3 updates Bumps the go-dependencies group in /Server with 3 updates: [golang.org/x/crypto](https://github.com/golang/crypto), [golang.org/x/mod](https://github.com/golang/mod) and google.golang.org/protobuf. Updates `golang.org/x/crypto` from 0.54.0 to 0.55.0 - [Commits](https://github.com/golang/crypto/compare/v0.54.0...v0.55.0) Updates `golang.org/x/mod` from 0.38.0 to 0.40.0 - [Commits](https://github.com/golang/mod/compare/v0.38.0...v0.40.0) Updates `google.golang.org/protobuf` from 1.36.11 to 1.36.12 --- updated-dependencies: - dependency-name: golang.org/x/crypto dependency-version: 0.55.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: go-dependencies - dependency-name: golang.org/x/mod dependency-version: 0.40.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: go-dependencies - dependency-name: google.golang.org/protobuf dependency-version: 1.36.12 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: go-dependencies ... Signed-off-by: dependabot[bot] <support@github.com> * chore(deps): bump the npm-dependencies group Bumps the npm-dependencies group in /Client/tauri-client with 3 updates: [knip](https://github.com/webpro-nl/knip/tree/HEAD/packages/knip), [oxlint](https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint) and [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint). Updates `knip` from 6.32.0 to 6.32.2 - [Release notes](https://github.com/webpro-nl/knip/releases) - [Commits](https://github.com/webpro-nl/knip/commits/knip@6.32.2/packages/knip) Updates `oxlint` from 1.77.0 to 1.78.0 - [Release notes](https://github.com/oxc-project/oxc/releases) - [Changelog](https://github.com/oxc-project/oxc/blob/main/npm/oxlint/CHANGELOG.md) - [Commits](https://github.com/oxc-project/oxc/commits/oxlint_v1.78.0/npm/oxlint) Updates `typescript-eslint` from 8.66.0 to 8.67.0 - [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases) - [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md) - [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.67.0/packages/typescript-eslint) --- updated-dependencies: - dependency-name: knip dependency-version: 6.32.2 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: npm-dependencies - dependency-name: oxlint dependency-version: 1.78.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: npm-dependencies - dependency-name: typescript-eslint dependency-version: 8.67.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: npm-dependencies ... Signed-off-by: dependabot[bot] <support@github.com> * chore(deps): bump futures-util Bumps the cargo-dependencies group in /Client/tauri-client/src-tauri with 1 update: [futures-util](https://github.com/rust-lang/futures-rs). Updates `futures-util` from 0.3.33 to 0.3.34 - [Release notes](https://github.com/rust-lang/futures-rs/releases) - [Changelog](https://github.com/rust-lang/futures-rs/blob/main/CHANGELOG.md) - [Commits](https://github.com/rust-lang/futures-rs/compare/0.3.33...0.3.34) --- updated-dependencies: - dependency-name: futures-util dependency-version: 0.3.34 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: cargo-dependencies ... Signed-off-by: dependabot[bot] <support@github.com> --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
39551de4a6 |
refactor(server): work off the complexity backlog — 62 findings to 0 (#1389)
* refactor(ws): split handleVoiceJoin into cohesive join-stage helpers handleVoiceJoin was 130 statements / cyclomatic 59 / nestif 11, breaking all three complexity budgets at once. Split along the stage boundaries the doc comment already described: precheck, leave-current, persist, restore moderator flags, grant token, complete. The publish-permission derivation becomes its own helper because it is the one branch-heavy block inside the token grant. Pure move: every statement is preserved verbatim. The only edits are bare `return`s becoming the typed returns of their new helper, `c.userID` becoming the `userID` parameter inside voiceJoinPublishPerms, and voiceJoinComplete re-reading `ch.VoiceMaxUsers` instead of receiving it — `ch` is never mutated, so the value is identical. Verified by normalising both revisions of the region to sorted, comment- and whitespace-stripped statements and diffing: the only deltas are the ones listed above. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * refactor: collapse the three duplicated sibling pairs dupl flagged three pairs of adjacent near-identical functions. Each pair is now one parameterised implementation plus two thin, still-greppable wrappers. - ws/voice_controls.go: handleVoiceMuteV2 / handleVoiceDeafenV2 share voiceSelfToggleV2; handleVoiceCameraV2 / handleVoiceScreenshareV2 share voiceStreamToggleV2. Camera and screenshare drawing from one voice_max_video budget (OC-0023) was a bug caused by exactly this duplication drifting, so one body is the point, not a side effect. - db/mention_queries.go: ListMentionTargetsByRoles / ListMentionTargetsByUserIDs share listMentionTargets. The matched column is a closed named type (mentionTargetColumn) rather than a bare string, so the value interpolated into the SELECT cannot become caller-supplied. Behaviour is unchanged: every rate-limit key, error code, error string, slog message and slog key is preserved verbatim, including the two "failed to update <kind> state" messages, which are now assembled the same way enableVideoSlot already assembled them. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * refactor(api): extract readEmojiUpload from handleCreateEmoji handleCreateEmoji was 101 lines against a 100-line budget. The upload-bytes stage — pull the file out of the parsed form, cap its size, sniff its MIME type and sniff its dimensions — is the one self-contained block in it, and it already wrote its own refusals, so it moves out whole as readEmojiUpload. The permission-before-parse ordering the doc comment calls out is unchanged; so is every error string. file.Close() now runs when the helper returns rather than when the handler does, which is strictly earlier and unobservable: the bytes are already copied into raw and nothing else touches the handle. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * refactor: extract one cohesive block from three single-budget offenders Each of these was over exactly one budget, so each gets exactly one extraction rather than a restructure: - api/totp_handler.go handleVerifyTOTP (102 lines / 100): the block that resolves the user behind the partial-auth challenge and decrypts their TOTP secret becomes totpChallengeSecret. The ban-inside-the-partial-window check moves with it. - service/message_reactions.go handleReaction (cyclop 21 / 20): the whole authorisation chain — channel lookup, archived gate, DM participant and block checks, non-DM permission check — becomes reactionAudience, which also returns the DM fan-out audience it already resolved. Check order is unchanged and load-bearing. - db/admin_queries.go BackupToSafe (cyclop 21 / 20): the character allowlist loop and the SQL-comment rejection become validateBackupPathChars. That loop alone was most of the branch count. No error string, no check and no ordering changed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * refactor(plugin): split InstallFromZip into staged install helpers 104 statements / cyclomatic 44 / nestif 12. Split along the stages the code already had: installZipExtract (the per-entry write loop, with installZipEntryDest holding the mode/symlink/zip-slip guard chain and installZipWriteEntry the size-capped copy), installZipStagedManifest, installZipPromote, and installZipReactivate for the :399 nested block. Every zip-slip, symlink, entry-mode and uncompressed-size check is preserved in the same order relative to the writes it guards. The 19 inline `cleanup(); return` sites collapse to 4 in the orchestrator, one per stage, because each helper now returns an error instead of unwinding itself — the staging directory is still removed on exactly the same set of failures. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * refactor(api): split newWAFMiddleware into engine build and per-phase helpers 184 lines / cyclomatic 38, and the request-body block at :382 was the worst nested site in the tree at nestif 17. Engine construction moves out of the closure (wafInlineEngine, wafCRSEngine — the Coraza directive string is lifted verbatim), and each request phase becomes its own helper: wafInlineRequestHeaders, wafCRSRequestHeaders (including the Host/Transfer-Encoding re-add for CRS 920280), wafFeedCRSBody and wafInspectRequestBody, which is the old :382 block. The three `handleWAFInterruption(w, it); return` sites inside the body block become one: the helper now returns the interruption and the orchestrator handles it. No statement runs between the two points on either side, so the verdict is honoured identically — in particular a CRS body interruption still returns without replacing r.Body. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * refactor(service): split SendMessage and lift EditMessage's access check SendMessage was 79 statements / cyclomatic 35 with an 11-deep nested attachment block at :101; EditMessage was one point over cyclop. SendMessage becomes sendMessagePrecheck (permission and DM-block gates, content sanitisation), sendMessageLinkAttachments (the :101 block: attachment ownership, claim and link) and sendMessageDMSideEffects. EditMessage gets editMessageCheckAccess and nothing else — one budget over earns one extraction. The sanitizeContent fixpoint and the attachment ownership check are unchanged, as is the order of every gate. The DM side effects run behind `isDM && !s.sendMessageDMSideEffects(...)`, so a non-DM never enters them; inside, only the GetDMParticipantIDs failure returns false, matching the one error the original early-returned on. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * refactor(admin): split handlePatchUser into per-field apply helpers 106 lines / cyclomatic 29, with the ban block at :154 nested 9 deep. Each optional field of the partial edit becomes its own helper — patchUserPrecheck, patchUserAuthorizeRole, patchUserApplyBan (the :154 block, including the session disconnect and the broadcast) and patchUserApplyRole. Each returns a bool meaning "keep going"; none of them writes a success response, so the single response site in the orchestrator is unchanged. Field application order, the permission-cache invalidation on a role change and the disconnect-and-broadcast on a ban are all preserved, as are the three fail-closed `mod == nil` guards, which now sit at the top of their own helper and still fire on exactly the same conditions. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * refactor(admin): split handleSetup into first-run setup stages 143 lines / cyclomatic 30, with the optional-wizard block at :219 sitting exactly on the nestif threshold. Split into the stages the endpoint already had: request gating (rate limit and origin check, which run before any auth exists on a fresh server), owner account creation, and the wizard application that was the :219 block. Every gate in front of the handler is a security control on an unauthenticated endpoint; none moved relative to the work it protects. setup_wizard.go is untouched. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * refactor: split run() into named bootstrap and shutdown steps 131 statements / cyclomatic 57, with the executable-path fallback at :126 nested 9 deep. The five anonymous `defer func(){...}()` blocks become named functions — telemetryStop, runClosePlugins, runStopEventPersistence, runStopAuditWriter, maintenanceStop — and the bootstrap stages move out likewise. Every defer is still registered in run() itself, at the same point in the sequence, so the LIFO teardown order is unchanged; that order is documented in the surrounding comments and is load-bearing (the audit-writer stop must follow database.Close's registration, the event-persistence stop must precede it). runStopEventPersistence is now registered unconditionally with a nil persister meaning "disabled", where the old code registered its defer inside the enabled branch — a no-op occupying that slot cannot change the relative order of the others. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * refactor(ws): split handleReconnect into resume stages 77 statements / cyclomatic 41, plus the replay block at :199 and, in handleFreshConnect, the voice-state restore at :622. handleReconnect becomes reconnectPrecheck, reconnectSelectReplay (with reconnectVetColdTail for the cold-tier gap check), reconnectRegister and reconnectWriteReplay. handleFreshConnect's stale-voice cleanup moves to its own helper, where the `if h.livekit != nil` wrapper becomes a guard clause — that block was the tail of its scope, so returning early and falling off the end are the same. The parts that carry the invariants are moved verbatim: reconnectRegister still takes h.seqMu, still calls registerNow inside that same critical section (BUG-123 / OC-0206), still unlocks on every exit, and still emits the "full" tier counter and telemetry on each of its three re-check failures. handleReconnect's two-boolean contract is unchanged — the collapsed `return false, false` sites are all fall-through-to-full-ready, and the single `return true, false` is still the handshake-write-failure path whose teardown already ran (OC-0051). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs(server): fold in the adversarial review of the complexity refactors Eleven skeptic passes over the refactor commits on this branch found no blocker and no major — behaviour is preserved throughout. They did find comment and accuracy defects worth correcting: - db/mention_queries.go: the mentionTargetColumn rationale claimed the named type made the interpolated column "only ever one of the two constants". A Go named type is not closed, so that is a convention the type makes visible, not one it enforces. Reworded, gosec justification included. - ws/voice_controls.go: the dupl collapse generalised away three specifics — that a server deafen is the moderator's to lift (now on the serverDeafen field), the concrete voice_states.camera / voice_states.screenshare column names, and the half of the OC-0023 rationale about neither stream kind hiding from the other's count. All three restored. - ws/voice_join.go: `maxUsers := ch.VoiceMaxUsers` had been hoisted to the top of voiceJoinComplete, moving a read across the tail supersession guard. The read is inert, but it was the one statement in that commit whose position relative to a security guard changed; it now sits at its use, as before. - ws/*_test.go: three test comments cited voice_join.go line numbers that the split invalidated. They now cite the helper by name instead. - service/message_reactions.go: reactionAudience's doc claimed to enforce "every gate on reacting"; it enforces the channel-scoped ones, and the doc now says which gates stay with the caller. - api/emoji_handler.go: the readEmojiUpload call reused the outer `ok` from the auth check by assignment; it gets its own readOK. - admin/setup_handler.go: a moved comment kept a "the response above" deictic that no longer had a response above it. No behaviour change. Build, vet, full tests and -race on five packages green. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * refactor(ws): clear the remaining complexity budgets across the hub Eight files, thirteen findings. Each function is split at the stages it already had; no branch is reordered, merged or inverted. - handlers.go handleMessage (cyclop 28, 88 stmts): session re-check, frame decode and result application become handleMessageSessionRecheck, handleMessageDecode and handleMessageApply. The V2 constructor lookup -> DispatchV2 -> Result resolution order is untouched. - serve_ready.go buildReady (cyclop 26, 61 stmts): the per-section fetches split out, readyChannelPayloads among them. Every visibility predicate is preserved verbatim — this is the payload that decides what a client may see. - serve_pumps.go writePump (cyclop 31): writePumpWrite, writePumpDeliver, writePumpDrainChannel and writePumpDrainAndClose. Every channel receive stays in the same select statement, so scheduling is unchanged. - hub_sweep.go sweepStaleVoiceStates (cyclop 22, 56 stmts): the staleness predicate, the hub-lock ordering and the position of the race hook are all as they were — handleVoiceJoin's BUG-088 ordering depends on them. - hub_broadcast.go channelReadAudienceImpl and RefreshChannelVisibility (cyclop 22 each, 57 stmts): channelReadAudienceDM and refreshChannelVisibilityCanSend. The audience predicate is the OC-0090 group-DM leak surface, so it is extracted, never simplified. - livekit_webhook.go (nestif 13 and 14): webhookJoinedEnforceVoiceState, webhookLeftCleanupClient and webhookLeftFinishLeave. DB delete still precedes broadcast on every path. - livekit_download.go EnsureLiveKitBinary (52 stmts): one extraction, ensureLiveKitStageBinary, keeping every archive path check intact. - voice_moderation.go (nestif 8): voiceModDeafenRollback. The persisted server_muted flag remains the authority. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * refactor(api): clear the remaining complexity budgets across the HTTP layer - router.go NewRouter (cyclop 28, 84 stmts): split by wiring concern into routerTOTPKey, routerHealthDeps, routerMiddleware, routerUploadRoutes, routerPluginWiring, routerVoiceRoutes and routerMetricsRoutes. Middleware ORDER is a security property (auth before handler, WAF before body parse, rate limit before work) and is unchanged; the returned cleanup func still closes over and releases everything it did before. - auth_handler.go handleRegister (133 lines) and handleLogin (cyclop 21, 152 lines): registerPolicyGate, registerReadRequest, loginReadRequest and loginAuthenticate. The always-compare posture, every rate-limit key, every counter reset and the ban-check-versus-password-compare order are all preserved — including loginUserFailureThreshold staying unscaled by scaledAuthLimit, which is deliberate and commented. - upload_handler.go handleServeFile (cyclop 31, 128 lines): serveFileResolve and serveFileAuthorize. Every header this sets — Content-Disposition included, which is what stops a stored file being served as active content — is still set with the same value in the same circumstances. - profile_handler.go handleUploadAvatar (120 lines): avatarUploadReadImage, mirroring readEmojiUpload in shape but with the avatar caps and MIME set. The two deliberately do not share a helper. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * refactor: clear the last complexity budgets in db and admin - db/account.go DeleteAccount (cyclop 28, 55 stmts): grouped by subsystem into deleteAccountAdminGuard, deleteAccountDMChannels and deleteAccountCloseDMChannels, each taking the same transaction. The transaction boundary, the delete ORDER (which foreign keys depend on) and the rollback path are unchanged. - admin/logstream.go handleLogStream (cyclop 24): logStreamAuthorize. Flush cadence, heartbeat and disconnect detection untouched. - admin/setup_wizard.go validateWizard (cyclop 23): grouped by section into wizardValidateIdentity, wizardValidateNetwork and wizardValidateMedia. Every message and bound is unchanged — this is the first input-validation boundary on a fresh server, before any auth exists. With this the tree is at zero: golangci-lint run reports 0 issues against the budgets set in #1384 (funlen 100/50, cyclop 20, nestif 8, dupl 150), with no //nolint and no exclusion added anywhere. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
7f87be6306 |
chore(lint): set complexity budgets to targets (intentionally red — backlog visible) (#1384)
* chore(lint): add ratcheted complexity budgets Enables funlen, cyclop, nestif and dupl. Each threshold sits just above today's worst offender, so the tree is green now and the budgets only block regression past the current extreme: funlen 320 lines / 135 statements (worst: main.go run, 311/131) cyclop 60 (worst: ws handleVoiceJoin, 59) nestif 18 (worst: 17) dupl 250 tokens (green boundary; 150 flags 3 real pairs) Measured over 1446 production functions with tests excluded. Verified tight rather than slack: 320/135 is green and 310/130 is not. The budgets apply to production code only. Table-driven tests are legitimately long, and duplicated setup between cases is clearer than a helper that hides what each case does. These are a ratchet, not a standard. 94 functions exceed 60 lines and 22 exceed 120; none of them are touched. The settings block records what each budget is waiting on, including the three duplicate pairs that must be collapsed before dupl can drop to the conventional 150. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * chore(lint): set complexity budgets to targets, not to what passes Replaces the ratchet (thresholds parked just above today's worst) with real targets. Existing offenders are left failing rather than excluded: an exclusion list goes stale and quietly becomes permanent, whereas a failing check is a backlog you can see and work off. funlen 100 lines / 50 statements (was 320/135) cyclop 20 (was 60) nestif 8 (was 18) dupl 150 tokens (was 250) These are not the tool defaults (60/40, 10, 4). Those descend from 1976-era cyclomatic-complexity work predating Go's explicit error handling, where every `if err != nil` costs a branch and idiomatic code scores high for no real complexity — which is why golangci-lint's other cyclomatic linter, gocyclo, defaults to 30 rather than 10. The values above are chosen for a Go server. Also disables three output limits that hide work. uniq-by-line is the sharp one: it keeps one issue per line, and because cyclop and funlen both anchor at the function declaration, enabling cyclop silently swallowed 16 of funlen's 21 findings. The visible backlog was 46; the real one is 62. This leaves the lint gate RED by design. No other linter regressed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
36be31db43 |
fix: 11 defects from bughunt sweep across server, db and client (#1382)
* fix(ws): 1 defect(s) (OC-0001) * fix(db): 1 defect(s) (OC-0002) sanitizeFTSQuery filtered only characters, so FTS5's bareword boolean keywords (AND, OR, NOT) reached MATCH as operators; a query in an invalid operator position raised "fts5: syntax error" instead of returning results. Drop those bareword tokens after sanitizing. * fix(dm): 1 defect(s) (OC-0004) * fix(ws): 1 defect(s) (OC-0005) * fix(voice): 1 defect(s) (OC-0006) Count the shared voice_max_video budget in streams rather than rows: a single user publishing both camera and screenshare consumed one slot while producing two live streams, letting a channel over-admit up to 2N streams against an N-stream cap. * fix(client): 2 defect(s) (OC-0007, OC-0009) OC-0007: mark the active channel loading before invalidating its message window on a full-ready resync, so MessageList shows the spinner instead of the empty-channel state for the duration of the refetch. OC-0009: fan USER_UPDATE renames out to voiceStore.voiceUsers, which keeps its own frozen username copy, so the voice roster no longer shows a stale name for the rest of the call. * fix(admin): 1 defect(s) (OC-0010) * fix(identity): 1 defect(s) (OC-0011) * fix(ws): 1 defect(s) (OC-0003) The public half of an invisible user's presence (PresenceOthersEvent, and BroadcastPresence's own mapped payload) went out via broadcastExcludeLow on the low-priority queue - the ephemeral, unsequenced, drop-on-overflow transport built for typing indicators - while every other source of the same user's presence shares the normal-priority queue. That split one user's presence across two per-client FIFOs with different durability and different drain order (writePump drains normal strictly before low), so a frame could land out of order against a later connect/disconnect presence frame, or be silently dropped with no replay recovery. Adds Hub.BroadcastToAllExcept, which routes through the same h.broadcast channel and seqMu-serialized deliverBroadcast as BroadcastToAll, carrying an excludeUserID that deliverBroadcast applies via pubsub.Publish(TopicGlobal, msg, excludeUserID). * fix(ws): 1 defect(s) (OC-0008) --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
d6c768cb90 |
feat(invariants): add server invariant rules and close five deadlock blind spots (#1383)
* feat(invariants): add the invariant-rule harness and the syncutil-locks rule * fix(ws,service): route the last five raw mutexes through syncutil The -tags deadlock CI pass only observes locks declared via syncutil, whose Mutex/RWMutex are build-tag aliases. These five were declared as raw sync types and were invisible to it, including the hub voice key-holder lock and the permission and role caches. TestServerInvariants now gates the tree against regressions. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(invariants): walk the tree through os.Root to close a symlink TOCTOU gosec G122: reading a filepath.WalkDir-supplied path is race-prone, since a symlink swapped between the walk and the read escapes the intended tree. os.Root confines every read to the root and cannot be traversed out of. Walking the root's fs.FS also yields slash-separated paths already relative to it, so the filepath.Rel and ToSlash conversion is no longer needed. * fix(invariants): close syncutil-locks evasions, isolate per-rule tests, harden the gate - I1: TestServerInvariants now asserts every registered Rule.Scope directory exists and holds at least one non-test .go file, so the gate cannot pass by scanning nothing. - I2: split CheckSource into a thin wrapper over an unexported checkSourceWith(rules, ...), so TestSyncutilLocks tests the syncutil-locks rule in isolation instead of the whole registry. - I3: broaden checkSyncutilLocks to a single SelectorExpr match (any sync.Mutex/sync.RWMutex reference bound via f.Imports, aliases included) instead of only *ast.Field/*ast.ValueSpec. Catches := composite literals, untyped var specs, type aliases, and []sync.Mutex/map[K]sync.Mutex, none of which the old rule saw. A dot-import of "sync" is now its own violation, since it would otherwise let a bare Mutex evade the selector match entirely. - M2: suppression now keys off the violation's own Rule id (allowed[v.Line][v.Rule]) rather than the running rule's ID, so a rule that ever emits a sub-id isn't silently unsuppressible. - M3: Run sorts with sort.SliceStable, since an unreasoned allow comment and the violation it fails to suppress can share a file:line. - M4/M5/M1-partial: add a build-tag-gated fixture test, document that allow comments must be same-line, and correct the skipDirs comment to describe both the generated-code and gitignored-runtime-dir cases it actually covers. All ten original TestSyncutilLocks subtests pass unchanged; six new subtests cover the evasions above. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs(server): point at the syncutil-locks invariant gate Server/CLAUDE.md told developers not to hand-roll around syncutil but never said it's enforced. Note that Server/invariants/ checks it at go test time and that exceptions are greppable via grep -rn "invariant:allow" Server/. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
150c6c42f4 |
chore: track the graphify knowledge graph and the bug-hunt ledger (#1381)
* chore: track the graphify knowledge graph and the bug-hunt ledger Both were local-only, so a clone — including a cloud session, which sees only tracked files — started with no graph and no findings history. graphify-out/: the top-level built graph is now tracked so a fresh clone can query it without a rebuild. Subdirectories stay ignored: cache/ is a per-machine AST cache, and graphify parks the previous graph in a dated YYYY-MM-DD/ backup on every rebuild (18 MB of stale duplicate, a local rollback aid rather than shared state). .gitattributes marks the tree -text: the repo-wide `* text=auto eol=lf` rule would otherwise rewrite line endings inside .graphify_labels.json.sig, which signs the labels byte-for-byte, and invalidate the signature on checkout. graph.json/graph.html also get -diff, and the tree is linguist-generated so it stays out of language stats and collapses in review. .superpowers/: findings-ledger.json, its FINDINGS.md render and render-ledger.mjs are tracked so contributors can add findings by PR. Hunt transcripts, .bak snapshots and debris patches remain per-session scratch. Tradeoff accepted deliberately: the post-commit rebuild hook rewrites graph.json, so each refresh writes a fresh ~18 MB blob into history. Refresh it in its own commit rather than folding it into an unrelated diff. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * chore(graphify): refresh the graph over the newly-tracked files The first commit added findings-ledger.json, FINDINGS.md and render-ledger.mjs to the tracked tree, so the post-commit rebuild picked them up and rewrote the graph. Also ignores .pending_changes, the transient rebuild-state file. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * chore(graphify): share the PreToolUse graph-first nudge hooks The two hook-guard hooks lived in the gitignored settings.local.json with an absolute C:/Users path, so no other clone got them. Portable form: bare `graphify` off PATH, and `|| exit 0` so a contributor without graphify installed is never blocked. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Update graph output files and manifest - Updated binary files: graph.html and graph.json with new content. - Added new entry for findings-ledger.json in manifest.json with updated metadata. --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
6a26f2a839 |
fix(server): drain fully before the self-update/restore restart handoff (#1380)
* feat(server): supervisor detection and server.restart_mode config key RunningUnderSupervisor detects systemd (INVOCATION_ID) and, best-effort, NSSM (NSSM_SERVICE_NAME — 2.24 does not set it, so NSSM deployments set the mode explicitly). server.restart_mode (auto|spawn|supervised, default auto, env OWNCORD_SERVER_RESTART_MODE) selects how a self-restart hands off after the server drains: exit for the supervisor to relaunch, or spawn the replacement directly. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ngzj2Rx9UGC35uLHAfErMp * fix(server): make the self-restart handoff drain fully before starting the successor The update/restore/wizard restart previously spawned the replacement while the old server was still serving, then SIGTERMed itself and hard-exited after 10s. That design failed in every documented deployment mode: under the shipped systemd unit the spawned child (same cgroup) was killed when the old main process exited and Restart=on-failure never relaunched a clean exit; on Windows the self-SIGTERM is unsupported and silently dropped, so graceful shutdown never ran — hub.GracefulStop (the only caller of LiveKitProcess.Stop) was skipped, orphaning livekit-server on TCP 7880/UDP 50000-60000 and dropping queued event/audit batches; and NSSM's relaunch raced the self-spawned replacement for the database lock. Admin handlers now perform only the on-disk swap and request a restart through an injected hook (admin.SetRestartHandoff). The main package's restart coordinator cancels the parent of run()'s signal.NotifyContext — the exact drain a SIGTERM triggers, on every platform — and after run() has fully torn down (listeners closed, hub and LiveKit stopped, queues flushed, DB closed and its lock released) main() performs the handoff: spawn the replacement in spawn mode, or exit 0 for the supervisor in supervised mode. A 90s backstop force-exits a wedged teardown; the DB-lock and bind retries demote to safety nets. A three-state guard (idle/busy/restart-pending) serializes update apply, backup restore, and setup-wizard restarts against each other: concurrent applies no longer race the same staged .new file or broadcast a spurious update_aborted, and conflicting requests get 409 UPDATE_IN_PROGRESS / RESTART_PENDING. The swap being free of process side effects also makes the apply success path unit-testable for the first time. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ngzj2Rx9UGC35uLHAfErMp * fix(server): errno-based bind-conflict detection, ACME bind retry, LiveKit Pdeathsig isAddrInUse now unwraps to the platform errno (EADDRINUSE; WSAEADDRINUSE 10048 on Windows) with the English strings kept only as fallback — the string-only match never fired on localized Windows, silently disabling the bind retry. The retry loop is extracted into serveWithBindRetry and now also covers the ACME :80 challenge server, which previously gave up on first conflict and stayed dead (breaking HTTP-01 renewals) until the next restart. The .old-binary boot cleanup retries briefly for the window where a spawn-mode predecessor has not fully exited. The companion livekit-server gets Pdeathsig SIGKILL on Linux so a parent killed without teardown (kill -9, OOM, backstop exit) cannot orphan it with the voice ports held. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ngzj2Rx9UGC35uLHAfErMp * docs(deploy): Restart=always unit and per-supervisor restart-mode guidance Restart=always is what lets the deliberate clean exit after a self-update/restore relaunch under systemd (systemctl stop is never auto-restarted; failure exits behave as before). Deployment docs gain the required NSSM AppEnvironmentExtra line, the Task Scheduler and Docker restart-policy notes, and the new drain-then-handoff update flow. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ngzj2Rx9UGC35uLHAfErMp --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
a366160dc8 |
test(ws): bound the load test's unregister settle by overallTimeout, not 5s (#1379)
TestTheLoadTest failed on the post-merge main run's windows-latest -race leg (job 95067154071) with "timed out after 5s waiting for churned clients to fully unregister" — every worker had finished, the hub was still running, and the goleak dump that followed was only the anchor drainers the t.Fatal skipped stopping. The runner was simply slow: the ws package took 276s against 159s on the identical tree an hour earlier, db and service ran 13-24% slower too, and Unregister drains asynchronously behind the hub loop's remaining broadcast work. Bound the settle wait by the test's own overallTimeout (90s), the same "only a genuine hang takes this long" limit the workers use. waitFor returns as soon as ClientCount matches, so a healthy run pays nothing — locally under -race the whole test still finishes in ~9s. Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
fb04a579c4 |
fix(docker): ship /app owned by the runtime uid; run the boot-smoke in CI too (#1378)
* fix(release): give the Docker boot-smoke a writable /app, and run it in CI The v1.2.0-alpha.3 release run died at "Boot-smoke Docker image": a bare `docker run` of the distroless image has nowhere the uid-65532 server can write — /app is root-owned, and the VOLUME /app/data anonymous volume is created root-owned too — so config.Load failed on "writing default config: open config.yaml: permission denied" and the container exited. Real deployments bind-mount config.yaml and data/, which is why the image itself is fine. Move the smoke into Server/scripts/docker-smoke.sh, run the container with `--tmpfs /app --tmpfs /app/data` (Docker's tmpfs default mode is 1777, so the non-root server can write both), and call the same script from ci.yml's docker-build job — loading the image it already builds — so the smoke is exercised on every PR to main instead of for the first time at tag time. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(docker): ship /app and /app/data owned by the runtime uid so a bare run boots The tmpfs approach did not survive CI: runc re-applies the underlying directory's mode to a tmpfs mounted over an existing path, so /app stayed root:755 and the write still failed. Fix the image instead of the harness: stage /app/data in the builder, chown it to 65532, COPY --chown it into the distroless stage before WORKDIR. Docker seeds the VOLUME's anonymous volume from that image dir, ownership included, so `docker run <image>` with no mounts now boots and answers /health — which is also the contract the smoke should be testing, so it goes back to a bare `docker run`. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>v1.2.0-alpha.3 |
||
|
|
fb4329dd94 |
release: v1.2.0-alpha.3 (#1377)
Bump the client version in package.json (+lock), tauri.conf.json and Cargo.toml (+lock) so release.yml's verify-versions gate passes and deployed clients see the update; refresh the literal version in the README and docs build examples; add the curated CHANGELOG entry covering the 199 verified defects fixed since v1.2.0-alpha.2 (#1366-#1375), the observability/backup/deployment hardening in #1376, migration 031, and the new config keys. Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
f5faf82a60 |
infra: observability, backups, guardrails, and deployment hardening (#1376)
* docs: add infrastructure roadmap plan Records the verified recommendations from an infrastructure review in three tracks: raising the single-instance ceiling, cheap seams for a possible multi-instance future, and ops hygiene. Includes explicit anti-recommendations and sequencing. Security-sensitive detail is intentionally excluded per docs/security.md. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017RtDNHSYWwPKArL8MsRdbj * feat(server): real health checks and saturation metrics /api/v1/metrics now exposes signals that were already computed in memory but never surfaced: reconnect replay tier hits, event-persister counters, SQLite writer-pool wait stats, aggregate per-client backpressure counters (including previously invisible low-priority drops), and permission-cache hit/miss. /health now returns a real verdict: hub dispatch-loop liveness, a bounded database ping, and a free-disk check, returning 503 with a subsystem reason when degraded. Checks are cached so the unauthenticated endpoint cannot amplify load. The hub's panic breaker now exits the process so a supervisor can restart it, instead of leaving broadcast delivery silently dead while clients still appear online. OTel instruments that were declared but never recorded are now wired (ws_active_connections, ws_broadcast_latency_seconds, ws_messages_total, ws_events_dropped_total, voice gauges) or removed (db_query_duration_seconds). Also corrects the docs/api.md description of broadcast_drops, which counts hub-queue overflow, not client send-queue overflow. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017RtDNHSYWwPKArL8MsRdbj * feat(server): implement scheduled backups, retention, and backup verification The backup_schedule and backup_retention settings have existed in the admin panel and API since the initial schema but were never read by any code. The 15-minute maintenance loop now enforces them: a scheduled backup is taken when the newest backup on disk is older than the schedule interval (manual backups reset the clock), and retention prunes backups older than the configured days while always keeping the newest one. Backups are now verified with PRAGMA integrity_check immediately after VACUUM INTO (a failed backup is removed rather than listed as restorable) and again before a restore may overwrite the live database. A failed VACUUM INTO also cleans up its partial output file — but never a pre-existing one. The backup directory is configurable via a new backup.dir key (default data/backups) so operators can point backups at another disk or an off-host mount, mirroring the SetDatabasePath plumb. Restore-handler tests now use real SQLite fixtures (the integrity gate correctly refuses text files) with the mid-copy failure injected through a test-only copy hook. Also adds audited gosec suppressions to the Windows disk-free syscall added in the previous commit, which the Windows lint leg flagged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017RtDNHSYWwPKArL8MsRdbj * feat(server): capacity and failure-mode guardrails - server.max_ws_connections: optional cap on concurrent WebSocket clients, checked before the upgrade with a 503 + Retry-After; rejections are counted and exposed as ws_conn_rejects in /api/v1/metrics. - Single-process database lock: an OS-level advisory lock (flock / exclusive handle) beside the SQLite file makes a second server process fail fast with a clear message instead of silently fighting the first over process-local state. A bounded retry covers the self-update/restore restart handoff, and the lock mechanism failing (e.g. network filesystems) only warns. - Disk-space awareness: boot-time warnings for the data and backup volumes, plus a disk_free_mb metrics field, via a small cross-platform diskutil package (already used by /health). - Upload storage failures: storage.Save now marks server-side filesystem failures with a sentinel (storage.ErrIO); handlers return 507 for those instead of blaming the client with a 400, and the emoji route stops echoing raw storage errors (which embed absolute paths) into responses. - Unknown config keys now warn at startup — a typo like admin_alowed_cidrs previously kept the default silently while the operator believed the setting changed. Never fatal: newer servers tolerate older configs. - Admin settings honesty: the three stored-but-inert settings (server_icon, max_upload_bytes, voice_quality) are shown read-only with a note pointing at the real config.yaml keys, instead of pretending to apply. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017RtDNHSYWwPKArL8MsRdbj * perf(db): write-path efficiency and capacity knobs - channel_focus/mark_read now skip the read-state UPSERT when the stored row already matches (same last_message_id, no mentions) — refocus events fire at up to 10/s/user and every no-op write still occupied the single SQLite writer connection. The extra existence check runs on the reader pool, which doesn't serialize. Same shape as the session-touch throttle. - DeleteExpiredSessions is now sargable: migration 031 normalizes legacy expiry formats to the RFC3339-Z layout the server writes and indexes expires_at, replacing the strftime full-table scan that ran on the writer every 15 minutes. - Boot-time ANALYZE runs only when a migration actually applied; unchanged schemas get the cheap PRAGMA optimize instead (which also covers crash-restarts that never reached the shutdown optimize). - The read/write SQL router gets a table-driven test with explicit expected values (INSERT ... RETURNING must hit the writer despite being :one). - New knobs, all defaulting to current behavior: database.max_readers, security.auth_rate_limit_multiplier (for shared-NAT communities), event_persistence.replay_ring_size and replay_cold_limit. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017RtDNHSYWwPKArL8MsRdbj * fix(server): shutdown lifecycle ordering - The event pruner and maintenance loop are now joined (bounded) before the database closes: bgCtx cancellation used to run AFTER database.Close via LIFO defers, contradicting its own comment, and neither goroutine was ever waited on — a mid-tick scheduled backup or prune could still hold the writer while the pool tore down. StartEventPruner returns a done channel with the same join contract EventPersister.Stop already had. - srv.Shutdown now runs before hub.GracefulStop, so in-flight HTTP handlers' broadcasts still reach a live hub and the event persister instead of vanishing from the replay/event store across a restart. Shutdown does not wait on hijacked WebSocket connections, so the swap adds no delay. - GracefulStopContext threads the 30s shutdown budget into the hub: the 5s client-notice window (matching the countdown clients are shown) ends early when the budget expires, and is skipped entirely when nobody is connected — early-return startup paths and idle servers no longer sleep 5s for an audience of zero. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017RtDNHSYWwPKArL8MsRdbj * build(deploy): systemd unit, compose hardening, boot-smoked releases, CI polish - deploy/owncord.service: hardened systemd unit template with the two verified caveats encoded (install dir stays writable for self-update under ProtectSystem=strict; CAP_NET_BIND_SERVICE for ACME's :80), plus a 'Linux (systemd)' deployment docs section — the Linux service story was previously 'Docker or nothing'. - New 'Reverse Proxy Topology' docs section with a working nginx snippet and the correct signaling-vs-media distinction: /livekit/* is already proxied by the server, only WebRTC media ports must be directly reachable. - docker-compose: log rotation, commented resource limits, and a healthcheck backed by a new 'chatserver healthcheck' subcommand (the distroless image has no shell) that probes /health without config side effects. - release.yml: a concurrency group (queue, never cancel), and boot-smoke gates — the freshly built server binaries and the Docker image are cold booted and probed healthy BEFORE anything is signed or pushed. The release feed drives signed self-updates, so a binary that compiles but dies on boot previously would have shipped itself to every auto-updating instance. - ci.yml: client-check/client-tests move to ubuntu with the reasoning recorded (no win32 code paths, LF enforced repo-wide); admin-e2e gets a written graduation criterion instead of an open-ended non-blocking status. - docs: Tailscale guide notes the CGNAT range vs the default admin CIDRs; architecture overview records presence/voice state as the fifth single-instance blocker and the macOS client scope decision. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017RtDNHSYWwPKArL8MsRdbj * perf(server): measured load tooling, narrowed invalidation, presence coalescing, storage and CIDR seams - Fix scripts/k6/ws-load.js against the real wire protocol: envelope-wrapped frames, correct message types (typing_start, presence_update), the correct /api/v1/ws path, and thresholds that fail a run where nobody authenticated or went ready — the script had drifted to pre-envelope framing and reported 100% green while every auth failed on the first frame. A new workflow_dispatch-only load-baseline workflow boots a real server, seeds users through the setup/invite APIs, runs the script, and uploads the k6 summary plus a metrics snapshot for before/after comparison. - Role-scoped channel-override changes now evict only the affected role's members from the permission cache (fail-safe: unreadable member list still flushes everything). InvalidateAll here repopulated every connected user — two reads each — synchronously inside the admin request via RefreshChannelVisibility, a stampede that scaled with total population rather than the role's size. Same pattern the per-user override endpoints already used. - Connect/disconnect presence broadcasts now pass through a 300ms latest-wins coalescer (QueuePresence): each un-coalesced presence change is a sequenced global broadcast (an O(clients) fan-out under seqMu), so a reconnect storm fired O(users) of them from the connect critical path. A flap inside the window collapses to its final state; the wire format, seq ordering, and replay behaviour are unchanged, and the delivery path (BroadcastPresence) is untouched. - Storage seam: api handlers now consume a FileStore interface (consumer-side, same pattern as service.Store) with Open returning a seekable storage.File — writing down the contract (range-request seeks included) an alternative backend would have to meet, without building one. - The metrics surfaces and the LiveKit webhook/health endpoints get their own allowlist keys (metrics_allowed_cidrs, livekit_webhook_allowed_cidrs, both defaulting to admin_allowed_cidrs), so a central Prometheus scraper or an externally-hosted LiveKit no longer requires widening the admin panel's perimeter. Startup now also warns when admin_allowed_cidrs is customized while trusted_proxies is empty — behind a proxy or container network the check would otherwise compare the proxy's private address, not the client's. - The container healthcheck probe now PINS the server's own certificate from disk (VerifyConnection, exact-match) instead of skipping TLS verification, addressing the CodeQL finding on the previous commit; WebPKI verification is used when no local cert exists (ACME). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017RtDNHSYWwPKArL8MsRdbj * fix(server): address self-review findings on the hardening branch Seven fixes from a high-effort review of the full branch diff: - healthcheck CLI now works under tls.mode acme: it overrides ServerName with the configured domain for WebPKI verification instead of pinning a cert that doesn't exist (or is stale) in that mode. Previously an ACME deployment's container healthcheck failed forever. - /health pings the READER pool (new db.PingRead): the writer ping queued behind a scheduled backup's VACUUM INTO and reported the server degraded for the whole backup — which an autoheal watchdog would turn into a nightly mid-backup restart. - /health runs its cached checks under context.WithoutCancel so a probe that disconnects mid-request cannot poison the shared cache with a false degraded verdict for the next 5 seconds. - The token CLI uses a new db.OpenShared that skips the single-process lock: minting a token against a running server is safe under WAL and was a documented workflow the lock had broken. - The per-user TOTP failure cap is no longer scaled by security.auth_rate_limit_multiplier — that knob exists for per-IP limits; scaling the only cross-IP brute-force defence multiplied an attacker's distributed guess budget. Mirrors the unscaled per-user login threshold. - A direct presence_update now drops the user's queued entry in the connect/disconnect coalescer, so a stale connect-time presence can no longer flush 300ms later over the user's fresher chosen status. - The scheduled-backup filename collision loop breaks on any stat error and bounds its suffix probing, instead of spinning the maintenance goroutine forever on a persistent EACCES. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017RtDNHSYWwPKArL8MsRdbj * test(admin): real SQLite fixture for the merged Close-failure restore test TestHandleRestoreBackup_RestartsWhenCloseFails arrived from main (#1375) with a plain-text backup fixture; this branch's restore handler verifies backups with integrity_check before touching the live database, so the text fixture was (correctly) refused with 400 before the Close-failure branch under test was reached. Use a real backup via BackupToSafe, matching the other restore tests. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017RtDNHSYWwPKArL8MsRdbj --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
ea0430c5b0 |
fix: batch of correctness fixes across server and client (#1375)
* fix(client): 1 defect(s) (OC-0201)
* fix(service): 1 defect(s) (OC-0202)
HandleTyping built the per-user-per-channel rate-limit key before resolving the channel or checking read permission, so forged channel ids could pin unbounded dead entries in the shared process-wide RateLimiter.
* fix(client): 2 defect(s) (OC-0203, OC-0224)
* fix(server): 1 defect(s) (OC-0204)
* fix(ws): 2 defect(s) (OC-0205, OC-0211)
* fix(admin): 2 defect(s) (OC-0209, OC-0212)
* fix(client): 1 defect(s) (OC-0210)
* fix(db): 1 defect(s) (OC-0213)
* fix(ws): 1 defect(s) (OC-0214)
Route handler-driven PresenceEvent through BroadcastToAll instead of BroadcastToAllLow so every source of a user's presence shares one ordered per-client FIFO.
* fix(admin): 1 defect(s) (OC-0215)
PATCH /users/{id} combining banned + role_id committed and broadcast the ban before authorizing the role change, so a refused role change returned an error while leaving the target banned. Authorize the role change up front via the new ModerationService.AuthorizeRoleChange.
* fix(db): 1 defect(s) (OC-0216)
LinkAttachmentsToMessage no longer claims an attachment that is a user's live avatar (users.avatar points at it). Once message_id is set, handleServeFile's avatar branch (gated on ChannelID == nil) is unreachable and the file falls under the message's channel ACL / soft-delete state, permanently disagreeing with users.avatar about who may read it.
* fix(emoji): 1 defect(s) (OC-0217)
* fix(client): 1 defect(s) (OC-0218)
The data-copy phase of an HTTP proxy tunnel was unbounded. Steps 1-2 of
handle_connection (header read, TCP connect, TLS handshake) each run under
a 10s guard, but step 3 called io::copy_bidirectional with no deadline. A
remote that completes the TLS handshake and then neither responds nor
closes parks the spawned connection task, the loopback socket and the
remote TLS session indefinitely: copy_bidirectional only resolves once
BOTH directions finish, so closing the local side alone does not free it.
Wrap the copy in copy_with_deadline, a generic helper bounded by
DATA_PHASE_TIMEOUT (600s). The bound is deliberately far looser than the
10s setup guards because this phase carries the REST body, including
attachment and avatar uploads, so it must reclaim only genuinely stuck
connections rather than merely slow ones. The helper is generic over the
stream types so it can be exercised without a live TLS connection.
Regression test drives two in-memory duplex pairs whose far ends stay
alive, so neither half ever observes EOF and raw copy_bidirectional would
block forever; the test asserts the call resolves on its own deadline with
ErrorKind::TimedOut.
Claude-Session: https://claude.ai/code/session_01ENMDTh8gDLiHCaRFdMYRiL
* fix(ws): 1 defect(s) (OC-0219)
* fix(client): 1 defect(s) (OC-0221)
UpdateNotifier scheduled its deferred update check with a setTimeout whose
handle was never retained, so destroy() could not cancel it. A component torn
down inside the 3s window (page swap / logout) still fired performCheck() and
issued a network update check against the old server URL. Retain the timer
handle and clear it in destroy().
* fix(dm): 1 defect(s) (OC-0222)
* fix(client): 1 defect(s) (OC-0223)
* fix(voice): 1 defect(s) (OC-0225)
The Grant-Microphone retry's .finally hardcoded grantMicBtn.disabled = false, undoing updateFrozen()'s socket-down freeze when the WS socket dropped while the mic permission request was in flight. Delegate the state back to render().
* fix(admin): 1 defect(s) (OC-0226)
handleApplyUpdate broadcasts a 'restarting in 5s' notice before the on-disk
swap. Every failure path in the swap returned silently, leaving clients
counting down to a restart that never happened. Extract the swap into
applyStagedUpdate and send a corrective 'update_aborted' broadcast from a
deferred guard on every path that does not reach the respawn.
* fix(admin): 1 defect(s) (OC-0227)
PATCH /channels/{id} accepted a blank or whitespace-only name, leaving the
channel unidentifiable in clients. updateChannelRequest.validate() now
rejects it the way handleCreateChannel already did.
* fix(identity): 1 defect(s) (OC-0228)
* fix(admin): run deferred cleanup before the update restart exits
The fix batch left three golangci-lint findings and two prettier findings
that CI gates on.
applyStagedUpdate called os.Exit(0) in the same function that defers both
staged.Close() and the corrective "update_aborted" broadcast, so neither
ran (gocritic exitAfterDefer). Return a bool instead and let the caller
exit once those defers have run — on Windows, releasing the staged binary's
file handle is the reason the restart exists at all, so this is a real fix
rather than a lint appeasement. The exported test hook calls the function as
a statement, so the added result does not affect it.
Also modernize a bulk-insert loop to range-over-int, compare backup bytes
with bytes.Equal, and reflow two test files to prettier's output.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ENMDTh8gDLiHCaRFdMYRiL
* test(ws): pin the live presence path against the invisible custom-status leak
OC-0207 and OC-0211 are the same defect at two emitters: hub_broadcast.go's
BroadcastPresence (connect/reconnect) and event.go's presenceEvents (live
presence_update). The fix for OC-0211 closed both sites in one change, but
only the hub_broadcast side got a regression test.
This pins the event.go sibling: an invisible user's real custom status must
be blanked on the PresenceOthersEvent frame while the owner's own
PresenceSelfEvent still carries it. Without it, a later change could reopen
the live path while the committed test kept passing.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ENMDTh8gDLiHCaRFdMYRiL
* fix(ws): 1 defect(s) (OC-0206)
* test(ws): silence a contextcheck false positive in the reconnect race test
RefreshChannelVisibility takes no context by design — it is reached through
the admin HubBroadcaster interface, which carries none, so it builds its own
internally. contextcheck flags the call only because the test closure around
it holds a ctx for its override write, so there is nothing to propagate.
Suppress at the call site rather than widen a production interface (and its
mocks) to satisfy a lint in a test.
golangci-lint v2.11.3 (the version ci.yml pins) now reports 0 issues.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ENMDTh8gDLiHCaRFdMYRiL
---------
Co-authored-by: Claude <noreply@anthropic.com>
|
||
|
|
b8b7a2a1f9 |
fix: correctness fixes across LiveKit voice, client session and transport paths (#1374)
* fix: enhance bugfix workflow documentation with detailed clustering and staging instructions * fix(voice): 6 defect(s) (OC-0001, OC-0006, OC-0009, OC-0010, OC-0015, OC-0029) * fix(voice): 1 defect(s) (OC-0005) * fix(client): 1 defect(s) (OC-0007) * fix(client): 1 defect(s) (OC-0011) * fix(client): 1 defect(s) (OC-0012) * fix(admin): 1 defect(s) (OC-0013) * fix(client): 3 defect(s) (OC-0014, OC-0024, OC-0031) * fix(voice): 1 defect(s) (OC-0018) * fix(voice): 1 defect(s) (OC-0019) * fix(client): 1 defect(s) (OC-0021) * fix(client): 1 defect(s) (OC-0025) * fix(ws): 1 defect(s) (OC-0026) * fix(client): 1 defect(s) (OC-0027) * fix(client): 1 defect(s) (OC-0028) * fix(identity): 1 defect(s) (OC-0030) * fix(voice): 1 defect(s) (OC-0016) * fix(client): 2 defect(s) (OC-0002, OC-0020) OC-0002: chain offer handling behind the announce chain so an offer that arrives immediately behind its sender's announce is not dropped as an unknown peer. OC-0020: retire a departing peer's ECDH key on participant-left so a replayed pre-leave announce cannot overwrite the fresh key they rejoined with. * fix(voice): 1 defect(s) (OC-0008) handleVoiceJoin handed the client its LiveKit token before checking whether the join had been superseded by a concurrent eviction (moderator kick/move, the CONNECT_VOICE revocation sweep, CleanupVoiceForChannel). Those evictors delete the voice_states row, clear the client's in-memory state, and call RemoveParticipant — which no-ops because the join has not reached the SFU yet. The client was left holding a live 5-minute RoomJoin credential for a membership the server had just torn down. Re-check the client's voice state immediately after GenerateToken and withhold the credential if the join was superseded, with a best-effort RemoveParticipant to match every other eviction path. * fix(ws): 2 defect(s) (OC-0017, OC-0022) OC-0017: sweepStaleVoiceStates re-checks the live client immediately before deleting a snapshotted-stale voice_states row. voice_join commits the row before calling c.setVoiceState, so a join that lands inside that window was snapshotted as a ghost and had its just-committed row deleted, leaving the client in voice in memory with no DB row. OC-0022: CleanupVoiceForChannel resolves its voice_leave audience with a variant of channelReadAudience that skips the archived short-circuit. Both production callers archive the channel before evicting, so the plain resolver always returned an empty audience and only the evicted participants learned the call ended. * fix(voice): 1 defect(s) (OC-0023) Camera and screenshare now draw from the same per-channel voice_max_video budget. handleVoiceScreenshareV2 performed no cap check at all, and the camera gate's slot-count subquery counted only `camera = 1` rows, so a screensharing occupant was invisible to it. Both gates now count `camera = 1 OR screenshare = 1` via a shared enableVideoSlot helper. * fix(client): 2 defect(s) (OC-0032, OC-0033) OC-0033: voice_disconnected staleness guard swallowed the kick toast when the sibling voice_leave had already cleared currentChannelId. Treat a cleared store as not-stale. OC-0032: VIDEO_LIMIT rollback assumed the camera, tearing down a working camera and leaving refused screen tracks published. Correlate by envelope id and roll back the kind that was actually refused. * fix(voice): 1 defect(s) (OC-0034) * fix(client): 1 defect(s) (OC-0035) A superseded video-enable id makes rollbackPendingVideo return undefined. The dispatcher's ternary treated undefined as "not screen" and called disableCamera(), tearing down a working camera the user never touched. Return early instead: undefined means there is nothing to roll back. * fix(voice): 1 defect(s) (OC-0036) --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
079f59d06d |
fix(workflows): drop hardcoded absolute repo path from bughunt prompts (#1373)
* fix: enhance bugfix workflow documentation with detailed clustering and staging instructions * fix(workflows): drop hardcoded absolute repo path from bughunt prompts The bughunt and bughunt-fix agent prompts told every finder, verifier, fix and prove agent that the repo lives at a specific absolute path from one contributor's machine. Anywhere else - a cloud session, CI, another checkout - that path does not exist, and the churn recon agent ran `git -C <that path> log ...` outright, so the most-churned-files inventory came back empty and every finder prompt lost its churn context. Point the prompts at the agent's working directory instead, which is the repo root on every platform. Both harnesses pass (bughunt.harness.mjs, bughunt-fix.harness.mjs). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ENMDTh8gDLiHCaRFdMYRiL --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
8787b9066d |
fix: batch of 34 correctness fixes across server and client (#1372)
* fix(client): 3 defect(s) (OC-0037, OC-0063, OC-0116) Route the tray Status submenu through saveUserStatus() (mapping the legacy "offline" to "invisible") so notifications, autoIdle, and reconnect presence restore all agree with the tray's choice; build the connected overlay from the auth_ok payload instead of a pre-dispatch authStore snapshot; keep the TOTP overlay open across a rejected verify (totpPending latch) and retain the partial token for the retry instead of clearing it in finally. Hand-applied combined cluster preserved from the previous fix run's overlap-guard block (both clusters edit main.ts). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(voice): 2 defect(s) (OC-0010, OC-0011) * fix(ws): 1 defect(s) (OC-0050) * fix(db): 1 defect(s) (OC-0052) * fix(client): 1 defect(s) (OC-0054) * fix(client): 1 defect(s) (OC-0059) * fix(auth): 1 defect(s) (OC-0061) * fix(ws): 1 defect(s) (OC-0062) * fix(client): 1 defect(s) (OC-0064) * fix(service): 1 defect(s) (OC-0070) * fix(ws): 1 defect(s) (OC-0073) * fix(service): 2 defect(s) (OC-0075, OC-0120) * fix(admin): 1 defect(s) (OC-0076) * fix(voice): 1 defect(s) (OC-0084) * fix(client): 2 defect(s) (OC-0085, OC-0094) Scope collapsed-category persistence to the connected host instead of the server display name, and stop the DM back button from jumping to the first text channel when DM mode was entered without recording channelBeforeDm. * fix(service): 1 defect(s) (OC-0087) * fix(client): 1 defect(s) (OC-0089) * fix(ws): 1 defect(s) (OC-0091) * fix(api): 1 defect(s) (OC-0093) * fix(identity): 1 defect(s) (OC-0118) * fix(dm): 1 defect(s) (OC-0119) * fix(voice): 1 defect(s) (OC-0135) * fix(api): 1 defect(s) (OC-0137) * fix(client): 1 defect(s) (OC-0142) * fix(client): 1 defect(s) (OC-0144) * fix(admin): 1 defect(s) (OC-0145) * fix(updater): 1 defect(s) (OC-0146) * fix(client): 1 defect(s) (OC-0150) * fix(mentions): 1 defect(s) (OC-0131) --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
7be9ccd2f9 |
fix: batch of 22 correctness fixes across server and client (#1371)
* fix(voice): 4 defect(s) (OC-0008, OC-0009, OC-0042, OC-0080) Guard LiveKit session state against supersession: bump the camera/screen generation in leaveVoice and teardownForReconnect so an in-flight enable discards its track, bail out of restoreLocalVoiceState when a newer room claimed _room mid-await, and recheck isStateConnected in the auto-reconnect tail. * fix(ws): 1 defect(s) (OC-0019) * fix(db): 1 defect(s) (OC-0023) * fix(ws): 1 defect(s) (OC-0029) * fix(ws): 1 defect(s) (OC-0032) * fix(voice): 1 defect(s) (OC-0034) * fix(admin): 1 defect(s) (OC-0035) * fix(service): 2 defect(s) (OC-0036, OC-0128) * fix(voice): 2 defect(s) (OC-0038, OC-0065) OC-0038: the LiveKit participant_left webhook cleared the leaver's own client voice state before broadcasting voice_leave, so the broadcast audience (READ_MESSAGES holders union still-in-the-room participants) could no longer see them. Voice membership is gated on CONNECT_VOICE alone, so a participant without READ_MESSAGES never learned the server had torn down their call. Extracted finishVoiceLeave's audience logic into broadcastVoiceEventWithLeaver and used it on the webhook path. OC-0065: handleWebhookParticipantJoined OR'd a GetVoiceState read error into the same branch as "no matching row", so a transient DB failure ejected a legitimate participant from the SFU mid-call. Now the read error is logged and the check skipped, matching sweepStaleVoiceStates. * fix(client): 1 defect(s) (OC-0041) * fix(client): 1 defect(s) (OC-0043) * fix(client): 1 defect(s) (OC-0046) * fix(client): 1 defect(s) (OC-0047) * fix(client): 1 defect(s) (OC-0049) * fix(client): 1 defect(s) (OC-0108) * fix(client): 2 defect(s) (OC-0111, OC-0143) OC-0111: retry a presence_update dropped by the 1-per-10s limiter once the window reopens, so auto-idle's return-to-online does not leave the server and every other client stuck on idle. OC-0143: pass apiConfig.host to the DM profile sidebar so per-user notes are scoped per server, matching channel mutes, the NSFW gate and volume. * test(ws): align aborted-switch test with OC-0034 no-resurrect behavior The fix agent rewrote this pre-existing test (it locked the buggy restore path) but the prove agent left it out of c67d25ed; committed state alone failed go test ./ws/ without it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
8579cb5d91 |
fix: batch of 25 correctness fixes across server and client (#1370)
* chore(workflows): raise subagent effort tiers (sonnet/haiku to xhigh, prove opus to high) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(voice): 6 defect(s) (OC-0098, OC-0004, OC-0005, OC-0006, OC-0007, OC-0020) * fix(db): 1 defect(s) (OC-0096) * fix(admin): 1 defect(s) (OC-0097) * fix(auth): 2 defect(s) (OC-0099, OC-0021) * fix(voice): 1 defect(s) (OC-0018) * fix(admin): 1 defect(s) (OC-0045) * fix(api): 1 defect(s) (OC-0103) * fix(client): 1 defect(s) (OC-0105) * fix(client): 1 defect(s) (OC-0107) * fix(api): 1 defect(s) (OC-0109) * fix(api): 1 defect(s) (OC-0112) * test(admin): compare restore bytes with bytes.Equal Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(voice): 2 defect(s) (OC-0095, OC-0014) OC-0095: createRoom never called setE2EEEnabled(true), so the full ECDH/HKDF/AES-GCM key exchange completed but frames still reached the SFU in plaintext. OC-0014: token refresh timer was 23h while the server mints LiveKit tokens with a 5-minute TTL, so any reconnect after minute 5 presented an expired token. * fix(profile): 2 defect(s) (OC-0100, OC-0102) * fix(service): 1 defect(s) (OC-0022) Archived channels were only read-only for SendMessage/DeleteMessage. Edit, reaction, pin and purge sinks bypassed the check. Route every write sink through a shared requireChannelWritable gate. * fix(api): 1 defect(s) (OC-0048) * chore(workflows): correct stale model labels in bughunt-fix phase details Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(client): 1 defect(s) (OC-0015) * fix(voice): 1 defect(s) (OC-0002) * test: fix two CI-only failures in the batch-4 test suite The delete-account broadcast test now observes member_ban on a second client's socket: the hub broadcasts and then force-disconnects the target, so on a slow runner the close could beat the target's own copy of the frame. The observer is also the party the event exists for. The voice e2e mock now echoes the real joined channel id on voice_leave (it hardcoded channel_id 0, which the dispatcher's channel-matched self-leave teardown correctly ignores), and the rejoin test waits for the mock's delayed echoes to settle before clicking the row again — clicking inside the echo window toggled a leave instead of a join. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
db0275a290 |
fix: batch of 29 correctness fixes across server and client (#1369)
* fix(ws): 2 defect(s) (OC-0013, OC-0140) * fix(voice): 1 defect(s) (OC-0044) * fix(ws): 1 defect(s) (OC-0024) * fix(server): 1 defect(s) (OC-0027) * fix(ws): 1 defect(s) (OC-0028) * fix(server): 7 defect(s) (OC-0033, OC-0066, OC-0067, OC-0068, OC-0074, OC-0077, OC-0106) * fix(ws): 1 defect(s) (OC-0051) * fix(client): 1 defect(s) (OC-0053) * fix(client): 1 defect(s) (OC-0055) * fix(service): 1 defect(s) (OC-0069) * fix(voice): 1 defect(s) (OC-0072) * fix(service): 1 defect(s) (OC-0082) * fix(client): 1 defect(s) (OC-0083) * fix(plugin): 1 defect(s) (OC-0088) * fix(plugin): 4 defect(s) (OC-0104, OC-0126, OC-0127, OC-0133) * fix(admin): 1 defect(s) (OC-0110) * fix(client): 1 defect(s) (OC-0114) * fix(api): 1 defect(s) (OC-0139) * fix(client): 1 defect(s) (OC-0149) * test(server): adapt existing tests to updated OpenDM and IncrementMentionCounts signatures * style(plugin): modernize loops and goroutine spawns in race test * fix(ws): mirror the focus admission gate in the post-subscribe revalidation * fix(service): detach DM post-commit side effects from the request ctx, fail delete closed, add empty-fan-out fallback * fix(plugin): preserve enabled intent when upgrade reactivation hits a runtime-less build * chore(skills): harden bughunt-fix workflow and fold review lessons into bughunt-run/db-change * Add comprehensive documentation for task-observer skill - Introduced environments.md to outline activation setup, compaction behavior, and handoff-doc mode. - Created skill-authoring.md detailing taxonomy, licensing, confidentiality, and editing rules for skill creation. - Added weekly-review.md for a structured review process of OPEN observations, including scheduled and in-session fallback modes. * chore(go): pin toolchain go1.26.6 (stdlib CVE fixes flagged by govulncheck) |
||
|
|
c3837fa32c |
fix(client): batch of 15 client correctness fixes (#1367)
* fix(client): 1 defect(s) (OC-0078) * fix(client): 1 defect(s) (OC-0147) * fix(client): 1 defect(s) (OC-0141) * fix(voice): 1 defect(s) (OC-0125) * fix(client): 1 defect(s) (OC-0138) * fix(client): 1 defect(s) (OC-0136) * fix(client): 1 defect(s) (OC-0121) * fix(voice): 1 defect(s) (OC-0132) * fix(client): 1 defect(s) (OC-0057) * fix(client): 1 defect(s) (OC-0122) * fix(client): 1 defect(s) (OC-0130) * fix(client): 1 defect(s) (OC-0060) * fix(client): 1 defect(s) (OC-0123) * fix(client): 1 defect(s) (OC-0124) * fix(ws): 1 defect(s) (OC-0056) --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
b1fb56511d |
fix(client): batch of 15 client correctness fixes (#1366)
* fix(message-list): rebuild virtual window when scroll leaves the rendered range Scroll-driven renderWindow calls previously never rebuilt the DOM, so scrolling past the overscan showed only spacer blank space until an unrelated data change forced a full re-render. The window now rebuilds whenever the computed visible range is not fully contained in the rendered one, keeping the no-op (and the existing rebuild rate limiter) for ranges that are already rendered. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0188TWF92r4aNM7T6eiiD7sU * fix(messages): refetch channel tail when revisiting a previously loaded channel The server only delivers live message broadcasts for the focused channel, so a channel's loaded window stops updating once the user switches away. Switching channels now drops the left channel's loaded flag so the next visit refetches the live tail, while keeping the old rows rendered until the refetch merges in (pending/failed rows are preserved). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0188TWF92r4aNM7T6eiiD7sU * fix(channel-sidebar): assign distinct slots when reordering channels with tied positions Categories whose channels share a position value (the server does not enforce uniqueness, and new channels default to position 0) previously produced an empty or partial reorder on drop, leaving the final order ambiguous. Tied slots are now nudged into a strictly increasing sequence before being reassigned, while already-distinct groups keep their range. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0188TWF92r4aNM7T6eiiD7sU * fix(embeds): keep the link-preview abort timer armed until the body is read The link-preview fetch previously cleared its 5 s abort timer as soon as response headers arrived, so reading the response body was unbounded in time and size. The timer is now cleared in a finally after the body read, so the timeout covers the whole request and the 50 KB parse cap applies as documented. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0188TWF92r4aNM7T6eiiD7sU * fix(message-list): anchor scroll-to-bottom and jump-to-present controls outside the scroller The two floating controls were appended inside the overflow scroller, so they were part of its scrollable overflow and translated away with the content whenever the user scrolled up — precisely when they become visible. They now anchor to a position:relative frame that wraps the scroller, keeping them pinned to the viewport edge. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0188TWF92r4aNM7T6eiiD7sU * fix(voice): reset the pinned input device before cycling the mic to default Selecting the default microphone (or losing the selected one to a hot unplug) only muted and unmuted the existing track, which kept capturing from the previously pinned device. The shared cycle now resets the capture device to the system default first so both paths actually reach it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0188TWF92r4aNM7T6eiiD7sU * fix(voice): re-check supersession after camera publish before announcing camera on A camera disable that completes while the enable's publishTrack call is still in flight now causes the enable to unpublish and stop its track and skip the enabled announcement, so the server's last word matches the local state instead of reporting a stopped camera as on. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0188TWF92r4aNM7T6eiiD7sU * fix(voice): re-check supersession across the screenshare publish loop A screenshare disable that completes while a publish in the enable loop is still in flight now stops the loop before the remaining tracks are published; the enable attempt unpublishes and stops all of its tracks and skips the enabled announcement, so tracks held only by that attempt are released and the server's last word matches the local state. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0188TWF92r4aNM7T6eiiD7sU * fix(sidebar): rethrow channel modal API failures so modals can recover The create/edit/delete channel callbacks caught API errors and only showed a toast, so the awaiting modal never saw the failure and left its submit button disabled with the in-flight label. The callbacks now rethrow after toasting, letting each modal re-enable its button and render its inline error so the user can retry without losing the form. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0188TWF92r4aNM7T6eiiD7sU * fix(messages): carry pending and failed rows across the prepend trim When a scroll-up page pushed a channel past the per-channel cap, prependMessages trimmed the tail wholesale, deleting pending/failed optimistic rows that hold the only copy of the user's composed text. The trim now carries those rows across, matching the other window-replacing writers. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0188TWF92r4aNM7T6eiiD7sU * fix(channel-sidebar): re-resolve the drop container when the sidebar re-renders mid-drag A store-driven sidebar re-render while a drag is in flight rebuilds the channel rows, detaching the container captured at mousedown; detached rows report all-zero rects, so the drop and the hover indicator could never resolve. The global handlers now re-target the live row, its container, and the store's current group snapshot before hit-testing. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0188TWF92r4aNM7T6eiiD7sU * fix(delete-channel-modal): re-arm the confirm button when a delete fails The confirm button was only restored from the catch block, so a caller that handled the failure itself and resolved left the button disabled on 'Deleting...' with no way to retry. Restoration now runs in a finally block whenever the modal is still open, regardless of how the callback settled. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0188TWF92r4aNM7T6eiiD7sU * fix(dm-sidebar): keep the presence dot when an avatar image loads The avatar swap cleared the whole circle before inserting the fetched image, which also removed the online/idle/dnd/offline dot on 1:1 rows. The initial now lives in its own node and only that node is replaced, so the presence dot survives the image arriving. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0188TWF92r4aNM7T6eiiD7sU * fix(formatting): compute the yesterday boundary from the calendar date The relative-day fence post was derived by subtracting a fixed 24 hours from local midnight, which lands inside the wrong calendar day when a DST transition makes the local day 23 or 25 hours long. It is now built from the calendar date directly, so hover and expanded message timestamps keep the correct Yesterday label around transitions. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0188TWF92r4aNM7T6eiiD7sU * fix(message-input): keep the empty-edit guard active while attachments are queued Edits are text-only, so a queued attachment no longer bypasses the empty-content guard while editing. Submitting an edit whose text was cleared is now refused with edit mode intact, matching the behavior when nothing is attached. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0188TWF92r4aNM7T6eiiD7sU --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
fa50d85413 |
fix(bughunt): opus verifiers, args-armed budget, and single-finder floor retune (#1365)
First-live-run fallout (2026-08-13, base
|
||
|
|
34f2e41207 |
feat(bughunt): single-finder hunt with graph-fed targeting, telemetry, and defect fixes (#1364)
* chore: ignore graphify-out * fix(bughunt): assemble the report in-script - the report agent dropped findings * fix(bughunt): retry only unverified candidates and catch garbage-verdict batches * fix(bughunt): retune the round budget floor and require a budget directive * feat(bughunt): per-round telemetry and a runStats aggregate Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(bughunt-run): record run telemetry and validate coordinates after each hunt * feat(bughunt): drop the sonnet finder slot Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(bughunt): rebuild adaptive targeting - directory clusters, cooldown, graph-fed explore lenses * fix(bughunt): rewind a dead finder's explore draw so unread files are never marked clean * docs(bughunt-run): pre-hunt graph ranking checklist and offline test roster * fix(bughunt): rewind thrown-stage explore draws and correct log/doc wording Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(bughunt): disambiguate absorb() drop log and retire dead sonnet label alternation Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
3af3489f71 |
feat(bughunt): add a fix-run circuit breaker and panel finder attribution (#1362)
* feat(bughunt-fix): add a circuit breaker for systematically failing runs A fix run had no abort condition. If something was systematically wrong - the operator on the wrong branch, a broken test runner, ledger coordinates gone stale after a rebase - it worked through every cluster, spending a high-effort agent on each, and only reported the wreckage at the end. Two trip points, because there are two distinct failure signals: - after the fix stage, a high blocked rate means the fixing itself is failing. Proving each of those costs a serial agent per cluster and cannot succeed, so phase 3 is skipped entirely. - inside the prove loop, a high revert-proof failure rate means the proving is failing. Break rather than attempt the rest. `declined` never counts as a failure - it is a judgement the fix prompt explicitly invites, and a run where several findings are correctly declined is a good run. Both points require a minimum number of attempts first, because "50% of two" is noise. Clusters never reached are marked blocked with a rationale naming the breaker, so nothing is left reported as fixed with no commit behind it, and the gate still runs over whatever committed before the trip. proveAttempts is incremented before the ok check so successes land in the denominator; inside the failure branch the ratio would be failures-over-failures and trip on the first failed cluster at any threshold. Verified with 6 new harness scenarios (21 -> 27, all green, bughunt.harness.mjs untouched at 21). The guard was also proved load-bearing: with the threshold temporarily raised to an unreachable 1.1, f16 runs all four clusters instead of stopping at three and f20 produces no breaker report - both fail for the reason the guard exists. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat(bughunt): attribute confirmed findings to the finder that produced them The dual-model panel unions its two finders rather than voting between them, so the second model's entire value is what it finds alone - and the union threw that away, leaving no way to tell whether sonnet earns its cost. Tag each finding with its panel slot. Because dedupe keeps the first occurrence and opus is slot 0, a confirmed finding tagged sonnet is one opus missed, which is exactly the number that decides the question. The run logs the split. Three details worth naming: - the tag is taken from the panel slot, not from the position in the surviving list. Filtering the nulls out before reading the index shifts sonnet into slot 0 whenever opus dies and mislabels its finds as opus - precisely when the attribution matters most. - the tag is stripped in verifyPrompt, not at its two call sites, so every caller routes through the guard. The verifier prompt says "another model" on purpose; naming it is an authority cue that erodes refute-by-default. - dropping to a single finder would also weaken convergence, since a round only counts as dry when the full panel reported. The skill records this next to the count so the decision is made with both halves in view. Verified with 4 new harness scenarios (21 -> 25). The dead-opus case is the load-bearing one: it fails against the naive filter-then-index form. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
74af0a56b2 |
chore(deps): bump the go-dependencies group in /Server with 3 updates (#1358)
Bumps the go-dependencies group in /Server with 3 updates: [github.com/corazawaf/coraza/v3](https://github.com/corazawaf/coraza), [github.com/knadh/koanf/providers/structs](https://github.com/knadh/koanf) and [go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc](https://github.com/open-telemetry/opentelemetry-go). Updates `github.com/corazawaf/coraza/v3` from 3.6.0 to 3.7.0 - [Release notes](https://github.com/corazawaf/coraza/releases) - [Changelog](https://github.com/corazawaf/coraza/blob/main/CHANGELOG.md) - [Commits](https://github.com/corazawaf/coraza/compare/v3.6.0...v3.7.0) Updates `github.com/knadh/koanf/providers/structs` from 1.0.0 to 1.0.1 - [Release notes](https://github.com/knadh/koanf/releases) - [Commits](https://github.com/knadh/koanf/compare/v1.0.0...parsers/hcl/v1.0.1) Updates `go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc` from 1.44.0 to 1.45.0 - [Release notes](https://github.com/open-telemetry/opentelemetry-go/releases) - [Changelog](https://github.com/open-telemetry/opentelemetry-go/blob/main/CHANGELOG.md) - [Commits](https://github.com/open-telemetry/opentelemetry-go/compare/v1.44.0...v1.45.0) --- updated-dependencies: - dependency-name: github.com/corazawaf/coraza/v3 dependency-version: 3.7.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: go-dependencies - dependency-name: github.com/knadh/koanf/providers/structs dependency-version: 1.0.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: go-dependencies - dependency-name: go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc dependency-version: 1.45.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: go-dependencies ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
b9d5d40e45 |
ci(deps): bump the actions-dependencies group with 2 updates (#1359)
Bumps the actions-dependencies group with 2 updates: [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action) and [anthropics/claude-code-action](https://github.com/anthropics/claude-code-action). Updates `golangci/golangci-lint-action` from 9.2.0 to 9.3.0 - [Release notes](https://github.com/golangci/golangci-lint-action/releases) - [Commits](https://github.com/golangci/golangci-lint-action/compare/1e7e51e771db61008b38414a730f564565cf7c20...ba0d7d2ec06a0ea1cb5fa41b2e4a3ab91d21278a) Updates `anthropics/claude-code-action` from 1.0.187 to 1.0.189 - [Release notes](https://github.com/anthropics/claude-code-action/releases) - [Commits](https://github.com/anthropics/claude-code-action/compare/1623c36729ac1cd5895198cded705a287de7db79...6b082c41935b4c8a3b8b0ef85ba4ba4d9eeb8975) --- updated-dependencies: - dependency-name: golangci/golangci-lint-action dependency-version: 9.3.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: actions-dependencies - dependency-name: anthropics/claude-code-action dependency-version: 1.0.189 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: actions-dependencies ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
32e2a93f3b |
chore(deps): bump tauri-plugin-updater (#1360)
Bumps the cargo-dependencies group in /Client/tauri-client/src-tauri with 1 update: [tauri-plugin-updater](https://github.com/tauri-apps/plugins-workspace). Updates `tauri-plugin-updater` from 2.10.0 to 2.10.1 - [Release notes](https://github.com/tauri-apps/plugins-workspace/releases) - [Commits](https://github.com/tauri-apps/plugins-workspace/compare/updater-v2.10.0...updater-v2.10.1) --- updated-dependencies: - dependency-name: tauri-plugin-updater dependency-version: 2.10.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: cargo-dependencies ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
6d964164b5 |
feat(bughunt): add the fix pipeline, a findings ledger, and cross-run memory (#1361)
* feat(bughunt): seed dedupe from the findings ledger via args.known * feat(bughunt): allow a scoped hunt via args.lenses * feat(bughunt): carry finder why/repro/evidence into confirmed records * feat(bughunt-fix): add workflow skeleton with per-file clustering * feat(bughunt-fix): add parallel per-file fix agents * fix(bughunt-fix): dedupe ids in the test stub, not in the merge loop * fix(bughunt-fix): drop foreign result ids loudly and assert the fix-prompt rules * feat(bughunt-fix): add serial revert-proof and per-cluster commits * fix(bughunt-fix): require real test output in the prove report * feat(bughunt-fix): add the ci-check gate and finalise the return shape * fix(bughunt-fix): harden the gate call and cover generated-code drift * docs(bughunt): add the bughunt-run operator skill * fix(bughunt-fix): guard cross-cluster edits, branch, and ledger handoff |
||
|
|
a39cd8e23c |
ci(deps): group Dependabot updates into one PR per ecosystem (#1357)
The 2026-08-10 refresh opened 17 PRs: ten gomod, four npm, three actions. Each one rewrites its ecosystem's lockfile, so merging any single PR invalidates every sibling, which then rebases and re-runs the full ~15 minute CI matrix. Clearing the batch sequentially costs 17 CI cycles for one weekly dependency refresh. A catch-all group per ecosystem makes that 4 PRs at most. It also keeps release trains intact -- the seven OpenTelemetry modules in that batch are one coordinated release and belong in one PR. The stryker and vitest groups are removed because the npm catch-all subsumes them; their reason for existing (exact peer pins across a family break under a partial merge) is now the rationale for the whole scheme and is recorded at the top of the file. Majors are already ignored for every ecosystem, so each group only ever carries patch and minor updates. A bad member goes on the ignore list rather than ungrouping the rest. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
0594a130cb |
fix(ws): give the LiveKit health check its own HTTP transport (#1356)
NewLiveKitProcess built its health-check http.Client without a Transport,
so it fell back to the process-wide http.DefaultTransport.
httptest.Server.Close calls CloseIdleConnections on http.DefaultTransport
by design ("assume most users of httptest.Server will be using the standard
transport, so help them out"), and ws is full of t.Parallel tests that each
defer srv.Close(). Any one of them finishing while a health check held a
pooled connection severed that request:
livekit_test.go:978: HealthCheck: livekit health check failed:
Get "http://127.0.0.1:41343": net/http: HTTP/1.x transport connection
broken: http: CloseIdleConnections called
That surfaced as an unrelated-looking CI failure on a TypeScript lint bump
(#1341). It is not purely a test artifact: in production the health check
also shared one connection pool with every other DefaultTransport user in
the server process.
Cloning DefaultTransport keeps its tuned defaults (proxy, dial and TLS
timeouts, HTTP/2) while giving the client a private pool.
Locked by TestHealthCheckClientOwnsItsTransport, which fails on the
unfixed constructor.
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
a29e28d018 |
ci(deps): bump actions/checkout, swatinem/rust-cache, and claude-code-action (#1355)
* ci(deps): bump actions/checkout from 4.2.2 to 4.4.0 Bumps [actions/checkout](https://github.com/actions/checkout) from 4.2.2 to 4.4.0. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/11bd71901bbe5b1630ceea73d27597364c9af683...11d5960a326750d5838078e36cf38b85af677262) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: 4.4.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> * ci(deps): bump swatinem/rust-cache from 2.9.1 to 2.9.2 Bumps [swatinem/rust-cache](https://github.com/swatinem/rust-cache) from 2.9.1 to 2.9.2. - [Release notes](https://github.com/swatinem/rust-cache/releases) - [Changelog](https://github.com/Swatinem/rust-cache/blob/master/CHANGELOG.md) - [Commits](https://github.com/swatinem/rust-cache/compare/c19371144df3bb44fab255c43d04cbc2ab54d1c4...6323deb102c322ba6fcbdcafc7e3dddab59af2b6) --- updated-dependencies: - dependency-name: swatinem/rust-cache dependency-version: 2.9.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> * ci(deps): bump anthropics/claude-code-action from 1.0.185 to 1.0.187 Bumps [anthropics/claude-code-action](https://github.com/anthropics/claude-code-action) from 1.0.185 to 1.0.187. - [Release notes](https://github.com/anthropics/claude-code-action/releases) - [Commits](https://github.com/anthropics/claude-code-action/compare/9db594c7a0e82298c121c18b7f08aa1579ce7341...1623c36729ac1cd5895198cded705a287de7db79) --- updated-dependencies: - dependency-name: anthropics/claude-code-action dependency-version: 1.0.187 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
9009fbc584 |
chore(deps): bump eslint, oxlint, knip, and typescript-eslint (#1354)
Batches the four open Dependabot npm PRs into one change so package-lock.json is rewritten once instead of four times: eslint 10.8.0 -> 10.8.1 oxlint 1.76.0 -> 1.77.0 knip 6.31.0 -> 6.32.0 typescript-eslint 8.65.0 -> 8.66.0 All four are devDependencies; no runtime dependency moves. Supersedes #1341, #1345, #1349, and #1352. Verified per the ci-check skill: 4822 unit tests across 171 files, tsc --noEmit, npm run lint, and prettier --check all pass. The no-underscore-dangle warnings oxlint prints on livekitSession.ts are pre-existing -- oxlint 1.76.0 emits the identical set -- and are warnings, not errors. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |