Skip to content
Open
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
21 changes: 17 additions & 4 deletions .fallowrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -182,16 +182,29 @@
},
{
"comment": "Daemon route handlers are reached only through the dynamic `import()` table in request-handler-chain.ts, which --production analysis cannot follow to a consumer.",
"file": "src/daemon/handlers/{human-control,lease,session,snapshot,react-native,record-trace,find,interaction}.ts",
"file": "src/daemon/handlers/{human-control,lease,session,snapshot,react-native,record-trace}.ts",
"exports": [
"handleHumanControlCommand",
"handleLeaseCommands",
"handleSessionCommands",
"handleSnapshotCommands",
"handleReactNativeCommands",
"handleRecordTraceCommands",
"handleFindCommands",
"handleInteractionCommands"
"handleRecordTraceCommands"
]
},
{
"comment": "The interaction façade is a named external seam: these helpers are part of the declared public surface, while Fallow cannot infer their consumers from the façade boundary.",
"file": "src/daemon/interaction/index.ts",
"exports": [
"RefSnapshotFlagGuardResponse",
"readTextForNode",
"assertRecordedFillParameterization",
"publishInteractionAmbiguityCandidates",
"assertRefMutationAdmitted",
"refMutationAdmissionResponse",
"refSnapshotFlagGuardResponse",
"handleInteractionCommands",
"handleFindCommands"
]
},
{
Expand Down
12 changes: 6 additions & 6 deletions fallow-baselines/health.json
Original file line number Diff line number Diff line change
Expand Up @@ -138,22 +138,22 @@
"count": 1
}
},
"src/daemon/handlers/__tests__/interaction-touch.test.ts": {
"src/daemon/interaction/internal/__tests__/interaction-touch.test.ts": {
"crap_moderate": {
"count": 2
}
},
"src/daemon/handlers/__tests__/interaction-touch-press.test.ts": {
"src/daemon/interaction/internal/__tests__/interaction-touch-press.test.ts": {
"crap_moderate": {
"count": 3
}
},
"src/daemon/handlers/__tests__/interaction-touch-press-admission.test.ts": {
"src/daemon/interaction/internal/__tests__/interaction-touch-press-admission.test.ts": {
"crap_moderate": {
"count": 2
}
},
"src/daemon/handlers/__tests__/interaction-touch-response.test.ts": {
"src/daemon/interaction/internal/__tests__/interaction-touch-response.test.ts": {
"crap_moderate": {
"count": 1
}
Expand Down Expand Up @@ -207,15 +207,15 @@
"count": 1
}
},
"src/daemon/handlers/find.ts": {
"src/daemon/interaction/internal/find.ts": {
"complexity_moderate": {
"count": 1
},
"crap_moderate": {
"count": 1
}
},
"src/daemon/handlers/interaction-touch-reference-frame.ts": {
"src/daemon/interaction/internal/interaction-touch-reference-frame.ts": {
"crap_moderate": {
"count": 1
}
Expand Down
10 changes: 5 additions & 5 deletions packages/contracts/src/interaction-guarantees.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ const PARENT_OWNED_TOUCH_POINT_GAP_ISSUE = 'https://github.com/callstack/agent-d
// (interaction-response-construction-guard.test.ts) keeps new branches on it.
const SHARED_RESPONSE_CONSTRUCTION: GuaranteeEnforcement = {
kind: 'runtime',
via: 'src/daemon/handlers/interaction-touch-response.ts#buildInteractionResponseData',
via: 'src/daemon/interaction/internal/interaction-touch-response.ts#buildInteractionResponseData',
};

// The two runtime tree paths (selector and ref resolution) run the SAME shared
Expand Down Expand Up @@ -183,7 +183,7 @@ const RUNTIME_TREE_SHARED_GUARANTEES = {
responseConstruction: SHARED_RESPONSE_CONSTRUCTION,
responseIdentity: {
kind: 'runtime',
via: 'src/daemon/handlers/interaction-touch-targets.ts#interactionResultExtra',
via: 'src/daemon/interaction/internal/interaction-touch-targets.ts#interactionResultExtra',
},
verifyEvidence: {
kind: 'runtime',
Expand Down Expand Up @@ -271,7 +271,7 @@ export const INTERACTION_DISPATCH_PATHS: Record<InteractionPathId, InteractionPa
},
responseConstruction: {
kind: 'runtime',
via: 'src/daemon/handlers/interaction-gesture-response.ts#gestureResponseData',
via: 'src/daemon/interaction/internal/interaction-gesture-response.ts#gestureResponseData',
},
responseIdentity: {
kind: 'runtime',
Expand Down Expand Up @@ -340,7 +340,7 @@ export const INTERACTION_DISPATCH_PATHS: Record<InteractionPathId, InteractionPa
responseConstruction: SHARED_RESPONSE_CONSTRUCTION,
responseIdentity: {
kind: 'runtime',
via: 'src/daemon/handlers/interaction-touch-targets.ts#interactionResultExtra',
via: 'src/daemon/interaction/internal/interaction-touch-targets.ts#interactionResultExtra',
},
verifyEvidence: {
kind: 'delegated',
Expand Down Expand Up @@ -464,7 +464,7 @@ export const INTERACTION_DISPATCH_PATHS: Record<InteractionPathId, InteractionPa
},
resolutionDisclosure: {
kind: 'runtime',
via: 'src/daemon/handlers/interaction-touch-response.ts#buildInteractionResponseData',
via: 'src/daemon/interaction/internal/interaction-touch-response.ts#buildInteractionResponseData',
},
},
},
Expand Down
2 changes: 1 addition & 1 deletion scripts/__tests__/help-conformance-sample-producers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { interactionCliOutputFormatters } from '../../src/commands/interaction/o
import { snapshotCliOutput } from '../../src/commands/capture/output.ts';
import { openCliOutput } from '../../src/commands/management/output.ts';
import { NEVER_SETTLED_HINT } from '../../src/commands/interaction/runtime/settle.ts';
import { buildAmbiguousMatchError } from '../../src/daemon/handlers/find-match-resolution.ts';
import { buildAmbiguousMatchError } from '../../src/daemon/interaction/internal/find-match-resolution.ts';
import { refMutationAdmissionResponse } from '../../src/daemon/interaction/index.ts';
import { buildDeviceInUseBySessionError } from '../../src/daemon/session-recovery-hints.ts';
import { buildDeviceClaimConflictError } from '../../src/daemon/device-claim-conflict.ts';
Expand Down
2 changes: 1 addition & 1 deletion scripts/__tests__/test-file-size-ratchet.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ const PINNED_TEST_FILE_LINES: Readonly<Record<string, number>> = Object.freeze({
'packages/platform-apple/src/runner/__tests__/runner-command-retry.test.ts': 1280,
'src/__tests__/cli-client-commands.test.ts': 1304,
'src/__tests__/cli-config.test.ts': 1282,
'src/daemon/handlers/__tests__/find.test.ts': 1199,
'src/daemon/interaction/internal/__tests__/find.test.ts': 1198,
'packages/platform-apple/src/core/__tests__/perf.test.ts': 1222,
'src/mcp/__tests__/command-tools.test.ts': 1216,
'src/daemon/replay/internal/__tests__/session-replay-divergence.test.ts': 1100,
Expand Down
10 changes: 6 additions & 4 deletions scripts/depgraph/affected.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,16 +89,16 @@ test('commandsReaching follows the dynamic import a route uses to load its handl
const edges = edgesOf({
'src/daemon/handlers/session.ts':
"import { helper } from '../../utils/helper.ts';\nexport const h = helper;",
'src/daemon/handlers/find.ts': 'export const f = 1;',
'src/daemon/interaction/index.ts': 'export const f = 1;',
'src/utils/helper.ts': 'export const helper = 1;',
'src/daemon/chain.ts': [
"export const routes = { session: () => import('./handlers/session.ts'),",
" find: () => import('./handlers/find.ts') };",
" find: () => import('./interaction/index.ts') };",
].join('\n'),
});
const chains = [
{ command: 'open', route: 'session', entry: 'src/daemon/handlers/session.ts' },
{ command: 'find', route: 'find', entry: 'src/daemon/handlers/find.ts' },
{ command: 'find', route: 'find', entry: 'src/daemon/interaction/index.ts' },
];

assert.deepEqual(
Expand All @@ -107,7 +107,9 @@ test('commandsReaching follows the dynamic import a route uses to load its handl
);
// The entry module itself counts as part of its own chain.
assert.deepEqual(
commandsReaching('src/daemon/handlers/find.ts', chains, edges).map((chain) => chain.command),
commandsReaching('src/daemon/interaction/index.ts', chains, edges).map(
(chain) => chain.command,
),
['find'],
);
});
Expand Down
2 changes: 1 addition & 1 deletion scripts/help-conformance-sample-outputs.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ export const STALE_REF_SAMPLE = {
Hint: Ref @e12 was minted from snapshot s5 but the session's ref frame is now s7 — re-run snapshot -i.`,
};

// AMBIGUOUS_MATCH from buildAmbiguousMatchError (src/daemon/handlers/find.ts)
// AMBIGUOUS_MATCH from buildAmbiguousMatchError (src/daemon/interaction/internal/find.ts)
// — the parity test drives that exact producer. The by-design rejection
// instead of silent disambiguation: #1597 made the candidate refs (ref, role,
// label/identifier — the same compact rendering as snapshot -i) print
Expand Down
31 changes: 31 additions & 0 deletions scripts/layering/architecture-ownership.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,16 @@ const DAEMON_INTERACTION_FACADE = {
exports: [
'CaptureSnapshotForSession',
'ContextFromFlags',
'FindRouteInput',
'InteractionRouteInput',
'RefSnapshotFlagGuardResponse',
'assertRecordedFillParameterization',
'assertRefMutationAdmitted',
'captureSnapshotForSession',
'createInteractionRuntime',
'finalizeTouchInteraction',
'handleFindCommands',
'handleInteractionCommands',
'publishInteractionAmbiguityCandidates',
'readSettleRequest',
'readTextForNode',
Expand All @@ -65,6 +68,34 @@ const DAEMON_INTERACTION_FACADE = {
],
} as const;

export const INTERACTION_RETIRED_HANDLER_PATHS = [
'src/daemon/handlers/find.ts',
'src/daemon/handlers/find-match-ranking.ts',
'src/daemon/handlers/find-match-resolution.ts',
'src/daemon/handlers/find-target-capture.ts',
'src/daemon/handlers/interaction.ts',
'src/daemon/handlers/interaction-android-escape.ts',
'src/daemon/handlers/interaction-gesture.ts',
'src/daemon/handlers/interaction-gesture-response.ts',
'src/daemon/handlers/interaction-ios-tap-outcome.ts',
'src/daemon/handlers/interaction-targeting.ts',
'src/daemon/handlers/interaction-touch.ts',
'src/daemon/handlers/interaction-touch-android-freshness.ts',
'src/daemon/handlers/interaction-touch-android-readiness.ts',
'src/daemon/handlers/interaction-touch-direct-ios-eligibility.ts',
'src/daemon/handlers/interaction-touch-direct-ios.ts',
'src/daemon/handlers/interaction-touch-fill.ts',
'src/daemon/handlers/interaction-touch-payload.ts',
'src/daemon/handlers/interaction-touch-policy.ts',
'src/daemon/handlers/interaction-touch-prepare.ts',
'src/daemon/handlers/interaction-touch-press-admission.ts',
'src/daemon/handlers/interaction-touch-press.ts',
'src/daemon/handlers/interaction-touch-reference-frame.ts',
'src/daemon/handlers/interaction-touch-response.ts',
'src/daemon/handlers/interaction-touch-runtime.ts',
'src/daemon/handlers/interaction-touch-targets.ts',
] as const;

export const SESSION_OBSERVABILITY_RETIRED_HANDLER_PATHS = [
'src/daemon/handlers/session-observability.ts',
'src/daemon/handlers/session-perf-runtime.ts',
Expand Down
2 changes: 2 additions & 0 deletions scripts/layering/check.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ import {
} from './model.ts';
import {
checkDaemonModularityRatchets,
checkRetiredInteractionPaths,
checkRetiredSessionLifecyclePaths,
checkRetiredSessionObservabilityPaths,
daemonModularitySummary,
Expand Down Expand Up @@ -587,6 +588,7 @@ export const LAYERING_RULES: Readonly<Record<LayeringRuleId, LayeringRule>> = {
'session-state-ownership': (context) => checkSessionStateOwnership(context.sources),
'daemon-modularity-ratchets': (context) => [
...checkDaemonModularityRatchets(context.edges, context.typeCycleMembers),
...checkRetiredInteractionPaths(context.sourceFiles),
...checkRetiredSessionLifecyclePaths(context.sourceFiles),
...checkRetiredSessionObservabilityPaths(context.sourceFiles),
],
Expand Down
37 changes: 35 additions & 2 deletions scripts/layering/daemon-modularity.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import assert from 'node:assert/strict';
import { test } from 'node:test';
import {
checkDaemonModularityRatchets,
checkRetiredInteractionPaths,
checkRetiredSessionLifecyclePaths,
checkRetiredSessionObservabilityPaths,
DAEMON_MODULARITY_BASELINE,
Expand Down Expand Up @@ -317,10 +318,18 @@ test('interaction rejects handler crossings and deep imports around its facade',
'src/daemon/handlers/interaction.ts',
"import { refSnapshotFlagGuardResponse } from '../interaction/internal/interaction-flags.ts';\nexport function handleInteractionCommands() {}",
],
[
'src/daemon/handlers/find.ts',
"import { preferOnscreenMatches } from '../interaction/internal/find-match-ranking.ts';\nexport function handleFindCommands() {}",
],
[
'src/daemon/interaction/internal/interaction-runtime.ts',
"import { handleInteractionCommands } from '../../handlers/interaction.ts';\nexport function createInteractionRuntime() {}",
],
[
'src/daemon/interaction/internal/find.ts',
"import { handleFindCommands } from '../../handlers/find.ts';\nexport function find() {}",
],
[
'src/daemon/generic-settle.ts',
"import { createInteractionRuntime } from './interaction/internal/interaction-runtime.ts';",
Expand All @@ -337,6 +346,10 @@ test('interaction rejects handler crossings and deep imports around its facade',
'src/daemon/interaction/internal/interaction-flags.ts',
'export function refSnapshotFlagGuardResponse() {}',
],
[
'src/daemon/interaction/internal/find-match-ranking.ts',
'export function preferOnscreenMatches() {}',
],
[
'src/daemon/interaction/internal/interaction-read.ts',
'export function readTextForNode() {}',
Expand All @@ -348,7 +361,7 @@ test('interaction rejects handler crossings and deep imports around its facade',
[...baselineEdges(), ...edges],
baselineTypeCycleMembers(),
);
assert.equal(violations.length, 5);
assert.equal(violations.length, 7);
assert.ok(
violations.some(({ message }) =>
message.includes(
Expand All @@ -367,7 +380,7 @@ test('interaction rejects handler crossings and deep imports around its facade',
violations.filter(({ message }) =>
message.includes("must not import daemon-interaction's internal tree"),
).length,
4,
5,
);
});

Expand Down Expand Up @@ -491,6 +504,26 @@ test('session observability rejects restored handler paths', () => {
);
});

test('interaction rejects restored handler paths', () => {
const restoredPaths = [
'src/daemon/handlers/find.ts',
'src/daemon/handlers/interaction-touch-direct-ios-regressed.ts',
'src/daemon/handlers/interaction-common-regressed.ts',
'src/daemon/interaction/internal/find.ts',
] as const;
const violations = checkRetiredInteractionPaths(restoredPaths);

assert.deepEqual(
violations.map(({ file, message }) => ({ file, message })),
restoredPaths.slice(0, 3).map((file) => ({
file,
message:
`retired interaction path was restored: ${file}. ` +
'Keep the neutral seam at its daemon owner instead of rebuilding a handler grab-bag.',
})),
);
});

test('R9 records zone ceilings and keeps engine files outside the largest component', () => {
// One commands file and one engine file traded for two provider-webdriver ones, so the
// total stays at the baseline and only the per-zone claims are on trial.
Expand Down
10 changes: 10 additions & 0 deletions scripts/layering/daemon-modularity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import path from 'node:path';
import {
LOGICAL_MODULE_POLICIES,
matchesDeclaredRoot,
INTERACTION_RETIRED_HANDLER_PATHS,
SESSION_LIFECYCLE_RETIRED_HANDLER_PATHS,
SESSION_OBSERVABILITY_RETIRED_HANDLER_PATHS,
type LogicalModulePolicy,
Expand Down Expand Up @@ -77,6 +78,15 @@ export function checkRetiredSessionObservabilityPaths(
);
}

export function checkRetiredInteractionPaths(sourceFiles: readonly string[]): LayeringViolation[] {
return checkRetiredHandlerPaths(
sourceFiles,
INTERACTION_RETIRED_HANDLER_PATHS,
/^src\/daemon\/handlers\/(?:find(?:-[^/]+)?|interaction(?:-[^/]+)?)\.ts$/,
'interaction',
);
}

function checkRetiredHandlerPaths(
sourceFiles: readonly string[],
retiredPaths: readonly string[],
Expand Down
6 changes: 4 additions & 2 deletions scripts/layering/model.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,6 @@ test('classifyZone separates the ranked spine from intentionally-unranked zones'
assert.equal(classifyZone('(root)'), 'unranked');
assert.equal(classifyZone('platform-runtime'), 'unranked');
assert.equal(classifyZone('platforms'), 'unclassified');
assert.equal(classifyZone('utils'), 'ranked');
// Every satellite zone joined the spine; only the composition root stays out, because R2
// forbids daemon/ from importing commands/ so the files that wire them cannot be ranked.
assert.equal(classifyZone('mcp'), 'ranked');
Expand Down Expand Up @@ -315,7 +314,10 @@ test('session-state writes are found by field, and non-daemon or undeclared name
// a local that is not a declared SessionState field
['src/daemon/session-observability/internal/session-audio.ts', 'session.somethingElse = 1;'],
// reads and comparisons are not writes
['src/daemon/handlers/find.ts', "if (session.refFrameState === 'active') return;"],
[
'src/daemon/interaction/internal/find.ts',
"if (session.refFrameState === 'active') return;",
],
// a write into a sub-object is not a write to the field itself
['src/daemon/handlers/session-probe.ts', 'session.refFrameState.inner = 1;'],
// a different binding that happens to have a matching property
Expand Down
5 changes: 0 additions & 5 deletions scripts/layering/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ const TARGET_DAG_RANK = new Map([
['screenshot-diff', 1],
['selectors', 1],
['snapshot', 1],
['utils', 1],
['core', 2],
['cli-schema', 3],
['commands', 3],
Expand Down Expand Up @@ -77,10 +76,6 @@ export function zoneRank(zone: string): number | null {
// the spine from above; extracted package zones are held by R11 package exports
// and the no-root-back-import rule instead of their former src folder rank.
//
// The satellite zones used to be listed here too, on the grounds that ranking them would
// invent an order the architecture had not committed to. Once `utils` joined the spine and
// `(root)` was emptied of shared contracts, every one of them turned out to have a
// consistent rank already — so the order was there, just unasserted.
// Extracted workspace packages are not src/ zones: R11 owns their physical seams, and their zone
// names only appear in workspace-aware graphs. The platform packages additionally carry R13's
// exact-family/composition/laziness policy.
Expand Down
Loading
Loading