diff --git a/sdks/node/src/plugins/managed-installation.ts b/sdks/node/src/plugins/managed-installation.ts index f4bbad3..f4e9525 100644 --- a/sdks/node/src/plugins/managed-installation.ts +++ b/sdks/node/src/plugins/managed-installation.ts @@ -67,6 +67,11 @@ export interface PreparedManagedInstallation { readonly transaction: PreparedManagedTransaction; } +interface RuntimeArtifactLocation { + readonly filePath: string; + readonly label: string; +} + export type { RenameFile }; export function samePath(left: string, right: string): boolean { @@ -145,6 +150,7 @@ function sameAgentId(left: unknown, right: string): boolean { async function validateRuntimeIdentity( paths: ManagedRuntimePaths, agentKey: string, + additionalArtifacts: readonly RuntimeArtifactLocation[] = [], ): Promise { if (!await inspectPhysicalDirectory(paths.runtimeRoot, 'Elydora runtime directory')) return; if (!await inspectPhysicalDirectory(paths.agentDirectory, 'Elydora agent runtime directory')) return; @@ -156,6 +162,10 @@ async function validateRuntimeIdentity( readPhysicalFile(path.join(paths.agentDirectory, 'chain-state.json'), 'Elydora chain state'), readPhysicalFile(path.join(paths.agentDirectory, 'status-cache.json'), 'Elydora status cache'), readPhysicalFile(path.join(paths.agentDirectory, 'error.log'), 'Elydora error log'), + ...additionalArtifacts.map((artifact) => readPhysicalFile( + artifact.filePath, + artifact.label, + )), ]); if (!config) { if (artifacts.some(Boolean)) { @@ -233,6 +243,15 @@ export async function prepareManagedInstallation( hookLocations: spec.hookSources, config: spec.config, }, guardScript, auditScript); + const runtimeFiles = (spec.runtimeFiles ?? []).map((runtimeFile) => ({ + runtimeFile, + filePath: runtimeFilePath(paths, runtimeFile.fileName), + })); + const runtimeArtifacts = runtimeFiles.map(({ runtimeFile, filePath }) => ({ + filePath, + label: runtimeFile.label, + })); + await validateRuntimeIdentity(paths, spec.agentKey, runtimeArtifacts); const changes = await Promise.all([ prepareManagedFileChange({ filePath: paths.guardPath, @@ -260,8 +279,8 @@ export async function prepareManagedInstallation( next: generateHookScript(spec.agentKey, spec.config.agentId, spec.auditOptions), mode: 0o700, }), - ...(spec.runtimeFiles ?? []).map((runtimeFile) => prepareManagedFileChange({ - filePath: runtimeFilePath(paths, runtimeFile.fileName), + ...runtimeFiles.map(({ runtimeFile, filePath }) => prepareManagedFileChange({ + filePath, label: runtimeFile.label, next: runtimeFile.source, mode: runtimeFile.mode, @@ -276,7 +295,7 @@ export async function prepareManagedInstallation( verifyExpectedSource: true, })), ]); - await validateRuntimeIdentity(paths, spec.agentKey); + await validateRuntimeIdentity(paths, spec.agentKey, runtimeArtifacts); return { paths, transaction: { diff --git a/sdks/node/test/augment-transaction.test.mjs b/sdks/node/test/augment-transaction.test.mjs index bbbcc8e..da3a132 100644 --- a/sdks/node/test/augment-transaction.test.mjs +++ b/sdks/node/test/augment-transaction.test.mjs @@ -197,6 +197,24 @@ test('Auggie rejects orphaned and mismatched runtime identities before writes', } }); + await t.test('orphaned wrapper', async () => { + const fixture = await createFixture(); + try { + await mkdir(fixture.agentDir, { recursive: true }); + await writeFile(fixture.guardWrapperPath, 'orphaned wrapper\n'); + const result = await fixture.install(); + assert.equal(result.code, 1); + assert.match(result.stderr, /identity cannot be verified/i); + assert.equal( + await readFile(fixture.guardWrapperPath, 'utf-8'), + 'orphaned wrapper\n', + ); + await assertMissing(fixture.settingsPath); + } finally { + await fixture.close(); + } + }); + await t.test('mismatched identity', async () => { const fixture = await createFixture(); try {