Conversation
Moves the 469-line createTestSuiteService factory into test-suite-service.ts so index.ts is a barrel-only re-export, following FSD conventions. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ers.ts
Aligns the handler orchestrator filename with FSD convention ({domain}-handlers.ts).
Updates import in ipc/index.ts, all 14 sub-handler files, and 3 doc comments.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…iance Renames 5 files (script-store, config-store, baseline-store, shared-steps-store, screenshot-capture) to their correct *-service.ts names and updates all exported interface/factory identifiers (ScriptStore→ScriptService, etc.) across test-suite-service.ts, test-suite-handlers.ts, the unit test, and a doc comment. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Merged schema-baselines.ts, schema-schedules.ts, and schema-shared-steps.ts into the main schema.ts, following the single-schema-per-domain convention used by every other feature. Updated db barrel and all 4 importers. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…criptSelector) StepPanel superseded by StepList/StepTimeline, WebviewPanel superseded by BrowserViewPanel, ScriptSelector had zero consumers. Verified via grep before deletion. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Move screenshot-utils.ts from components/ to lib/ (utility, not a component) and move selector-builder.ts from main process to renderer lib/ (uses browser-only APIs). Update importers in ScreenshotPreview, ScreenshotThumbnailStrip, and selector-builder unit test. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Merge analytics-schemas.ts, baseline-schemas.ts, and power-schemas.ts into the single schemas.ts, following the single-file convention used by all other domains. Update contract.ts, index.ts barrel, and two renderer components to import from the unified source. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…o single source Removes the locally-defined TestSuiteService and TestSuiteRunEvent interfaces from test-suite-handlers.ts (which used unknown[] returns) and re-exports the canonical strongly-typed versions from test-suite-service.ts. All 14 sub-handler files continue to resolve TestSuiteService from '../test-suite-handlers' unchanged. Also removes the now-unused InferZodType helper, locally-inferred QaRun/QaRunStatus/ QaRunReport/TestSuiteStep types, four Zod schema imports, and nine sub-service type imports that only existed to serve the deleted local interface. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Consolidates 6 inline RunRecord definitions into a single canonical interface at src/renderer/features/test-suite/lib/types.ts, with optional fields to satisfy all consumers including RunLogDialog's narrower shape. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… selectScript alias - Remove duplicate StatusFilter from store; import/re-export from lib/constants - Add StoreOutputLine and StreamOutputLine to lib/types; remove local definitions from store and useRunOutput - Update ResultsOutputLog to import StreamOutputLine from lib/types instead of defining locally - Remove selectScript back-compat alias (no external consumers found) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove duplicate DEFAULT_CONFIG_VIEWPORT_WIDTH/HEIGHT from starter-test.ts; import DEFAULT_VIEWPORT_WIDTH/HEIGHT from constants.ts - Replace stepToLabel with describeStep as canonical step-to-label function; delete stepToLabel and shortTarget from format.ts - Remove local toFileUrl from DiffViewer.tsx; import fileUrl from screenshot-utils.ts - Fix text-text-muted typo to text-muted-foreground in format.ts - Update ResultsPanel.tsx to remove unnecessary type cast on scriptSteps Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ySchema
Adds companion type aliases for all major schemas in the IPC schemas barrel,
extracts TriggeredBySchema from inline enums in contract.ts and QaRunSchema,
tightens RunHistoryEntrySchema.status to QaRunStatusSchema, replaces seven
inline z.object({ success }) with SuccessResponseSchema in BROWSER-VIEW and
OPEN.REPORT channels, and updates the index.ts type export block.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…tSuiteKeys Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…line key Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Removes nanoid dependency from 8 main-process test-suite files and replaces all nanoid() calls (including nanoid(8) in diff-engine.ts) with generateId() from @shared/lib/id, aligning with the project standard of crypto.randomUUID(). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…lineService Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…usIndicator, MetadataList, Code, Stack) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Moves generateAssertionSuggestions to lib/assertion-suggestions.ts and buildDefaultDescription (with its formatDuration dep) to lib/run-description.ts, removing duplicate logic from SaveRecordingDialog and CreateTaskFromRunDialog. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…, unused params - Rename Props -> BrowserViewPanelProps in BrowserViewPanel.tsx - Remove redundant title attr from RunSparkline dots (Tooltip already handles it) - Change absolute @Renderer import to relative in ConfigEditDialog.tsx - contract.ts doc comment already comprehensive (P2-T5), skipped - Remove unused _specNames param from renderYaml() and update both callers Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Maps the user's screenshotMode setting to Playwright's built-in screenshot option: 'manual' → 'off', any active mode → 'on', unset → 'only-on-failure'. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… status Wires up the RUN.STEP IPC event through the full stack: adds stepType/status/durationMs fields to the contract schema, detects Playwright action lines in runner stdout via a pattern map + helper, forwards step events through TestSuiteRunEvent and the router, and updates useRunSteps to use payload.stepType directly and mark the previous step as passed on each new step arrival. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…e fields Adds AssertMethodSchema (8 assertion types) and optional attribute field to QaStepAssertSchema; mirrors both additions in the QaStepAssert TS interface with assertMethod optional for backward compat with existing scripts. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… interface Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
feat(peers): P2P sync phases 3a + 3b + 4 + 5 — full Hub removal
P2P sync rollout complete (Phases 3a + 3b + 4 + 5): - Phase 3a: peer pairing primitives (PIN ritual, TLS, mDNS, peer-server) - Phase 3b: renderer pairing UI (Settings → Peers, dialogs, hooks) - Phase 4: extended SYNC_TABLES + workflow_runs_summary dual-write + op-log GC - Phase 5: Hub server fully removed (renderer + main + Docker + nginx + certs) This is a breaking change — the standalone Hub server no longer exists. Multi-device sync is now direct peer-to-peer over TLS-pinned WSS. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The hub-tests job in test.yml referenced hub/package-lock.json which no longer exists, failing all CI runs post-Hub-removal with "Some specified paths were not resolved, unable to cache dependencies". The release.yml had a guard (`if [ -f hub/package.json ]`) so it silently skipped — that's why v0.3.0 still released cleanly. Removing the dead stanza for hygiene. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
CI workflow fixes (drop hub-tests + hub build step) need a version bump to release because the release workflow refuses to overwrite existing tags. Patch bump since no user-visible behavior changes. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Root cause of v0.3.0 / v0.3.1 macOS startup crash:
TypeError: Cannot read properties of undefined (reading 'get')
at _interopNamespaceDefault (out/main/index.cjs:59)
at Module.<anonymous> (out/main/index.cjs:69)
@peculiar/webcrypto was in devDependencies, so electron-vite's
externalizeDepsPlugin() didn't externalize it — Rollup inlined its
source. The bundled webcrypto does `import * as process from 'process'`
which Rollup wrapped with `_interopNamespaceDefault(require('process'))`.
At runtime, the for-in loop over Node's process global hit
prototype-inherited enumerable properties whose getOwnPropertyDescriptor
returned undefined, and `d.get` threw.
Fix: move @peculiar/webcrypto and @peculiar/x509 from devDependencies
to dependencies. They were always meant to be runtime deps (they back
the per-peer self-signed Ed25519 TLS cert generation in peer-tls.ts).
externalizeDepsPlugin now externalizes them, the bundled require('process')
disappears, and the crashing wrapper is no longer called.
Verified locally: the bundled require("process") call is gone post-fix.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…HMAC, drop plaintext PIN from session
… set https timeouts
… privkey from PeerIdentity surface
Audit refs: tmp/audit/01-security.md
- peer-identity.ts:39-43 — plaintext private key fallback no 0o600
- peer-identity.ts:73 — PeerIdentity.privkey exported but unused
Changes:
- Add IdentityOpts { allowPlaintext?: boolean } and require explicit
opt-in when safeStorage.isEncryptionAvailable() is false. Throw
before generating the keypair so failure path doesn't leak entropy.
Error message instructs users to set
ADC_PEERS_ALLOW_PLAINTEXT_IDENTITY=1 to opt in.
- Log a serviceLogger warning when writing in plaintext mode.
- Persist identity file with mode 0o600 (was default 0o644 minus umask).
Mirrors peer-tls.ts pattern.
- Drop privkey from the exported PeerIdentity interface — only sign()
is used by callers (verified via git grep: no consumers of
PeerIdentity.privkey in src/main/features/peers or assistant). The
on-disk JSON format is unchanged (still pubkey/privkey/useSafeStorage),
only the in-memory surface is minimized.
- peers-service.ts: pass { allowPlaintext: process.env
.ADC_PEERS_ALLOW_PLAINTEXT_IDENTITY === '1' } at the single call site.
Tests:
- New tests/unit/peers/peer-identity.test.ts (8 cases, 1 skipped on
win32) — covers throw-without-opt-in, no-leak-on-failure-path,
0o600 mode (POSIX), reload symmetry, sign() signature shape, and
asserts 'privkey' not in identity.
- Update tests/integration/peers/peer-identity.test.ts to pass
allowPlaintext: true (mock keeps safeStorage unavailable) and
remove .privkey assertions; add a throw-without-opt-in case.
peer-tls.ts ripple: none. peer-tls.ts derives its own X.509
keypair via @peculiar/x509 and never reads PeerIdentity.privkey.
…and permanent-fail on fingerprint mismatch
… incoming sockets, dedupe peerStore/pairing
…rip peerIdShort from GC frontier, WALL_PAD=13
Audit 04 C1+C2: PeersService bootstrap was a fire-and-forget IIFE that left handlers throwing "peers-service not yet initialized" if any IPC call landed during the boot window, and disposePeerTransport was a no-op if shutdown raced ahead of the IIFE — leaking the TLS listener + mDNS. - Add wrapAsyncPeersService(): a Proxy that wraps Promise<PeersService> and returns Promise-shaped methods that await the inner promise before delegating. Source-compatible cast to PeersService at the wrap-site. - Replace the IIFE in service-registry.ts with a single peersServicePromise + wrapper. disposePeerTransport now awaits the same promise so half-constructed resources still get cleaned up. - Add a peersDisposed guard so a second dispose during the same window is a no-op. - Update peers-handlers.ts comments — handlers continue to use Promise.resolve(...) which transparently chains the wrapper Promise for sync-typed methods (listPaired/listDiscovered/getIdentity/revoke) and keeps the existing await-thenable lint clean. - Tests: 6 unit (wrapAsyncPeersService) + 3 integration (bootstrap-race) covering forward/race-safe/dispose-before-resolve/rejected-bootstrap. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…edPeer source-of-truth Closes audit H1-H5 from tmp/audit/04-service-ipc.md: - H1: validatedHandle helper parses both input and output schemas (output validated only in non-production for zero prod overhead). - H2: peers-handlers reaches through peersInvoke[channel] map instead of importing individual schemas. - H3: HostnameSchema regex (DNS / IPv4 / IPv6 / zone-id / brackets) replaces arbitrary z.string() on host fields, closing SSRF surface. - H4: displayName normalized to z.string().nullable() across DiscoveredPeerSchema, PairInitInputSchema, PairConfirmInputSchema, PinIssuedEventSchema. Drops .optional() permutations. - H5: PairedPeer is now exported from @shared/ipc/peers/contract.ts; peer-store re-exports it. Single source of truth. Also normalize DiscoveredPeerWithPaired.displayName to string|null and coalesce ad.displayName ?? null in enrichDiscovered so output schema validation passes in dev. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…casts and inline keys
… visibility Closes audit H4. Previously <IncomingPinDialog /> was mounted inside SettingsPage, so a PIN issued by a remote initiator while the user was on any non-Settings route was silently lost (the useIncomingPin hook only listens while mounted). Hoisted the dialog to RootLayout alongside other always-mounted overlays (AssistantWidget, WorkflowPermissionModal, notifications). Mounted in the post-onboarding shell only — incoming pair invites have no meaning before the workspace is initialized. Removed the corresponding TODO(p2p-phase4) comment and the now-unused IncomingPinDialog import from SettingsPage. Backstop test added at tests/unit/renderer/peers/incoming-pin-global.test.ts. The repo's vitest config uses environment: 'node' (no jsdom), so the test falls back to static-analysis assertions that verify the dialog is imported and rendered in RootLayout and absent from SettingsPage. To be replaced with a render test if/when a jsdom-enabled vitest project is added.
…ePeerListPanel) + format helpers Closes audit C3, C4, M2, M5 (and L3 partially) from tmp/audit/05-renderer.md. - Extract useOutgoingPair hook owning Stage state machine, session token, PIN value (sanitized via sanitizePin), and pair-init/pair-confirm mutations. OutgoingPairDialog becomes render-only. - Extract usePeerListPanel hook owning inviteTarget state and the four query/mutation hooks. PeerListPanel becomes render-only and the renderSelfBody / renderPairedBody / renderDiscoveredBody helpers are now typed sub-components <SelfBody/>, <PairedList/>, <DiscoveredList/>. - Replace lib/truncate.ts with lib/format.ts adding peerLabel() and sanitizePin(); re-export truncate. Removes inline (peer.displayName ?? truncate(peer.peerId)) duplication across all three dialogs. - Add src/shared/ipc/peers/constants.ts with PIN_LENGTH=6 (M5) and PEER_ID_DISPLAY_MAX=16. format.ts and the hook import the shared constant; main + renderer agree on PIN size from one source. - IncomingPinDialog (L3): replace Heading as="h1" for the PIN value with Text size="lg" + className="text-3xl font-mono tracking-widest" (Text size tokens are sm/md/lg only — visual scale comes from the Tailwind utility). Uses peerLabel() helper. - index.ts: re-export the hooks, peerLabel, sanitizePin, truncate; drop truncate.ts barrel entry. Tests (44 added/touched, all passing): - format.test.ts (15 real unit tests on truncate/peerLabel/sanitizePin) - useOutgoingPair.test.ts (10 static-analysis assertions, T13/T14 style) - usePeerListPanel.test.ts (10 static-analysis assertions) Verification: vitest peers/ green (44/44), npx tsc --noEmit clean, npx eslint clean on all touched files. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ase1 deprecations Adds src/main/features/peers/peer-constants.ts as the single source of truth for protocol-level + runtime constants shared across the peers module. Updates consumers to import from there: - peer-pairing.ts: SESSION_TTL_MS, SESSION_MAX_ATTEMPTS, SESSION_SOFT_LIMIT (drops local DEFAULT_TTL_MS / DEFAULT_MAX_ATTEMPTS / DEFAULT_MAX_ACTIVE_SESSIONS) - pair-server.ts: PAIR_BODY_MAX_BYTES, PAIR_REQUEST_TIMEOUT_MS, PAIR_HEADERS_TIMEOUT_MS, PAIR_KEEPALIVE_TIMEOUT_MS, PAIR_BODY_READ_TIMEOUT_MS, LOOPBACK_HOST - ws-transport.ts: WS_CLOSE_CODES (SCHEMA_MISMATCH, FINGERPRINT_MISMATCH, MALFORMED_FRAME, UNTRUSTED), MAX_INBOUND_SOCKETS, LOOPBACK_HOST - peer-mdns.ts: MDNS_SERVICE_TYPE, MDNS_PROTOCOL, PEER_ID_SHORT_LEN - peers-service.ts: GC_INTERVAL_MS, LOOPBACK_HOST - outbound-dialer.ts: WS_RECONNECT_BASE_MS / WS_RECONNECT_MAX_MS / WS_RECONNECT_JITTER as defaults (still overridable via opts) Removes the Phase1PeerConfig type alias and loadPhase1PeerConfig() function from peer-config.ts. Grep across src/ + tests/ found zero non-self callers. Pure mechanical refactor — no value changes, no behavior changes. Audit refs: 02-transport.md H2/H3/L4/L7, 01-security.md "magic numbers" section. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…peer-state schema, drop peerId fallbacks, schema-hash uses node:crypto, cross-device-query DI, relocate peerKeys - Move migration-tags.ts from src/main/features/peers/ to src/main/db/ (audit 01/Low — generic Drizzle journal logic, not peer-specific). - Clear revokedAt on re-pair in peerStore.upsert (audit 01/Medium — silent re-pair failure on previously-revoked peers). - Merge peer-state-schema.ts into peers/schema.ts (audit 03/Low — one schema file per domain). - Drop dev-default 'aaaaaaaa' / 'peer-a' peerId fallbacks in service-registry; resolve identity once via getOrCreatePeerIdentity and pass through to peers-service + replicationEngine. Throws if identity is unresolved (audit 04/L3). - cross-device-query receives PeerStore via DI rather than constructing its own; reuses the registry-owned singleton (audit 04/M6). - schema-hash.ts switches to node:crypto and is now sync — drops unnecessary async API at the only call site (audit 03/M8). - Relocate peerKeys to @shared/ipc/peers/queryKeys.ts; renderer feature re-exports for source compat. EventBridge imports from @shared/ipc/peers, removing the boundaries/dependencies disable (audit 05/T13). Tests: new tests/unit/peers/peer-store-revoked-reset.test.ts; schema-hash + migration-tags + cross-device-query tests updated. typecheck + lint pass on touched files. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
fix(peers): close 50+ audit findings across P2P sync system
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Why
Changes
Testing
npm run lintpasses cleannpm run typecheckpasses cleannpm run buildsucceedsnpm run dev)Screenshots