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
6 changes: 3 additions & 3 deletions fallow-baselines/health.json
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@
"count": 1
}
},
"src/daemon/handlers/__tests__/session-appstate-input-perf.test.ts": {
"src/daemon/handlers/__tests__/session-appstate-input.test.ts": {
"crap_high": {
"count": 3
},
Expand All @@ -189,7 +189,7 @@
"count": 1
}
},
"src/daemon/handlers/__tests__/session-logs.test.ts": {
"src/daemon/session-observability/internal/__tests__/session-logs.test.ts": {
"crap_moderate": {
"count": 1
}
Expand Down Expand Up @@ -230,7 +230,7 @@
"count": 1
}
},
"src/daemon/handlers/session-observability.ts": {
"src/daemon/session-observability/internal/session-observability.ts": {
"crap_moderate": {
"count": 2
}
Expand Down
8 changes: 8 additions & 0 deletions scripts/layering/architecture-ownership.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
ARCHITECTURE_OWNERSHIP,
matchesDeclaredRoot,
SESSION_LIFECYCLE_RETIRED_HANDLER_PATHS,
SESSION_OBSERVABILITY_RETIRED_HANDLER_PATHS,
} from './architecture-ownership.ts';
import { readNamedExports } from './facade-exports.ts';
import { resolveImportEdges } from './model.ts';
Expand Down Expand Up @@ -66,6 +67,13 @@ test('session lifecycle retires its handler-owned helper paths', () => {
}
});

test('session observability retires its handler-owned paths', () => {
const tracked = new Set(listTrackedTypeScriptFiles(repoRoot));
for (const retiredPath of SESSION_OBSERVABILITY_RETIRED_HANDLER_PATHS) {
assert.equal(tracked.has(retiredPath), false, `retired path was restored: ${retiredPath}`);
}
});

