Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/adr/0030-process-lock-exclusion.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ require the same confirmation for every process using their own paths.

## Registration operations

[Shared retirement](../../src/daemon-registration-owner.ts) owns verified termination, protected
[Shared retirement](../../packages/daemon-contracts/src/daemon-registration-owner.ts) owns verified termination, protected
metadata inspection and removal, and release. Takeover, failed startup, replay cleanup, timeout
reset and manual stop await its result. Abandoned recovery uses the same protected retirement
sequence without signaling a live process. Daemon publication and shutdown use functions bound
Expand Down
40 changes: 24 additions & 16 deletions docs/adr/0033-collocation-decision-table.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,33 +123,36 @@ already owns the operations behind the vocabulary.
### (root)

`#3288` assigns every process-root module a logical zone in
`scripts/layering/root-module-zones.ts` (`daemon-contracts` rank 2, `command-runtime` rank 3,
`scripts/layering/root-module-zones.ts` (`command-runtime` rank 3,
`platform-runtime` rank 4, …) while leaving the files physically in place. Per the maintainer
design decision, that reclassification is **not** completed collocation: the physical moves under
#3294 (child of #3276) replace each file's row with folder-derived ownership.
`daemon-diagnostics-scope.ts` has moved into `src/daemon-contracts/`; the runtime assembly pair
`runtime-command-surface.ts` / `runtime-factory.ts` has moved into `src/command-runtime/`.
The other rows below are keeps: the
daemon ⇄ client shared files (`daemon-policy-file.ts`, `provider-credential-fingerprint.ts`,
`request-progress-protocol.ts`) trace to #2559, which relocated the shared contracts **to the
process root** so the client stops importing `src/daemon/` at all; R78 keeps any client→daemon
The runtime assembly pair
`runtime-command-surface.ts` / `runtime-factory.ts` has moved into `src/command-runtime/`, and
the whole `daemon-contracts` zone has moved into `packages/daemon-contracts` — the folder and
package derive the zone, so every moved file's `ROOT_MODULE_ZONES` row is gone.
The daemon ⇄ client shared files that remain here (`provider-credential-fingerprint.ts`) trace
to #2559, which relocated the shared contracts **to the process root** so the client stops
importing `src/daemon/` at all; R78 keeps any client→daemon
edge at zero runtime and a five-line recorded type-only residue, and #3288's `daemon-contracts`
zone is the declaration of that shared-below-both ownership. Rows on path-keyed evidence alone
(the `platform-runtime-*` seams) say "retain for this batch": a guard's current path lists
constrain a relocation, they are not independent proof the placement is optimal.
zone was the declaration of that shared-below-both ownership — the extraction makes it
physical, with R11 and the package's dependency edges holding the same boundary. Rows on
path-keyed evidence alone (the `platform-runtime-*` seams) say "retain for this batch": a guard's
current path lists constrain a relocation, they are not independent proof the placement is
optimal.

| File | Decision | Reason |
| --- | --- | --- |
| `src/daemon-diagnostics-scope.ts` | moved → `src/daemon-contracts/daemon-diagnostics-scope.ts` ([#3297](https://github.com/callstack/agent-device/pull/3297)) | Physical move landed: the helper now lives under the folder that derives its `daemon-contracts` zone, so `topFolder` replaces the per-file `ROOT_MODULE_ZONES` row and an internal rename needs no ownership-table edit. |
| `src/daemon-policy-file.ts` | keep | Daemon ⇄ client shared contract from #2559, declared `daemon-contracts` by #3288 — the shared-below-both ownership the spine needs, since daemon-client (rank 5) dynamically imports it. |
| `src/daemon-diagnostics-scope.ts` | moved → `packages/daemon-contracts/src/daemon-diagnostics-scope.ts` ([#3297](https://github.com/callstack/agent-device/pull/3297) into `src/daemon-contracts/`, then the package extraction) | The whole `daemon-contracts` zone now lives as `packages/daemon-contracts`, so the package directory derives the zone and the per-file `ROOT_MODULE_ZONES` row stays deleted; an internal rename needs no ownership-table edit. |
| `src/daemon-policy-file.ts` | moved → `packages/daemon-contracts/src/daemon-policy-file.ts` | Daemon ⇄ client shared contract from #2559, declared `daemon-contracts` by #3288 — the shared-below-both ownership the spine needs, since daemon-client (rank 5) dynamically imports it. The extraction makes that placement physical: the package sits below both sides and its manifest exports own the boundary. |
| `src/daemon.ts` | keep | Entry point (`internal/daemon` bundle entry); composition roots stay in root by umbrella §4. |
| `src/platform-runtime-apple-runner-owner.ts` | retain for this batch | `platform-runtime-*` composition seam: R13's exact-importer rule and the ADR 0022 R76 inventory key on its path, so relocation requires coordinated ownership changes to both declarations — the rules constrain the move, they do not independently prove the placement. |
| `src/platform-runtime-daemon-lifecycle.ts` | retain for this batch | Same path-keyed R13/ADR 0022 seam: relocation is possible only with coordinated changes to those declarations. |
| `src/platform-runtime-device-boot.ts` | retain for this batch | Same path-keyed R13/ADR 0022 seam. |
| `src/platform-runtime-resource-cleanup.ts` | retain for this batch | Same path-keyed R13/ADR 0022 seam. |
| `src/provider-credential-fingerprint.ts` | keep | Daemon ⇄ client shared contract from the same #2559 decision as `daemon-policy-file.ts`; #3288 declares its zone beside the provider composition that reads the credentials it fingerprints. |
| `src/provider-limrun-runtime.ts` | keep | Proposed → provider-limrun, but there is no seam: the class's constructor self-builds the root dependency factory (`src/sdk/limrun-runtime-dependencies.ts`), which is the ADR 0019 composition seam — it loads the root's adb-host binder, core Android interactor, and platform-runtime app-state helpers that the package must not import. Moving the class means moving that composition, which is design, not a move. |
| `src/request-progress-protocol.ts` | keep | Daemon ⇄ client shared contract from the same #2559 decision — the client reads it statically (`daemon-client-progress.ts`) and the server through `src/daemon/server/`, so only #2559's shared-placement boundary and the wire-compat ledgers pin it; declared `daemon-contracts` by #3288. Contracts was considered and R18 keeps contracts free of envelope validation mechanics. |
| `src/request-progress-protocol.ts` | moved → `packages/daemon-contracts/src/request-progress-protocol.ts` | Daemon ⇄ client shared contract from the same #2559 decision — the client reads it statically (`daemon-client-progress.ts`) and the server through `src/daemon/server/`, so only #2559's shared-placement boundary and the wire-compat ledgers pin it; declared `daemon-contracts` by #3288 and now physically placed by the extraction. Contracts was considered and R18 keeps contracts free of envelope validation mechanics. The wire-ledger keys moved with the file. |
| `src/runtime-command-surface.ts` | moved → `src/command-runtime/runtime-command-surface.ts` ([#3299](https://github.com/callstack/agent-device/pull/3299)) | Physical move landed with `runtime-factory.ts` as one assembly group: the command-surface binding sits beside the factory it composes, under the folder that derives its `command-runtime` zone, so `topFolder` replaces the per-file `ROOT_MODULE_ZONES` row. |
| `src/runtime-factory.ts` | moved → `src/command-runtime/runtime-factory.ts` ([#3299](https://github.com/callstack/agent-device/pull/3299)) | Physical move landed: the runtime assembly now lives under the folder that derives its `command-runtime` zone, with the same row deletion through folder-derived ownership. |

Expand All @@ -173,12 +176,17 @@ files have no seam (published facade, public-type collision, root dependency fac
## Follow-ups (tracked on #3281)

- Physical root-pass moves under [#3294](https://github.com/callstack/agent-device/issues/3294)
(child of #3276): `daemon-diagnostics-scope.ts` has moved into `src/daemon-contracts/`
([#3297](https://github.com/callstack/agent-device/pull/3297)); the runtime assembly pair
(child of #3276): the runtime assembly pair
`runtime-command-surface.ts` / `runtime-factory.ts` is collocated in `src/command-runtime/`
([#3299](https://github.com/callstack/agent-device/pull/3299)). Each folder derives its zone,
replacing the moved files' `ROOT_MODULE_ZONES` rows; #3288's zone assignments alone were
classification, and each landed move completes its own file's collocation.
classification, and each landed move completes its own file's collocation. The
`daemon-contracts` side landed as a workspace package: the zone's ten files (`daemon-owner-cleanup`,
`daemon-policy-file`, `daemon-process`, `daemon-registration-owner`, `daemon-registration`,
`daemon-resolution`, `daemon-shutdown-report`, `request-progress-protocol`,
`session-repair-tombstone`, and `daemon-diagnostics-scope` from `src/daemon-contracts/`, first
moved there by [#3297](https://github.com/callstack/agent-device/pull/3297)) are now
`packages/daemon-contracts/src/` behind narrow per-file subpath exports.

## Refuted alternatives

Expand Down
1 change: 1 addition & 0 deletions examples/sdk/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
{ "path": "../../packages/capture-kit" },
{ "path": "../../packages/command-registry" },
{ "path": "../../packages/contracts" },
{ "path": "../../packages/daemon-contracts" },
{ "path": "../../packages/device-selection" },
{ "path": "../../packages/host-kit" },
{ "path": "../../packages/kernel" },
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,7 @@
"@agent-device/capture-kit": "workspace:*",
"@agent-device/command-registry": "workspace:*",
"@agent-device/contracts": "workspace:*",
"@agent-device/daemon-contracts": "workspace:*",
"@agent-device/device-selection": "workspace:*",
"@agent-device/host-kit": "workspace:*",
"@agent-device/kernel": "workspace:*",
Expand Down
2 changes: 1 addition & 1 deletion packages/contracts/src/request-progress.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* Progress events the daemon streams to a connected client while a request runs.
*
* These are wire values: `src/request-progress-protocol.ts` serializes them onto the
* These are wire values: `packages/daemon-contracts/src/request-progress-protocol.ts` serializes them onto the
* response stream and the CLI reconstructs them before handing them to the replay-test
* reporter registry. They therefore belong to neither side — the daemon's request-global
* progress plumbing carries them, and the replay-test scheduler produces and interprets
Expand Down
59 changes: 59 additions & 0 deletions packages/daemon-contracts/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
{
"name": "@agent-device/daemon-contracts",
"version": "0.0.0",
"private": true,
"type": "module",
"description": "On-disk and wire contracts a daemon process shares with its clients (#2559): state-dir and transport resolution, registration and ownership, process liveness, shutdown reports, the repair tombstone, progress-envelope framing, the diagnostics scope, and owner-cleanup vocabulary. Internal workspace package bundled into the published agent-device artifact.",
"dependencies": {
"@agent-device/command-registry": "workspace:*",
"@agent-device/contracts": "workspace:*",
"@agent-device/host-kit": "workspace:*",
"@agent-device/kernel": "workspace:*"
},
"exports": {
"./daemon-diagnostics-scope": {
"types": "./src/daemon-diagnostics-scope.ts",
"default": "./src/daemon-diagnostics-scope.ts"
},
"./daemon-owner-cleanup": {
"types": "./src/daemon-owner-cleanup.ts",
"default": "./src/daemon-owner-cleanup.ts"
},
"./daemon-policy-file": {
"types": "./src/daemon-policy-file.ts",
"default": "./src/daemon-policy-file.ts"
},
"./daemon-process": {
"types": "./src/daemon-process.ts",
"default": "./src/daemon-process.ts"
},
"./daemon-registration": {
"types": "./src/daemon-registration.ts",
"default": "./src/daemon-registration.ts"
},
"./daemon-registration-fixtures": {
"types": "./src/daemon-registration.fixtures.ts",
"default": "./src/daemon-registration.fixtures.ts"
},
"./daemon-registration-owner": {
"types": "./src/daemon-registration-owner.ts",
"default": "./src/daemon-registration-owner.ts"
},
"./daemon-resolution": {
"types": "./src/daemon-resolution.ts",
"default": "./src/daemon-resolution.ts"
},
"./daemon-shutdown-report": {
"types": "./src/daemon-shutdown-report.ts",
"default": "./src/daemon-shutdown-report.ts"
},
"./request-progress-protocol": {
"types": "./src/request-progress-protocol.ts",
"default": "./src/request-progress-protocol.ts"
},
"./session-repair-tombstone": {
"types": "./src/session-repair-tombstone.ts",
"default": "./src/session-repair-tombstone.ts"
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import path from 'node:path';
import { afterEach, beforeEach, test } from 'vitest';
import { emitDiagnostic } from '@agent-device/host-kit/diagnostics';
import { withDaemonDiagnosticsScope } from './daemon-diagnostics-scope.ts';
import { mkdtempForTestSync } from '../__tests__/test-utils/tmp-dir.ts';
import { mkdtempForTestSync } from './tmp-dir.fixtures.ts';

let dir: string;
let logPath: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import path from 'node:path';
import { expect, test } from 'vitest';
import { commandDescriptors } from '@agent-device/command-registry/registry';
import type { CommandDescriptor } from '@agent-device/command-registry/types';
import { mkdtempForTestSync } from './test-utils/tmp-dir.ts';
import { DAEMON_POLICY_ENV, loadDaemonPolicy, parseDaemonPolicy } from '../daemon-policy-file.ts';
import { mkdtempForTestSync } from './tmp-dir.fixtures.ts';
import { DAEMON_POLICY_ENV, loadDaemonPolicy, parseDaemonPolicy } from './daemon-policy-file.ts';

const SOURCE = '/etc/agent-device/policy.json';

Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import os from 'node:os';
import path from 'node:path';
import { afterEach, test } from 'vitest';
import { isProcessAlive, readProcessStartTime } from '@agent-device/host-kit/process';
import { isAgentDeviceDaemonProcess, stopDaemonProcess } from '../daemon-process.ts';
import { isAgentDeviceDaemonProcess, stopDaemonProcess } from './daemon-process.ts';

const TAKEOVER_TIMEOUTS = { termTimeoutMs: 5_000, killTimeoutMs: 2_000 };
const spawnedChildren: { child: ChildProcess; exited: Promise<void> }[] = [];
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import assert from 'node:assert/strict';
import { test } from 'vitest';
import { isAgentDeviceDaemonCommand } from '../daemon-process.ts';
import { isAgentDeviceDaemonCommand } from './daemon-process.ts';

test('isAgentDeviceDaemonCommand matches expected daemon command', () => {
assert.equal(isAgentDeviceDaemonCommand('node /tmp/agent-device/dist/src/daemon.js'), true);
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import {
type ProcessLockAcquisition,
} from '@agent-device/host-kit/file';
import { emitDiagnostic } from '@agent-device/host-kit/diagnostics';
import { withDaemonDiagnosticsScope } from './daemon-contracts/daemon-diagnostics-scope.ts';
import { withDaemonDiagnosticsScope } from './daemon-diagnostics-scope.ts';
import type { DaemonCodeOrigin } from '@agent-device/host-kit/code-signature';
import {
resolveDaemonPaths,
Expand Down
23 changes: 23 additions & 0 deletions packages/daemon-contracts/src/daemon-registration.fixtures.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import fs from 'node:fs';
import path from 'node:path';
import type { OwnerIdentity } from '@agent-device/host-kit/process';

/**
* Publishes the daemon registration a running daemon writes for its state dir.
* Claim ownership is only reachable through the daemon named there, so tests
* that exercise reachability have to stand one in.
*/
export function publishDaemonRegistration(stateDir: string, owner: OwnerIdentity): void {
fs.mkdirSync(stateDir, { recursive: true });
fs.writeFileSync(
path.join(stateDir, 'daemon.json'),
JSON.stringify({
port: 1,
transport: 'socket',
token: 'test-token',
pid: owner.pid,
...(owner.startTime === null ? {} : { processStartTime: owner.startTime }),
stateDir,
}),
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ import {
isSupersededDaemonOwner,
readRegisteredDaemonIdentity,
readRegisteredDaemonOwnership,
} from '../daemon-registration.ts';
import { publishDaemonRegistration } from './test-utils/device-claim-store.ts';
import { mkdtempForTestSync } from './test-utils/tmp-dir.ts';
} from './daemon-registration.ts';
import { publishDaemonRegistration } from './daemon-registration.fixtures.ts';
import { mkdtempForTestSync } from './tmp-dir.fixtures.ts';

const roots: string[] = [];

Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import fs from 'node:fs';
import path from 'node:path';
import { expect, test } from 'vitest';
import { readDaemonShutdownReport, buildDaemonShutdownReport } from '../daemon-shutdown-report.ts';
import { LeaseRegistry } from '../daemon/lease-registry.ts';
import { mkdtempForTestSync } from './test-utils/tmp-dir.ts';
import type { DeviceLease } from '@agent-device/contracts/device';
import { readDaemonShutdownReport, buildDaemonShutdownReport } from './daemon-shutdown-report.ts';
import { mkdtempForTestSync } from './tmp-dir.fixtures.ts';

const claim = {
deviceKey: 'local:android:none:emulator-5554',
Expand All @@ -14,11 +14,16 @@ const claim = {

test('round-trips provider release and device claim records without lease credentials', () => {
const stateDir = mkdtempForTestSync('agent-device-shutdown-report-');
const lease = new LeaseRegistry().allocateLease({
const lease: DeviceLease = {
leaseId: 'lease-1',
tenantId: 'tenant-a',
runId: 'run-1',
backend: 'android-instance',
leaseProvider: 'limrun',
});
createdAt: 0,
heartbeatAt: 0,
expiresAt: 0,
};

try {
const report = buildDaemonShutdownReport({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import fs from 'node:fs';
import path from 'node:path';
import { expect, test } from 'vitest';
import { AppError } from '@agent-device/kernel/errors';
import { mkdtempForTestSync } from './__tests__/test-utils/tmp-dir.ts';
import { mkdtempForTestSync } from './tmp-dir.fixtures.ts';
import {
findUnrecoveredRepairCommitFailure,
readRepairTombstoneFile,
Expand Down
12 changes: 12 additions & 0 deletions packages/daemon-contracts/src/tmp-dir.fixtures.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import fs from 'node:fs';
import fsPromises from 'node:fs/promises';
import os from 'node:os';
import path from 'node:path';

export async function mkdtempForTest(prefix: string): Promise<string> {
return fsPromises.mkdtemp(path.join(os.tmpdir(), prefix));
}

export function mkdtempForTestSync(prefix: string): string {
return fs.mkdtempSync(path.join(os.tmpdir(), prefix));
}
19 changes: 19 additions & 0 deletions packages/daemon-contracts/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"composite": true,
"noEmit": false,
"emitDeclarationOnly": true,
"declaration": true,
"declarationDir": "./dist-types",
"rootDir": "./src",
"tsBuildInfoFile": "./dist-types/index.tsbuildinfo"
},
"include": ["src"],
"references": [
{ "path": "../command-registry" },
{ "path": "../contracts" },
{ "path": "../host-kit" },
{ "path": "../kernel" }
]
}
18 changes: 18 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading