diff --git a/guarantees/agent/golden/scenes/component-boundaries.scene.yaml b/guarantees/agent/golden/scenes/component-boundaries.scene.yaml index a180019e..16a0b66f 100644 --- a/guarantees/agent/golden/scenes/component-boundaries.scene.yaml +++ b/guarantees/agent/golden/scenes/component-boundaries.scene.yaml @@ -3,6 +3,18 @@ id: sdk.golden.component-boundaries title: sdk golden component boundaries scope: local-component-tests workflow: + - id: cli-read-library-unit + action: { verifier: sdk.golden.cli-read-library-unit } + expect: { status: passed } + - id: cli-read-proposal-unit + action: { verifier: sdk.golden.cli-read-proposal-unit } + expect: { status: passed } + - id: cli-read-team-unit + action: { verifier: sdk.golden.cli-read-team-unit } + expect: { status: passed } + - id: cli-read-denials-unit + action: { verifier: sdk.golden.cli-read-denials-unit } + expect: { status: passed } - id: frozen-draft-unit action: { verifier: sdk.golden.frozen-draft-unit } expect: { status: passed } diff --git a/guarantees/verifiers/golden.verifiers.yaml b/guarantees/verifiers/golden.verifiers.yaml index 7eb09c59..c5c9e1c5 100644 --- a/guarantees/verifiers/golden.verifiers.yaml +++ b/guarantees/verifiers/golden.verifiers.yaml @@ -1,6 +1,26 @@ schemaVersion: treeseed.guarantee-verifiers/v1 ownerPackage: "@treeseed/sdk" verifiers: + sdk.golden.cli-read-library-unit: + kind: vitestCase + ownerPackage: "@treeseed/sdk" + testFile: tests/unit/operator-contracts/acceptance/golden-cli-arguments.test.ts + testName: "retains supported library read arguments without unsupported server or team flags" + sdk.golden.cli-read-proposal-unit: + kind: vitestCase + ownerPackage: "@treeseed/sdk" + testFile: tests/unit/operator-contracts/acceptance/golden-cli-arguments.test.ts + testName: "retains exact project proposal authority without adding a team selector" + sdk.golden.cli-read-team-unit: + kind: vitestCase + ownerPackage: "@treeseed/sdk" + testFile: tests/unit/operator-contracts/acceptance/golden-cli-arguments.test.ts + testName: "retains exact team and local server authority for workday assignment and graph reads" + sdk.golden.cli-read-denials-unit: + kind: vitestCase + ownerPackage: "@treeseed/sdk" + testFile: tests/unit/operator-contracts/acceptance/golden-cli-arguments.test.ts + testName: "denies missing unknown mutating and duplicate reader scope without rewriting arguments" sdk.golden.fixed-permissions-unit: kind: vitestCase ownerPackage: "@treeseed/sdk" diff --git a/tests/acceptance/golden-product.test.ts b/tests/acceptance/golden-product.test.ts index 6f1deb63..0b954e94 100644 --- a/tests/acceptance/golden-product.test.ts +++ b/tests/acceptance/golden-product.test.ts @@ -2,12 +2,12 @@ import assert from 'node:assert/strict'; import { execFileSync } from 'node:child_process'; import { readFileSync } from 'node:fs'; import test from 'node:test'; -import { sdkGoldenSource, sdkGoldenWorkdayId, verifySdkGoldenProduct, sdkGoldenProposalProvenance, sdkGoldenFrozenProposal, sdkGoldenLibraryFiles, sdkGoldenProposalDocument, type SdkGoldenOriginal } from './golden-product.ts'; +import { sdkGoldenReadArguments, sdkGoldenSource, sdkGoldenWorkdayId, verifySdkGoldenProduct, sdkGoldenProposalProvenance, sdkGoldenFrozenProposal, sdkGoldenLibraryFiles, sdkGoldenProposalDocument, type SdkGoldenOriginal } from './golden-product.ts'; import { assignmentAttemptSchema, assignmentResultSchema } from '@treeseed/sdk/agent-capacity'; test('SDK golden retains exact source and passing measured candidate release gates', () => { const read = (args: string[]) => { - const envelope = JSON.parse(execFileSync('trsd', [...args, '--server', 'local', '--team', process.env.TREESEED_ACCEPTANCE_TEAM ?? 'treeseed', '--json'], + const envelope = JSON.parse(execFileSync('trsd', sdkGoldenReadArguments(args, process.env.TREESEED_ACCEPTANCE_TEAM ?? 'treeseed'), { encoding: 'utf8', timeout: 30000, maxBuffer: 32 * 1024 * 1024 })); assert.equal(envelope.ok, true, 'ACCEPTANCE_SDK_READ: Authoritative read failed'); return envelope.result; }; diff --git a/tests/acceptance/golden-product.ts b/tests/acceptance/golden-product.ts index 0fdeeba5..1a19b262 100644 --- a/tests/acceptance/golden-product.ts +++ b/tests/acceptance/golden-product.ts @@ -5,8 +5,25 @@ import { exactEntityReferenceSchema, executionEdgeSchema, activityProfileSchema import { validatePortableContentData } from '@treeseed/sdk/content-validation'; import { createHash } from 'node:crypto'; import { parse, parseDocument, visit } from 'yaml'; +import { TREESEED_COMMAND_TREE_V1, type CommandLeafDescriptor } from '@treeseed/sdk/operator-contracts'; type Row = Record; +export function sdkGoldenReadArguments(args: readonly string[], team: string): string[] { + let nodes = TREESEED_COMMAND_TREE_V1.commands, selected: CommandLeafDescriptor | undefined; + for (const segment of args) { + const node = nodes.find(value => value.segment === segment); + assert.ok(node, 'ACCEPTANCE_SDK_READ: Supported public read command required'); + if (node.nodeType === 'leaf') { selected = node; break; } + nodes = node.children; + } + assert.ok(selected?.kind === 'read', 'ACCEPTANCE_SDK_READ: Supported public read command required'); + const operation = selected.execution.kind === 'operation' ? selected.execution : undefined; + const scopedTeam = operation?.input.some(input => input.source === 'context' && input.name === 'team'); + assert.ok(!scopedTeam || (typeof team === 'string' && team.trim()), 'ACCEPTANCE_SDK_READ: Exact team required'); + assert.ok(!args.some(value => ['--server', '--team', '--json'].includes(value)), + 'ACCEPTANCE_SDK_READ: Scope selectors must be supplied once by the owning reader'); + return [...args, ...(operation ? ['--server', 'local'] : []), ...(scopedTeam ? ['--team', team] : []), '--json']; +} export type SdkGoldenOriginal = { binding: Row; files: Row; initialProposal: Row; teamLibrary?: Row; referenceFiles?: Row[]; graph?: Row }; /** The selected scene consumes the existing CLI library-read result. */ export function sdkGoldenLibraryFiles(readback: Row): Row { diff --git a/tests/unit/operator-contracts/acceptance/golden-cli-arguments.test.ts b/tests/unit/operator-contracts/acceptance/golden-cli-arguments.test.ts new file mode 100644 index 00000000..2ff98fdf --- /dev/null +++ b/tests/unit/operator-contracts/acceptance/golden-cli-arguments.test.ts @@ -0,0 +1,36 @@ +import { expect, it } from 'vitest'; +import { sdkGoldenReadArguments } from '../../../acceptance/golden-product.ts'; + +it('retains supported library read arguments without unsupported server or team flags', () => { + for (const args of [['library', 'show', 'sdk'], ['library', 'show', 'team'], + ['library', 'read', 'sdk', 'proposals/current.mdx', '--ref', 'a'.repeat(40)]]) { + const held = [...args]; + expect.soft(sdkGoldenReadArguments(args, 'governed-team')).toEqual([...args, '--json']); + expect(args).toEqual(held); + } +}); + +it('retains exact project proposal authority without adding a team selector', () => { + const args = ['proposals', 'show', 'original-proposal', '--project', 'original-project'], held = [...args]; + expect(sdkGoldenReadArguments(args, 'governed-team')).toEqual([...args, '--server', 'local', '--json']); + expect(args).toEqual(held); +}); + +it('denies missing unknown mutating and duplicate reader scope without rewriting arguments', () => { + for (const args of [[], ['unknown'], ['library'], ['workdays', 'stop', 'original-workday'], + ['workdays', 'show', 'original-workday', '--server', 'foreign'], + ['library', 'show', 'sdk', '--team', 'foreign'], ['library', 'show', 'sdk', '--json']]) { + const held = [...args]; expect.soft(() => sdkGoldenReadArguments(args, 'governed-team')).toThrow('ACCEPTANCE_SDK_READ'); + expect(args).toEqual(held); + } + for (const team of ['', ' ']) expect(() => sdkGoldenReadArguments(['assignments', 'list'], team)).toThrow('ACCEPTANCE_SDK_READ'); +}); + +it('retains exact team and local server authority for workday assignment and graph reads', () => { + for (const args of [['workdays', 'show', 'original-workday'], ['assignments', 'list', '--limit', '50'], + ['assignments', 'show', 'original-attempt'], ['execution', 'graph', 'show']]) { + const held = [...args]; + expect(sdkGoldenReadArguments(args, 'governed-team')).toEqual([...args, '--server', 'local', '--team', 'governed-team', '--json']); + expect(args).toEqual(held); + } +});