diff --git a/docs/command-reference.md b/docs/command-reference.md index 9840155..9f671be 100644 --- a/docs/command-reference.md +++ b/docs/command-reference.md @@ -362,6 +362,7 @@ Control-plane operation: `governance.proposals.withdraw`. - `--server `: Control-plane server profile or URL. - `--project `: Project id or slug. +- `--yes`: Confirm authorized automation. - `--json`: Emit the stable JSON envelope. - `--if-match `: Exact current resource version, or new when unconfigured. - `--idempotency-key `: Reuse the same request identity when retrying this mutation. @@ -2139,6 +2140,9 @@ Control-plane operation: `capacity.usage`. - `--server `: Control-plane server profile or URL. - `--team `: Team id or slug. - `--project `: Project id or slug. +- `--status `: Status filter. +- `--limit `: Page size. +- `--cursor `: Opaque page cursor. - `--json`: Emit the stable JSON envelope. - `--workday `: Restrict evidence to one workday. diff --git a/schemas/command-tree.json b/schemas/command-tree.json index 495471e..6f6801e 100644 --- a/schemas/command-tree.json +++ b/schemas/command-tree.json @@ -1262,7 +1262,7 @@ ], "authorization": { "capability": "command.withdraw", - "confirmation": "never" + "confirmation": "destructive" }, "resultSchemaId": "treeseed.command.withdraw/v1", "execution": { @@ -5583,6 +5583,16 @@ "name": "--workday", "description": "Restrict evidence to one workday.", "type": "string" + }, + { + "name": "--limit", + "description": "Page size.", + "type": "number" + }, + { + "name": "--cursor", + "description": "Opaque page cursor.", + "type": "string" } ], "resultSchemaId": "treeseed.command.usage/v1", @@ -5613,6 +5623,30 @@ "name": "workday", "required": false, "transform": "identity" + }, + { + "target": "query", + "field": "status", + "source": "option", + "name": "status", + "required": false, + "transform": "identity" + }, + { + "target": "query", + "field": "limit", + "source": "option", + "name": "limit", + "required": false, + "transform": "integer" + }, + { + "target": "query", + "field": "cursor", + "source": "option", + "name": "cursor", + "required": false, + "transform": "identity" } ] } diff --git a/src/cli/commands/development.ts b/src/cli/commands/development.ts index 83ee398..f4df135 100644 --- a/src/cli/commands/development.ts +++ b/src/cli/commands/development.ts @@ -290,6 +290,8 @@ async function rebuildPackage(input: { state: LocalSessionState; record: { sessi } async function restartConsumer(input: { state: LocalSessionState; runtime: DevelopmentRuntime; target: DevelopmentTarget; worktree: string; mode: 'candidate' | 'live'; context: CommandContext; recordGeneration?: boolean }) { const { state, runtime, target, worktree, mode, context } = input, key = `${runtime.project.id}.${target.id}`; + const resolved = await invoke(context, 'local.dev.environment', { sessionId: state.sessionId, projectId: runtime.project.id, targetId: target.id }) as { environment?: NodeJS.ProcessEnv }; + if (usesManagedContainer(target) && !usesManagerBuild(target) && target.kind === 'rebuild-restart' && target.operations.build) runOneShotOperation(state, target.operations.build, worktree, mode, context.env, resolved.environment ?? {}); await stopProcess(state, key); if (usesManagedContainer(target)) { // Preserve the live selection if manager custody refuses an active claim. @@ -297,13 +299,11 @@ async function restartConsumer(input: { state: LocalSessionState; runtime: Devel await invoke(context, 'local.dev.use', {sessionId:state.sessionId,projectId:runtime.project.id,targetId:target.id,mode:'released'}); } else if (target.operations.cleanup) runOneShotOperation(state, target.operations.cleanup, worktree, mode, context.env, { TREESEED_DEVELOPMENT_CLEANUP_SCOPE: 'runtime' }); - const resolved = await invoke(context, 'local.dev.environment', { sessionId: state.sessionId, projectId: runtime.project.id, targetId: target.id }) as { environment?: NodeJS.ProcessEnv }; if (target.operations.setup) runOneShotOperation(state, target.operations.setup, worktree, mode, context.env, resolved.environment ?? {}); if (!target.operations.start && target.kind === 'rebuild-restart' && target.operations.build) { runOneShotOperation(state, target.operations.build, worktree, mode, context.env, resolved.environment ?? {}); await waitForDirectReadiness(target, target.ready.kind === 'process' ? target.ready.graceSeconds : target.ready.timeoutSeconds); } else if (usesManagedContainer(target)) { - if (!usesManagerBuild(target) && target.kind === 'rebuild-restart' && target.operations.build) runOneShotOperation(state, target.operations.build, worktree, mode, context.env, resolved.environment ?? {}); await containerOperation(context, state.sessionId, runtime, target, 'start'); } else { startOperation(state, runtime, target, worktree, mode, context.env, resolved.environment ?? {}); diff --git a/tests/unit/command-boundary/host/managed-development-container.test.ts b/tests/unit/command-boundary/host/managed-development-container.test.ts index d92305e..6f65f81 100644 --- a/tests/unit/command-boundary/host/managed-development-container.test.ts +++ b/tests/unit/command-boundary/host/managed-development-container.test.ts @@ -42,6 +42,16 @@ test('container startup and cleanup only invoke the protected manager, including assert.equal(await runCommandLine(['dev','restart','api.service','--session',selected,'--plan','--json'],{...context,write:(value:string)=>planned.push(value)}),0); assert.deepEqual(actions,beforePlan,'Plan must not stop or start a container'); assert.equal(record.session.targets[0].mode,'live','Plan must not switch routes'); + Object.assign(target, { kind: 'rebuild-restart' }); + Object.assign(target.operations, { build: { command: process.execPath, args: ['-e', 'process.exit(7)'], environment: {}, timeoutSeconds: 10 } }); + writeFileSync(file,JSON.stringify({development:runtime})); + const beforeFailedBuild = [...actions]; + assert.equal(await runCommandLine(['dev','restart','api.service','--session',selected,'--json'],context),1); + assert.deepEqual(actions,beforeFailedBuild,'A failed candidate build must not stop the healthy managed runtime'); + assert.equal(record.session.targets[0].mode,'live','A failed candidate build must retain the live route'); + delete (target.operations as { build?: unknown }).build; + Object.assign(target, { kind: 'live-api' }); + writeFileSync(file,JSON.stringify({development:runtime})); assert.equal(JSON.parse(planned[0]!).result.mutation,false); rejectStop=true; for(const command of ['restart','rebuild']) {