@adea-ai/plugins 2.0.40 → 2.0.41
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/portable-catalog.js
CHANGED
|
@@ -139,6 +139,25 @@ export async function synchronizePortable(options) {
|
|
|
139
139
|
},
|
|
140
140
|
};
|
|
141
141
|
}
|
|
142
|
+
/**
|
|
143
|
+
* A single-line summary of why a release's package descriptor was refused.
|
|
144
|
+
*
|
|
145
|
+
* The schema's own failure is a structured issue list; the catalog's refusal
|
|
146
|
+
* contract is one typed line. The first issue is kept as the detail, because
|
|
147
|
+
* it names the field that was wrong.
|
|
148
|
+
*/
|
|
149
|
+
function describeProvenanceFailure(error) {
|
|
150
|
+
const issues = error.issues;
|
|
151
|
+
if (Array.isArray(issues) && issues.length > 0) {
|
|
152
|
+
const first = issues[0];
|
|
153
|
+
const path = Array.isArray(first.path) ? first.path.join('.') : '';
|
|
154
|
+
const message = typeof first.message === 'string' ? first.message : '';
|
|
155
|
+
return [path, message].filter(Boolean).join(': ').replace(/\s+/g, ' ').slice(0, 200);
|
|
156
|
+
}
|
|
157
|
+
return error instanceof Error
|
|
158
|
+
? error.message.replace(/\s+/g, ' ').slice(0, 200)
|
|
159
|
+
: 'the descriptor is not a package recipe';
|
|
160
|
+
}
|
|
142
161
|
export function verifyPortableCatalog(catalog, requirePortable = false) {
|
|
143
162
|
const { catalogId, ...body } = CatalogSchema.parse(catalog);
|
|
144
163
|
if (catalogId !== `catalog:${digest(body).slice(7)}`)
|
|
@@ -162,7 +181,18 @@ export function verifyPortableCatalog(catalog, requirePortable = false) {
|
|
|
162
181
|
throw new Error(`PORTABLE_RESYNC_REQUIRED: ${plugin.pluginId}`);
|
|
163
182
|
continue;
|
|
164
183
|
}
|
|
165
|
-
|
|
184
|
+
let pkg;
|
|
185
|
+
try {
|
|
186
|
+
pkg = verifyAgentPackage(descriptor);
|
|
187
|
+
}
|
|
188
|
+
catch (error) {
|
|
189
|
+
// A consumer imports a catalog it did not compile, so a malformed
|
|
190
|
+
// provenance descriptor is an IMPORT failure, and it must be refused
|
|
191
|
+
// the way every other failure here is refused: one typed, single-line
|
|
192
|
+
// reason that names the plugin. The schema's own multi-line issue
|
|
193
|
+
// dump is not a refusal a consumer can match on.
|
|
194
|
+
throw new Error(`PACKAGE_PROVENANCE_INVALID: ${plugin.pluginId} (${describeProvenanceFailure(error)})`, { cause: error });
|
|
195
|
+
}
|
|
166
196
|
if (release.contentResolution !== 'complete' ||
|
|
167
197
|
pkg.sourceDigest !== release.canonicalContentDigest)
|
|
168
198
|
throw new Error(`PACKAGE_PROVENANCE_MISMATCH: ${plugin.pluginId}`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"portable-catalog.js","sourceRoot":"","sources":["../src/portable-catalog.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"portable-catalog.js","sourceRoot":"","sources":["../src/portable-catalog.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adea-ai/plugins",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.41",
|
|
4
4
|
"description": "Deterministic compiler for AI agent plugin marketplaces: schemas, source parsing, provenance verification, per-harness materialization, and provenance-checked static artifacts.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agents",
|
|
@@ -270,3 +270,320 @@ describe('MCP binding preparation', () => {
|
|
|
270
270
|
}
|
|
271
271
|
})
|
|
272
272
|
})
|
|
273
|
+
|
|
274
|
+
/** A package built from an explicit file map, so every shape is explicit. */
|
|
275
|
+
function packageFrom(files: Record<string, string>) {
|
|
276
|
+
return compileAgentPackage({
|
|
277
|
+
files: new Map(
|
|
278
|
+
Object.entries(files).map(([path, text]) => [path, new TextEncoder().encode(text)])
|
|
279
|
+
),
|
|
280
|
+
name: 'issues',
|
|
281
|
+
})
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const MANIFEST = JSON.stringify({ $schema: PLUGIN_SCHEMA_ID, name: 'issues' })
|
|
285
|
+
const SKILL = '---\nname: issues\ndescription: Work with issues.\n---\nInstructions.'
|
|
286
|
+
const MCP_BOTH = JSON.stringify({
|
|
287
|
+
$schema: MCP_SCHEMA_ID,
|
|
288
|
+
mcpServers: {
|
|
289
|
+
api: { type: 'streamable-http', url: 'https://example.test/mcp' },
|
|
290
|
+
local: { type: 'stdio', command: 'node' },
|
|
291
|
+
},
|
|
292
|
+
})
|
|
293
|
+
|
|
294
|
+
/** The package shapes the compatibility matrix is exercised over. */
|
|
295
|
+
const PACKAGES = {
|
|
296
|
+
full: packageFrom({
|
|
297
|
+
'plugin.json': MANIFEST,
|
|
298
|
+
'skills/issues/SKILL.md': SKILL,
|
|
299
|
+
'mcp.json': MCP_BOTH,
|
|
300
|
+
}),
|
|
301
|
+
skillsOnly: packageFrom({
|
|
302
|
+
'plugin.json': MANIFEST,
|
|
303
|
+
'skills/issues/SKILL.md': SKILL,
|
|
304
|
+
}),
|
|
305
|
+
mcpOnly: packageFrom({
|
|
306
|
+
'plugin.json': MANIFEST,
|
|
307
|
+
'mcp.json': MCP_BOTH,
|
|
308
|
+
}),
|
|
309
|
+
withHooks: packageFrom({
|
|
310
|
+
'plugin.json': MANIFEST,
|
|
311
|
+
'skills/issues/SKILL.md': SKILL,
|
|
312
|
+
'mcp.json': MCP_BOTH,
|
|
313
|
+
'hooks/hooks.json': '{}',
|
|
314
|
+
}),
|
|
315
|
+
bare: packageFrom({ 'plugin.json': MANIFEST }),
|
|
316
|
+
} as const
|
|
317
|
+
|
|
318
|
+
/** The profile shapes the compatibility matrix is exercised over. */
|
|
319
|
+
const PROFILES = {
|
|
320
|
+
native: profile({
|
|
321
|
+
agentPlugins: {
|
|
322
|
+
versions: ['1.0.0'],
|
|
323
|
+
skills: true,
|
|
324
|
+
mcpTransports: ['stdio', 'streamable-http'],
|
|
325
|
+
},
|
|
326
|
+
components: { skillDirectories: true, mcpTransports: ['stdio', 'streamable-http'] },
|
|
327
|
+
}),
|
|
328
|
+
versionUnsupported: profile({
|
|
329
|
+
agentPlugins: {
|
|
330
|
+
versions: ['9.9.9'],
|
|
331
|
+
skills: true,
|
|
332
|
+
mcpTransports: ['stdio', 'streamable-http'],
|
|
333
|
+
},
|
|
334
|
+
components: { skillDirectories: true, mcpTransports: ['stdio', 'streamable-http'] },
|
|
335
|
+
}),
|
|
336
|
+
nativeSkillsOff: profile({
|
|
337
|
+
agentPlugins: {
|
|
338
|
+
versions: ['1.0.0'],
|
|
339
|
+
skills: false,
|
|
340
|
+
mcpTransports: ['stdio', 'streamable-http'],
|
|
341
|
+
},
|
|
342
|
+
components: { skillDirectories: true, mcpTransports: ['stdio', 'streamable-http'] },
|
|
343
|
+
}),
|
|
344
|
+
adapterStronger: profile({
|
|
345
|
+
agentPlugins: { versions: ['1.0.0'], skills: true, mcpTransports: ['stdio'] },
|
|
346
|
+
components: { skillDirectories: true, mcpTransports: ['stdio', 'streamable-http'] },
|
|
347
|
+
}),
|
|
348
|
+
nothingCovered: profile({
|
|
349
|
+
agentPlugins: { versions: ['1.0.0'], skills: false, mcpTransports: [] },
|
|
350
|
+
components: { skillDirectories: false, mcpTransports: [] },
|
|
351
|
+
}),
|
|
352
|
+
nativeWithoutHttp: profile({
|
|
353
|
+
agentPlugins: { versions: ['1.0.0'], skills: true, mcpTransports: ['stdio'] },
|
|
354
|
+
components: { skillDirectories: false, mcpTransports: [] },
|
|
355
|
+
}),
|
|
356
|
+
} as const
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Twelve compatibility CASES, chosen to cover the boundaries that matter.
|
|
360
|
+
*
|
|
361
|
+
* This is a curated set, NOT a Cartesian product of the package shapes and the
|
|
362
|
+
* profile shapes: the shapes multiply out to far more combinations than the
|
|
363
|
+
* contract distinguishes. Each case states the package shape, the profile
|
|
364
|
+
* shape, whether a partial adoption was allowed, and what the plan must then
|
|
365
|
+
* contain — the strategy, the compatibility, the selected skills and servers,
|
|
366
|
+
* and the COMPLETE set of disabled components.
|
|
367
|
+
*
|
|
368
|
+
* The expectations are the EXISTING contract: the native loader is used only
|
|
369
|
+
* when the profile supports the package version and the native path covers at
|
|
370
|
+
* least as much as the adapter; a partial plan is refused unless the caller
|
|
371
|
+
* opts in; a plan that covers nothing is unsupported.
|
|
372
|
+
*/
|
|
373
|
+
const MATRIX: {
|
|
374
|
+
name: string
|
|
375
|
+
pkg: keyof typeof PACKAGES
|
|
376
|
+
profile: keyof typeof PROFILES
|
|
377
|
+
allowPartial?: boolean
|
|
378
|
+
strategy: string
|
|
379
|
+
compatibility: string
|
|
380
|
+
skills: string[]
|
|
381
|
+
mcp: string[]
|
|
382
|
+
disabled: string[]
|
|
383
|
+
}[] = [
|
|
384
|
+
{
|
|
385
|
+
name: 'supported version, native covers everything',
|
|
386
|
+
pkg: 'full',
|
|
387
|
+
profile: 'native',
|
|
388
|
+
strategy: 'native-agent-plugin',
|
|
389
|
+
compatibility: 'full',
|
|
390
|
+
skills: ['skills/issues'],
|
|
391
|
+
mcp: ['api', 'local'],
|
|
392
|
+
disabled: [],
|
|
393
|
+
},
|
|
394
|
+
{
|
|
395
|
+
name: 'unsupported package version falls back to the component adapter',
|
|
396
|
+
pkg: 'full',
|
|
397
|
+
profile: 'versionUnsupported',
|
|
398
|
+
strategy: 'component-adapter',
|
|
399
|
+
compatibility: 'full',
|
|
400
|
+
skills: ['skills/issues'],
|
|
401
|
+
mcp: ['api', 'local'],
|
|
402
|
+
disabled: [],
|
|
403
|
+
},
|
|
404
|
+
{
|
|
405
|
+
name: 'a native loader without skills yields to the stronger adapter',
|
|
406
|
+
pkg: 'full',
|
|
407
|
+
profile: 'nativeSkillsOff',
|
|
408
|
+
strategy: 'component-adapter',
|
|
409
|
+
compatibility: 'full',
|
|
410
|
+
skills: ['skills/issues'],
|
|
411
|
+
mcp: ['api', 'local'],
|
|
412
|
+
disabled: [],
|
|
413
|
+
},
|
|
414
|
+
{
|
|
415
|
+
name: 'an adapter that covers more wins over a weaker native loader',
|
|
416
|
+
pkg: 'full',
|
|
417
|
+
profile: 'adapterStronger',
|
|
418
|
+
strategy: 'component-adapter',
|
|
419
|
+
compatibility: 'full',
|
|
420
|
+
skills: ['skills/issues'],
|
|
421
|
+
mcp: ['api', 'local'],
|
|
422
|
+
disabled: [],
|
|
423
|
+
},
|
|
424
|
+
{
|
|
425
|
+
name: 'a profile that covers nothing is unsupported',
|
|
426
|
+
pkg: 'full',
|
|
427
|
+
profile: 'nothingCovered',
|
|
428
|
+
strategy: 'unavailable',
|
|
429
|
+
compatibility: 'unsupported',
|
|
430
|
+
skills: [],
|
|
431
|
+
mcp: [],
|
|
432
|
+
disabled: ['skill:issues', 'mcp:api', 'mcp:local'],
|
|
433
|
+
},
|
|
434
|
+
{
|
|
435
|
+
name: 'a transport the native loader lacks is disabled, and a partial plan is refused',
|
|
436
|
+
pkg: 'full',
|
|
437
|
+
profile: 'nativeWithoutHttp',
|
|
438
|
+
strategy: 'unavailable',
|
|
439
|
+
compatibility: 'partial',
|
|
440
|
+
skills: [],
|
|
441
|
+
mcp: [],
|
|
442
|
+
disabled: ['mcp:api'],
|
|
443
|
+
},
|
|
444
|
+
{
|
|
445
|
+
name: 'the same package is adopted partially when the caller opts in',
|
|
446
|
+
pkg: 'full',
|
|
447
|
+
profile: 'nativeWithoutHttp',
|
|
448
|
+
allowPartial: true,
|
|
449
|
+
strategy: 'native-agent-plugin',
|
|
450
|
+
compatibility: 'partial',
|
|
451
|
+
skills: ['skills/issues'],
|
|
452
|
+
mcp: ['local'],
|
|
453
|
+
disabled: ['mcp:api'],
|
|
454
|
+
},
|
|
455
|
+
{
|
|
456
|
+
name: 'a skills-only package plans natively',
|
|
457
|
+
pkg: 'skillsOnly',
|
|
458
|
+
profile: 'native',
|
|
459
|
+
strategy: 'native-agent-plugin',
|
|
460
|
+
compatibility: 'full',
|
|
461
|
+
skills: ['skills/issues'],
|
|
462
|
+
mcp: [],
|
|
463
|
+
disabled: [],
|
|
464
|
+
},
|
|
465
|
+
{
|
|
466
|
+
name: 'an mcp-only package plans natively',
|
|
467
|
+
pkg: 'mcpOnly',
|
|
468
|
+
profile: 'native',
|
|
469
|
+
strategy: 'native-agent-plugin',
|
|
470
|
+
compatibility: 'full',
|
|
471
|
+
skills: [],
|
|
472
|
+
mcp: ['api', 'local'],
|
|
473
|
+
disabled: [],
|
|
474
|
+
},
|
|
475
|
+
{
|
|
476
|
+
name: 'a non-portable extra keeps the package off the native loader',
|
|
477
|
+
pkg: 'withHooks',
|
|
478
|
+
profile: 'native',
|
|
479
|
+
strategy: 'unavailable',
|
|
480
|
+
compatibility: 'partial',
|
|
481
|
+
skills: [],
|
|
482
|
+
mcp: [],
|
|
483
|
+
disabled: ['hooks'],
|
|
484
|
+
},
|
|
485
|
+
{
|
|
486
|
+
name: 'the same package is adopted partially when the caller opts in',
|
|
487
|
+
pkg: 'withHooks',
|
|
488
|
+
profile: 'native',
|
|
489
|
+
allowPartial: true,
|
|
490
|
+
strategy: 'component-adapter',
|
|
491
|
+
compatibility: 'partial',
|
|
492
|
+
skills: ['skills/issues'],
|
|
493
|
+
mcp: ['api', 'local'],
|
|
494
|
+
disabled: ['hooks'],
|
|
495
|
+
},
|
|
496
|
+
{
|
|
497
|
+
name: 'a package with no skills and no servers is unsupported',
|
|
498
|
+
pkg: 'bare',
|
|
499
|
+
profile: 'native',
|
|
500
|
+
strategy: 'unavailable',
|
|
501
|
+
compatibility: 'unsupported',
|
|
502
|
+
skills: [],
|
|
503
|
+
mcp: [],
|
|
504
|
+
disabled: [],
|
|
505
|
+
},
|
|
506
|
+
]
|
|
507
|
+
|
|
508
|
+
describe('the compatibility cases across package and profile capabilities', () => {
|
|
509
|
+
for (const row of MATRIX) {
|
|
510
|
+
it(row.name, () => {
|
|
511
|
+
const pkg = PACKAGES[row.pkg]
|
|
512
|
+
const plan = createInstallationPlan({
|
|
513
|
+
pluginId: 'plugin:test:issues',
|
|
514
|
+
releaseId: `release:${'a'.repeat(64)}`,
|
|
515
|
+
instanceId: 'workspace-a/user-a',
|
|
516
|
+
source: {
|
|
517
|
+
repositoryUrl: 'https://github.com/example/issues',
|
|
518
|
+
commitSha: 'b'.repeat(40),
|
|
519
|
+
pluginSubdirectory: '.',
|
|
520
|
+
contentDigest: pkg.sourceDigest,
|
|
521
|
+
},
|
|
522
|
+
package: pkg,
|
|
523
|
+
profile: PROFILES[row.profile],
|
|
524
|
+
...(row.allowPartial === undefined ? {} : { allowPartial: row.allowPartial }),
|
|
525
|
+
})
|
|
526
|
+
assert.equal(plan.strategy, row.strategy)
|
|
527
|
+
assert.equal(plan.compatibility, row.compatibility)
|
|
528
|
+
assert.deepEqual(plan.selection.skillDirectories, row.skills)
|
|
529
|
+
assert.deepEqual(Object.keys(plan.selection.mcpServers).toSorted(), row.mcp.toSorted())
|
|
530
|
+
// The COMPLETE set of disabled components, not a subset.
|
|
531
|
+
assert.deepEqual(
|
|
532
|
+
plan.disabled.map((entry) => entry.component).toSorted(),
|
|
533
|
+
row.disabled.toSorted()
|
|
534
|
+
)
|
|
535
|
+
// A refused plan selects nothing, and no plan ever activates by itself.
|
|
536
|
+
assert.equal(plan.allowedToActivate, false)
|
|
537
|
+
assert.equal(plan.approvalRequired, true)
|
|
538
|
+
if (row.strategy === 'unavailable') {
|
|
539
|
+
assert.deepEqual(plan.selection.skillDirectories, [])
|
|
540
|
+
assert.deepEqual(plan.selection.mcpServers, {})
|
|
541
|
+
}
|
|
542
|
+
})
|
|
543
|
+
}
|
|
544
|
+
})
|
|
545
|
+
|
|
546
|
+
describe('upgrade identity isolation', () => {
|
|
547
|
+
it('isolates instances and plugins, and pins the package identity across upgrades', () => {
|
|
548
|
+
const base = input()
|
|
549
|
+
const first = createInstallationPlan(base)
|
|
550
|
+
|
|
551
|
+
// A distinct instance of the same plugin owns its own data.
|
|
552
|
+
assert.notEqual(
|
|
553
|
+
first.dataKey,
|
|
554
|
+
createInstallationPlan({ ...base, instanceId: 'workspace-b/user-a' }).dataKey
|
|
555
|
+
)
|
|
556
|
+
// A distinct plugin under the same instance id owns its own data.
|
|
557
|
+
assert.notEqual(
|
|
558
|
+
first.dataKey,
|
|
559
|
+
createInstallationPlan({ ...base, pluginId: 'plugin:other:issues' }).dataKey
|
|
560
|
+
)
|
|
561
|
+
|
|
562
|
+
// An upgraded package (new bytes) is a different PACKAGE, under the same
|
|
563
|
+
// instance data: the data is preserved across the upgrade.
|
|
564
|
+
const upgraded = createInstallationPlan({
|
|
565
|
+
...input(packageFixture({ 'CHANGELOG.md': 'A new release.' })),
|
|
566
|
+
releaseId: `release:${'c'.repeat(64)}`,
|
|
567
|
+
})
|
|
568
|
+
assert.notEqual(first.packageKey, upgraded.packageKey)
|
|
569
|
+
assert.equal(first.dataKey, upgraded.dataKey)
|
|
570
|
+
assert.equal(upgraded.preserveDataAcrossUpdates, true)
|
|
571
|
+
|
|
572
|
+
// A new release id over the SAME bytes is the same package identity: the
|
|
573
|
+
// package key pins the source and the package bytes, and the release is
|
|
574
|
+
// deliberately excluded from the data key.
|
|
575
|
+
const rereleased = createInstallationPlan({ ...base, releaseId: `release:${'d'.repeat(64)}` })
|
|
576
|
+
assert.equal(first.packageKey, rereleased.packageKey)
|
|
577
|
+
assert.equal(first.dataKey, rereleased.dataKey)
|
|
578
|
+
assert.notEqual(first.releaseId, rereleased.releaseId)
|
|
579
|
+
|
|
580
|
+
// A different source commit is a different package identity under the same
|
|
581
|
+
// instance data.
|
|
582
|
+
const otherCommit = createInstallationPlan({
|
|
583
|
+
...base,
|
|
584
|
+
source: { ...base.source, commitSha: 'c'.repeat(40) },
|
|
585
|
+
})
|
|
586
|
+
assert.notEqual(first.packageKey, otherCommit.packageKey)
|
|
587
|
+
assert.equal(first.dataKey, otherCommit.dataKey)
|
|
588
|
+
})
|
|
589
|
+
})
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { it } from 'node:test'
|
|
2
2
|
import assert from 'node:assert/strict'
|
|
3
|
-
import {
|
|
3
|
+
import { existsSync, lstatSync, realpathSync } from 'node:fs'
|
|
4
|
+
import { writeFile, mkdir, readFile, readdir, mkdtemp, symlink, rm, chmod } from 'node:fs/promises'
|
|
4
5
|
import { join } from 'node:path'
|
|
6
|
+
import { tmpdir } from 'node:os'
|
|
5
7
|
import { synchronize, verifyArtifacts, digest } from './index.js'
|
|
6
8
|
import {
|
|
7
9
|
synchronizePortable,
|
|
@@ -9,8 +11,17 @@ import {
|
|
|
9
11
|
PACKAGE_METADATA_KEY,
|
|
10
12
|
} from './portable-catalog.js'
|
|
11
13
|
import { verifyAgentPackage, materializeAgentPackage } from './agent-plugins-catalog.js'
|
|
14
|
+
import { createCatalogInstallationPlan, createInstallationPlan } from './agent-plugins-harness.js'
|
|
15
|
+
import type { HarnessProfile } from './agent-plugins-schema.js'
|
|
12
16
|
import { snapshotFromDirectory } from './index.js'
|
|
13
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
copyDependency,
|
|
19
|
+
nodeCandidates,
|
|
20
|
+
packageLifecycleScripts,
|
|
21
|
+
selectPinnedNode,
|
|
22
|
+
withPackedConsumerFixture,
|
|
23
|
+
withPortableFixture,
|
|
24
|
+
} from '../test-support/portable-fixture.js'
|
|
14
25
|
|
|
15
26
|
it('normalizes a real offline catalog and preserves source/release identity', () =>
|
|
16
27
|
withPortableFixture(async (input, root) => {
|
|
@@ -184,3 +195,403 @@ it('advertises where it is published once the publication is known', () =>
|
|
|
184
195
|
// The fixture plugin ships no mark, so the map is present and empty.
|
|
185
196
|
assert.deepEqual(navigation.brandMarks, {})
|
|
186
197
|
}))
|
|
198
|
+
|
|
199
|
+
/** The capability profile a packed consumer presents to the planner. */
|
|
200
|
+
function harnessProfile(overrides: Partial<HarnessProfile> = {}): HarnessProfile {
|
|
201
|
+
return {
|
|
202
|
+
profileVersion: 1,
|
|
203
|
+
harness: 'example-harness',
|
|
204
|
+
runtimeVersion: 'fixture-1',
|
|
205
|
+
adapterVersion: 'fixture-1',
|
|
206
|
+
agentPlugins: {
|
|
207
|
+
versions: ['1.0.0'],
|
|
208
|
+
skills: true,
|
|
209
|
+
mcpTransports: ['stdio'],
|
|
210
|
+
},
|
|
211
|
+
components: { skillDirectories: true, mcpTransports: ['stdio'] },
|
|
212
|
+
...overrides,
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
it('gives a packed consumer the same installation plan as the direct package path', () =>
|
|
217
|
+
withPortableFixture(async (input) => {
|
|
218
|
+
const result = await synchronizePortable(input)
|
|
219
|
+
const catalog = result.catalog!
|
|
220
|
+
const plugin = catalog.plugins[0]!
|
|
221
|
+
const release = plugin.availableReleases[0]!
|
|
222
|
+
const fromCatalog = createCatalogInstallationPlan({
|
|
223
|
+
catalog,
|
|
224
|
+
pluginId: plugin.pluginId,
|
|
225
|
+
instanceId: 'workspace-a/user-a',
|
|
226
|
+
profile: harnessProfile(),
|
|
227
|
+
})
|
|
228
|
+
const direct = createInstallationPlan({
|
|
229
|
+
pluginId: plugin.pluginId,
|
|
230
|
+
releaseId: release.releaseId,
|
|
231
|
+
instanceId: 'workspace-a/user-a',
|
|
232
|
+
profile: harnessProfile(),
|
|
233
|
+
package: release.releaseMetadata[PACKAGE_METADATA_KEY],
|
|
234
|
+
source: {
|
|
235
|
+
repositoryUrl: release.resolvedRepositoryUrl,
|
|
236
|
+
commitSha: release.resolvedCommitSha,
|
|
237
|
+
pluginSubdirectory: release.pluginSubdirectory,
|
|
238
|
+
contentDigest: release.canonicalContentDigest,
|
|
239
|
+
},
|
|
240
|
+
requiredCredentials: release.requiredCredentials,
|
|
241
|
+
requiredConnectors: release.requiredConnectors,
|
|
242
|
+
})
|
|
243
|
+
// The import path and the direct path must plan the SAME installation.
|
|
244
|
+
assert.deepEqual(fromCatalog, direct)
|
|
245
|
+
// The plan is deterministic: the same catalog and profile serialize the
|
|
246
|
+
// same bytes, every time.
|
|
247
|
+
const again = createCatalogInstallationPlan({
|
|
248
|
+
catalog,
|
|
249
|
+
pluginId: plugin.pluginId,
|
|
250
|
+
instanceId: 'workspace-a/user-a',
|
|
251
|
+
profile: harnessProfile(),
|
|
252
|
+
})
|
|
253
|
+
assert.equal(JSON.stringify(again), JSON.stringify(fromCatalog))
|
|
254
|
+
// The upgrade identity: the instance data key is stable and the package
|
|
255
|
+
// key is pinned to the exact source and package bytes.
|
|
256
|
+
assert.equal(fromCatalog.dataKey, direct.dataKey)
|
|
257
|
+
assert.ok(fromCatalog.packageKey.startsWith('packages/'))
|
|
258
|
+
}))
|
|
259
|
+
|
|
260
|
+
it('selects the component adapter when the profile does not support the package version', () =>
|
|
261
|
+
withPortableFixture(async (input) => {
|
|
262
|
+
const result = await synchronizePortable(input)
|
|
263
|
+
const catalog = result.catalog!
|
|
264
|
+
const plugin = catalog.plugins[0]!
|
|
265
|
+
const release = plugin.availableReleases[0]!
|
|
266
|
+
const plan = createInstallationPlan({
|
|
267
|
+
pluginId: plugin.pluginId,
|
|
268
|
+
releaseId: release.releaseId,
|
|
269
|
+
instanceId: 'workspace-a/user-a',
|
|
270
|
+
profile: harnessProfile({
|
|
271
|
+
agentPlugins: { versions: ['9.9.9'], skills: true, mcpTransports: ['stdio'] },
|
|
272
|
+
}),
|
|
273
|
+
package: release.releaseMetadata[PACKAGE_METADATA_KEY],
|
|
274
|
+
source: {
|
|
275
|
+
repositoryUrl: release.resolvedRepositoryUrl,
|
|
276
|
+
commitSha: release.resolvedCommitSha,
|
|
277
|
+
pluginSubdirectory: release.pluginSubdirectory,
|
|
278
|
+
contentDigest: release.canonicalContentDigest,
|
|
279
|
+
},
|
|
280
|
+
})
|
|
281
|
+
// The package version is not supported natively, so the adapter plans it.
|
|
282
|
+
assert.equal(plan.strategy, 'component-adapter')
|
|
283
|
+
assert.equal(plan.compatibility, 'full')
|
|
284
|
+
// The same package on a profile that supports its version goes native.
|
|
285
|
+
const native = createInstallationPlan({
|
|
286
|
+
pluginId: plugin.pluginId,
|
|
287
|
+
releaseId: release.releaseId,
|
|
288
|
+
instanceId: 'workspace-a/user-a',
|
|
289
|
+
profile: harnessProfile(),
|
|
290
|
+
package: release.releaseMetadata[PACKAGE_METADATA_KEY],
|
|
291
|
+
source: {
|
|
292
|
+
repositoryUrl: release.resolvedRepositoryUrl,
|
|
293
|
+
commitSha: release.resolvedCommitSha,
|
|
294
|
+
pluginSubdirectory: release.pluginSubdirectory,
|
|
295
|
+
contentDigest: release.canonicalContentDigest,
|
|
296
|
+
},
|
|
297
|
+
})
|
|
298
|
+
assert.equal(native.strategy, 'native-agent-plugin')
|
|
299
|
+
}))
|
|
300
|
+
|
|
301
|
+
it('refuses a malformed provenance descriptor with one typed line naming the plugin', () =>
|
|
302
|
+
withPortableFixture(async (input) => {
|
|
303
|
+
const result = await synchronizePortable(input)
|
|
304
|
+
const catalog = structuredClone(result.catalog!)
|
|
305
|
+
const release = catalog.plugins[0]!.availableReleases[0]!
|
|
306
|
+
release.releaseMetadata[PACKAGE_METADATA_KEY] = { normalizerVersion: 1, files: 'not-an-array' }
|
|
307
|
+
const { catalogId: _oldId, ...body } = catalog
|
|
308
|
+
catalog.catalogId = `catalog:${digest(body).slice(7)}`
|
|
309
|
+
assert.throws(
|
|
310
|
+
() => verifyPortableCatalog(catalog),
|
|
311
|
+
(error: Error) => {
|
|
312
|
+
// One typed line, naming the plugin — the catalog's refusal contract.
|
|
313
|
+
assert.match(
|
|
314
|
+
error.message,
|
|
315
|
+
/^PACKAGE_PROVENANCE_INVALID: plugin:test-source:review-kit \(.+\)$/
|
|
316
|
+
)
|
|
317
|
+
assert.equal(error.message.includes('\n'), false)
|
|
318
|
+
return true
|
|
319
|
+
}
|
|
320
|
+
)
|
|
321
|
+
// The packed consumer's import path refuses it the same way.
|
|
322
|
+
assert.throws(
|
|
323
|
+
() =>
|
|
324
|
+
createCatalogInstallationPlan({
|
|
325
|
+
catalog,
|
|
326
|
+
pluginId: catalog.plugins[0]!.pluginId,
|
|
327
|
+
instanceId: 'workspace-a/user-a',
|
|
328
|
+
profile: harnessProfile(),
|
|
329
|
+
}),
|
|
330
|
+
/PACKAGE_PROVENANCE_INVALID: plugin:test-source:review-kit/
|
|
331
|
+
)
|
|
332
|
+
}))
|
|
333
|
+
|
|
334
|
+
const CONSUMER_SCRIPT = `import { readFileSync } from 'node:fs'
|
|
335
|
+
import { createCatalogInstallationPlan } from '@adea-ai/plugins/agent-plugins/harness'
|
|
336
|
+
import { synchronizePortable } from '@adea-ai/plugins/portable-catalog'
|
|
337
|
+
import { digest } from '@adea-ai/plugins'
|
|
338
|
+
const request = JSON.parse(readFileSync(process.argv[2], 'utf8'))
|
|
339
|
+
const result = await synchronizePortable(request.input)
|
|
340
|
+
let catalog = result.catalog
|
|
341
|
+
if (request.mode === 'malformed') {
|
|
342
|
+
const copy = structuredClone(catalog)
|
|
343
|
+
const release = copy.plugins[0].availableReleases[0]
|
|
344
|
+
release.releaseMetadata.agentPlugins = { normalizerVersion: 1, files: 'not-an-array' }
|
|
345
|
+
const { catalogId: _oldId, ...body } = copy
|
|
346
|
+
copy.catalogId = 'catalog:' + digest(body).slice(7)
|
|
347
|
+
catalog = copy
|
|
348
|
+
}
|
|
349
|
+
try {
|
|
350
|
+
const plan = createCatalogInstallationPlan({
|
|
351
|
+
catalog,
|
|
352
|
+
pluginId: catalog.plugins[0].pluginId,
|
|
353
|
+
instanceId: request.instanceId,
|
|
354
|
+
profile: request.profile,
|
|
355
|
+
})
|
|
356
|
+
process.stdout.write(
|
|
357
|
+
JSON.stringify({
|
|
358
|
+
ok: true,
|
|
359
|
+
plan,
|
|
360
|
+
catalogId: result.catalog.catalogId,
|
|
361
|
+
resolvedHarness: import.meta.resolve('@adea-ai/plugins/agent-plugins/harness'),
|
|
362
|
+
resolvedCatalog: import.meta.resolve('@adea-ai/plugins/portable-catalog'),
|
|
363
|
+
})
|
|
364
|
+
)
|
|
365
|
+
} catch (error) {
|
|
366
|
+
process.stdout.write(
|
|
367
|
+
JSON.stringify({ ok: false, error: String(error && error.message ? error.message : error) })
|
|
368
|
+
)
|
|
369
|
+
}
|
|
370
|
+
`
|
|
371
|
+
|
|
372
|
+
it('gives a PACKED consumer the same installation plan as the workspace path', () =>
|
|
373
|
+
withPackedConsumerFixture(async (consumer) => {
|
|
374
|
+
const source = await synchronizePortable(consumer.input)
|
|
375
|
+
assert.ok(source.catalog)
|
|
376
|
+
const sourcePlan = createCatalogInstallationPlan({
|
|
377
|
+
catalog: source.catalog,
|
|
378
|
+
pluginId: source.catalog.plugins[0]!.pluginId,
|
|
379
|
+
instanceId: 'workspace-a/user-a',
|
|
380
|
+
profile: harnessProfile(),
|
|
381
|
+
})
|
|
382
|
+
|
|
383
|
+
// The consumer imports the package by its PUBLIC specifiers, from its own
|
|
384
|
+
// directory, and compiles and plans from the packed artifact alone.
|
|
385
|
+
await writeFile(join(consumer.consumerRoot, 'consume.mjs'), CONSUMER_SCRIPT)
|
|
386
|
+
const requestPath = join(consumer.consumerRoot, 'request.json')
|
|
387
|
+
await writeFile(
|
|
388
|
+
requestPath,
|
|
389
|
+
JSON.stringify({
|
|
390
|
+
input: consumer.input,
|
|
391
|
+
profile: harnessProfile(),
|
|
392
|
+
instanceId: 'workspace-a/user-a',
|
|
393
|
+
})
|
|
394
|
+
)
|
|
395
|
+
const underNode = consumer.runUnderNode(join(consumer.consumerRoot, 'consume.mjs'), requestPath)
|
|
396
|
+
assert.equal(underNode.status, 0, underNode.stderr)
|
|
397
|
+
const nodeResult = JSON.parse(underNode.stdout) as {
|
|
398
|
+
ok: boolean
|
|
399
|
+
plan: typeof sourcePlan
|
|
400
|
+
catalogId: string
|
|
401
|
+
resolvedHarness: string
|
|
402
|
+
resolvedCatalog: string
|
|
403
|
+
}
|
|
404
|
+
assert.equal(nodeResult.ok, true)
|
|
405
|
+
// The compiler output is identical across the packaging boundary.
|
|
406
|
+
assert.equal(nodeResult.catalogId, source.catalog.catalogId)
|
|
407
|
+
// The import path and the workspace path plan the SAME installation, and
|
|
408
|
+
// the independently constructed plan serializes to the same bytes: the
|
|
409
|
+
// determinism claim is about the artifact and about two independent
|
|
410
|
+
// constructions, not about one in-memory object.
|
|
411
|
+
assert.deepEqual(nodeResult.plan, sourcePlan)
|
|
412
|
+
assert.equal(JSON.stringify(nodeResult.plan), JSON.stringify(sourcePlan))
|
|
413
|
+
// And the plan carries the real content, not an empty one.
|
|
414
|
+
assert.equal(nodeResult.plan.strategy, 'native-agent-plugin')
|
|
415
|
+
assert.equal(nodeResult.plan.compatibility, 'full')
|
|
416
|
+
assert.deepEqual(nodeResult.plan.selection.skillDirectories, ['skills/review'])
|
|
417
|
+
assert.deepEqual(Object.keys(nodeResult.plan.selection.mcpServers), ['local'])
|
|
418
|
+
assert.equal(nodeResult.plan.allowedToActivate, false)
|
|
419
|
+
assert.equal(nodeResult.plan.approvalRequired, true)
|
|
420
|
+
// The compiled artifact was reached through the PUBLIC specifier: the
|
|
421
|
+
// resolved module lives in the packed package's dist, not in the
|
|
422
|
+
// workspace source.
|
|
423
|
+
assert.ok(nodeResult.resolvedHarness.startsWith('file://' + consumer.consumerRoot))
|
|
424
|
+
assert.ok(nodeResult.resolvedHarness.includes('@adea-ai/plugins'))
|
|
425
|
+
assert.ok(nodeResult.resolvedHarness.includes('/dist/'))
|
|
426
|
+
assert.ok(nodeResult.resolvedCatalog.startsWith('file://' + consumer.consumerRoot))
|
|
427
|
+
assert.ok(nodeResult.resolvedCatalog.includes('@adea-ai/plugins'))
|
|
428
|
+
assert.ok(nodeResult.resolvedCatalog.includes('/dist/'))
|
|
429
|
+
|
|
430
|
+
// Bun is qualified separately: the package's bun export condition
|
|
431
|
+
// intentionally selects src/*.ts, so it exercises the source condition and
|
|
432
|
+
// must still reach the same plan.
|
|
433
|
+
const underBun = consumer.runUnderBun(join(consumer.consumerRoot, 'consume.mjs'), requestPath)
|
|
434
|
+
assert.equal(underBun.status, 0, underBun.stderr)
|
|
435
|
+
const bunResult = JSON.parse(underBun.stdout) as {
|
|
436
|
+
ok: boolean
|
|
437
|
+
plan: typeof sourcePlan
|
|
438
|
+
resolvedHarness: string
|
|
439
|
+
resolvedCatalog: string
|
|
440
|
+
}
|
|
441
|
+
assert.equal(bunResult.ok, true)
|
|
442
|
+
assert.deepEqual(bunResult.plan, sourcePlan)
|
|
443
|
+
assert.ok(bunResult.resolvedHarness.startsWith('file://' + consumer.consumerRoot))
|
|
444
|
+
assert.ok(bunResult.resolvedHarness.includes('@adea-ai/plugins'))
|
|
445
|
+
assert.ok(bunResult.resolvedHarness.includes('/src/'))
|
|
446
|
+
assert.ok(bunResult.resolvedCatalog.startsWith('file://' + consumer.consumerRoot))
|
|
447
|
+
assert.ok(bunResult.resolvedCatalog.includes('@adea-ai/plugins'))
|
|
448
|
+
assert.ok(bunResult.resolvedCatalog.includes('/src/'))
|
|
449
|
+
}))
|
|
450
|
+
|
|
451
|
+
it('keeps the packed consumer inside the packed artifact and its own dependencies', () =>
|
|
452
|
+
withPackedConsumerFixture(async (consumer) => {
|
|
453
|
+
// The packed artifact was built from the CURRENT source: a stale build
|
|
454
|
+
// would still carry the sentinel the rebuild removes.
|
|
455
|
+
assert.equal(consumer.builtFromCurrentSource, true)
|
|
456
|
+
assert.equal(existsSync(join(consumer.packedRoot, 'dist', 'agent-plugins-harness.js')), true)
|
|
457
|
+
// Every copied dependency really resolves INSIDE the consumer: the
|
|
458
|
+
// workspace installs them as links into the package manager's store, so
|
|
459
|
+
// a copy that kept the link would resolve outside the consumer.
|
|
460
|
+
const consumerRoot = realpathSync(consumer.consumerRoot)
|
|
461
|
+
for (const real of consumer.dependencyRealpaths)
|
|
462
|
+
assert.ok(real.startsWith(consumerRoot + '/'), `${real} resolves outside the consumer`)
|
|
463
|
+
assert.ok(consumer.packageRealpath.startsWith(consumerRoot + '/'))
|
|
464
|
+
// The consumer's dependency closure is the PACKED manifest's own
|
|
465
|
+
// dependencies, and never the whole workspace.
|
|
466
|
+
const packedManifest = JSON.parse(
|
|
467
|
+
await readFile(join(consumer.packedRoot, 'package.json'), 'utf8')
|
|
468
|
+
) as { dependencies?: Record<string, string> }
|
|
469
|
+
const declared = Object.keys(packedManifest.dependencies ?? {})
|
|
470
|
+
for (const name of declared) assert.ok(consumer.linkedDependencies.includes(name))
|
|
471
|
+
const installed = await readdir(join(consumer.consumerRoot, 'node_modules'))
|
|
472
|
+
for (const name of installed) {
|
|
473
|
+
if (name.startsWith('@')) continue
|
|
474
|
+
assert.ok(
|
|
475
|
+
consumer.linkedDependencies.includes(name),
|
|
476
|
+
`the consumer must not resolve ${name}: it is not a declared dependency`
|
|
477
|
+
)
|
|
478
|
+
}
|
|
479
|
+
// The packing step cannot build: the package declares no lifecycle, and
|
|
480
|
+
// the workspace prepare only wires git hooks.
|
|
481
|
+
const packageManifest = JSON.parse(
|
|
482
|
+
await readFile(join(consumer.packedRoot, 'package.json'), 'utf8')
|
|
483
|
+
) as { scripts?: Record<string, string> }
|
|
484
|
+
const lifecycle = packageLifecycleScripts({ package: packageManifest })
|
|
485
|
+
assert.deepEqual(lifecycle, {})
|
|
486
|
+
|
|
487
|
+
const requestPath = join(consumer.consumerRoot, 'request.json')
|
|
488
|
+
await writeFile(
|
|
489
|
+
requestPath,
|
|
490
|
+
JSON.stringify({
|
|
491
|
+
input: consumer.input,
|
|
492
|
+
profile: harnessProfile(),
|
|
493
|
+
instanceId: 'workspace-a/user-a',
|
|
494
|
+
})
|
|
495
|
+
)
|
|
496
|
+
|
|
497
|
+
// NEGATIVE CONTROL — a broken export. The consumer resolves the packed
|
|
498
|
+
// package's OWN exports, so a specifier the packed manifest does not
|
|
499
|
+
// export must not resolve.
|
|
500
|
+
const brokenExport = join(consumer.consumerRoot, 'broken-export.mjs')
|
|
501
|
+
await writeFile(brokenExport, "import '@adea-ai/plugins/not-an-export-of-this-package'\n")
|
|
502
|
+
const broken = consumer.runUnderNode(brokenExport, requestPath)
|
|
503
|
+
assert.notEqual(broken.status, 0)
|
|
504
|
+
assert.match(
|
|
505
|
+
broken.stderr,
|
|
506
|
+
/ERR_PACKAGE_PATH_NOT_EXPORTED|ERR_MODULE_NOT_FOUND|Cannot find module/
|
|
507
|
+
)
|
|
508
|
+
|
|
509
|
+
// NEGATIVE CONTROL — an undeclared package. The consumer's install holds
|
|
510
|
+
// only the packed manifest's declared closure, so a package the workspace
|
|
511
|
+
// happens to have must NOT resolve from the consumer.
|
|
512
|
+
const undeclared = join(consumer.consumerRoot, 'undeclared.mjs')
|
|
513
|
+
await writeFile(undeclared, "import 'oxlint'\n")
|
|
514
|
+
const undeclaredRun = consumer.runUnderNode(undeclared, requestPath)
|
|
515
|
+
assert.notEqual(undeclaredRun.status, 0)
|
|
516
|
+
assert.match(undeclaredRun.stderr, /ERR_MODULE_NOT_FOUND|Cannot find module/)
|
|
517
|
+
}))
|
|
518
|
+
|
|
519
|
+
it('refuses a malformed provenance descriptor for a PACKED consumer too', () =>
|
|
520
|
+
withPackedConsumerFixture(async (consumer) => {
|
|
521
|
+
await writeFile(join(consumer.consumerRoot, 'consume.mjs'), CONSUMER_SCRIPT)
|
|
522
|
+
const requestPath = join(consumer.consumerRoot, 'request.json')
|
|
523
|
+
await writeFile(
|
|
524
|
+
requestPath,
|
|
525
|
+
JSON.stringify({
|
|
526
|
+
mode: 'malformed',
|
|
527
|
+
input: consumer.input,
|
|
528
|
+
profile: harnessProfile(),
|
|
529
|
+
instanceId: 'workspace-a/user-a',
|
|
530
|
+
})
|
|
531
|
+
)
|
|
532
|
+
const underNode = consumer.runUnderNode(join(consumer.consumerRoot, 'consume.mjs'), requestPath)
|
|
533
|
+
assert.equal(underNode.status, 0, underNode.stderr)
|
|
534
|
+
const result = JSON.parse(underNode.stdout) as { ok: boolean; error?: string }
|
|
535
|
+
assert.equal(result.ok, false)
|
|
536
|
+
// One typed line, naming the plugin — the catalog's refusal contract.
|
|
537
|
+
assert.match(
|
|
538
|
+
result.error ?? '',
|
|
539
|
+
/^PACKAGE_PROVENANCE_INVALID: plugin:test-source:review-kit \(.+\)$/
|
|
540
|
+
)
|
|
541
|
+
assert.equal((result.error ?? '').includes('\n'), false)
|
|
542
|
+
}))
|
|
543
|
+
|
|
544
|
+
it('copies a symlinked dependency as self-contained content', async () => {
|
|
545
|
+
// The workspace installs the package's dependencies as SYMLINKS into the
|
|
546
|
+
// package manager's store, and a recursive copy keeps a link as a link.
|
|
547
|
+
// The consumer must receive the CONTENT, and the copy must resolve inside
|
|
548
|
+
// the consumer.
|
|
549
|
+
const workspace = await mkdtemp(join(tmpdir(), 'adea-symlink-dependency-'))
|
|
550
|
+
try {
|
|
551
|
+
const real = join(workspace, 'real-pkg')
|
|
552
|
+
await mkdir(real)
|
|
553
|
+
await writeFile(
|
|
554
|
+
join(real, 'package.json'),
|
|
555
|
+
JSON.stringify({ name: 'real-pkg', version: '1.0.0' })
|
|
556
|
+
)
|
|
557
|
+
const link = join(workspace, 'linked-pkg')
|
|
558
|
+
await symlink(real, link)
|
|
559
|
+
const consumer = join(workspace, 'consumer')
|
|
560
|
+
await mkdir(consumer, { recursive: true })
|
|
561
|
+
|
|
562
|
+
const copied = await copyDependency(link, 'linked-pkg', consumer)
|
|
563
|
+
// The copy is a real directory, not a link.
|
|
564
|
+
assert.equal(lstatSync(copied.path).isSymbolicLink(), false)
|
|
565
|
+
// And it resolves inside the consumer, not at the workspace's target.
|
|
566
|
+
assert.ok(copied.realpath.startsWith(realpathSync(consumer) + '/'))
|
|
567
|
+
assert.notEqual(copied.realpath, realpathSync(real))
|
|
568
|
+
// The workspace's own link is untouched.
|
|
569
|
+
assert.equal(lstatSync(link).isSymbolicLink(), true)
|
|
570
|
+
} finally {
|
|
571
|
+
await rm(workspace, { recursive: true, force: true })
|
|
572
|
+
}
|
|
573
|
+
})
|
|
574
|
+
|
|
575
|
+
it('qualifies the compiled artifact only on Node 24', async () => {
|
|
576
|
+
// The qualification exists because the package's `bun` export condition
|
|
577
|
+
// selects src/*.ts: a Node that is not 24 would change WHICH artifact is
|
|
578
|
+
// under test, so it is not a candidate.
|
|
579
|
+
const directory = await mkdtemp(join(tmpdir(), 'adea-node-candidate-'))
|
|
580
|
+
try {
|
|
581
|
+
const node24 = join(directory, 'node24')
|
|
582
|
+
const node20 = join(directory, 'node20')
|
|
583
|
+
await writeFile(node24, '#!/bin/sh\necho v24.21.0\n')
|
|
584
|
+
await writeFile(node20, '#!/bin/sh\necho v20.11.0\n')
|
|
585
|
+
await chmod(node24, 0o755)
|
|
586
|
+
await chmod(node20, 0o755)
|
|
587
|
+
assert.equal(selectPinnedNode([node20, node24]), node24)
|
|
588
|
+
assert.throws(() => selectPinnedNode([node20]), /needs Node 24/)
|
|
589
|
+
assert.throws(() => selectPinnedNode([]), /needs Node 24/)
|
|
590
|
+
// The discovery ends with the `node` on PATH, which is where a CI
|
|
591
|
+
// runner's pinned setup action puts Node 24.
|
|
592
|
+
const candidates = nodeCandidates()
|
|
593
|
+
assert.ok(candidates.includes('node'))
|
|
594
|
+
} finally {
|
|
595
|
+
await rm(directory, { recursive: true, force: true })
|
|
596
|
+
}
|
|
597
|
+
})
|
package/src/portable-catalog.ts
CHANGED
|
@@ -168,6 +168,26 @@ export async function synchronizePortable(options: SyncInput): Promise<SyncResul
|
|
|
168
168
|
}
|
|
169
169
|
}
|
|
170
170
|
|
|
171
|
+
/**
|
|
172
|
+
* A single-line summary of why a release's package descriptor was refused.
|
|
173
|
+
*
|
|
174
|
+
* The schema's own failure is a structured issue list; the catalog's refusal
|
|
175
|
+
* contract is one typed line. The first issue is kept as the detail, because
|
|
176
|
+
* it names the field that was wrong.
|
|
177
|
+
*/
|
|
178
|
+
function describeProvenanceFailure(error: unknown): string {
|
|
179
|
+
const issues = (error as { issues?: unknown }).issues
|
|
180
|
+
if (Array.isArray(issues) && issues.length > 0) {
|
|
181
|
+
const first = issues[0] as { path?: unknown; message?: unknown }
|
|
182
|
+
const path = Array.isArray(first.path) ? first.path.join('.') : ''
|
|
183
|
+
const message = typeof first.message === 'string' ? first.message : ''
|
|
184
|
+
return [path, message].filter(Boolean).join(': ').replace(/\s+/g, ' ').slice(0, 200)
|
|
185
|
+
}
|
|
186
|
+
return error instanceof Error
|
|
187
|
+
? error.message.replace(/\s+/g, ' ').slice(0, 200)
|
|
188
|
+
: 'the descriptor is not a package recipe'
|
|
189
|
+
}
|
|
190
|
+
|
|
171
191
|
export function verifyPortableCatalog(catalog: Catalog, requirePortable = false): void {
|
|
172
192
|
const { catalogId, ...body } = CatalogSchema.parse(catalog)
|
|
173
193
|
if (catalogId !== `catalog:${digest(body).slice(7)}`) throw new Error('CATALOG_ID_MISMATCH')
|
|
@@ -189,7 +209,20 @@ export function verifyPortableCatalog(catalog: Catalog, requirePortable = false)
|
|
|
189
209
|
throw new Error(`PORTABLE_RESYNC_REQUIRED: ${plugin.pluginId}`)
|
|
190
210
|
continue
|
|
191
211
|
}
|
|
192
|
-
|
|
212
|
+
let pkg: ReturnType<typeof verifyAgentPackage>
|
|
213
|
+
try {
|
|
214
|
+
pkg = verifyAgentPackage(descriptor)
|
|
215
|
+
} catch (error) {
|
|
216
|
+
// A consumer imports a catalog it did not compile, so a malformed
|
|
217
|
+
// provenance descriptor is an IMPORT failure, and it must be refused
|
|
218
|
+
// the way every other failure here is refused: one typed, single-line
|
|
219
|
+
// reason that names the plugin. The schema's own multi-line issue
|
|
220
|
+
// dump is not a refusal a consumer can match on.
|
|
221
|
+
throw new Error(
|
|
222
|
+
`PACKAGE_PROVENANCE_INVALID: ${plugin.pluginId} (${describeProvenanceFailure(error)})`,
|
|
223
|
+
{ cause: error }
|
|
224
|
+
)
|
|
225
|
+
}
|
|
193
226
|
if (
|
|
194
227
|
release.contentResolution !== 'complete' ||
|
|
195
228
|
pkg.sourceDigest !== release.canonicalContentDigest
|