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
119 changes: 119 additions & 0 deletions scripts/layering/contracts-exports.snapshot.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
[
"@agent-device/contracts/alert-contract",
"@agent-device/contracts/alert-runtime",
"@agent-device/contracts/android-clipboard-support",
"@agent-device/contracts/android-helper-artifacts",
"@agent-device/contracts/android-input-ownership",
"@agent-device/contracts/android-observation",
"@agent-device/contracts/android-snapshot-quality",
"@agent-device/contracts/android-system-chrome",
"@agent-device/contracts/android-touch-plan",
"@agent-device/contracts/app-deployment-runtime",
"@agent-device/contracts/app-deployment-runtime-plan",
"@agent-device/contracts/app-event-runtime",
"@agent-device/contracts/app-inventory-runtime",
"@agent-device/contracts/app-log-runtime",
"@agent-device/contracts/app-state-runtime",
"@agent-device/contracts/app-switcher-runtime",
"@agent-device/contracts/apple-multitouch-support",
"@agent-device/contracts/apple-runner-request",
"@agent-device/contracts/application-lifecycle-interaction",
"@agent-device/contracts/application-lifecycle-runtime",
"@agent-device/contracts/application-lifecycle-runtime-plan",
"@agent-device/contracts/async-lifecycle",
"@agent-device/contracts/audio-probe-result",
"@agent-device/contracts/audio-probe-runtime",
"@agent-device/contracts/audio-probe-runtime-host",
"@agent-device/contracts/audio-probe-support",
"@agent-device/contracts/audio-runtime-plan",
"@agent-device/contracts/back-mode",
"@agent-device/contracts/back-runtime",
"@agent-device/contracts/backend-diagnostics",
"@agent-device/contracts/boot-failure",
"@agent-device/contracts/capture",
"@agent-device/contracts/click-button",
"@agent-device/contracts/client",
"@agent-device/contracts/clipboard",
"@agent-device/contracts/clipboard-runtime",
"@agent-device/contracts/command",
"@agent-device/contracts/command-platform-execution",
"@agent-device/contracts/daemon-owner-cleanup",
"@agent-device/contracts/device",
"@agent-device/contracts/device-readiness-runtime",
"@agent-device/contracts/device-shutdown-runtime",
"@agent-device/contracts/divergence",
"@agent-device/contracts/durable-resource",
"@agent-device/contracts/durable-resource-envelope",
"@agent-device/contracts/element-text-runtime",
"@agent-device/contracts/focus-runtime",
"@agent-device/contracts/gesture-admission",
"@agent-device/contracts/gesture-input",
"@agent-device/contracts/gesture-normalization",
"@agent-device/contracts/gesture-plan",
"@agent-device/contracts/gesture-plan-types",
"@agent-device/contracts/gesture-runtime",
"@agent-device/contracts/home-runtime",
"@agent-device/contracts/host-diagnostics",
"@agent-device/contracts/interaction",
"@agent-device/contracts/interaction-error",
"@agent-device/contracts/interaction-guarantees",
"@agent-device/contracts/interactor-operation-catalog",
"@agent-device/contracts/interactor-types",
"@agent-device/contracts/ios-snapshot",
"@agent-device/contracts/is-predicate",
"@agent-device/contracts/keyboard",
"@agent-device/contracts/keyboard-runtime",
"@agent-device/contracts/local-interactor-operation-set",
"@agent-device/contracts/logs-runtime-plan",
"@agent-device/contracts/managed-device-allocation",
"@agent-device/contracts/managed-web-backend",
"@agent-device/contracts/navigation",
"@agent-device/contracts/network-runtime",
"@agent-device/contracts/network-runtime-plan",
"@agent-device/contracts/network-traffic",
"@agent-device/contracts/observability",
"@agent-device/contracts/orientation-runtime",
"@agent-device/contracts/perf-runtime",
"@agent-device/contracts/perf-runtime-host",
"@agent-device/contracts/perf-runtime-operation-builder",
"@agent-device/contracts/perf-runtime-plan",
"@agent-device/contracts/platform-module",
"@agent-device/contracts/platform-plugin",
"@agent-device/contracts/platform-providers",
"@agent-device/contracts/platform-resource-cleanup",
"@agent-device/contracts/platform-runtime",
"@agent-device/contracts/platform-runtime-host",
"@agent-device/contracts/platform-runtime-operations",
"@agent-device/contracts/platform-runtime-unavailable",
"@agent-device/contracts/progress",
"@agent-device/contracts/react-native-overlay",
"@agent-device/contracts/record-runtime-execution",
"@agent-device/contracts/recording",
"@agent-device/contracts/remote",
"@agent-device/contracts/replay",
"@agent-device/contracts/runner-lease-context",
"@agent-device/contracts/screen-recording-runtime",
"@agent-device/contracts/screen-recording-runtime-host",
"@agent-device/contracts/screen-recording-runtime-plan",
"@agent-device/contracts/screenshot-runtime",
"@agent-device/contracts/scroll-command",
"@agent-device/contracts/scroll-gesture",
"@agent-device/contracts/scroll-runtime",
"@agent-device/contracts/selector-observation-runtime",
"@agent-device/contracts/session",
"@agent-device/contracts/settings",
"@agent-device/contracts/settings-runtime",
"@agent-device/contracts/snapshot",
"@agent-device/contracts/snapshot-presentation",
"@agent-device/contracts/snapshot-runtime",
"@agent-device/contracts/snapshot-scope",
"@agent-device/contracts/snapshot-timeout-evidence",
"@agent-device/contracts/startup-recovery-fence",
"@agent-device/contracts/touch-runtime",
"@agent-device/contracts/tv-remote",
"@agent-device/contracts/tv-remote-runtime",
"@agent-device/contracts/type-text-runtime",
"@agent-device/contracts/viewport-runtime",
"@agent-device/contracts/wait",
"@agent-device/contracts/wait-runtime-plan"
]
33 changes: 33 additions & 0 deletions scripts/layering/generate-contracts-exports-snapshot.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Regenerates contracts-exports.snapshot.json from packages/contracts/package.json#exports.
//
// package-boundaries.test.ts diffs the live manifest's export specifiers against this committed
// snapshot so widening or shrinking `@agent-device/contracts`'s export surface fails the gate
// (the property the deleted ~120-line inline CONTRACT_EXPORTS pin used to give). The snapshot is a
// separate file rather than an inline list so an editor who adds or removes a contracts subpath
// runs this script and reviews the diff, instead of hand-retyping an alphabetized array.
//
// Run after any packages/contracts/package.json#exports change:
// node --experimental-strip-types scripts/layering/generate-contracts-exports-snapshot.ts
// then commit the resulting snapshot diff alongside the manifest change.

