From 85bd29093a73698ea795ea3512076d1392cfb1b3 Mon Sep 17 00:00:00 2001 From: Adrian Webb Date: Wed, 16 Sep 2026 13:52:04 -0400 Subject: [PATCH 1/4] feat(workdays): bind and validate high-level allocation options --- src/cli/commands/operator.ts | 16 +++++++++++++++ .../workdays/selection.test.ts | 20 +++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/src/cli/commands/operator.ts b/src/cli/commands/operator.ts index 08d15dd..d0115d6 100644 --- a/src/cli/commands/operator.ts +++ b/src/cli/commands/operator.ts @@ -4,6 +4,7 @@ import { resolve } from 'node:path'; import { parse as parseYaml } from 'yaml'; import { controlPlaneOperation, encodeConfirmationState, parseCommunicationAddresses, validateWorkdayIntentSelection, normalizeWorkdayAgentSelection, type CommandInputBinding } from '@treeseed/sdk/operator-contracts'; import { ControlPlaneClientError, resolveControlPlaneServer } from '@treeseed/sdk/control-plane-client'; +import { workdayAllocationOverridesSchema } from '@treeseed/sdk/agent-capacity'; import type { CommandContext, ParsedInvocation } from '../types.js'; import { launchApplication } from '../application/launch.js'; import { runInteractiveChat } from '../communication/interactive-chat.js'; @@ -34,7 +35,17 @@ function sourceValue(binding: CommandInputBinding, invocation: ParsedInvocation, function transform(value: unknown, binding: CommandInputBinding) { if (value === undefined || value === null) return undefined; if (binding.transform === 'csv') return (Array.isArray(value) ? value : [value]).flatMap(item => String(item).split(',').map(part => part.trim())); + if (binding.transform === 'json') { + try { return JSON.parse(String(value)); } catch { throw Object.assign(new Error(`Invalid JSON for --${binding.name}.`), + { category: 'invalid_input', code: 'command_json_invalid' }); } + } if (value === '') return undefined; + if (binding.transform === 'number') { + const parsed = Number(value); + if (!Number.isFinite(parsed)) throw Object.assign(new Error(`${binding.name} must be a finite number.`), + { category: 'invalid_input', code: 'command_number_invalid' }); + return parsed; + } if (binding.transform === 'integer') { const parsed = Number(value); if (!Number.isInteger(parsed)) throw new Error(`${binding.name} must be an integer.`); @@ -71,6 +82,11 @@ async function operationInput(invocation: ParsedInvocation, context: CommandCont if (value !== undefined) setOperationInputField(input[binding.target], binding.field, value); } const operation = controlPlaneOperation(invocation.command.execution.operationId); + if (operation.descriptor.operationId === 'workdays.plan' && input.body.allocation !== undefined) { + const parsed = workdayAllocationOverridesSchema.safeParse(input.body.allocation); + if (!parsed.success) throw Object.assign(new Error(parsed.error.issues.map((issue) => `${issue.path.join('.')}: ${issue.message}`).join(' ')), + { category: 'invalid_input', code: 'workday_allocation_invalid' }); + } if (operation.descriptor.operationId === 'workdays.plan' && input.body.agentSelection !== undefined) { const diagnostics = validateWorkdayIntentSelection(input.body.agentSelection); if (diagnostics.length) throw Object.assign(new Error(diagnostics.map(item => `${item.path}: ${item.message}`).join(' ')), { category: 'invalid_input', code: 'workday_agent_selection_invalid' }); diff --git a/tests/unit/command-boundary/workdays/selection.test.ts b/tests/unit/command-boundary/workdays/selection.test.ts index 6abdee2..d58b936 100644 --- a/tests/unit/command-boundary/workdays/selection.test.ts +++ b/tests/unit/command-boundary/workdays/selection.test.ts @@ -5,6 +5,26 @@ import { getOperationInputField, setOperationInputField } from '../../../../src/ const base = ['workdays', 'plan', '--team', '11111111-1111-4111-8111-111111111111', '--profile', 'documentation', '--projects', 'sdk', '--start', '2030-01-01T00:00:00Z', '--duration', '600', '--json']; +test('high-level allocation options use one nested policy input', async () => { + let body: any; + assert.equal(await runCommandLine([...base, '--planning-percent', '20', '--allocation-weight', '2', + '--planning-turn-maximum-seconds', '180', '--project-percentages', '{"sdk":60,"api":40}', + '--agent-class-percentages', '{"sdk":{"engineer":100}}'], { + interactiveUi: false, write() {}, operationInvoke: async (_id, input) => { body = input.body; return { data: {} }; }, + }), 0); + assert.deepEqual(body.allocation, { planningPercent: 20, allocationWeight: 2, planningTurnMaximumSeconds: 180, + projectPercentages: { sdk: 60, api: 40 }, agentClassPercentages: { sdk: { engineer: 100 } } }); +}); + +for (const options of [['--project-percentages', 'invalid'], ['--allocation-weight', '0'], ['--planning-percent', '101']]) { + test(`invalid allocation ${JSON.stringify(options)} never invokes the API`, async () => { + let calls = 0; + assert.equal(await runCommandLine([...base, ...options], { interactiveUi: false, write() {}, + operationInvoke: async () => { calls++; } }), 1); + assert.equal(calls, 0); + }); +} + test('repeated and CSV selectors become a normalized intersecting nested intent', async () => { const calls: Array<{ operationId: string; input: any }> = []; const exit = await runCommandLine([...base, '--agent', 'reviewer,architect', '--agent', 'reviewer', '--activity', 'reviewing', '--class', 'engineering'], { From a3497b8a9a092abf345478fa6beeccb6f1011c64 Mon Sep 17 00:00:00 2001 From: Adrian Webb Date: Wed, 16 Sep 2026 18:33:38 -0400 Subject: [PATCH 2/4] Verify allocation integration against accepted exact SDK artifact --- .github/workflows/verify.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml index ae215d5..52699cd 100644 --- a/.github/workflows/verify.yml +++ b/.github/workflows/verify.yml @@ -30,7 +30,12 @@ jobs: timeout-minutes: 20 run: npm ci --no-audit --no-fund - - name: Verify package + - name: Install exact integrated SDK + uses: treeseed-ai/sdk/.github/actions/install-exact-sdk@e440819668c01fbc1f9a487cb67e87def7405f07 + with: + github-token: ${{ github.token }} + + - name: Verify package with exact SDK run: npm run verify:direct - name: Seal protected staging candidate From 0e69aa08c41346a72225ac64f4824a0ce05f8a20 Mon Sep 17 00:00:00 2001 From: Adrian Webb Date: Wed, 16 Sep 2026 18:57:32 -0400 Subject: [PATCH 3/4] Use verified merged SDK artifact for integration checks --- .github/workflows/verify.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml index 52699cd..4ce07dc 100644 --- a/.github/workflows/verify.yml +++ b/.github/workflows/verify.yml @@ -31,7 +31,7 @@ jobs: run: npm ci --no-audit --no-fund - name: Install exact integrated SDK - uses: treeseed-ai/sdk/.github/actions/install-exact-sdk@e440819668c01fbc1f9a487cb67e87def7405f07 + uses: treeseed-ai/sdk/.github/actions/install-exact-sdk@c57912d9ce2e05bfb09209b03aa7c3a1d9489585 with: github-token: ${{ github.token }} From e92ff739d0e951414e2bbf722f8f215a3a561c51 Mon Sep 17 00:00:00 2001 From: Adrian Webb Date: Wed, 16 Sep 2026 19:18:35 -0400 Subject: [PATCH 4/4] Unlink owned overlay symlinks reliably on Node 24 --- src/cli/commands/development-support/overlays.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/cli/commands/development-support/overlays.ts b/src/cli/commands/development-support/overlays.ts index 4e5cfb9..02feea3 100644 --- a/src/cli/commands/development-support/overlays.ts +++ b/src/cli/commands/development-support/overlays.ts @@ -1,5 +1,5 @@ import { spawn } from 'node:child_process'; -import { closeSync, existsSync, lstatSync, mkdirSync, openSync, readFileSync, readlinkSync, renameSync, rmSync, symlinkSync } from 'node:fs'; +import { closeSync, existsSync, lstatSync, mkdirSync, openSync, readFileSync, readlinkSync, renameSync, rmSync, symlinkSync, unlinkSync } from 'node:fs'; import { dirname, relative, resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; import type { DevelopmentRuntime, DevelopmentTarget } from '@treeseed/sdk/development'; @@ -93,7 +93,8 @@ export function installPackageOverlay(state: OverlaySessionState, record: { sess continue; } if (repair) { - rmSync(link, { recursive: true, force: true }); + // This path was verified as our recorded symbolic link, not a directory. + unlinkSync(link); symlinkSync(relativeOverlayTarget(link, overlayRoot), link, 'dir'); continue; }