@stryker-mutator/{api,core,vitest-runner} were bumped to 9.6.1 while
typescript-checker stayed at 9.6.0, which hard-pins core@9.6.0 as a peer.
npm install failed with ERESOLVE. Bump typescript-checker to match.
Also reformat two files for prettier 3.9.6, which changed how union
types are broken across lines.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A pre-merge review caught things my local verification missed, because two CI
gates could not run in the sandbox and I mis-read a third.
golangci-lint (BLOCKER — would have turned CI red on both server matrix legs).
My local binary was built for Go 1.25 against a repo targeting 1.26, so I could
not run it. Installed 2.11.3 with the repo's own toolchain: 5 issues, all in
files this PR adds, base clean. Now 0 issues:
- bodyclose x3 in api/livekit_proxy_ws_test.go — websocket.Dial's *http.Response
was discarded; adopt the repo's existing pattern from ws/ws_integration_test.go
- gocritic stringXbytes — string(got) != string(payload) -> !bytes.Equal
- staticcheck SA4000 in ws/topic_rate_limiter_test.go — `!Allow() || !Allow()`.
This was a real defect, not just a lint: || short-circuits, so a failing first
call skipped the second, left a token unspent, and the next assertion would
have reported the wrong thing. Split into two statements.
Playwright: I reported this suite as passing. It does not. I read the exit code
of `tail` through a pipeline instead of playwright's own. Re-run properly: 229
of 255 web tests fail, all cascading from the shared login helper
(navigateToMainPage never sees [data-testid='app-layout']). It reproduces on a
clean b3caceb worktree, so it is pre-existing on main and unrelated to this
diff — but it was never true that I had verified it. Recorded as new finding
T-2026-07-25-21 and promoted to backlog #2; the client-e2e job stays
continue-on-error and now carries timeout-minutes so a red suite cannot burn
unbounded Actions minutes. rust-tests gets a timeout too.
Audit-doc corrections (all confirmed by re-measurement):
- screenShare.ts was listed as "untouched by this pass" at 61.1% when this PR
takes it to 100%; T-12's wording made it the exception when it is the best
- IsEitherBlocked was NOT zero-coverage — 83.3% at base via message_test.go
- excluded LOC 2,229 -> 1,827
- "40 Playwright spec files" -> 44 (33 web + 11 native), 255 web tests
- HandleLiveKitHealthForTest callers: eight -> seven
- admin coverage: 71.4% is with only the T-01 fix; 77.9% with this PR's tests
Verified after the fixes: golangci-lint 0 issues, go vet, go test -race,
go test -tags deadlock, vitest 94.87%, cargo test --lib 74/74, tsc, prettier.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AEETs3Vh6sAHHb1jMBL75g
Audits what actually has tests, then closes the gaps it found. Full write-up
with before/after numbers in docs/audit-test-coverage-2026-07-25.md.
Measurement first: `go test ./... -coverprofile` (what CI runs) instruments
each package only for itself, so code exercised through another package's
tests reads as uncovered — `service` reported 36.7% against a real 85%. All
analysis here uses -coverpkg=./..., and both views now have Makefile targets.
Features that had zero coverage at every layer:
- user blocking (db + service + the /api/v1/blocks routes)
- auth lockout persistence — the DB round-trip that survives a restart
- plugin install/enable/disable/uninstall and the plugin KV namespace
- event replay bounds (GetMaxEventSeq, PruneEventsOlderThan)
- LiveKit participant_joined webhook (replayed-token guard), the room-service
client, and proxyWebSocket/copyWS
- ws_proxy.rs and livekit_proxy.rs — pure helpers extracted, matching the
existing tofu.rs pattern, so cert-pin and header-injection checks are testable
Gaps that were hidden rather than absent:
- Server/admin reported 0.3% coverage with 307 tests passing. TestSpawnDetached_*
re-execs the test binary; the child inherited GOCOVERDIR and the parent's
stdout, clobbering the profile and printing "[no tests to run]". Now 71.4%,
and CI's uploaded artifact is correct.
- vitest.config.ts excluded 2.2k LOC unexplained, including two files that
already had tests. Trimmed to three entries, each justified inline.
- api.HandleLiveKitHealthForTest re-implemented the handler it claimed to
expose, so eight call sites tested a copy. Added a hook to the real one.
Two bugs found and pinned rather than silently patched: logctx.WithGroup nests
req_id under the group, and drag-reorder.ts takes one listener ref per channel
but releases one per sidebar, so the count never reaches zero.
Coverage: client 92.93% -> 94.87% statements (3371 -> 3572 tests) even after
un-excluding hidden files; Rust 47 -> 74 tests; Go zero-coverage functions
~70 -> 21, with plugin 61->77%, admin 67->86%, db 76->84%, service 85->91%.
Verified: go vet, all four build-tag variants, go test -race, -tags deadlock,
vitest --coverage, cargo test --lib, cargo clippy --all-targets, playwright.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AEETs3Vh6sAHHb1jMBL75g
Rebasing onto post-F3 main surfaced two spots the auto-merge left inconsistent:
- profile_handler UpdateIdentityKey path: thread ctx into the writeServiceError
call (the signature gained a context param in the server logging change)
- identity-pin store lookup: drop a needless borrow flagged by clippy
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- tauri-plugin-log: rotating Rust log file in the app-log dir so shipped users
can retrieve proxy/TLS/TOFU diagnostics (a release build detaches the console)
- log the health-check failure cause; log persist failures in save_settings /
store_cert_fingerprint; add a TOFU cert-pin accept/change audit trail; log
http/livekit proxy-loop panics instead of swallowing them
- stop persisting the raw WS frame content and the auth token prefix to disk
- drain the pre-init in-memory log buffer so bootstrap logs reach disk
- surface the server X-Request-Id on API errors for cross-tier correlation
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Make server failures debuggable without leaking secrets:
- configurable stdout log level (config.yaml logging.level + OWNCORD_LOGGING_LEVEL)
- preserve the DB cause in ErrInternal wraps; log auth-DB failures distinctly
from bad tokens; log the previously-silent expired-session cleanup goroutine
- route HTTP handler panics through slog (was chi stderr-only, invisible to
the admin log stream)
- stackutil: argument-free panic stacks so key/token bytes never reach the
admin ring buffer / SSE; slog.LogValuer redaction on VoiceConfig/GitHubConfig/
GIFConfig/Config and db.User/db.Session
- logctx: req_id/trace_id correlation on ...Context log calls
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace hand-rolled code with first-party Tauri v2 plugins where a plugin can
do the job, and add the genuine gaps:
- single-instance: focus the running window on a second launch instead of
opening a duplicate (two WS connections / tray icons). Registered first;
built with the "deep-link" feature so owncord:// links reach the running app.
- window-state: replace the hand-rolled save/restore plumbing with
tauri-plugin-window-state. Keep only the one thing the plugin lacks — an
off-screen re-center guard for windows restored onto a now-disconnected
monitor (isRectOnScreen).
- autostart: "Launch on Login" toggle in Advanced settings, reading/writing
real OS state via tauri-plugin-autostart (not a stored preference).
- deep-link: register the owncord:// scheme and route invite links into the
register form. OwnCord invites are registration invites, so a link pre-fills
and opens the form rather than completing a one-click join.
Intentionally NOT replaced: push-to-talk (ptt.rs) stays hand-rolled —
tauri-plugin-global-shortcut registers OS hotkeys that grab the key
system-wide (RegisterHotKey / XGrabKey), which cannot express non-consuming
press-and-hold PTT. Clipboard stays on the native Web API (no custom code).
Verified: tsc, eslint, prettier, 3369 unit tests, cargo check, cargo clippy
(client code clean; one pre-existing needless-borrow lint in commands.rs is
flagged only by newer local clippy, untouched here).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Follow-up to the F3 re-pin/forward-secrecy fix, closing two residuals found by
adversarial re-review of the first fix:
- Concurrent-leave rotation drop (medium): rotateKeyPeriodically's _rotatingKey
guard silently skipped a rotation already in flight, so a keyed peer that left
mid-rotation kept a live room key until the next periodic (<=5 min) rotation.
A coincident keyed-peer leave now DEFERS its rekey (_rotationPending) instead
of dropping it; the completing rotation drains it via a shared
drainPendingRotationOrArmTimer, excluding the departed member. Applied to both
the become-holder and periodic rotation paths; reset in clearE2EEState.
- Blind re-pin (info, defense-in-depth): the mismatch modal's Trust action pinned
publishedKey even when its fingerprint could not be computed (nothing shown to
verify). onAccept now refuses to pin when fingerprint is null.
Client gates green: typecheck, lint (0 errors), prettier, vitest (3364).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Multi-agent F3 security review surfaced two voice-E2EE defects:
- Re-pin TOCTOU (voice-E2EE MITM): the identity-mismatch modal showed a
fingerprint from one membersStore read, but rePinPeerIdentity re-read the
server-writable store to decide what to pin. A malicious server (F3's threat
model) could swap in an attacker key via a user_update during the human
out-of-band verification window and have it pinned, silently defeating the
mismatch prompt. rePinPeerIdentity now takes the exact verified key as a
parameter; ChannelSidebar passes the bytes whose fingerprint it displayed.
- Membership forward secrecy: the key holder rotated the room key only when the
holder ROLE transferred, so a departed non-key-holder kept a valid room key
until the next periodic (<=5 min) rotation. The holder now also rotates when a
peer that held the key leaves (reusing rotateKeyPeriodically), gated on the
leaver having actually held a key.
Client gates green: typecheck, lint (0 errors), prettier, vitest (3361).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Render the per-peer E2EE identity-verification state (F3 TOFU) on voice
user rows in the live channel sidebar, and give legitimate key rotation an
in-app recovery path:
- Per-peer shield badge in renderVoiceChannelItem: green shield-check
(verified, safety number in tooltip), muted shield (unverified/legacy),
red shield-alert (mismatch, click to review).
- createIdentityMismatchModal (in CertMismatchModal.ts, reusing the .cert-*
CSS and buildRow) — the identity-key analogue of the cert-mismatch prompt.
It shows the changed key's fingerprint for out-of-band verification, and
"Trust New Key" re-pins via rePinPeerIdentity to recover from a genuine
rotation.
- Fold verification status into the sidebar's voiceStore structural
signature so a verified/unverified/mismatch flip re-renders the badge.
- Three Lucide shield icons; .vu-verify layout CSS.
The badge lives in ChannelSidebar.renderVoiceChannelItem (the live voice
renderer); createVoiceChannel in VoiceChannel.ts is dead/unused.
Client gates green: typecheck, lint (0 errors), prettier, full vitest (3358).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- client: update endpoint now sends {{target}}-{{arch}}-{{bundle_type}} so the
server-echoed platforms key matches the updater plugin's
{os}-{arch}-{installer} lookup (previously bare {{target}} produced a key
the plugin never matches, so no update was ever surfaced)
- client: TOFU cert pin is scoped to the OwnCord server host via
HostScopedVerifier; the GitHub installer download validates against web PKI
instead of failing the pinned-fingerprint check on every install
- client: check/install share one build_updater helper so the two paths cannot
diverge; tauri-plugin-updater minor-pinned per its configure_client guidance
- server: client-update endpoint serves target-specific artifacts (NSIS,
per-arch AppImage) and returns 204 for targets without a published updater
artifact (deb, darwin) instead of always serving the Windows NSIS installer
- release: server-update-manifest.json now binds both OS assets (legacy
top-level pair kept pointing at the Windows binary so deployed servers still
verify); VerifyReleaseManifest resolves the entry matching the downloaded
asset, fixing Linux server self-update
- release: ARM64 staging renames installer, tar.gz and .sig consistently so
signatures keep pairing and arch-less names cannot collide with x86_64 assets
- ci: run cargo test --lib (Rust #[cfg(test)] code was never compiled in CI);
merge the two ptt tests that raced on the global PTT_VKEY atomic
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Fixes all 109 golangci-lint findings (106 contextcheck, 1 gocritic,
2 gosec) that accumulated after D2 wired dbgen (whose queries take ctx)
under ctx-less db.DB wrappers while CI lint was quota-dead. No nolint
comments added; every finding fixed by genuinely threading context.
- db: all 138 hand-written db.DB methods take ctx first; the dbCtx()
Background shim is deleted; raw Query/QueryRow/Exec/Begin use their
Context variants; the four redundant ctx-less passthroughs removed.
db.Auditor/WriteAudit gain ctx.
- Seams: permissions.Checker (DB iface, HasChannelPerm,
RequireChannelAccess) and the service.Store interface mirror the new
signatures (ws.EventStore and plugin.PluginStore already did).
- Callers: api/admin handlers use r.Context(); ws per-message paths use
the connection ctx via DispatchV2; hub loops and startup wiring use
context.Background(); service methods thread ctx where they have one
and Background where no ctx exists. Public service surface reached by
ctx-holding chains (PermissionService.HasChannelPerm/GetRoleForUser/
RequireChannelAccess, message/dm/block/invite/profile methods) is now
ctx-first.
- Detached (context.WithoutCancel) where cancellation would break an
invariant, found by a 3-lens adversarial review of the diff:
* voice-leave background retries (a dead webhook/connection ctx killed
retry 2 before it ran, leaving ghost capacity-holding voice rows)
* rollbackVoiceJoin's compensating delete (its trigger IS the cancel)
* post-2FA-change DeleteOtherSessions and logout DeleteSession (the
security tail of a committed change must not die with the request)
* all api/ws audit writes (a banned user could suppress their own
login_blocked_banned row by aborting the request mid-bcrypt)
* admin backup VACUUM INTO (an interrupt left a truncated .db that
the backup list presented as restorable)
* post-commit message/edit refetches (a committed message must still
fan out when the sender disconnects)
* hub settings-cache refresh (one dead connection could pin stale
values for the 30s TTL)
- gocritic rangeValCopy fixed (index iteration); gosec G306 excluded in
config with justification (generated source must stay world-readable)
instead of flipping genprotocol output to 0o600.
Verified: gofmt/vet, all four build-tag variants, full suite, deadlock
pass, full -race pass, golangci-lint 0 issues uncapped.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
knip findings (repo CI config), each verified including dynamic imports,
HTML refs, and the Rust side:
Files deleted: pluginBridge.ts (its documented PluginContainer.tsx
collaborator never existed in the repo; the server plugin host stays per
D11 — reinstate from git if client plugin UI work ever starts),
message-input/file-upload.ts, message-input/picker-toggle.ts (dir now
empty, removed), message-list/virtual-scroll.ts (MessageList does its
own virtualization via FenwickTree).
Dependencies removed: zod (zero imports; typegen uses
validation_library none — stale CLAUDE.md claim fixed),
@tauri-apps/plugin-store and plugin-updater npm halves (both features
are Rust-driven via StoreExt/UpdaterExt — Rust halves stay), and
tauri-plugin-global-shortcut on BOTH sides (PTT polls via device_query;
zero GlobalShortcutExt use): Cargo.toml dep, lib.rs registration, and
the 5 capability permission lines. Inert webview capability entries
store:default/updater:default also dropped. @stryker-mutator/api added
to devDependencies (stryker.config.mjs imports its types; core pins the
same version, zero install delta).
Exports removed: livekitSession clearOnError bound-const, ConnectPage/
MainPage ReturnType aliases, readAllPersistedLogs (never wired to any
UI) with its test blocks. getLogDir kept as the suite's observability
point, tagged @public for knip. protocolTypes.ts *Value types are
generated surface — knip.json now ignores that file instead.
Rust compile is CI-verified only (no MSVC toolchain here, same as the
F4/F8 TOFU work); Cargo.lock resolution pruned cleanly. Client gate
green: tsc, oxlint/eslint 0 errors, prettier, 3304/3304 vitest, knip
clean.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Each verified: the generated dbgen method's only references were the
.sql definition and dbgen output itself (no wrapper in db/*.go, no
test, no script). ArchiveChannel, DeleteAttachment,
FindExistingDMChannel, GetDefaultRole, GetMessagesByChannel,
GetMessagesByChannelBeforeCursor, GetMessagesForAPIBeforeCursor,
GetPinnedMessageRows, GetPlugin, GetPluginByName, InsertDMChannel,
InsertDMOpenState, InsertDMParticipants, LinkAttachmentToMessage,
SetChannelMixingThreshold, SetChannelVoiceMaxVideo,
SetChannelVoiceQuality, UpdateVoiceSpeaking.
dbgen regenerated with the pinned sqlc v1.30.0 (132 → 114 queries);
sqlc-verify clean; db/service/ws suites green including -race.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Applied from a deadcode (RTA from mains, all build tags) sweep with
per-symbol adversarial verification:
Deleted (nothing but their own self-tests used them):
- admin.Handler (deprecated since Phase 6; production mounts NewHandler)
plus its two self-tests
- ws.Hub.broadcastVoiceStateUpdate + wrapper + two self-tests (pre-V2
leftover; the live voice_state path is the hub voice routines)
- ws.VoiceLeaveEvent + methods ('retained as scaffolding', never
constructed in production; MsgTypeVoiceLeaveBC stays — live via the
leave routine)
- ws.parseIdentity (production calls parseParticipantIdentity directly;
ParseIdentityForTest now exercises the real parser)
- telemetry.Float64 (String/Int64 are used; the float case is covered by
the otel-tagged internal test, re-addable when a caller appears)
Moved into export_test.go so they leave the production binary (all
callers are same-package tests): the eight ws test-client constructors
and voice/E2EE setters from ws/client.go, admin.SetBackupBaseDir
(new admin/export_test.go), api.SecurityHeaders (test-only wrapper;
production uses SecurityHeadersWithTLS — docs/api.md updated to the
real name). Client.getVoiceJoinToken/setVoiceChID inlined into their
existing ForTest wrappers; TestSetVoiceChID_* self-tests deleted.
Kept after verification: updater.SetBaseURL (11 cross-package test call
sites) and telemetry.resetAppMetricsForInit (live under -tags otel —
untagged deadcode false positive).
Full gate green: gofmt/vet, 4 build-tag variants, full suite, deadlock,
race.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- security-hardening-remediation.md: status header now records that only
W2-4 and W3-3 remain open (verified by the 2026-07-23 deletion audit),
with a staleness note scoping the deleted store/-and-Postgres
references as historical. Closes audit finding A-2026-07-15.
- security-scan-2026-07-22-remediation.md: F6 recorded as committed
(ef58c04); resume checklist trimmed — F3 (voice E2EE identity TOFU)
is the only remaining finding.
- audit-2026-07-19.md: A-2026-07-15 closure row flipped to RESOLVED.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Verified-safe deletions from the 2026-07-23 deletion audit, applied now
that the permission-consolidation work (which deferred IsOwnerRole) has
landed:
- Server/service/voice.go: VoiceService was constructed in service.New
and never called by any handler, ws routine, or test.
- permissions.IsOwnerRole: zero callers.
- Server/admin/static/admin-mockup.html: 1299 lines embedded into every
release binary via //go:embed static, referenced by nothing.
- .cache/project-map/*.json: tool cache committed before .gitignore
grew the .cache/ rule.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Design note (permission-middleware-consolidation.md, status implemented),
closure-table + §3 rows for A-2026-07-16, the A-2026-07-07 amendment
recording the missed fifth site, the D13 decision row, and the settled
two-scope authorization contract in architecture/server.md. Backlog row
12 stays untouched: the auth-route sweep is deferred to a future D14.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>