import fs from 'node:fs';
import path from 'node:path';

const repoRoot = path.resolve(import.meta.dirname, '../..');
const manifestFile = 'packages/contracts/package.json';
const snapshotFile = path.join(import.meta.dirname, 'contracts-exports.snapshot.json');

const manifest = JSON.parse(fs.readFileSync(path.join(repoRoot, manifestFile), 'utf8')) as {
name?: string;
exports?: Record<string, unknown>;
};
if (!manifest.name) throw new Error(`${manifestFile} has no "name"`);

const specifiers = Object.keys(manifest.exports ?? {})
.map((subpath) => path.posix.join(manifest.name!, subpath))
.sort();

fs.writeFileSync(snapshotFile, `${JSON.stringify(specifiers, null, 2)}\n`);
console.log(
`Wrote ${specifiers.length} export specifiers to ${path.relative(repoRoot, snapshotFile)}`,
);
169 changes: 49 additions & 120 deletions scripts/layering/package-boundaries.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
specifierSites,
type WorkspacePackage,
} from './package-boundaries.ts';
import { listTrackedTypeScriptFiles } from './tracked-sources.ts';

const repoRoot = path.resolve(import.meta.dirname, '../..');

Expand All @@ -46,130 +47,54 @@ const contracts: WorkspacePackage = {
};

