From e6feccd6ed809b4420941d26de330ef39b77760e Mon Sep 17 00:00:00 2001 From: Adrian Webb Date: Sun, 11 Oct 2026 08:49:21 -0400 Subject: [PATCH 1/2] test: reject counted SDK graph pairs unrelated to authored work Criterion170 regression: original product verifier accepts six counted pairs after a Reviewer is substituted with an unrelated work item. Baseline passes, mutation remains unchanged, expected denial is absent. Node24.12 unit15/14/1; retain RED before verifier repair. SDK product source remains untouched. --- .../acceptance/golden-product-fixture.ts | 12 +++++++++++- tests/unit/operator-contracts/golden-product.test.ts | 11 +++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/tests/unit/operator-contracts/acceptance/golden-product-fixture.ts b/tests/unit/operator-contracts/acceptance/golden-product-fixture.ts index 40d140fd1..2b10e2ee3 100644 --- a/tests/unit/operator-contracts/acceptance/golden-product-fixture.ts +++ b/tests/unit/operator-contracts/acceptance/golden-product-fixture.ts @@ -55,9 +55,19 @@ export function proposalReadback(...input: [unknown?, number?, (boolean | 'alias const digest = createHash('sha256').update(content).digest('hex'), repositoryId = 'repo_sdk_fixture', commitSha = 'c'.repeat(40), contentPath = 'proposals/governance/sdk-proposal.mdx'; const initialProposal = structuredClone({ ...proposal, status: 'draft' }); for (const item of initialProposal.executionPlan.workItems) { Reflect.deleteProperty(item, 'estimate'); Reflect.deleteProperty(item, 'reviewEstimate'); } + // Controlled graph observations, not generated production evidence. + const sourceRef = { store: 'treedx', model: 'proposal', id: proposal.id, revision: 1, digest: `sha256:${digest}`, + repository: repositoryId, commit: commitSha, path: contentPath }; + const nodes = proposal.executionPlan.workItems.flatMap(item => ['actor', 'reviewer'].map(pairRole => ({ + id: `${pairRole}-${item.id}`, teamId: 'sdk-team', projectId: proposal.projectId, workdayId: 'workday-fixture', + workItemId: item.id, pairRole, kind: pairRole === 'actor' ? 'acting' : 'reviewing', + agentClass: pairRole === 'actor' ? item.agentClass : 'reviewer', workspace: pairRole === 'actor' ? item.workspace : 'treedx', + maximumReviewCycles: item.maximumReviewCycles, sourceRef: structuredClone(sourceRef) }))); + const graph = { nodes, edges: proposal.executionPlan.workItems.map(item => ({ id: `pair-${item.id}`, teamId: 'sdk-team', + fromNodeId: `actor-${item.id}`, toNodeId: `reviewer-${item.id}`, provenance: 'review-pair', sourceRef: structuredClone(sourceRef) })) }; return { governance: { id: fields.id, projectId: fields.projectId, title: fields.title, summary: fields.summary, body: fields.request, activeVersion: 1, activeContentHash: digest, metadata: { contentProvenance: { repositoryId, commitSha, contentPath, digest } } }, - original: { initialProposal, binding: { repositoryId }, + original: { initialProposal, graph, binding: { repositoryId }, teamLibrary: { project: { id: 'team-project', slug: 'team', teamId: 'sdk-team' }, library: { projectId: 'team-project', teamId: 'sdk-team', repositoryId: 'repo_team_fixture' } }, referenceFiles: structuredClone(referenced.map(item => item.read)), files: { ok: true, repoId: repositoryId, resolvedRef: commitSha, files: [{ path: contentPath, content, frontmatter: parse(encoded) as unknown }] } } }; diff --git a/tests/unit/operator-contracts/golden-product.test.ts b/tests/unit/operator-contracts/golden-product.test.ts index cc956acf7..5a0b06ba7 100644 --- a/tests/unit/operator-contracts/golden-product.test.ts +++ b/tests/unit/operator-contracts/golden-product.test.ts @@ -7,6 +7,17 @@ import { commit, release, review, proposal, proposalReadback } from './acceptanc const verifySdkGoldenProduct = (items: Parameters[0], source: string) => Reflect.apply(verifyProduct, undefined, [items, source, proposalReadback().governance, proposalReadback().original]); describe('SDK golden product gate (fixtures are not acceptance)', () => { + it('rejects six counted Actor Reviewer pairs that do not represent their exact authored proposal work', () => { + const observed = proposalReadback(); + const inspect = (value: typeof observed) => Reflect.apply(verifyProduct, undefined, [[release, review], commit, value.governance, value.original]); + expect(() => inspect(observed)).not.toThrow(); + const changed = structuredClone(observed); + changed.original.graph.nodes[1]!.workItemId = 'unrelated-authored-work'; + const retained = structuredClone(changed); + expect(() => inspect(changed)).toThrow('ACCEPTANCE_SDK_GRAPH_PAIRS'); + expect(changed).toEqual(retained); + expect(() => inspect(observed)).not.toThrow(); + }); it('rejects a coherently rehashed closed discussion that names another proposal repository or path', () => { for (const [field, value] of [['repository', 'repo_foreign'], ['path', 'proposals/foreign.mdx']]) { const input = structuredClone(proposal), original = proposalReadback().original, file = original.referenceFiles[2]!.files[0]!; From 4a0e40be821bed57c44c623ebf21b4ac70ba4e4c Mon Sep 17 00:00:00 2001 From: Adrian Webb Date: Sun, 11 Oct 2026 08:55:04 -0400 Subject: [PATCH 2/2] fix: verify exact authored SDK graph Actor and Reviewer pairs Retain original raw proposal and fixed draft assertions; compare complete public workday graph against each authored row, exact source and generated reviewer authority. Extend existing native SDK HTTP case with20 graph corruptions, concurrent reads,403/503/malformed/interrupted errors and unchanged retry; keep original5s/15s bounds and finally cleanup. Preserve unrelated workday nodes. Unit15, native16 and broader26 pass; strict tsc/file policies pass. Retain initial oversized-boundary fixture failure and exact correction. Add only necessary unit component selection; selected SDK live asset uses supported public graph read-back. No SDK product source change. --- .../scenes/component-boundaries.scene.yaml | 3 +++ guarantees/verifiers/golden.verifiers.yaml | 5 ++++ tests/acceptance/golden-product.test.ts | 2 ++ tests/acceptance/golden-product.ts | 25 +++++++++++++++++- .../acceptance/golden-product-fixture.ts | 24 ++++++++++++++--- .../acceptance/golden-proposal-native.test.ts | 26 ++++++++++++++++--- .../operator-contracts/golden-product.test.ts | 12 ++++++++- 7 files changed, 89 insertions(+), 8 deletions(-) diff --git a/guarantees/agent/golden/scenes/component-boundaries.scene.yaml b/guarantees/agent/golden/scenes/component-boundaries.scene.yaml index c13d86ee3..960bf443a 100644 --- a/guarantees/agent/golden/scenes/component-boundaries.scene.yaml +++ b/guarantees/agent/golden/scenes/component-boundaries.scene.yaml @@ -678,6 +678,9 @@ workflow: - id: assignment-workday-unit action: { verifier: sdk.golden.assignment-workday-unit } expect: { status: passed } + - id: authored-graph-pairs-unit + action: { verifier: sdk.golden.authored-graph-pairs-unit } + expect: { status: passed } - id: assignment-workday-native action: { verifier: sdk.golden.assignment-workday-native } expect: { status: passed } diff --git a/guarantees/verifiers/golden.verifiers.yaml b/guarantees/verifiers/golden.verifiers.yaml index 90a10f2bf..084519f4b 100644 --- a/guarantees/verifiers/golden.verifiers.yaml +++ b/guarantees/verifiers/golden.verifiers.yaml @@ -1,6 +1,11 @@ schemaVersion: treeseed.guarantee-verifiers/v1 ownerPackage: "@treeseed/sdk" verifiers: + sdk.golden.authored-graph-pairs-unit: + kind: vitestCase + ownerPackage: "@treeseed/sdk" + testFile: tests/unit/operator-contracts/golden-product.test.ts + testName: "rejects six counted Actor Reviewer pairs that do not represent their exact authored proposal work" sdk.golden.frozen-draft-unit: kind: vitestCase ownerPackage: "@treeseed/sdk" diff --git a/tests/acceptance/golden-product.test.ts b/tests/acceptance/golden-product.test.ts index 3090c14fa..6f1deb638 100644 --- a/tests/acceptance/golden-product.test.ts +++ b/tests/acceptance/golden-product.test.ts @@ -46,6 +46,7 @@ test('SDK golden retains exact source and passing measured candidate release gat assert.ok(result.page.nextCursor && result.page.nextCursor !== cursor && page < 39, 'ACCEPTANCE_SDK_PAGINATION: Incomplete assignment evidence'); cursor = result.page.nextCursor; } + original.graph = read(['execution', 'graph', 'show']); verifySdkGoldenProduct(assignments, sdkGoldenSource(freeze, run.parameters.proposalIds), proposal, original); // Complete public list/show custody, not a time-cutoff approximation or a // synthetic replacement for a managed Releaser and its independent Reviewer. @@ -56,6 +57,7 @@ test('SDK golden retains exact source and passing measured candidate release gat return value; }); verifySdkGoldenProduct(full, sdkGoldenSource(freeze, run.parameters.proposalIds), proposal, original); + assert.deepEqual(read(['execution', 'graph', 'show']), original.graph, 'ACCEPTANCE_SDK_GRAPH_PAIRS: Public graph changed during independent read-back'); const final = (activity: string, workItemId = 'simulate-release') => full.filter(item => item.status === 'completed' && item.assignmentAttempt?.workItemId === workItemId && item.assignmentAttempt.effectiveProfile?.activity === activity) .sort((a, b) => String(a.createdAt).localeCompare(String(b.createdAt))).at(-1); diff --git a/tests/acceptance/golden-product.ts b/tests/acceptance/golden-product.ts index b72fb8b80..35aed294b 100644 --- a/tests/acceptance/golden-product.ts +++ b/tests/acceptance/golden-product.ts @@ -7,7 +7,7 @@ import { createHash } from 'node:crypto'; import { parse, parseDocument, visit } from 'yaml'; type Row = Record; -export type SdkGoldenOriginal = { binding: Row; files: Row; initialProposal: Row; teamLibrary?: Row; referenceFiles?: Row[] }; +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 { const result = readback && typeof readback === 'object' && !Array.isArray(readback) ? readback.result : undefined; @@ -111,6 +111,28 @@ function verifySdkGoldenAuthoredWork(proposal: Row, draft = false): void { } } +function verifySdkGoldenGraph(proposal: Row, attempt: Row, graph: Row | undefined): void { + const code = 'ACCEPTANCE_SDK_GRAPH_PAIRS'; + assert.ok(graph && graph.teamId === attempt.teamId && Array.isArray(graph.nodes), `${code}: Independent public graph required`); + assert.ok(graph.nodes.every((node: unknown) => node && typeof node === 'object' && !Array.isArray(node)), `${code}: Malformed graph node`); + const nodes: Row[] = graph.nodes.filter((node: Row) => node.workdayId === attempt.workdayId && node.pairRole !== null); + const items: Row[] = proposal.executionPlan.workItems; + assert.equal(nodes.length, items.length * 2, `${code}: Exactly six authored Actors and six generated Reviewers required`); + assert.equal(new Set(nodes.map(node => node.id)).size, nodes.length, `${code}: Duplicate node identity`); + for (const item of items) for (const role of ['actor', 'reviewer']) { + const pair = nodes.filter(node => node.workItemId === item.id && node.pairRole === role); + assert.equal(pair.length, 1, `${code}: Each authored work item requires one exact ${role}`); + const node = pair[0]!; + assert.ok(typeof node.id === 'string' && node.id && node.teamId === attempt.teamId && node.projectId === proposal.projectId + && node.kind === (role === 'actor' ? item.activity : 'reviewing') + && node.agentClass === (role === 'actor' ? item.agentClass : 'reviewer') + && node.workspace === (role === 'actor' ? item.workspace : 'treedx') + && node.maximumReviewCycles === item.maximumReviewCycles && item.review === 'required', + `${code}: Authored Actor or generated Reviewer authority drifted`); + assert.deepEqual(node.sourceRef, attempt.sourceRef, `${code}: Pair must represent the exact original authored proposal`); + } +} + export function verifySdkGoldenProduct(assignments: Row[], sourceBase: string, storedProposal: unknown, original?: SdkGoldenOriginal): void { const actors = assignments.filter(item => item.status === 'completed' @@ -155,6 +177,7 @@ export function verifySdkGoldenProduct(assignments: Row[], sourceBase: string, s && sourceRef.repository === provenance.repositoryId && sourceRef.commit === provenance.commitSha && sourceRef.path === provenance.contentPath, 'ACCEPTANCE_SDK_PROPOSAL: Read content differs from original issued proposal authority'); + verifySdkGoldenGraph(proposal, release.assignmentAttempt, original.graph); for (const item of actors.filter(item => ['research-context', 'architecture-contract', 'tests-first'].includes(item.assignmentAttempt.workItemId))) assert.ok(item.assignmentAttempt.contextRefs?.some((ref: Row) => ref.store === 'git' && ref.commit === sourceBase), 'ACCEPTANCE_SDK_SOURCE: Early roles must receive exact frozen SDK source'); diff --git a/tests/unit/operator-contracts/acceptance/golden-product-fixture.ts b/tests/unit/operator-contracts/acceptance/golden-product-fixture.ts index 2b10e2ee3..96cf83165 100644 --- a/tests/unit/operator-contracts/acceptance/golden-product-fixture.ts +++ b/tests/unit/operator-contracts/acceptance/golden-product-fixture.ts @@ -2,6 +2,21 @@ import { createHash } from 'node:crypto'; import { parse, stringify } from 'yaml'; export const commit = 'a'.repeat(40); const libraryCommit = 'c'.repeat(40); +export const graphPairFailures = ['missing', 'foreign-team', 'duplicate-id', 'missing-node', 'extra-node', 'work-item', + 'pair-role', 'kind', 'class', 'workspace', 'cycles', 'project', 'team', 'workday', 'source-id', 'source-revision', + 'source-digest', 'source-commit', 'source-path', 'source-repository'] as const; +export function mutateGraphPair(graph: { teamId: string; nodes: Array> }, mode: string): void { + const node = graph.nodes[1]!; + if (mode === 'missing') Reflect.deleteProperty(graph, 'nodes'); + else if (mode === 'foreign-team') graph.teamId = 'foreign'; + else if (mode === 'duplicate-id') node.id = graph.nodes[0]!.id; + else if (mode === 'missing-node') graph.nodes.pop(); + else if (mode === 'extra-node') graph.nodes.push({ ...node, id: 'extra-pair' }); + else if (mode.startsWith('source-')) Reflect.set(node.sourceRef as object, mode.slice(7), mode === 'source-revision' ? 2 : mode === 'source-digest' ? `sha256:${'e'.repeat(64)}` : mode === 'source-commit' ? 'd'.repeat(40) : 'foreign'); + else Reflect.set(node, ({ 'work-item': 'workItemId', 'pair-role': 'pairRole', class: 'agentClass', cycles: 'maximumReviewCycles', + project: 'projectId', team: 'teamId', workday: 'workdayId' } as Record)[mode] ?? mode, + mode === 'cycles' ? 3 : mode === 'kind' ? 'acting' : mode === 'pair-role' ? 'actor' : mode === 'workspace' ? 'git' : 'foreign'); +} const referenced = [ { schemaVersion: 'treeseed.objective/v1', id: 'sdk-objective', projectId: 'sdk-project', title: 'SDK objective', outcome: 'Portable intent', status: 'active' }, { schemaVersion: 'treeseed.objective/v1', id: 'team-objective', projectId: 'team-project', title: 'Team objective', outcome: 'Governed execution', status: 'active' }, @@ -59,12 +74,15 @@ export function proposalReadback(...input: [unknown?, number?, (boolean | 'alias const sourceRef = { store: 'treedx', model: 'proposal', id: proposal.id, revision: 1, digest: `sha256:${digest}`, repository: repositoryId, commit: commitSha, path: contentPath }; const nodes = proposal.executionPlan.workItems.flatMap(item => ['actor', 'reviewer'].map(pairRole => ({ + schemaVersion: 'treeseed.execution-node/v1', ruleRevision: 1, nodeRevision: 1, status: 'completed', + graphRevisionCreated: 1, graphRevisionUpdated: 1, id: `${pairRole}-${item.id}`, teamId: 'sdk-team', projectId: proposal.projectId, workdayId: 'workday-fixture', workItemId: item.id, pairRole, kind: pairRole === 'actor' ? 'acting' : 'reviewing', agentClass: pairRole === 'actor' ? item.agentClass : 'reviewer', workspace: pairRole === 'actor' ? item.workspace : 'treedx', - maximumReviewCycles: item.maximumReviewCycles, sourceRef: structuredClone(sourceRef) }))); - const graph = { nodes, edges: proposal.executionPlan.workItems.map(item => ({ id: `pair-${item.id}`, teamId: 'sdk-team', - fromNodeId: `actor-${item.id}`, toNodeId: `reviewer-${item.id}`, provenance: 'review-pair', sourceRef: structuredClone(sourceRef) })) }; + maximumReviewCycles: item.maximumReviewCycles, sourceRef: structuredClone(sourceRef), + estimate: structuredClone(pairRole === 'actor' ? item.estimate : item.reviewEstimate), + requiredCapabilities: ['source.read'], requestedPermissions: structuredClone(item.requestedPermissions) }))); + const graph = { teamId: 'sdk-team', revision: 1, digest: `sha256:${'f'.repeat(64)}`, nodes, edges: [] }; return { governance: { id: fields.id, projectId: fields.projectId, title: fields.title, summary: fields.summary, body: fields.request, activeVersion: 1, activeContentHash: digest, metadata: { contentProvenance: { repositoryId, commitSha, contentPath, digest } } }, original: { initialProposal, graph, binding: { repositoryId }, diff --git a/tests/unit/operator-contracts/acceptance/golden-proposal-native.test.ts b/tests/unit/operator-contracts/acceptance/golden-proposal-native.test.ts index 4aa19456c..0e129a305 100644 --- a/tests/unit/operator-contracts/acceptance/golden-proposal-native.test.ts +++ b/tests/unit/operator-contracts/acceptance/golden-proposal-native.test.ts @@ -7,11 +7,12 @@ import { resolve } from 'node:path'; import { createHash } from 'node:crypto'; import { parse } from 'yaml'; import { expect, it } from 'vitest'; -import { commit, release, review, proposal, proposalReadback } from './golden-product-fixture.ts'; +import { commit, release, review, proposal, proposalReadback, graphPairFailures, mutateGraphPair } from './golden-product-fixture.ts'; import { workdayStartFixture } from './workday-start-fixture.ts'; it('native public SDK proposal reads in isolated product verification reject changed stored identity title and summary and retain denied observations before unchanged retry', async () => { let mode = 'exact'; const calls: string[] = [], referenceCalls: string[] = [], failed: Array> = []; + const graphCalls: string[] = [], graphFailures: string[] = []; const discussionBytes = (kind: string, bytes: string) => kind === 'reference-open' ? bytes.replace('status: closed', 'status: open') : kind === 'reference-repository' ? bytes.replace('repository: repo_sdk_fixture', 'repository: repo_foreign') : kind === 'reference-path' ? bytes.replace('path: proposals/governance/sdk-proposal.mdx', 'path: proposals/foreign.mdx') : bytes; @@ -27,7 +28,16 @@ it('native public SDK proposal reads in isolated product verification reject cha const fail = (error: Error) => { for (const item of pending.values()) { clearTimeout(item.timer); item.reject(error); } pending.clear(); }; const server = createServer((request, response) => { let bytes = ''; request.setEncoding('utf8'); request.on('data', value => { bytes += String(value); }); request.on('end', () => { - const fixture = proposalReadback().original, path = request.method === 'POST' ? JSON.parse(bytes).paths?.[0] : undefined; + const fixture = proposalReadback(proposal, mode === 'budget-boundary' ? 1_048_576 : undefined).original, path = request.method === 'POST' ? JSON.parse(bytes).paths?.[0] : undefined; + if (request.url === '/v1/teams/sdk-team/execution-graph') { + graphCalls.push(`${request.method} ${request.url}`); response.setHeader('content-type', 'application/json'); + if (mode.startsWith('graph-')) graphFailures.push(mode); + if (mode === 'graph-denied' || mode === 'graph-unavailable') { response.writeHead(mode === 'graph-denied' ? 403 : 503).end(JSON.stringify({ title: 'Controlled graph failure' })); return; } + if (mode === 'graph-interrupted') { request.socket.destroy(); return; } + if (mode === 'graph-json') { response.end('{'); return; } + if (mode.startsWith('graph-')) mutateGraphPair(fixture.graph, mode.slice(6)); + response.end(JSON.stringify({ data: fixture.graph })); return; + } if (['reference-open', 'reference-repository', 'reference-path'].includes(mode)) { const discussion = fixture.referenceFiles[2]!.files[0]!; discussion.content = discussionBytes(mode, discussion.content); @@ -126,7 +136,8 @@ const teamBinding=await client.invoke(controlPlaneOperation('treedx.library.show const referenceFiles=[]; for(const ref of [...(Array.isArray(document.objectiveRefs)?document.objectiveRefs:[]),...(document.discussionRef?[document.discussionRef]:[])]){ const result=await client.invoke(controlPlaneOperation('treedx.repositories.files.read'),{path:{projectId:ref.repository===teamBinding.data.repositoryId?'team-project':'sdk-project',repoId:ref.repository},query:{},body:{ref:ref.commit,paths:[ref.path],encoding:'utf8',parseFrontmatter:true}});referenceFiles.push(sdkGoldenLibraryFiles({result:result.data,receipt:{operation:'read'}}));} -Reflect.apply(verifySdkGoldenProduct,undefined,[actors,${JSON.stringify(commit)},observed.data,{...original,teamLibrary:{project:teamProject.data,library:teamBinding.data},referenceFiles}]);process.send({id,passed:true}); +const graph=await client.invoke(controlPlaneOperation('execution.graph.show'),{path:{teamId:'sdk-team'},query:{},body:undefined}); +Reflect.apply(verifySdkGoldenProduct,undefined,[actors,${JSON.stringify(commit)},observed.data,{...original,graph:graph.data,teamLibrary:{project:teamProject.data,library:teamBinding.data},referenceFiles}]);process.send({id,passed:true}); }catch(error){process.send({id,passed:false,error:error instanceof Error?error.message:'Native verification failed'});}});`; worker = spawn(process.execPath, ['--import', 'tsx', '--input-type=module', '-e', code], { cwd: new URL('../../../../', import.meta.url), stdio: ['ignore', 'ignore', 'pipe', 'ipc'], env: process.env, @@ -240,6 +251,15 @@ Reflect.apply(verifySdkGoldenProduct,undefined,[actors,${JSON.stringify(commit)} const preservedDrafts = structuredClone(deniedDrafts); writeFileSync(inputPath, inputBytes); expect((await observe()).stdout).toBe('verified'); expect(deniedDrafts).toEqual(preservedDrafts); expect(failed).toEqual(deniedReferences); expect(calls).toHaveLength(291); + const graphStart = graphCalls.length; + mode = 'exact'; expect((await Promise.all([observe(), observe()])).map(value => value.stdout)).toEqual(['verified', 'verified']); + for (const denial of [...graphPairFailures, 'denied', 'unavailable', 'interrupted', 'json']) { + mode = `graph-${denial}`; await expect.soft(observe(), mode).rejects.toThrow(); + } + const retainedGraphFailures = structuredClone(graphFailures); + mode = 'exact'; expect((await observe()).stdout).toBe('verified'); + expect(graphFailures).toEqual(retainedGraphFailures); expect(graphFailures).toHaveLength(graphPairFailures.length + 4); + expect(graphCalls.slice(graphStart)).toEqual(Array(graphPairFailures.length + 7).fill('GET /v1/teams/sdk-team/execution-graph')); for (const file of proposalReadback().original.referenceFiles) expect(referenceCalls.some(value => value.endsWith(` ${file.files[0]!.path}`))).toBe(true); expect({ release, review, proposal }).toEqual(original); diff --git a/tests/unit/operator-contracts/golden-product.test.ts b/tests/unit/operator-contracts/golden-product.test.ts index 5a0b06ba7..f3983dab5 100644 --- a/tests/unit/operator-contracts/golden-product.test.ts +++ b/tests/unit/operator-contracts/golden-product.test.ts @@ -3,7 +3,7 @@ import { readFileSync } from 'node:fs'; import { sdkGoldenSource, sdkGoldenLibraryFiles, verifySdkGoldenProposalReferences, verifySdkGoldenProduct as verifyProduct } from '../../acceptance/golden-product.ts'; import { stringify } from 'yaml'; import { createHash } from 'node:crypto'; -import { commit, release, review, proposal, proposalReadback } from './acceptance/golden-product-fixture.ts'; +import { commit, release, review, proposal, proposalReadback, graphPairFailures, mutateGraphPair } from './acceptance/golden-product-fixture.ts'; const verifySdkGoldenProduct = (items: Parameters[0], source: string) => Reflect.apply(verifyProduct, undefined, [items, source, proposalReadback().governance, proposalReadback().original]); describe('SDK golden product gate (fixtures are not acceptance)', () => { @@ -11,6 +11,16 @@ describe('SDK golden product gate (fixtures are not acceptance)', () => { const observed = proposalReadback(); const inspect = (value: typeof observed) => Reflect.apply(verifyProduct, undefined, [[release, review], commit, value.governance, value.original]); expect(() => inspect(observed)).not.toThrow(); + const unrelated = structuredClone(observed); + unrelated.original.graph.nodes.push({ ...structuredClone(unrelated.original.graph.nodes[0]!), id: 'unrelated-retained-actor', workdayId: 'another-workday' }); + const retainedUnrelated = structuredClone(unrelated); + expect(() => inspect(unrelated)).not.toThrow(); expect(unrelated).toEqual(retainedUnrelated); + for (const mode of graphPairFailures) { + const changed = structuredClone(observed); mutateGraphPair(changed.original.graph, mode); + const retained = structuredClone(changed); + expect.soft(() => inspect(changed), mode).toThrow('ACCEPTANCE_SDK_GRAPH_PAIRS'); + expect(changed).toEqual(retained); + } const changed = structuredClone(observed); changed.original.graph.nodes[1]!.workItemId = 'unrelated-authored-work'; const retained = structuredClone(changed);