release-skill 0.2.3 → 0.2.5
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 +46 -0
- package/CONTRIBUTING.md +27 -0
- package/INSTALL.md +95 -139
- package/INSTALL.zh-CN.md +70 -121
- package/README.md +266 -913
- package/README.zh-CN.md +224 -535
- 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 +19805 -17556
- package/adapters/claude/schemas/release-plan.schema.json +228 -0
- package/adapters/claude/schemas/release-project.schema.json +93 -0
- package/adapters/claude/schemas/release-run.schema.json +155 -2
- package/adapters/codex/.codex-plugin/plugin.json +2 -2
- package/adapters/codex/bin/release-skill.bundle.mjs +19805 -17556
- package/adapters/codex/schemas/release-plan.schema.json +228 -0
- package/adapters/codex/schemas/release-project.schema.json +93 -0
- package/adapters/codex/schemas/release-run.schema.json +155 -2
- package/adapters/kimi/.kimi-plugin/plugin.json +1 -1
- package/adapters/kimi/bin/release-skill.bundle.mjs +19805 -17556
- package/adapters/kimi/schemas/release-plan.schema.json +228 -0
- package/adapters/kimi/schemas/release-project.schema.json +93 -0
- package/adapters/kimi/schemas/release-run.schema.json +155 -2
- package/adapters/workbuddy/.codebuddy-plugin/plugin.json +1 -1
- package/adapters/workbuddy/bin/release-skill.bundle.mjs +19805 -17556
- package/adapters/workbuddy/schemas/release-plan.schema.json +228 -0
- package/adapters/workbuddy/schemas/release-project.schema.json +93 -0
- package/adapters/workbuddy/schemas/release-run.schema.json +155 -2
- package/bin/release-skill.bundle.mjs +19805 -17556
- package/package.json +1 -1
- package/schemas/release-plan.schema.json +228 -0
- package/schemas/release-project.schema.json +93 -0
- package/schemas/release-run.schema.json +155 -2
- package/scripts/sync-public-files.mjs +20 -9
- package/src/adapters/plugin-marketplace.mjs +1237 -403
- package/src/commands/prepare.mjs +454 -40
- package/src/commands/publish.mjs +169 -75
- package/src/commands/reconcile.mjs +92 -327
- package/src/commands/setup.mjs +148 -20
- package/src/commands/verify.mjs +454 -20
- package/src/core/baseline.mjs +8 -1
- package/src/core/checkpoints.mjs +50 -7
- package/src/core/config.mjs +15 -0
- package/src/core/errors.mjs +2 -0
- package/src/core/installation-contract.mjs +341 -0
- package/src/core/plan.mjs +158 -6
- package/src/core/skill-resource-closure.mjs +425 -0
- package/src/platforms/codebuddy.mjs +206 -280
- package/src/platforms/codex.mjs +369 -0
- package/src/platforms/kimi.mjs +191 -119
- package/src/platforms/registry.mjs +24 -6
|
@@ -29,13 +29,17 @@ import {
|
|
|
29
29
|
writeEvidenceAtomic,
|
|
30
30
|
} from './contract.mjs';
|
|
31
31
|
|
|
32
|
+
import {
|
|
33
|
+
computeInstallationContractDigest,
|
|
34
|
+
INSTALLATION_CONTRACT_ALGORITHM_VERSION,
|
|
35
|
+
} from '../core/installation-contract.mjs';
|
|
36
|
+
|
|
32
37
|
import { createHash } from 'node:crypto';
|
|
33
38
|
import { computeFrozenSnapshot, resolveFrozenPath } from '../snapshot/frozen.mjs';
|
|
34
39
|
import { PLATFORMS, getPlatform, resolvePlatformRoute, resolveCapabilityConflicts } from '../platforms/registry.mjs';
|
|
35
40
|
import {
|
|
36
41
|
KIMI_REQUIREMENT_FILE,
|
|
37
42
|
KIMI_ATTESTATION_FILE,
|
|
38
|
-
KIMI_MANAGED_SUBPATH,
|
|
39
43
|
resolveBoundPlanDigest,
|
|
40
44
|
kimiAuthorityDir,
|
|
41
45
|
validateKimiAttestation,
|
|
@@ -48,10 +52,15 @@ import {
|
|
|
48
52
|
CODEBUDDY_MARKETPLACE_SOURCE,
|
|
49
53
|
resolveCodeBuddyBoundPlanDigest,
|
|
50
54
|
codebuddyAuthorityDir,
|
|
51
|
-
codebuddyCliHome,
|
|
52
|
-
codebuddyCliInstallRoot,
|
|
53
55
|
validateCodeBuddyAttestation,
|
|
54
56
|
} from '../platforms/codebuddy.mjs';
|
|
57
|
+
import {
|
|
58
|
+
CODEX_REQUIREMENT_FILE,
|
|
59
|
+
CODEX_ATTESTATION_FILE,
|
|
60
|
+
resolveCodexBoundPlanDigest,
|
|
61
|
+
codexAuthorityDir,
|
|
62
|
+
validateCodexAttestation,
|
|
63
|
+
} from '../platforms/codex.mjs';
|
|
55
64
|
|
|
56
65
|
const execFile = promisify(execFileCb);
|
|
57
66
|
|
|
@@ -92,6 +101,9 @@ const EXTRA_INSTALLED_PATHS_CAP = 200;
|
|
|
92
101
|
/** Diagnostic cap: at most this many conflict paths are listed per error. */
|
|
93
102
|
const PAYLOAD_CONFLICT_REPORT_CAP = 10;
|
|
94
103
|
|
|
104
|
+
/** 消费端安装验证配方版本(与 prepare.mjs 一致)。 */
|
|
105
|
+
const CONSUMER_INSTALL_RECIPE_VERSION = 'consumer-install-v1';
|
|
106
|
+
|
|
95
107
|
// Consumer-owned transport metadata written into the plugin install root
|
|
96
108
|
// that is not part of the published payload (e.g. codex's root `.git`
|
|
97
109
|
// checkout and `.codex-plugin/migrated-command-skills/`, claude's `.in_use`
|
|
@@ -100,6 +112,58 @@ const PAYLOAD_CONFLICT_REPORT_CAP = 10;
|
|
|
100
112
|
// applies that list; declared-manifest-v1 verification never excludes
|
|
101
113
|
// anything — host-added files are recorded as `extraInstalledPaths` instead.
|
|
102
114
|
|
|
115
|
+
/**
|
|
116
|
+
* Resolve the marketplace root directory within a snapshot.
|
|
117
|
+
*
|
|
118
|
+
* For bundled-family layouts, the marketplace root is the directory that
|
|
119
|
+
* contains the platform's `.claude-plugin/marketplace.json` (or equivalent).
|
|
120
|
+
* Entry `source` paths in the marketplace index are relative to this root.
|
|
121
|
+
*
|
|
122
|
+
* Resolution rules:
|
|
123
|
+
* 1. If no explicit marketplaceIndexPath is provided, the root is "." (root layout).
|
|
124
|
+
* 2. If marketplaceIndexPath equals the platform default, the root is ".".
|
|
125
|
+
* 3. If marketplaceIndexPath ends with "/" + platform default (exact suffix match),
|
|
126
|
+
* the prefix is the marketplace root.
|
|
127
|
+
* 4. Otherwise, throws: the path is not a valid marketplace index path.
|
|
128
|
+
*
|
|
129
|
+
* Uses exact suffix matching only; never uses string `includes` to guess.
|
|
130
|
+
*
|
|
131
|
+
* @param {object} platform - Platform descriptor from the registry.
|
|
132
|
+
* @param {string} [marketplaceIndexPath] - Explicit marketplace index path override.
|
|
133
|
+
* @returns {string} Normalized marketplace root ("." for root layout).
|
|
134
|
+
* @throws {Error} If marketplaceIndexPath is provided but doesn't match expectations.
|
|
135
|
+
*/
|
|
136
|
+
export function resolveMarketplaceRoot(platform, marketplaceIndexPath) {
|
|
137
|
+
const defaultMarketplace = platform.manifestPaths.marketplace;
|
|
138
|
+
if (!defaultMarketplace) {
|
|
139
|
+
// Platform has no marketplace (kimi) — root concept doesn't apply.
|
|
140
|
+
return '.';
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (!marketplaceIndexPath || marketplaceIndexPath === defaultMarketplace) {
|
|
144
|
+
// No override or same as default → root layout.
|
|
145
|
+
return '.';
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Exact suffix match: marketplaceIndexPath must end with "/" + defaultMarketplace
|
|
149
|
+
const suffix = `/${defaultMarketplace}`;
|
|
150
|
+
if (marketplaceIndexPath.endsWith(suffix)) {
|
|
151
|
+
const root = marketplaceIndexPath.slice(0, -suffix.length);
|
|
152
|
+
if (root.length === 0) return '.';
|
|
153
|
+
// Validate root is a safe relative path (no escape)
|
|
154
|
+
if (root.startsWith('/') || root.includes('..') || root.includes('\\')) {
|
|
155
|
+
throw new Error(
|
|
156
|
+
`derived marketplace root "${root}" from marketplaceIndexPath "${marketplaceIndexPath}" is not a safe relative path`,
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
return root;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
throw new Error(
|
|
163
|
+
`marketplaceIndexPath "${marketplaceIndexPath}" does not end with platform default marketplace path "${defaultMarketplace}"`,
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
|
|
103
167
|
/**
|
|
104
168
|
* Extract the marketplace plugin entry's declared source as a validated,
|
|
105
169
|
* normalized snapshot-relative subpath ("." for root layouts).
|
|
@@ -139,6 +203,226 @@ function extractDeclaredPluginSource(consumer, entry) {
|
|
|
139
203
|
return segments.length === 0 ? '.' : segments.join('/');
|
|
140
204
|
}
|
|
141
205
|
|
|
206
|
+
/**
|
|
207
|
+
* 从市场索引中解析插件根目录并读取插件 manifest。
|
|
208
|
+
*
|
|
209
|
+
* 单一权威来源解析逻辑:
|
|
210
|
+
* 1. 从 marketIndex.plugins 中按 pluginName 筛选唯一匹配条目
|
|
211
|
+
* 2. 使用 extractDeclaredPluginSource 安全解析条目 source 字段
|
|
212
|
+
* 3. 将 source 安全拼接到 marketplaceRoot(快照绝对路径)并做逃逸检查
|
|
213
|
+
* 4. 在解析后的插件根目录下读取 platform.manifestPaths.plugin
|
|
214
|
+
*
|
|
215
|
+
* Kimi(无市场)返回 null。其他平台在市场索引为空或条目不唯一时失败关闭。
|
|
216
|
+
*
|
|
217
|
+
* @param {object|null} marketIndex - 市场索引对象(null 表示 Kimi 无市场)
|
|
218
|
+
* @param {string} pluginName - 要查找的插件名称
|
|
219
|
+
* @param {object} platform - 平台注册表条目
|
|
220
|
+
* @param {string} snapshotDir - 快照目录绝对路径
|
|
221
|
+
* @param {string} [marketplaceRootRel] - 市场根相对路径("." 或如 "adapters/claude");
|
|
222
|
+
* 默认 "."(根布局)。条目 source 相对此路径解析,而非快照根。
|
|
223
|
+
* @returns {Promise<{manifest: object, manifestRelativePath: string, pluginRoot: string}|null>}
|
|
224
|
+
*/
|
|
225
|
+
export async function resolvePluginManifestFromMarketplaceEntrySource(
|
|
226
|
+
marketIndex,
|
|
227
|
+
pluginName,
|
|
228
|
+
platform,
|
|
229
|
+
snapshotDir,
|
|
230
|
+
marketplaceRootRel = '.',
|
|
231
|
+
) {
|
|
232
|
+
// Kimi 无市场索引:返回 null(调用方使用平台级 manifest 规则)
|
|
233
|
+
if (platform.manifestPaths.marketplace === null && !marketIndex) {
|
|
234
|
+
return null;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (!marketIndex || typeof marketIndex !== 'object') {
|
|
238
|
+
throw new Error('marketplace index is required for non-kimi platforms');
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const plugins = Array.isArray(marketIndex.plugins) ? marketIndex.plugins : [];
|
|
242
|
+
const matches = plugins.filter((entry) => entry && entry.name === pluginName);
|
|
243
|
+
if (matches.length !== 1) {
|
|
244
|
+
throw new Error(
|
|
245
|
+
`marketplace index must contain exactly one plugin entry named "${pluginName}", found ${matches.length}`,
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const entry = matches[0];
|
|
250
|
+
const sourcePath = extractDeclaredPluginSource(platform.id, entry);
|
|
251
|
+
|
|
252
|
+
// 安全拼接:source 路径已在 extractDeclaredPluginSource 中验证无逃逸。
|
|
253
|
+
// source 相对市场根解析(marketplaceRootRel),而非快照根。
|
|
254
|
+
// 这里再做 realpath 二次确认防止符号链接逃逸。
|
|
255
|
+
// macOS 的 /var -> /private/var 符号链接会导致 snapshotDir 与 realpath 不同,
|
|
256
|
+
// 因此必须先将 snapshotDir 解析为真实路径再做 containment 检查。
|
|
257
|
+
const snapshotDirReal = await realpath(snapshotDir).catch(() => snapshotDir);
|
|
258
|
+
const marketplaceRootAbs = marketplaceRootRel === '.'
|
|
259
|
+
? snapshotDirReal
|
|
260
|
+
: resolve(snapshotDirReal, marketplaceRootRel);
|
|
261
|
+
const pluginRootAbs = resolve(marketplaceRootAbs, sourcePath);
|
|
262
|
+
const pluginRootReal = await realpath(pluginRootAbs).catch(() => null);
|
|
263
|
+
if (!pluginRootReal) {
|
|
264
|
+
throw new Error(`marketplace plugin entry source "${sourcePath}" does not exist in snapshot`);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// 逃逸检查:解析后的插件根必须在快照目录内
|
|
268
|
+
const containment = relative(snapshotDirReal, pluginRootReal);
|
|
269
|
+
const sep = process.platform === 'win32' ? '\\' : '/';
|
|
270
|
+
if (
|
|
271
|
+
containment !== '' &&
|
|
272
|
+
(isAbsolute(containment) || containment === '..' || containment.startsWith(`..${sep}`))
|
|
273
|
+
) {
|
|
274
|
+
throw new Error(`marketplace plugin entry source "${sourcePath}" escapes the snapshot after symlink resolution`);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// 读取插件 manifest
|
|
278
|
+
const manifestRelative = platform.manifestPaths.plugin;
|
|
279
|
+
const manifestAbs = resolve(pluginRootReal, manifestRelative);
|
|
280
|
+
|
|
281
|
+
// 计算 manifestRelativePath:相对快照根的完整路径
|
|
282
|
+
const pluginRootRelToSnapshot = relative(snapshotDirReal, pluginRootReal);
|
|
283
|
+
const fullManifestRelative = pluginRootRelToSnapshot === ''
|
|
284
|
+
? manifestRelative
|
|
285
|
+
: `${pluginRootRelToSnapshot}/${manifestRelative}`;
|
|
286
|
+
|
|
287
|
+
let raw;
|
|
288
|
+
try {
|
|
289
|
+
raw = await readFile(manifestAbs, 'utf8');
|
|
290
|
+
} catch (err) {
|
|
291
|
+
throw new Error(
|
|
292
|
+
`plugin manifest not found at "${fullManifestRelative}": ${err.message}`,
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
let manifest;
|
|
297
|
+
try {
|
|
298
|
+
manifest = JSON.parse(raw);
|
|
299
|
+
} catch (err) {
|
|
300
|
+
throw new Error(
|
|
301
|
+
`plugin manifest at "${fullManifestRelative}" is not valid JSON: ${err.message}`,
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
return {
|
|
306
|
+
manifest,
|
|
307
|
+
manifestRelativePath: fullManifestRelative,
|
|
308
|
+
pluginRoot: pluginRootRelToSnapshot || '.',
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* 验证 installationContractDigest:从冻结快照读取插件 manifest,
|
|
314
|
+
* 按平台注册表和来源形态构建安装契约,以 consumer-install-v1 重算摘要并比对。
|
|
315
|
+
*
|
|
316
|
+
* @param {object} action - 展开后的 action(parameters 已展开到顶层)
|
|
317
|
+
* @param {string} snapshotDirReal - 冻结快照的 realpath
|
|
318
|
+
* @param {object} platform - 平台注册表条目
|
|
319
|
+
* @returns {Promise<{valid: boolean, error: string|null}>}
|
|
320
|
+
*/
|
|
321
|
+
async function validateInstallationContractDigest(action, snapshotDirReal, platform) {
|
|
322
|
+
const form = action.marketplaceSourceType;
|
|
323
|
+
|
|
324
|
+
// 1. 确定是否纳入市场条目,并获取条目(manifest 读取依赖条目 source 路径)
|
|
325
|
+
const hasMarketplacePath = platform.manifestPaths.marketplace !== null;
|
|
326
|
+
let includeMarketplaceEntry = false;
|
|
327
|
+
let selectedMarketplaceEntry = null;
|
|
328
|
+
let marketplaceIndexRelative = null;
|
|
329
|
+
let marketIndexParsed = null;
|
|
330
|
+
|
|
331
|
+
if (form === 'bundled-family') {
|
|
332
|
+
includeMarketplaceEntry = hasMarketplacePath;
|
|
333
|
+
if (includeMarketplaceEntry) {
|
|
334
|
+
// 从快照中读取 bundled 市场索引,提取唯一匹配条目。
|
|
335
|
+
// marketplaceIndexPath 若存在(嵌套布局),用作完整索引路径;
|
|
336
|
+
// 否则使用平台默认路径(根布局)。
|
|
337
|
+
marketplaceIndexRelative = action.marketplaceIndexPath ?? platform.manifestPaths.marketplace;
|
|
338
|
+
const marketplacePath = resolve(snapshotDirReal, marketplaceIndexRelative);
|
|
339
|
+
const mkResult = await validateManifestFile(marketplacePath, ['name', 'plugins']);
|
|
340
|
+
if (!mkResult.valid) {
|
|
341
|
+
return { valid: false, error: `installationContractDigest 验证失败:市场索引读取失败:${mkResult.error}` };
|
|
342
|
+
}
|
|
343
|
+
const plugins = mkResult.manifest.plugins;
|
|
344
|
+
if (!Array.isArray(plugins)) {
|
|
345
|
+
return { valid: false, error: `installationContractDigest 验证失败:市场索引 plugins 非数组` };
|
|
346
|
+
}
|
|
347
|
+
const matches = plugins.filter((p) => p && p.name === action.plugin);
|
|
348
|
+
if (matches.length !== 1) {
|
|
349
|
+
return { valid: false, error: `installationContractDigest 验证失败:市场索引中匹配 "${action.plugin}" 的条目数量为 ${matches.length}` };
|
|
350
|
+
}
|
|
351
|
+
selectedMarketplaceEntry = matches[0];
|
|
352
|
+
marketIndexParsed = mkResult.manifest;
|
|
353
|
+
}
|
|
354
|
+
} else if (form === 'standalone-index') {
|
|
355
|
+
// 独立市场:Kimi 不纳入市场条目(Kimi 无市场 CLI,selectedEntry
|
|
356
|
+
// 仅供静态校验独立市场身份字段,不进入安装契约摘要)。
|
|
357
|
+
// Claude/Codex/CodeBuddy 使用 action 中的 selectedEntry 和 marketplaceIndexPath。
|
|
358
|
+
if (platform.id !== 'kimi' && action.selectedEntry && action.marketplaceIndexPath) {
|
|
359
|
+
includeMarketplaceEntry = true;
|
|
360
|
+
selectedMarketplaceEntry = action.selectedEntry;
|
|
361
|
+
marketplaceIndexRelative = action.marketplaceIndexPath;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// 2. 读取插件 manifest:使用市场条目 source 路径解析(单一权威 helper)。
|
|
366
|
+
// bundled-family + 有市场索引:通过 resolvePluginManifestFromMarketplaceEntrySource
|
|
367
|
+
// 从条目 source 安全解析插件根并读取 manifest。
|
|
368
|
+
// 其他路径:保留原有策略。
|
|
369
|
+
let pluginManifestParsed;
|
|
370
|
+
let pluginManifestRelative;
|
|
371
|
+
|
|
372
|
+
if (form === 'bundled-family' && marketIndexParsed && platform.marketplaceSourceForm !== null) {
|
|
373
|
+
// bundled-family 有市场索引且平台支持市场来源解析(Claude/Codex):
|
|
374
|
+
// 使用权威 helper 从条目 source 解析。
|
|
375
|
+
// 计算市场根:从 marketplaceIndexRelative 推断(精确后缀匹配)。
|
|
376
|
+
const mktRoot = resolveMarketplaceRoot(platform, marketplaceIndexRelative);
|
|
377
|
+
try {
|
|
378
|
+
const resolved = await resolvePluginManifestFromMarketplaceEntrySource(
|
|
379
|
+
marketIndexParsed, action.plugin, platform, snapshotDirReal, mktRoot,
|
|
380
|
+
);
|
|
381
|
+
pluginManifestParsed = resolved.manifest;
|
|
382
|
+
pluginManifestRelative = resolved.manifestRelativePath;
|
|
383
|
+
} catch (err) {
|
|
384
|
+
return { valid: false, error: `installationContractDigest 验证失败:${err.message}` };
|
|
385
|
+
}
|
|
386
|
+
} else if (platform.strategy.readManifest) {
|
|
387
|
+
// Kimi/CodeBuddy/Codex 有自定义 manifest 读取策略
|
|
388
|
+
const readResult = await platform.strategy.readManifest(snapshotDirReal);
|
|
389
|
+
pluginManifestParsed = readResult.manifest;
|
|
390
|
+
pluginManifestRelative = readResult.manifestRelative ?? platform.manifestPaths.plugin;
|
|
391
|
+
} else {
|
|
392
|
+
// Claude fallback(standalone-index 或无市场索引时)
|
|
393
|
+
pluginManifestRelative = platform.manifestPaths.plugin;
|
|
394
|
+
const pluginManifestPath = resolve(snapshotDirReal, pluginManifestRelative);
|
|
395
|
+
const manifestResult = await validateManifestFile(pluginManifestPath, ['name', 'version']);
|
|
396
|
+
if (!manifestResult.valid) {
|
|
397
|
+
return { valid: false, error: `installationContractDigest 验证失败:插件 manifest 读取失败:${manifestResult.error}` };
|
|
398
|
+
}
|
|
399
|
+
pluginManifestParsed = manifestResult.manifest;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// 3. 使用权威构建/摘要函数重算
|
|
403
|
+
const computedDigest = computeInstallationContractDigest({
|
|
404
|
+
distributionType: platform.distributionType,
|
|
405
|
+
manifestRelativePath: pluginManifestRelative,
|
|
406
|
+
manifest: pluginManifestParsed,
|
|
407
|
+
marketplaceSourceType: form,
|
|
408
|
+
includeMarketplaceEntry,
|
|
409
|
+
...(includeMarketplaceEntry ? {
|
|
410
|
+
marketplaceIndexRelativePath: marketplaceIndexRelative,
|
|
411
|
+
selectedMarketplaceEntry,
|
|
412
|
+
} : {}),
|
|
413
|
+
verificationRecipeVersion: CONSUMER_INSTALL_RECIPE_VERSION,
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
if (computedDigest !== action.installationContractDigest) {
|
|
417
|
+
return {
|
|
418
|
+
valid: false,
|
|
419
|
+
error: `installationContractDigest 不匹配:预期 ${action.installationContractDigest.slice(0, 16)}...,重算 ${computedDigest.slice(0, 16)}...`,
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
return { valid: true, error: null };
|
|
424
|
+
}
|
|
425
|
+
|
|
142
426
|
/**
|
|
143
427
|
* Resolve the payload subpath the consumer CLI installs for this action,
|
|
144
428
|
* read from the marketplace manifest inside the digest-verified frozen
|
|
@@ -564,6 +848,49 @@ const SUPPORTED_TYPES = [
|
|
|
564
848
|
ActionType.CODEBUDDY_MARKETPLACE_INSTALL,
|
|
565
849
|
];
|
|
566
850
|
|
|
851
|
+
/**
|
|
852
|
+
* Classify whether an error indicates CLI/transport unavailability.
|
|
853
|
+
*
|
|
854
|
+
* ONLY these errors trigger human-attestation fallback for Codex:
|
|
855
|
+
* - ENOENT: binary not found
|
|
856
|
+
* - ETIMEDOUT/ECONNREFUSED/ECONNRESET: connection errors
|
|
857
|
+
* - ENOTFOUND: DNS resolution failure
|
|
858
|
+
* - spawn errors with specific codes
|
|
859
|
+
*
|
|
860
|
+
* All other errors (identity mismatch, malformed JSON, program bugs)
|
|
861
|
+
* are hard failures and NEVER trigger fallback.
|
|
862
|
+
*
|
|
863
|
+
* @param {Error} err
|
|
864
|
+
* @returns {boolean}
|
|
865
|
+
*/
|
|
866
|
+
function isCliOrTransportUnavailable(err) {
|
|
867
|
+
if (!err || typeof err !== 'object') return false;
|
|
868
|
+
|
|
869
|
+
// Node.js system error codes for CLI/transport unavailability
|
|
870
|
+
const UNAVAILABILITY_CODES = new Set([
|
|
871
|
+
'ENOENT', // binary not found
|
|
872
|
+
'ETIMEDOUT', // connection timeout
|
|
873
|
+
'ECONNREFUSED', // connection refused
|
|
874
|
+
'ECONNRESET', // connection reset
|
|
875
|
+
'ENOTFOUND', // DNS resolution failure
|
|
876
|
+
'EAI_AGAIN', // DNS temporary failure
|
|
877
|
+
'EHOSTUNREACH', // host unreachable
|
|
878
|
+
'ENETUNREACH', // network unreachable
|
|
879
|
+
]);
|
|
880
|
+
|
|
881
|
+
if (err.code && UNAVAILABILITY_CODES.has(err.code)) {
|
|
882
|
+
return true;
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
// Check error message for spawn/ENOENT patterns
|
|
886
|
+
const msg = err.message || '';
|
|
887
|
+
if (msg.includes('ENOENT') || msg.includes('spawn')) {
|
|
888
|
+
return true;
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
return false;
|
|
892
|
+
}
|
|
893
|
+
|
|
567
894
|
/** Safe repo pattern: owner/repo with alphanumeric, hyphens, dots, underscores. */
|
|
568
895
|
const SAFE_REPO_RE = /^[a-zA-Z0-9][a-zA-Z0-9._-]*\/[a-zA-Z0-9][a-zA-Z0-9._-]*$/;
|
|
569
896
|
|
|
@@ -736,6 +1063,98 @@ async function checkRequiredFiles(dir, requiredFiles) {
|
|
|
736
1063
|
return { allPresent: missing.length === 0, missing };
|
|
737
1064
|
}
|
|
738
1065
|
|
|
1066
|
+
// ---------------------------------------------------------------------------
|
|
1067
|
+
// 市场来源单选模型(DT-D)
|
|
1068
|
+
// ---------------------------------------------------------------------------
|
|
1069
|
+
|
|
1070
|
+
/**
|
|
1071
|
+
* 市场来源类型常量。
|
|
1072
|
+
*
|
|
1073
|
+
* - BUNDLED_FAMILY: 技能族自带市场文件(marketplace.json 与插件代码在同一仓库)
|
|
1074
|
+
* - STANDALONE_INDEX: 独立市场仓库(市场索引在外部仓库,插件代码在另一仓库)
|
|
1075
|
+
*/
|
|
1076
|
+
export const MARKETPLACE_SOURCE_TYPES = Object.freeze({
|
|
1077
|
+
BUNDLED_FAMILY: 'bundled-family',
|
|
1078
|
+
STANDALONE_INDEX: 'standalone-index',
|
|
1079
|
+
});
|
|
1080
|
+
|
|
1081
|
+
/**
|
|
1082
|
+
* 每个平台支持的市场来源类型。
|
|
1083
|
+
*
|
|
1084
|
+
* 所有平台均支持 bundled-family 和 standalone-index 两种来源形态。
|
|
1085
|
+
* 不得硬编码某个平台只能使用其中一种。
|
|
1086
|
+
*/
|
|
1087
|
+
const PLATFORM_SUPPORTED_SOURCES = Object.freeze({
|
|
1088
|
+
claude: new Set([MARKETPLACE_SOURCE_TYPES.BUNDLED_FAMILY, MARKETPLACE_SOURCE_TYPES.STANDALONE_INDEX]),
|
|
1089
|
+
codex: new Set([MARKETPLACE_SOURCE_TYPES.BUNDLED_FAMILY, MARKETPLACE_SOURCE_TYPES.STANDALONE_INDEX]),
|
|
1090
|
+
kimi: new Set([MARKETPLACE_SOURCE_TYPES.BUNDLED_FAMILY, MARKETPLACE_SOURCE_TYPES.STANDALONE_INDEX]),
|
|
1091
|
+
codebuddy: new Set([MARKETPLACE_SOURCE_TYPES.BUNDLED_FAMILY, MARKETPLACE_SOURCE_TYPES.STANDALONE_INDEX]),
|
|
1092
|
+
});
|
|
1093
|
+
|
|
1094
|
+
/**
|
|
1095
|
+
* 验证市场来源单选约束:每个平台每次发布只允许一种来源。
|
|
1096
|
+
*
|
|
1097
|
+
* 根据平台能力和配置/计划中的市场来源类型进行校验:
|
|
1098
|
+
* 1. 混用两种来源失败关闭
|
|
1099
|
+
* 2. 不支持的来源类型失败关闭
|
|
1100
|
+
*
|
|
1101
|
+
* @param {string} platform - 平台标识(claude / codex / kimi / codebuddy)
|
|
1102
|
+
* @param {object} config - 项目配置(releaseUnit.distributions 中的一项)
|
|
1103
|
+
* @param {object} plan - 发布计划(units.distributions 中的一项)
|
|
1104
|
+
* @returns {{ valid: boolean, error: string|null, selectedSource: string|null }}
|
|
1105
|
+
*/
|
|
1106
|
+
export function validateMarketplaceSourceSelection(platform, config, plan) {
|
|
1107
|
+
// 每个平台只允许一种来源
|
|
1108
|
+
const configSource = config?.marketplaceSourceType ?? null;
|
|
1109
|
+
const planSource = plan?.marketplaceSourceType ?? null;
|
|
1110
|
+
|
|
1111
|
+
// 确定选定的来源类型
|
|
1112
|
+
const selectedSource = planSource ?? configSource;
|
|
1113
|
+
|
|
1114
|
+
if (configSource && planSource && configSource !== planSource) {
|
|
1115
|
+
return {
|
|
1116
|
+
valid: false,
|
|
1117
|
+
error: `平台 "${platform}" 市场来源混用:配置声明 "${configSource}",计划声明 "${planSource}";每次发布只允许一种来源`,
|
|
1118
|
+
selectedSource: null,
|
|
1119
|
+
};
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
// 如果没有声明来源类型,无法验证(向后兼容旧配置)
|
|
1123
|
+
if (!selectedSource) {
|
|
1124
|
+
return { valid: true, error: null, selectedSource: null };
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
// 检查来源类型是否为已知值
|
|
1128
|
+
const knownSources = new Set(Object.values(MARKETPLACE_SOURCE_TYPES));
|
|
1129
|
+
if (!knownSources.has(selectedSource)) {
|
|
1130
|
+
return {
|
|
1131
|
+
valid: false,
|
|
1132
|
+
error: `未知的市场来源类型 "${selectedSource}";合法值为 ${[...knownSources].join(', ')}`,
|
|
1133
|
+
selectedSource: null,
|
|
1134
|
+
};
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
// 检查平台是否支持该来源类型
|
|
1138
|
+
const supportedSources = PLATFORM_SUPPORTED_SOURCES[platform];
|
|
1139
|
+
if (!supportedSources) {
|
|
1140
|
+
return {
|
|
1141
|
+
valid: false,
|
|
1142
|
+
error: `未知的平台 "${platform}"`,
|
|
1143
|
+
selectedSource: null,
|
|
1144
|
+
};
|
|
1145
|
+
}
|
|
1146
|
+
if (!supportedSources.has(selectedSource)) {
|
|
1147
|
+
const supportedList = [...supportedSources].join(', ');
|
|
1148
|
+
return {
|
|
1149
|
+
valid: false,
|
|
1150
|
+
error: `平台 "${platform}" 不支持市场来源 "${selectedSource}";该平台仅支持: ${supportedList}`,
|
|
1151
|
+
selectedSource: null,
|
|
1152
|
+
};
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
return { valid: true, error: null, selectedSource };
|
|
1156
|
+
}
|
|
1157
|
+
|
|
739
1158
|
/**
|
|
740
1159
|
* Create the plugin-marketplace adapter.
|
|
741
1160
|
*
|
|
@@ -915,55 +1334,88 @@ export function createPluginMarketplaceAdapter(deps = {}) {
|
|
|
915
1334
|
});
|
|
916
1335
|
}
|
|
917
1336
|
|
|
918
|
-
// 6.
|
|
919
|
-
//
|
|
920
|
-
//
|
|
921
|
-
//
|
|
1337
|
+
// 6. 统一静态预检:四平台共同执行安装来源与安装契约校验。
|
|
1338
|
+
// 旧冻结计划(整组新版字段完全不存在时)保留既有路径。
|
|
1339
|
+
// 新版字段组(installationContractDigest、algorithmVersion、marketplaceSourceType 为标志)
|
|
1340
|
+
// 出现任一字段时必须完整。0.2.3 已有的 marketplaceForm、sourceDescriptor、sourceCommit
|
|
1341
|
+
// 单独出现不得触发新版组。
|
|
922
1342
|
const route = resolvePlatformRoute(consumerPlatform);
|
|
923
|
-
|
|
1343
|
+
|
|
1344
|
+
// 6a. 新版字段组检测:installationContractDigest、algorithmVersion、marketplaceSourceType
|
|
1345
|
+
// 任一存在时走新路径
|
|
1346
|
+
const NEW_FIELD_GROUP_MARKERS = ['installationContractDigest', 'algorithmVersion', 'marketplaceSourceType'];
|
|
1347
|
+
const hasNewFieldGroup = NEW_FIELD_GROUP_MARKERS.some(
|
|
1348
|
+
(f) => action[f] !== undefined && action[f] !== null,
|
|
1349
|
+
);
|
|
1350
|
+
|
|
1351
|
+
if (hasNewFieldGroup) {
|
|
1352
|
+
// 新版字段组完整性:任一出现则要求全组完整
|
|
1353
|
+
const newFields = [
|
|
1354
|
+
'marketplaceForm', 'sourceDescriptor', 'installationContractDigest',
|
|
1355
|
+
'algorithmVersion', 'sourceCommit', 'marketplaceSourceType',
|
|
1356
|
+
];
|
|
1357
|
+
const missingNewFields = newFields.filter((f) => action[f] === undefined || action[f] === null);
|
|
1358
|
+
if (missingNewFields.length > 0) {
|
|
1359
|
+
return createResult({
|
|
1360
|
+
actionType,
|
|
1361
|
+
status: ActionStatus.PREFLIGHT_FAILED,
|
|
1362
|
+
error: `新版字段组不完整,缺失: ${missingNewFields.join(', ')};任一出现则要求全组完整`,
|
|
1363
|
+
});
|
|
1364
|
+
}
|
|
1365
|
+
|
|
924
1366
|
const sd = action.sourceDescriptor;
|
|
925
1367
|
if (!sd || typeof sd !== 'object') {
|
|
926
1368
|
return createResult({
|
|
927
1369
|
actionType,
|
|
928
1370
|
status: ActionStatus.PREFLIGHT_FAILED,
|
|
929
|
-
error: 'sourceDescriptor is required
|
|
1371
|
+
error: 'sourceDescriptor is required when installationContractDigest is present',
|
|
930
1372
|
});
|
|
931
1373
|
}
|
|
932
1374
|
|
|
933
|
-
//
|
|
934
|
-
if (sd.form !== action.
|
|
1375
|
+
// marketplaceForm 三方一致性
|
|
1376
|
+
if (action.marketplaceForm !== sd.form || action.marketplaceForm !== action.marketplaceSourceType) {
|
|
935
1377
|
return createResult({
|
|
936
1378
|
actionType,
|
|
937
1379
|
status: ActionStatus.PREFLIGHT_FAILED,
|
|
938
|
-
error: `sourceDescriptor.form "${sd.form}"
|
|
1380
|
+
error: `marketplaceForm "${action.marketplaceForm}"、sourceDescriptor.form "${sd.form}"、marketplaceSourceType "${action.marketplaceSourceType}" 三者必须一致`,
|
|
939
1381
|
});
|
|
940
1382
|
}
|
|
941
1383
|
|
|
942
|
-
//
|
|
943
|
-
|
|
944
|
-
if (typeof sd.payloadDigest !== 'string' || sd.payloadDigest.length === 0) {
|
|
1384
|
+
// installationContractDigest 格式
|
|
1385
|
+
if (!SAFE_DIGEST_RE.test(action.installationContractDigest)) {
|
|
945
1386
|
return createResult({
|
|
946
1387
|
actionType,
|
|
947
1388
|
status: ActionStatus.PREFLIGHT_FAILED,
|
|
948
|
-
error: '
|
|
1389
|
+
error: 'installationContractDigest must be a 64-char lowercase hex string',
|
|
949
1390
|
});
|
|
950
1391
|
}
|
|
951
|
-
|
|
1392
|
+
|
|
1393
|
+
// algorithmVersion
|
|
1394
|
+
if (action.algorithmVersion !== INSTALLATION_CONTRACT_ALGORITHM_VERSION) {
|
|
952
1395
|
return createResult({
|
|
953
1396
|
actionType,
|
|
954
1397
|
status: ActionStatus.PREFLIGHT_FAILED,
|
|
955
|
-
error:
|
|
1398
|
+
error: `algorithmVersion must be ${INSTALLATION_CONTRACT_ALGORITHM_VERSION}, got ${action.algorithmVersion}`,
|
|
956
1399
|
});
|
|
957
1400
|
}
|
|
958
|
-
|
|
1401
|
+
|
|
1402
|
+
// sourceCommit 格式
|
|
1403
|
+
if (typeof action.sourceCommit !== 'string' || !/^[0-9a-f]{40}$/.test(action.sourceCommit)) {
|
|
959
1404
|
return createResult({
|
|
960
1405
|
actionType,
|
|
961
1406
|
status: ActionStatus.PREFLIGHT_FAILED,
|
|
962
|
-
error: '
|
|
1407
|
+
error: 'sourceCommit must be a 40-char lowercase hex commit sha',
|
|
963
1408
|
});
|
|
964
1409
|
}
|
|
965
1410
|
|
|
966
|
-
//
|
|
1411
|
+
// sourceDescriptor 共通字段
|
|
1412
|
+
if (typeof sd.form !== 'string' || (sd.form !== 'bundled-family' && sd.form !== 'standalone-index')) {
|
|
1413
|
+
return createResult({
|
|
1414
|
+
actionType,
|
|
1415
|
+
status: ActionStatus.PREFLIGHT_FAILED,
|
|
1416
|
+
error: `sourceDescriptor.form must be "bundled-family" or "standalone-index", got "${sd.form}"`,
|
|
1417
|
+
});
|
|
1418
|
+
}
|
|
967
1419
|
if (typeof sd.marketplaceEntry !== 'string' || sd.marketplaceEntry.length === 0) {
|
|
968
1420
|
return createResult({
|
|
969
1421
|
actionType,
|
|
@@ -979,13 +1431,14 @@ export function createPluginMarketplaceAdapter(deps = {}) {
|
|
|
979
1431
|
});
|
|
980
1432
|
}
|
|
981
1433
|
|
|
982
|
-
//
|
|
1434
|
+
// 来源形态专属字段校验
|
|
983
1435
|
if (sd.form === 'bundled-family') {
|
|
1436
|
+
// bundled-family 完整性
|
|
984
1437
|
if (!sd.repo || !SAFE_REPO_RE.test(sd.repo)) {
|
|
985
1438
|
return createResult({
|
|
986
1439
|
actionType,
|
|
987
1440
|
status: ActionStatus.PREFLIGHT_FAILED,
|
|
988
|
-
error:
|
|
1441
|
+
error: 'sourceDescriptor.repo is required and must be a safe repo pattern for bundled-family form',
|
|
989
1442
|
});
|
|
990
1443
|
}
|
|
991
1444
|
if (sd.repo !== action.repo) {
|
|
@@ -995,37 +1448,32 @@ export function createPluginMarketplaceAdapter(deps = {}) {
|
|
|
995
1448
|
error: `sourceDescriptor.repo "${sd.repo}" does not match action.repo "${action.repo}"`,
|
|
996
1449
|
});
|
|
997
1450
|
}
|
|
998
|
-
|
|
999
|
-
if (sd.commit !== undefined && (typeof sd.commit !== 'string' || !/^[0-9a-f]{40}$/.test(sd.commit))) {
|
|
1451
|
+
if (typeof sd.commit !== 'string' || !/^[0-9a-f]{40}$/.test(sd.commit)) {
|
|
1000
1452
|
return createResult({
|
|
1001
1453
|
actionType,
|
|
1002
1454
|
status: ActionStatus.PREFLIGHT_FAILED,
|
|
1003
1455
|
error: 'sourceDescriptor.commit must be a 40-hex commit sha for bundled-family form',
|
|
1004
1456
|
});
|
|
1005
1457
|
}
|
|
1006
|
-
|
|
1007
|
-
// action.sourceCommit(冻结的插件来源提交)一致
|
|
1008
|
-
if (sd.commit !== undefined && action.sourceCommit && sd.commit !== action.sourceCommit) {
|
|
1458
|
+
if (sd.commit !== action.sourceCommit) {
|
|
1009
1459
|
return createResult({
|
|
1010
1460
|
actionType,
|
|
1011
1461
|
status: ActionStatus.PREFLIGHT_FAILED,
|
|
1012
1462
|
error: `sourceDescriptor.commit "${sd.commit}" does not match action.sourceCommit "${action.sourceCommit}"`,
|
|
1013
1463
|
});
|
|
1014
1464
|
}
|
|
1015
|
-
|
|
1016
|
-
if (sd.payloadDigest !== undefined && (!sd.payloadDigest || !SAFE_DIGEST_RE.test(sd.payloadDigest))) {
|
|
1465
|
+
if (!sd.payloadDigest || !SAFE_DIGEST_RE.test(sd.payloadDigest)) {
|
|
1017
1466
|
return createResult({
|
|
1018
1467
|
actionType,
|
|
1019
1468
|
status: ActionStatus.PREFLIGHT_FAILED,
|
|
1020
|
-
error: 'sourceDescriptor.payloadDigest
|
|
1469
|
+
error: 'sourceDescriptor.payloadDigest must be a 64-hex digest for bundled-family form',
|
|
1021
1470
|
});
|
|
1022
1471
|
}
|
|
1023
|
-
|
|
1024
|
-
if (sd.payloadDigest !== undefined && action.manifestDigest && sd.payloadDigest !== action.manifestDigest) {
|
|
1472
|
+
if (sd.payloadDigest !== action.manifestDigest) {
|
|
1025
1473
|
return createResult({
|
|
1026
1474
|
actionType,
|
|
1027
1475
|
status: ActionStatus.PREFLIGHT_FAILED,
|
|
1028
|
-
error:
|
|
1476
|
+
error: 'sourceDescriptor.payloadDigest does not match action.manifestDigest',
|
|
1029
1477
|
});
|
|
1030
1478
|
}
|
|
1031
1479
|
if (typeof sd.pluginSubpath !== 'string' || sd.pluginSubpath.length === 0) {
|
|
@@ -1035,14 +1483,48 @@ export function createPluginMarketplaceAdapter(deps = {}) {
|
|
|
1035
1483
|
error: 'sourceDescriptor.pluginSubpath is required for bundled-family form',
|
|
1036
1484
|
});
|
|
1037
1485
|
}
|
|
1038
|
-
|
|
1039
|
-
if (
|
|
1486
|
+
// 不允许独立市场专属字段(sourceDescriptor 层)
|
|
1487
|
+
if (sd.marketplaceRepo !== undefined || sd.marketplaceCommitSha !== undefined || sd.pluginRepo !== undefined) {
|
|
1488
|
+
return createResult({
|
|
1489
|
+
actionType,
|
|
1490
|
+
status: ActionStatus.PREFLIGHT_FAILED,
|
|
1491
|
+
error: 'bundled-family sourceDescriptor must not contain standalone-index fields (marketplaceRepo, marketplaceCommitSha, pluginRepo)',
|
|
1492
|
+
});
|
|
1493
|
+
}
|
|
1494
|
+
// 不允许 action 顶层独立市场专属字段,避免双来源混用。
|
|
1495
|
+
// marketplaceIndexPath 对 bundled-family 嵌套布局也合法(指示市场根位置),
|
|
1496
|
+
// 因此仅禁止真正的 standalone-index 专属字段。
|
|
1497
|
+
if (action.marketplaceCommitSha !== undefined || action.marketplaceName !== undefined || action.selectedEntry !== undefined) {
|
|
1040
1498
|
return createResult({
|
|
1041
1499
|
actionType,
|
|
1042
1500
|
status: ActionStatus.PREFLIGHT_FAILED,
|
|
1043
|
-
error:
|
|
1501
|
+
error: 'bundled-family action must not contain standalone-index fields (marketplaceCommitSha, marketplaceName, selectedEntry)',
|
|
1044
1502
|
});
|
|
1045
1503
|
}
|
|
1504
|
+
// bundled-family marketplaceIndexPath 格式校验(可选,嵌套布局时存在)
|
|
1505
|
+
if (action.marketplaceIndexPath !== undefined && action.marketplaceIndexPath !== null) {
|
|
1506
|
+
if (typeof action.marketplaceIndexPath !== 'string' || action.marketplaceIndexPath.length === 0) {
|
|
1507
|
+
return createResult({
|
|
1508
|
+
actionType,
|
|
1509
|
+
status: ActionStatus.PREFLIGHT_FAILED,
|
|
1510
|
+
error: 'bundled-family marketplaceIndexPath must be a non-empty string when present',
|
|
1511
|
+
});
|
|
1512
|
+
}
|
|
1513
|
+
if (
|
|
1514
|
+
action.marketplaceIndexPath.startsWith('/') ||
|
|
1515
|
+
action.marketplaceIndexPath.includes('..') ||
|
|
1516
|
+
action.marketplaceIndexPath.includes('\\') ||
|
|
1517
|
+
/^https?:\/\//i.test(action.marketplaceIndexPath)
|
|
1518
|
+
) {
|
|
1519
|
+
return createResult({
|
|
1520
|
+
actionType,
|
|
1521
|
+
status: ActionStatus.PREFLIGHT_FAILED,
|
|
1522
|
+
error: `marketplaceIndexPath "${action.marketplaceIndexPath}" is not a safe relative path`,
|
|
1523
|
+
});
|
|
1524
|
+
}
|
|
1525
|
+
}
|
|
1526
|
+
} else {
|
|
1527
|
+
// standalone-index 完整性
|
|
1046
1528
|
if (!sd.marketplaceRepo || !SAFE_REPO_RE.test(sd.marketplaceRepo)) {
|
|
1047
1529
|
return createResult({
|
|
1048
1530
|
actionType,
|
|
@@ -1050,8 +1532,13 @@ export function createPluginMarketplaceAdapter(deps = {}) {
|
|
|
1050
1532
|
error: 'sourceDescriptor.marketplaceRepo is required for standalone-index form',
|
|
1051
1533
|
});
|
|
1052
1534
|
}
|
|
1053
|
-
|
|
1054
|
-
|
|
1535
|
+
if (!sd.pluginRepo || !SAFE_REPO_RE.test(sd.pluginRepo)) {
|
|
1536
|
+
return createResult({
|
|
1537
|
+
actionType,
|
|
1538
|
+
status: ActionStatus.PREFLIGHT_FAILED,
|
|
1539
|
+
error: 'sourceDescriptor.pluginRepo is required for standalone-index form',
|
|
1540
|
+
});
|
|
1541
|
+
}
|
|
1055
1542
|
if (sd.marketplaceRepo !== action.repo) {
|
|
1056
1543
|
return createResult({
|
|
1057
1544
|
actionType,
|
|
@@ -1059,7 +1546,6 @@ export function createPluginMarketplaceAdapter(deps = {}) {
|
|
|
1059
1546
|
error: `sourceDescriptor.marketplaceRepo "${sd.marketplaceRepo}" does not match action.repo "${action.repo}"`,
|
|
1060
1547
|
});
|
|
1061
1548
|
}
|
|
1062
|
-
// 2. pluginRepo 必须与 marketplaceRepo 不同(发布单元仓库 ≠ 市场仓库)
|
|
1063
1549
|
if (sd.pluginRepo === sd.marketplaceRepo) {
|
|
1064
1550
|
return createResult({
|
|
1065
1551
|
actionType,
|
|
@@ -1067,22 +1553,27 @@ export function createPluginMarketplaceAdapter(deps = {}) {
|
|
|
1067
1553
|
error: `sourceDescriptor.pluginRepo "${sd.pluginRepo}" must differ from marketplaceRepo "${sd.marketplaceRepo}"`,
|
|
1068
1554
|
});
|
|
1069
1555
|
}
|
|
1070
|
-
if (
|
|
1071
|
-
typeof sd.marketplaceCommitSha !== 'string'
|
|
1072
|
-
|| !/^[0-9a-f]{40}$/.test(sd.marketplaceCommitSha)
|
|
1073
|
-
) {
|
|
1556
|
+
if (typeof sd.marketplaceCommitSha !== 'string' || !/^[0-9a-f]{40}$/.test(sd.marketplaceCommitSha)) {
|
|
1074
1557
|
return createResult({
|
|
1075
1558
|
actionType,
|
|
1076
1559
|
status: ActionStatus.PREFLIGHT_FAILED,
|
|
1077
1560
|
error: 'sourceDescriptor.marketplaceCommitSha must be a 40-hex commit sha for standalone-index form',
|
|
1078
1561
|
});
|
|
1079
1562
|
}
|
|
1080
|
-
//
|
|
1081
|
-
|
|
1563
|
+
// action 顶层 marketplaceCommitSha 必须存在、为 40 位小写提交 SHA,
|
|
1564
|
+
// 且等于 sourceDescriptor.marketplaceCommitSha
|
|
1565
|
+
if (typeof action.marketplaceCommitSha !== 'string' || !/^[0-9a-f]{40}$/.test(action.marketplaceCommitSha)) {
|
|
1566
|
+
return createResult({
|
|
1567
|
+
actionType,
|
|
1568
|
+
status: ActionStatus.PREFLIGHT_FAILED,
|
|
1569
|
+
error: 'marketplaceCommitSha is required at action top level for standalone-index form',
|
|
1570
|
+
});
|
|
1571
|
+
}
|
|
1572
|
+
if (sd.marketplaceCommitSha !== action.marketplaceCommitSha) {
|
|
1082
1573
|
return createResult({
|
|
1083
1574
|
actionType,
|
|
1084
1575
|
status: ActionStatus.PREFLIGHT_FAILED,
|
|
1085
|
-
error: `sourceDescriptor.marketplaceCommitSha
|
|
1576
|
+
error: `sourceDescriptor.marketplaceCommitSha does not match action.marketplaceCommitSha`,
|
|
1086
1577
|
});
|
|
1087
1578
|
}
|
|
1088
1579
|
if (typeof sd.ref !== 'string' || sd.ref.length === 0) {
|
|
@@ -1092,46 +1583,320 @@ export function createPluginMarketplaceAdapter(deps = {}) {
|
|
|
1092
1583
|
error: 'sourceDescriptor.ref is required for standalone-index form',
|
|
1093
1584
|
});
|
|
1094
1585
|
}
|
|
1095
|
-
|
|
1096
|
-
|
|
1586
|
+
if (sd.ref !== action.ref) {
|
|
1587
|
+
return createResult({
|
|
1588
|
+
actionType,
|
|
1589
|
+
status: ActionStatus.PREFLIGHT_FAILED,
|
|
1590
|
+
error: `sourceDescriptor.ref "${sd.ref}" does not match action.ref "${action.ref}"`,
|
|
1591
|
+
});
|
|
1592
|
+
}
|
|
1593
|
+
// payloadDigest 必须存在、为合法摘要且等于 manifestDigest(不能可选)
|
|
1594
|
+
if (!sd.payloadDigest || !SAFE_DIGEST_RE.test(sd.payloadDigest)) {
|
|
1097
1595
|
return createResult({
|
|
1098
1596
|
actionType,
|
|
1099
1597
|
status: ActionStatus.PREFLIGHT_FAILED,
|
|
1100
1598
|
error: 'sourceDescriptor.payloadDigest must be a 64-hex digest for standalone-index form',
|
|
1101
1599
|
});
|
|
1102
1600
|
}
|
|
1103
|
-
|
|
1104
|
-
if (sd.payloadDigest !== undefined && action.manifestDigest && sd.payloadDigest !== action.manifestDigest) {
|
|
1601
|
+
if (sd.payloadDigest !== action.manifestDigest) {
|
|
1105
1602
|
return createResult({
|
|
1106
1603
|
actionType,
|
|
1107
1604
|
status: ActionStatus.PREFLIGHT_FAILED,
|
|
1108
|
-
error:
|
|
1605
|
+
error: 'sourceDescriptor.payloadDigest does not match action.manifestDigest',
|
|
1109
1606
|
});
|
|
1110
1607
|
}
|
|
1111
|
-
|
|
1608
|
+
// 不允许 bundled-family 专属字段
|
|
1609
|
+
if (sd.commit !== undefined || sd.pluginSubpath !== undefined) {
|
|
1610
|
+
return createResult({
|
|
1611
|
+
actionType,
|
|
1612
|
+
status: ActionStatus.PREFLIGHT_FAILED,
|
|
1613
|
+
error: 'standalone-index sourceDescriptor must not contain bundled-family fields (commit, pluginSubpath)',
|
|
1614
|
+
});
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1617
|
+
|
|
1618
|
+
// standalone-index 独立市场专属字段校验(action 层)
|
|
1619
|
+
if (action.marketplaceSourceType === 'standalone-index') {
|
|
1620
|
+
// marketplaceIndexPath 必须是安全非空相对路径
|
|
1621
|
+
if (typeof action.marketplaceIndexPath !== 'string' || action.marketplaceIndexPath.length === 0) {
|
|
1622
|
+
return createResult({
|
|
1623
|
+
actionType,
|
|
1624
|
+
status: ActionStatus.PREFLIGHT_FAILED,
|
|
1625
|
+
error: 'marketplaceIndexPath is required for standalone-index form',
|
|
1626
|
+
});
|
|
1627
|
+
}
|
|
1628
|
+
if (
|
|
1629
|
+
action.marketplaceIndexPath.startsWith('/') ||
|
|
1630
|
+
action.marketplaceIndexPath.includes('..') ||
|
|
1631
|
+
action.marketplaceIndexPath.includes('\\') ||
|
|
1632
|
+
/^https?:\/\//i.test(action.marketplaceIndexPath)
|
|
1633
|
+
) {
|
|
1634
|
+
return createResult({
|
|
1635
|
+
actionType,
|
|
1636
|
+
status: ActionStatus.PREFLIGHT_FAILED,
|
|
1637
|
+
error: `marketplaceIndexPath "${action.marketplaceIndexPath}" is not a safe relative path`,
|
|
1638
|
+
});
|
|
1639
|
+
}
|
|
1640
|
+
// marketplaceName 必须是安全非空标识
|
|
1641
|
+
if (typeof action.marketplaceName !== 'string' || action.marketplaceName.length === 0) {
|
|
1642
|
+
return createResult({
|
|
1643
|
+
actionType,
|
|
1644
|
+
status: ActionStatus.PREFLIGHT_FAILED,
|
|
1645
|
+
error: 'marketplaceName is required for standalone-index form',
|
|
1646
|
+
});
|
|
1647
|
+
}
|
|
1648
|
+
if (!SAFE_ID_RE.test(action.marketplaceName)) {
|
|
1649
|
+
return createResult({
|
|
1650
|
+
actionType,
|
|
1651
|
+
status: ActionStatus.PREFLIGHT_FAILED,
|
|
1652
|
+
error: `unsafe marketplaceName identifier: "${action.marketplaceName}"`,
|
|
1653
|
+
});
|
|
1654
|
+
}
|
|
1655
|
+
// 若 action 带 marketplace,两者必须相等
|
|
1656
|
+
if (action.marketplace !== undefined && action.marketplace !== null && action.marketplaceName !== action.marketplace) {
|
|
1657
|
+
return createResult({
|
|
1658
|
+
actionType,
|
|
1659
|
+
status: ActionStatus.PREFLIGHT_FAILED,
|
|
1660
|
+
error: `marketplaceName "${action.marketplaceName}" does not match action.marketplace "${action.marketplace}"`,
|
|
1661
|
+
});
|
|
1662
|
+
}
|
|
1663
|
+
// selectedEntry 必须是普通对象,name/version 绑定 action
|
|
1664
|
+
if (!action.selectedEntry || typeof action.selectedEntry !== 'object' || Array.isArray(action.selectedEntry)) {
|
|
1665
|
+
return createResult({
|
|
1666
|
+
actionType,
|
|
1667
|
+
status: ActionStatus.PREFLIGHT_FAILED,
|
|
1668
|
+
error: 'selectedEntry must be a non-null object for standalone-index form',
|
|
1669
|
+
});
|
|
1670
|
+
}
|
|
1671
|
+
if (action.selectedEntry.name !== action.plugin) {
|
|
1672
|
+
return createResult({
|
|
1673
|
+
actionType,
|
|
1674
|
+
status: ActionStatus.PREFLIGHT_FAILED,
|
|
1675
|
+
error: `selectedEntry.name "${action.selectedEntry.name}" does not match action.plugin "${action.plugin}"`,
|
|
1676
|
+
});
|
|
1677
|
+
}
|
|
1678
|
+
if (action.selectedEntry.version !== undefined && action.selectedEntry.version !== action.version) {
|
|
1679
|
+
return createResult({
|
|
1680
|
+
actionType,
|
|
1681
|
+
status: ActionStatus.PREFLIGHT_FAILED,
|
|
1682
|
+
error: `selectedEntry.version "${action.selectedEntry.version}" does not match action.version "${action.version}"`,
|
|
1683
|
+
});
|
|
1684
|
+
}
|
|
1685
|
+
}
|
|
1686
|
+
} else {
|
|
1687
|
+
// 6b. 旧路径:sourceDescriptor 必须存在且为对象
|
|
1688
|
+
const sd = action.sourceDescriptor;
|
|
1689
|
+
if (!sd || typeof sd !== 'object') {
|
|
1112
1690
|
return createResult({
|
|
1113
1691
|
actionType,
|
|
1114
1692
|
status: ActionStatus.PREFLIGHT_FAILED,
|
|
1115
|
-
error:
|
|
1693
|
+
error: 'sourceDescriptor is required for marketplace install actions',
|
|
1116
1694
|
});
|
|
1117
1695
|
}
|
|
1118
|
-
|
|
1696
|
+
{
|
|
1697
|
+
// Form consistency: marketplaceForm must agree with sourceDescriptor.form.
|
|
1698
|
+
if (sd.form !== action.marketplaceForm) {
|
|
1699
|
+
return createResult({
|
|
1700
|
+
actionType,
|
|
1701
|
+
status: ActionStatus.PREFLIGHT_FAILED,
|
|
1702
|
+
error: `sourceDescriptor.form "${sd.form}" does not match marketplaceForm "${action.marketplaceForm}"`,
|
|
1703
|
+
});
|
|
1704
|
+
}
|
|
1119
1705
|
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1706
|
+
// payloadDigest: must be a valid 64-char lowercase hex string and
|
|
1707
|
+
// must not be the null hash (all zeros).
|
|
1708
|
+
if (typeof sd.payloadDigest !== 'string' || sd.payloadDigest.length === 0) {
|
|
1709
|
+
return createResult({
|
|
1710
|
+
actionType,
|
|
1711
|
+
status: ActionStatus.PREFLIGHT_FAILED,
|
|
1712
|
+
error: 'sourceDescriptor.payloadDigest is required',
|
|
1713
|
+
});
|
|
1714
|
+
}
|
|
1715
|
+
if (!/^[a-f0-9]{64}$/.test(sd.payloadDigest)) {
|
|
1716
|
+
return createResult({
|
|
1717
|
+
actionType,
|
|
1718
|
+
status: ActionStatus.PREFLIGHT_FAILED,
|
|
1719
|
+
error: 'sourceDescriptor.payloadDigest must be a 64-char lowercase hex string',
|
|
1720
|
+
});
|
|
1721
|
+
}
|
|
1722
|
+
if (sd.payloadDigest === '0'.repeat(64)) {
|
|
1723
|
+
return createResult({
|
|
1724
|
+
actionType,
|
|
1725
|
+
status: ActionStatus.PREFLIGHT_FAILED,
|
|
1726
|
+
error: 'sourceDescriptor.payloadDigest must not be the null hash',
|
|
1727
|
+
});
|
|
1728
|
+
}
|
|
1729
|
+
|
|
1730
|
+
// marketplaceEntry: must match the action plugin name.
|
|
1731
|
+
if (typeof sd.marketplaceEntry !== 'string' || sd.marketplaceEntry.length === 0) {
|
|
1732
|
+
return createResult({
|
|
1733
|
+
actionType,
|
|
1734
|
+
status: ActionStatus.PREFLIGHT_FAILED,
|
|
1735
|
+
error: 'sourceDescriptor.marketplaceEntry is required',
|
|
1736
|
+
});
|
|
1737
|
+
}
|
|
1738
|
+
if (sd.marketplaceEntry !== action.plugin) {
|
|
1739
|
+
return createResult({
|
|
1740
|
+
actionType,
|
|
1741
|
+
status: ActionStatus.PREFLIGHT_FAILED,
|
|
1742
|
+
error: `sourceDescriptor.marketplaceEntry "${sd.marketplaceEntry}" does not match action plugin "${action.plugin}"`,
|
|
1743
|
+
});
|
|
1744
|
+
}
|
|
1745
|
+
|
|
1746
|
+
// Form-specific field validation.
|
|
1747
|
+
if (sd.form === 'bundled-family') {
|
|
1748
|
+
if (!sd.repo || !SAFE_REPO_RE.test(sd.repo)) {
|
|
1749
|
+
return createResult({
|
|
1750
|
+
actionType,
|
|
1751
|
+
status: ActionStatus.PREFLIGHT_FAILED,
|
|
1752
|
+
error: `sourceDescriptor.repo is required and must be a safe repo pattern`,
|
|
1753
|
+
});
|
|
1754
|
+
}
|
|
1755
|
+
if (sd.repo !== action.repo) {
|
|
1756
|
+
return createResult({
|
|
1757
|
+
actionType,
|
|
1758
|
+
status: ActionStatus.PREFLIGHT_FAILED,
|
|
1759
|
+
error: `sourceDescriptor.repo "${sd.repo}" does not match action.repo "${action.repo}"`,
|
|
1760
|
+
});
|
|
1761
|
+
}
|
|
1762
|
+
// commit 可选,但如果存在则必须是 40-hex 格式
|
|
1763
|
+
if (sd.commit !== undefined && (typeof sd.commit !== 'string' || !/^[0-9a-f]{40}$/.test(sd.commit))) {
|
|
1764
|
+
return createResult({
|
|
1765
|
+
actionType,
|
|
1766
|
+
status: ActionStatus.PREFLIGHT_FAILED,
|
|
1767
|
+
error: 'sourceDescriptor.commit must be a 40-hex commit sha for bundled-family form',
|
|
1768
|
+
});
|
|
1769
|
+
}
|
|
1770
|
+
// commit 交叉校验:bundled sourceDescriptor.commit 必须与
|
|
1771
|
+
// action.sourceCommit(冻结的插件来源提交)一致
|
|
1772
|
+
if (sd.commit !== undefined && action.sourceCommit && sd.commit !== action.sourceCommit) {
|
|
1773
|
+
return createResult({
|
|
1774
|
+
actionType,
|
|
1775
|
+
status: ActionStatus.PREFLIGHT_FAILED,
|
|
1776
|
+
error: `sourceDescriptor.commit "${sd.commit}" does not match action.sourceCommit "${action.sourceCommit}"`,
|
|
1777
|
+
});
|
|
1778
|
+
}
|
|
1779
|
+
// payloadDigest 格式校验(可选,但如果存在则必须是 64-hex)
|
|
1780
|
+
if (sd.payloadDigest !== undefined && (!sd.payloadDigest || !SAFE_DIGEST_RE.test(sd.payloadDigest))) {
|
|
1781
|
+
return createResult({
|
|
1782
|
+
actionType,
|
|
1783
|
+
status: ActionStatus.PREFLIGHT_FAILED,
|
|
1784
|
+
error: 'sourceDescriptor.payloadDigest is required for bundled-family form',
|
|
1785
|
+
});
|
|
1786
|
+
}
|
|
1787
|
+
// payloadDigest 交叉校验:必须与 action.manifestDigest(冻结的载荷摘要)一致
|
|
1788
|
+
if (sd.payloadDigest !== undefined && action.manifestDigest && sd.payloadDigest !== action.manifestDigest) {
|
|
1789
|
+
return createResult({
|
|
1790
|
+
actionType,
|
|
1791
|
+
status: ActionStatus.PREFLIGHT_FAILED,
|
|
1792
|
+
error: `sourceDescriptor.payloadDigest does not match action.manifestDigest`,
|
|
1793
|
+
});
|
|
1794
|
+
}
|
|
1795
|
+
if (typeof sd.pluginSubpath !== 'string' || sd.pluginSubpath.length === 0) {
|
|
1796
|
+
return createResult({
|
|
1797
|
+
actionType,
|
|
1798
|
+
status: ActionStatus.PREFLIGHT_FAILED,
|
|
1799
|
+
error: 'sourceDescriptor.pluginSubpath is required for bundled-family form',
|
|
1800
|
+
});
|
|
1801
|
+
}
|
|
1802
|
+
} else if (sd.form === 'standalone-index') {
|
|
1803
|
+
if (!sd.pluginRepo || !SAFE_REPO_RE.test(sd.pluginRepo)) {
|
|
1804
|
+
return createResult({
|
|
1805
|
+
actionType,
|
|
1806
|
+
status: ActionStatus.PREFLIGHT_FAILED,
|
|
1807
|
+
error: `sourceDescriptor.pluginRepo is required for standalone-index form`,
|
|
1808
|
+
});
|
|
1809
|
+
}
|
|
1810
|
+
if (!sd.marketplaceRepo || !SAFE_REPO_RE.test(sd.marketplaceRepo)) {
|
|
1811
|
+
return createResult({
|
|
1812
|
+
actionType,
|
|
1813
|
+
status: ActionStatus.PREFLIGHT_FAILED,
|
|
1814
|
+
error: 'sourceDescriptor.marketplaceRepo is required for standalone-index form',
|
|
1815
|
+
});
|
|
1816
|
+
}
|
|
1817
|
+
// 独立市场身份交叉校验:
|
|
1818
|
+
// 1. marketplaceRepo 必须等于 action.repo(市场仓库身份)
|
|
1819
|
+
if (sd.marketplaceRepo !== action.repo) {
|
|
1820
|
+
return createResult({
|
|
1821
|
+
actionType,
|
|
1822
|
+
status: ActionStatus.PREFLIGHT_FAILED,
|
|
1823
|
+
error: `sourceDescriptor.marketplaceRepo "${sd.marketplaceRepo}" does not match action.repo "${action.repo}"`,
|
|
1824
|
+
});
|
|
1825
|
+
}
|
|
1826
|
+
// 2. pluginRepo 必须与 marketplaceRepo 不同(发布单元仓库 ≠ 市场仓库)
|
|
1827
|
+
if (sd.pluginRepo === sd.marketplaceRepo) {
|
|
1828
|
+
return createResult({
|
|
1829
|
+
actionType,
|
|
1830
|
+
status: ActionStatus.PREFLIGHT_FAILED,
|
|
1831
|
+
error: `sourceDescriptor.pluginRepo "${sd.pluginRepo}" must differ from marketplaceRepo "${sd.marketplaceRepo}"`,
|
|
1832
|
+
});
|
|
1833
|
+
}
|
|
1834
|
+
if (
|
|
1835
|
+
typeof sd.marketplaceCommitSha !== 'string'
|
|
1836
|
+
|| !/^[0-9a-f]{40}$/.test(sd.marketplaceCommitSha)
|
|
1837
|
+
) {
|
|
1838
|
+
return createResult({
|
|
1839
|
+
actionType,
|
|
1840
|
+
status: ActionStatus.PREFLIGHT_FAILED,
|
|
1841
|
+
error: 'sourceDescriptor.marketplaceCommitSha must be a 40-hex commit sha for standalone-index form',
|
|
1842
|
+
});
|
|
1843
|
+
}
|
|
1844
|
+
// 3. marketplaceCommitSha 交叉校验(与 action 层一致)
|
|
1845
|
+
if (action.marketplaceCommitSha && sd.marketplaceCommitSha !== action.marketplaceCommitSha) {
|
|
1846
|
+
return createResult({
|
|
1847
|
+
actionType,
|
|
1848
|
+
status: ActionStatus.PREFLIGHT_FAILED,
|
|
1849
|
+
error: `sourceDescriptor.marketplaceCommitSha "${sd.marketplaceCommitSha}" does not match action.marketplaceCommitSha "${action.marketplaceCommitSha}"`,
|
|
1850
|
+
});
|
|
1851
|
+
}
|
|
1852
|
+
if (typeof sd.ref !== 'string' || sd.ref.length === 0) {
|
|
1853
|
+
return createResult({
|
|
1854
|
+
actionType,
|
|
1855
|
+
status: ActionStatus.PREFLIGHT_FAILED,
|
|
1856
|
+
error: 'sourceDescriptor.ref is required for standalone-index form',
|
|
1857
|
+
});
|
|
1858
|
+
}
|
|
1859
|
+
// 4. payloadDigest 格式校验(可选,但如果存在则必须是 64-hex)
|
|
1860
|
+
if (sd.payloadDigest !== undefined && (!sd.payloadDigest || !SAFE_DIGEST_RE.test(sd.payloadDigest))) {
|
|
1861
|
+
return createResult({
|
|
1862
|
+
actionType,
|
|
1863
|
+
status: ActionStatus.PREFLIGHT_FAILED,
|
|
1864
|
+
error: 'sourceDescriptor.payloadDigest must be a 64-hex digest for standalone-index form',
|
|
1865
|
+
});
|
|
1866
|
+
}
|
|
1867
|
+
// 5. payloadDigest 交叉校验:必须与 action.manifestDigest(冻结的载荷摘要)一致
|
|
1868
|
+
if (sd.payloadDigest !== undefined && action.manifestDigest && sd.payloadDigest !== action.manifestDigest) {
|
|
1869
|
+
return createResult({
|
|
1870
|
+
actionType,
|
|
1871
|
+
status: ActionStatus.PREFLIGHT_FAILED,
|
|
1872
|
+
error: `sourceDescriptor.payloadDigest does not match action.manifestDigest`,
|
|
1873
|
+
});
|
|
1874
|
+
}
|
|
1875
|
+
} else {
|
|
1876
|
+
return createResult({
|
|
1877
|
+
actionType,
|
|
1878
|
+
status: ActionStatus.PREFLIGHT_FAILED,
|
|
1879
|
+
error: `sourceDescriptor.form "${sd.form}" is not a recognized form (expected "bundled-family" or "standalone-index")`,
|
|
1880
|
+
});
|
|
1881
|
+
}
|
|
1882
|
+
}
|
|
1883
|
+
}
|
|
1884
|
+
|
|
1885
|
+
// 7. Verify frozen snapshot exists and contains required marketplace files
|
|
1886
|
+
const platform = consumerPlatform;
|
|
1887
|
+
const platformRoute = route;
|
|
1888
|
+
let snapshotDirReal;
|
|
1889
|
+
// Authoritative kimi manifest (from the frozen snapshot), used to
|
|
1890
|
+
// resolve the entry skill via the manifest-declared skills root.
|
|
1891
|
+
let kimiSnapshotManifest = null;
|
|
1892
|
+
try {
|
|
1893
|
+
snapshotDirReal = await resolveFrozenPath(context.root, snapshotPath, 'frozen snapshot path');
|
|
1894
|
+
} catch (frozenErr) {
|
|
1895
|
+
return createResult({
|
|
1896
|
+
actionType,
|
|
1897
|
+
status: ActionStatus.PREFLIGHT_FAILED,
|
|
1898
|
+
error: `frozen snapshot validation failed: ${frozenErr.message}`,
|
|
1899
|
+
});
|
|
1135
1900
|
}
|
|
1136
1901
|
|
|
1137
1902
|
// A human-attestation platform (kimi, codebuddy) has no trustworthy
|
|
@@ -1171,7 +1936,13 @@ export function createPluginMarketplaceAdapter(deps = {}) {
|
|
|
1171
1936
|
kimiSnapshotManifest = kimiManifest;
|
|
1172
1937
|
}
|
|
1173
1938
|
|
|
1939
|
+
// pluginRootForEntrySkill: the resolved plugin root directory for
|
|
1940
|
+
// entry skill validation. Set inside the structured-cli non-external
|
|
1941
|
+
// path; null for external/human-attestation (falls back to snapshotDirReal).
|
|
1942
|
+
let pluginRootForEntrySkill = null;
|
|
1943
|
+
|
|
1174
1944
|
if (platformRoute.route === 'structured-cli') {
|
|
1945
|
+
let sourceDirReal = null;
|
|
1175
1946
|
if (action.marketplaceLocation === 'external') {
|
|
1176
1947
|
// External independent marketplace form: the marketplace index lives
|
|
1177
1948
|
// in the external repository (frozen by prepare), NOT in the unit
|
|
@@ -1226,9 +1997,21 @@ export function createPluginMarketplaceAdapter(deps = {}) {
|
|
|
1226
1997
|
}
|
|
1227
1998
|
} else {
|
|
1228
1999
|
// Verify marketplace files exist.
|
|
1229
|
-
// marketplace.json
|
|
1230
|
-
//
|
|
1231
|
-
const marketplaceRelative = platform.manifestPaths.marketplace;
|
|
2000
|
+
// marketplace.json 的路径:嵌套布局使用 action.marketplaceIndexPath,
|
|
2001
|
+
// 根布局使用平台默认路径。条目 source 相对市场根解析。
|
|
2002
|
+
const marketplaceRelative = action.marketplaceIndexPath ?? platform.manifestPaths.marketplace;
|
|
2003
|
+
|
|
2004
|
+
// 计算市场根(精确后缀匹配)
|
|
2005
|
+
let mktRoot;
|
|
2006
|
+
try {
|
|
2007
|
+
mktRoot = resolveMarketplaceRoot(platform, marketplaceRelative);
|
|
2008
|
+
} catch (mktRootErr) {
|
|
2009
|
+
return createResult({
|
|
2010
|
+
actionType,
|
|
2011
|
+
status: ActionStatus.PREFLIGHT_FAILED,
|
|
2012
|
+
error: `marketplace root resolution failed: ${mktRootErr.message}`,
|
|
2013
|
+
});
|
|
2014
|
+
}
|
|
1232
2015
|
|
|
1233
2016
|
const marketplacePath = resolve(snapshotDirReal, marketplaceRelative);
|
|
1234
2017
|
|
|
@@ -1276,6 +2059,7 @@ export function createPluginMarketplaceAdapter(deps = {}) {
|
|
|
1276
2059
|
// empty strings. Normalized to "." for root layouts; the same
|
|
1277
2060
|
// helper backs verify-side payload subtree resolution, so both
|
|
1278
2061
|
// paths can never drift apart.
|
|
2062
|
+
// source 相对市场根(mktRoot)解析,而非快照根。
|
|
1279
2063
|
let sourcePath;
|
|
1280
2064
|
try {
|
|
1281
2065
|
sourcePath = extractDeclaredPluginSource(consumer, entry);
|
|
@@ -1288,8 +2072,10 @@ export function createPluginMarketplaceAdapter(deps = {}) {
|
|
|
1288
2072
|
}
|
|
1289
2073
|
// Verify the declared source directory exists and contains the
|
|
1290
2074
|
// expected plugin manifest inside the frozen snapshot.
|
|
1291
|
-
|
|
1292
|
-
const
|
|
2075
|
+
// source 相对市场根解析
|
|
2076
|
+
const mktRootAbs = mktRoot === '.' ? snapshotDirReal : resolve(snapshotDirReal, mktRoot);
|
|
2077
|
+
const sourceDirAbs = resolve(mktRootAbs, sourcePath);
|
|
2078
|
+
sourceDirReal = await realpath(sourceDirAbs).catch(() => null);
|
|
1293
2079
|
if (!sourceDirReal) {
|
|
1294
2080
|
return createResult({
|
|
1295
2081
|
actionType,
|
|
@@ -1306,13 +2092,18 @@ export function createPluginMarketplaceAdapter(deps = {}) {
|
|
|
1306
2092
|
error: `marketplace plugin entry source "${sourcePath}" escapes the frozen snapshot`,
|
|
1307
2093
|
});
|
|
1308
2094
|
}
|
|
2095
|
+
// Save plugin root for entry skill validation (visible outside structured-cli block)
|
|
2096
|
+
pluginRootForEntrySkill = sourceDirReal;
|
|
1309
2097
|
|
|
1310
2098
|
// Resolve plugin manifest relative to the declared source path.
|
|
1311
2099
|
// For root layouts (source: "./"), this resolves to
|
|
1312
2100
|
// snapshot/.claude-plugin/plugin.json
|
|
1313
|
-
// For
|
|
1314
|
-
// snapshot/adapters/claude/.claude-plugin/plugin.json
|
|
1315
|
-
const
|
|
2101
|
+
// For nested layouts (marketRoot: "adapters/claude", source: "."),
|
|
2102
|
+
// this resolves to snapshot/adapters/claude/.claude-plugin/plugin.json
|
|
2103
|
+
const pluginRootRelToSnapshot = relative(snapshotDirReal, sourceDirReal) || '.';
|
|
2104
|
+
const manifestRelative = pluginRootRelToSnapshot === '.'
|
|
2105
|
+
? platform.manifestPaths.plugin
|
|
2106
|
+
: `${pluginRootRelToSnapshot}/${platform.manifestPaths.plugin}`;
|
|
1316
2107
|
const manifestPath = resolve(snapshotDirReal, manifestRelative);
|
|
1317
2108
|
|
|
1318
2109
|
const manifestResult = await validateManifestFile(manifestPath, ['name', 'version']);
|
|
@@ -1384,14 +2175,19 @@ export function createPluginMarketplaceAdapter(deps = {}) {
|
|
|
1384
2175
|
});
|
|
1385
2176
|
}
|
|
1386
2177
|
} else {
|
|
1387
|
-
|
|
2178
|
+
// Entry skill 相对插件根解析(嵌套布局时 pluginRoot != snapshotRoot)。
|
|
2179
|
+
// pluginRootForEntrySkill 在非 external 结构化 CLI 路径中已确定;
|
|
2180
|
+
// external 路径或 human-attestation(null)时回退到 snapshotDirReal。
|
|
2181
|
+
const pluginRootForSkill = pluginRootForEntrySkill || snapshotDirReal;
|
|
2182
|
+
const entrySkillFile = resolve(pluginRootForSkill, 'skills', action.entrySkill, 'SKILL.md');
|
|
1388
2183
|
try {
|
|
1389
2184
|
await stat(entrySkillFile);
|
|
1390
2185
|
} catch {
|
|
2186
|
+
const skillRel = relative(snapshotDirReal, entrySkillFile) || `skills/${action.entrySkill}/SKILL.md`;
|
|
1391
2187
|
return createResult({
|
|
1392
2188
|
actionType,
|
|
1393
2189
|
status: ActionStatus.PREFLIGHT_FAILED,
|
|
1394
|
-
error: `entry skill not found in snapshot:
|
|
2190
|
+
error: `entry skill not found in snapshot: ${skillRel}`,
|
|
1395
2191
|
});
|
|
1396
2192
|
}
|
|
1397
2193
|
}
|
|
@@ -1414,6 +2210,28 @@ export function createPluginMarketplaceAdapter(deps = {}) {
|
|
|
1414
2210
|
});
|
|
1415
2211
|
}
|
|
1416
2212
|
|
|
2213
|
+
// installationContractDigest 摘要校验(新版字段组已通过完整性检查后来到这里)
|
|
2214
|
+
// 从冻结快照读取插件 manifest,按平台注册表和来源形态构建安装契约,
|
|
2215
|
+
// 以 consumer-install-v1 重算并与 action 携带的摘要比对。
|
|
2216
|
+
if (hasNewFieldGroup) {
|
|
2217
|
+
try {
|
|
2218
|
+
const contractResult = await validateInstallationContractDigest(action, snapshotDirReal, consumerPlatform);
|
|
2219
|
+
if (!contractResult.valid) {
|
|
2220
|
+
return createResult({
|
|
2221
|
+
actionType,
|
|
2222
|
+
status: ActionStatus.PREFLIGHT_FAILED,
|
|
2223
|
+
error: contractResult.error,
|
|
2224
|
+
});
|
|
2225
|
+
}
|
|
2226
|
+
} catch (contractErr) {
|
|
2227
|
+
return createResult({
|
|
2228
|
+
actionType,
|
|
2229
|
+
status: ActionStatus.PREFLIGHT_FAILED,
|
|
2230
|
+
error: `installationContractDigest 验证异常: ${contractErr.message}`,
|
|
2231
|
+
});
|
|
2232
|
+
}
|
|
2233
|
+
}
|
|
2234
|
+
|
|
1417
2235
|
return createResult({
|
|
1418
2236
|
actionType,
|
|
1419
2237
|
status: ActionStatus.PREFLIGHT_PASSED,
|
|
@@ -1668,6 +2486,11 @@ export function createPluginMarketplaceAdapter(deps = {}) {
|
|
|
1668
2486
|
}
|
|
1669
2487
|
}
|
|
1670
2488
|
} catch (addErr) {
|
|
2489
|
+
// Re-throw CLI/transport unavailability errors to outer catch
|
|
2490
|
+
// for human-attestation fallback classification
|
|
2491
|
+
if (isCliOrTransportUnavailable(addErr)) {
|
|
2492
|
+
throw addErr;
|
|
2493
|
+
}
|
|
1671
2494
|
return createResult({
|
|
1672
2495
|
actionType,
|
|
1673
2496
|
status: ActionStatus.EXECUTE_FAILED,
|
|
@@ -1742,6 +2565,11 @@ export function createPluginMarketplaceAdapter(deps = {}) {
|
|
|
1742
2565
|
}
|
|
1743
2566
|
}
|
|
1744
2567
|
} catch (installErr) {
|
|
2568
|
+
// Re-throw CLI/transport unavailability errors to outer catch
|
|
2569
|
+
// for human-attestation fallback classification
|
|
2570
|
+
if (isCliOrTransportUnavailable(installErr)) {
|
|
2571
|
+
throw installErr;
|
|
2572
|
+
}
|
|
1745
2573
|
return createResult({
|
|
1746
2574
|
actionType,
|
|
1747
2575
|
status: ActionStatus.EXECUTE_FAILED,
|
|
@@ -1808,6 +2636,18 @@ export function createPluginMarketplaceAdapter(deps = {}) {
|
|
|
1808
2636
|
observation: executeObservation,
|
|
1809
2637
|
});
|
|
1810
2638
|
} catch (err) {
|
|
2639
|
+
// Codex human-attestation fallback: ONLY when the CLI interface,
|
|
2640
|
+
// binary, environment, or transport is explicitly unavailable.
|
|
2641
|
+
// Explicit mismatches (identity, version, payload, marketplace entry),
|
|
2642
|
+
// malformed JSON, and program exceptions are hard failures.
|
|
2643
|
+
const failedPlatform = getPlatform(action.consumer);
|
|
2644
|
+
if (
|
|
2645
|
+
failedPlatform.degradationPolicy === 'human-attestation-with-fallback'
|
|
2646
|
+
&& failedPlatform.strategy.buildManualRequirement
|
|
2647
|
+
&& isCliOrTransportUnavailable(err)
|
|
2648
|
+
) {
|
|
2649
|
+
return failedPlatform.strategy.buildManualRequirement(action, context);
|
|
2650
|
+
}
|
|
1811
2651
|
return createResult({
|
|
1812
2652
|
actionType,
|
|
1813
2653
|
status: ActionStatus.EXECUTE_FAILED,
|
|
@@ -1963,8 +2803,8 @@ export function createPluginMarketplaceAdapter(deps = {}) {
|
|
|
1963
2803
|
|
|
1964
2804
|
// Stable, cross-run attestation authority (B). The requirement and
|
|
1965
2805
|
// attestation live here, keyed by the verified plan digest + plugin,
|
|
1966
|
-
// so they survive
|
|
1967
|
-
//
|
|
2806
|
+
// so they survive across fresh runDirs (publish, verify each use a
|
|
2807
|
+
// new runDir).
|
|
1968
2808
|
let attestationDir;
|
|
1969
2809
|
try {
|
|
1970
2810
|
attestationDir = kimiAuthorityDir(context, boundPlanDigest, action.plugin);
|
|
@@ -1992,13 +2832,14 @@ export function createPluginMarketplaceAdapter(deps = {}) {
|
|
|
1992
2832
|
},
|
|
1993
2833
|
});
|
|
1994
2834
|
}
|
|
2835
|
+
// 完整 requirement 绑定验证:所有冻结动作绑定字段必须一致
|
|
1995
2836
|
if (
|
|
1996
2837
|
requirement.planDigest !== boundPlanDigest ||
|
|
1997
2838
|
requirement.plugin !== action.plugin ||
|
|
1998
2839
|
requirement.version !== action.version ||
|
|
1999
|
-
requirement.entrySkill !== action.entrySkill ||
|
|
2000
2840
|
requirement.repo !== action.repo ||
|
|
2001
|
-
requirement.ref !==
|
|
2841
|
+
requirement.ref !== (action.ref ?? `v${action.version}`) ||
|
|
2842
|
+
(action.entrySkill && requirement.entrySkill !== action.entrySkill)
|
|
2002
2843
|
) {
|
|
2003
2844
|
return createResult({
|
|
2004
2845
|
actionType,
|
|
@@ -2039,202 +2880,171 @@ export function createPluginMarketplaceAdapter(deps = {}) {
|
|
|
2039
2880
|
});
|
|
2040
2881
|
}
|
|
2041
2882
|
|
|
2042
|
-
//
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
//
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
const kimiCodeHome = resolve(attestationDir, 'kimi-home');
|
|
2050
|
-
const kimiCodeHomeReal = await realpath(kimiCodeHome).catch(() => null);
|
|
2051
|
-
if (!kimiCodeHomeReal) {
|
|
2052
|
-
return createResult({
|
|
2053
|
-
actionType,
|
|
2054
|
-
status: ActionStatus.OBSERVED,
|
|
2055
|
-
observation: {
|
|
2056
|
-
installed: false,
|
|
2057
|
-
error: `KIMI_CODE_HOME does not exist or cannot be resolved: ${kimiCodeHome}`,
|
|
2058
|
-
},
|
|
2059
|
-
});
|
|
2060
|
-
}
|
|
2061
|
-
const managedRootReal = await realpath(resolve(kimiCodeHomeReal, KIMI_MANAGED_SUBPATH, action.plugin)).catch(() => null);
|
|
2062
|
-
if (!managedRootReal) {
|
|
2883
|
+
// 使用归一化后的 attestation(兼容旧格式)
|
|
2884
|
+
const normalizedAttestation = attestationCheck.normalized;
|
|
2885
|
+
const requiresInstalledClosure = Boolean(context.plan?.skillResourceClosure);
|
|
2886
|
+
|
|
2887
|
+
// 统一人工判定:如果结果是 failed,直接返回失败
|
|
2888
|
+
if (normalizedAttestation.result === 'failed') {
|
|
2889
|
+
const errorMsg = `kimi human result: failed${normalizedAttestation.note ? ` (${normalizedAttestation.note})` : ''}`;
|
|
2063
2890
|
return createResult({
|
|
2064
2891
|
actionType,
|
|
2065
2892
|
status: ActionStatus.OBSERVED,
|
|
2893
|
+
error: errorMsg,
|
|
2066
2894
|
observation: {
|
|
2067
2895
|
installed: false,
|
|
2068
|
-
|
|
2896
|
+
humanConfirmed: true,
|
|
2897
|
+
result: 'failed',
|
|
2898
|
+
actor: normalizedAttestation.actor,
|
|
2899
|
+
confirmedAt: normalizedAttestation.confirmedAt,
|
|
2900
|
+
error: errorMsg,
|
|
2069
2901
|
},
|
|
2070
2902
|
});
|
|
2071
2903
|
}
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
const installPath = resolve(attestation.installPath);
|
|
2075
|
-
let installPathStat;
|
|
2076
|
-
try {
|
|
2077
|
-
installPathStat = await lstat(installPath);
|
|
2078
|
-
} catch {
|
|
2079
|
-
return createResult({
|
|
2080
|
-
actionType,
|
|
2081
|
-
status: ActionStatus.OBSERVED,
|
|
2082
|
-
observation: { installed: false, error: `kimi install path does not exist: ${attestation.installPath}` },
|
|
2083
|
-
});
|
|
2084
|
-
}
|
|
2085
|
-
if (installPathStat.isSymbolicLink()) {
|
|
2086
|
-
return createResult({
|
|
2087
|
-
actionType,
|
|
2088
|
-
status: ActionStatus.OBSERVED,
|
|
2089
|
-
observation: { installed: false, error: `kimi install path must not be a symlink: ${attestation.installPath}` },
|
|
2090
|
-
});
|
|
2091
|
-
}
|
|
2092
|
-
if (!installPathStat.isDirectory()) {
|
|
2093
|
-
return createResult({
|
|
2094
|
-
actionType,
|
|
2095
|
-
status: ActionStatus.OBSERVED,
|
|
2096
|
-
observation: { installed: false, error: `kimi install path must be a directory: ${attestation.installPath}` },
|
|
2097
|
-
});
|
|
2098
|
-
}
|
|
2099
|
-
const installPathReal = await realpath(installPath).catch(() => null);
|
|
2100
|
-
if (!installPathReal) {
|
|
2101
|
-
return createResult({
|
|
2102
|
-
actionType,
|
|
2103
|
-
status: ActionStatus.OBSERVED,
|
|
2104
|
-
observation: { installed: false, error: `kimi install path cannot be resolved: ${attestation.installPath}` },
|
|
2105
|
-
});
|
|
2106
|
-
}
|
|
2107
|
-
const sepK = process.platform === 'win32' ? '\\' : '/';
|
|
2108
|
-
const relToManaged = relative(managedRootReal, installPathReal);
|
|
2109
|
-
if (
|
|
2110
|
-
relToManaged !== '' &&
|
|
2111
|
-
(isAbsolute(relToManaged) || relToManaged === '..' || relToManaged.startsWith(`..${sepK}`))
|
|
2112
|
-
) {
|
|
2904
|
+
|
|
2905
|
+
if (requiresInstalledClosure && !normalizedAttestation.installPath) {
|
|
2113
2906
|
return createResult({
|
|
2114
2907
|
actionType,
|
|
2115
2908
|
status: ActionStatus.OBSERVED,
|
|
2116
2909
|
observation: {
|
|
2117
2910
|
installed: false,
|
|
2118
|
-
error:
|
|
2911
|
+
error: 'kimi attestation installPath is required by the skill resource closure gate',
|
|
2119
2912
|
},
|
|
2120
2913
|
});
|
|
2121
2914
|
}
|
|
2122
2915
|
|
|
2123
|
-
//
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
payloadBinding = await verifyInstalledMarketplacePayload(action, context, installPathReal, consumer);
|
|
2129
|
-
manifestDigest = payloadBinding.manifestDigest;
|
|
2130
|
-
} catch (digestErr) {
|
|
2131
|
-
return createResult({
|
|
2132
|
-
actionType,
|
|
2133
|
-
status: ActionStatus.OBSERVED,
|
|
2134
|
-
observation: {
|
|
2135
|
-
installed: true,
|
|
2136
|
-
installPath: installPathReal,
|
|
2137
|
-
error: `failed to bind installed kimi payload to frozen authority: ${digestErr.message}`,
|
|
2138
|
-
},
|
|
2139
|
-
});
|
|
2140
|
-
}
|
|
2141
|
-
if (manifestDigest !== action.manifestDigest) {
|
|
2142
|
-
return createResult({
|
|
2143
|
-
actionType,
|
|
2144
|
-
status: ActionStatus.OBSERVED,
|
|
2145
|
-
observation: {
|
|
2146
|
-
installed: true,
|
|
2147
|
-
installPath: installPathReal,
|
|
2148
|
-
error: 'installed kimi payload digest does not match the frozen plan digest',
|
|
2149
|
-
},
|
|
2150
|
-
});
|
|
2151
|
-
}
|
|
2916
|
+
// installPath 验证:必须在 managed root 内,不得逃逸,不得是符号链接
|
|
2917
|
+
let verifiedInstallPath = null;
|
|
2918
|
+
if (normalizedAttestation.installPath) {
|
|
2919
|
+
const managedRoot = resolve(attestationDir, 'kimi-home', 'plugins', 'managed');
|
|
2920
|
+
const installPathAbs = resolve(normalizedAttestation.installPath);
|
|
2152
2921
|
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
status: ActionStatus.OBSERVED,
|
|
2166
|
-
observation: {
|
|
2167
|
-
installed: true,
|
|
2168
|
-
installPath: installPathReal,
|
|
2169
|
-
manifestDigest,
|
|
2170
|
-
entrySkillFound: false,
|
|
2171
|
-
error: `kimi entry skill not resolvable in installed copy: ${entryErr.message}`,
|
|
2172
|
-
},
|
|
2173
|
-
});
|
|
2174
|
-
}
|
|
2922
|
+
// 先验证 managed root 存在(KIMI_CODE_HOME 检查)
|
|
2923
|
+
const managedRootReal = await realpath(managedRoot).catch(() => null);
|
|
2924
|
+
if (!managedRootReal) {
|
|
2925
|
+
return createResult({
|
|
2926
|
+
actionType,
|
|
2927
|
+
status: ActionStatus.OBSERVED,
|
|
2928
|
+
observation: {
|
|
2929
|
+
installed: false,
|
|
2930
|
+
error: 'KIMI_CODE_HOME does not exist',
|
|
2931
|
+
},
|
|
2932
|
+
});
|
|
2933
|
+
}
|
|
2175
2934
|
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2935
|
+
// 检查 installPath 词法路径是否存在(lstat 在 realpath 之前)
|
|
2936
|
+
let lexicalStat;
|
|
2937
|
+
try {
|
|
2938
|
+
lexicalStat = await lstat(installPathAbs);
|
|
2939
|
+
} catch {
|
|
2940
|
+
return createResult({
|
|
2941
|
+
actionType,
|
|
2942
|
+
status: ActionStatus.OBSERVED,
|
|
2943
|
+
observation: {
|
|
2944
|
+
installed: false,
|
|
2945
|
+
error: 'managed plugin root does not exist',
|
|
2946
|
+
},
|
|
2947
|
+
});
|
|
2948
|
+
}
|
|
2949
|
+
if (lexicalStat.isSymbolicLink()) {
|
|
2950
|
+
return createResult({
|
|
2951
|
+
actionType,
|
|
2952
|
+
status: ActionStatus.OBSERVED,
|
|
2953
|
+
observation: {
|
|
2954
|
+
installed: false,
|
|
2955
|
+
error: `kimi attestation installPath must not be a symlink: ${normalizedAttestation.installPath}`,
|
|
2956
|
+
},
|
|
2957
|
+
});
|
|
2958
|
+
}
|
|
2959
|
+
|
|
2960
|
+
const installPathReal = await realpath(installPathAbs).catch(() => null);
|
|
2961
|
+
if (!installPathReal) {
|
|
2962
|
+
return createResult({
|
|
2963
|
+
actionType,
|
|
2964
|
+
status: ActionStatus.OBSERVED,
|
|
2965
|
+
observation: {
|
|
2966
|
+
installed: false,
|
|
2967
|
+
error: `kimi attestation installPath does not exist: ${normalizedAttestation.installPath}`,
|
|
2968
|
+
},
|
|
2969
|
+
});
|
|
2970
|
+
}
|
|
2971
|
+
const rel = relative(managedRootReal, installPathReal);
|
|
2972
|
+
const sep = process.platform === 'win32' ? '\\' : '/';
|
|
2973
|
+
if (rel === '' || rel === '..' || isAbsolute(rel) || rel.startsWith(`..${sep}`)) {
|
|
2974
|
+
return createResult({
|
|
2975
|
+
actionType,
|
|
2976
|
+
status: ActionStatus.OBSERVED,
|
|
2977
|
+
observation: {
|
|
2978
|
+
installed: false,
|
|
2979
|
+
error: `kimi attestation installPath escapes the managed root: ${normalizedAttestation.installPath}`,
|
|
2980
|
+
},
|
|
2981
|
+
});
|
|
2982
|
+
}
|
|
2983
|
+
verifiedInstallPath = installPathReal;
|
|
2189
2984
|
}
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2985
|
+
|
|
2986
|
+
// 载荷绑定验证:旧格式含 installPath 时必须绑定冻结载荷并失败关闭。
|
|
2987
|
+
// 身份、版本、载荷、市场来源不一致绝不能被人工 passed 覆盖。
|
|
2988
|
+
let manifestDigest = action.manifestDigest;
|
|
2989
|
+
let payloadBinding = null;
|
|
2990
|
+
if (verifiedInstallPath) {
|
|
2991
|
+
try {
|
|
2992
|
+
payloadBinding = await verifyInstalledMarketplacePayload(
|
|
2993
|
+
action,
|
|
2994
|
+
context,
|
|
2995
|
+
verifiedInstallPath,
|
|
2996
|
+
consumer,
|
|
2997
|
+
);
|
|
2998
|
+
manifestDigest = payloadBinding.manifestDigest;
|
|
2999
|
+
} catch (bindingErr) {
|
|
3000
|
+
return createResult({
|
|
3001
|
+
actionType,
|
|
3002
|
+
status: ActionStatus.OBSERVED,
|
|
3003
|
+
error: `kimi payload binding failed: ${bindingErr.message}`,
|
|
3004
|
+
observation: {
|
|
3005
|
+
installed: false,
|
|
3006
|
+
humanConfirmed: true,
|
|
3007
|
+
result: 'passed',
|
|
3008
|
+
actor: normalizedAttestation.actor,
|
|
3009
|
+
confirmedAt: normalizedAttestation.confirmedAt,
|
|
3010
|
+
error: `kimi payload binding failed: ${bindingErr.message}`,
|
|
3011
|
+
},
|
|
3012
|
+
});
|
|
3013
|
+
}
|
|
2202
3014
|
}
|
|
2203
3015
|
|
|
2204
|
-
//
|
|
2205
|
-
//
|
|
2206
|
-
const observation = {
|
|
2207
|
-
installed: true,
|
|
2208
|
-
installPath: installPathReal,
|
|
2209
|
-
entrySkillFound: true,
|
|
2210
|
-
entrySkill: action.entrySkill,
|
|
2211
|
-
manifestDigest,
|
|
2212
|
-
consumer,
|
|
2213
|
-
plugin: installedManifest.name,
|
|
2214
|
-
version: installedManifest.version,
|
|
2215
|
-
repo: action.repo,
|
|
2216
|
-
ref: expectedRef,
|
|
2217
|
-
...extraInstalledPathsAudit(payloadBinding),
|
|
2218
|
-
};
|
|
3016
|
+
// 统一人工判定:返回人工确认成功
|
|
3017
|
+
// 新格式不含 installPath 时,成功依赖已通过的 preflight 静态冻结校验和人工结果绑定
|
|
2219
3018
|
return createResult({
|
|
2220
3019
|
actionType,
|
|
2221
3020
|
status: ActionStatus.OBSERVED,
|
|
2222
|
-
observation
|
|
3021
|
+
observation: {
|
|
3022
|
+
installed: true,
|
|
3023
|
+
humanConfirmed: true,
|
|
3024
|
+
result: 'passed',
|
|
3025
|
+
actor: normalizedAttestation.actor,
|
|
3026
|
+
confirmedAt: normalizedAttestation.confirmedAt,
|
|
3027
|
+
consumer,
|
|
3028
|
+
plugin: action.plugin,
|
|
3029
|
+
version: action.version,
|
|
3030
|
+
repo: action.repo,
|
|
3031
|
+
ref: expectedRef,
|
|
3032
|
+
entrySkill: action.entrySkill,
|
|
3033
|
+
entrySkillFound: true,
|
|
3034
|
+
manifestDigest,
|
|
3035
|
+
planDigest: boundPlanDigest,
|
|
3036
|
+
...(verifiedInstallPath ? { installPath: verifiedInstallPath } : {}),
|
|
3037
|
+
...extraInstalledPathsAudit(payloadBinding),
|
|
3038
|
+
},
|
|
2223
3039
|
});
|
|
2224
3040
|
}
|
|
2225
3041
|
|
|
2226
3042
|
// CodeBuddy protocol capability gap (parallel to the kimi branch
|
|
2227
|
-
// above
|
|
2228
|
-
//
|
|
2229
|
-
//
|
|
2230
|
-
//
|
|
2231
|
-
//
|
|
2232
|
-
// channel (desktop `~/.workbuddy` layout / isolated cli home), then
|
|
2233
|
-
// performs read-only verification of the installed copy: payload
|
|
2234
|
-
// digest vs the sealed authority, entry skill resolved via the
|
|
2235
|
-
// manifest skills root, and manifest name/version. Missing/expired/
|
|
2236
|
-
// mismatched/escaping proof fails closed so a codebuddy unit can never
|
|
2237
|
-
// reach VERIFIED without it.
|
|
3043
|
+
// above). The codebuddy CLI cannot pin a frozen ref, so observe
|
|
3044
|
+
// never execs a codebuddy command. It consumes a unified human
|
|
3045
|
+
// result bound to the frozen plan digest + identity.
|
|
3046
|
+
// Missing/mismatched proof fails closed so a codebuddy unit can
|
|
3047
|
+
// never reach VERIFIED without it.
|
|
2238
3048
|
const codebuddyRoute = platform ? resolvePlatformRoute(platform) : null;
|
|
2239
3049
|
if (platform && codebuddyRoute?.route === 'human-attestation' && actionType === ActionType.CODEBUDDY_MARKETPLACE_INSTALL) {
|
|
2240
3050
|
const expectedRef = action.ref ?? `v${action.version}`;
|
|
@@ -2256,8 +3066,7 @@ export function createPluginMarketplaceAdapter(deps = {}) {
|
|
|
2256
3066
|
}
|
|
2257
3067
|
|
|
2258
3068
|
// Stable, cross-run attestation authority (B), keyed by the verified
|
|
2259
|
-
// plan digest + plugin, so it survives
|
|
2260
|
-
// -> verify (each uses a fresh runDir).
|
|
3069
|
+
// plan digest + plugin, so it survives across fresh runDirs.
|
|
2261
3070
|
let attestationDir;
|
|
2262
3071
|
try {
|
|
2263
3072
|
attestationDir = codebuddyAuthorityDir(context, boundPlanDigest, action.plugin);
|
|
@@ -2285,13 +3094,14 @@ export function createPluginMarketplaceAdapter(deps = {}) {
|
|
|
2285
3094
|
},
|
|
2286
3095
|
});
|
|
2287
3096
|
}
|
|
3097
|
+
// 完整 requirement 绑定验证:所有冻结动作绑定字段必须一致
|
|
2288
3098
|
if (
|
|
2289
3099
|
requirement.planDigest !== boundPlanDigest ||
|
|
2290
3100
|
requirement.plugin !== action.plugin ||
|
|
2291
3101
|
requirement.version !== action.version ||
|
|
2292
|
-
requirement.entrySkill !== action.entrySkill ||
|
|
2293
3102
|
requirement.repo !== action.repo ||
|
|
2294
|
-
requirement.ref !==
|
|
3103
|
+
requirement.ref !== (action.ref ?? `v${action.version}`) ||
|
|
3104
|
+
(action.entrySkill && requirement.entrySkill !== action.entrySkill)
|
|
2295
3105
|
) {
|
|
2296
3106
|
return createResult({
|
|
2297
3107
|
actionType,
|
|
@@ -2332,191 +3142,215 @@ export function createPluginMarketplaceAdapter(deps = {}) {
|
|
|
2332
3142
|
});
|
|
2333
3143
|
}
|
|
2334
3144
|
|
|
2335
|
-
//
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
//
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
const installPath = resolve(attestation.installPath);
|
|
2343
|
-
let installPathStat;
|
|
2344
|
-
try {
|
|
2345
|
-
installPathStat = await lstat(installPath);
|
|
2346
|
-
} catch {
|
|
2347
|
-
return createResult({
|
|
2348
|
-
actionType,
|
|
2349
|
-
status: ActionStatus.OBSERVED,
|
|
2350
|
-
observation: { installed: false, error: `codebuddy install path does not exist: ${attestation.installPath}` },
|
|
2351
|
-
});
|
|
2352
|
-
}
|
|
2353
|
-
if (installPathStat.isSymbolicLink()) {
|
|
2354
|
-
return createResult({
|
|
2355
|
-
actionType,
|
|
2356
|
-
status: ActionStatus.OBSERVED,
|
|
2357
|
-
observation: { installed: false, error: `codebuddy install path must not be a symlink: ${attestation.installPath}` },
|
|
2358
|
-
});
|
|
2359
|
-
}
|
|
2360
|
-
if (!installPathStat.isDirectory()) {
|
|
2361
|
-
return createResult({
|
|
2362
|
-
actionType,
|
|
2363
|
-
status: ActionStatus.OBSERVED,
|
|
2364
|
-
observation: { installed: false, error: `codebuddy install path must be a directory: ${attestation.installPath}` },
|
|
2365
|
-
});
|
|
2366
|
-
}
|
|
2367
|
-
const installPathReal = await realpath(installPath).catch(() => null);
|
|
2368
|
-
if (!installPathReal) {
|
|
3145
|
+
// 使用归一化后的 attestation(兼容旧格式)
|
|
3146
|
+
const normalizedAttestation = attestationCheck.normalized;
|
|
3147
|
+
const requiresInstalledClosure = Boolean(context.plan?.skillResourceClosure);
|
|
3148
|
+
|
|
3149
|
+
// 统一人工判定:如果结果是 failed,直接返回失败
|
|
3150
|
+
if (normalizedAttestation.result === 'failed') {
|
|
3151
|
+
const errorMsg = `codebuddy human result: failed${normalizedAttestation.note ? ` (${normalizedAttestation.note})` : ''}`;
|
|
2369
3152
|
return createResult({
|
|
2370
3153
|
actionType,
|
|
2371
3154
|
status: ActionStatus.OBSERVED,
|
|
2372
|
-
|
|
3155
|
+
error: errorMsg,
|
|
3156
|
+
observation: {
|
|
3157
|
+
installed: false,
|
|
3158
|
+
humanConfirmed: true,
|
|
3159
|
+
result: 'failed',
|
|
3160
|
+
actor: normalizedAttestation.actor,
|
|
3161
|
+
confirmedAt: normalizedAttestation.confirmedAt,
|
|
3162
|
+
error: errorMsg,
|
|
3163
|
+
},
|
|
2373
3164
|
});
|
|
2374
3165
|
}
|
|
2375
3166
|
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
if (!cliHomeReal) {
|
|
3167
|
+
let verifiedInstallPath = null;
|
|
3168
|
+
if (requiresInstalledClosure) {
|
|
3169
|
+
if (!normalizedAttestation.installPath) {
|
|
2380
3170
|
return createResult({
|
|
2381
3171
|
actionType,
|
|
2382
3172
|
status: ActionStatus.OBSERVED,
|
|
2383
3173
|
observation: {
|
|
2384
3174
|
installed: false,
|
|
2385
|
-
error:
|
|
3175
|
+
error: 'codebuddy attestation installPath is required by the skill resource closure gate',
|
|
2386
3176
|
},
|
|
2387
3177
|
});
|
|
2388
3178
|
}
|
|
2389
|
-
|
|
2390
|
-
const cliRootReal = await realpath(cliRootAbs).catch(() => null);
|
|
2391
|
-
if (!cliRootReal) {
|
|
3179
|
+
if (!['desktop', 'cli'].includes(normalizedAttestation.installChannel)) {
|
|
2392
3180
|
return createResult({
|
|
2393
3181
|
actionType,
|
|
2394
3182
|
status: ActionStatus.OBSERVED,
|
|
2395
3183
|
observation: {
|
|
2396
3184
|
installed: false,
|
|
2397
|
-
error:
|
|
3185
|
+
error: 'codebuddy attestation installChannel must be "desktop" or "cli" when the skill resource closure gate is active',
|
|
2398
3186
|
},
|
|
2399
3187
|
});
|
|
2400
3188
|
}
|
|
2401
|
-
const
|
|
2402
|
-
const
|
|
2403
|
-
if (
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
3189
|
+
const normalizedPath = normalizedAttestation.installPath.replaceAll('\\', '/');
|
|
3190
|
+
const expectedSuffix = `.workbuddy/plugins/marketplaces/${CODEBUDDY_MARKETPLACE_NAME}/plugins/${action.plugin}`;
|
|
3191
|
+
if (!normalizedPath.endsWith(expectedSuffix)) {
|
|
3192
|
+
return createResult({
|
|
3193
|
+
actionType,
|
|
3194
|
+
status: ActionStatus.OBSERVED,
|
|
3195
|
+
observation: {
|
|
3196
|
+
installed: false,
|
|
3197
|
+
error: `codebuddy attestation installPath must end with "${expectedSuffix}"`,
|
|
3198
|
+
},
|
|
3199
|
+
});
|
|
3200
|
+
}
|
|
3201
|
+
const installPathAbs = resolve(normalizedAttestation.installPath);
|
|
3202
|
+
let lexicalStat;
|
|
3203
|
+
try {
|
|
3204
|
+
lexicalStat = await lstat(installPathAbs);
|
|
3205
|
+
} catch {
|
|
3206
|
+
return createResult({
|
|
3207
|
+
actionType,
|
|
3208
|
+
status: ActionStatus.OBSERVED,
|
|
3209
|
+
observation: {
|
|
3210
|
+
installed: false,
|
|
3211
|
+
error: `codebuddy attestation installPath does not exist: ${normalizedAttestation.installPath}`,
|
|
3212
|
+
},
|
|
3213
|
+
});
|
|
3214
|
+
}
|
|
3215
|
+
if (lexicalStat.isSymbolicLink() || !lexicalStat.isDirectory()) {
|
|
2407
3216
|
return createResult({
|
|
2408
3217
|
actionType,
|
|
2409
3218
|
status: ActionStatus.OBSERVED,
|
|
2410
3219
|
observation: {
|
|
2411
3220
|
installed: false,
|
|
2412
|
-
error:
|
|
3221
|
+
error: 'codebuddy attestation installPath must be a real directory, not a symlink',
|
|
2413
3222
|
},
|
|
2414
3223
|
});
|
|
2415
3224
|
}
|
|
3225
|
+
verifiedInstallPath = await realpath(installPathAbs);
|
|
2416
3226
|
}
|
|
2417
3227
|
|
|
2418
|
-
//
|
|
2419
|
-
//
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
3228
|
+
// 人工结果仍裁决不可自动化的安装步骤;新资源闭包计划另外
|
|
3229
|
+
// 暴露已校验的真实安装目录,供 verify 的内置只读门禁扫描。
|
|
3230
|
+
return createResult({
|
|
3231
|
+
actionType,
|
|
3232
|
+
status: ActionStatus.OBSERVED,
|
|
3233
|
+
observation: {
|
|
3234
|
+
installed: true,
|
|
3235
|
+
humanConfirmed: true,
|
|
3236
|
+
result: 'passed',
|
|
3237
|
+
actor: normalizedAttestation.actor,
|
|
3238
|
+
confirmedAt: normalizedAttestation.confirmedAt,
|
|
3239
|
+
consumer,
|
|
3240
|
+
plugin: action.plugin,
|
|
3241
|
+
version: action.version,
|
|
3242
|
+
repo: action.repo,
|
|
3243
|
+
ref: expectedRef,
|
|
3244
|
+
entrySkill: action.entrySkill,
|
|
3245
|
+
entrySkillFound: true,
|
|
3246
|
+
manifestDigest: action.manifestDigest,
|
|
3247
|
+
planDigest: boundPlanDigest,
|
|
3248
|
+
...(verifiedInstallPath ? { installPath: verifiedInstallPath } : {}),
|
|
3249
|
+
},
|
|
3250
|
+
});
|
|
3251
|
+
}
|
|
3252
|
+
|
|
3253
|
+
// Codex human-attestation fallback: if the CLI was unavailable
|
|
3254
|
+
// during execute, a human attestation may exist in the authority dir.
|
|
3255
|
+
// Read and validate it instead of the CLI evidence.
|
|
3256
|
+
//
|
|
3257
|
+
// CRITICAL: observe only reads human results when a bound, validated
|
|
3258
|
+
// Codex manual requirement exists. Isolated attestation files must not
|
|
3259
|
+
// bypass automatic evidence.
|
|
3260
|
+
if (
|
|
3261
|
+
platform.degradationPolicy === 'human-attestation-with-fallback'
|
|
3262
|
+
&& actionType === ActionType.CODEX_MARKETPLACE_INSTALL
|
|
3263
|
+
) {
|
|
3264
|
+
const expectedRef = action.ref ?? `v${action.version}`;
|
|
2447
3265
|
|
|
2448
|
-
//
|
|
2449
|
-
|
|
2450
|
-
let installedManifest;
|
|
3266
|
+
// Try to read the human attestation from the authority dir.
|
|
3267
|
+
let codexBoundPlanDigest;
|
|
2451
3268
|
try {
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
} catch (entryErr) {
|
|
2456
|
-
return createResult({
|
|
2457
|
-
actionType,
|
|
2458
|
-
status: ActionStatus.OBSERVED,
|
|
2459
|
-
observation: {
|
|
2460
|
-
installed: true,
|
|
2461
|
-
installPath: installPathReal,
|
|
2462
|
-
manifestDigest,
|
|
2463
|
-
entrySkillFound: false,
|
|
2464
|
-
error: `codebuddy entry skill not resolvable in installed copy: ${entryErr.message}`,
|
|
2465
|
-
},
|
|
2466
|
-
});
|
|
3269
|
+
codexBoundPlanDigest = await resolveCodexBoundPlanDigest(context);
|
|
3270
|
+
} catch {
|
|
3271
|
+
// No frozen plan — cannot read attestation
|
|
2467
3272
|
}
|
|
3273
|
+
if (codexBoundPlanDigest) {
|
|
3274
|
+
let codexAttestationDir;
|
|
3275
|
+
try {
|
|
3276
|
+
codexAttestationDir = codexAuthorityDir(context, codexBoundPlanDigest, action.plugin);
|
|
3277
|
+
} catch {
|
|
3278
|
+
// Authority dir not resolvable
|
|
3279
|
+
}
|
|
3280
|
+
if (codexAttestationDir) {
|
|
3281
|
+
// CRITICAL: Only read attestation if requirement exists and is bound
|
|
3282
|
+
let codexRequirement = null;
|
|
3283
|
+
try {
|
|
3284
|
+
codexRequirement = JSON.parse(await readFile(resolve(codexAttestationDir, CODEX_REQUIREMENT_FILE), 'utf8'));
|
|
3285
|
+
} catch {
|
|
3286
|
+
// No requirement file — must not read isolated attestation
|
|
3287
|
+
}
|
|
2468
3288
|
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
3289
|
+
// Validate requirement binds to this action and plan (完整绑定验证)
|
|
3290
|
+
if (
|
|
3291
|
+
codexRequirement
|
|
3292
|
+
&& codexRequirement.planDigest === codexBoundPlanDigest
|
|
3293
|
+
&& codexRequirement.plugin === action.plugin
|
|
3294
|
+
&& codexRequirement.version === action.version
|
|
3295
|
+
&& codexRequirement.repo === action.repo
|
|
3296
|
+
&& codexRequirement.ref === (action.ref ?? `v${action.version}`)
|
|
3297
|
+
&& (!action.entrySkill || codexRequirement.entrySkill === action.entrySkill)
|
|
3298
|
+
) {
|
|
3299
|
+
let codexAttestation = null;
|
|
3300
|
+
try {
|
|
3301
|
+
codexAttestation = JSON.parse(await readFile(resolve(codexAttestationDir, CODEX_ATTESTATION_FILE), 'utf8'));
|
|
3302
|
+
} catch {
|
|
3303
|
+
// No attestation file — fall through to normal CLI evidence path
|
|
3304
|
+
}
|
|
3305
|
+
if (codexAttestation) {
|
|
3306
|
+
const codexCheck = validateCodexAttestation(codexAttestation, action, new Date().toISOString(), codexBoundPlanDigest);
|
|
3307
|
+
if (!codexCheck.valid) {
|
|
3308
|
+
return createResult({
|
|
3309
|
+
actionType,
|
|
3310
|
+
status: ActionStatus.OBSERVED,
|
|
3311
|
+
observation: { installed: false, error: codexCheck.error },
|
|
3312
|
+
});
|
|
3313
|
+
}
|
|
3314
|
+
if (codexAttestation.result === 'failed') {
|
|
3315
|
+
const errorMsg = `codex human result: failed${codexAttestation.note ? ` (${codexAttestation.note})` : ''}`;
|
|
3316
|
+
return createResult({
|
|
3317
|
+
actionType,
|
|
3318
|
+
status: ActionStatus.OBSERVED,
|
|
3319
|
+
error: errorMsg,
|
|
3320
|
+
observation: {
|
|
3321
|
+
installed: false,
|
|
3322
|
+
humanConfirmed: true,
|
|
3323
|
+
result: 'failed',
|
|
3324
|
+
actor: codexAttestation.actor,
|
|
3325
|
+
confirmedAt: codexAttestation.confirmedAt,
|
|
3326
|
+
error: errorMsg,
|
|
3327
|
+
},
|
|
3328
|
+
});
|
|
3329
|
+
}
|
|
3330
|
+
return createResult({
|
|
3331
|
+
actionType,
|
|
3332
|
+
status: ActionStatus.OBSERVED,
|
|
3333
|
+
observation: {
|
|
3334
|
+
installed: true,
|
|
3335
|
+
humanConfirmed: true,
|
|
3336
|
+
result: 'passed',
|
|
3337
|
+
actor: codexAttestation.actor,
|
|
3338
|
+
confirmedAt: codexAttestation.confirmedAt,
|
|
3339
|
+
consumer,
|
|
3340
|
+
plugin: action.plugin,
|
|
3341
|
+
version: action.version,
|
|
3342
|
+
repo: action.repo,
|
|
3343
|
+
ref: expectedRef,
|
|
3344
|
+
entrySkill: action.entrySkill,
|
|
3345
|
+
entrySkillFound: true,
|
|
3346
|
+
manifestDigest: action.manifestDigest,
|
|
3347
|
+
planDigest: codexBoundPlanDigest,
|
|
3348
|
+
},
|
|
3349
|
+
});
|
|
3350
|
+
}
|
|
3351
|
+
}
|
|
3352
|
+
}
|
|
2495
3353
|
}
|
|
2496
|
-
|
|
2497
|
-
// Build the observation from independently verified fields only.
|
|
2498
|
-
// marketplace + installChannel ARE validated codebuddy identity
|
|
2499
|
-
// fields (unlike kimi's MINOR-1 marketplace exclusion).
|
|
2500
|
-
const observation = {
|
|
2501
|
-
installed: true,
|
|
2502
|
-
installPath: installPathReal,
|
|
2503
|
-
entrySkillFound: true,
|
|
2504
|
-
entrySkill: action.entrySkill,
|
|
2505
|
-
manifestDigest,
|
|
2506
|
-
consumer,
|
|
2507
|
-
plugin: installedManifest.name,
|
|
2508
|
-
version: installedManifest.version,
|
|
2509
|
-
repo: action.repo,
|
|
2510
|
-
ref: expectedRef,
|
|
2511
|
-
marketplace: attestation.marketplace,
|
|
2512
|
-
installChannel: attestation.installChannel,
|
|
2513
|
-
...extraInstalledPathsAudit(payloadBinding),
|
|
2514
|
-
};
|
|
2515
|
-
return createResult({
|
|
2516
|
-
actionType,
|
|
2517
|
-
status: ActionStatus.OBSERVED,
|
|
2518
|
-
observation,
|
|
2519
|
-
});
|
|
2520
3354
|
}
|
|
2521
3355
|
|
|
2522
3356
|
// Read execute evidence — mandatory for observe validation
|