const ALL = [kernel, contracts];
const CONTRACT_EXPORTS = [
'@agent-device/contracts/alert-contract',
'@agent-device/contracts/alert-runtime',
'@agent-device/contracts/android-clipboard-support',
'@agent-device/contracts/android-helper-artifacts',
'@agent-device/contracts/android-input-ownership',
'@agent-device/contracts/android-observation',
'@agent-device/contracts/android-snapshot-quality',
'@agent-device/contracts/android-system-chrome',
'@agent-device/contracts/android-touch-plan',
'@agent-device/contracts/app-deployment-runtime',
'@agent-device/contracts/app-deployment-runtime-plan',
'@agent-device/contracts/app-event-runtime',
'@agent-device/contracts/app-inventory-runtime',
'@agent-device/contracts/app-log-runtime',
'@agent-device/contracts/app-state-runtime',
'@agent-device/contracts/app-switcher-runtime',
'@agent-device/contracts/apple-multitouch-support',
'@agent-device/contracts/apple-runner-request',
'@agent-device/contracts/application-lifecycle-interaction',
'@agent-device/contracts/application-lifecycle-runtime',
'@agent-device/contracts/application-lifecycle-runtime-plan',
'@agent-device/contracts/async-lifecycle',
'@agent-device/contracts/audio-probe-result',
'@agent-device/contracts/audio-probe-runtime',
'@agent-device/contracts/audio-probe-runtime-host',
'@agent-device/contracts/audio-probe-support',
'@agent-device/contracts/audio-runtime-plan',
'@agent-device/contracts/back-mode',
'@agent-device/contracts/backend-diagnostics',
'@agent-device/contracts/back-runtime',
'@agent-device/contracts/boot-failure',
'@agent-device/contracts/capture',
'@agent-device/contracts/click-button',
'@agent-device/contracts/client',
'@agent-device/contracts/clipboard',
'@agent-device/contracts/clipboard-runtime',
'@agent-device/contracts/command',
'@agent-device/contracts/command-platform-execution',
'@agent-device/contracts/device',
'@agent-device/contracts/device-readiness-runtime',
'@agent-device/contracts/device-shutdown-runtime',
'@agent-device/contracts/divergence',
'@agent-device/contracts/durable-resource',
'@agent-device/contracts/durable-resource-envelope',
'@agent-device/contracts/element-text-runtime',
'@agent-device/contracts/focus-runtime',
'@agent-device/contracts/gesture-admission',
'@agent-device/contracts/gesture-input',
'@agent-device/contracts/gesture-normalization',
'@agent-device/contracts/gesture-plan',
'@agent-device/contracts/gesture-plan-types',
'@agent-device/contracts/gesture-runtime',
'@agent-device/contracts/home-runtime',
'@agent-device/contracts/host-diagnostics',
'@agent-device/contracts/daemon-owner-cleanup',
'@agent-device/contracts/interaction',
'@agent-device/contracts/interaction-error',
'@agent-device/contracts/interaction-guarantees',
'@agent-device/contracts/interactor-operation-catalog',
'@agent-device/contracts/interactor-types',
'@agent-device/contracts/ios-snapshot',
'@agent-device/contracts/is-predicate',
'@agent-device/contracts/keyboard',
'@agent-device/contracts/keyboard-runtime',
'@agent-device/contracts/local-interactor-operation-set',
'@agent-device/contracts/logs-runtime-plan',
'@agent-device/contracts/managed-device-allocation',
'@agent-device/contracts/managed-web-backend',
'@agent-device/contracts/navigation',
'@agent-device/contracts/network-runtime',
'@agent-device/contracts/network-runtime-plan',
'@agent-device/contracts/network-traffic',
'@agent-device/contracts/observability',
'@agent-device/contracts/orientation-runtime',
'@agent-device/contracts/perf-runtime',
'@agent-device/contracts/perf-runtime-host',
'@agent-device/contracts/perf-runtime-operation-builder',
'@agent-device/contracts/perf-runtime-plan',
'@agent-device/contracts/platform-module',
'@agent-device/contracts/platform-plugin',
'@agent-device/contracts/platform-providers',
'@agent-device/contracts/platform-resource-cleanup',
'@agent-device/contracts/platform-runtime',
'@agent-device/contracts/platform-runtime-host',
'@agent-device/contracts/platform-runtime-operations',
'@agent-device/contracts/platform-runtime-unavailable',
'@agent-device/contracts/progress',
'@agent-device/contracts/record-runtime-execution',
'@agent-device/contracts/recording',
'@agent-device/contracts/remote',
'@agent-device/contracts/replay',
'@agent-device/contracts/react-native-overlay',
'@agent-device/contracts/runner-lease-context',
'@agent-device/contracts/screen-recording-runtime',
'@agent-device/contracts/screen-recording-runtime-host',
'@agent-device/contracts/screen-recording-runtime-plan',
'@agent-device/contracts/screenshot-runtime',
'@agent-device/contracts/scroll-command',
'@agent-device/contracts/scroll-gesture',
'@agent-device/contracts/scroll-runtime',
'@agent-device/contracts/selector-observation-runtime',
'@agent-device/contracts/session',
'@agent-device/contracts/settings',
'@agent-device/contracts/settings-runtime',
'@agent-device/contracts/snapshot',
'@agent-device/contracts/snapshot-presentation',
'@agent-device/contracts/snapshot-runtime',
'@agent-device/contracts/snapshot-scope',
'@agent-device/contracts/snapshot-timeout-evidence',
'@agent-device/contracts/startup-recovery-fence',
'@agent-device/contracts/touch-runtime',
'@agent-device/contracts/tv-remote',
'@agent-device/contracts/tv-remote-runtime',
'@agent-device/contracts/type-text-runtime',
'@agent-device/contracts/viewport-runtime',
'@agent-device/contracts/wait',
'@agent-device/contracts/wait-runtime-plan',
] as const;

