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
8 changes: 6 additions & 2 deletions docs/adr/0019-request-bound-platform-runtime.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,8 +153,12 @@ selection, R11/R13 package enumeration, and the composite typecheck project list
> screenshot density and pixel diffing, snapshot occlusion, mobile snapshot semantics,
> quality verdicts and backend capability tables. Snapshot *behavior* is capture domain, not
> contracts vocabulary, and host mechanics are host-kit's, not capture-kit's.
> - `@agent-device/provision-kit` owns provisioning mechanics — install-artifact acquisition
> (local paths, archives, guarded network downloads) and host toolchain probing.
> - `@agent-device/provision-kit` owns the provisioning domain — everything that gets a device
> and app ready to run: install-artifact acquisition (local paths, archives, guarded network
> downloads), host toolchain readiness probing, device boot-failure classification, and
> app-resolution caching. Platform packages may import provision-kit; provision-kit may not
> import a platform package or be imported by capture-kit (both directions planted red in the
> layering suite).
> - The enforced direction is `kernel < contracts < host-kit < capture-kit < provision-kit <
> platform/provider/daemon`.
> - Contracts stays vocabulary, plan models, and pure classification with no process,
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@
"check:unit": "pnpm test:unit && pnpm check:tmpdir-leaks && pnpm test:smoke",
"check": "pnpm check:tooling && pnpm check:fallow && pnpm check:unit",
"prepack": "pnpm check:mcp-metadata && pnpm package:npm",
"typecheck": "tsc -b packages/xml packages/kernel packages/contracts packages/host-kit packages/capture-kit packages/platform-apple packages/platform-android packages/platform-harmonyos packages/platform-vega packages/platform-linux packages/platform-web packages/ad-script packages/selectors packages/ad-replay packages/maestro packages/replay-test packages/provider-webdriver packages/provider-limrun && tsc -p tsconfig.json && tsc -p examples/sdk/tsconfig.json",
"typecheck": "tsc -b packages/xml packages/kernel packages/contracts packages/host-kit packages/capture-kit packages/provision-kit packages/platform-apple packages/platform-android packages/platform-harmonyos packages/platform-vega packages/platform-linux packages/platform-web packages/ad-script packages/selectors packages/ad-replay packages/maestro packages/replay-test packages/provider-webdriver packages/provider-limrun && tsc -p tsconfig.json && tsc -p examples/sdk/tsconfig.json",
"test-app:install": "pnpm install --dir examples/test-app",
"test-app:start": "pnpm --dir examples/test-app start",
"test-app:ios": "pnpm --dir examples/test-app ios",
Expand Down Expand Up @@ -289,6 +289,7 @@
"@agent-device/platform-web": "workspace:*",
"@agent-device/provider-limrun": "workspace:*",
"@agent-device/provider-webdriver": "workspace:*",
"@agent-device/provision-kit": "workspace:*",
"@agent-device/replay-test": "workspace:*",
"@agent-device/selectors": "workspace:*",
"@agent-device/xml": "workspace:*",
Expand Down
4 changes: 4 additions & 0 deletions packages/kernel/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@
"types": "./src/location-coordinates.ts",
"default": "./src/location-coordinates.ts"
},
"./numeric": {
"types": "./src/numeric.ts",
"default": "./src/numeric.ts"
},
"./record": {
"types": "./src/record.ts",
"default": "./src/record.ts"
Expand Down
File renamed without changes.
44 changes: 44 additions & 0 deletions packages/provision-kit/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
{
"name": "@agent-device/provision-kit",
"version": "0.0.0",
"private": true,
"type": "module",
"description": "Private provisioning mechanics shared by platform runtimes and daemon orchestration: install-artifact acquisition (local paths, archives, guarded network downloads) and host toolchain probing. Internal workspace package bundled into the published agent-device artifact.",
"dependencies": {
"@agent-device/contracts": "workspace:*",
"@agent-device/host-kit": "workspace:*",
"@agent-device/kernel": "workspace:*",
"ipaddr.js": "^2.5.0",
"undici": "7.29.0"
},
"exports": {
"./app-resolution-cache": {
"types": "./src/app-resolution-cache.ts",
"default": "./src/app-resolution-cache.ts"
},
"./boot-diagnostics": {
"types": "./src/boot-diagnostics.ts",
"default": "./src/boot-diagnostics.ts"
},
"./install-artifact-archive-context": {
"types": "./src/install-artifact-archive-context.ts",
"default": "./src/install-artifact-archive-context.ts"
},
"./install-source": {
"types": "./src/install-source.ts",
"default": "./src/install-source.ts"
},
"./install-source-network": {
"types": "./src/install-source-network.ts",
"default": "./src/install-source-network.ts"
},
"./install-source-network-transport": {
"types": "./src/install-source-network-transport.ts",
"default": "./src/install-source-network-transport.ts"
},
"./toolchain-probe": {
"types": "./src/toolchain-probe.ts",
"default": "./src/toolchain-probe.ts"
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { test } from 'vitest';
import assert from 'node:assert/strict';
import { createAppResolutionCache } from '../app-resolution-cache.ts';
import { createAppResolutionCache } from './app-resolution-cache.ts';

test('app resolution cache returns values until the expiry boundary', () => {
let nowMs = 1_000;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
bootFailureHint,
classifyBootFailure,
isInfrastructureBootFailureReason,
} from '../boot-diagnostics.ts';
} from './boot-diagnostics.ts';
import { AppError } from '@agent-device/kernel/errors';

test('classifyBootFailure maps timeout errors', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ type BootDiagnosticContext = {
phase?: 'boot' | 'connect' | 'transport';
};

// fallow-ignore-next-line complexity
export function classifyBootFailure(input: {
error?: unknown;
message?: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import dns from 'node:dns/promises';
import fs from 'node:fs/promises';
import { Readable } from 'node:stream';
import { test, vi } from 'vitest';
import { mkdtempForTest } from '../../__tests__/test-utils/tmp-dir.ts';
import { downloadInstallSource } from '../install-source-download.ts';
import * as networkTransport from '../install-source-network-transport.ts';
import { mkdtempForTest } from './tmp-dir.fixtures.ts';
import { downloadInstallSource } from './install-source-download.ts';
import * as networkTransport from './install-source-network-transport.ts';

test('download redirects revalidate destinations and strip sensitive cross-origin headers', async () => {
const tempRoot = await mkdtempForTest('agent-device-download-redirect-');
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 { matchesNoProxy, resolveProxyForUrl } from '../install-source-network-transport.ts';
import { matchesNoProxy, resolveProxyForUrl } from './install-source-network-transport.ts';

test('lowercase proxy variables override uppercase even when empty', () => {
assert.equal(
Expand Down
8 changes: 8 additions & 0 deletions packages/provision-kit/src/tmp-dir.fixtures.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import fsPromises from 'node:fs/promises';
import os from 'node:os';
import path from 'node:path';

// fallow-ignore-next-line code-duplication
export async function mkdtempForTest(prefix: string): Promise<string> {
return fsPromises.mkdtemp(path.join(os.tmpdir(), prefix));
}
12 changes: 12 additions & 0 deletions packages/provision-kit/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"composite": true,
"noEmit": false,
"emitDeclarationOnly": true,
"declaration": true,
"declarationDir": "./dist-types",
"rootDir": "./src"
},
"include": ["src"]
}
21 changes: 21 additions & 0 deletions pnpm-lock.yaml

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

4 changes: 4 additions & 0 deletions scripts/layering/check.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ import {
} from './package-boundaries.ts';
import {
checkPlatformPackagePolicy,
checkPlatformsRootShape,
platformPackagePolicySummary,
} from './platform-package-policy.ts';
import {
Expand Down Expand Up @@ -552,6 +553,7 @@ export const LAYERING_RULE_IDS = [
'bin-alias-fast-path',
'package-boundaries',
'platform-package-policy',
'platforms-root-shape',
] as const;

export type LayeringRuleId = (typeof LAYERING_RULE_IDS)[number];
Expand Down Expand Up @@ -585,6 +587,8 @@ export const LAYERING_RULES: Readonly<Record<LayeringRuleId, LayeringRule>> = {
readTrackedPlatformPackageDeclarations(repoRoot),
{ untrackedProductionFiles: listUntrackedProductionTypeScriptFiles(repoRoot) },
),
'platforms-root-shape': (context) =>
checkPlatformsRootShape([...context.allTypeScriptSources.keys()]),
};

export function main(): number {
Expand Down
1 change: 1 addition & 0 deletions scripts/layering/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ export const UNRANKED_ZONES: ReadonlySet<string> = new Set([
'kernel',
'host-kit',
'capture-kit',
'provision-kit',
'platform-apple',
'platform-android',
'platform-harmonyos',
Expand Down
23 changes: 23 additions & 0 deletions scripts/layering/package-boundaries.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -470,6 +470,29 @@ test('the real tree parses, declares, and passes R11', () => {
'@agent-device/capture-kit/snapshot-quality-backend-capabilities',
'@agent-device/capture-kit/snapshot-quality-verdict',
]);

const provisionKitPackage = packages.find((pkg) => pkg.name === '@agent-device/provision-kit');
assert.ok(provisionKitPackage, 'provision-kit package must exist');
assert.equal(
JSON.parse(fs.readFileSync(path.join(repoRoot, 'packages/provision-kit/package.json'), 'utf8'))
.private,
true,
'provision-kit stays a private implementation package',
);
assert.deepEqual([...provisionKitPackage.exportTargets.keys()].sort(), [
'@agent-device/provision-kit/app-resolution-cache',
'@agent-device/provision-kit/boot-diagnostics',
'@agent-device/provision-kit/install-artifact-archive-context',
'@agent-device/provision-kit/install-source',
'@agent-device/provision-kit/install-source-network',
'@agent-device/provision-kit/install-source-network-transport',
'@agent-device/provision-kit/toolchain-probe',
]);
assert.deepEqual([...provisionKitPackage.workspaceDependencies].sort(), [
'@agent-device/contracts',
'@agent-device/host-kit',
'@agent-device/kernel',
]);
assert.deepEqual([...captureKitPackage.workspaceDependencies].sort(), [
'@agent-device/contracts',
'@agent-device/host-kit',
Expand Down
51 changes: 50 additions & 1 deletion scripts/layering/platform-package-policy.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { test } from 'node:test';
import {
CANONICAL_PLATFORM_FAMILIES,
checkPlatformPackagePolicy,
checkPlatformsRootShape,
type PlatformPackageDeclaration,
} from './platform-package-policy.ts';
import { classifyZone } from './model.ts';
Expand Down Expand Up @@ -385,7 +386,7 @@ test('platform packages may use capture-kit but no unrelated workspace implement
);
assert.match(
messages(sources).join('\n'),
/may import workspace code only from capture-kit, host-kit, contracts, kernel, or xml/,
/may import workspace code only from capture-kit, host-kit, provision-kit, contracts, kernel, or xml/,
);
}
});
Expand Down Expand Up @@ -490,3 +491,51 @@ test('Node resolves only each platform package root facade', () => {
);
}
});

test('the src/platforms root holds only family directories and __tests__', () => {
const clean = CANONICAL_PLATFORM_FAMILIES.map((family) => `src/platforms/${family}/doctor.ts`);
assert.deepEqual(
checkPlatformsRootShape([...clean, 'src/platforms/__tests__/install-source.test.ts']),
[],
);
});

test('a new direct production file or sibling directory under src/platforms fails closed', () => {
const planted = [
'src/platforms/shared-helper.ts',
'src/platforms/common/util.ts',
'src/platforms/perf-utils.ts',
];
const found = checkPlatformsRootShape(planted);
assert.deepEqual(
found.map(({ file }) => file),
planted,
);
for (const violation of found) {
assert.equal(violation.rule, 'platforms-root-shape');
assert.match(violation.message, /substrate package/);
}
});

test('platform packages may import the provision-kit substrate', () => {
const sources = validSources();
sources.set(
'packages/platform-apple/src/install.ts',
"import { resolveInstallSource } from '@agent-device/provision-kit/install-source';",
);
assert.deepEqual(checkPlatformPackagePolicy(sources, declarations()), []);
});

test('a provision-kit import of a concrete platform package fails closed', () => {
const sources = validSources();
sources.set(
'packages/provision-kit/src/backdoor.ts',
"import { runtimeModule } from '@agent-device/platform-android';",
);
assert.match(
checkPlatformPackagePolicy(sources, declarations())
.map(({ message }) => message)
.join('\n'),
/may import '@agent-device\/platform-android'/,
);
});
18 changes: 17 additions & 1 deletion scripts/layering/platform-package-policy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,21 @@ const RAW_PROCESS_SPECIFIERS = new Set(['child_process', 'node:child_process']);
// narrows with it — the facet itself is durable Apple ownership, not a
// temporary exception).
export const APPLE_RUNNER_SUBTREE = 'packages/platform-apple/src/runner/';

export function checkPlatformsRootShape(files: readonly string[]): LayeringViolation[] {
const allowedChild = new RegExp(
`^src/platforms/(?:${[...CANONICAL_PLATFORM_FAMILIES, '__tests__'].join('|')})/`,
);
return files
.filter((file) => file.startsWith('src/platforms/') && !allowedChild.test(file))
.map((file) => ({
rule: 'platforms-root-shape',
file,
line: 1,
message:
'src/platforms may hold only the family directories and __tests__; shared code belongs in a substrate package',
}));
}
const APPLE_RUNNER_FACADE = '@agent-device/platform-apple/runner';
const APPLE_RUNNER_CLIENT = '@agent-device/platform-apple/runner/client';
const APPLE_RUNNER_TEST_HOST = '@agent-device/platform-apple/runner/test-host';
Expand Down Expand Up @@ -264,6 +279,7 @@ function checkSource(file: string, source: string): LayeringViolation[] {
site.spec !== '@agent-device/capture-kit' &&
!site.spec.startsWith('@agent-device/capture-kit/') &&
!site.spec.startsWith('@agent-device/host-kit/') &&
!site.spec.startsWith('@agent-device/provision-kit/') &&
!site.spec.startsWith('@agent-device/kernel/') &&
site.spec !== '@agent-device/xml' &&
!isPackageOwnedFacadeTest(file, ownerFamily, site.spec)
Expand All @@ -272,7 +288,7 @@ function checkSource(file: string, source: string): LayeringViolation[] {
violation(
file,
site.line,
`platform-${ownerFamily} may import workspace code only from capture-kit, host-kit, contracts, kernel, or xml; found '${site.spec}'`,
`platform-${ownerFamily} may import workspace code only from capture-kit, host-kit, provision-kit, contracts, kernel, or xml; found '${site.spec}'`,
),
);
}
Expand Down
10 changes: 10 additions & 0 deletions src/__tests__/eager-closure-budgets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,15 @@ export const FACADE_BUDGETS: Readonly<Record<string, number>> = Object.freeze({
'packages/host-kit/src/retry.ts': 6,
'packages/host-kit/src/version.ts': 4,

// --- @agent-device/provision-kit ---
'packages/provision-kit/src/app-resolution-cache.ts': 1,
'packages/provision-kit/src/boot-diagnostics.ts': 4,
'packages/provision-kit/src/install-artifact-archive-context.ts': 10,
'packages/provision-kit/src/install-source.ts': 25,
'packages/provision-kit/src/install-source-network.ts': 3,
'packages/provision-kit/src/install-source-network-transport.ts': 1,
'packages/provision-kit/src/toolchain-probe.ts': 8,

// --- @agent-device/contracts ---
'packages/contracts/src/alert-contract.ts': 1,
'packages/contracts/src/android-clipboard-support.ts': 1,
Expand Down Expand Up @@ -259,6 +268,7 @@ export const FACADE_BUDGETS: Readonly<Record<string, number>> = Object.freeze({
'packages/kernel/src/errors.ts': 2,
// Added by #2041: keyed async lock moved from src/utils for the extracted IME lifecycle.
'packages/kernel/src/keyed-lock.ts': 1,
'packages/kernel/src/numeric.ts': 1,
'packages/kernel/src/rect-center.ts': 2,
'packages/kernel/src/rect.ts': 1,
'packages/kernel/src/device-isolation.ts': 1,
Expand Down
Loading
Loading