test('vocabulary roots are exported contract facades', () => {
const manifest = JSON.parse(
fs.readFileSync(path.join(repoRoot, 'packages/contracts/package.json'), 'utf8'),
Expand Down
18 changes: 18 additions & 0 deletions scripts/layering/architecture-ownership.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ const DAEMON_SESSION_LIFECYCLE_FACADE = {
],
} as const;

const DAEMON_SESSION_OBSERVABILITY_FACADE = {
root: 'src/daemon/session-observability/index.ts',
exports: ['SessionObservabilityCommandInput', 'handleSessionObservabilityCommands'],
} as const;

export const SESSION_LIFECYCLE_RETIRED_HANDLER_PATHS = [
'src/daemon/handlers/session-device-utils.ts',
'src/daemon/handlers/session-runtime-admission.ts',
Expand Down Expand Up @@ -60,6 +65,13 @@ const DAEMON_INTERACTION_FACADE = {
],
} as const;

export const SESSION_OBSERVABILITY_RETIRED_HANDLER_PATHS = [
'src/daemon/handlers/session-observability.ts',
'src/daemon/handlers/session-perf-runtime.ts',
'src/daemon/handlers/session-network.ts',
'src/daemon/handlers/session-audio.ts',
] as const;

export const LOGICAL_MODULE_POLICIES = [
{
name: 'ad-replay',
Expand Down Expand Up @@ -106,6 +118,12 @@ export const LOGICAL_MODULE_POLICIES = [
internalForbiddenTargetRoots: ['src/daemon/handlers/'],
facade: DAEMON_INTERACTION_FACADE,
},
{
name: 'daemon-session-observability',
roots: ['src/daemon/session-observability/'],
forbiddenTargetRoots: ['src/daemon/handlers/'],
facade: DAEMON_SESSION_OBSERVABILITY_FACADE,
},
] as const satisfies readonly LogicalModulePolicy[];

export const ARCHITECTURE_OWNERSHIP = {
Expand Down
2 changes: 2 additions & 0 deletions scripts/layering/check.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ import {
import {
checkDaemonModularityRatchets,
checkRetiredSessionLifecyclePaths,
checkRetiredSessionObservabilityPaths,
daemonModularitySummary,
} from './daemon-modularity.ts';
import {
Expand Down Expand Up @@ -585,6 +586,7 @@ export const LAYERING_RULES: Readonly<Record<LayeringRuleId, LayeringRule>> = {
'daemon-modularity-ratchets': (context) => [
...checkDaemonModularityRatchets(context.edges, context.typeCycleMembers),
...checkRetiredSessionLifecyclePaths(context.sourceFiles),
...checkRetiredSessionObservabilityPaths(context.sourceFiles),
],
'daemon-platform-boundary': (context) =>
checkDaemonPlatformBoundary([...context.sources].map(([path, source]) => ({ path, source }))),
Expand Down
69 changes: 69 additions & 0 deletions scripts/layering/daemon-modularity.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { test } from 'node:test';
import {
checkDaemonModularityRatchets,
checkRetiredSessionLifecyclePaths,
checkRetiredSessionObservabilityPaths,
DAEMON_MODULARITY_BASELINE,
TYPE_CYCLE_BASELINE,
} from './daemon-modularity.ts';
Expand Down Expand Up @@ -370,6 +371,50 @@ test('interaction rejects handler crossings and deep imports around its facade',
);
});

test('session observability rejects handler deep imports in both directions', () => {
const edges = resolveImportEdges(
new Map([
[
'src/daemon/handlers/session.ts',
"import { handleSessionObservabilityCommands } from '../session-observability/internal/session-observability.ts';\nexport function handleSessionCommands() {}",
],
[
'src/daemon/session-observability/internal/session-observability.ts',
"import { handleSessionCommands } from '../../handlers/session.ts';\nexport function handleSessionObservabilityCommands() {}",
],
[
'src/daemon/session-observability/index.ts',
'export function handleSessionObservabilityCommands() {}',
],
]),
);

const violations = checkDaemonModularityRatchets(
[...baselineEdges(), ...edges],
baselineTypeCycleMembers(),
);
assert.deepEqual(
violations.map(({ file, line, message }) => ({
file,
line,
message: message.replace(/;.*/, ''),
})),
[
{
file: 'src/daemon/handlers/session.ts',
line: 1,
message:
"src/daemon/handlers/session.ts must not import daemon-session-observability's internal tree (src/daemon/session-observability/internal/session-observability.ts)",
},
{
file: 'src/daemon/session-observability/internal/session-observability.ts',
line: 1,
message: 'daemon-session-observability must not import src/daemon/handlers/session.ts',
},
],
);
});

test('session lifecycle rejects restored neutral helper paths', () => {
const violations = checkRetiredSessionLifecyclePaths([
'src/daemon/session-device-resolution.ts',
Expand Down Expand Up @@ -422,6 +467,30 @@ test('session lifecycle rejects any restored open or close handler path', () =>
);
});

test('session observability rejects restored handler paths', () => {
const restoredPaths = [
'src/daemon/handlers/session-observability.ts',
'src/daemon/handlers/session-perf-runtime.ts',
'src/daemon/handlers/session-network.ts',
'src/daemon/handlers/session-audio.ts',
'src/daemon/handlers/session-network-regressed.ts',
'src/daemon/handlers/session-perf.ts',
'src/daemon/handlers/session-logs.ts',
'src/daemon/handlers/session-events.ts',
] as const;
const violations = checkRetiredSessionObservabilityPaths(restoredPaths);

assert.deepEqual(
violations.map(({ file, message }) => ({ file, message })),
restoredPaths.map((file) => ({
file,
message:
`retired session observability 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
47 changes: 35 additions & 12 deletions scripts/layering/daemon-modularity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
LOGICAL_MODULE_POLICIES,
matchesDeclaredRoot,
SESSION_LIFECYCLE_RETIRED_HANDLER_PATHS,
SESSION_OBSERVABILITY_RETIRED_HANDLER_PATHS,
type LogicalModulePolicy,
} from './architecture-ownership.ts';
import { targetDagZone, type LayeringViolation, type ResolvedImportEdge } from './model.ts';
Expand Down Expand Up @@ -57,19 +58,41 @@ export function checkDaemonModularityRatchets(
export function checkRetiredSessionLifecyclePaths(
sourceFiles: readonly string[],
): LayeringViolation[] {
const restoredPaths = sourceFiles.filter(
(file) =>
SESSION_LIFECYCLE_RETIRED_HANDLER_PATHS.some((retiredPath) => retiredPath === file) ||
/^src\/daemon\/handlers\/session-(?:open|close)(?:-[^/]+)?\.ts$/.test(file),
return checkRetiredHandlerPaths(
sourceFiles,
SESSION_LIFECYCLE_RETIRED_HANDLER_PATHS,
/^src\/daemon\/handlers\/session-(?:open|close)(?:-[^/]+)?\.ts$/,
'session lifecycle',
);
return restoredPaths.map((file) => ({
rule: 'R10 daemon-modularity',
file,
line: 1,
message:
`retired session lifecycle path was restored: ${file}. ` +
'Keep the neutral seam at its daemon owner instead of rebuilding a handler grab-bag.',
}));
}

export function checkRetiredSessionObservabilityPaths(
sourceFiles: readonly string[],
): LayeringViolation[] {
return checkRetiredHandlerPaths(
sourceFiles,
SESSION_OBSERVABILITY_RETIRED_HANDLER_PATHS,
/^src\/daemon\/handlers\/session-(?:observability|perf|logs|events|network|audio)(?:-[^/]+)?\.ts$/,
'session observability',
);
}

function checkRetiredHandlerPaths(
sourceFiles: readonly string[],
retiredPaths: readonly string[],
pattern: RegExp,
capability: string,
): LayeringViolation[] {
return sourceFiles
.filter((file) => retiredPaths.includes(file) || pattern.test(file))
.map((file) => ({
rule: 'R10 daemon-modularity',
file,
line: 1,
message:
`retired ${capability} path was restored: ${file}. ` +
'Keep the neutral seam at its daemon owner instead of rebuilding a handler grab-bag.',
}));
}

function checkSessionStateBaseline(): LayeringViolation[] {
Expand Down
2 changes: 1 addition & 1 deletion scripts/layering/model.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,7 @@ test('session-state writes are found by field, and non-daemon or undeclared name
// a runner session outside the daemon is a different type that happens to share a name
['src/platforms/apple/runner-session.ts', 'session.refFrameState = 1;'],
// a local that is not a declared SessionState field
['src/daemon/handlers/session-audio.ts', 'session.somethingElse = 1;'],
['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;"],
// a write into a sub-object is not a write to the field itself
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -272,25 +272,3 @@ test('clipboard rejects unsupported iOS physical devices', async () => {
expect(response.error.message).toMatch(/clipboard is not supported on this device/i);
}
});

test('perf requires an active session', async () => {
const sessionStore = makeSessionStore();
const response = await handleSessionCommands({
req: {
token: 't',
session: 'default',
command: 'perf',
positionals: [],
flags: {},
},
sessionName: 'default',
logPath: path.join(os.tmpdir(), 'daemon.log'),
sessionStore,
invoke: noopInvoke,
});
expect(response).toBeTruthy();
expect(response?.ok).toBe(false);
if (response && !response.ok) {
expect(response.error.code).toBe('SESSION_NOT_FOUND');
}
});
2 changes: 1 addition & 1 deletion src/daemon/handlers/find.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { contextFromFlags } from '../context.ts';
import { readCommandMessage, successText } from '@agent-device/kernel/success-text';
import { errorResponse, noActiveSessionError } from '../response.ts';
import { withSystemSurfaceDisclosure } from './system-surface-disclosure.ts';
import { recordSessionAction } from './handler-utils.ts';
import { recordSessionAction } from '../session-action-recorder.ts';
import { stripInternalInteractionFlags } from '../interaction-outcome-policy.ts';
import { resolveFindMatch } from './find-match-resolution.ts';
import { executeFocusPoint } from '../focus-runtime.ts';
Expand Down
26 changes: 0 additions & 26 deletions src/daemon/handlers/handler-utils.ts

This file was deleted.

2 changes: 1 addition & 1 deletion src/daemon/handlers/record-runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import { resolveSessionScope } from '../session-routing.ts';
import type { SessionStore } from '../session-store.ts';
import type { BindDeviceRuntime, BindExactDeviceRuntime } from '../request-runtime-binding.ts';
import type { DaemonRequest, DaemonResponse, SessionState } from '../types.ts';
import { recordSessionAction } from './handler-utils.ts';
import { recordSessionAction } from '../session-action-recorder.ts';
import {
missingAppSessionResponse,
prepareRecordingRequest,
Expand Down
2 changes: 1 addition & 1 deletion src/daemon/handlers/session-app-deployment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import type { DaemonRequest, DaemonResponse, SessionState } from '../types.ts';
import { resolvePayloadInput } from '../../utils/payload-input.ts';
import { resolveDeployResultTarget } from '../result-serialization.ts';
import { withSuccessText } from '@agent-device/kernel/success-text';
import { recordSessionAction } from './handler-utils.ts';
import { recordSessionAction } from '../session-action-recorder.ts';
import { errorResponse } from '../response.ts';
import {
requireSessionOrExplicitSelector,
Expand Down
2 changes: 1 addition & 1 deletion src/daemon/handlers/session-app-source-deployment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { SessionStore } from '../session-store.ts';
import type { DaemonRequest, DaemonResponse, SessionState } from '../types.ts';
import { resolveInstallFromSourceResultTarget } from '../result-serialization.ts';
import { withSuccessText } from '@agent-device/kernel/success-text';
import { recordSessionAction } from './handler-utils.ts';
import { recordSessionAction } from '../session-action-recorder.ts';
import { resolveCommandDevice } from '../session-device-resolution.ts';
import {
requireRuntimeBinding,
Expand Down
2 changes: 1 addition & 1 deletion src/daemon/handlers/session-clipboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { admitRuntimeUse, type RuntimeAdmissionBindings } from '../runtime-admis
import { runtimeExecutionFromContext } from '../snapshot-runtime-capture-input.ts';
import { successText } from '@agent-device/kernel/success-text';
import { errorResponse, type DaemonFailureResponse } from '../response.ts';
import { recordSessionAction } from './handler-utils.ts';
import { recordSessionAction } from '../session-action-recorder.ts';
import {
requireSessionOrExplicitSelector,
resolveCommandDevice,
Expand Down
2 changes: 1 addition & 1 deletion src/daemon/handlers/session-selector-dispatch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
resolveCommandDevice,
} from '../session-device-resolution.ts';
import { errorResponse } from '../response.ts';
import { recordSessionAction } from './handler-utils.ts';
import { recordSessionAction } from '../session-action-recorder.ts';
import { resolveBoundAppEventRuntime } from '../app-event-runtime.ts';
import { resolveBoundKeyboardRuntime } from '../keyboard-runtime.ts';
import { resolveRefFrameEffect } from '../daemon-command-registry.ts';
Expand Down
5 changes: 3 additions & 2 deletions src/daemon/handlers/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
handleSessionOpenCommands,
} from '../session-lifecycle/index.ts';
import { handleSessionStateCommands } from './session-state.ts';
import { handleSessionObservabilityCommands } from './session-observability.ts';
import { handleSessionObservabilityCommands } from '../session-observability/index.ts';
import { handleReplayCommand, handleReplayTestCommand } from './session-replay-command.ts';
import { handleSessionScriptPublication } from './session-script-publication.ts';
import { handleSessionClipboardCommand } from './session-clipboard.ts';
Expand All @@ -26,6 +26,7 @@ import type {
SessionInventoryCommandInput,
SessionOpenCommandInput,
} from '../session-lifecycle/index.ts';
import type { SessionObservabilityCommandInput } from '../session-observability/index.ts';
import type { DescriptorSessionRouteCommandName } from '../../core/command-descriptor/registry.ts';
import { LeaseRegistry } from '../lease-registry.ts';

Expand Down Expand Up @@ -93,7 +94,7 @@ const handleSessionObservabilityCommandGroup: SessionCommandHandler = async ({
audioProbeAdmissionLedger,
perfCaptureAdmissionLedger,
throwIfCanceled,
});
} satisfies SessionObservabilityCommandInput);

/**
* Descriptor-driven exhaustive dispatch table for the daemon's `session`
Expand Down
Loading
Loading