function rules(violations: { rule: string }[]): string[] {
return violations.map((violation) => violation.rule);
}

/**
* The manifest is its own inventory for two checks: every `exports` target must resolve to an
* existing, TRACKED source file, and every manifest entry must have produced exactly one
* `exportTargets` entry (nothing dropped, nothing collapsed by a duplicate key). Neither check
* catches the export SURFACE itself widening or shrinking -- `pkg.exportTargets` and
* `manifest.exports` are read from the same file, so adding or removing a subpath moves both
* counts together and the equality holds regardless (#2297 review). `snapshotFile`, an
* independently committed baseline regenerated by `generate-contracts-exports-snapshot.ts`,
* restores that guarantee: it changes only when a contributor deliberately reruns the generator
* and reviews the diff, so a subpath added or removed without doing so fails here.
*/
function assertExportTargetsMatchManifest(
pkg: WorkspacePackage,
manifestFile: string,
snapshotFile: string,
): void {
const manifest = JSON.parse(fs.readFileSync(path.join(repoRoot, manifestFile), 'utf8')) as {
exports?: Record<string, unknown>;
};
const trackedSources = new Set(listTrackedTypeScriptFiles(repoRoot));
for (const [specifier, target] of pkg.exportTargets) {
assert.ok(
trackedSources.has(target) && fs.existsSync(path.join(repoRoot, target)),
`${specifier} -> ${target} must resolve to an existing, tracked file`,
);
}
assert.equal(
pkg.exportTargets.size,
Object.keys(manifest.exports ?? {}).length,
`${pkg.name} exports map entries must each produce one resolved export target`,
);
const snapshot = JSON.parse(
fs.readFileSync(path.join(repoRoot, snapshotFile), 'utf8'),
) as string[];
assert.deepEqual(
[...pkg.exportTargets.keys()].sort(),
[...snapshot].sort(),
`${pkg.name}'s export surface no longer matches ${snapshotFile} -- regenerate it with ` +
`'node --experimental-strip-types scripts/layering/generate-contracts-exports-snapshot.ts' ` +
'and review the diff',
);
}

test('specifier sites carry 1-based lines for static and dynamic imports', () => {
const sites = specifierSites(
'src/a.ts',
Expand Down Expand Up @@ -451,7 +376,11 @@ test('the real tree parses, declares, and passes R11', () => {
assert.ok(kernelPackage.exportTargets.size >= 8, 'kernel exports its vocabulary subpaths');
const contractsPackage = packages.find((pkg) => pkg.name === '@agent-device/contracts');
assert.ok(contractsPackage, 'contracts package must exist');
assert.deepEqual([...contractsPackage.exportTargets.keys()].sort(), [...CONTRACT_EXPORTS].sort());
assertExportTargetsMatchManifest(
contractsPackage,
'packages/contracts/package.json',
'scripts/layering/contracts-exports.snapshot.json',
);
assert.deepEqual([...contractsPackage.workspaceDependencies], ['@agent-device/kernel']);
const captureKitPackage = packages.find((pkg) => pkg.name === '@agent-device/capture-kit');
assert.ok(captureKitPackage, 'capture-kit package must exist');
Expand Down
Loading