From 2dcc943215c1c04a9d0222c1397b2e0e591ea07a Mon Sep 17 00:00:00 2001 From: Relayflow Date: Tue, 22 Sep 2026 18:39:48 +0000 Subject: [PATCH 1/4] fix(fleet): retire removed workspace rollout API --- .../skills/orchestrating-agent-relay/SKILL.md | 5 +- .../manifest-contract.test.ts | 4 - .agentworkforce/features/manifest.yaml | 28 -- .agentworkforce/features/verify/procedures.md | 10 +- .../compact_7kbovjgptr28_2026-09-22.json | 48 ++++ .../compact_7kbovjgptr28_2026-09-22.md | 21 ++ .../skills/orchestrating-agent-relay/SKILL.md | 5 +- CHANGELOG.md | 13 + flows/verify/features.spec.ts | 8 +- flows/verify/fleet-daytona.spec.ts | 4 +- packages/cli/src/cli/bootstrap.test.ts | 6 +- packages/cli/src/cli/commands/fleet.test.ts | 108 ++++---- packages/cli/src/cli/commands/fleet.ts | 54 ++-- packages/sdk/src/__tests__/facade.test.ts | 30 -- packages/sdk/src/__tests__/messaging.test.ts | 30 -- .../sdk/src/__tests__/observer-source.test.ts | 2 +- packages/sdk/src/facade.ts | 7 - .../sdk/src/messaging/relaycast-client.ts | 5 - .../sdk/src/messaging/relaycast-translate.ts | 10 - packages/sdk/src/messaging/relaycast.ts | 24 -- packages/sdk/src/messaging/types.ts | 11 - scripts/verify-features/fleet-daytona.mjs | 145 ++-------- summary.md | 19 ++ tests/fixtures/verify-fleet-daytona.test.ts | 27 +- tests/relayflows/cleanroom/README.md | 10 +- .../cleanroom/fleet-cli-inventory.json | 257 +++++++----------- .../cleanroom/fleet-daytona.matrix.json | 143 ++++------ tests/relayflows/cleanroom/relay.matrix.json | 4 - 28 files changed, 392 insertions(+), 646 deletions(-) create mode 100644 .agentworkforce/trajectories/compacted/compact_7kbovjgptr28_2026-09-22.json create mode 100644 .agentworkforce/trajectories/compacted/compact_7kbovjgptr28_2026-09-22.md create mode 100644 summary.md diff --git a/.agents/skills/orchestrating-agent-relay/SKILL.md b/.agents/skills/orchestrating-agent-relay/SKILL.md index ffeae9eb5b..cca358472e 100644 --- a/.agents/skills/orchestrating-agent-relay/SKILL.md +++ b/.agents/skills/orchestrating-agent-relay/SKILL.md @@ -265,10 +265,7 @@ available via SDK subscriptions): #### When you coordinate across nodes rather than only the local broker, capabilities ```bash -# Enable fleet nodes for the workspace FIRST — it is off by default, and a -# node you bring up before enabling will not register/list -agent-relay fleet enable -agent-relay fleet config # inspect workspace fleet config +# Fleet nodes need no per-workspace enablement. agent-relay fleet status # local broker status + this node's provider attachment # Bring this node up, serving its node definition (advertises its capabilities). diff --git a/.agentworkforce/agents/relay-feature-guardian/manifest-contract.test.ts b/.agentworkforce/agents/relay-feature-guardian/manifest-contract.test.ts index 5156735ef3..44b842eca2 100644 --- a/.agentworkforce/agents/relay-feature-guardian/manifest-contract.test.ts +++ b/.agentworkforce/agents/relay-feature-guardian/manifest-contract.test.ts @@ -136,10 +136,6 @@ describe('feature manifest contract', () => { 'relay fleet nodes', 'relay fleet spawn', 'relay fleet release', - 'relay fleet config', - 'relay fleet enable', - 'relay fleet disable', - 'relay fleet inherit', 'relay fleet status', // Mounted product surfaces. Each product's real tree lives in its own SDK // spec, so the manifest documents the group and the surface owns the rest. diff --git a/.agentworkforce/features/manifest.yaml b/.agentworkforce/features/manifest.yaml index 46ab5ecedd..f95f9a189d 100644 --- a/.agentworkforce/features/manifest.yaml +++ b/.agentworkforce/features/manifest.yaml @@ -731,34 +731,6 @@ categories: location: packages/cli/src/cli/commands/fleet.ts verify_tier: 5 - - id: fleet-config - name: Fleet Config - cli: relay fleet config - description: Show the workspace fleet-node configuration - location: packages/cli/src/cli/commands/fleet.ts - verify_tier: 5 - - - id: fleet-enable - name: Enable Fleet - cli: relay fleet enable - description: Enable fleet nodes for the workspace (modifies cloud state) - location: packages/cli/src/cli/commands/fleet.ts - verify_tier: 5 - - - id: fleet-disable - name: Disable Fleet - cli: relay fleet disable - description: Disable fleet nodes for the workspace (modifies cloud state) - location: packages/cli/src/cli/commands/fleet.ts - verify_tier: 5 - - - id: fleet-inherit - name: Inherit Fleet Config - cli: relay fleet inherit - description: Use deployment defaults for fleet node configuration (modifies cloud state) - location: packages/cli/src/cli/commands/fleet.ts - verify_tier: 5 - - id: fleet-status name: Fleet Node Status cli: relay fleet status diff --git a/.agentworkforce/features/verify/procedures.md b/.agentworkforce/features/verify/procedures.md index 931e55b740..31240a07a8 100644 --- a/.agentworkforce/features/verify/procedures.md +++ b/.agentworkforce/features/verify/procedures.md @@ -329,19 +329,17 @@ The CLI persists the daemon PID and log path in its isolated local worker state, ## fleet-management -**Features:** `fleet-nodes`, `fleet-nodes-pretty`, `fleet-config`, `fleet-enable`, `fleet-disable`, `fleet-inherit`, `fleet-status`. +**Features:** `fleet-nodes`, `fleet-nodes-pretty`, `fleet-status`. -**Prerequisites:** disposable workspace; `fleet-status` additionally benefits from a local broker. +**Prerequisites:** workspace credentials; `fleet-status` additionally benefits from a local broker. ```bash relay fleet nodes | jq -e '.nodes' relay fleet nodes list --pretty | grep -Eiq 'NODE|No fleet nodes found' -BEFORE="$(relay fleet config)" -relay fleet enable; relay fleet config | jq -e 'type == "object"' -relay fleet disable; relay fleet inherit; relay fleet status | jq -e '.broker' +relay fleet status | jq -e '.broker' ``` -Snapshot and restore configuration or discard the workspace. For full two-node dispatch/enrollment coverage, run `npm run test:e2e` with `tests/e2e/fleet/README.md` prerequisites. +For full two-node dispatch/enrollment coverage, run `npm run test:e2e` with `tests/e2e/fleet/README.md` prerequisites. ## workspace-management diff --git a/.agentworkforce/trajectories/compacted/compact_7kbovjgptr28_2026-09-22.json b/.agentworkforce/trajectories/compacted/compact_7kbovjgptr28_2026-09-22.json new file mode 100644 index 0000000000..aab925004a --- /dev/null +++ b/.agentworkforce/trajectories/compacted/compact_7kbovjgptr28_2026-09-22.json @@ -0,0 +1,48 @@ +{ + "id": "compact_7kbovjgptr28", + "version": 1, + "type": "compacted", + "compactedAt": "2026-09-22T18:39:39.513Z", + "sourceTrajectories": [ + "traj_xjrrec3f7hzx" + ], + "dateRange": { + "start": "2026-09-22T18:32:56.391Z", + "end": "2026-09-22T18:39:20.897Z" + }, + "summary": { + "totalDecisions": 2, + "totalEvents": 2, + "uniqueAgents": [ + "default" + ] + }, + "decisionGroups": [ + { + "category": "api", + "decisions": [ + { + "question": "Keep hidden exit-0 compatibility commands and retain their cleanroom coverage", + "chosen": "Keep hidden exit-0 compatibility commands and retain their cleanroom coverage", + "reasoning": "Reviewed plan Option A preserves shipped scripts without invoking the removed API.", + "fromTrajectory": "traj_xjrrec3f7hzx" + } + ] + }, + { + "category": "testing", + "decisions": [ + { + "question": "Regenerate the full CLI inventory and reconcile existing option drift", + "chosen": "Regenerate the full CLI inventory and reconcile existing option drift", + "reasoning": "The live inventory includes new release/spawn options and removes obsolete node-agent options; matrix coverage must match the generated inventory exactly.", + "fromTrajectory": "traj_xjrrec3f7hzx" + } + ] + } + ], + "keyLearnings": [], + "keyFindings": [], + "filesAffected": [], + "commits": [] +} \ No newline at end of file diff --git a/.agentworkforce/trajectories/compacted/compact_7kbovjgptr28_2026-09-22.md b/.agentworkforce/trajectories/compacted/compact_7kbovjgptr28_2026-09-22.md new file mode 100644 index 0000000000..07800c74a3 --- /dev/null +++ b/.agentworkforce/trajectories/compacted/compact_7kbovjgptr28_2026-09-22.md @@ -0,0 +1,21 @@ +# Trajectory Compaction: Sep 22, 2026 - Sep 22, 2026 + +## Summary +- Sessions: 1 +- Decisions: 2 +- Events: 2 +- Agents: default +- Files: 0 +- Commits: 0 + +## Api +- Keep hidden exit-0 compatibility commands and retain their cleanroom coverage -> Keep hidden exit-0 compatibility commands and retain their cleanroom coverage (traj_xjrrec3f7hzx) + +## Testing +- Regenerate the full CLI inventory and reconcile existing option drift -> Regenerate the full CLI inventory and reconcile existing option drift (traj_xjrrec3f7hzx) + +## Key Learnings +- None + +## Key Findings +- None \ No newline at end of file diff --git a/.claude/skills/orchestrating-agent-relay/SKILL.md b/.claude/skills/orchestrating-agent-relay/SKILL.md index 8e548c2045..98ec970b64 100644 --- a/.claude/skills/orchestrating-agent-relay/SKILL.md +++ b/.claude/skills/orchestrating-agent-relay/SKILL.md @@ -512,10 +512,7 @@ When you coordinate across nodes rather than only the local broker, capabilities and placement come into play: ```bash -# Enable fleet nodes for the workspace FIRST — it is off by default, and a -# node you bring up before enabling will not register/list -agent-relay fleet enable -agent-relay fleet config # inspect workspace fleet config +# Fleet nodes need no per-workspace enablement. agent-relay fleet status # local broker status + this node's provider attachment # Bring this node up, serving its node definition (advertises its capabilities). diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f8707793c..19f43d68cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,8 +9,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +- `agent-relay fleet config|enable|disable|inherit` now exit successfully as hidden compatibility no-ops instead of failing on the removed workspace rollout API. - Broker `manual_flush` recovery now replays a missing cumulative-ACK predecessor without duplicating an already-completed PTY injection, restores it ahead of parked successors, and reports the head/ACK/received sequence gap plus the reconciliation action in `message flush` and `message auto` results. +### Removed + +- `@agent-relay/sdk`: removed `workspace.fleetNodes` and `RelayWorkspaceFleetNodesConfig`, whose underlying service API no longer exists. + +### Breaking Changes + +- SDK consumers must remove references to `workspace.fleetNodes` and `RelayWorkspaceFleetNodesConfig`. + +### Migration Guidance + +- Fleet nodes need no per-workspace enablement. Remove `fleet enable` from provisioning scripts; `fleet disable` no longer disables nodes. + ## [12.4.1] - 2026-09-22 ### Added diff --git a/flows/verify/features.spec.ts b/flows/verify/features.spec.ts index 4a7914a9d8..9447639bbd 100644 --- a/flows/verify/features.spec.ts +++ b/flows/verify/features.spec.ts @@ -1010,10 +1010,10 @@ skip_check "cloud enroll" "requires interactive browser auth" gated_check cloud "fleet nodes" "relay fleet nodes" "." gated_check cloud "fleet nodes list --pretty" \ "relay fleet nodes list --pretty | grep -E '^(NODE|No fleet nodes found\.)'" "." -gated_check cloud "fleet config" "relay fleet config" "." -gated_check cloud "fleet inherit" "relay fleet inherit --help" "Usage" -skip_check "fleet enable" "mutates workspace cloud state" -skip_check "fleet disable" "mutates workspace cloud state" +run_check "fleet config" "relay fleet config" "deprecated" +run_check "fleet inherit" "relay fleet inherit 2>&1" "no-op" +run_check "fleet enable" "relay fleet enable 2>&1" "no-op" +run_check "fleet disable" "relay fleet disable 2>&1" "no-op" # fleet spawn/release were undocumented in the manifest until this change and # are still unverified: spawning burns provider credits on a remote node. diff --git a/flows/verify/fleet-daytona.spec.ts b/flows/verify/fleet-daytona.spec.ts index 52d128eeb8..4999562b49 100644 --- a/flows/verify/fleet-daytona.spec.ts +++ b/flows/verify/fleet-daytona.spec.ts @@ -30,8 +30,8 @@ * The flag is still accepted on Node 24 and 26, so one invocation works * everywhere. * - * Workspace-wide enable/disable/inherit probes are safety-skipped unless the - * active workspace is disposable and VERIFY_FLEET_DISPOSABLE_WORKSPACE=1. + * Legacy fleet config/enable/disable/inherit probes verify deprecated no-ops. + * VERIFY_FLEET_DISPOSABLE_WORKSPACE=1 still requires a clean baseline. */ import { randomBytes } from 'node:crypto'; diff --git a/packages/cli/src/cli/bootstrap.test.ts b/packages/cli/src/cli/bootstrap.test.ts index e22a855d85..f07483fdba 100644 --- a/packages/cli/src/cli/bootstrap.test.ts +++ b/packages/cli/src/cli/bootstrap.test.ts @@ -41,12 +41,8 @@ const expectedLeafCommands = [ 'flows', // `session` (singular) is the hidden alias of `sessions`, so it is absent here. 'sessions', - // fleet (serve is a hidden error stub, filtered out below) + // fleet (serve is a hidden error stub; config/enable/disable/inherit are hidden no-ops) 'fleet agent list', - 'fleet config', - 'fleet disable', - 'fleet enable', - 'fleet inherit', 'fleet nodes list', 'fleet release', 'fleet spawn', diff --git a/packages/cli/src/cli/commands/fleet.test.ts b/packages/cli/src/cli/commands/fleet.test.ts index f229c1936d..fe22e7a912 100644 --- a/packages/cli/src/cli/commands/fleet.test.ts +++ b/packages/cli/src/cli/commands/fleet.test.ts @@ -109,58 +109,64 @@ describe('spawn lifecycle receipts', () => { }); describe('fleet command support', () => { - it.each([ - ['config', 'get', undefined], - ['enable', 'set', true], - ['disable', 'set', false], - ['inherit', 'inherit', undefined], - ] as const)('fleet %s delegates to workspace fleet node config API', async (command, method, value) => { - const fleetNodes = { - get: vi.fn(async () => ({ enabled: false, defaultEnabled: false, override: null })), - set: vi.fn(async (enabled: boolean) => ({ enabled, defaultEnabled: false, override: enabled })), - inherit: vi.fn(async () => ({ enabled: false, defaultEnabled: false, override: null })), - }; - const createWorkspaceRelay = vi.fn(() => ({ workspace: { fleetNodes } })); - const logs: string[] = []; - const program = new Command(); - program.exitOverride(); - registerFleetCommands(program, { - resolveSandboxRepository: () => undefined, - sdk: { - createAgentRelay: vi.fn() as never, - createWorkspaceRelay: createWorkspaceRelay as never, - createWorkspace: vi.fn() as never, - log: (message: unknown) => logs.push(String(message)), - error: vi.fn(), - exit: vi.fn(() => { - throw new Error('__exit__'); - }) as never, - }, - log: () => undefined, - warn: () => undefined, - error: () => undefined, - }); - - await program.parseAsync( - ['fleet', command, '--workspace-key', 'rk_live_test', '--base-url', 'https://relay.example'], - { from: 'user' } - ); - - expect(createWorkspaceRelay).toHaveBeenCalledWith({ - workspaceKey: 'rk_live_test', - token: undefined, - baseUrl: 'https://relay.example', - }); - if (method === 'set') { - expect(fleetNodes.set).toHaveBeenCalledWith(value); - } else { - expect(fleetNodes[method]).toHaveBeenCalledTimes(1); + it.each(['config', 'enable', 'disable', 'inherit'])( + 'fleet %s is a hidden no-op without workspace access', + async (command) => { + const createWorkspaceRelay = vi.fn(() => { + throw new Error('workspace access forbidden'); + }); + const createFleetWorkspaceClient = vi.fn(() => { + throw new Error('fleet access forbidden'); + }); + const logs: string[] = []; + const errors: string[] = []; + const exit = vi.fn(); + const program = new Command(); + program.exitOverride(); + registerFleetCommands(program, { + createFleetWorkspaceClient: createFleetWorkspaceClient as never, + sdk: { + createAgentRelay: vi.fn() as never, + createWorkspaceRelay: createWorkspaceRelay as never, + createWorkspace: vi.fn() as never, + log: (message: unknown) => logs.push(String(message)), + error: vi.fn(), + exit: exit as never, + }, + log: () => undefined, + warn: () => undefined, + error: (message: string) => errors.push(message), + exit: exit as never, + }); + // Both credential-free fresh workspaces and older scripts with SDK options work. + for (const options of [[], ['--workspace-key', 'rk_live_test', '--base-url', 'http://127.0.0.1:1']]) { + await program.parseAsync(['fleet', command, ...options], { from: 'user' }); + } + expect(createWorkspaceRelay).not.toHaveBeenCalled(); + expect(createFleetWorkspaceClient).not.toHaveBeenCalled(); + expect(exit).not.toHaveBeenCalled(); + expect(errors.join('\n')).toContain( + 'Fleet nodes need no per-workspace enablement; this command is a no-op.' + ); + expect(errors.join('\n')).not.toContain('requires @relaycast/sdk'); + const group = program.commands.find((entry) => entry.name() === 'fleet')!; + expect( + (group.commands.find((entry) => entry.name() === command) as Command & { _hidden: boolean })._hidden + ).toBe(true); + expect(group.helpInformation()).not.toMatch(new RegExp(`\\b${command}\\b`)); + if (command === 'config') { + expect(logs).toHaveLength(2); + for (const log of logs) + expect(JSON.parse(log)).toEqual({ + command: 'fleet config', + status: 'deprecated', + effect: 'none', + message: 'Fleet nodes need no per-workspace enablement; this command is a no-op.', + }); + } else expect(logs).toEqual([]); + if (command === 'disable') expect(errors.join('\n')).toContain('Fleet nodes have not been disabled.'); } - expect(JSON.parse(logs[0]!)).toMatchObject({ - enabled: method === 'set' ? value : false, - defaultEnabled: false, - }); - }); + ); it('fleet nodes accepts --wk as an alias for --workspace-key', async () => { const nodes = { list: vi.fn(async () => []) }; diff --git a/packages/cli/src/cli/commands/fleet.ts b/packages/cli/src/cli/commands/fleet.ts index c447f50e70..8c4707f336 100644 --- a/packages/cli/src/cli/commands/fleet.ts +++ b/packages/cli/src/cli/commands/fleet.ts @@ -59,6 +59,9 @@ import { type SdkCommandDeps, } from '../lib/sdk-command.js'; +const FLEET_NODES_ALWAYS_ON_MESSAGE = + 'Fleet nodes need no per-workspace enablement; this command is a no-op.'; + const SERVE_REPLACEMENT_MESSAGE = "'fleet serve' has been replaced. Run 'relay node up' (with an optional --config ); " + "for Cloud-managed nodes run 'relay cloud enroll --token ' first."; @@ -1168,41 +1171,24 @@ export function registerFleetCommands( }); }); - addSdkOptions(group.command('config').description('Show workspace fleet node configuration')).action( - async (options: Record) => { - await runSdk(deps.sdk, async () => { - const relay = deps.sdk.createWorkspaceRelay(sdkOptionsFromOpts(options)); - printJson(deps.sdk, await relay.workspace.fleetNodes.get()); - }); - } - ); - - addSdkOptions(group.command('enable').description('Enable fleet nodes for the workspace')).action( - async (options: Record) => { - await runSdk(deps.sdk, async () => { - const relay = deps.sdk.createWorkspaceRelay(sdkOptionsFromOpts(options)); - printJson(deps.sdk, await relay.workspace.fleetNodes.set(true)); - }); - } - ); - - addSdkOptions(group.command('disable').description('Disable fleet nodes for the workspace')).action( - async (options: Record) => { - await runSdk(deps.sdk, async () => { - const relay = deps.sdk.createWorkspaceRelay(sdkOptionsFromOpts(options)); - printJson(deps.sdk, await relay.workspace.fleetNodes.set(false)); - }); - } - ); - - addSdkOptions( - group.command('inherit').description('Use the deployment default for workspace fleet nodes') - ).action(async (options: Record) => { - await runSdk(deps.sdk, async () => { - const relay = deps.sdk.createWorkspaceRelay(sdkOptionsFromOpts(options)); - printJson(deps.sdk, await relay.workspace.fleetNodes.inherit()); + for (const command of ['config', 'enable', 'disable', 'inherit']) { + addSdkOptions( + group + .command(command, { hidden: true }) + .description('Deprecated: fleet nodes need no per-workspace enablement') + ).action(() => { + deps.error(FLEET_NODES_ALWAYS_ON_MESSAGE); + if (command === 'disable') deps.error('Fleet nodes have not been disabled.'); + if (command === 'config') { + printJson(deps.sdk, { + command: 'fleet config', + status: 'deprecated', + effect: 'none', + message: FLEET_NODES_ALWAYS_ON_MESSAGE, + }); + } }); - }); + } addSdkOptions( group.command('status').description('Show local broker status and this node’s provider attachment') diff --git a/packages/sdk/src/__tests__/facade.test.ts b/packages/sdk/src/__tests__/facade.test.ts index d76033fd54..f39c67eda3 100644 --- a/packages/sdk/src/__tests__/facade.test.ts +++ b/packages/sdk/src/__tests__/facade.test.ts @@ -34,11 +34,6 @@ function createMessagingMock() { }; const workspace = { info: vi.fn(async () => ({ id: 'ws_1', name: 'Ops' })), - fleetNodes: { - get: vi.fn(async () => ({ enabled: false, defaultEnabled: false, override: null })), - set: vi.fn(async (enabled: boolean) => ({ enabled, defaultEnabled: false, override: enabled })), - inherit: vi.fn(async () => ({ enabled: false, defaultEnabled: false, override: null })), - }, }; const messaging = { messages, agents, workspace, events: {} } as unknown as RelayMessaging; return { messaging, messages, agents, workspace }; @@ -80,31 +75,6 @@ describe('AgentRelay facade (Phase A)', () => { expect(client.token).toBe('tok-self'); }); - it('workspace.fleetNodes delegates fleet node config calls', async () => { - const { messaging, workspace } = createMessagingMock(); - const relay = new AgentRelay({ messaging }); - - await expect(relay.workspace.fleetNodes.get()).resolves.toEqual({ - enabled: false, - defaultEnabled: false, - override: null, - }); - await expect(relay.workspace.fleetNodes.set(true)).resolves.toEqual({ - enabled: true, - defaultEnabled: false, - override: true, - }); - await expect(relay.workspace.fleetNodes.inherit()).resolves.toEqual({ - enabled: false, - defaultEnabled: false, - override: null, - }); - - expect(workspace.fleetNodes.get).toHaveBeenCalledTimes(1); - expect(workspace.fleetNodes.set).toHaveBeenCalledWith(true); - expect(workspace.fleetNodes.inherit).toHaveBeenCalledTimes(1); - }); - it('sendMessage routes #channel to messages.send and a bare name to direct', async () => { const { messaging, messages } = createMessagingMock(); const relay = new AgentRelay({ messaging, createAgentMessaging: () => messaging }); diff --git a/packages/sdk/src/__tests__/messaging.test.ts b/packages/sdk/src/__tests__/messaging.test.ts index 1e8f267e18..8175410ac8 100644 --- a/packages/sdk/src/__tests__/messaging.test.ts +++ b/packages/sdk/src/__tests__/messaging.test.ts @@ -158,11 +158,6 @@ function createWorkspace() { }, workspace: { info: vi.fn(async () => ({ id: 'ws_1', name: 'Ops' })), - fleetNodes: { - get: vi.fn(async () => ({ enabled: true, default_enabled: false, override: true })), - set: vi.fn(async (enabled: boolean) => ({ enabled, default_enabled: false, override: enabled })), - inherit: vi.fn(async () => ({ enabled: false, default_enabled: false, override: null })), - }, }, }; } @@ -462,31 +457,6 @@ describe('RelaycastMessagingClient', () => { }); }); - it('delegates workspace fleet node config calls to Relaycast', async () => { - const workspace = createWorkspace(); - const client = new RelaycastMessagingClient({ relaycast: workspace }); - - await expect(client.workspace.fleetNodes.get()).resolves.toEqual({ - enabled: true, - defaultEnabled: false, - override: true, - }); - await expect(client.workspace.fleetNodes.set(false)).resolves.toEqual({ - enabled: false, - defaultEnabled: false, - override: false, - }); - await expect(client.workspace.fleetNodes.inherit()).resolves.toEqual({ - enabled: false, - defaultEnabled: false, - override: null, - }); - - expect(workspace.workspace.fleetNodes.get).toHaveBeenCalledTimes(1); - expect(workspace.workspace.fleetNodes.set).toHaveBeenCalledWith(false); - expect(workspace.workspace.fleetNodes.inherit).toHaveBeenCalledTimes(1); - }); - it('normalizes fleet node roster fields and passes node query options through', async () => { const workspace = createWorkspace(); const client = new RelaycastMessagingClient({ relaycast: workspace }); diff --git a/packages/sdk/src/__tests__/observer-source.test.ts b/packages/sdk/src/__tests__/observer-source.test.ts index 6570d0a587..1543840a70 100644 --- a/packages/sdk/src/__tests__/observer-source.test.ts +++ b/packages/sdk/src/__tests__/observer-source.test.ts @@ -490,7 +490,7 @@ describe('AgentRelay observer mode', () => { // A partial messaging fake: observer mode never uses the workspace // client's event stream and register/reconnect throw at the facade. const messaging = { - workspace: { info: vi.fn(async () => ({})), fleetNodes: {} }, + workspace: { info: vi.fn(async () => ({})) }, agents: {}, events: undefined, } as unknown as RelayMessaging; diff --git a/packages/sdk/src/facade.ts b/packages/sdk/src/facade.ts index 8e45433ad7..a711c5b379 100644 --- a/packages/sdk/src/facade.ts +++ b/packages/sdk/src/facade.ts @@ -13,7 +13,6 @@ import type { RelayAgentReleaseResult, RelaySendChannelMessageInput, RelayWorkspaceInfo, - RelayWorkspaceFleetNodesConfig, } from './messaging/index.js'; import { actionSchemaToJsonSchema, @@ -204,11 +203,6 @@ export interface RelayWorkspace { release(input: RelayReleaseAgentInput): Promise; reconnect(input: { apiToken: string }): Promise; info(): Promise; - fleetNodes: { - get(): Promise; - set(enabled: boolean): Promise; - inherit(): Promise; - }; } export interface NotifyOptions { @@ -371,7 +365,6 @@ export function createWorkspaceFacade(messaging: RelayMessaging, deps?: Workspac return { info: () => messaging.workspace.info(), - fleetNodes: messaging.workspace.fleetNodes, register: register as RelayWorkspace['register'], release: async (input) => { // async so an unavailable-on-this-client error is always a rejected diff --git a/packages/sdk/src/messaging/relaycast-client.ts b/packages/sdk/src/messaging/relaycast-client.ts index a7ce82019e..a6d440074e 100644 --- a/packages/sdk/src/messaging/relaycast-client.ts +++ b/packages/sdk/src/messaging/relaycast-client.ts @@ -84,11 +84,6 @@ export type RelaycastWorkspaceLike = { }; workspace?: { info(): Promise; - fleetNodes?: { - get(): Promise; - set(enabled: boolean): Promise; - inherit(): Promise; - }; }; as?: (agentToken: string, options?: AgentClientOptions) => RelaycastAgentLike; // Workspace-scoped realtime stream (relaycast 2.5+): lets a workspace-key diff --git a/packages/sdk/src/messaging/relaycast-translate.ts b/packages/sdk/src/messaging/relaycast-translate.ts index 1d01478244..ecab4a16d2 100644 --- a/packages/sdk/src/messaging/relaycast-translate.ts +++ b/packages/sdk/src/messaging/relaycast-translate.ts @@ -22,7 +22,6 @@ import type { RelayTrigger, RelayTriggerInput, RelayWebhookSubscription, - RelayWorkspaceFleetNodesConfig, } from './types.js'; // --- primitive record readers ------------------------------------------------- @@ -201,15 +200,6 @@ export function toRelayTrigger(raw: unknown): RelayTrigger { }; } -export function toRelayWorkspaceFleetNodesConfig(raw: unknown): RelayWorkspaceFleetNodesConfig { - const record = asRecord(raw); - return { - enabled: readBoolean(record, 'enabled') ?? false, - defaultEnabled: readBoolean(record, 'defaultEnabled', 'default_enabled') ?? false, - override: readBoolean(record, 'override') ?? null, - }; -} - /** Normalize a relaycast invoke ack (camelized) into the relay `RelayActionInvocationAck`. */ export function normalizeActionInvocationAck(raw: unknown): RelayActionInvocationAck { const record = asRecord(raw); diff --git a/packages/sdk/src/messaging/relaycast.ts b/packages/sdk/src/messaging/relaycast.ts index add93e55ba..9c062002ae 100644 --- a/packages/sdk/src/messaging/relaycast.ts +++ b/packages/sdk/src/messaging/relaycast.ts @@ -29,7 +29,6 @@ import { toRelayCapability, toRelayNode, toRelayTrigger, - toRelayWorkspaceFleetNodesConfig, toTriggerRequest, } from './relaycast-translate.js'; import { @@ -79,7 +78,6 @@ import type { RelayRegisterCapabilityInput, RelayWebhook, RelayWorkspaceInfo, - RelayWorkspaceFleetNodesConfig, InboxAckInput, InboxDeferInput, InboxFailInput, @@ -1068,17 +1066,6 @@ export class RelaycastMessagingClient implements RelayMessagingClient { } return (await this.relaycast.workspace.info()) as RelayWorkspaceInfo; }, - fleetNodes: { - get: async (): Promise => { - return toRelayWorkspaceFleetNodesConfig(await this.requireWorkspaceFleetNodes().get()); - }, - set: async (enabled: boolean): Promise => { - return toRelayWorkspaceFleetNodesConfig(await this.requireWorkspaceFleetNodes().set(enabled)); - }, - inherit: async (): Promise => { - return toRelayWorkspaceFleetNodesConfig(await this.requireWorkspaceFleetNodes().inherit()); - }, - }, }; private resolvePlacementNode(node: string | 'self' | undefined, selfNodeName?: string): string | undefined { @@ -1292,17 +1279,6 @@ export class RelaycastMessagingClient implements RelayMessagingClient { return this.relaycast.triggers; } - private requireWorkspaceFleetNodes(): NonNullable< - NonNullable['fleetNodes'] - > { - if (!this.relaycast.workspace?.fleetNodes) { - throw new Error( - 'RelaycastMessagingClient.workspace.fleetNodes requires @relaycast/sdk with the workspace fleet nodes API.' - ); - } - return this.relaycast.workspace.fleetNodes; - } - private requireAgentActions(operation: string): NonNullable { const actions = this.agentClient?.actions; if (!actions) { diff --git a/packages/sdk/src/messaging/types.ts b/packages/sdk/src/messaging/types.ts index 5cb245bb21..2ba2ebbf36 100644 --- a/packages/sdk/src/messaging/types.ts +++ b/packages/sdk/src/messaging/types.ts @@ -694,12 +694,6 @@ export interface RelayWorkspaceInfo { [key: string]: unknown; } -export interface RelayWorkspaceFleetNodesConfig { - enabled: boolean; - defaultEnabled: boolean; - override: boolean | null; -} - /** * Relay inbox states built on the canonical delivery-status lifecycle: * `queued`/`delivered`/`failed` surface directly, the terminal `acked` and @@ -1052,11 +1046,6 @@ export interface RelayMessagingClient { }; readonly workspace: { info(): Promise; - fleetNodes: { - get(): Promise; - set(enabled: boolean): Promise; - inherit(): Promise; - }; }; } diff --git a/scripts/verify-features/fleet-daytona.mjs b/scripts/verify-features/fleet-daytona.mjs index e0c5c6103d..0ab1cca0fe 100644 --- a/scripts/verify-features/fleet-daytona.mjs +++ b/scripts/verify-features/fleet-daytona.mjs @@ -3372,7 +3372,7 @@ export function validateFleetEvidence(evidence, matrix) { } } const environment = assertObject(evidence.environment, 'evidence.environment'); - for (const key of ['policyMutationRequested', 'policyMutationAuthorized', 'policyMutationPerformed']) { + for (const key of ['policyMutationRequested']) { if (typeof environment[key] !== 'boolean') throw new Error(`evidence environment.${key} is invalid`); } if (environment.controlPlaneClean !== true) { @@ -3737,22 +3737,6 @@ export function validateFleetEvidence(evidence, matrix) { `cleanup cannot pass after release failure for ${failedReleaseAttempt.target ?? 'unknown target'}` ); } - const mutationOperations = evidence.operations.filter(({ id }) => - ['fleet-enable', 'fleet-disable', 'fleet-inherit'].includes(id) - ); - if (mutationOperations.some(({ status }) => status !== 'safety-skipped')) { - if ( - environment.policyMutationAuthorized !== true || - environment.policyMutationPerformed !== true || - !environment.expectedWorkspaceId || - provenance.resolvedWorkspaceId !== environment.expectedWorkspaceId - ) { - throw new Error('workspace policy mutation was not bound to the explicitly expected workspace'); - } - if (environment.policyRestoration?.status !== 'pass') { - throw new Error('workspace policy mutation was not restored to its exact initial override'); - } - } validateCriticalLifecycleEvidence(evidence.criticalLifecycle, matrix, boardNodes, evidence.nonce); const derived = deriveFleetVerdict(evidence.operations, evidence.cleanup, evidence.criticalLifecycle); if (evidence.verdict !== derived) throw new Error(`evidence verdict must be ${derived}`); @@ -3969,8 +3953,6 @@ class FleetBoard { policyMutationRequested: process.env.VERIFY_FLEET_DISPOSABLE_WORKSPACE === '1', expectedWorkspaceId: process.env.VERIFY_FLEET_EXPECTED_WORKSPACE_ID?.trim() || null, expectedRelayWorkspaceId: process.env.VERIFY_FLEET_EXPECTED_RELAY_WORKSPACE_ID?.trim() || null, - policyMutationAuthorized: false, - policyMutationPerformed: false, controlPlaneClean: false, releaseQualificationRequested: process.env.VERIFY_FLEET_RELEASE_QUALIFICATION === '1', expectedSnapshotId: process.env.VERIFY_FLEET_SNAPSHOT_ID?.trim() || null, @@ -6829,106 +6811,31 @@ class FleetBoard { } } - async fleetPolicyAndStatus() { - let rawConfig; - const configOperation = await this.record('fleet-config', async () => { - rawConfig = await execute(this.cliArgv('fleet', 'config'), { - timeoutMs: 45_000, - maxCaptureBytes: 1024 * 1024, - }); - const payload = tryParseJson(rawConfig._rawStdout); - const schemaValid = - payload && - Object.prototype.hasOwnProperty.call(payload, 'override') && - [true, false, null].includes(payload.override) && - typeof payload.effective === 'boolean'; - return { - ...stripPrivateExecution(rawConfig), - exitCode: rawConfig.exitCode === 0 && schemaValid ? 0 : 1, - summary: `schemaValid=${Boolean(schemaValid)} override=${String(payload?.override)} effective=${String(payload?.effective)}`, - }; - }); - const configPayload = rawConfig ? tryParseJson(rawConfig._rawStdout) : undefined; - const hasRestorableOverride = - configPayload && - Object.prototype.hasOwnProperty.call(configPayload, 'override') && - [true, false, null].includes(configPayload.override); - const initialOverride = hasRestorableOverride ? configPayload.override : undefined; - const expectedWorkspaceId = this.evidence.environment.expectedWorkspaceId; - const actualWorkspaceId = this.evidence.provenance?.resolvedWorkspaceId; - const requested = this.evidence.environment.policyMutationRequested; - const authorized = - requested && - typeof expectedWorkspaceId === 'string' && - expectedWorkspaceId.length > 0 && - actualWorkspaceId === expectedWorkspaceId; - this.evidence.environment.policyMutationAuthorized = authorized; - this.evidence.environment.policyInitialOverride = hasRestorableOverride ? initialOverride : 'unknown'; - await this.checkpoint(); - - if (!authorized || configOperation.status !== 'pass' || !hasRestorableOverride) { - const safetyReason = !requested - ? 'Set both VERIFY_FLEET_DISPOSABLE_WORKSPACE=1 and VERIFY_FLEET_EXPECTED_WORKSPACE_ID to authorize workspace policy mutation.' - : !expectedWorkspaceId - ? 'VERIFY_FLEET_EXPECTED_WORKSPACE_ID is required for workspace policy mutation.' - : actualWorkspaceId !== expectedWorkspaceId - ? `Active workspace ${actualWorkspaceId ?? 'unknown'} does not match the explicitly expected workspace.` - : 'fleet config did not return a restorable override, so mutation was not attempted.'; - for (const id of ['fleet-enable', 'fleet-disable', 'fleet-inherit']) { - await this.derived(id, { safetyReason }); - } - } else { - this.evidence.environment.policyMutationPerformed = true; - const runPolicy = async (id, action, expectedOverride) => - this.record(id, async () => { - const mutation = await execute(this.cliArgv('fleet', action), { timeoutMs: 45_000 }); - const readback = await execute(this.cliArgv('fleet', 'config'), { - timeoutMs: 45_000, - maxCaptureBytes: 1024 * 1024, - }); - const payload = tryParseJson(readback._rawStdout); - const readbackMatches = - readback.exitCode === 0 && - payload && - Object.prototype.hasOwnProperty.call(payload, 'override') && - payload.override === expectedOverride; - return { - ...stripPrivateExecution(mutation), - exitCode: mutation.exitCode === 0 && readbackMatches ? 0 : 1, - summary: `action=${action} expectedOverride=${String(expectedOverride)} observedOverride=${String(payload?.override)} readbackExit=${readback.exitCode}`, - }; - }); - try { - await runPolicy('fleet-enable', 'enable', true); - await runPolicy('fleet-disable', 'disable', false); - await runPolicy('fleet-inherit', 'inherit', null); - } finally { - const restoreArg = - initialOverride === true ? 'enable' : initialOverride === false ? 'disable' : 'inherit'; - const restore = await execute(this.cliArgv('fleet', restoreArg), { timeoutMs: 45_000 }); - const verify = await execute(this.cliArgv('fleet', 'config'), { - timeoutMs: 45_000, - maxCaptureBytes: 1024 * 1024, - }); - const restoredPayload = tryParseJson(verify._rawStdout); - const restoredExactly = - verify.exitCode === 0 && - restoredPayload && - Object.prototype.hasOwnProperty.call(restoredPayload, 'override') && - restoredPayload.override === initialOverride; - this.evidence.environment.policyRestoration = { - targetOverride: initialOverride, - command: restoreArg, - exitCode: restore.exitCode, - timedOut: restore.timedOut === true, - verificationExitCode: verify.exitCode, - restoredExactly: restoredExactly === true, - status: - restore.exitCode === 0 && restore.timedOut !== true && restoredExactly === true ? 'pass' : 'fail', - stderr: redactFleetEvidence(`${restore.stderr ?? ''}\n${verify.stderr ?? ''}`), + async fleetDeprecatedNoopsAndStatus() { + for (const command of ['config', 'enable', 'disable', 'inherit']) { + await this.record(`fleet-${command}`, async () => { + // An unreachable endpoint prevents these compatibility probes from mutating + // the active workspace and proves they need no working service connection. + const result = await execute( + this.cliArgv('fleet', command, '--base-url', 'http://127.0.0.1:1', '--workspace-key', 'deprecated-noop'), + { timeoutMs: 45_000, maxCaptureBytes: 1024 * 1024 } + ); + const payload = tryParseJson(result._rawStdout); + const notice = result.stderr.includes( + 'Fleet nodes need no per-workspace enablement; this command is a no-op.' + ); + const outputValid = + command === 'config' + ? payload?.command === 'fleet config' && + payload.status === 'deprecated' && + payload.effect === 'none' + : result._rawStdout.trim() === ''; + return { + ...stripPrivateExecution(result), + exitCode: result.exitCode === 0 && notice && outputValid ? 0 : 1, + summary: `deprecated no-op: notice=${notice} outputValid=${outputValid}; unreachable endpoint, no workspace mutation`, }; - await this.checkpoint(); - } + }); } const statusNode = this.availableBoardNodes()[0]; if (!statusNode?.id) { @@ -7515,7 +7422,7 @@ class FleetBoard { await this.targetedFleetSpawns(); await this.fleetProviderMatrix(); await this.mountedSandboxCases(); - await this.fleetPolicyAndStatus(); + await this.fleetDeprecatedNoopsAndStatus(); await this.nodeSpawnMatrix(); await this.criticalLifecycleRepeatability(); await this.nodeWorkflows(); diff --git a/summary.md b/summary.md new file mode 100644 index 0000000000..2016ebc118 --- /dev/null +++ b/summary.md @@ -0,0 +1,19 @@ +# Retire the removed workspace fleet rollout API + +`fleet config`, `fleet enable`, `fleet disable`, and `fleet inherit` called an API removed by Relaycast, so every invocation failed. They now remain as hidden compatibility commands that exit 0, accept legacy SDK options, and print a deprecation notice without creating a client. `config` emits a JSON deprecation object; `disable` explicitly says nodes have not been disabled. + +Removed `workspace.fleetNodes`, its messaging implementation, and `RelayWorkspaceFleetNodesConfig` from the SDK. SDK consumers must remove references to that API/type. Fleet nodes need no per-workspace enablement. + +Updated help tests, current docs and skills, the feature manifest, verification procedures, and the Unreleased changelog. The cleanroom harness retains all 110 operations and verifies the four no-ops against an unreachable endpoint. Regenerated the CLI inventory and its digest; reconciled existing option drift by recording skipped coverage for three newer options and dropping coverage entries for nine removed options. + +Validation: + +- `npm run build` and `npm run typecheck` passed. +- `npm run lint` passed with 107 warnings and no errors. +- CLI fleet/bootstrap and cleanroom/guardian suites: 164 tests passed. +- SDK facade/messaging/observer suites, using the SDK's own Vitest config: 53 tests passed. +- All four built CLI commands exited 0 with an empty temporary HOME and no credentials. +- Generated inventory matches the built CLI and matrix digest; 110 operations retained. +- Acceptance searches found no `fleetNodes` references in SDK/CLI source, obsolete policy-restoration fields, or fleet-off-by-default guidance. + +The live Daytona campaign was not run; validation covers local regression tests and the built CLI. diff --git a/tests/fixtures/verify-fleet-daytona.test.ts b/tests/fixtures/verify-fleet-daytona.test.ts index fc1d2e3790..76b725c791 100644 --- a/tests/fixtures/verify-fleet-daytona.test.ts +++ b/tests/fixtures/verify-fleet-daytona.test.ts @@ -543,11 +543,8 @@ function completeEvidence(matrix: { }, environment: { policyMutationRequested: true, - policyMutationAuthorized: true, - policyMutationPerformed: true, expectedWorkspaceId: 'workspace_fixture', controlPlaneClean: true, - policyRestoration: { status: 'pass' }, }, baseline: { agentCount: 0, @@ -2067,6 +2064,12 @@ describe('complete Daytona Fleet board', () => { expect(actual.commands).toHaveLength(36); expect(actual.commands.filter(({ leaf }: { leaf: boolean }) => leaf)).toHaveLength(30); expect(inventorySha256(actual)).toMatch(/^[a-f0-9]{64}$/); + for (const verb of ['config', 'enable', 'disable', 'inherit']) { + expect(actual.commands.find(({ path }: { path: string }) => path === `fleet ${verb}`)).toMatchObject({ + hidden: true, + leaf: true, + }); + } expect(actual.commands.find(({ path }: { path: string }) => path === 'fleet serve')).toMatchObject({ hidden: true, leaf: true, @@ -2096,6 +2099,24 @@ describe('complete Daytona Fleet board', () => { expect(() => compareFleetCliInventory(actual, changedOption)).toThrow('inventory changed'); }, 30_000); + it('runs deprecated fleet probes successfully without a workspace service', async () => { + const records = new Map(); + await FleetBoard.prototype.fleetDeprecatedNoopsAndStatus.call({ + cliArgv: (...args: string[]) => [ + process.execPath, path.resolve('packages/cli/dist/cli/index.js'), ...args, + ], + record: async (id: string, run: () => Promise<{ exitCode: number; summary: string }>) => { + records.set(id, await run()); + }, + availableBoardNodes: () => [], + derived: async () => undefined, + }); + for (const verb of ['config', 'enable', 'disable', 'inherit']) { + expect(records.get(`fleet-${verb}`)).toMatchObject({ exitCode: 0 }); + expect(records.get(`fleet-${verb}`)?.summary).toContain('notice=true outputValid=true'); + } + }, 30_000); + it('rejects duplicate operations and an incomplete provider board', async () => { const matrix = await loadFleetMatrix('tests/relayflows/cleanroom/fleet-daytona.matrix.json'); const duplicate = structuredClone(matrix); diff --git a/tests/relayflows/cleanroom/README.md b/tests/relayflows/cleanroom/README.md index 799021a059..df7779b2ca 100644 --- a/tests/relayflows/cleanroom/README.md +++ b/tests/relayflows/cleanroom/README.md @@ -157,11 +157,11 @@ node scripts/verify-features/fleet-daytona.mjs cleanup \ --nonce ``` -`fleet enable`, `fleet disable`, and `fleet inherit` affect a whole workspace. -They are evidence-visible safety skips unless the active workspace is disposable, -`VERIFY_FLEET_DISPOSABLE_WORKSPACE=1` is set, and -`VERIFY_FLEET_EXPECTED_WORKSPACE_ID` exactly matches the resolved Cloud workspace. -The runner captures the initial override and restores it in `finally`. `node down +`fleet config`, `fleet enable`, `fleet disable`, and `fleet inherit` are hidden +compatibility no-ops. The runner checks successful exits and deprecation notices +against an unreachable endpoint, with a JSON deprecation object for `fleet config`. +They change no workspace state and need no restoration. Disposable-workspace and +workspace-identity checks still apply to the campaign baseline. `node down --all` runs only inside an exact owned Daytona sandbox, never on the operator host. Each attempt is sealed under diff --git a/tests/relayflows/cleanroom/fleet-cli-inventory.json b/tests/relayflows/cleanroom/fleet-cli-inventory.json index 9f965038a0..b07850c444 100644 --- a/tests/relayflows/cleanroom/fleet-cli-inventory.json +++ b/tests/relayflows/cleanroom/fleet-cli-inventory.json @@ -166,7 +166,7 @@ { "path": "fleet config", "aliases": [], - "hidden": false, + "hidden": true, "leaf": true, "arguments": [], "options": [ @@ -243,7 +243,7 @@ { "path": "fleet disable", "aliases": [], - "hidden": false, + "hidden": true, "leaf": true, "arguments": [], "options": [ @@ -320,7 +320,7 @@ { "path": "fleet enable", "aliases": [], - "hidden": false, + "hidden": true, "leaf": true, "arguments": [], "options": [ @@ -397,7 +397,7 @@ { "path": "fleet inherit", "aliases": [], - "hidden": false, + "hidden": true, "leaf": true, "arguments": [], "options": [ @@ -878,6 +878,23 @@ "defaultValue": null, "presetArg": null }, + { + "flags": "--unsubscribe-bindings", + "short": null, + "long": "--unsubscribe-bindings", + "mandatory": false, + "valueRequired": false, + "valueOptional": false, + "variadic": false, + "negate": false, + "hidden": false, + "choices": null, + "conflictsWith": [], + "implied": null, + "envVar": null, + "defaultValue": null, + "presetArg": null + }, { "flags": "--wk ", "short": null, @@ -945,6 +962,23 @@ } ], "options": [ + { + "flags": "--auto-place", + "short": null, + "long": "--auto-place", + "mandatory": false, + "valueRequired": false, + "valueOptional": false, + "variadic": false, + "negate": false, + "hidden": false, + "choices": null, + "conflictsWith": [], + "implied": null, + "envVar": null, + "defaultValue": null, + "presetArg": null + }, { "flags": "--base-url ", "short": null, @@ -979,6 +1013,23 @@ "defaultValue": null, "presetArg": null }, + { + "flags": "--checkout", + "short": null, + "long": "--checkout", + "mandatory": false, + "valueRequired": false, + "valueOptional": false, + "variadic": false, + "negate": false, + "hidden": false, + "choices": null, + "conflictsWith": [], + "implied": null, + "envVar": null, + "defaultValue": null, + "presetArg": null + }, { "flags": "--confirm-timeout ", "short": null, @@ -2106,7 +2157,9 @@ "conflictsWith": [], "implied": null, "envVar": null, - "defaultValue": ["general"], + "defaultValue": [ + "general" + ], "presetArg": null }, { @@ -2126,23 +2179,6 @@ "defaultValue": null, "presetArg": null }, - { - "flags": "--endpoint ", - "short": null, - "long": "--endpoint", - "mandatory": false, - "valueRequired": true, - "valueOptional": false, - "variadic": false, - "negate": false, - "hidden": false, - "choices": null, - "conflictsWith": [], - "implied": null, - "envVar": null, - "defaultValue": null, - "presetArg": null - }, { "flags": "--exit-after-task", "short": null, @@ -2211,40 +2247,6 @@ "defaultValue": null, "presetArg": null }, - { - "flags": "--protocol ", - "short": null, - "long": "--protocol", - "mandatory": false, - "valueRequired": true, - "valueOptional": false, - "variadic": false, - "negate": false, - "hidden": false, - "choices": null, - "conflictsWith": [], - "implied": null, - "envVar": null, - "defaultValue": null, - "presetArg": null - }, - { - "flags": "--release ", - "short": null, - "long": "--release", - "mandatory": false, - "valueRequired": true, - "valueOptional": false, - "variadic": false, - "negate": false, - "hidden": false, - "choices": null, - "conflictsWith": [], - "implied": null, - "envVar": null, - "defaultValue": null, - "presetArg": null - }, { "flags": "--runtime ", "short": null, @@ -2262,23 +2264,6 @@ "defaultValue": "auto", "presetArg": null }, - { - "flags": "--session-id ", - "short": null, - "long": "--session-id", - "mandatory": false, - "valueRequired": true, - "valueOptional": false, - "variadic": false, - "negate": false, - "hidden": false, - "choices": null, - "conflictsWith": [], - "implied": null, - "envVar": null, - "defaultValue": null, - "presetArg": null - }, { "flags": "--spawn-mode ", "short": null, @@ -2352,25 +2337,7 @@ "defaultValue": null } ], - "options": [ - { - "flags": "--json", - "short": null, - "long": "--json", - "mandatory": false, - "valueRequired": false, - "valueOptional": false, - "variadic": false, - "negate": false, - "hidden": false, - "choices": null, - "conflictsWith": [], - "implied": null, - "envVar": null, - "defaultValue": null, - "presetArg": null - } - ] + "options": [] }, { "path": "node agent spawn", @@ -2401,7 +2368,9 @@ "conflictsWith": [], "implied": null, "envVar": null, - "defaultValue": ["general"], + "defaultValue": [ + "general" + ], "presetArg": null }, { @@ -2421,23 +2390,6 @@ "defaultValue": null, "presetArg": null }, - { - "flags": "--endpoint ", - "short": null, - "long": "--endpoint", - "mandatory": false, - "valueRequired": true, - "valueOptional": false, - "variadic": false, - "negate": false, - "hidden": false, - "choices": null, - "conflictsWith": [], - "implied": null, - "envVar": null, - "defaultValue": null, - "presetArg": null - }, { "flags": "--exit-after-task", "short": null, @@ -2489,40 +2441,6 @@ "defaultValue": null, "presetArg": null }, - { - "flags": "--protocol ", - "short": null, - "long": "--protocol", - "mandatory": false, - "valueRequired": true, - "valueOptional": false, - "variadic": false, - "negate": false, - "hidden": false, - "choices": null, - "conflictsWith": [], - "implied": null, - "envVar": null, - "defaultValue": null, - "presetArg": null - }, - { - "flags": "--release ", - "short": null, - "long": "--release", - "mandatory": false, - "valueRequired": true, - "valueOptional": false, - "variadic": false, - "negate": false, - "hidden": false, - "choices": null, - "conflictsWith": [], - "implied": null, - "envVar": null, - "defaultValue": null, - "presetArg": null - }, { "flags": "--runtime ", "short": null, @@ -2540,23 +2458,6 @@ "defaultValue": "auto", "presetArg": null }, - { - "flags": "--session-id ", - "short": null, - "long": "--session-id", - "mandatory": false, - "valueRequired": true, - "valueOptional": false, - "variadic": false, - "negate": false, - "hidden": false, - "choices": null, - "conflictsWith": [], - "implied": null, - "envVar": null, - "defaultValue": null, - "presetArg": null - }, { "flags": "--spawn-mode ", "short": null, @@ -2900,6 +2801,40 @@ "defaultValue": null, "presetArg": null }, + { + "flags": "--force", + "short": null, + "long": "--force", + "mandatory": false, + "valueRequired": false, + "valueOptional": false, + "variadic": false, + "negate": false, + "hidden": false, + "choices": null, + "conflictsWith": [], + "implied": null, + "envVar": null, + "defaultValue": null, + "presetArg": null + }, + { + "flags": "--local-only", + "short": null, + "long": "--local-only", + "mandatory": false, + "valueRequired": false, + "valueOptional": false, + "variadic": false, + "negate": false, + "hidden": false, + "choices": null, + "conflictsWith": [], + "implied": null, + "envVar": null, + "defaultValue": null, + "presetArg": null + }, { "flags": "--log-file ", "short": null, diff --git a/tests/relayflows/cleanroom/fleet-daytona.matrix.json b/tests/relayflows/cleanroom/fleet-daytona.matrix.json index 3cb7647761..69b839d7be 100644 --- a/tests/relayflows/cleanroom/fleet-daytona.matrix.json +++ b/tests/relayflows/cleanroom/fleet-daytona.matrix.json @@ -8,7 +8,7 @@ "requiredSnapshotRelayVersion": "11.10.3", "artifactRoot": ".workflow-artifacts/verify-fleet-daytona", "inventoryFile": "fleet-cli-inventory.json", - "inventorySha256": "27dd9c9fd15677894538893e54d4157521bd4562fce2e6c0a5cc404886a8d534", + "inventorySha256": "8bdc4be9341d4efb5a5e04ae8fe601fd30a9d664681b82ab2c4c729f46b4ac16", "commandSurface": { "fleet agent list": [ "fleet-agent-list-json", @@ -169,88 +169,88 @@ { "option": "--base-url", "status": "skipped", - "reason": "The proof uses the credential-bound workspace endpoint; alternate endpoint selection is covered by CLI connection tests without changing the Daytona target." + "reason": "Legacy SDK options remain accepted and ignored; this compatibility command opens no connection." }, { "option": "--token", "status": "skipped", - "reason": "Token argv would duplicate reveal-once credentials; the board proves redaction and uses the bound workspace key." + "reason": "Legacy SDK options remain accepted and ignored; this compatibility command opens no connection." }, { "option": "--wk", "status": "skipped", - "reason": "The legacy --wk credential alias is covered by CLI connection tests; the board must not mint a second credential source." + "reason": "Legacy SDK options remain accepted and ignored; this compatibility command opens no connection." }, { "option": "--workspace-key", "status": "skipped", - "reason": "The bound workspace key is injected only where the operation needs it; repeating it on every Fleet read would multiply secret-bearing argv." + "reason": "Legacy SDK options remain accepted and ignored; this compatibility command opens no connection." } ], "fleet disable": [ { "option": "--base-url", "status": "skipped", - "reason": "The proof uses the credential-bound workspace endpoint; alternate endpoint selection is covered by CLI connection tests without changing the Daytona target." + "reason": "Legacy SDK options remain accepted and ignored; this compatibility command opens no connection." }, { "option": "--token", "status": "skipped", - "reason": "Token argv would duplicate reveal-once credentials; the board proves redaction and uses the bound workspace key." + "reason": "Legacy SDK options remain accepted and ignored; this compatibility command opens no connection." }, { "option": "--wk", "status": "skipped", - "reason": "The legacy --wk credential alias is covered by CLI connection tests; the board must not mint a second credential source." + "reason": "Legacy SDK options remain accepted and ignored; this compatibility command opens no connection." }, { "option": "--workspace-key", "status": "skipped", - "reason": "The bound workspace key is injected only where the operation needs it; repeating it on every Fleet read would multiply secret-bearing argv." + "reason": "Legacy SDK options remain accepted and ignored; this compatibility command opens no connection." } ], "fleet enable": [ { "option": "--base-url", "status": "skipped", - "reason": "The proof uses the credential-bound workspace endpoint; alternate endpoint selection is covered by CLI connection tests without changing the Daytona target." + "reason": "Legacy SDK options remain accepted and ignored; this compatibility command opens no connection." }, { "option": "--token", "status": "skipped", - "reason": "Token argv would duplicate reveal-once credentials; the board proves redaction and uses the bound workspace key." + "reason": "Legacy SDK options remain accepted and ignored; this compatibility command opens no connection." }, { "option": "--wk", "status": "skipped", - "reason": "The legacy --wk credential alias is covered by CLI connection tests; the board must not mint a second credential source." + "reason": "Legacy SDK options remain accepted and ignored; this compatibility command opens no connection." }, { "option": "--workspace-key", "status": "skipped", - "reason": "The bound workspace key is injected only where the operation needs it; repeating it on every Fleet read would multiply secret-bearing argv." + "reason": "Legacy SDK options remain accepted and ignored; this compatibility command opens no connection." } ], "fleet inherit": [ { "option": "--base-url", "status": "skipped", - "reason": "The proof uses the credential-bound workspace endpoint; alternate endpoint selection is covered by CLI connection tests without changing the Daytona target." + "reason": "Legacy SDK options remain accepted and ignored; this compatibility command opens no connection." }, { "option": "--token", "status": "skipped", - "reason": "Token argv would duplicate reveal-once credentials; the board proves redaction and uses the bound workspace key." + "reason": "Legacy SDK options remain accepted and ignored; this compatibility command opens no connection." }, { "option": "--wk", "status": "skipped", - "reason": "The legacy --wk credential alias is covered by CLI connection tests; the board must not mint a second credential source." + "reason": "Legacy SDK options remain accepted and ignored; this compatibility command opens no connection." }, { "option": "--workspace-key", "status": "skipped", - "reason": "The bound workspace key is injected only where the operation needs it; repeating it on every Fleet read would multiply secret-bearing argv." + "reason": "Legacy SDK options remain accepted and ignored; this compatibility command opens no connection." } ], "fleet nodes": [ @@ -352,6 +352,11 @@ } ], "fleet release": [ + { + "option": "--unsubscribe-bindings", + "status": "skipped", + "reason": "Provider-binding retirement is covered by CLI release tests; this board creates no provider subscriptions." + }, { "option": "--base-url", "status": "skipped", @@ -401,6 +406,16 @@ } ], "fleet spawn": [ + { + "option": "--auto-place", + "status": "skipped", + "reason": "Automatic placement is covered by CLI tests; this board pins exact owned nodes for cleanup." + }, + { + "option": "--checkout", + "status": "skipped", + "reason": "Checkout placement is covered by CLI tests; this board uses its provisioned fixture checkout." + }, { "option": "--base-url", "status": "skipped", @@ -750,11 +765,6 @@ "status": "supported", "operationId": "node-agent-new-view" }, - { - "option": "--endpoint", - "status": "skipped", - "reason": "Headless AppServer endpoint options belong to node agent spawn, not the PTY/native agent new path." - }, { "option": "--exit-after-task", "status": "skipped", @@ -775,16 +785,6 @@ "status": "supported", "operationId": "node-agent-new-view" }, - { - "option": "--protocol", - "status": "skipped", - "reason": "Headless AppServer protocol options belong to node agent spawn, not the PTY/native agent new path." - }, - { - "option": "--release", - "status": "skipped", - "reason": "agent new cannot attach to headless AppServer workers; release policy is exercised by node agent spawn headless." - }, { "option": "--runtime", "status": "supported", @@ -807,11 +807,6 @@ } ] }, - { - "option": "--session-id", - "status": "skipped", - "reason": "Headless AppServer session options belong to node agent spawn, not the PTY/native agent new path." - }, { "option": "--spawn-mode", "status": "skipped", @@ -825,12 +820,6 @@ ], "node agent release": [], "node agent set-model": [ - { - "option": "--json", - "status": "supported", - "operationId": "node-agent-set-model", - "takesValue": false - } ], "node agent spawn": [ { @@ -843,11 +832,6 @@ "status": "supported", "operationId": "node-agent-spawn-codex-pty" }, - { - "option": "--endpoint", - "status": "supported", - "operationId": "node-agent-set-model-app-server-a" - }, { "option": "--exit-after-task", "status": "supported", @@ -864,26 +848,11 @@ "status": "supported", "operationId": "node-agent-spawn-codex-auto-a" }, - { - "option": "--protocol", - "status": "supported", - "operationId": "node-agent-set-model-app-server-a" - }, - { - "option": "--release", - "status": "supported", - "operationId": "node-agent-set-model-app-server-a" - }, { "option": "--runtime", "status": "supported", "operationId": "node-agent-spawn-codex-pty" }, - { - "option": "--session-id", - "status": "supported", - "operationId": "node-agent-set-model-app-server-a" - }, { "option": "--spawn-mode", "status": "supported", @@ -1080,29 +1049,18 @@ "teardownAssertion": "Final cleanup re-verifies the exact sandbox remains absent.", "retryAssertion": "Reconciliation is exact-name/id based and repeated deletion is idempotent." }, - "fleet-policy-read": { + "fleet-deprecated-noop": { "candidateSurface": "operator-candidate", "executionScope": "ephemeral-workspace", "effectAssertions": [ - "Fleet policy returns an explicit nullable override and boolean effective value." + "Exits 0 and prints a deprecation notice without workspace mutation; config emits a JSON deprecation object." ], "negativeAssertions": [ - "Missing SDK support, malformed schema, or an unknown initial override fails." + "An unreachable endpoint must not cause a failure; the removed workspace API must not be called." ], - "lifecycleAssertion": "The policy read is bounded and timed.", - "teardownAssertion": "Read-only; the captured value becomes the exact restoration target for mutation probes.", - "retryAssertion": "A retry must converge on the same authoritative override." - }, - "fleet-policy-mutation": { - "candidateSurface": "operator-candidate", - "executionScope": "ephemeral-workspace", - "effectAssertions": ["The authorized disposable workspace reads back the exact requested override."], - "negativeAssertions": [ - "Wrong workspace identity, missing authorization, failed readback, or failed exact restoration cannot pass." - ], - "lifecycleAssertion": "Each mutation/readback and final restoration is bounded and timed.", - "teardownAssertion": "The original nullable override is restored in finally and independently verified.", - "retryAssertion": "Read-after-write makes repeated mutation idempotent and observable." + "lifecycleAssertion": "Each compatibility command is bounded and timed.", + "teardownAssertion": "No workspace state is changed and no restoration is required.", + "retryAssertion": "Repeated invocations are idempotent no-ops." }, "node-read": { "candidateSurface": "daytona-candidate", @@ -1281,10 +1239,10 @@ "fleet-release": "release-process", "fleet-release-delete-agent": "release-process", "fleet-release-reclaims-owned-sandbox": "release-sandbox", - "fleet-config": "fleet-policy-read", - "fleet-enable": "fleet-policy-mutation", - "fleet-disable": "fleet-policy-mutation", - "fleet-inherit": "fleet-policy-mutation", + "fleet-config": "fleet-deprecated-noop", + "fleet-enable": "fleet-deprecated-noop", + "fleet-disable": "fleet-deprecated-noop", + "fleet-inherit": "fleet-deprecated-noop", "fleet-status": "node-read", "fleet-serve-migration": "expected-rejection", "fleet-serve-migration-default": "expected-rejection", @@ -1532,24 +1490,21 @@ "argvMustContain": ["--delete-agent"] }, { "id": "fleet-release-reclaims-owned-sandbox", "group": "fleet", "expect": "success" }, - { "id": "fleet-config", "group": "fleet-policy", "expect": "success" }, + { "id": "fleet-config", "group": "fleet", "expect": "success" }, { "id": "fleet-enable", - "group": "fleet-policy", - "expect": "success", - "destructiveScope": "workspace-policy" + "group": "fleet", + "expect": "success" }, { "id": "fleet-disable", - "group": "fleet-policy", - "expect": "success", - "destructiveScope": "workspace-policy" + "group": "fleet", + "expect": "success" }, { "id": "fleet-inherit", - "group": "fleet-policy", - "expect": "success", - "destructiveScope": "workspace-policy" + "group": "fleet", + "expect": "success" }, { "id": "fleet-status", "group": "fleet", "expect": "success" }, { diff --git a/tests/relayflows/cleanroom/relay.matrix.json b/tests/relayflows/cleanroom/relay.matrix.json index 379aa1cf29..2e3a46c60c 100644 --- a/tests/relayflows/cleanroom/relay.matrix.json +++ b/tests/relayflows/cleanroom/relay.matrix.json @@ -448,10 +448,6 @@ "fleet-nodes-pretty", "fleet-spawn", "fleet-release", - "fleet-config", - "fleet-enable", - "fleet-disable", - "fleet-inherit", "fleet-status" ] }, From c921da3e77e358c6e22d5571a4b0f8aaee9b5f16 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 22 Sep 2026 18:42:40 +0000 Subject: [PATCH 2/4] style: auto-format with Prettier --- scripts/verify-features/fleet-daytona.mjs | 9 ++++++++- tests/fixtures/verify-fleet-daytona.test.ts | 4 +++- tests/relayflows/cleanroom/fleet-cli-inventory.json | 8 ++------ tests/relayflows/cleanroom/fleet-daytona.matrix.json | 3 +-- 4 files changed, 14 insertions(+), 10 deletions(-) diff --git a/scripts/verify-features/fleet-daytona.mjs b/scripts/verify-features/fleet-daytona.mjs index 0ab1cca0fe..65b8c9a179 100644 --- a/scripts/verify-features/fleet-daytona.mjs +++ b/scripts/verify-features/fleet-daytona.mjs @@ -6817,7 +6817,14 @@ class FleetBoard { // An unreachable endpoint prevents these compatibility probes from mutating // the active workspace and proves they need no working service connection. const result = await execute( - this.cliArgv('fleet', command, '--base-url', 'http://127.0.0.1:1', '--workspace-key', 'deprecated-noop'), + this.cliArgv( + 'fleet', + command, + '--base-url', + 'http://127.0.0.1:1', + '--workspace-key', + 'deprecated-noop' + ), { timeoutMs: 45_000, maxCaptureBytes: 1024 * 1024 } ); const payload = tryParseJson(result._rawStdout); diff --git a/tests/fixtures/verify-fleet-daytona.test.ts b/tests/fixtures/verify-fleet-daytona.test.ts index 76b725c791..015572b5ed 100644 --- a/tests/fixtures/verify-fleet-daytona.test.ts +++ b/tests/fixtures/verify-fleet-daytona.test.ts @@ -2103,7 +2103,9 @@ describe('complete Daytona Fleet board', () => { const records = new Map(); await FleetBoard.prototype.fleetDeprecatedNoopsAndStatus.call({ cliArgv: (...args: string[]) => [ - process.execPath, path.resolve('packages/cli/dist/cli/index.js'), ...args, + process.execPath, + path.resolve('packages/cli/dist/cli/index.js'), + ...args, ], record: async (id: string, run: () => Promise<{ exitCode: number; summary: string }>) => { records.set(id, await run()); diff --git a/tests/relayflows/cleanroom/fleet-cli-inventory.json b/tests/relayflows/cleanroom/fleet-cli-inventory.json index b07850c444..498bdbd847 100644 --- a/tests/relayflows/cleanroom/fleet-cli-inventory.json +++ b/tests/relayflows/cleanroom/fleet-cli-inventory.json @@ -2157,9 +2157,7 @@ "conflictsWith": [], "implied": null, "envVar": null, - "defaultValue": [ - "general" - ], + "defaultValue": ["general"], "presetArg": null }, { @@ -2368,9 +2366,7 @@ "conflictsWith": [], "implied": null, "envVar": null, - "defaultValue": [ - "general" - ], + "defaultValue": ["general"], "presetArg": null }, { diff --git a/tests/relayflows/cleanroom/fleet-daytona.matrix.json b/tests/relayflows/cleanroom/fleet-daytona.matrix.json index 69b839d7be..15ab206075 100644 --- a/tests/relayflows/cleanroom/fleet-daytona.matrix.json +++ b/tests/relayflows/cleanroom/fleet-daytona.matrix.json @@ -819,8 +819,7 @@ } ], "node agent release": [], - "node agent set-model": [ - ], + "node agent set-model": [], "node agent spawn": [ { "option": "--channels", From 40b06e686af7531449c5f86f05ef26ffbff0504c Mon Sep 17 00:00:00 2001 From: Proactive Runtime Bot Date: Tue, 22 Sep 2026 13:42:53 -0700 Subject: [PATCH 3/4] docs(changelog): raise the pending release to Major Removing `workspace.fleetNodes` and `RelayWorkspaceFleetNodesConfig` from `@agent-relay/sdk` drops members from a published interface, so consumers on a compatible range stop compiling. The entry already carries Breaking Changes and Migration Guidance sections, which the repo rules reserve for a SemVer-major change, while the heading still said Patch. Co-Authored-By: Claude Opus 5 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 19f43d68cc..79e121ebd1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ All notable changes to Agent Relay will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [Unreleased - Patch] +## [Unreleased - Major] ### Fixed From e00add4265363488af9a987debb65782d13becba Mon Sep 17 00:00:00 2001 From: Proactive Runtime Bot Date: Tue, 22 Sep 2026 13:59:28 -0700 Subject: [PATCH 4/4] chore: drop the garden working file from the change summary.md is the flow's own pull-request body source, not part of the change. The flow that produced this branch committed it before working-file exclusion landed, which would have added a build artifact to the repo root. Co-Authored-By: Claude Opus 5 --- summary.md | 19 ------------------- 1 file changed, 19 deletions(-) delete mode 100644 summary.md diff --git a/summary.md b/summary.md deleted file mode 100644 index 2016ebc118..0000000000 --- a/summary.md +++ /dev/null @@ -1,19 +0,0 @@ -# Retire the removed workspace fleet rollout API - -`fleet config`, `fleet enable`, `fleet disable`, and `fleet inherit` called an API removed by Relaycast, so every invocation failed. They now remain as hidden compatibility commands that exit 0, accept legacy SDK options, and print a deprecation notice without creating a client. `config` emits a JSON deprecation object; `disable` explicitly says nodes have not been disabled. - -Removed `workspace.fleetNodes`, its messaging implementation, and `RelayWorkspaceFleetNodesConfig` from the SDK. SDK consumers must remove references to that API/type. Fleet nodes need no per-workspace enablement. - -Updated help tests, current docs and skills, the feature manifest, verification procedures, and the Unreleased changelog. The cleanroom harness retains all 110 operations and verifies the four no-ops against an unreachable endpoint. Regenerated the CLI inventory and its digest; reconciled existing option drift by recording skipped coverage for three newer options and dropping coverage entries for nine removed options. - -Validation: - -- `npm run build` and `npm run typecheck` passed. -- `npm run lint` passed with 107 warnings and no errors. -- CLI fleet/bootstrap and cleanroom/guardian suites: 164 tests passed. -- SDK facade/messaging/observer suites, using the SDK's own Vitest config: 53 tests passed. -- All four built CLI commands exited 0 with an empty temporary HOME and no credentials. -- Generated inventory matches the built CLI and matrix digest; 110 operations retained. -- Acceptance searches found no `fleetNodes` references in SDK/CLI source, obsolete policy-restoration fields, or fleet-off-by-default guidance. - -The live Daytona campaign was not run; validation covers local regression tests and the built CLI.