mirror of
https://github.com/callstack/agent-device.git
synced 2026-09-14 20:06:34 +08:00
1f9d940bff
* refactor(capture-kit): relocate snapshot and recording zones into capture-kit
Move the ADR 0019 end-state capture zones into @agent-device/capture-kit:
- src/snapshot/** -> packages/capture-kit/src/snapshot/** (presentation,
freshness, scroll-edge-state, ios-snapshot-runtime, android occlusion)
- src/recording/** -> packages/capture-kit/src/recording/**
- src/core/snapshot-{chrome,state,tree-ingestion,node-lookup}.ts ->
packages/capture-kit/src/
- src/snapshot-quality/ test -> capture-kit presentation tree (directory
retires with its last file)
Pure renames: import re-pointing and gate updates follow in the next commit.
The snapshot-desktop-surface test parks in src/__tests__/ because it pins
the root eager-import-closure walker.
* refactor(capture-kit): re-point capture and recording consumers to the new subpaths
Rewires every consumer of the relocated snapshot/recording modules to the new @agent-device/capture-kit subpath exports, adds the 23 subpath entries to the capture-kit exports map, fixes the moved recording-scripts test's __dirname-relative paths for the deeper location, and records the completed migration in ADR 0019's end state.
* chore(gates): align layering, mutation, fallow and CI gates with the capture-kit relocation
Moves the executable-policy roots, presentation-owner constant, zone ranks, authority fixture, mutation sharding globs, stryker aliases, fallow baselines and the iOS workflow's android-owned paths-ignore entry onto the new packages/capture-kit paths, and extends the planted-red coverage to the new presentation-owner subpath.
* chore: point capture-domain source-of-truth comments at the relocated capture-kit modules
* test: point shutdown recording mock at capture-kit and cover interactor acquisition presentation
* test(capture-kit): update upstream presentation test imports
* chore(gates): follow relocated snapshot assembly in R74
* test(daemon): freeze prewarm deadline assertion clocks
126 lines
5.0 KiB
TypeScript
126 lines
5.0 KiB
TypeScript
// Catches: a provider-* package acquiring an iOS snapshot outside the capture-kit acquisition
|
|
// entrypoint, or presenting it outside packages/capture-kit/src/snapshot/ios-snapshot-runtime.ts
|
|
// — the exact split R72's engine convergence closed for the runner layer, mirrored here one
|
|
// layer up for the provider packages that call into it.
|
|
// Evidence: 7ee1a5ded7 (#2233) carried provider acquisitions through this one presentation
|
|
// owner, the change this policy was written to hold in place.
|
|
// Cost: 195 LOC (111 rule + 84 test).
|
|
// Kill criterion: none enforced today; retire only by maintainer decision that provider-* packages
|
|
// reaching presentation only through @agent-device/capture-kit/ios-snapshot-acquisition, and
|
|
// never constructing, discarding, or reassigning acquisition residue, no longer matter. An
|
|
// exports map cannot replace it: it restricts external specifiers, not the transitive walk
|
|
// into packages/capture-kit/src/snapshot/ or a provider-local `residue` property or assignment.
|
|
|
|
import { parseSync } from 'oxc-parser';
|
|
import type { LayeringViolation, ResolvedImportEdge } from './model.ts';
|
|
import { memberPath, propertyName, visitAst } from './layering-ast.ts';
|
|
|
|
export const PROVIDER_SNAPSHOT_PRESENTATION_RULE = 'R73 provider-snapshot-presentation-ownership';
|
|
export const IOS_SNAPSHOT_ACQUISITION_ENTRYPOINT =
|
|
'@agent-device/capture-kit/ios-snapshot-acquisition';
|
|
export const IOS_SNAPSHOT_PRESENTATION_OWNER =
|
|
'packages/capture-kit/src/snapshot/ios-snapshot-runtime.ts';
|
|
|
|
const PROVIDER_SOURCE = /^packages\/provider-[^/]+\/src\//;
|
|
const IOS_SNAPSHOT_CAPTURE_KIT_ROOT = 'packages/capture-kit/src/ios-snapshot-';
|
|
|
|
type AstNode = Record<string, unknown>;
|
|
|
|
export function providerSnapshotPresentationViolations(
|
|
sources: ReadonlyMap<string, string>,
|
|
edges: readonly ResolvedImportEdge[],
|
|
): LayeringViolation[] {
|
|
const edgesByFile = new Map<string, ResolvedImportEdge[]>();
|
|
for (const edge of edges) {
|
|
const fileEdges = edgesByFile.get(edge.file) ?? [];
|
|
fileEdges.push(edge);
|
|
edgesByFile.set(edge.file, fileEdges);
|
|
}
|
|
|
|
const violations: LayeringViolation[] = [];
|
|
for (const file of sources.keys()) {
|
|
if (!PROVIDER_SOURCE.test(file)) continue;
|
|
violations.push(...presentationImportViolations(file, edgesByFile));
|
|
violations.push(...residueViolations(file, sources.get(file)!));
|
|
}
|
|
return violations;
|
|
}
|
|
|
|
function presentationImportViolations(
|
|
providerFile: string,
|
|
edgesByFile: ReadonlyMap<string, readonly ResolvedImportEdge[]>,
|
|
): LayeringViolation[] {
|
|
const origins = new Map<string, ResolvedImportEdge>();
|
|
const visited = new Set([providerFile]);
|
|
const queue = [providerFile];
|
|
const violations: LayeringViolation[] = [];
|
|
|
|
while (queue.length > 0) {
|
|
const file = queue.shift()!;
|
|
for (const edge of edgesByFile.get(file) ?? []) {
|
|
const origin = origins.get(file) ?? edge;
|
|
if (isPresentationTarget(edge.target)) {
|
|
violations.push(
|
|
violation(
|
|
providerFile,
|
|
origin.line,
|
|
`provider package reaches ${edge.target} before host owner ${IOS_SNAPSHOT_PRESENTATION_OWNER}; use ${IOS_SNAPSHOT_ACQUISITION_ENTRYPOINT} for acquisition`,
|
|
),
|
|
);
|
|
continue;
|
|
}
|
|
if (visited.has(edge.target)) continue;
|
|
visited.add(edge.target);
|
|
origins.set(edge.target, origin);
|
|
queue.push(edge.target);
|
|
}
|
|
}
|
|
return violations;
|
|
}
|
|
|
|
function residueViolations(providerFile: string, source: string): LayeringViolation[] {
|
|
const violations: LayeringViolation[] = [];
|
|
const program = parseSync(providerFile, source).program;
|
|
visitAst(program, (node) => {
|
|
if (node.type === 'Property' && propertyName(node.key) === 'residue') {
|
|
violations.push(
|
|
violation(
|
|
providerFile,
|
|
sourceLine(source, node.start as number | undefined),
|
|
'provider package must not construct or discard acquisition residue; the acquisition entrypoint owns it',
|
|
),
|
|
);
|
|
}
|
|
if (
|
|
node.type === 'AssignmentExpression' &&
|
|
memberPath(node.left as AstNode | undefined)?.includes('residue')
|
|
) {
|
|
violations.push(
|
|
violation(
|
|
providerFile,
|
|
sourceLine(source, node.start as number | undefined),
|
|
'provider package must not rewrite acquisition residue; the acquisition entrypoint owns it',
|
|
),
|
|
);
|
|
}
|
|
});
|
|
return violations;
|
|
}
|
|
|
|
function isPresentationTarget(target: string): boolean {
|
|
return (
|
|
target === IOS_SNAPSHOT_PRESENTATION_OWNER ||
|
|
target.startsWith('packages/capture-kit/src/snapshot/') ||
|
|
(target.startsWith(IOS_SNAPSHOT_CAPTURE_KIT_ROOT) &&
|
|
target !== 'packages/capture-kit/src/ios-snapshot-acquisition.ts')
|
|
);
|
|
}
|
|
|
|
function sourceLine(source: string, offset: number | undefined): number {
|
|
return source.slice(0, typeof offset === 'number' ? offset : 0).split('\n').length;
|
|
}
|
|
|
|
function violation(file: string, line: number, message: string): LayeringViolation {
|
|
return { rule: PROVIDER_SNAPSHOT_PRESENTATION_RULE, file, line, message };
|
|
}
|