diff --git a/.changeset/sort-issuer-identities.md b/.changeset/sort-issuer-identities.md new file mode 100644 index 000000000..355213413 --- /dev/null +++ b/.changeset/sort-issuer-identities.md @@ -0,0 +1,5 @@ +--- +'agent-bundle': patch +--- + +Keep AB6005 issuer ordering stable across checkout paths. (#768) diff --git a/packages/agent-bundle/src/build/external-policy.ts b/packages/agent-bundle/src/build/external-policy.ts index b544e1627..e5b06b5a9 100644 --- a/packages/agent-bundle/src/build/external-policy.ts +++ b/packages/agent-bundle/src/build/external-policy.ts @@ -85,7 +85,7 @@ export const viewSelfContainmentDiagnostics = ( evidence.externals.map((external) => artifactDiagnostic( 'AB6005', `Compiled MCP App view ${JSON.stringify(asset)} ` - + keptExternalClause(external, external.issuers.map((issuer) => posixRelativeWhenInside(projectRoot, issuer))) + + keptExternalClause(external, external.issuers.map((issuer) => posixRelativeWhenInside(projectRoot, issuer)).sort()) + 'a view inlines every module it loads.', asset, )); diff --git a/packages/agent-bundle/src/build/rslib.ts b/packages/agent-bundle/src/build/rslib.ts index 8596a2cd1..f466c6992 100644 --- a/packages/agent-bundle/src/build/rslib.ts +++ b/packages/agent-bundle/src/build/rslib.ts @@ -781,7 +781,7 @@ export const compileResultOf = ( externals: Object.freeze(record.externals.map((external): ExternalIR => ({ asset, externalType: external.externalType, - issuers: external.issuers.map((issuer) => posixRelativeWhenInside(options.cwd, issuer)), + issuers: external.issuers.map((issuer) => posixRelativeWhenInside(options.cwd, issuer)).sort(), kind: classifyExternal(external, { asset, emittedAssets: options.emittedAssets }), request: external.request, userRequest: external.userRequest, diff --git a/packages/agent-bundle/tests/compile-stages.test.ts b/packages/agent-bundle/tests/compile-stages.test.ts index c9ac49c40..85cd0cca5 100644 --- a/packages/agent-bundle/tests/compile-stages.test.ts +++ b/packages/agent-bundle/tests/compile-stages.test.ts @@ -4,9 +4,10 @@ import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; +import type { CompilationEvidence } from '../src/build/compile-result.ts'; import { compileRslibSurfaces, settledRslibSurface } from '../src/build/compiler.ts'; import { generatedMetaModulePath, metaModuleSpecifier } from '../src/build/meta.ts'; -import { buildRslibSurfaces, entryLibId, type RslibEntry } from '../src/build/rslib.ts'; +import { buildRslibSurfaces, compileResultOf, entryLibId, type RslibEntry } from '../src/build/rslib.ts'; import { planCompileStages } from '../src/build/compile-stages.ts'; import type { AgentBundleMeta } from '../src/meta.ts'; @@ -107,6 +108,36 @@ const surfaceEntry = (name: string, outputRelativePath: string, source: string): sourceInputs: [source], }); +describe('compileResultOf', () => { + it('sorts issuer identities after making project paths relative', () => { + const project = join(tmpdir(), 'a-agent-bundle-project'); + const externalIssuer = join(tmpdir(), 'z-agent-bundle-source', 'terminal-capability.ts'); + const projectIssuer = join(project, 'src', 'scripts', 'pad.ts'); + const record: CompilationEvidence = { + compiler: 'agent-bundle-scripts-pad', + externals: [{ + externalType: 'global', + issuers: [projectIssuer, externalIssuer].sort(), + request: 'fs', + userRequest: 'node:fs', + }], + modules: [], + }; + + const result = compileResultOf(record, { + asset: { path: 'scripts/pad.mjs', sourceInputs: [projectIssuer] }, + cwd: project, + dependencyRoots: new Map(), + emittedAssets: new Set(['scripts/pad.mjs']), + }); + + expect(result.externals[0]?.issuers).toEqual([ + externalIssuer, + 'src/scripts/pad.ts', + ].sort()); + }); +}); + describe('buildRslibSurfaces', () => { it('lowers every surface of the root through one Rslib instance and hands each surface its own evidence', async () => { const outputRoot = await mkdtemp(join(tmpdir(), 'agent-bundle-compile-stages-')); diff --git a/packages/agent-bundle/tests/external-policy.test.ts b/packages/agent-bundle/tests/external-policy.test.ts index a9d19cab0..9734cfe3f 100644 --- a/packages/agent-bundle/tests/external-policy.test.ts +++ b/packages/agent-bundle/tests/external-policy.test.ts @@ -1,10 +1,13 @@ import { describe, expect, it } from '@rstest/core'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; -import type { CompileResult, ExternalIR } from '../src/build/compile-result.ts'; +import type { CompilationEvidence, CompileResult, ExternalIR } from '../src/build/compile-result.ts'; import { classifyExternal, externalizedSpecifiers, selfContainmentDiagnostics, + viewSelfContainmentDiagnostics, } from '../src/build/external-policy.ts'; const resultWith = (externals: readonly ExternalIR[]): CompileResult => ({ @@ -139,6 +142,28 @@ describe('selfContainmentDiagnostics', () => { }); }); +describe('viewSelfContainmentDiagnostics', () => { + it('sorts issuer identities after making project paths relative', () => { + const project = join(tmpdir(), 'a-agent-bundle-project'); + const externalIssuer = join(tmpdir(), 'z-agent-bundle-source', 'view-runtime.ts'); + const projectIssuer = join(project, 'views', 'status.tsx'); + const evidence: CompilationEvidence = { + compiler: 'agent-bundle-mcp-app-status', + externals: [{ + externalType: 'module', + issuers: [projectIssuer, externalIssuer].sort(), + request: 'react', + userRequest: 'react', + }], + modules: [], + }; + + expect(viewSelfContainmentDiagnostics(evidence, 'mcp-apps/status.html', project)[0]?.message).toBe( + `Compiled MCP App view "mcp-apps/status.html" keeps "react" external (module) from ${externalIssuer}, views/status.tsx; a view inlines every module it loads.`, + ); + }); +}); + describe('externalizedSpecifiers', () => { it('accepts builtins, disabled object entries, regular expressions, and functions', () => { expect(externalizedSpecifiers('fs')).toEqual([]);