diff --git a/AGENTS.md b/AGENTS.md index 8e30ddc8cc..389a4c8ef4 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -105,7 +105,7 @@ under `contracts/fixtures/`. ## Runtime and diagnostics seams Diagnostics use `@agent-device/capture-kit/diagnostics`. Request diagnostics belong in the session request log; -session artifact paths come from `src/daemon/session-store.ts`. App/device logs remain in `app.log`; +session artifact paths come from `src/daemon/session-artifact-paths.ts`. App/device logs remain in `app.log`; Apple runner and xcodebuild output remains in `runner.log`. Normalize failures with `normalizeError` and preserve `hint`, `diagnosticId`, `logPath`, and typed diff --git a/docs/adr/0016-active-session-script-publication.md b/docs/adr/0016-active-session-script-publication.md index 303ea930d6..c1ee8c4a74 100644 --- a/docs/adr/0016-active-session-script-publication.md +++ b/docs/adr/0016-active-session-script-publication.md @@ -223,7 +223,7 @@ computed from whether `session` still exists in the daemon's own store when the by re-parsing the script for `close`. This is what lets the real CLI/IPC client (not just the in-process handler) actually honor "the session stays active": without an explicit signal in the response, the client's one-shot `replay`/`test` teardown had no way to distinguish a still-active handoff from a -finished one, and tore the owning daemon down regardless (`src/daemon/client/daemon-client-lifecycle.ts`). +finished one, and tore the owning daemon down regardless (`src/daemon-client/daemon-client-lifecycle.ts`). `sessionActive` is `true` for every close-less run (including a `--from` resume) and `false` once the script's terminal `close` — or a repair-armed run's deferred equivalent — has executed. diff --git a/fallow-baselines/health.json b/fallow-baselines/health.json index c8998508bc..52fe7fdb4d 100644 --- a/fallow-baselines/health.json +++ b/fallow-baselines/health.json @@ -107,7 +107,7 @@ "count": 1 } }, - "src/daemon/client/daemon-client-lifecycle.ts": { + "src/daemon-client/daemon-client-lifecycle.ts": { "complexity_high": { "count": 1 }, @@ -115,17 +115,17 @@ "count": 1 } }, - "src/daemon/client/daemon-client-metadata.ts": { + "src/daemon-client/daemon-client-metadata.ts": { "complexity_moderate": { "count": 1 } }, - "src/daemon/client/daemon-client-rpc.ts": { + "src/daemon-client/daemon-client-rpc.ts": { "crap_moderate": { "count": 1 } }, - "src/daemon/client/daemon-client.ts": { + "src/daemon-client/daemon-client.ts": { "complexity_moderate": { "count": 1 } @@ -533,7 +533,7 @@ }, "runtime_coverage_findings": [], "target_keys": [ - "src/daemon/client/daemon-client.ts:high impact", + "src/daemon-client/daemon-client.ts:high impact", "src/cli/parser/args.ts:high impact", "src/daemon/lease-context.ts:high impact", "src/daemon/replay/internal/session-replay-heal.ts:high impact", @@ -584,7 +584,7 @@ "src/daemon/server/http-server.ts:complexity", "packages/platform-android/src/ui-hierarchy.ts:high impact", "packages/platform-android/src/sdk.ts:high impact", - "src/daemon/client/daemon-client-lifecycle.ts:complexity", + "src/daemon-client/daemon-client-lifecycle.ts:complexity", "src/client/client-companion-tunnel-worker.ts:complexity" ] } diff --git a/oxlint.config.ts b/oxlint.config.ts index 948be89c53..9478b1e6b4 100644 --- a/oxlint.config.ts +++ b/oxlint.config.ts @@ -122,7 +122,7 @@ export default defineConfig({ }, { files: [ - 'src/daemon/client/daemon-client-rpc.ts', + 'src/daemon-client/daemon-client-rpc.ts', 'src/daemon/downloadable-artifact-http.ts', 'src/remote/remote-request-diagnostics.ts', 'src/remote/upload-stream.ts', diff --git a/scripts/ios-snapshot-benchmark/cell-admission.ts b/scripts/ios-snapshot-benchmark/cell-admission.ts index 7733a26b67..a4fa2c49fb 100644 --- a/scripts/ios-snapshot-benchmark/cell-admission.ts +++ b/scripts/ios-snapshot-benchmark/cell-admission.ts @@ -1,6 +1,6 @@ import fs from 'node:fs'; import { resolveDaemonPaths } from '../../src/daemon/config.ts'; -import { readDaemonInfo } from '../../src/daemon/client/daemon-client-metadata.ts'; +import { readDaemonInfo } from '../../src/daemon-client/daemon-client-metadata.ts'; import { isAgentDeviceDaemonProcess } from '../../src/daemon/daemon-process.ts'; import { openFixture, diff --git a/scripts/layering/daemon-modularity.test.ts b/scripts/layering/daemon-modularity.test.ts index 5704f69a93..1f647d6549 100644 --- a/scripts/layering/daemon-modularity.test.ts +++ b/scripts/layering/daemon-modularity.test.ts @@ -122,7 +122,14 @@ test('external daemon request/session-state importer membership changes require REFERENCE, ); assert.equal(violations.length, 1); - assert.match(violations[0]!.message, /may only shrink from the recorded 2/); + // The recorded list owns its own size (#2342 relocated five client files into it), so the + // message assertion reads it rather than pinning a literal that the baseline would outgrow. + assert.match( + violations[0]!.message, + new RegExp( + `may only shrink from the recorded ${DAEMON_MODULARITY_BASELINE.externalDaemonTypesImporters.length}`, + ), + ); const removed = checkDaemonModularityRatchets( baselineDaemonTypesEdges().slice(1), diff --git a/scripts/layering/daemon-modularity.ts b/scripts/layering/daemon-modularity.ts index d1f8373966..99c5b79ebf 100644 --- a/scripts/layering/daemon-modularity.ts +++ b/scripts/layering/daemon-modularity.ts @@ -17,6 +17,18 @@ import type { LayeringRatchets } from './ratchet-reference.ts'; export const DAEMON_MODULARITY_BASELINE = { externalDaemonTypesImporters: [ 'src/client/client-normalizers.ts', + // #2342 relocated the daemon client to `src/daemon-client/`. These five edges are unchanged + // by that move — the client has always built `DaemonRequest` and read `DaemonResponse`, it + // simply sat inside `src/daemon/` and so fell under the prefix skip below. Naming the files + // is stronger than letting a folder prefix hide them: the set can only shrink, so a new + // `src/daemon-client/` module reaching `session-state.ts` or the daemon-private request half + // still fails this gate. Reducing these five means giving the client a neutral request + // contract, which is a type change, not a relocation. + 'src/daemon-client/daemon-client-lifecycle.ts', + 'src/daemon-client/daemon-client-progress.ts', + 'src/daemon-client/daemon-client-rpc.ts', + 'src/daemon-client/daemon-client-transport.ts', + 'src/daemon-client/daemon-client.ts', 'src/remote/daemon-artifacts.ts', ], } as const; diff --git a/scripts/layering/model.ts b/scripts/layering/model.ts index f022be9e76..d32600e63a 100644 --- a/scripts/layering/model.ts +++ b/scripts/layering/model.ts @@ -266,7 +266,8 @@ export function topFolder(file: string): string { } export function targetDagZone(file: string): string { - if (file.startsWith('src/daemon/client/')) return 'daemon-client'; + // #2342 relocated the daemon client to its own `src/daemon-client/` folder, so the + // client zone now falls out of the folder itself; `src/daemon/` is server-only. if (file.startsWith('src/daemon/')) return 'daemon-server'; return topFolder(file); } diff --git a/src/__tests__/cli-agent-cdp-session.test.ts b/src/__tests__/cli-agent-cdp-session.test.ts index a7e5d6e52d..52de51c3c0 100644 --- a/src/__tests__/cli-agent-cdp-session.test.ts +++ b/src/__tests__/cli-agent-cdp-session.test.ts @@ -19,7 +19,7 @@ import { hashRemoteConfigFile, writeRemoteConnectionState, } from '../remote/remote-connection-state.ts'; -import type { DaemonResponse } from '../daemon/client/daemon-client.ts'; +import type { DaemonResponse } from '../daemon-client/daemon-client.ts'; afterEach(() => { vi.clearAllMocks(); diff --git a/src/__tests__/cli-capture.ts b/src/__tests__/cli-capture.ts index 8d8f058b2f..8ff2798d9e 100644 --- a/src/__tests__/cli-capture.ts +++ b/src/__tests__/cli-capture.ts @@ -4,7 +4,7 @@ import type { DaemonRequest, DaemonResponse, sendToDaemon, -} from '../daemon/client/daemon-client.ts'; +} from '../daemon-client/daemon-client.ts'; import { installIsolatedCliTestEnv } from './cli-test-env.ts'; import { mkdtempForTestSync } from './test-utils/tmp-dir.ts'; diff --git a/src/__tests__/cli-diagnostics.test.ts b/src/__tests__/cli-diagnostics.test.ts index e39f11e361..860875bcd1 100644 --- a/src/__tests__/cli-diagnostics.test.ts +++ b/src/__tests__/cli-diagnostics.test.ts @@ -2,7 +2,7 @@ import { test } from 'vitest'; import assert from 'node:assert/strict'; import fs from 'node:fs'; import path from 'node:path'; -import type { DaemonResponse } from '../daemon/client/daemon-client.ts'; +import type { DaemonResponse } from '../daemon-client/daemon-client.ts'; import { resolveDaemonPaths } from '../daemon/config.ts'; import { runCliCapture as captureCli, diff --git a/src/__tests__/cli-diff.test.ts b/src/__tests__/cli-diff.test.ts index 5c934888f6..0cad30a6d0 100644 --- a/src/__tests__/cli-diff.test.ts +++ b/src/__tests__/cli-diff.test.ts @@ -4,7 +4,7 @@ import fs from 'node:fs'; import os from 'node:os'; import path from 'node:path'; import { PNG } from '@agent-device/capture-kit/png'; -import type { DaemonResponse } from '../daemon/client/daemon-client.ts'; +import type { DaemonResponse } from '../daemon-client/daemon-client.ts'; import { runCliCapture as captureCli, type CapturedCliRun, diff --git a/src/__tests__/cli-exit-paths.test.ts b/src/__tests__/cli-exit-paths.test.ts index 73ceb8e524..13228c5e21 100644 --- a/src/__tests__/cli-exit-paths.test.ts +++ b/src/__tests__/cli-exit-paths.test.ts @@ -13,7 +13,7 @@ import { runCli } from '../cli.ts'; import { runWebCommand } from '../cli/commands/web.ts'; import { installIsolatedCliTestEnv } from './cli-test-env.ts'; import { resolveDaemonPaths } from '../daemon/config.ts'; -import type { DaemonResponse } from '../daemon/client/daemon-client.ts'; +import type { DaemonResponse } from '../daemon-client/daemon-client.ts'; afterEach(() => { vi.clearAllMocks(); diff --git a/src/__tests__/cli-react-devtools-session.test.ts b/src/__tests__/cli-react-devtools-session.test.ts index c801bb8893..88581dfb7d 100644 --- a/src/__tests__/cli-react-devtools-session.test.ts +++ b/src/__tests__/cli-react-devtools-session.test.ts @@ -19,7 +19,7 @@ import type { DaemonRequest, DaemonResponse, sendToDaemon as SendToDaemon, -} from '../daemon/client/daemon-client.ts'; +} from '../daemon-client/daemon-client.ts'; afterEach(() => { vi.clearAllMocks(); diff --git a/src/__tests__/cli-remote-diagnostics.test.ts b/src/__tests__/cli-remote-diagnostics.test.ts index 231bf20cd1..ddfbefd09f 100644 --- a/src/__tests__/cli-remote-diagnostics.test.ts +++ b/src/__tests__/cli-remote-diagnostics.test.ts @@ -11,7 +11,7 @@ import fs from 'node:fs'; import path from 'node:path'; import { normalizeError, AppError } from '@agent-device/kernel/errors'; import { createDaemonHttpServer } from '../daemon/server/http-server.ts'; -import { resolveSessionRequestLogPath } from '../daemon/session-store.ts'; +import { resolveSessionRequestLogPath } from '../daemon/session-artifact-paths.ts'; import { safeSessionName } from '../daemon/session-paths.ts'; import type { DaemonRequest, DaemonResponse } from '../daemon/daemon-request.ts'; import { runCliCapture, type CapturedCliRun } from './cli-capture.ts'; diff --git a/src/__tests__/command-descriptor-platform-execution-cli-route.test.ts b/src/__tests__/command-descriptor-platform-execution-cli-route.test.ts index 2d16d7d01a..a0904460aa 100644 --- a/src/__tests__/command-descriptor-platform-execution-cli-route.test.ts +++ b/src/__tests__/command-descriptor-platform-execution-cli-route.test.ts @@ -292,7 +292,7 @@ describe('platform-execution coherence across CLI route delegation', () => { test('wiring the transport into a client transport factory stays allowed', () => { const wiring = ` - import { sendToDaemon } from '../daemon/client/daemon-client.ts'; + import { sendToDaemon } from '../daemon-client/daemon-client.ts'; type CliDeps = { sendToDaemon: typeof sendToDaemon }; function build(deps: CliDeps) { return createCliDaemonTransport({ transport: deps.sendToDaemon }); diff --git a/src/__tests__/daemon-client-progress.test.ts b/src/__tests__/daemon-client-progress.test.ts index a4e7ec6adf..92b1f856b9 100644 --- a/src/__tests__/daemon-client-progress.test.ts +++ b/src/__tests__/daemon-client-progress.test.ts @@ -4,7 +4,7 @@ import { EventEmitter } from 'node:events'; import type { Socket } from 'node:net'; import { test } from 'vitest'; import type { DaemonRequest, DaemonResponse } from '../daemon/daemon-request.ts'; -import { readDaemonSocketProgressResponse } from '../daemon/client/daemon-client-progress.ts'; +import { readDaemonSocketProgressResponse } from '../daemon-client/daemon-client-progress.ts'; import { AppError } from '@agent-device/kernel/errors'; type MockSocket = EventEmitter & { diff --git a/src/__tests__/test-utils/boundary-fault-matrix.ts b/src/__tests__/test-utils/boundary-fault-matrix.ts index a8b56d2437..152f3fe161 100644 --- a/src/__tests__/test-utils/boundary-fault-matrix.ts +++ b/src/__tests__/test-utils/boundary-fault-matrix.ts @@ -56,22 +56,22 @@ export const BOUNDARY_FAULT_MATRIX = { timeout: { mutation: { kind: 'covered', - evidence: ['src/daemon/client/__tests__/boundary-fault-transport.test.ts:timeout'], + evidence: ['src/daemon-client/__tests__/boundary-fault-transport.test.ts:timeout'], invariants: ['bounded-deadline', 'typed-error-identity'], }, read: { kind: 'covered', - evidence: ['src/daemon/client/__tests__/daemon-client-timeout-route.test.ts:http timeout'], + evidence: ['src/daemon-client/__tests__/daemon-client-timeout-route.test.ts:http timeout'], invariants: ['bounded-deadline', 'typed-error-identity'], }, 'artifact-producing': { kind: 'covered', - evidence: ['src/daemon/client/__tests__/boundary-fault-transport.test.ts:timeout'], + evidence: ['src/daemon-client/__tests__/boundary-fault-transport.test.ts:timeout'], invariants: ['bounded-deadline', 'typed-error-identity'], }, 'session-lifecycle': { kind: 'covered', - evidence: ['src/daemon/client/__tests__/boundary-fault-transport.test.ts:timeout'], + evidence: ['src/daemon-client/__tests__/boundary-fault-transport.test.ts:timeout'], invariants: ['bounded-deadline', 'typed-error-identity'], }, }, @@ -100,44 +100,44 @@ export const BOUNDARY_FAULT_MATRIX = { 'response-framing': { mutation: { kind: 'covered', - evidence: ['src/daemon/client/__tests__/boundary-fault-transport.test.ts:response framing'], + evidence: ['src/daemon-client/__tests__/boundary-fault-transport.test.ts:response framing'], invariants: ['typed-error-identity', 'no-mutation-replay'], }, read: { kind: 'covered', - evidence: ['src/daemon/client/__tests__/boundary-fault-transport.test.ts:response framing'], + evidence: ['src/daemon-client/__tests__/boundary-fault-transport.test.ts:response framing'], invariants: ['typed-error-identity'], }, 'artifact-producing': { kind: 'covered', - evidence: ['src/daemon/client/__tests__/boundary-fault-transport.test.ts:response framing'], + evidence: ['src/daemon-client/__tests__/boundary-fault-transport.test.ts:response framing'], invariants: ['typed-error-identity'], }, 'session-lifecycle': { kind: 'covered', - evidence: ['src/daemon/client/__tests__/boundary-fault-transport.test.ts:response framing'], + evidence: ['src/daemon-client/__tests__/boundary-fault-transport.test.ts:response framing'], invariants: ['typed-error-identity'], }, }, 'process-death': { mutation: { kind: 'covered', - evidence: ['src/daemon/client/__tests__/boundary-fault-acceptance.test.ts'], + evidence: ['src/daemon-client/__tests__/boundary-fault-acceptance.test.ts'], invariants: ['bounded-deadline', 'no-mutation-replay'], }, read: { kind: 'covered', - evidence: ['src/daemon/client/__tests__/boundary-fault-acceptance.test.ts'], + evidence: ['src/daemon-client/__tests__/boundary-fault-acceptance.test.ts'], invariants: ['bounded-deadline'], }, 'artifact-producing': { kind: 'covered', - evidence: ['src/daemon/client/__tests__/boundary-fault-acceptance.test.ts'], + evidence: ['src/daemon-client/__tests__/boundary-fault-acceptance.test.ts'], invariants: ['bounded-deadline'], }, 'session-lifecycle': { kind: 'covered', - evidence: ['src/daemon/client/__tests__/boundary-fault-acceptance.test.ts'], + evidence: ['src/daemon-client/__tests__/boundary-fault-acceptance.test.ts'], invariants: ['bounded-deadline'], }, }, diff --git a/src/agent-device-client.ts b/src/agent-device-client.ts index 9f6784b094..1cc5201644 100644 --- a/src/agent-device-client.ts +++ b/src/agent-device-client.ts @@ -62,7 +62,7 @@ import { type DaemonCommandName, } from './commands/command-projection.ts'; import type { CommandResult } from '@agent-device/command-registry/command-result'; -import { sendToDaemon } from './daemon/client/daemon-client.ts'; +import { sendToDaemon } from './daemon-client/daemon-client.ts'; import { resolveDaemonPaths } from './daemon/config.ts'; import { prepareMetroRuntime, reloadMetro } from './metro/client-metro.ts'; import { diff --git a/src/cli.ts b/src/cli.ts index 8cf86e3e67..941ac5cc3b 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -7,11 +7,11 @@ import { throwDaemonError, type NormalizedError, } from '@agent-device/kernel/errors'; -import { resolveRemoteRequestDiagnosticsPath } from './daemon/session-store.ts'; +import { resolveRemoteRequestDiagnosticsPath } from './daemon/session-artifact-paths.ts'; import { exitAfterFlush } from './cli/process-exit.ts'; import { readVersion } from '@agent-device/host-kit/version'; import { pathToFileURL } from 'node:url'; -import { sendToDaemon } from './daemon/client/daemon-client.ts'; +import { sendToDaemon } from './daemon-client/daemon-client.ts'; import fs from 'node:fs'; import type { BatchStep } from '@agent-device/contracts/client'; import type { ReplayTestReporterRuntime } from './cli/replay-test/reporting.ts'; diff --git a/src/cli/commands/proxy.ts b/src/cli/commands/proxy.ts index 0e17fde13c..5a9a2b172e 100644 --- a/src/cli/commands/proxy.ts +++ b/src/cli/commands/proxy.ts @@ -4,7 +4,7 @@ import { buildDaemonHttpBaseUrl } from '@agent-device/contracts/daemon-http'; import { ensureDaemon, resolveClientSettings, -} from '../../daemon/client/daemon-client-lifecycle.ts'; +} from '../../daemon-client/daemon-client-lifecycle.ts'; import { AppError } from '@agent-device/kernel/errors'; import { colorize, supportsColor } from '../../commands/output/color.ts'; import type { CliFlags } from '@agent-device/contracts/command'; diff --git a/src/cli/injected-daemon-dispatch.ts b/src/cli/injected-daemon-dispatch.ts index d7e2644ce6..bfe9da4352 100644 --- a/src/cli/injected-daemon-dispatch.ts +++ b/src/cli/injected-daemon-dispatch.ts @@ -1,4 +1,4 @@ -import type { DaemonResponse, sendToDaemon } from '../daemon/client/daemon-client.ts'; +import type { DaemonResponse, sendToDaemon } from '../daemon-client/daemon-client.ts'; import { INTERNAL_COMMANDS } from '@agent-device/command-registry/catalog'; import type { DescriptorCliCommandName, diff --git a/src/commands/batch/cli.test.ts b/src/commands/batch/cli.test.ts index a3e40334b0..b412376bac 100644 --- a/src/commands/batch/cli.test.ts +++ b/src/commands/batch/cli.test.ts @@ -2,7 +2,7 @@ import { test } from 'vitest'; import assert from 'node:assert/strict'; import fs from 'node:fs'; import path from 'node:path'; -import type { DaemonResponse } from '../../daemon/client/daemon-client.ts'; +import type { DaemonResponse } from '../../daemon-client/daemon-client.ts'; import { runCliCapture as captureCli, type CapturedCliRun, diff --git a/src/daemon/client/__tests__/boundary-fault-acceptance.test.ts b/src/daemon-client/__tests__/boundary-fault-acceptance.test.ts similarity index 93% rename from src/daemon/client/__tests__/boundary-fault-acceptance.test.ts rename to src/daemon-client/__tests__/boundary-fault-acceptance.test.ts index 52b6fdfdc8..596f7c46db 100644 --- a/src/daemon/client/__tests__/boundary-fault-acceptance.test.ts +++ b/src/daemon-client/__tests__/boundary-fault-acceptance.test.ts @@ -3,10 +3,10 @@ import http from 'node:http'; import path from 'node:path'; import { test } from 'vitest'; import { AppError } from '@agent-device/kernel/errors'; -import { assertRejectsAppError } from '../../../__tests__/test-utils/app-error.ts'; -import { mkdtempForTestSync } from '../../../__tests__/test-utils/tmp-dir.ts'; -import type { DaemonPaths } from '../../config.ts'; -import type { DaemonRequest } from '../../daemon-request.ts'; +import { assertRejectsAppError } from '../../__tests__/test-utils/app-error.ts'; +import { mkdtempForTestSync } from '../../__tests__/test-utils/tmp-dir.ts'; +import type { DaemonPaths } from '../../daemon/config.ts'; +import type { DaemonRequest } from '../../daemon/daemon-request.ts'; import { sendRequest } from '../daemon-client-transport.ts'; type ProcessDeathCommand = 'press' | 'fill' | 'snapshot' | 'screenshot' | 'open' | 'close'; diff --git a/src/daemon/client/__tests__/boundary-fault-transport.test.ts b/src/daemon-client/__tests__/boundary-fault-transport.test.ts similarity index 97% rename from src/daemon/client/__tests__/boundary-fault-transport.test.ts rename to src/daemon-client/__tests__/boundary-fault-transport.test.ts index b34cb718e3..8c2afe2a32 100644 --- a/src/daemon/client/__tests__/boundary-fault-transport.test.ts +++ b/src/daemon-client/__tests__/boundary-fault-transport.test.ts @@ -9,10 +9,10 @@ import { closeLoopbackServer, listenOnLoopback, type LoopbackServer, -} from '../../../__tests__/test-utils/loopback.ts'; -import { mkdtempForTestSync } from '../../../__tests__/test-utils/tmp-dir.ts'; -import type { DaemonPaths } from '../../config.ts'; -import type { DaemonRequest } from '../../daemon-request.ts'; +} from '../../__tests__/test-utils/loopback.ts'; +import { mkdtempForTestSync } from '../../__tests__/test-utils/tmp-dir.ts'; +import type { DaemonPaths } from '../../daemon/config.ts'; +import type { DaemonRequest } from '../../daemon/daemon-request.ts'; import { sendRequest } from '../daemon-client-transport.ts'; const { mockRunCmdSync } = vi.hoisted(() => ({ mockRunCmdSync: vi.fn() })); diff --git a/src/daemon/client/__tests__/daemon-client-lifecycle.test.ts b/src/daemon-client/__tests__/daemon-client-lifecycle.test.ts similarity index 99% rename from src/daemon/client/__tests__/daemon-client-lifecycle.test.ts rename to src/daemon-client/__tests__/daemon-client-lifecycle.test.ts index fe564c76fa..898ce66172 100644 --- a/src/daemon/client/__tests__/daemon-client-lifecycle.test.ts +++ b/src/daemon-client/__tests__/daemon-client-lifecycle.test.ts @@ -5,7 +5,7 @@ import http from 'node:http'; import net from 'node:net'; import path from 'node:path'; import { afterEach, test, vi } from 'vitest'; -import { mkdtempForTestSync } from '../../../__tests__/test-utils/tmp-dir.ts'; +import { mkdtempForTestSync } from '../../__tests__/test-utils/tmp-dir.ts'; vi.mock('@agent-device/host-kit/command', async (importOriginal) => ({ ...(await importOriginal()), @@ -18,7 +18,7 @@ vi.mock('@agent-device/host-kit/retry', async (importOriginal) => ({ sleep: vi.fn(async () => {}), })); -import { resolveDaemonPaths, type DaemonPaths } from '../../config.ts'; +import { resolveDaemonPaths, type DaemonPaths } from '../../daemon/config.ts'; import { sendToDaemon, type DaemonRequest, type DaemonResponse } from '../daemon-client.ts'; import { attachActiveSessionAddressHint } from '../daemon-client-lifecycle.ts'; import { computeDaemonCodeSignature } from '@agent-device/host-kit/code-signature'; @@ -27,7 +27,7 @@ import { closeLoopbackServer, listenOnLoopback, supportsLoopbackBind, -} from '../../../__tests__/test-utils/loopback.ts'; +} from '../../__tests__/test-utils/loopback.ts'; import { AppError } from '@agent-device/kernel/errors'; import { runCmdDetachedMonitored, diff --git a/src/daemon/client/__tests__/daemon-client-timeout-route.test.ts b/src/daemon-client/__tests__/daemon-client-timeout-route.test.ts similarity index 98% rename from src/daemon/client/__tests__/daemon-client-timeout-route.test.ts rename to src/daemon-client/__tests__/daemon-client-timeout-route.test.ts index 3f8870f671..43a3131975 100644 --- a/src/daemon/client/__tests__/daemon-client-timeout-route.test.ts +++ b/src/daemon-client/__tests__/daemon-client-timeout-route.test.ts @@ -1,6 +1,6 @@ // Production-seam coverage for the real request-timeout route. // -// src/daemon/client/__tests__/daemon-client.test.ts covers `resolveRequestTimeoutHint` +// src/daemon-client/__tests__/daemon-client.test.ts covers `resolveRequestTimeoutHint` // as a pure formatter, but a pure-formatter test cannot catch a bug in // CLEANUP ELIGIBILITY: whether `cleanupTimedOutIosRunnerBuilds` (the Apple // xcodebuild pkill sweep) actually runs. This file spies on the real @@ -40,9 +40,9 @@ vi.mock('@agent-device/host-kit/command', async () => { import { AppError } from '@agent-device/kernel/errors'; import { sendRequest } from '../daemon-client-transport.ts'; -import type { DaemonRequest } from '../../daemon-request.ts'; +import type { DaemonRequest } from '../../daemon/daemon-request.ts'; import type { DaemonInfo } from '../daemon-client-metadata.ts'; -import type { DaemonPaths } from '../../config.ts'; +import type { DaemonPaths } from '../../daemon/config.ts'; const TIMEOUT_MS = 120; diff --git a/src/daemon/client/__tests__/daemon-client.test.ts b/src/daemon-client/__tests__/daemon-client.test.ts similarity index 99% rename from src/daemon/client/__tests__/daemon-client.test.ts rename to src/daemon-client/__tests__/daemon-client.test.ts index 9b9f6e93fa..15fa4c1d9c 100644 --- a/src/daemon/client/__tests__/daemon-client.test.ts +++ b/src/daemon-client/__tests__/daemon-client.test.ts @@ -10,7 +10,7 @@ import { closeLoopbackServer, listenOnLoopback, supportsLoopbackBind, -} from '../../../__tests__/test-utils/loopback.ts'; +} from '../../__tests__/test-utils/loopback.ts'; import { runCmdBackground } from '@agent-device/host-kit/command'; import { isProcessAlive, @@ -20,7 +20,7 @@ import { } from '@agent-device/host-kit/process'; import { sendToDaemon } from '../daemon-client.ts'; import { computeDaemonCodeSignature } from '@agent-device/host-kit/code-signature'; -import { downloadRemoteArtifact } from '../../../remote/daemon-artifacts.ts'; +import { downloadRemoteArtifact } from '../../remote/daemon-artifacts.ts'; import { cleanupFailedDaemonStartupMetadata, resolveDaemonStartupHint, @@ -31,10 +31,10 @@ import { resolveRequestTimeoutHint, shouldResetDaemonAfterRequestTimeout, } from '../daemon-client-timeout.ts'; -import { resolveDaemonPaths } from '../../config.ts'; -import { stopProcessForTakeover } from '../../daemon-process.ts'; +import { resolveDaemonPaths } from '../../daemon/config.ts'; +import { stopProcessForTakeover } from '../../daemon/daemon-process.ts'; import { findProjectRoot, readVersion } from '@agent-device/host-kit/version'; -import { mkdtempForTestSync } from '../../../__tests__/test-utils/tmp-dir.ts'; +import { mkdtempForTestSync } from '../../__tests__/test-utils/tmp-dir.ts'; // readProcessStartTime/readProcessCommand shell out to `ps` with a 1s // timeout (see host-process.ts). isAgentDeviceDaemonProcess re-reads both for diff --git a/src/daemon/client/__tests__/daemon-launch-spec.test.ts b/src/daemon-client/__tests__/daemon-launch-spec.test.ts similarity index 100% rename from src/daemon/client/__tests__/daemon-launch-spec.test.ts rename to src/daemon-client/__tests__/daemon-launch-spec.test.ts diff --git a/src/daemon/client/daemon-client-lifecycle.ts b/src/daemon-client/daemon-client-lifecycle.ts similarity index 99% rename from src/daemon/client/daemon-client-lifecycle.ts rename to src/daemon-client/daemon-client-lifecycle.ts index 23a9f57e54..fc51a7db7a 100644 --- a/src/daemon/client/daemon-client-lifecycle.ts +++ b/src/daemon-client/daemon-client-lifecycle.ts @@ -3,8 +3,8 @@ import net from 'node:net'; import os from 'node:os'; import path from 'node:path'; import { AppError, normalizeError } from '@agent-device/kernel/errors'; -import { readReplayDivergenceResume } from '../../core/replay-divergence.ts'; -import type { DaemonRequest, DaemonResponse } from '../daemon-request.ts'; +import { readReplayDivergenceResume } from '../core/replay-divergence.ts'; +import type { DaemonRequest, DaemonResponse } from '../daemon/daemon-request.ts'; import { runCmdDetachedMonitored, type ExecDetachedExit, @@ -14,7 +14,7 @@ import { emitDiagnostic } from '@agent-device/host-kit/diagnostics'; import { sleep } from '@agent-device/host-kit/retry'; import { readVersion } from '@agent-device/host-kit/version'; -import { findUnrecoveredRepairCommitFailure } from '../session-store.ts'; +import { findUnrecoveredRepairCommitFailure } from '../daemon/session-repair-tombstone.ts'; import { resolveDaemonPaths, resolveDaemonServerMode, @@ -22,7 +22,7 @@ import { type DaemonPaths, type DaemonServerMode, type DaemonTransportPreference, -} from '../config.ts'; +} from '../daemon/config.ts'; import { resolveDaemonLaunchSpec, resolveLocalDaemonCodeSignature } from './daemon-launch-spec.ts'; import { PUBLIC_COMMANDS } from '@agent-device/command-registry/catalog'; diff --git a/src/daemon/client/daemon-client-metadata.ts b/src/daemon-client/daemon-client-metadata.ts similarity index 99% rename from src/daemon/client/daemon-client-metadata.ts rename to src/daemon-client/daemon-client-metadata.ts index 9c1549d4f8..7d9d818c35 100644 --- a/src/daemon/client/daemon-client-metadata.ts +++ b/src/daemon-client/daemon-client-metadata.ts @@ -1,9 +1,9 @@ import fs from 'node:fs'; import { shellQuote } from '@agent-device/host-kit/command'; import { emitDiagnostic } from '@agent-device/host-kit/diagnostics'; -import { isAgentDeviceDaemonProcess, stopProcessForTakeover } from '../daemon-process.ts'; +import { isAgentDeviceDaemonProcess, stopProcessForTakeover } from '../daemon/daemon-process.ts'; -import { resolveDaemonPaths, type DaemonPaths, type DaemonServerMode } from '../config.ts'; +import { resolveDaemonPaths, type DaemonPaths, type DaemonServerMode } from '../daemon/config.ts'; export type DaemonInfo = { port?: number; diff --git a/src/daemon/client/daemon-client-progress.ts b/src/daemon-client/daemon-client-progress.ts similarity index 97% rename from src/daemon/client/daemon-client-progress.ts rename to src/daemon-client/daemon-client-progress.ts index ebcbed4e47..4e9d0bcfe5 100644 --- a/src/daemon/client/daemon-client-progress.ts +++ b/src/daemon-client/daemon-client-progress.ts @@ -4,13 +4,13 @@ import type { RequestProgressSink } from '@agent-device/contracts/progress'; import type http from 'node:http'; import type { Socket } from 'node:net'; import { AppError } from '@agent-device/kernel/errors'; -import type { DaemonRequest, DaemonResponse } from '../daemon-request.ts'; +import type { DaemonRequest, DaemonResponse } from '../daemon/daemon-request.ts'; import { consumeTextLines } from '@agent-device/host-kit/transport'; import { isDaemonProgressEnvelope, isDaemonResponseEnvelope, shouldStreamRequestProgress, -} from '../request-progress-protocol.ts'; +} from '../daemon/request-progress-protocol.ts'; type ProgressLineReader = { handleLine(line: string): boolean; diff --git a/src/daemon/client/daemon-client-rpc.test.ts b/src/daemon-client/daemon-client-rpc.test.ts similarity index 100% rename from src/daemon/client/daemon-client-rpc.test.ts rename to src/daemon-client/daemon-client-rpc.test.ts diff --git a/src/daemon/client/daemon-client-rpc.ts b/src/daemon-client/daemon-client-rpc.ts similarity index 95% rename from src/daemon/client/daemon-client-rpc.ts rename to src/daemon-client/daemon-client-rpc.ts index 6bada6f385..fd6a93adb4 100644 --- a/src/daemon/client/daemon-client-rpc.ts +++ b/src/daemon-client/daemon-client-rpc.ts @@ -6,15 +6,15 @@ import { } from '@agent-device/kernel/errors'; import { sanitizeErrorCause } from '@agent-device/kernel/redaction'; import { createRequestId } from '@agent-device/host-kit/diagnostics'; -import type { DaemonRequest, DaemonResponse } from '../daemon-request.ts'; -import { materializeRemoteArtifacts } from '../../remote/daemon-artifacts.ts'; -import { localizeRemoteDaemonError } from '../../remote/remote-request-diagnostics.ts'; +import type { DaemonRequest, DaemonResponse } from '../daemon/daemon-request.ts'; +import { materializeRemoteArtifacts } from '../remote/daemon-artifacts.ts'; +import { localizeRemoteDaemonError } from '../remote/remote-request-diagnostics.ts'; import type { DaemonInfo } from './daemon-client-metadata.ts'; import { leaseScopeFromRequest, leaseScopeToLeaseRpcParams, type LeaseRpcCommand, -} from '../../core/lease-scope.ts'; +} from '../core/lease-scope.ts'; export function handleDaemonHttpResponseBody( body: string, diff --git a/src/daemon/client/daemon-client-timeout.ts b/src/daemon-client/daemon-client-timeout.ts similarity index 97% rename from src/daemon/client/daemon-client-timeout.ts rename to src/daemon-client/daemon-client-timeout.ts index 8dbf63cb6d..6926be1449 100644 --- a/src/daemon/client/daemon-client-timeout.ts +++ b/src/daemon-client/daemon-client-timeout.ts @@ -2,10 +2,10 @@ import { AppError } from '@agent-device/kernel/errors'; import { runCmdSync } from '@agent-device/host-kit/command'; import { emitDiagnostic } from '@agent-device/host-kit/diagnostics'; -import { isAgentDeviceDaemonProcess } from '../daemon-process.ts'; +import { isAgentDeviceDaemonProcess } from '../daemon/daemon-process.ts'; import { PUBLIC_COMMANDS } from '@agent-device/command-registry/catalog'; import { resolveCommandTimeoutPolicy } from '@agent-device/command-registry/registry'; -import type { DaemonPaths } from '../config.ts'; +import type { DaemonPaths } from '../daemon/config.ts'; import type { PlatformSelector } from '@agent-device/kernel/device'; import { removeDaemonInfo, @@ -104,7 +104,7 @@ export function shouldResetDaemonAfterRequestTimeout(command: string | undefined // real pkill/process-kill side effects, so its wording is verified through // this pure sub-function rather than the full timeout path (see also the // production-seam route tests in -// src/daemon/client/__tests__/daemon-client-timeout-route.test.ts, which +// src/daemon-client/__tests__/daemon-client-timeout-route.test.ts, which // prove the cleanup-eligibility side of this contract that a pure formatter // test cannot). export function resolveRequestTimeoutHint(params: { diff --git a/src/daemon/client/daemon-client-transport.ts b/src/daemon-client/daemon-client-transport.ts similarity index 99% rename from src/daemon/client/daemon-client-transport.ts rename to src/daemon-client/daemon-client-transport.ts index b386f32072..5cc3137bb1 100644 --- a/src/daemon/client/daemon-client-transport.ts +++ b/src/daemon-client/daemon-client-transport.ts @@ -2,9 +2,9 @@ import type { RequestProgressSink } from '@agent-device/contracts/progress'; import net from 'node:net'; import { AppError } from '@agent-device/kernel/errors'; import { loadNodeHttpRequester, readNodeHttpResponseBody } from '@agent-device/host-kit/transport'; -import type { DaemonRequest, DaemonResponse } from '../daemon-request.ts'; +import type { DaemonRequest, DaemonResponse } from '../daemon/daemon-request.ts'; import { emitDiagnostic } from '@agent-device/host-kit/diagnostics'; -import type { DaemonPaths, DaemonTransportPreference } from '../config.ts'; +import type { DaemonPaths, DaemonTransportPreference } from '../daemon/config.ts'; import { readDaemonHttpProgressResponse, readDaemonSocketProgressResponse, diff --git a/src/daemon/client/daemon-client.ts b/src/daemon-client/daemon-client.ts similarity index 98% rename from src/daemon/client/daemon-client.ts rename to src/daemon-client/daemon-client.ts index 6505129954..95a4ad4728 100644 --- a/src/daemon/client/daemon-client.ts +++ b/src/daemon-client/daemon-client.ts @@ -2,7 +2,7 @@ import type { RequestProgressSink } from '@agent-device/contracts/progress'; import type { DaemonRequest as SharedDaemonRequest, DaemonResponse as SharedDaemonResponse, -} from '../daemon-request.ts'; +} from '../daemon/daemon-request.ts'; import type { AgentDeviceDaemonTransportContext } from '@agent-device/contracts/client'; import { AppError } from '@agent-device/kernel/errors'; import { @@ -13,7 +13,7 @@ import { import { INTERNAL_COMMANDS, PUBLIC_COMMANDS } from '@agent-device/command-registry/catalog'; import { resolveCommandTimeoutPolicy } from '@agent-device/command-registry/registry'; import { resolveCommandRequestTimeoutMs } from '@agent-device/command-registry/timeout-policy'; -import { prepareRemoteRequestArtifacts } from '../../remote/daemon-artifacts.ts'; +import { prepareRemoteRequestArtifacts } from '../remote/daemon-artifacts.ts'; import { attachActiveSessionAddressHint, attachRepairSessionAddressHint, diff --git a/src/daemon/client/daemon-launch-spec.ts b/src/daemon-client/daemon-launch-spec.ts similarity index 100% rename from src/daemon/client/daemon-launch-spec.ts rename to src/daemon-client/daemon-launch-spec.ts diff --git a/src/daemon/__tests__/http-server-tenant-trust.test.ts b/src/daemon/__tests__/http-server-tenant-trust.test.ts index 6980fbce7f..1946957153 100644 --- a/src/daemon/__tests__/http-server-tenant-trust.test.ts +++ b/src/daemon/__tests__/http-server-tenant-trust.test.ts @@ -3,7 +3,7 @@ import assert from 'node:assert/strict'; import fs from 'node:fs'; import path from 'node:path'; import { createDaemonHttpServer } from '../server/http-server.ts'; -import { resolveSessionRequestLogPath } from '../session-store.ts'; +import { resolveSessionRequestLogPath } from '../session-artifact-paths.ts'; import { safeSessionName } from '../session-paths.ts'; import { DAEMON_HTTP_TENANT_HEADER } from '@agent-device/contracts/daemon-http'; import type { DaemonRequest, DaemonResponse } from '../daemon-request.ts'; diff --git a/src/daemon/__tests__/request-diagnostics-http.test.ts b/src/daemon/__tests__/request-diagnostics-http.test.ts index 34c89bb1ca..9be771cbb4 100644 --- a/src/daemon/__tests__/request-diagnostics-http.test.ts +++ b/src/daemon/__tests__/request-diagnostics-http.test.ts @@ -4,7 +4,7 @@ import fs from 'node:fs'; import http from 'node:http'; import path from 'node:path'; import { createDaemonHttpServer } from '../server/http-server.ts'; -import { resolveSessionRequestLogPath } from '../session-store.ts'; +import { resolveSessionRequestLogPath } from '../session-artifact-paths.ts'; import { safeSessionName } from '../session-paths.ts'; import type { DaemonResponse } from '../daemon-request.ts'; import { diff --git a/src/daemon/__tests__/request-execution-scope.test.ts b/src/daemon/__tests__/request-execution-scope.test.ts index a835a28dbd..76009c31b9 100644 --- a/src/daemon/__tests__/request-execution-scope.test.ts +++ b/src/daemon/__tests__/request-execution-scope.test.ts @@ -19,7 +19,7 @@ import { createRequestExecutionScope, prepareLockedRequestScope, } from '../request-execution-scope.ts'; -import { resolveSessionRequestLogPath } from '../session-store.ts'; +import { resolveSessionRequestLogPath } from '../session-artifact-paths.ts'; import type { DaemonRequest } from '../daemon-request.ts'; import { mkdtempForTestSync } from '../../__tests__/test-utils/tmp-dir.ts'; import { makeTestScreenRecordingResource } from '../../__tests__/test-utils/screen-recording-live-handle.ts'; diff --git a/src/daemon/replay/internal/__tests__/session-replay-repair-transaction.test.ts b/src/daemon/replay/internal/__tests__/session-replay-repair-transaction.test.ts index de2188f6f1..36be862cf6 100644 --- a/src/daemon/replay/internal/__tests__/session-replay-repair-transaction.test.ts +++ b/src/daemon/replay/internal/__tests__/session-replay-repair-transaction.test.ts @@ -6,7 +6,7 @@ * process-level keep-alive (Fix 1's daemon * teardown guard) is a different architectural layer — a client-side process * manager, not session/script state — and is covered separately in - * `src/daemon/client/__tests__/daemon-client-lifecycle.test.ts` + * `src/daemon-client/__tests__/daemon-client-lifecycle.test.ts` * ("keeps an owned ephemeral daemon alive and hints its --state-dir..."). * * Fix 1 (session-side): a divergence never deletes the session — it stays in diff --git a/src/daemon/replay/internal/__tests__/session-replay-runtime.test.ts b/src/daemon/replay/internal/__tests__/session-replay-runtime.test.ts index 58a6ebdf45..e2c6aca59e 100644 --- a/src/daemon/replay/internal/__tests__/session-replay-runtime.test.ts +++ b/src/daemon/replay/internal/__tests__/session-replay-runtime.test.ts @@ -64,7 +64,7 @@ test('a successful replay prints one line with the step count and wall time', as // producer (`completeReplayRun`'s `sessionStore.get(sessionName)` check), not // asserted against a hand-crafted fixture — deleting that line would fail // these, unlike the client-lifecycle tests in -// `src/daemon/client/__tests__/daemon-client-lifecycle.test.ts`, which only prove the +// `src/daemon-client/__tests__/daemon-client-lifecycle.test.ts`, which only prove the // CLIENT'S reaction to a `sessionActive` value it is handed. --- test('a close-less replay reports sessionActive: true (real producer, session still in the store)', async () => { diff --git a/src/daemon/request-execution-scope.ts b/src/daemon/request-execution-scope.ts index fd1e01f205..654a4e9be6 100644 --- a/src/daemon/request-execution-scope.ts +++ b/src/daemon/request-execution-scope.ts @@ -37,11 +37,8 @@ import { shouldRecordEventForRequest, } from './session-event-log.ts'; import type { LeaseRegistry } from './lease-registry.ts'; -import { - resolveSessionRequestLog, - resolveSessionRunnerLogPath, - type SessionStore, -} from './session-store.ts'; +import { type SessionStore } from './session-store.ts'; +import { resolveSessionRequestLog, resolveSessionRunnerLogPath } from './session-artifact-paths.ts'; import type { DaemonRequest, DaemonResponse } from './daemon-request.ts'; import type { SessionState } from './session-state.ts'; import { teardownSessionResources } from './session-teardown.ts'; diff --git a/src/daemon/server/daemon-runtime.ts b/src/daemon/server/daemon-runtime.ts index c9ec4512f4..afe457fa77 100644 --- a/src/daemon/server/daemon-runtime.ts +++ b/src/daemon/server/daemon-runtime.ts @@ -1,6 +1,7 @@ import crypto from 'node:crypto'; import { asAppError, AppError } from '@agent-device/kernel/errors'; -import { resolveSessionRequestLogPath, SessionStore } from '../session-store.ts'; +import { SessionStore } from '../session-store.ts'; +import { resolveSessionRequestLogPath } from '../session-artifact-paths.ts'; import { resolveDaemonPaths, resolveDaemonServerMode } from '../config.ts'; import { createDaemonHttpServer } from './http-server.ts'; import { trackDownloadableArtifact } from '../artifact-tracking.ts'; diff --git a/src/daemon/session-artifact-paths.ts b/src/daemon/session-artifact-paths.ts new file mode 100644 index 0000000000..9e6e404088 --- /dev/null +++ b/src/daemon/session-artifact-paths.ts @@ -0,0 +1,51 @@ +import path from 'node:path'; +import type { DiagnosticsRecordRef } from '@agent-device/kernel/errors'; +import { safeSessionName } from './session-paths.ts'; + +/** Path to session-scoped platform subprocess output, such as Apple runner xcodebuild logs. */ +export function resolveSessionRunnerLogPath(sessionDir: string): string { + return path.join(sessionDir, 'runner.log'); +} + +/** Path to request-scoped daemon diagnostics for this session. */ +export function resolveSessionRequestLogPath( + sessionDir: string, + requestId: string | undefined, +): string { + const safeRequestId = safeSessionName(requestId && requestId.length > 0 ? requestId : 'unknown'); + return path.join(sessionDir, 'requests', `${safeRequestId}.ndjson`); +} + +/** + * The request diagnostics record for one request: the path it is written to on + * this host, and the locator a remote caller fetches the same record by + * (#1801). Built in one call so the two can never name different records. + */ +export function resolveSessionRequestLog(params: { + sessionDir: string; + session: string; + requestId: string | undefined; +}): { path: string; ref: DiagnosticsRecordRef } { + return { + path: resolveSessionRequestLogPath(params.sessionDir, params.requestId), + ref: { + session: params.session, + requestId: params.requestId && params.requestId.length > 0 ? params.requestId : 'unknown', + }, + }; +} + +/** + * Where a CLIENT keeps its own copy of a remote daemon's request diagnostics + * record (#1801). Mirrors the daemon-side layout under the caller's state dir + * so a CI job can archive `remote-diagnostics/` wholesale. + */ +export function resolveRemoteRequestDiagnosticsPath( + stateDir: string, + ref: DiagnosticsRecordRef, +): string { + return resolveSessionRequestLogPath( + path.join(stateDir, 'remote-diagnostics', safeSessionName(ref.session)), + ref.requestId, + ); +} diff --git a/src/daemon/session-lifecycle/internal/inventory.ts b/src/daemon/session-lifecycle/internal/inventory.ts index 4651e6f786..835b844dce 100644 --- a/src/daemon/session-lifecycle/internal/inventory.ts +++ b/src/daemon/session-lifecycle/internal/inventory.ts @@ -27,7 +27,8 @@ import { canonicalLocalDeviceKey } from '../../device-claim-paths.ts'; import { deviceClaimIdentity } from '../../device-claims.ts'; import type { DaemonRequest, DaemonResponse } from '../../daemon-request.ts'; import type { SessionRef } from '../../session-state.ts'; -import { resolveSessionRunnerLogPath, SessionStore } from '../../session-store.ts'; +import { SessionStore } from '../../session-store.ts'; +import { resolveSessionRunnerLogPath } from '../../session-artifact-paths.ts'; import { requireSessionOrExplicitSelector, resolveCommandDevice, diff --git a/src/daemon/session-lifecycle/internal/session-open-execution.ts b/src/daemon/session-lifecycle/internal/session-open-execution.ts index d48e272b30..232e4a312b 100644 --- a/src/daemon/session-lifecycle/internal/session-open-execution.ts +++ b/src/daemon/session-lifecycle/internal/session-open-execution.ts @@ -19,11 +19,11 @@ import { } from '../../session-script-publication-capability.ts'; import { isRequestCanceled } from '@agent-device/host-kit/request'; import { createRequestCanceledError } from '@agent-device/kernel/errors'; +import { SessionStore } from '../../session-store.ts'; import { resolveSessionRequestLogPath, resolveSessionRunnerLogPath, - SessionStore, -} from '../../session-store.ts'; +} from '../../session-artifact-paths.ts'; import { countConfiguredRuntimeHints, runtimeHintValues, diff --git a/src/daemon/session-repair-tombstone.ts b/src/daemon/session-repair-tombstone.ts new file mode 100644 index 0000000000..83459e1826 --- /dev/null +++ b/src/daemon/session-repair-tombstone.ts @@ -0,0 +1,91 @@ +import path from 'node:path'; +import fs from 'node:fs'; + +/** + * ADR 0012 decision 6, R7 (C5a): a reaped repair session leaves this bounded + * marker so the next command on the same key gets `REPAIR_SESSION_EXPIRED` + + * re-run guidance, never a bare `SESSION_NOT_FOUND`. Bounded by `expiresAt` + * so an old tombstone never shadows an unrelated future session name. + */ +export type RepairSessionTombstone = { + owner: string; + reapedAt: number; + expiresAt: number; + sourcePath?: string; + /** + * ADR 0012 decision 6 (BLOCKER 2): set iff this tombstone marks a COMPLETE + * transaction whose commit FAILED at teardown (no-clobber refusal, bare + * `@ref`, or a filesystem write error) — as opposed to a transaction that + * was merely reaped before it ever finished. Preserves the real failure + * instead of losing it behind a generic "reaped before it was finalized" + * expiry, so `repairExpiredIfTombstoned` can surface a distinct + * `REPAIR_COMMIT_FAILED` with the actual cause. + */ + commitFailure?: { code: string; message: string }; +}; + +/** The tombstone file inside one session directory. Single owner of the file name. */ +export function resolveRepairTombstonePath(sessionDir: string): string { + return path.join(sessionDir, 'repair-tombstone.json'); +} + +/** Parses/validates a tombstone file at `tombstonePath`; `undefined` if missing, malformed, or expired. */ +export function readRepairTombstoneFile(tombstonePath: string): RepairSessionTombstone | undefined { + let raw: string; + try { + raw = fs.readFileSync(tombstonePath, 'utf8'); + } catch { + return undefined; + } + let parsed: RepairSessionTombstone; + try { + parsed = JSON.parse(raw) as RepairSessionTombstone; + } catch { + return undefined; + } + if (typeof parsed?.expiresAt !== 'number' || parsed.expiresAt <= Date.now()) return undefined; + return parsed; +} + +/** + * ADR 0012 decision 6 (BLOCKER 2, third follow-up): scans every session + * subdirectory under `sessionsDir` for a non-expired repair tombstone that + * records an UNRECOVERED commit failure (`commitFailure` set) — used by the + * CLIENT side of the daemon boundary (`cleanupDaemonAfterRequest` in + * `daemon-client-lifecycle.ts`), which has no live `SessionStore`/session name + * to key off of, only the filesystem path an owned ephemeral daemon was given. + * An owned ephemeral state dir services exactly one repair transaction at a + * time, so the first match found is returned. + * + * Lives below both the store and the client (#2342) so reading the artifact a + * reaped transaction left on disk does not oblige either side to import the + * other. + */ +export function findUnrecoveredRepairCommitFailure(sessionsDir: string): + | { + sessionName: string; + tombstone: RepairSessionTombstone & { + commitFailure: NonNullable; + }; + } + | undefined { + let entries: fs.Dirent[]; + try { + entries = fs.readdirSync(sessionsDir, { withFileTypes: true }); + } catch { + return undefined; + } + for (const entry of entries) { + if (!entry.isDirectory()) continue; + const tombstone = readRepairTombstoneFile( + resolveRepairTombstonePath(path.join(sessionsDir, entry.name)), + ); + if (tombstone?.commitFailure) { + return { + sessionName: entry.name, + tombstone: { ...tombstone, commitFailure: tombstone.commitFailure }, + }; + } + } + return undefined; +} diff --git a/src/daemon/session-store.ts b/src/daemon/session-store.ts index 83b1985704..646e31a678 100644 --- a/src/daemon/session-store.ts +++ b/src/daemon/session-store.ts @@ -1,10 +1,15 @@ import path from 'node:path'; import fs from 'node:fs'; -import { AppError, type DiagnosticsRecordRef } from '@agent-device/kernel/errors'; +import { AppError } from '@agent-device/kernel/errors'; import { emitDiagnostic } from '@agent-device/host-kit/diagnostics'; import type { SessionRef, SessionRuntimeHints, SessionState } from './session-state.ts'; import { recordActionEntry, type RecordActionEntry } from './session-action-recorder.ts'; import { expandSessionPath, isSafeSessionSegment, safeSessionName } from './session-paths.ts'; +import { + readRepairTombstoneFile, + resolveRepairTombstonePath, + type RepairSessionTombstone, +} from './session-repair-tombstone.ts'; import { NO_SCRIPT_PUBLICATION, isRepairCommittable } from './session-script-publication-state.ts'; import { effectiveWriteForce } from './session-script-publication-capability.ts'; import { @@ -27,29 +32,6 @@ import { type SessionEventLogPage, } from './session-event-log.ts'; -/** - * ADR 0012 decision 6, R7 (C5a): a reaped repair session leaves this bounded - * marker so the next command on the same key gets `REPAIR_SESSION_EXPIRED` + - * re-run guidance, never a bare `SESSION_NOT_FOUND`. Bounded by `expiresAt` - * so an old tombstone never shadows an unrelated future session name. - */ -export type RepairSessionTombstone = { - owner: string; - reapedAt: number; - expiresAt: number; - sourcePath?: string; - /** - * ADR 0012 decision 6 (BLOCKER 2): set iff this tombstone marks a COMPLETE - * transaction whose commit FAILED at teardown (no-clobber refusal, bare - * `@ref`, or a filesystem write error) — as opposed to a transaction that - * was merely reaped before it ever finished. Preserves the real failure - * instead of losing it behind a generic "reaped before it was finalized" - * expiry, so `repairExpiredIfTombstoned` can surface a distinct - * `REPAIR_COMMIT_FAILED` with the actual cause. - */ - commitFailure?: { code: string; message: string }; -}; - const REPAIR_TOMBSTONE_TTL_MS = 60 * 60_000; export class SessionStore { @@ -280,7 +262,7 @@ export class SessionStore { /** Returns a non-expired repair tombstone for `sessionName`, or `undefined`. */ readRepairTombstone(sessionName: string): RepairSessionTombstone | undefined { - return readTombstoneFile(this.repairTombstonePath(sessionName)); + return readRepairTombstoneFile(this.repairTombstonePath(sessionName)); } /** ADR 0012 R7 (C5a): a fresh `replay --save-script` on this key clears the tombstone. */ @@ -291,7 +273,7 @@ export class SessionStore { } private repairTombstonePath(sessionName: string): string { - return path.join(this.resolveSessionDir(sessionName), 'repair-tombstone.json'); + return resolveRepairTombstonePath(this.resolveSessionDir(sessionName)); } defaultTracePath(session: SessionState): string { @@ -357,108 +339,3 @@ export class SessionStore { return session.name; } } - -/** Parses/validates a tombstone file at `tombstonePath`; `undefined` if missing, malformed, or expired. */ -function readTombstoneFile(tombstonePath: string): RepairSessionTombstone | undefined { - let raw: string; - try { - raw = fs.readFileSync(tombstonePath, 'utf8'); - } catch { - return undefined; - } - let parsed: RepairSessionTombstone; - try { - parsed = JSON.parse(raw) as RepairSessionTombstone; - } catch { - return undefined; - } - if (typeof parsed?.expiresAt !== 'number' || parsed.expiresAt <= Date.now()) return undefined; - return parsed; -} - -/** - * ADR 0012 decision 6 (BLOCKER 2, third follow-up): scans every session - * subdirectory under `sessionsDir` for a non-expired repair tombstone that - * records an UNRECOVERED commit failure (`commitFailure` set) — used by the - * CLIENT side of the daemon boundary (`cleanupDaemonAfterRequest` in - * `daemon-client-lifecycle.ts`), which has no live `SessionStore`/session name - * to key off of, only the filesystem path an owned ephemeral daemon was given. - * An owned ephemeral state dir services exactly one repair transaction at a - * time, so the first match found is returned. - */ -export function findUnrecoveredRepairCommitFailure(sessionsDir: string): - | { - sessionName: string; - tombstone: RepairSessionTombstone & { - commitFailure: NonNullable; - }; - } - | undefined { - let entries: fs.Dirent[]; - try { - entries = fs.readdirSync(sessionsDir, { withFileTypes: true }); - } catch { - return undefined; - } - for (const entry of entries) { - if (!entry.isDirectory()) continue; - const tombstone = readTombstoneFile( - path.join(sessionsDir, entry.name, 'repair-tombstone.json'), - ); - if (tombstone?.commitFailure) { - return { - sessionName: entry.name, - tombstone: { ...tombstone, commitFailure: tombstone.commitFailure }, - }; - } - } - return undefined; -} - -/** Path to session-scoped platform subprocess output, such as Apple runner xcodebuild logs. */ -export function resolveSessionRunnerLogPath(sessionDir: string): string { - return path.join(sessionDir, 'runner.log'); -} - -/** Path to request-scoped daemon diagnostics for this session. */ -export function resolveSessionRequestLogPath( - sessionDir: string, - requestId: string | undefined, -): string { - const safeRequestId = safeSessionName(requestId && requestId.length > 0 ? requestId : 'unknown'); - return path.join(sessionDir, 'requests', `${safeRequestId}.ndjson`); -} - -/** - * The request diagnostics record for one request: the path it is written to on - * this host, and the locator a remote caller fetches the same record by - * (#1801). Built in one call so the two can never name different records. - */ -export function resolveSessionRequestLog(params: { - sessionDir: string; - session: string; - requestId: string | undefined; -}): { path: string; ref: DiagnosticsRecordRef } { - return { - path: resolveSessionRequestLogPath(params.sessionDir, params.requestId), - ref: { - session: params.session, - requestId: params.requestId && params.requestId.length > 0 ? params.requestId : 'unknown', - }, - }; -} - -/** - * Where a CLIENT keeps its own copy of a remote daemon's request diagnostics - * record (#1801). Mirrors the daemon-side layout under the caller's state dir - * so a CI job can archive `remote-diagnostics/` wholesale. - */ -export function resolveRemoteRequestDiagnosticsPath( - stateDir: string, - ref: DiagnosticsRecordRef, -): string { - return resolveSessionRequestLogPath( - path.join(stateDir, 'remote-diagnostics', safeSessionName(ref.session)), - ref.requestId, - ); -} diff --git a/src/remote/remote-request-diagnostics.ts b/src/remote/remote-request-diagnostics.ts index 6caf4fe59d..e257b9f89f 100644 --- a/src/remote/remote-request-diagnostics.ts +++ b/src/remote/remote-request-diagnostics.ts @@ -26,7 +26,7 @@ import { buildDaemonHttpTenantHeaders, buildDaemonHttpUrl, } from '@agent-device/contracts/daemon-http'; -import { resolveRemoteRequestDiagnosticsPath } from '../daemon/session-store.ts'; +import { resolveRemoteRequestDiagnosticsPath } from '../daemon/session-artifact-paths.ts'; const REMOTE_DIAGNOSTICS_FETCH_TIMEOUT_MS = 10_000; const REMOTE_DIAGNOSTICS_MAX_BYTES = 8 * 1024 * 1024; diff --git a/test/integration/android-emulator-e2e/live-observability-scenario.ts b/test/integration/android-emulator-e2e/live-observability-scenario.ts index dbc876587d..5ec79f54c1 100644 --- a/test/integration/android-emulator-e2e/live-observability-scenario.ts +++ b/test/integration/android-emulator-e2e/live-observability-scenario.ts @@ -3,7 +3,7 @@ import fs from 'node:fs'; import path from 'node:path'; import { PUBLIC_COMMANDS } from '@agent-device/command-registry/catalog'; -import { readDaemonInfo } from '../../../src/daemon/client/daemon-client-metadata.ts'; +import { readDaemonInfo } from '../../../src/daemon-client/daemon-client-metadata.ts'; import { resolveDaemonPaths } from '../../../src/daemon/config.ts'; import { collectPagedEventTimeline, diff --git a/test/integration/ios-simulator-e2e/live-runner.ts b/test/integration/ios-simulator-e2e/live-runner.ts index 5140956194..3234ec16fb 100644 --- a/test/integration/ios-simulator-e2e/live-runner.ts +++ b/test/integration/ios-simulator-e2e/live-runner.ts @@ -5,7 +5,7 @@ import { pathToFileURL } from 'node:url'; import type { AgentDeviceDaemonTransport } from '@agent-device/contracts/client'; import { PUBLIC_COMMANDS } from '@agent-device/command-registry/catalog'; -import { sendToDaemon } from '../../../src/daemon/client/daemon-client.ts'; +import { sendToDaemon } from '../../../src/daemon-client/daemon-client.ts'; import { assertPngFile } from '../provider-scenarios/assertions.ts'; import { assertFilesDiffer, diff --git a/test/wire-compat/README.md b/test/wire-compat/README.md index 9c12ce9856..a8e7d1fa1b 100644 --- a/test/wire-compat/README.md +++ b/test/wire-compat/README.md @@ -14,7 +14,7 @@ ledger honest from opposite sides. ## Why this boundary and not the local daemon -A local daemon cannot skew. `isReusableDaemonInfo` (`src/daemon/client/daemon-client-lifecycle.ts`) +A local daemon cannot skew. `isReusableDaemonInfo` (`src/daemon-client/daemon-client-lifecycle.ts`) takes over any daemon whose package version differs from the client's, so the two are always the same build. diff --git a/test/wire-compat/closure-policy.ts b/test/wire-compat/closure-policy.ts index 20d565e028..c011b2de5d 100644 --- a/test/wire-compat/closure-policy.ts +++ b/test/wire-compat/closure-policy.ts @@ -41,7 +41,7 @@ export const WIRE_CLOSURE_WAIVERS: Readonly> = { 'Error-code vocabulary is carried as a plain string in DaemonError.code; adding a code cannot change the envelope a peer parses.', 'packages/kernel/src/errors.ts#AppErrorDetails': 'Local detail bag behind AppError; the wire form is DaemonError.details, typed as Record and listed.', - 'src/daemon/client/daemon-client-metadata.ts#DaemonInfo': + 'src/daemon-client/daemon-client-metadata.ts#DaemonInfo': 'Client-side record of where a daemon is listening (pid, ports, state dir). Never serialized into a request or response.', 'src/daemon/daemon-request.ts#DaemonRequest': 'Server-side narrowing of the listed kernel DaemonRequest; the declaration that fixes the shape is packages/kernel/src/contracts.ts#DaemonRequest.', diff --git a/test/wire-compat/ledger.json b/test/wire-compat/ledger.json index 2b8f67cbb6..d13ec18476 100644 --- a/test/wire-compat/ledger.json +++ b/test/wire-compat/ledger.json @@ -48,23 +48,23 @@ "packages/kernel/src/errors.ts#readDiagnosticsRecordRef": "sha256:3a210f401221c0a15eb75d488450034885c7b73be39f5e93424c72874d14c395", "src/commands/cli-grammar/types.ts#DaemonCommandRequest": "sha256:ea3f4118244711f0eaec0e471f62b0de42ca97181c085df165d37b63301a4619", "src/core/lease-scope.ts#LeaseRpcCommand": "sha256:714f66efa725a5d0654e492a76daeb4059caad31b79562cfc9133029ea95bfe5", - "src/daemon/client/daemon-client-progress.ts#ProgressResponseFormat": "sha256:6d7a99ac13fd422671fd86f8fe093efa40ca369f4ae1f97c98d40a7132543637", - "src/daemon/client/daemon-client-progress.ts#createInvalidDaemonResponseError": "sha256:0af812346b667a23fdfef641b242cd86d07edaa05eb928c4c531628c09a4031b", - "src/daemon/client/daemon-client-progress.ts#shouldReadDaemonProgressStream": "sha256:6811404f41d8db3fa6fc751332c7186a2399ec7ecbc4e9c547d9cb9f14dbb350", - "src/daemon/client/daemon-client-rpc.ts#appErrorFromDaemonError": "sha256:ac4761006c71d93ccba9f8e37cbd4cf48283dbb22737c163dc9c1a878e154eea", - "src/daemon/client/daemon-client-rpc.ts#buildHttpRpcPayload": "sha256:efa9a5da4c7288cceae5656b468946ad3a2af0f0f0fb940f8a37987f922aa4b8", - "src/daemon/client/daemon-client-rpc.ts#buildLeaseRpcParams": "sha256:1755f46be8c62e7a8eb409e9a94d61781c96ccbef3306702121aa94264d63267", - "src/daemon/client/daemon-client-rpc.ts#handleDaemonHttpResponseBody": "sha256:8f1c4bba1918545db6296ad29b95f0baa9f949e7c23bc04d59e261130f14611e", - "src/daemon/client/daemon-client-rpc.ts#isLeaseRpcCommand": "sha256:955dc1b73462d40593e5728dbcbaeec1307944b08262a22627e84cc5dc507e8f", - "src/daemon/client/daemon-client-rpc.ts#leaseRpcMethodForCommand": "sha256:3613555c6002c23ab5bec4b98bc1b4d6947014b52fa100176177f955cf762881", - "src/daemon/client/daemon-client-rpc.ts#parseDaemonHttpResponseBody": "sha256:0cbe39dbd09e321ec00854a1626a7d89b6344e59c6faabe2fe02504268b2d886", - "src/daemon/client/daemon-client-rpc.ts#rejectDaemonHttpRpcError": "sha256:83b0312fe88bc3b3497de799e23d8d14455f665b7cf880f4617cd62b181351cd", - "src/daemon/client/daemon-client-rpc.ts#resolveDaemonHttpResult": "sha256:296f9d376ce67c8cb20209bdf1c59c2423ffcfa35b5565a99e70271263149048", - "src/daemon/client/daemon-client-rpc.ts#toDaemonHttpRpcError": "sha256:888246763c48670e7da893054d025744654f8715c3b4906312617a2b5028316b", - "src/daemon/client/daemon-client-transport.ts#RemoteDaemonHealth": "sha256:38fe712390d247de59a74b9dc209fdf407ff5f9b1e32b698ae71ebc57569b66d", - "src/daemon/client/daemon-client-transport.ts#readDaemonHttpHealth": "sha256:5e75af39e96045ce7faba986c900744cee76cdaa1e0df360ea38f3c020520711", - "src/daemon/client/daemon-client-transport.ts#readHealthPayload": "sha256:5b15e14319b16aebf32d07336986a7f1f2a6b13cec1823ca9aa7c9f01bf1b0b6", - "src/daemon/client/daemon-client-transport.ts#readRemoteDaemonHealth": "sha256:b22111f693ecb65705195a66bce617e58441483ebfd67cf264d6900ba1dcb401", + "src/daemon-client/daemon-client-progress.ts#ProgressResponseFormat": "sha256:6d7a99ac13fd422671fd86f8fe093efa40ca369f4ae1f97c98d40a7132543637", + "src/daemon-client/daemon-client-progress.ts#createInvalidDaemonResponseError": "sha256:0af812346b667a23fdfef641b242cd86d07edaa05eb928c4c531628c09a4031b", + "src/daemon-client/daemon-client-progress.ts#shouldReadDaemonProgressStream": "sha256:6811404f41d8db3fa6fc751332c7186a2399ec7ecbc4e9c547d9cb9f14dbb350", + "src/daemon-client/daemon-client-rpc.ts#appErrorFromDaemonError": "sha256:ac4761006c71d93ccba9f8e37cbd4cf48283dbb22737c163dc9c1a878e154eea", + "src/daemon-client/daemon-client-rpc.ts#buildHttpRpcPayload": "sha256:efa9a5da4c7288cceae5656b468946ad3a2af0f0f0fb940f8a37987f922aa4b8", + "src/daemon-client/daemon-client-rpc.ts#buildLeaseRpcParams": "sha256:1755f46be8c62e7a8eb409e9a94d61781c96ccbef3306702121aa94264d63267", + "src/daemon-client/daemon-client-rpc.ts#handleDaemonHttpResponseBody": "sha256:8f1c4bba1918545db6296ad29b95f0baa9f949e7c23bc04d59e261130f14611e", + "src/daemon-client/daemon-client-rpc.ts#isLeaseRpcCommand": "sha256:955dc1b73462d40593e5728dbcbaeec1307944b08262a22627e84cc5dc507e8f", + "src/daemon-client/daemon-client-rpc.ts#leaseRpcMethodForCommand": "sha256:3613555c6002c23ab5bec4b98bc1b4d6947014b52fa100176177f955cf762881", + "src/daemon-client/daemon-client-rpc.ts#parseDaemonHttpResponseBody": "sha256:0cbe39dbd09e321ec00854a1626a7d89b6344e59c6faabe2fe02504268b2d886", + "src/daemon-client/daemon-client-rpc.ts#rejectDaemonHttpRpcError": "sha256:83b0312fe88bc3b3497de799e23d8d14455f665b7cf880f4617cd62b181351cd", + "src/daemon-client/daemon-client-rpc.ts#resolveDaemonHttpResult": "sha256:296f9d376ce67c8cb20209bdf1c59c2423ffcfa35b5565a99e70271263149048", + "src/daemon-client/daemon-client-rpc.ts#toDaemonHttpRpcError": "sha256:888246763c48670e7da893054d025744654f8715c3b4906312617a2b5028316b", + "src/daemon-client/daemon-client-transport.ts#RemoteDaemonHealth": "sha256:38fe712390d247de59a74b9dc209fdf407ff5f9b1e32b698ae71ebc57569b66d", + "src/daemon-client/daemon-client-transport.ts#readDaemonHttpHealth": "sha256:5e75af39e96045ce7faba986c900744cee76cdaa1e0df360ea38f3c020520711", + "src/daemon-client/daemon-client-transport.ts#readHealthPayload": "sha256:5b15e14319b16aebf32d07336986a7f1f2a6b13cec1823ca9aa7c9f01bf1b0b6", + "src/daemon-client/daemon-client-transport.ts#readRemoteDaemonHealth": "sha256:b22111f693ecb65705195a66bce617e58441483ebfd67cf264d6900ba1dcb401", "src/daemon/downloadable-artifact-http.ts#DownloadableArtifactHttpAuthorizer": "sha256:1b2702a929ca9170db2ca97c08e3ab67e17edb3ee75325a576c4c1b9cdbebb44", "src/daemon/downloadable-artifact-http.ts#DownloadableArtifactHttpRoute": "sha256:e63c4581ccde668913914149c9092d16ecf8a5cbd7ab33c6eb8617e77fc1015e", "src/daemon/downloadable-artifact-http.ts#handleArtifactDownload": "sha256:7f96d17b7c605230fb3cc21ceaa5b2e515d7445653ff95b2b0f6f15fa6214d4e", @@ -201,7 +201,7 @@ "rationale": "#2246 passes isDirectory only for the new test-artifacts type and uses the exact path the client materializer published. Existing screenshot and recording artifacts still report their requested file path; only the new directory response gains its suite-invocation segment." }, { - "declaration": "src/daemon/client/daemon-client-rpc.ts#buildHttpRpcPayload", + "declaration": "src/daemon-client/daemon-client-rpc.ts#buildHttpRpcPayload", "digest": "sha256:efa9a5da4c7288cceae5656b468946ad3a2af0f0f0fb940f8a37987f922aa4b8", "rationale": "#2110 adds the optional providerApp parameter to lease allocation. A released protocol-2 daemon ignores unknown JSON-RPC parameters, while requests without an initial app retain the previous payload exactly." }, @@ -226,12 +226,12 @@ "rationale": "#1801 and #1862 add optional fields to the normalized error the daemon serializes, most recently `cause`; they are omitted unless set, and a released client ignores them otherwise." }, { - "declaration": "src/daemon/client/daemon-client-rpc.ts#handleDaemonHttpResponseBody", + "declaration": "src/daemon-client/daemon-client-rpc.ts#handleDaemonHttpResponseBody", "digest": "sha256:8f1c4bba1918545db6296ad29b95f0baa9f949e7c23bc04d59e261130f14611e", "rationale": "#1801 client-side only: the RPC error branch now runs through `rejectDaemonHttpRpcError`, which reads a superset of the fields it read before and, for a remote daemon, replaces the daemon-host `logPath` with a caller-local copy. Every field a released daemon sends is still accepted; nothing new is required of the daemon." }, { - "declaration": "src/daemon/client/daemon-client-rpc.ts#toDaemonHttpRpcError", + "declaration": "src/daemon-client/daemon-client-rpc.ts#toDaemonHttpRpcError", "digest": "sha256:888246763c48670e7da893054d025744654f8715c3b4906312617a2b5028316b", "rationale": "#1801 and #1862 client-side only: this reads optional daemon error metadata, most recently the structured `cause`. Parsing of every previously read field is unchanged, so a released daemon's error payload is accepted exactly as before." }, @@ -251,7 +251,7 @@ "rationale": "#1801 and #1862 lift optional metadata onto the normalized error, most recently a redacted structured `cause`. No previously emitted field changes shape or value, and peers that do not know the field ignore it." }, { - "declaration": "src/daemon/client/daemon-client-rpc.ts#appErrorFromDaemonError", + "declaration": "src/daemon-client/daemon-client-rpc.ts#appErrorFromDaemonError", "digest": "sha256:ac4761006c71d93ccba9f8e37cbd4cf48283dbb22737c163dc9c1a878e154eea", "rationale": "#1862 client-side only: this rehydrates the new optional structured `cause` when present. Released daemons that omit it follow the unchanged path, and no request field or existing response field is narrowed." } diff --git a/test/wire-compat/surface.ts b/test/wire-compat/surface.ts index 540cf672f0..4cbfb85127 100644 --- a/test/wire-compat/surface.ts +++ b/test/wire-compat/surface.ts @@ -51,9 +51,9 @@ const REQUEST_DIAGNOSTICS_HTTP = 'src/daemon/request-diagnostics-http.ts'; const HTTP_REQUEST_TARGET = 'src/daemon/http-request-target.ts'; const REMOTE_REQUEST_DIAGNOSTICS = 'src/remote/remote-request-diagnostics.ts'; const PROGRESS_PROTOCOL = 'src/daemon/request-progress-protocol.ts'; -const CLIENT_RPC = 'src/daemon/client/daemon-client-rpc.ts'; -const CLIENT_PROGRESS = 'src/daemon/client/daemon-client-progress.ts'; -const CLIENT_TRANSPORT = 'src/daemon/client/daemon-client-transport.ts'; +const CLIENT_RPC = 'src/daemon-client/daemon-client-rpc.ts'; +const CLIENT_PROGRESS = 'src/daemon-client/daemon-client-progress.ts'; +const CLIENT_TRANSPORT = 'src/daemon-client/daemon-client-transport.ts'; const UPLOAD_CLIENT = 'src/remote/upload-client.ts'; const REMOTE_ARTIFACTS = 'src/remote/daemon-artifacts.ts'; const ARTIFACT_DOWNLOAD = 'src/remote/artifact-download.ts'; diff --git a/test/wire-compat/wire-mutations.test.ts b/test/wire-compat/wire-mutations.test.ts index 2ec8bf4ca2..0c868f3346 100644 --- a/test/wire-compat/wire-mutations.test.ts +++ b/test/wire-compat/wire-mutations.test.ts @@ -72,7 +72,7 @@ const MUTATIONS: readonly WireMutation[] = [ }, { breakClass: 'response parsing: the client narrows what a daemon may return', - file: 'src/daemon/client/daemon-client-rpc.ts', + file: 'src/daemon-client/daemon-client-rpc.ts', name: 'parseDaemonHttpResponseBody', from: 'error?: { message?: string; data?: Record }', to: 'error?: { message: string }', @@ -145,21 +145,21 @@ const MUTATIONS: readonly WireMutation[] = [ // why claiming "both sides" required these to be listed and proved. { breakClass: 'health consumer: the client stops reading the advertised protocol version', - file: 'src/daemon/client/daemon-client-transport.ts', + file: 'src/daemon-client/daemon-client-transport.ts', name: 'readHealthPayload', from: "typeof parsed.rpcProtocolVersion === 'number' ? parsed.rpcProtocolVersion : undefined", to: 'undefined', }, { breakClass: 'health consumer: the mismatch refusal ADR 0006 built is weakened', - file: 'src/daemon/client/daemon-client-transport.ts', + file: 'src/daemon-client/daemon-client-transport.ts', name: 'readRemoteDaemonHealth', from: 'health.rpcProtocolVersion !== DAEMON_RPC_PROTOCOL_VERSION', to: 'false', }, { breakClass: 'health consumer: the parsed health shape drops a released field', - file: 'src/daemon/client/daemon-client-transport.ts', + file: 'src/daemon-client/daemon-client-transport.ts', name: 'RemoteDaemonHealth', from: 'rpcProtocolVersion?: number;', to: '',