Skip to content

Commit a007145

Browse files
authored
refactor(mcp): project push management family output schemas from their owning module (#3029)
* refactor(mcp): project push management family output schemas from their owning module Moves the 2 hand-authored MCP outputSchema entries for push and trigger-app-event out of command-output-schemas.ts into a frozen PUSH_MANAGEMENT_COMMAND_OUTPUT_SCHEMAS map exported from src/commands/management/push.ts, following the #2810 projection seam already used by the replay, system, interaction, and device-management families on this stack. Registers the family in the PROJECTED_FAMILIES disjointness check and adds a colocated command-tools-management-push-schemas.test.ts proving reference equality with the module object. Neither command carries the post-action observation trait, so no settle-graft copy applies here. The moved code is byte-identical to what command-output-schemas.ts held: same objectSchema/stringSchema/numberSchema calls and field order, with a locally duplicated constSchema helper matching the precedent from the interaction, system, and device-management families' moves. Part of #2819. * refactor(mcp): import the shared constSchema in the push management family
1 parent 03df0e8 commit a007145

4 files changed

Lines changed: 81 additions & 32 deletions

File tree

‎src/commands/management/push.ts‎

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import type {
44
JsonObject,
55
} from '@agent-device/contracts/client';
66
import type { CommandSchemaOverride } from '@agent-device/command-registry/command-schema';
7+
import type { CommandResultMap } from '@agent-device/command-registry/command-result';
78
import { PUBLIC_COMMANDS } from '@agent-device/command-registry/catalog';
89
import {
910
commonInputFromFlags,
@@ -12,10 +13,14 @@ import {
1213
requiredString,
1314
} from '../cli-grammar/common.ts';
1415
import type { CliReader, DaemonWriter } from '../cli-grammar/types.ts';
16+
import type { JsonSchema } from '../command-contract.ts';
1517
import {
18+
constSchema,
1619
jsonSchemaField,
1720
looseObjectField,
1821
looseObjectSchema,
22+
numberSchema,
23+
objectSchema,
1924
requiredField,
2025
stringField,
2126
stringSchema,
@@ -24,6 +29,47 @@ import {
2429
import { defineCommandFacet } from '../family/types.ts';
2530
import { defineFieldCommandMetadata } from '../field-command-contract.ts';
2631

32+
/**
33+
* This family's advertised MCP `outputSchema`s, keyed by daemon command name and projected into
34+
* the command map by `src/mcp/command-output-schemas.ts`. Non-strict like every other entry: no
35+
* `additionalProperties: false`, so additive response fields such as `cost` keep validating.
36+
* Neither command carries the post-action observation trait (#1652): both fire-and-report a push
37+
* delivery or an app event, not an interaction, so no settle graft applies here.
38+
*/
39+
export const PUSH_MANAGEMENT_COMMAND_OUTPUT_SCHEMAS = {
40+
// packages/contracts/src/push.ts — discriminated union on public platform.
41+
push: {
42+
type: 'object',
43+
oneOf: [
44+
objectSchema(
45+
{ platform: constSchema('ios'), bundleId: stringSchema(), message: stringSchema() },
46+
['platform', 'bundleId', 'message'],
47+
),
48+
objectSchema(
49+
{
50+
platform: constSchema('android'),
51+
package: stringSchema(),
52+
action: stringSchema(),
53+
extrasCount: numberSchema(),
54+
message: stringSchema(),
55+
},
56+
['platform', 'package', 'action', 'extrasCount', 'message'],
57+
),
58+
],
59+
},
60+
61+
// packages/contracts/src/app-events.ts
62+
'trigger-app-event': objectSchema(
63+
{
64+
event: stringSchema(),
65+
eventUrl: stringSchema(),
66+
transport: constSchema('deep-link'),
67+
message: stringSchema(),
68+
},
69+
['event', 'eventUrl', 'transport', 'message'],
70+
),
71+
} satisfies Pick<Record<keyof CommandResultMap, JsonSchema>, 'push' | 'trigger-app-event'>;
72+
2773
const pushCommandMetadata = defineFieldCommandMetadata(
2874
'push',
2975
'Deliver push notification payloads to an installed app.',
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import assert from 'node:assert/strict';
2+
import { test } from 'vitest';
3+
import { ownerFilesForCommand } from '@agent-device/command-registry/owner-files';
4+
import { PUSH_MANAGEMENT_COMMAND_OUTPUT_SCHEMAS } from '../../commands/management/push.ts';
5+
import { COMMAND_OUTPUT_SCHEMAS } from '../command-output-schemas.ts';
6+
7+
const PUSH_MANAGEMENT_COMMANDS = Object.keys(PUSH_MANAGEMENT_COMMAND_OUTPUT_SCHEMAS) as Array<
8+
keyof typeof PUSH_MANAGEMENT_COMMAND_OUTPUT_SCHEMAS
9+
>;
10+
11+
// Neither push nor trigger-app-event carries the post-action observation trait (#1652): both
12+
// fire-and-report a push delivery or an app event, not an interaction, so the composed map never
13+
// copies either entry and reference equality must hold for both.
14+
test('MCP push management family output schemas are the family module entries, not copies', () => {
15+
for (const command of PUSH_MANAGEMENT_COMMANDS) {
16+
assert.equal(
17+
COMMAND_OUTPUT_SCHEMAS[command],
18+
PUSH_MANAGEMENT_COMMAND_OUTPUT_SCHEMAS[command],
19+
`${command} is not reference-equal to the push management module's own schema object`,
20+
);
21+
}
22+
});
23+
24+
test('the projected family map declares exactly the commands its module owns', () => {
25+
for (const command of PUSH_MANAGEMENT_COMMANDS) {
26+
assert.ok(
27+
ownerFilesForCommand(command).includes('src/commands/management/push.ts'),
28+
`${command} projects its output schema from this module but does not name it as its owner`,
29+
);
30+
}
31+
});

‎src/mcp/__tests__/command-tools-replay-schemas.test.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import type { ReplayCommandResult, ReplaySuiteResult } from '@agent-device/contr
44
import { ownerFilesForCommand } from '@agent-device/command-registry/owner-files';
55
import { commandSupportsSettleObservation } from '@agent-device/command-registry/registry';
66
import { DEVICE_MANAGEMENT_COMMAND_OUTPUT_SCHEMAS } from '../../commands/management/device.ts';
7+
import { PUSH_MANAGEMENT_COMMAND_OUTPUT_SCHEMAS } from '../../commands/management/push.ts';
78
import { INTERACTION_COMMAND_OUTPUT_SCHEMAS } from '../../commands/interaction/index.ts';
89
import { REPLAY_COMMAND_OUTPUT_SCHEMAS } from '../../commands/replay/index.ts';
910
import { SYSTEM_COMMAND_OUTPUT_SCHEMAS } from '../../commands/system/index.ts';
@@ -75,6 +76,7 @@ const PROJECTED_FAMILIES = [
7576
{ name: 'system', schemas: SYSTEM_COMMAND_OUTPUT_SCHEMAS },
7677
{ name: 'interaction', schemas: INTERACTION_COMMAND_OUTPUT_SCHEMAS },
7778
{ name: 'management-device', schemas: DEVICE_MANAGEMENT_COMMAND_OUTPUT_SCHEMAS },
79+
{ name: 'management-push', schemas: PUSH_MANAGEMENT_COMMAND_OUTPUT_SCHEMAS },
7880
] as const;
7981

8082
test('projected output-schema families claim disjoint commands and survive the composition intact', () => {

‎src/mcp/command-output-schemas.ts‎

Lines changed: 2 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
stringSchema,
1313
} from '../commands/command-input.ts';
1414
import { DEVICE_MANAGEMENT_COMMAND_OUTPUT_SCHEMAS } from '../commands/management/device.ts';
15+
import { PUSH_MANAGEMENT_COMMAND_OUTPUT_SCHEMAS } from '../commands/management/push.ts';
1516
import {
1617
INTERACTION_COMMAND_OUTPUT_SCHEMAS,
1718
postActionSurfaceChangeSchema,
@@ -272,38 +273,6 @@ const BASE_COMMAND_OUTPUT_SCHEMAS = {
272273
],
273274
),
274275

275-
// packages/contracts/src/push.ts — discriminated union on public platform.
276-
push: {
277-
type: 'object',
278-
oneOf: [
279-
objectSchema(
280-
{ platform: constSchema('ios'), bundleId: stringSchema(), message: stringSchema() },
281-
['platform', 'bundleId', 'message'],
282-
),
283-
objectSchema(
284-
{
285-
platform: constSchema('android'),
286-
package: stringSchema(),
287-
action: stringSchema(),
288-
extrasCount: numberSchema(),
289-
message: stringSchema(),
290-
},
291-
['platform', 'package', 'action', 'extrasCount', 'message'],
292-
),
293-
],
294-
},
295-
296-
// packages/contracts/src/app-events.ts
297-
'trigger-app-event': objectSchema(
298-
{
299-
event: stringSchema(),
300-
eventUrl: stringSchema(),
301-
transport: constSchema('deep-link'),
302-
message: stringSchema(),
303-
},
304-
['event', 'eventUrl', 'transport', 'message'],
305-
),
306-
307276
// packages/contracts/src/doctor.ts
308277
doctor: objectSchema(
309278
{
@@ -430,6 +399,7 @@ const BASE_COMMAND_OUTPUT_SCHEMAS = {
430399
// a projected command then fails as TS2783 instead of quietly overriding the family's,
431400
// and this map's `satisfies` still refuses a missing `CommandResultMap` key.
432401
...DEVICE_MANAGEMENT_COMMAND_OUTPUT_SCHEMAS,
402+
...PUSH_MANAGEMENT_COMMAND_OUTPUT_SCHEMAS,
433403
...INTERACTION_COMMAND_OUTPUT_SCHEMAS,
434404
...REPLAY_COMMAND_OUTPUT_SCHEMAS,
435405
...SYSTEM_COMMAND_OUTPUT_SCHEMAS,

0 commit comments

Comments
 (0)