release-skill 0.9.6 → 0.9.7
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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codebuddy-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +2 -2
- package/.kimi-plugin/plugin.json +1 -1
- package/CHANGELOG.md +28 -0
- package/INSTALL.md +2 -2
- package/INSTALL.zh-CN.md +2 -2
- package/README.md +14 -14
- package/README.zh-CN.md +15 -15
- package/adapters/claude/.claude-plugin/marketplace.json +1 -1
- package/adapters/claude/.claude-plugin/plugin.json +1 -1
- package/adapters/claude/bin/release-skill.bundle.mjs +79 -9
- package/adapters/codex/.codex-plugin/plugin.json +2 -2
- package/adapters/codex/bin/release-skill.bundle.mjs +79 -9
- package/adapters/kimi/.kimi-plugin/plugin.json +1 -1
- package/adapters/kimi/bin/release-skill.bundle.mjs +79 -9
- package/adapters/workbuddy/.codebuddy-plugin/plugin.json +1 -1
- package/adapters/workbuddy/bin/release-skill.bundle.mjs +79 -9
- package/bin/release-skill.bundle.mjs +79 -9
- package/package.json +1 -1
- package/platform-manifest.json +2 -2
- package/src/commands/prepare.mjs +5 -3
- package/src/commands/publish.mjs +30 -3
- package/src/commands/verify.mjs +14 -7
- package/src/core/skill-resource-closure.mjs +19 -1
- package/src/core/surface-host-bindings.mjs +35 -0
|
@@ -9,9 +9,9 @@ const __bundlePkgRoot = __bundleResolve(__bundleDirname(__bundleFileURLToPath(im
|
|
|
9
9
|
// Provide a real require() for CJS packages bundled into ESM (e.g. yaml, ajv).
|
|
10
10
|
const __bundleRealRequire = __bundleCreateRequire(import.meta.url);
|
|
11
11
|
// Package identity injected at build time — closure-independent --version probe.
|
|
12
|
-
const __bundlePkg = Object.freeze({"name":"release-skill","version":"0.9.
|
|
12
|
+
const __bundlePkg = Object.freeze({"name":"release-skill","version":"0.9.7"});
|
|
13
13
|
// Build-time source digest for the BUNDLE_STALE freshness gate (see above).
|
|
14
|
-
const __bundleSourceDigest = "
|
|
14
|
+
const __bundleSourceDigest = "938bc6828f17e2248a4fe167bcd3353b43848ec1a494dc17dc663d390847150c";
|
|
15
15
|
|
|
16
16
|
var __create = Object.create;
|
|
17
17
|
var __defProp = Object.defineProperty;
|
|
@@ -109520,9 +109520,27 @@ async function checkSkillResourceClosure({
|
|
|
109520
109520
|
result2.receiptDigest = digestDocument(receiptProjection(result2));
|
|
109521
109521
|
return result2;
|
|
109522
109522
|
}
|
|
109523
|
-
function evaluateDeclaredHostSurfaceCoverage(expectedHosts, surfaces) {
|
|
109523
|
+
function evaluateDeclaredHostSurfaceCoverage(expectedHosts, surfaces, coverageClaims = []) {
|
|
109524
109524
|
const missing = [];
|
|
109525
|
+
const observedById = new Map((surfaces ?? []).map((surface) => [surface.id, surface]));
|
|
109526
|
+
const claimsByHost = /* @__PURE__ */ new Map();
|
|
109527
|
+
for (const claim of coverageClaims ?? []) {
|
|
109528
|
+
const hostClaims = claimsByHost.get(claim.host) ?? [];
|
|
109529
|
+
hostClaims.push(claim);
|
|
109530
|
+
claimsByHost.set(claim.host, hostClaims);
|
|
109531
|
+
}
|
|
109525
109532
|
for (const expectedHost of [...new Set(expectedHosts ?? [])].sort((a, b) => a.localeCompare(b))) {
|
|
109533
|
+
const declaredClaims = claimsByHost.get(expectedHost) ?? [];
|
|
109534
|
+
if (declaredClaims.length > 0) {
|
|
109535
|
+
const missingClaim = declaredClaims.find((claim) => {
|
|
109536
|
+
const surface3 = observedById.get(claim.surfaceId);
|
|
109537
|
+
return !surface3 || !(surface3.skillCount >= 1);
|
|
109538
|
+
});
|
|
109539
|
+
if (!missingClaim) continue;
|
|
109540
|
+
const surface2 = observedById.get(missingClaim.surfaceId);
|
|
109541
|
+
missing.push({ host: expectedHost, skillCount: surface2?.skillCount ?? 0 });
|
|
109542
|
+
continue;
|
|
109543
|
+
}
|
|
109526
109544
|
const surface = (surfaces ?? []).find((item) => item.host === expectedHost);
|
|
109527
109545
|
if (!surface || !(surface.skillCount >= 1)) {
|
|
109528
109546
|
missing.push({ host: expectedHost, skillCount: surface?.skillCount ?? 0 });
|
|
@@ -112496,6 +112514,23 @@ async function deriveSurfaceHostBinding({ manifest, pluginRoot, platform, snapsh
|
|
|
112496
112514
|
const host = await normalizeHostId(platform.buildAdapter.name);
|
|
112497
112515
|
return { surfaceId, host };
|
|
112498
112516
|
}
|
|
112517
|
+
function groupSurfaceHostBindings(claims = []) {
|
|
112518
|
+
if (!Array.isArray(claims)) {
|
|
112519
|
+
throw new TypeError("surfaceHostBindings must be an array");
|
|
112520
|
+
}
|
|
112521
|
+
const bySurface = /* @__PURE__ */ new Map();
|
|
112522
|
+
for (const claim of claims) {
|
|
112523
|
+
if (!claim || typeof claim !== "object" || typeof claim.surfaceId !== "string" || typeof claim.host !== "string") {
|
|
112524
|
+
throw new TypeError("surfaceHostBindings entries must be objects with string surfaceId and host");
|
|
112525
|
+
}
|
|
112526
|
+
const existing = bySurface.get(claim.surfaceId) ?? [];
|
|
112527
|
+
existing.push({ surfaceId: claim.surfaceId, host: claim.host });
|
|
112528
|
+
bySurface.set(claim.surfaceId, existing);
|
|
112529
|
+
}
|
|
112530
|
+
const coverageClaims = [...bySurface.values()].flat().sort((left, right) => left.surfaceId.localeCompare(right.surfaceId) || left.host.localeCompare(right.host));
|
|
112531
|
+
const checkerBindings = [...bySurface.values()].filter((surfaceClaims) => surfaceClaims.length === 1).map(([claim]) => claim).sort((left, right) => left.surfaceId.localeCompare(right.surfaceId) || left.host.localeCompare(right.host));
|
|
112532
|
+
return { coverageClaims, checkerBindings };
|
|
112533
|
+
}
|
|
112499
112534
|
var SKILLS_NORMALIZERS;
|
|
112500
112535
|
var init_surface_host_bindings = __esm({
|
|
112501
112536
|
async "src/core/surface-host-bindings.mjs"() {
|
|
@@ -112513,6 +112548,7 @@ var init_surface_host_bindings = __esm({
|
|
|
112513
112548
|
__name(pluginRootFromManifestRelativePath, "pluginRootFromManifestRelativePath");
|
|
112514
112549
|
__name(assertDeclaredSkillsDirExists, "assertDeclaredSkillsDirExists");
|
|
112515
112550
|
__name(deriveSurfaceHostBinding, "deriveSurfaceHostBinding");
|
|
112551
|
+
__name(groupSurfaceHostBindings, "groupSurfaceHostBindings");
|
|
112516
112552
|
}
|
|
112517
112553
|
});
|
|
112518
112554
|
|
|
@@ -113943,11 +113979,16 @@ async function verifyRelease(options) {
|
|
|
113943
113979
|
const unit = (plan.units ?? []).find((u) => u.id === unitId);
|
|
113944
113980
|
const dist = unit?.distributions?.find((d) => d.type === platform.distributionType);
|
|
113945
113981
|
let binding = null;
|
|
113982
|
+
let frozenPluginRoot = null;
|
|
113946
113983
|
const contract2 = dist?.installationContract;
|
|
113947
113984
|
if (contract2?.normalizedManifest) {
|
|
113985
|
+
frozenPluginRoot = pluginRootFromManifestRelativePath(contract2.manifestRelativePath);
|
|
113948
113986
|
binding = await deriveSurfaceHostBinding({
|
|
113949
113987
|
manifest: contract2.normalizedManifest,
|
|
113950
|
-
|
|
113988
|
+
// Adapter observations are already rooted at the installed
|
|
113989
|
+
// plugin. The frozen manifest's skills path is therefore
|
|
113990
|
+
// relative to '.', not to the snapshot-root manifest path.
|
|
113991
|
+
pluginRoot: ".",
|
|
113951
113992
|
platform,
|
|
113952
113993
|
snapshotDir: check.observation.installPath
|
|
113953
113994
|
});
|
|
@@ -113960,6 +114001,7 @@ async function verifyRelease(options) {
|
|
|
113960
114001
|
unitId,
|
|
113961
114002
|
surfaceId: binding?.surfaceId ?? ".",
|
|
113962
114003
|
binding,
|
|
114004
|
+
frozenPluginRoot,
|
|
113963
114005
|
unit
|
|
113964
114006
|
});
|
|
113965
114007
|
}
|
|
@@ -114042,7 +114084,7 @@ async function verifyRelease(options) {
|
|
|
114042
114084
|
);
|
|
114043
114085
|
}
|
|
114044
114086
|
const pluginDistCount = (surface.unit?.distributions ?? []).filter((d) => d.type !== "npm").length;
|
|
114045
|
-
if (pluginDistCount === 1) {
|
|
114087
|
+
if (pluginDistCount === 1 && surface.frozenPluginRoot === ".") {
|
|
114046
114088
|
const observedReceipt = createSkillResourceClosureReceipt(closureResult, {
|
|
114047
114089
|
unitId: surface.unitId,
|
|
114048
114090
|
preparedAt: expectedUnitReceipt.preparedAt ?? null,
|
|
@@ -129636,10 +129678,11 @@ async function runPrepareSkillResourceClosureGate({
|
|
|
129636
129678
|
});
|
|
129637
129679
|
if (binding) surfaceHostBindings.push(binding);
|
|
129638
129680
|
}
|
|
129681
|
+
const { checkerBindings, coverageClaims } = groupSurfaceHostBindings(surfaceHostBindings);
|
|
129639
129682
|
const closureResult = await checkSkillResourceClosure({
|
|
129640
129683
|
snapshotDir: manifest.outputDir,
|
|
129641
129684
|
host: "root",
|
|
129642
|
-
surfaceHostBindings
|
|
129685
|
+
surfaceHostBindings: checkerBindings
|
|
129643
129686
|
});
|
|
129644
129687
|
const receipt = createSkillResourceClosureReceipt(closureResult, {
|
|
129645
129688
|
unitId: unit.id,
|
|
@@ -129684,7 +129727,8 @@ async function runPrepareSkillResourceClosureGate({
|
|
|
129684
129727
|
}
|
|
129685
129728
|
const hostCoverage = evaluateDeclaredHostSurfaceCoverage(
|
|
129686
129729
|
expectedHosts,
|
|
129687
|
-
closureResult.surfaces
|
|
129730
|
+
closureResult.surfaces,
|
|
129731
|
+
coverageClaims
|
|
129688
129732
|
);
|
|
129689
129733
|
if (!hostCoverage.passed) {
|
|
129690
129734
|
await evidence.append({
|
|
@@ -129708,7 +129752,7 @@ async function runPrepareSkillResourceClosureGate({
|
|
|
129708
129752
|
}
|
|
129709
129753
|
);
|
|
129710
129754
|
}
|
|
129711
|
-
for (const binding of
|
|
129755
|
+
for (const binding of coverageClaims) {
|
|
129712
129756
|
const boundSurface = closureResult.surfaces.find(
|
|
129713
129757
|
(surface) => surface.id === binding.surfaceId
|
|
129714
129758
|
);
|
|
@@ -133318,10 +133362,11 @@ async function publishRelease(options) {
|
|
|
133318
133362
|
});
|
|
133319
133363
|
if (binding) surfaceHostBindings.push(binding);
|
|
133320
133364
|
}
|
|
133365
|
+
const { checkerBindings, coverageClaims } = groupSurfaceHostBindings(surfaceHostBindings);
|
|
133321
133366
|
const closureResult = await checkSkillResourceClosure({
|
|
133322
133367
|
snapshotDir,
|
|
133323
133368
|
host: "root",
|
|
133324
|
-
surfaceHostBindings
|
|
133369
|
+
surfaceHostBindings: checkerBindings
|
|
133325
133370
|
});
|
|
133326
133371
|
if (closureResult.findings.length > 0) {
|
|
133327
133372
|
await evidence.append({
|
|
@@ -133337,6 +133382,31 @@ async function publishRelease(options) {
|
|
|
133337
133382
|
{ unitId, findings: closureResult.findings }
|
|
133338
133383
|
);
|
|
133339
133384
|
}
|
|
133385
|
+
const expectedHosts = [];
|
|
133386
|
+
for (const distribution of frozenUnit?.distributions ?? []) {
|
|
133387
|
+
const platform = PLATFORMS.find((item) => item.distributionType === distribution.type);
|
|
133388
|
+
if (platform) expectedHosts.push(await normalizeHostId(platform.buildAdapter.name));
|
|
133389
|
+
}
|
|
133390
|
+
const hostCoverage = evaluateDeclaredHostSurfaceCoverage(
|
|
133391
|
+
expectedHosts,
|
|
133392
|
+
closureResult.surfaces,
|
|
133393
|
+
coverageClaims
|
|
133394
|
+
);
|
|
133395
|
+
if (!hostCoverage.passed) {
|
|
133396
|
+
await evidence.append({
|
|
133397
|
+
phase: "safety-gate",
|
|
133398
|
+
gate: "skill-resource-closure",
|
|
133399
|
+
status: "failed",
|
|
133400
|
+
unitId,
|
|
133401
|
+
reason: "declared-host-surface-missing",
|
|
133402
|
+
missingHosts: hostCoverage.missing
|
|
133403
|
+
});
|
|
133404
|
+
throw new ReleaseError(
|
|
133405
|
+
GATE_FAILED,
|
|
133406
|
+
`skill resource closure recheck failed for unit "${unitId}": declared host surface(s) missing or empty: ${hostCoverage.missing.map((item) => item.host).join(", ")}`,
|
|
133407
|
+
{ unitId, missingHosts: hostCoverage.missing }
|
|
133408
|
+
);
|
|
133409
|
+
}
|
|
133340
133410
|
const observed = createSkillResourceClosureReceipt(closureResult, {
|
|
133341
133411
|
unitId,
|
|
133342
133412
|
preparedAt: expected.preparedAt ?? null,
|
package/package.json
CHANGED
package/platform-manifest.json
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
"schemaVersion": "1.0.0",
|
|
3
3
|
"platformId": "release-skill",
|
|
4
4
|
"platformName": "release-skill Plugin Platform",
|
|
5
|
-
"version": "0.9.
|
|
5
|
+
"version": "0.9.7",
|
|
6
6
|
"logicalRoot": "skills-src/",
|
|
7
7
|
"logicalRootDigest": "4338c7c5a7efd38ca372049526e5bdd198087491f76702cd21d491741bfad835",
|
|
8
8
|
"projectionDigest": "c4d40afd481d78907d51ef9be1dc2238834d566b9a9792f1d25da19f57368d7d",
|
|
9
|
-
"projectionId": "release-skill:platform:0.9.
|
|
9
|
+
"projectionId": "release-skill:platform:0.9.7",
|
|
10
10
|
"manifestRequired": true,
|
|
11
11
|
"projectionSources": {
|
|
12
12
|
"skills-src-root": {
|
package/src/commands/prepare.mjs
CHANGED
|
@@ -82,7 +82,7 @@ import {
|
|
|
82
82
|
normalizeHostId,
|
|
83
83
|
buildExpectedStandaloneIndexInstallIdentity,
|
|
84
84
|
} from '../platforms/registry.mjs';
|
|
85
|
-
import { deriveSurfaceHostBinding, pluginRootFromManifestRelativePath } from '../core/surface-host-bindings.mjs';
|
|
85
|
+
import { deriveSurfaceHostBinding, groupSurfaceHostBindings, pluginRootFromManifestRelativePath } from '../core/surface-host-bindings.mjs';
|
|
86
86
|
import { validateMarketplaceSourceSelection, MARKETPLACE_SOURCE_TYPES, resolvePluginManifestFromMarketplaceEntrySource, resolveMarketplaceRoot } from '../adapters/plugin-marketplace.mjs';
|
|
87
87
|
import { buildInstallationContract, computeInstallationContractDigest, INSTALLATION_CONTRACT_ALGORITHM_VERSION } from '../core/installation-contract.mjs';
|
|
88
88
|
import {
|
|
@@ -2818,10 +2818,11 @@ async function runPrepareSkillResourceClosureGate({
|
|
|
2818
2818
|
if (binding) surfaceHostBindings.push(binding);
|
|
2819
2819
|
}
|
|
2820
2820
|
|
|
2821
|
+
const { checkerBindings, coverageClaims } = groupSurfaceHostBindings(surfaceHostBindings);
|
|
2821
2822
|
const closureResult = await checkSkillResourceClosure({
|
|
2822
2823
|
snapshotDir: manifest.outputDir,
|
|
2823
2824
|
host: 'root',
|
|
2824
|
-
surfaceHostBindings,
|
|
2825
|
+
surfaceHostBindings: checkerBindings,
|
|
2825
2826
|
});
|
|
2826
2827
|
|
|
2827
2828
|
// G5: bind execution time + exit code into the frozen receipt.
|
|
@@ -2880,6 +2881,7 @@ async function runPrepareSkillResourceClosureGate({
|
|
|
2880
2881
|
const hostCoverage = evaluateDeclaredHostSurfaceCoverage(
|
|
2881
2882
|
expectedHosts,
|
|
2882
2883
|
closureResult.surfaces,
|
|
2884
|
+
coverageClaims,
|
|
2883
2885
|
);
|
|
2884
2886
|
if (!hostCoverage.passed) {
|
|
2885
2887
|
await evidence.append({
|
|
@@ -2908,7 +2910,7 @@ async function runPrepareSkillResourceClosureGate({
|
|
|
2908
2910
|
// 含一个技能(binding-surface existence)。G4 按宿主名覆盖,这里按
|
|
2909
2911
|
// 绑定表面路径覆盖 —— 例如 manifest 指向 './adapters/claude/skills/'
|
|
2910
2912
|
// 但 publicFiles 丢弃该目录时,绑定表面缺失必须失败关闭。
|
|
2911
|
-
for (const binding of
|
|
2913
|
+
for (const binding of coverageClaims) {
|
|
2912
2914
|
const boundSurface = closureResult.surfaces.find(
|
|
2913
2915
|
(surface) => surface.id === binding.surfaceId,
|
|
2914
2916
|
);
|
package/src/commands/publish.mjs
CHANGED
|
@@ -27,8 +27,8 @@
|
|
|
27
27
|
|
|
28
28
|
import { readFile, mkdir } from 'node:fs/promises';
|
|
29
29
|
import { isAbsolute, join, relative } from 'node:path';
|
|
30
|
-
import { PLATFORMS } from '../platforms/registry.mjs';
|
|
31
|
-
import { deriveSurfaceHostBinding, pluginRootFromManifestRelativePath } from '../core/surface-host-bindings.mjs';
|
|
30
|
+
import { PLATFORMS, normalizeHostId } from '../platforms/registry.mjs';
|
|
31
|
+
import { deriveSurfaceHostBinding, groupSurfaceHostBindings, pluginRootFromManifestRelativePath } from '../core/surface-host-bindings.mjs';
|
|
32
32
|
|
|
33
33
|
import { assertImmutablePlanAuthority, computePlanDigest, validatePlan, validatePlanActionCompleteness } from '../core/plan.mjs';
|
|
34
34
|
import {
|
|
@@ -48,6 +48,7 @@ import {
|
|
|
48
48
|
assertSkillResourceClosureReceipt,
|
|
49
49
|
checkSkillResourceClosure,
|
|
50
50
|
createSkillResourceClosureReceipt,
|
|
51
|
+
evaluateDeclaredHostSurfaceCoverage,
|
|
51
52
|
} from '../core/skill-resource-closure.mjs';
|
|
52
53
|
import {
|
|
53
54
|
ADAPTER_ACTION_TYPE_MAP,
|
|
@@ -672,10 +673,11 @@ export async function publishRelease(options) {
|
|
|
672
673
|
});
|
|
673
674
|
if (binding) surfaceHostBindings.push(binding);
|
|
674
675
|
}
|
|
676
|
+
const { checkerBindings, coverageClaims } = groupSurfaceHostBindings(surfaceHostBindings);
|
|
675
677
|
const closureResult = await checkSkillResourceClosure({
|
|
676
678
|
snapshotDir,
|
|
677
679
|
host: 'root',
|
|
678
|
-
surfaceHostBindings,
|
|
680
|
+
surfaceHostBindings: checkerBindings,
|
|
679
681
|
});
|
|
680
682
|
if (closureResult.findings.length > 0) {
|
|
681
683
|
await evidence.append({
|
|
@@ -691,6 +693,31 @@ export async function publishRelease(options) {
|
|
|
691
693
|
{ unitId, findings: closureResult.findings },
|
|
692
694
|
);
|
|
693
695
|
}
|
|
696
|
+
const expectedHosts = [];
|
|
697
|
+
for (const distribution of frozenUnit?.distributions ?? []) {
|
|
698
|
+
const platform = PLATFORMS.find((item) => item.distributionType === distribution.type);
|
|
699
|
+
if (platform) expectedHosts.push(await normalizeHostId(platform.buildAdapter.name));
|
|
700
|
+
}
|
|
701
|
+
const hostCoverage = evaluateDeclaredHostSurfaceCoverage(
|
|
702
|
+
expectedHosts,
|
|
703
|
+
closureResult.surfaces,
|
|
704
|
+
coverageClaims,
|
|
705
|
+
);
|
|
706
|
+
if (!hostCoverage.passed) {
|
|
707
|
+
await evidence.append({
|
|
708
|
+
phase: 'safety-gate',
|
|
709
|
+
gate: 'skill-resource-closure',
|
|
710
|
+
status: 'failed',
|
|
711
|
+
unitId,
|
|
712
|
+
reason: 'declared-host-surface-missing',
|
|
713
|
+
missingHosts: hostCoverage.missing,
|
|
714
|
+
});
|
|
715
|
+
throw new ReleaseError(
|
|
716
|
+
GATE_FAILED,
|
|
717
|
+
`skill resource closure recheck failed for unit "${unitId}": declared host surface(s) missing or empty: ${hostCoverage.missing.map((item) => item.host).join(', ')}`,
|
|
718
|
+
{ unitId, missingHosts: hostCoverage.missing },
|
|
719
|
+
);
|
|
720
|
+
}
|
|
694
721
|
// G5: preparedAt/exitCode are record-layer fields frozen by prepare
|
|
695
722
|
// (bound by the plan digest); they cannot be recomputed from the
|
|
696
723
|
// snapshot, so the recheck carries them forward from the expected
|
package/src/commands/verify.mjs
CHANGED
|
@@ -2052,11 +2052,12 @@ export async function verifyRelease(options) {
|
|
|
2052
2052
|
await evidence.append({ phase: 'verify', step: 'skill-resource-closure', status: 'started' });
|
|
2053
2053
|
|
|
2054
2054
|
// Collect install paths from marketplace adapter checks.
|
|
2055
|
-
// R-13 P2 (ruling 6):
|
|
2056
|
-
//
|
|
2057
|
-
//
|
|
2058
|
-
//
|
|
2059
|
-
//
|
|
2055
|
+
// R-13 P2 (ruling 6): prepare and publish derive surface bindings in
|
|
2056
|
+
// frozen snapshot-root coordinates. An adapter observation is already
|
|
2057
|
+
// rooted at the installed plugin, so verify derives the same manifest
|
|
2058
|
+
// skills fact relative to '.' at that consumer coordinate. Plans
|
|
2059
|
+
// without a frozen installation contract (legacy) fall back to the
|
|
2060
|
+
// plugin-root surface ('.') with the legacy permissive checks.
|
|
2060
2061
|
const installSurfaces = [];
|
|
2061
2062
|
for (const check of adapterChecks) {
|
|
2062
2063
|
const platform = PLATFORMS.find((p) => p.actionType === check.actionType);
|
|
@@ -2065,11 +2066,16 @@ export async function verifyRelease(options) {
|
|
|
2065
2066
|
const unit = (plan.units ?? []).find((u) => u.id === unitId);
|
|
2066
2067
|
const dist = unit?.distributions?.find((d) => d.type === platform.distributionType);
|
|
2067
2068
|
let binding = null;
|
|
2069
|
+
let frozenPluginRoot = null;
|
|
2068
2070
|
const contract = dist?.installationContract;
|
|
2069
2071
|
if (contract?.normalizedManifest) {
|
|
2072
|
+
frozenPluginRoot = pluginRootFromManifestRelativePath(contract.manifestRelativePath);
|
|
2070
2073
|
binding = await deriveSurfaceHostBinding({
|
|
2071
2074
|
manifest: contract.normalizedManifest,
|
|
2072
|
-
|
|
2075
|
+
// Adapter observations are already rooted at the installed
|
|
2076
|
+
// plugin. The frozen manifest's skills path is therefore
|
|
2077
|
+
// relative to '.', not to the snapshot-root manifest path.
|
|
2078
|
+
pluginRoot: '.',
|
|
2073
2079
|
platform,
|
|
2074
2080
|
snapshotDir: check.observation.installPath,
|
|
2075
2081
|
});
|
|
@@ -2082,6 +2088,7 @@ export async function verifyRelease(options) {
|
|
|
2082
2088
|
unitId,
|
|
2083
2089
|
surfaceId: binding?.surfaceId ?? '.',
|
|
2084
2090
|
binding,
|
|
2091
|
+
frozenPluginRoot,
|
|
2085
2092
|
unit,
|
|
2086
2093
|
});
|
|
2087
2094
|
}
|
|
@@ -2192,7 +2199,7 @@ export async function verifyRelease(options) {
|
|
|
2192
2199
|
// publish's recheck uses.
|
|
2193
2200
|
const pluginDistCount = (surface.unit?.distributions ?? [])
|
|
2194
2201
|
.filter((d) => d.type !== 'npm').length;
|
|
2195
|
-
if (pluginDistCount === 1) {
|
|
2202
|
+
if (pluginDistCount === 1 && surface.frozenPluginRoot === '.') {
|
|
2196
2203
|
const observedReceipt = createSkillResourceClosureReceipt(closureResult, {
|
|
2197
2204
|
unitId: surface.unitId,
|
|
2198
2205
|
preparedAt: expectedUnitReceipt.preparedAt ?? null,
|
|
@@ -742,9 +742,27 @@ export async function checkSkillResourceClosure({
|
|
|
742
742
|
* @param {Array<{ id: string, host: string, skillCount: number }>} surfaces
|
|
743
743
|
* @returns {{ passed: boolean, missing: Array<{ host: string, skillCount: number }> }}
|
|
744
744
|
*/
|
|
745
|
-
export function evaluateDeclaredHostSurfaceCoverage(expectedHosts, surfaces) {
|
|
745
|
+
export function evaluateDeclaredHostSurfaceCoverage(expectedHosts, surfaces, coverageClaims = []) {
|
|
746
746
|
const missing = [];
|
|
747
|
+
const observedById = new Map((surfaces ?? []).map((surface) => [surface.id, surface]));
|
|
748
|
+
const claimsByHost = new Map();
|
|
749
|
+
for (const claim of coverageClaims ?? []) {
|
|
750
|
+
const hostClaims = claimsByHost.get(claim.host) ?? [];
|
|
751
|
+
hostClaims.push(claim);
|
|
752
|
+
claimsByHost.set(claim.host, hostClaims);
|
|
753
|
+
}
|
|
747
754
|
for (const expectedHost of [...new Set(expectedHosts ?? [])].sort((a, b) => a.localeCompare(b))) {
|
|
755
|
+
const declaredClaims = claimsByHost.get(expectedHost) ?? [];
|
|
756
|
+
if (declaredClaims.length > 0) {
|
|
757
|
+
const missingClaim = declaredClaims.find((claim) => {
|
|
758
|
+
const surface = observedById.get(claim.surfaceId);
|
|
759
|
+
return !surface || !(surface.skillCount >= 1);
|
|
760
|
+
});
|
|
761
|
+
if (!missingClaim) continue;
|
|
762
|
+
const surface = observedById.get(missingClaim.surfaceId);
|
|
763
|
+
missing.push({ host: expectedHost, skillCount: surface?.skillCount ?? 0 });
|
|
764
|
+
continue;
|
|
765
|
+
}
|
|
748
766
|
const surface = (surfaces ?? []).find((item) => item.host === expectedHost);
|
|
749
767
|
if (!surface || !(surface.skillCount >= 1)) {
|
|
750
768
|
missing.push({ host: expectedHost, skillCount: surface?.skillCount ?? 0 });
|
|
@@ -186,3 +186,38 @@ export async function deriveSurfaceHostBinding({ manifest, pluginRoot, platform,
|
|
|
186
186
|
const host = await normalizeHostId(platform.buildAdapter.name);
|
|
187
187
|
return { surfaceId, host };
|
|
188
188
|
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Group manifest-backed claims before invoking the closure checker.
|
|
192
|
+
*
|
|
193
|
+
* A shared canonical source surface is scanned once. Claims remain complete
|
|
194
|
+
* for host coverage, while checker overrides are passed only for an
|
|
195
|
+
* unshared surface; this keeps the checker fail-closed when called directly
|
|
196
|
+
* with conflicting host claims.
|
|
197
|
+
*
|
|
198
|
+
* @param {Array<{surfaceId: string, host: string}>} claims
|
|
199
|
+
* @returns {{ coverageClaims: Array<{surfaceId: string, host: string}>, checkerBindings: Array<{surfaceId: string, host: string}> }}
|
|
200
|
+
*/
|
|
201
|
+
export function groupSurfaceHostBindings(claims = []) {
|
|
202
|
+
if (!Array.isArray(claims)) {
|
|
203
|
+
throw new TypeError('surfaceHostBindings must be an array');
|
|
204
|
+
}
|
|
205
|
+
const bySurface = new Map();
|
|
206
|
+
for (const claim of claims) {
|
|
207
|
+
if (!claim || typeof claim !== 'object'
|
|
208
|
+
|| typeof claim.surfaceId !== 'string' || typeof claim.host !== 'string') {
|
|
209
|
+
throw new TypeError('surfaceHostBindings entries must be objects with string surfaceId and host');
|
|
210
|
+
}
|
|
211
|
+
const existing = bySurface.get(claim.surfaceId) ?? [];
|
|
212
|
+
existing.push({ surfaceId: claim.surfaceId, host: claim.host });
|
|
213
|
+
bySurface.set(claim.surfaceId, existing);
|
|
214
|
+
}
|
|
215
|
+
const coverageClaims = [...bySurface.values()]
|
|
216
|
+
.flat()
|
|
217
|
+
.sort((left, right) => left.surfaceId.localeCompare(right.surfaceId) || left.host.localeCompare(right.host));
|
|
218
|
+
const checkerBindings = [...bySurface.values()]
|
|
219
|
+
.filter((surfaceClaims) => surfaceClaims.length === 1)
|
|
220
|
+
.map(([claim]) => claim)
|
|
221
|
+
.sort((left, right) => left.surfaceId.localeCompare(right.surfaceId) || left.host.localeCompare(right.host));
|
|
222
|
+
return { coverageClaims, checkerBindings };
|
|
223
|
+
}
|