@@ -1076,6 +1076,139 @@ it('emitted install.mjs mirrors the core replace policy: no-op, owned-only repla
10761076 }
10771077} , 60_000 ) ;
10781078
1079+ it ( 'emitted install.mjs refuses a foreign destination that lacks artifact-manifest paths' , async ( ) => {
1080+ const root = await mkdtemp ( join ( tmpdir ( ) , 'agent-bundle-foreign-manifest-' ) ) ;
1081+ const bundle = join ( root , 'bundle' ) ;
1082+ const home = join ( root , 'home' ) ;
1083+ const destination = join ( home , '.cursor' , 'plugins' , 'local' , 'install-fixture' ) ;
1084+ const installer = join ( bundle , 'install.mjs' ) ;
1085+ const foreignReceipt = join ( destination , '.plugin-library-install.json' ) ;
1086+ try {
1087+ const writes = writesFor ( 'cursor' ) ;
1088+ await mkdir ( join ( bundle , '.cursor-plugin' ) , { recursive : true } ) ;
1089+ await mkdir ( join ( destination , 'skills' ) , { recursive : true } ) ;
1090+ await Promise . all ( [
1091+ writeFile ( installer , writes . get ( 'install.mjs' ) ?? '' ) ,
1092+ writeFile ( join ( bundle , 'INSTALL.md' ) , writes . get ( 'INSTALL.md' ) ?? '' ) ,
1093+ writeFile ( join ( bundle , '.cursor-plugin' , 'plugin.json' ) , JSON . stringify ( { name : 'install-fixture' , version : '1.2.3' } ) ) ,
1094+ writeFile ( join ( bundle , 'payload.txt' ) , 'payload\n' ) ,
1095+ writeFile ( join ( bundle , 'agent-bundle.compile-evidence.json' ) , '{}\n' ) ,
1096+ writeFile ( join ( bundle , 'agent-bundle.manifest.json' ) , `${ JSON . stringify ( {
1097+ files : [ { path : 'agent-bundle.compile-evidence.json' } , { path : 'payload.txt' } ] ,
1098+ projections : [ { builtInHost : 'cursor' , documents : { plugin : '.cursor-plugin/plugin.json' } } ] ,
1099+ } ) } \n`) ,
1100+ writeFile ( foreignReceipt , '{ "installer": "plugin-library" }\n' ) ,
1101+ writeFile ( join ( destination , 'skills' , 'SKILL.md' ) , '# kept\n' ) ,
1102+ ] ) ;
1103+
1104+ const refused = await run ( installer , [ ] , home ) ;
1105+ expect ( refused . code ) . toBe ( 1 ) ;
1106+ expect ( refused . stderr ) . toContain ( 'Refusing foreign install' ) ;
1107+ expect ( refused . stderr ) . not . toContain ( 'ENOENT' ) ;
1108+ expect ( await readFile ( foreignReceipt , 'utf8' ) ) . toBe ( '{ "installer": "plugin-library" }\n' ) ;
1109+ expect ( await readFile ( join ( destination , 'skills' , 'SKILL.md' ) , 'utf8' ) ) . toBe ( '# kept\n' ) ;
1110+
1111+ const replaced = await run ( installer , [ '--replace' ] , home ) ;
1112+ expect ( replaced . code ) . toBe ( 1 ) ;
1113+ expect ( replaced . stderr ) . toContain ( 'Refusing foreign install' ) ;
1114+ expect ( replaced . stderr ) . toContain ( '--replace does not apply' ) ;
1115+ expect ( await readFile ( join ( destination , 'skills' , 'SKILL.md' ) , 'utf8' ) ) . toBe ( '# kept\n' ) ;
1116+
1117+ await rm ( join ( bundle , 'agent-bundle.compile-evidence.json' ) ) ;
1118+ const broken = await run ( installer , [ ] , home ) ;
1119+ expect ( broken . code ) . toBe ( 1 ) ;
1120+ expect ( broken . stderr ) . toContain ( 'bundle does not match its manifest: agent-bundle.compile-evidence.json is missing.' ) ;
1121+ expect ( broken . stderr ) . not . toContain ( 'lstat' ) ;
1122+ expect ( await readFile ( foreignReceipt , 'utf8' ) ) . toBe ( '{ "installer": "plugin-library" }\n' ) ;
1123+ } finally {
1124+ await rm ( root , { force : true , recursive : true } ) ;
1125+ }
1126+ } ) ;
1127+
1128+ it ( 'emitted install.mjs reruns a marketplace stage with unlisted files as already staged and refuses a newly declared path' , async ( ) => {
1129+ const root = await mkdtemp ( join ( tmpdir ( ) , 'agent-bundle-marketplace-restage-' ) ) ;
1130+ const bundle = join ( root , 'bundle' ) ;
1131+ const home = join ( root , 'home' ) ;
1132+ const installer = join ( bundle , 'install.mjs' ) ;
1133+ const stagedPlugin = join ( home , '.cursor' , 'agent-bundle' , 'marketplaces' , 'install-fixture' , 'plugins' , 'install-fixture' ) ;
1134+ const manifest = ( extra : readonly string [ ] ) => `${ JSON . stringify ( {
1135+ files : [ { path : 'payload.txt' } , ...extra . map ( ( path ) => ( { path } ) ) ] ,
1136+ projections : [ { builtInHost : 'cursor' , documents : { plugin : '.cursor-plugin/plugin.json' } } ] ,
1137+ } ) } \n`;
1138+ try {
1139+ const writes = writesFor ( 'cursor' ) ;
1140+ await mkdir ( join ( bundle , '.cursor-plugin' ) , { recursive : true } ) ;
1141+ await mkdir ( join ( home , '.cursor' ) , { recursive : true } ) ;
1142+ await Promise . all ( [
1143+ writeFile ( installer , writes . get ( 'install.mjs' ) ?? '' ) ,
1144+ writeFile ( join ( bundle , 'INSTALL.md' ) , writes . get ( 'INSTALL.md' ) ?? '' ) ,
1145+ writeFile ( join ( bundle , '.cursor-plugin' , 'plugin.json' ) , JSON . stringify ( { name : 'install-fixture' , version : '1.2.3' } ) ) ,
1146+ writeFile ( join ( bundle , 'payload.txt' ) , 'payload\n' ) ,
1147+ writeFile ( join ( bundle , 'package.json' ) , '{ "name": "install-fixture" }\n' ) ,
1148+ writeFile ( join ( bundle , 'agent-bundle.manifest.json' ) , manifest ( [ ] ) ) ,
1149+ ] ) ;
1150+
1151+ const staged = await run ( installer , [ '--mode' , 'marketplace' ] , home ) ;
1152+ expect ( staged ) . toMatchObject ( { code : 0 , stderr : '' } ) ;
1153+ expect ( staged . stdout ) . toContain ( 'Staged install-fixture@1.2.3' ) ;
1154+ expect ( await readFile ( join ( stagedPlugin , 'payload.txt' ) , 'utf8' ) ) . toBe ( 'payload\n' ) ;
1155+ const commit = / @ ( [ 0 - 9 a - f ] { 40 } ) / u. exec ( staged . stdout ) ?. [ 1 ] ;
1156+ expect ( commit ) . toMatch ( / ^ [ 0 - 9 a - f ] { 40 } $ / u) ;
1157+
1158+ const rerun = await run ( installer , [ '--mode' , 'marketplace' ] , home ) ;
1159+ expect ( rerun ) . toMatchObject ( { code : 0 , stderr : '' } ) ;
1160+ expect ( rerun . stdout ) . toContain ( 'Already staged install-fixture@1.2.3' ) ;
1161+ expect ( rerun . stdout ) . toContain ( `@ ${ commit } ` ) ;
1162+
1163+ await writeFile ( join ( bundle , 'extra.txt' ) , 'extra\n' ) ;
1164+ await writeFile ( join ( bundle , 'agent-bundle.manifest.json' ) , manifest ( [ 'extra.txt' ] ) ) ;
1165+ const restaged = await run ( installer , [ '--mode' , 'marketplace' ] , home ) ;
1166+ expect ( restaged . code ) . toBe ( 1 ) ;
1167+ expect ( restaged . stderr ) . toContain ( 'Refusing content collision' ) ;
1168+ expect ( restaged . stderr ) . not . toContain ( 'ENOENT' ) ;
1169+ expect ( await readFile ( join ( stagedPlugin , 'payload.txt' ) , 'utf8' ) ) . toBe ( 'payload\n' ) ;
1170+ await expect ( readFile ( join ( stagedPlugin , 'extra.txt' ) ) ) . rejects . toMatchObject ( { code : 'ENOENT' } ) ;
1171+ } finally {
1172+ await rm ( root , { force : true , recursive : true } ) ;
1173+ }
1174+ } ) ;
1175+
1176+ it ( 'emitted install.mjs --uninstall --force removes present files from a pre-receipt copy and keeps state/' , async ( ) => {
1177+ const root = await mkdtemp ( join ( tmpdir ( ) , 'agent-bundle-legacy-uninstall-' ) ) ;
1178+ const bundle = join ( root , 'bundle' ) ;
1179+ const home = join ( root , 'home' ) ;
1180+ const destination = join ( home , '.cursor' , 'plugins' , 'local' , 'install-fixture' ) ;
1181+ const installer = join ( bundle , 'install.mjs' ) ;
1182+ try {
1183+ const writes = writesFor ( 'cursor' ) ;
1184+ await mkdir ( join ( bundle , '.cursor-plugin' ) , { recursive : true } ) ;
1185+ await mkdir ( join ( destination , '.cursor-plugin' ) , { recursive : true } ) ;
1186+ await mkdir ( join ( destination , 'state' ) , { recursive : true } ) ;
1187+ await Promise . all ( [
1188+ writeFile ( installer , writes . get ( 'install.mjs' ) ?? '' ) ,
1189+ writeFile ( join ( bundle , 'INSTALL.md' ) , writes . get ( 'INSTALL.md' ) ?? '' ) ,
1190+ writeFile ( join ( bundle , '.cursor-plugin' , 'plugin.json' ) , JSON . stringify ( { name : 'install-fixture' , version : '1.2.3' } ) ) ,
1191+ writeFile ( join ( bundle , 'payload.txt' ) , 'payload\n' ) ,
1192+ writeFile ( join ( bundle , 'agent-bundle.manifest.json' ) , `${ JSON . stringify ( {
1193+ files : [ { path : 'payload.txt' } ] ,
1194+ projections : [ { builtInHost : 'cursor' , documents : { plugin : '.cursor-plugin/plugin.json' } } ] ,
1195+ } ) } \n`) ,
1196+ writeFile ( join ( destination , 'INSTALL.md' ) , 'legacy\n' ) ,
1197+ writeFile ( join ( destination , 'install.mjs' ) , 'legacy\n' ) ,
1198+ writeFile ( join ( destination , '.cursor-plugin' , 'plugin.json' ) , JSON . stringify ( { name : 'install-fixture' , version : '1.2.3' } ) ) ,
1199+ writeFile ( join ( destination , 'operator.txt' ) , 'operator\n' ) ,
1200+ writeFile ( join ( destination , 'state' , 'plugin.sqlite' ) , 'durable\n' ) ,
1201+ ] ) ;
1202+
1203+ const removed = await run ( installer , [ '--uninstall' , '--force' ] , home ) ;
1204+ expect ( removed ) . toMatchObject ( { code : 0 , stderr : '' } ) ;
1205+ await expect ( readFile ( join ( destination , 'operator.txt' ) ) ) . rejects . toMatchObject ( { code : 'ENOENT' } ) ;
1206+ expect ( await readFile ( join ( destination , 'state' , 'plugin.sqlite' ) , 'utf8' ) ) . toBe ( 'durable\n' ) ;
1207+ } finally {
1208+ await rm ( root , { force : true , recursive : true } ) ;
1209+ }
1210+ } ) ;
1211+
10791212it ( 'emitted install.mjs marks new explicit state roots and retains pre-existing ones' , async ( ) => {
10801213 const root = await mkdtemp ( join ( tmpdir ( ) , 'agent-bundle-state-ownership-mjs-' ) ) ;
10811214 const bundle = join ( root , 'bundle' ) ;
0 commit comments