release-skill 0.2.2 → 0.2.4

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.
Files changed (54) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.codebuddy-plugin/plugin.json +4 -2
  4. package/.codex-plugin/plugin.json +2 -2
  5. package/.kimi-plugin/plugin.json +1 -1
  6. package/CHANGELOG.md +41 -0
  7. package/CONTRIBUTING.md +27 -0
  8. package/INSTALL.md +95 -139
  9. package/INSTALL.zh-CN.md +70 -121
  10. package/README.md +265 -916
  11. package/README.zh-CN.md +222 -537
  12. package/adapters/claude/.claude-plugin/marketplace.json +1 -1
  13. package/adapters/claude/.claude-plugin/plugin.json +1 -1
  14. package/adapters/claude/bin/release-skill.bundle.mjs +18710 -16694
  15. package/adapters/claude/schemas/release-plan.schema.json +137 -0
  16. package/adapters/claude/schemas/release-project.schema.json +93 -0
  17. package/adapters/claude/schemas/release-run.schema.json +70 -2
  18. package/adapters/codex/.codex-plugin/plugin.json +2 -2
  19. package/adapters/codex/bin/release-skill.bundle.mjs +18710 -16694
  20. package/adapters/codex/schemas/release-plan.schema.json +137 -0
  21. package/adapters/codex/schemas/release-project.schema.json +93 -0
  22. package/adapters/codex/schemas/release-run.schema.json +70 -2
  23. package/adapters/kimi/.kimi-plugin/plugin.json +1 -1
  24. package/adapters/kimi/bin/release-skill.bundle.mjs +18710 -16694
  25. package/adapters/kimi/schemas/release-plan.schema.json +137 -0
  26. package/adapters/kimi/schemas/release-project.schema.json +93 -0
  27. package/adapters/kimi/schemas/release-run.schema.json +70 -2
  28. package/adapters/workbuddy/.codebuddy-plugin/plugin.json +1 -1
  29. package/adapters/workbuddy/bin/release-skill.bundle.mjs +18710 -16694
  30. package/adapters/workbuddy/schemas/release-plan.schema.json +137 -0
  31. package/adapters/workbuddy/schemas/release-project.schema.json +93 -0
  32. package/adapters/workbuddy/schemas/release-run.schema.json +70 -2
  33. package/bin/release-skill.bundle.mjs +18710 -16694
  34. package/package.json +1 -1
  35. package/schemas/release-plan.schema.json +137 -0
  36. package/schemas/release-project.schema.json +93 -0
  37. package/schemas/release-run.schema.json +70 -2
  38. package/src/adapters/plugin-marketplace.mjs +1602 -605
  39. package/src/commands/prepare.mjs +441 -32
  40. package/src/commands/publish.mjs +107 -75
  41. package/src/commands/reconcile.mjs +92 -327
  42. package/src/commands/setup.mjs +148 -20
  43. package/src/commands/verify.mjs +315 -25
  44. package/src/core/baseline.mjs +21 -1
  45. package/src/core/checkpoints.mjs +50 -7
  46. package/src/core/config.mjs +15 -0
  47. package/src/core/errors.mjs +2 -0
  48. package/src/core/installation-contract.mjs +341 -0
  49. package/src/core/plan.mjs +307 -6
  50. package/src/platforms/codebuddy.mjs +191 -238
  51. package/src/platforms/codex.mjs +369 -0
  52. package/src/platforms/kimi.mjs +164 -119
  53. package/src/platforms/registry.mjs +180 -4
  54. package/src/producers/build-adapters.mjs +9 -2
@@ -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
- import { PLATFORMS, getPlatform } from '../platforms/registry.mjs';
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
@@ -448,21 +732,37 @@ async function resolveKimiEntrySkillFile(pluginRootReal, manifest, entrySkill) {
448
732
  * @returns {string} normalized relative path ('' for the plugin root itself)
449
733
  */
450
734
  function normalizeCodeBuddySkillsRel(skillsRaw) {
451
- if (typeof skillsRaw !== 'string' || skillsRaw.length === 0) {
735
+ // CodeBuddy real validator accepts array form; extract first element for
736
+ // backward compatibility with string form.
737
+ let skillsPath;
738
+ if (Array.isArray(skillsRaw)) {
739
+ if (skillsRaw.length !== 1) {
740
+ throw new Error(
741
+ `codebuddy manifest skills array must have exactly one element, got ${skillsRaw.length}`,
742
+ );
743
+ }
744
+ skillsPath = skillsRaw[0];
745
+ } else if (typeof skillsRaw === 'string') {
746
+ skillsPath = skillsRaw;
747
+ } else {
748
+ throw new Error('codebuddy manifest skills must be a string or single-element array when present');
749
+ }
750
+
751
+ if (typeof skillsPath !== 'string' || skillsPath.length === 0) {
452
752
  throw new Error('codebuddy manifest skills must be a non-empty relative path when present');
453
753
  }
454
754
  if (
455
- skillsRaw.startsWith('/') ||
456
- skillsRaw.includes('..') ||
457
- skillsRaw.includes('\\') ||
458
- /^https?:\/\//i.test(skillsRaw)
755
+ skillsPath.startsWith('/') ||
756
+ skillsPath.includes('..') ||
757
+ skillsPath.includes('\\') ||
758
+ /^https?:\/\//i.test(skillsPath)
459
759
  ) {
460
- throw new Error(`codebuddy manifest skills "${skillsRaw}" is not a safe relative path`);
760
+ throw new Error(`codebuddy manifest skills "${skillsPath}" is not a safe relative path`);
461
761
  }
462
- let rel = skillsRaw.replace(/^\.\//, '');
762
+ let rel = skillsPath.replace(/^\.\//, '');
463
763
  rel = rel.replace(/\/+$/, '');
464
764
  if (rel.split('/').some((segment) => segment === '' || segment === '.' || segment === '..')) {
465
- throw new Error(`codebuddy manifest skills "${skillsRaw}" is not a safe relative path`);
765
+ throw new Error(`codebuddy manifest skills "${skillsPath}" is not a safe relative path`);
466
766
  }
467
767
  return rel;
468
768
  }
@@ -548,9 +848,55 @@ const SUPPORTED_TYPES = [
548
848
  ActionType.CODEBUDDY_MARKETPLACE_INSTALL,
549
849
  ];
550
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
+
551
894
  /** Safe repo pattern: owner/repo with alphanumeric, hyphens, dots, underscores. */
552
895
  const SAFE_REPO_RE = /^[a-zA-Z0-9][a-zA-Z0-9._-]*\/[a-zA-Z0-9][a-zA-Z0-9._-]*$/;
553
896
 
897
+ /** Safe digest pattern: 64-hex SHA-256. */
898
+ const SAFE_DIGEST_RE = /^[0-9a-f]{64}$/;
899
+
554
900
  /** Valid consumer ids, derived from the platform registry (single source). */
555
901
  const CONSUMER_IDS = new Set(PLATFORMS.map((p) => p.id));
556
902
 
@@ -634,7 +980,9 @@ function validateMarketplaceParams(params) {
634
980
  // marketplace but NO non-interactive install API, so `marketplace` carries no
635
981
  // executable meaning for kimi and is optional (validated only if present); it
636
982
  // must not become a required identity condition for kimi execution/observe.
637
- if (!getPlatform(consumer).automatable) {
983
+ const consumerPlatform = getPlatform(consumer);
984
+ const consumerRoute = resolvePlatformRoute(consumerPlatform);
985
+ if (consumerRoute.route === 'human-attestation') {
638
986
  if (marketplace !== undefined && marketplace !== null && !SAFE_ID_RE.test(marketplace)) {
639
987
  return { valid: false, error: `unsafe marketplace identifier: "${marketplace}"` };
640
988
  }
@@ -715,6 +1063,98 @@ async function checkRequiredFiles(dir, requiredFiles) {
715
1063
  return { allPresent: missing.length === 0, missing };
716
1064
  }
717
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
+
718
1158
  /**
719
1159
  * Create the plugin-marketplace adapter.
720
1160
  *
@@ -881,130 +1321,709 @@ export function createPluginMarketplaceAdapter(deps = {}) {
881
1321
  });
882
1322
  }
883
1323
 
884
- // 6. Verify frozen snapshot exists and contains required marketplace files
1324
+ // Capability conflict check (before sourceDescriptor validation).
1325
+ // If the platform definition is internally inconsistent, fail closed.
885
1326
  const consumer = action.consumer;
886
- const platform = getPlatform(consumer);
887
- let snapshotDirReal;
888
- // Authoritative kimi manifest (from the frozen snapshot), used to
889
- // resolve the entry skill via the manifest-declared skills root.
890
- let kimiSnapshotManifest = null;
891
- try {
892
- snapshotDirReal = await resolveFrozenPath(context.root, snapshotPath, 'frozen snapshot path');
893
- } catch (frozenErr) {
1327
+ const consumerPlatform = getPlatform(consumer);
1328
+ const capabilityConflicts = resolveCapabilityConflicts(consumerPlatform);
1329
+ if (capabilityConflicts.length > 0) {
894
1330
  return createResult({
895
1331
  actionType,
896
1332
  status: ActionStatus.PREFLIGHT_FAILED,
897
- error: `frozen snapshot validation failed: ${frozenErr.message}`,
1333
+ error: `platform capability conflict: ${capabilityConflicts.join('; ')}`,
898
1334
  });
899
1335
  }
900
1336
 
901
- // A non-automatable platform (kimi, codebuddy) has no trustworthy
902
- // automated install: the whole repo is installed as one plugin. The
903
- // authoritative manifest is read from the verified snapshot root via
904
- // the platform strategy (kimi: kimi.plugin.json over
905
- // .kimi-plugin/plugin.json; codebuddy: .codebuddy-plugin/plugin.json).
906
- if (!platform.automatable) {
907
- // Error-message label keeps the kimi wording byte-identical while
908
- // giving codebuddy its own wording.
909
- const manifestLabel = consumer === 'codebuddy' ? 'codebuddy' : 'kimi';
910
- let kimiManifestResult;
911
- try {
912
- kimiManifestResult = await platform.strategy.readManifest(snapshotDirReal);
913
- } catch (manifestErr) {
1337
+ // 6. 统一静态预检:四平台共同执行安装来源与安装契约校验。
1338
+ // 旧冻结计划(整组新版字段完全不存在时)保留既有路径。
1339
+ // 新版字段组(installationContractDigest、algorithmVersion、marketplaceSourceType 为标志)
1340
+ // 出现任一字段时必须完整。0.2.3 已有的 marketplaceForm、sourceDescriptor、sourceCommit
1341
+ // 单独出现不得触发新版组。
1342
+ const route = resolvePlatformRoute(consumerPlatform);
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) {
914
1359
  return createResult({
915
1360
  actionType,
916
1361
  status: ActionStatus.PREFLIGHT_FAILED,
917
- error: `frozen snapshot ${manifestLabel} manifest invalid: ${manifestErr.message}`,
1362
+ error: `新版字段组不完整,缺失: ${missingNewFields.join(', ')};任一出现则要求全组完整`,
918
1363
  });
919
1364
  }
920
- const kimiManifest = kimiManifestResult.manifest;
921
- if (typeof kimiManifest.name !== 'string' || kimiManifest.name !== action.plugin) {
1365
+
1366
+ const sd = action.sourceDescriptor;
1367
+ if (!sd || typeof sd !== 'object') {
922
1368
  return createResult({
923
1369
  actionType,
924
1370
  status: ActionStatus.PREFLIGHT_FAILED,
925
- error: `plugin manifest name "${kimiManifest.name}" does not match action plugin "${action.plugin}"`,
1371
+ error: 'sourceDescriptor is required when installationContractDigest is present',
926
1372
  });
927
1373
  }
928
- if (typeof kimiManifest.version !== 'string' || kimiManifest.version !== action.version) {
1374
+
1375
+ // marketplaceForm 三方一致性
1376
+ if (action.marketplaceForm !== sd.form || action.marketplaceForm !== action.marketplaceSourceType) {
929
1377
  return createResult({
930
1378
  actionType,
931
1379
  status: ActionStatus.PREFLIGHT_FAILED,
932
- error: `plugin manifest version "${kimiManifest.version}" does not match action version "${action.version}"`,
1380
+ error: `marketplaceForm "${action.marketplaceForm}"、sourceDescriptor.form "${sd.form}"、marketplaceSourceType "${action.marketplaceSourceType}" 三者必须一致`,
933
1381
  });
934
1382
  }
935
- kimiSnapshotManifest = kimiManifest;
936
- }
937
1383
 
938
- if (platform.automatable) {
939
- if (action.marketplaceLocation === 'external') {
940
- // External independent marketplace form: the marketplace index lives
941
- // in the external repository (frozen by prepare), NOT in the unit
942
- // snapshot, so the snapshot marketplace-manifest section is skipped
943
- // entirely. The plugin manifest is read directly from the snapshot
944
- // root and the frozen external identity fields are validated. ref was
945
- // already injection-checked above (step 2); the entry skill reuses the
946
- // automatable fixed layout validated below.
947
- if (
948
- typeof action.marketplaceCommitSha !== 'string'
949
- || !/^[0-9a-f]{40}$/.test(action.marketplaceCommitSha)
950
- ) {
951
- return createResult({
952
- actionType,
953
- status: ActionStatus.PREFLIGHT_FAILED,
954
- error: `external marketplace marketplaceCommitSha must be a 40-hex commit sha, got ${JSON.stringify(action.marketplaceCommitSha)}`,
955
- });
956
- }
957
- if (
958
- typeof action.repo !== 'string'
959
- || !/^[a-z0-9][a-z0-9._-]*\/[a-z0-9][a-z0-9._-]*$/.test(action.repo)
960
- ) {
961
- return createResult({
962
- actionType,
963
- status: ActionStatus.PREFLIGHT_FAILED,
964
- error: `external marketplace repo must be an owner/name repository, got ${JSON.stringify(action.repo)}`,
965
- });
966
- }
967
- const externalManifestRelative = platform.manifestPaths.plugin;
968
- const externalManifestPath = resolve(snapshotDirReal, externalManifestRelative);
969
- const externalManifestResult = await validateManifestFile(externalManifestPath, ['name', 'version']);
970
- if (!externalManifestResult.valid) {
971
- return createResult({
972
- actionType,
973
- status: ActionStatus.PREFLIGHT_FAILED,
974
- error: `frozen snapshot ${externalManifestRelative} invalid: ${externalManifestResult.error}`,
975
- });
976
- }
977
- if (externalManifestResult.manifest.name !== action.plugin) {
978
- return createResult({
979
- actionType,
980
- status: ActionStatus.PREFLIGHT_FAILED,
981
- error: `plugin manifest name "${externalManifestResult.manifest.name}" does not match action plugin "${action.plugin}"`,
982
- });
983
- }
984
- if (externalManifestResult.manifest.version !== action.version) {
985
- return createResult({
986
- actionType,
987
- status: ActionStatus.PREFLIGHT_FAILED,
988
- error: `plugin manifest version "${externalManifestResult.manifest.version}" does not match action version "${action.version}"`,
989
- });
990
- }
991
- } else {
992
- // Verify marketplace files exist.
993
- // marketplace.json is at the snapshot root; plugin manifest is
994
- // resolved relative to the entry's declared source path.
995
- const marketplaceRelative = platform.manifestPaths.marketplace;
1384
+ // installationContractDigest 格式
1385
+ if (!SAFE_DIGEST_RE.test(action.installationContractDigest)) {
1386
+ return createResult({
1387
+ actionType,
1388
+ status: ActionStatus.PREFLIGHT_FAILED,
1389
+ error: 'installationContractDigest must be a 64-char lowercase hex string',
1390
+ });
1391
+ }
996
1392
 
997
- const marketplacePath = resolve(snapshotDirReal, marketplaceRelative);
1393
+ // algorithmVersion
1394
+ if (action.algorithmVersion !== INSTALLATION_CONTRACT_ALGORITHM_VERSION) {
1395
+ return createResult({
1396
+ actionType,
1397
+ status: ActionStatus.PREFLIGHT_FAILED,
1398
+ error: `algorithmVersion must be ${INSTALLATION_CONTRACT_ALGORITHM_VERSION}, got ${action.algorithmVersion}`,
1399
+ });
1400
+ }
998
1401
 
999
- // marketplace.json must exist and have root name (no root version required)
1000
- const marketplaceResult = await validateManifestFile(marketplacePath, ['name']);
1001
- if (!marketplaceResult.valid) {
1002
- return createResult({
1003
- actionType,
1004
- status: ActionStatus.PREFLIGHT_FAILED,
1005
- error: `frozen snapshot ${marketplaceRelative} invalid: ${marketplaceResult.error}`,
1006
- });
1007
- }
1402
+ // sourceCommit 格式
1403
+ if (typeof action.sourceCommit !== 'string' || !/^[0-9a-f]{40}$/.test(action.sourceCommit)) {
1404
+ return createResult({
1405
+ actionType,
1406
+ status: ActionStatus.PREFLIGHT_FAILED,
1407
+ error: 'sourceCommit must be a 40-char lowercase hex commit sha',
1408
+ });
1409
+ }
1410
+
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
+ }
1419
+ if (typeof sd.marketplaceEntry !== 'string' || sd.marketplaceEntry.length === 0) {
1420
+ return createResult({
1421
+ actionType,
1422
+ status: ActionStatus.PREFLIGHT_FAILED,
1423
+ error: 'sourceDescriptor.marketplaceEntry is required',
1424
+ });
1425
+ }
1426
+ if (sd.marketplaceEntry !== action.plugin) {
1427
+ return createResult({
1428
+ actionType,
1429
+ status: ActionStatus.PREFLIGHT_FAILED,
1430
+ error: `sourceDescriptor.marketplaceEntry "${sd.marketplaceEntry}" does not match action plugin "${action.plugin}"`,
1431
+ });
1432
+ }
1433
+
1434
+ // 来源形态专属字段校验
1435
+ if (sd.form === 'bundled-family') {
1436
+ // bundled-family 完整性
1437
+ if (!sd.repo || !SAFE_REPO_RE.test(sd.repo)) {
1438
+ return createResult({
1439
+ actionType,
1440
+ status: ActionStatus.PREFLIGHT_FAILED,
1441
+ error: 'sourceDescriptor.repo is required and must be a safe repo pattern for bundled-family form',
1442
+ });
1443
+ }
1444
+ if (sd.repo !== action.repo) {
1445
+ return createResult({
1446
+ actionType,
1447
+ status: ActionStatus.PREFLIGHT_FAILED,
1448
+ error: `sourceDescriptor.repo "${sd.repo}" does not match action.repo "${action.repo}"`,
1449
+ });
1450
+ }
1451
+ if (typeof sd.commit !== 'string' || !/^[0-9a-f]{40}$/.test(sd.commit)) {
1452
+ return createResult({
1453
+ actionType,
1454
+ status: ActionStatus.PREFLIGHT_FAILED,
1455
+ error: 'sourceDescriptor.commit must be a 40-hex commit sha for bundled-family form',
1456
+ });
1457
+ }
1458
+ if (sd.commit !== action.sourceCommit) {
1459
+ return createResult({
1460
+ actionType,
1461
+ status: ActionStatus.PREFLIGHT_FAILED,
1462
+ error: `sourceDescriptor.commit "${sd.commit}" does not match action.sourceCommit "${action.sourceCommit}"`,
1463
+ });
1464
+ }
1465
+ if (!sd.payloadDigest || !SAFE_DIGEST_RE.test(sd.payloadDigest)) {
1466
+ return createResult({
1467
+ actionType,
1468
+ status: ActionStatus.PREFLIGHT_FAILED,
1469
+ error: 'sourceDescriptor.payloadDigest must be a 64-hex digest for bundled-family form',
1470
+ });
1471
+ }
1472
+ if (sd.payloadDigest !== action.manifestDigest) {
1473
+ return createResult({
1474
+ actionType,
1475
+ status: ActionStatus.PREFLIGHT_FAILED,
1476
+ error: 'sourceDescriptor.payloadDigest does not match action.manifestDigest',
1477
+ });
1478
+ }
1479
+ if (typeof sd.pluginSubpath !== 'string' || sd.pluginSubpath.length === 0) {
1480
+ return createResult({
1481
+ actionType,
1482
+ status: ActionStatus.PREFLIGHT_FAILED,
1483
+ error: 'sourceDescriptor.pluginSubpath is required for bundled-family form',
1484
+ });
1485
+ }
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) {
1498
+ return createResult({
1499
+ actionType,
1500
+ status: ActionStatus.PREFLIGHT_FAILED,
1501
+ error: 'bundled-family action must not contain standalone-index fields (marketplaceCommitSha, marketplaceName, selectedEntry)',
1502
+ });
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 完整性
1528
+ if (!sd.marketplaceRepo || !SAFE_REPO_RE.test(sd.marketplaceRepo)) {
1529
+ return createResult({
1530
+ actionType,
1531
+ status: ActionStatus.PREFLIGHT_FAILED,
1532
+ error: 'sourceDescriptor.marketplaceRepo is required for standalone-index form',
1533
+ });
1534
+ }
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
+ }
1542
+ if (sd.marketplaceRepo !== action.repo) {
1543
+ return createResult({
1544
+ actionType,
1545
+ status: ActionStatus.PREFLIGHT_FAILED,
1546
+ error: `sourceDescriptor.marketplaceRepo "${sd.marketplaceRepo}" does not match action.repo "${action.repo}"`,
1547
+ });
1548
+ }
1549
+ if (sd.pluginRepo === sd.marketplaceRepo) {
1550
+ return createResult({
1551
+ actionType,
1552
+ status: ActionStatus.PREFLIGHT_FAILED,
1553
+ error: `sourceDescriptor.pluginRepo "${sd.pluginRepo}" must differ from marketplaceRepo "${sd.marketplaceRepo}"`,
1554
+ });
1555
+ }
1556
+ if (typeof sd.marketplaceCommitSha !== 'string' || !/^[0-9a-f]{40}$/.test(sd.marketplaceCommitSha)) {
1557
+ return createResult({
1558
+ actionType,
1559
+ status: ActionStatus.PREFLIGHT_FAILED,
1560
+ error: 'sourceDescriptor.marketplaceCommitSha must be a 40-hex commit sha for standalone-index form',
1561
+ });
1562
+ }
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) {
1573
+ return createResult({
1574
+ actionType,
1575
+ status: ActionStatus.PREFLIGHT_FAILED,
1576
+ error: `sourceDescriptor.marketplaceCommitSha does not match action.marketplaceCommitSha`,
1577
+ });
1578
+ }
1579
+ if (typeof sd.ref !== 'string' || sd.ref.length === 0) {
1580
+ return createResult({
1581
+ actionType,
1582
+ status: ActionStatus.PREFLIGHT_FAILED,
1583
+ error: 'sourceDescriptor.ref is required for standalone-index form',
1584
+ });
1585
+ }
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)) {
1595
+ return createResult({
1596
+ actionType,
1597
+ status: ActionStatus.PREFLIGHT_FAILED,
1598
+ error: 'sourceDescriptor.payloadDigest must be a 64-hex digest for standalone-index form',
1599
+ });
1600
+ }
1601
+ if (sd.payloadDigest !== action.manifestDigest) {
1602
+ return createResult({
1603
+ actionType,
1604
+ status: ActionStatus.PREFLIGHT_FAILED,
1605
+ error: 'sourceDescriptor.payloadDigest does not match action.manifestDigest',
1606
+ });
1607
+ }
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') {
1690
+ return createResult({
1691
+ actionType,
1692
+ status: ActionStatus.PREFLIGHT_FAILED,
1693
+ error: 'sourceDescriptor is required for marketplace install actions',
1694
+ });
1695
+ }
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
+ }
1705
+
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
+ });
1900
+ }
1901
+
1902
+ // A human-attestation platform (kimi, codebuddy) has no trustworthy
1903
+ // automated install: the whole repo is installed as one plugin. The
1904
+ // authoritative manifest is read from the verified snapshot root via
1905
+ // the platform strategy (kimi: kimi.plugin.json over
1906
+ // .kimi-plugin/plugin.json; codebuddy: .codebuddy-plugin/plugin.json).
1907
+ if (platformRoute.route === 'human-attestation') {
1908
+ // Error-message label keeps the kimi wording byte-identical while
1909
+ // giving codebuddy its own wording.
1910
+ const manifestLabel = consumer === 'codebuddy' ? 'codebuddy' : 'kimi';
1911
+ let kimiManifestResult;
1912
+ try {
1913
+ kimiManifestResult = await platform.strategy.readManifest(snapshotDirReal);
1914
+ } catch (manifestErr) {
1915
+ return createResult({
1916
+ actionType,
1917
+ status: ActionStatus.PREFLIGHT_FAILED,
1918
+ error: `frozen snapshot ${manifestLabel} manifest invalid: ${manifestErr.message}`,
1919
+ });
1920
+ }
1921
+ const kimiManifest = kimiManifestResult.manifest;
1922
+ if (typeof kimiManifest.name !== 'string' || kimiManifest.name !== action.plugin) {
1923
+ return createResult({
1924
+ actionType,
1925
+ status: ActionStatus.PREFLIGHT_FAILED,
1926
+ error: `plugin manifest name "${kimiManifest.name}" does not match action plugin "${action.plugin}"`,
1927
+ });
1928
+ }
1929
+ if (typeof kimiManifest.version !== 'string' || kimiManifest.version !== action.version) {
1930
+ return createResult({
1931
+ actionType,
1932
+ status: ActionStatus.PREFLIGHT_FAILED,
1933
+ error: `plugin manifest version "${kimiManifest.version}" does not match action version "${action.version}"`,
1934
+ });
1935
+ }
1936
+ kimiSnapshotManifest = kimiManifest;
1937
+ }
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
+
1944
+ if (platformRoute.route === 'structured-cli') {
1945
+ let sourceDirReal = null;
1946
+ if (action.marketplaceLocation === 'external') {
1947
+ // External independent marketplace form: the marketplace index lives
1948
+ // in the external repository (frozen by prepare), NOT in the unit
1949
+ // snapshot, so the snapshot marketplace-manifest section is skipped
1950
+ // entirely. The plugin manifest is read directly from the snapshot
1951
+ // root and the frozen external identity fields are validated. ref was
1952
+ // already injection-checked above (step 2); the entry skill reuses the
1953
+ // automatable fixed layout validated below.
1954
+ if (
1955
+ typeof action.marketplaceCommitSha !== 'string'
1956
+ || !/^[0-9a-f]{40}$/.test(action.marketplaceCommitSha)
1957
+ ) {
1958
+ return createResult({
1959
+ actionType,
1960
+ status: ActionStatus.PREFLIGHT_FAILED,
1961
+ error: `external marketplace marketplaceCommitSha must be a 40-hex commit sha, got ${JSON.stringify(action.marketplaceCommitSha)}`,
1962
+ });
1963
+ }
1964
+ if (
1965
+ typeof action.repo !== 'string'
1966
+ || !/^[a-z0-9][a-z0-9._-]*\/[a-z0-9][a-z0-9._-]*$/.test(action.repo)
1967
+ ) {
1968
+ return createResult({
1969
+ actionType,
1970
+ status: ActionStatus.PREFLIGHT_FAILED,
1971
+ error: `external marketplace repo must be an owner/name repository, got ${JSON.stringify(action.repo)}`,
1972
+ });
1973
+ }
1974
+ const externalManifestRelative = platform.manifestPaths.plugin;
1975
+ const externalManifestPath = resolve(snapshotDirReal, externalManifestRelative);
1976
+ const externalManifestResult = await validateManifestFile(externalManifestPath, ['name', 'version']);
1977
+ if (!externalManifestResult.valid) {
1978
+ return createResult({
1979
+ actionType,
1980
+ status: ActionStatus.PREFLIGHT_FAILED,
1981
+ error: `frozen snapshot ${externalManifestRelative} invalid: ${externalManifestResult.error}`,
1982
+ });
1983
+ }
1984
+ if (externalManifestResult.manifest.name !== action.plugin) {
1985
+ return createResult({
1986
+ actionType,
1987
+ status: ActionStatus.PREFLIGHT_FAILED,
1988
+ error: `plugin manifest name "${externalManifestResult.manifest.name}" does not match action plugin "${action.plugin}"`,
1989
+ });
1990
+ }
1991
+ if (externalManifestResult.manifest.version !== action.version) {
1992
+ return createResult({
1993
+ actionType,
1994
+ status: ActionStatus.PREFLIGHT_FAILED,
1995
+ error: `plugin manifest version "${externalManifestResult.manifest.version}" does not match action version "${action.version}"`,
1996
+ });
1997
+ }
1998
+ } else {
1999
+ // Verify marketplace files exist.
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
+ }
2015
+
2016
+ const marketplacePath = resolve(snapshotDirReal, marketplaceRelative);
2017
+
2018
+ // marketplace.json must exist and have root name (no root version required)
2019
+ const marketplaceResult = await validateManifestFile(marketplacePath, ['name']);
2020
+ if (!marketplaceResult.valid) {
2021
+ return createResult({
2022
+ actionType,
2023
+ status: ActionStatus.PREFLIGHT_FAILED,
2024
+ error: `frozen snapshot ${marketplaceRelative} invalid: ${marketplaceResult.error}`,
2025
+ });
2026
+ }
1008
2027
 
1009
2028
  // Root name must equal action.marketplace
1010
2029
  if (marketplaceResult.manifest.name !== action.marketplace) {
@@ -1040,6 +2059,7 @@ export function createPluginMarketplaceAdapter(deps = {}) {
1040
2059
  // empty strings. Normalized to "." for root layouts; the same
1041
2060
  // helper backs verify-side payload subtree resolution, so both
1042
2061
  // paths can never drift apart.
2062
+ // source 相对市场根(mktRoot)解析,而非快照根。
1043
2063
  let sourcePath;
1044
2064
  try {
1045
2065
  sourcePath = extractDeclaredPluginSource(consumer, entry);
@@ -1052,8 +2072,10 @@ export function createPluginMarketplaceAdapter(deps = {}) {
1052
2072
  }
1053
2073
  // Verify the declared source directory exists and contains the
1054
2074
  // expected plugin manifest inside the frozen snapshot.
1055
- const sourceDirAbs = resolve(snapshotDirReal, sourcePath);
1056
- const sourceDirReal = await realpath(sourceDirAbs).catch(() => null);
2075
+ // source 相对市场根解析
2076
+ const mktRootAbs = mktRoot === '.' ? snapshotDirReal : resolve(snapshotDirReal, mktRoot);
2077
+ const sourceDirAbs = resolve(mktRootAbs, sourcePath);
2078
+ sourceDirReal = await realpath(sourceDirAbs).catch(() => null);
1057
2079
  if (!sourceDirReal) {
1058
2080
  return createResult({
1059
2081
  actionType,
@@ -1070,13 +2092,18 @@ export function createPluginMarketplaceAdapter(deps = {}) {
1070
2092
  error: `marketplace plugin entry source "${sourcePath}" escapes the frozen snapshot`,
1071
2093
  });
1072
2094
  }
2095
+ // Save plugin root for entry skill validation (visible outside structured-cli block)
2096
+ pluginRootForEntrySkill = sourceDirReal;
1073
2097
 
1074
2098
  // Resolve plugin manifest relative to the declared source path.
1075
2099
  // For root layouts (source: "./"), this resolves to
1076
2100
  // snapshot/.claude-plugin/plugin.json
1077
- // For subdirectory layouts (source: "./adapters/claude"), this resolves to
1078
- // snapshot/adapters/claude/.claude-plugin/plugin.json
1079
- const manifestRelative = join(sourcePath, platform.manifestPaths.plugin);
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}`;
1080
2107
  const manifestPath = resolve(snapshotDirReal, manifestRelative);
1081
2108
 
1082
2109
  const manifestResult = await validateManifestFile(manifestPath, ['name', 'version']);
@@ -1128,13 +2155,13 @@ export function createPluginMarketplaceAdapter(deps = {}) {
1128
2155
  }
1129
2156
 
1130
2157
  // Verify the entry skill exists in the snapshot.
1131
- // Automatable platforms' manifests always declare ./skills/, so the
2158
+ // Structured-cli platforms' manifests always declare ./skills/, so the
1132
2159
  // fixed skills/<entrySkill>/SKILL.md layout is authoritative for
1133
- // them. A non-automatable platform (kimi, codebuddy) resolves the
2160
+ // them. A human-attestation platform (kimi, codebuddy) resolves the
1134
2161
  // entry skill via the manifest-declared skills root (MAJOR-4): the
1135
2162
  // root is validated + realpath-contained, and omitted `skills` means
1136
2163
  // the official single-skill root SKILL.md.
1137
- if (!platform.automatable) {
2164
+ if (platformRoute.route === 'human-attestation') {
1138
2165
  const resolveEntrySkillFile = consumer === 'codebuddy'
1139
2166
  ? resolveCodeBuddyEntrySkillFile
1140
2167
  : resolveKimiEntrySkillFile;
@@ -1148,14 +2175,19 @@ export function createPluginMarketplaceAdapter(deps = {}) {
1148
2175
  });
1149
2176
  }
1150
2177
  } else {
1151
- const entrySkillFile = resolve(snapshotDirReal, 'skills', action.entrySkill, 'SKILL.md');
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');
1152
2183
  try {
1153
2184
  await stat(entrySkillFile);
1154
2185
  } catch {
2186
+ const skillRel = relative(snapshotDirReal, entrySkillFile) || `skills/${action.entrySkill}/SKILL.md`;
1155
2187
  return createResult({
1156
2188
  actionType,
1157
2189
  status: ActionStatus.PREFLIGHT_FAILED,
1158
- error: `entry skill not found in snapshot: skills/${action.entrySkill}/SKILL.md`,
2190
+ error: `entry skill not found in snapshot: ${skillRel}`,
1159
2191
  });
1160
2192
  }
1161
2193
  }
@@ -1167,15 +2199,37 @@ export function createPluginMarketplaceAdapter(deps = {}) {
1167
2199
  return createResult({
1168
2200
  actionType,
1169
2201
  status: ActionStatus.PREFLIGHT_FAILED,
1170
- error: `manifestDigest mismatch: expected ${manifestDigest.slice(0, 16)}..., actual ${actualDigest.slice(0, 16)}...`,
2202
+ error: `manifestDigest mismatch: expected ${manifestDigest.slice(0, 16)}..., actual ${actualDigest.slice(0, 16)}...`,
2203
+ });
2204
+ }
2205
+ } catch (digestErr) {
2206
+ return createResult({
2207
+ actionType,
2208
+ status: ActionStatus.PREFLIGHT_FAILED,
2209
+ error: `failed to compute snapshot digest: ${digestErr.message}`,
2210
+ });
2211
+ }
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}`,
1171
2231
  });
1172
2232
  }
1173
- } catch (digestErr) {
1174
- return createResult({
1175
- actionType,
1176
- status: ActionStatus.PREFLIGHT_FAILED,
1177
- error: `failed to compute snapshot digest: ${digestErr.message}`,
1178
- });
1179
2233
  }
1180
2234
 
1181
2235
  return createResult({
@@ -1341,10 +2395,11 @@ export function createPluginMarketplaceAdapter(deps = {}) {
1341
2395
  // It is handled entirely by its manual-requirement strategy, which
1342
2396
  // uses a stable plan-digest-keyed home and deliberately SKIPS the
1343
2397
  // per-run isolated consumer dir and its runDir containment check
1344
- // (that model only fits automatable platforms, which exec a real
2398
+ // (that model only fits structured-cli platforms, which exec a real
1345
2399
  // CLI into a per-run HOME).
1346
2400
  const platform = getPlatform(action.consumer);
1347
- if (!platform.automatable) {
2401
+ const executeRoute = resolvePlatformRoute(platform);
2402
+ if (executeRoute.route === 'human-attestation') {
1348
2403
  return platform.strategy.buildManualRequirement(action, context);
1349
2404
  }
1350
2405
 
@@ -1431,6 +2486,11 @@ export function createPluginMarketplaceAdapter(deps = {}) {
1431
2486
  }
1432
2487
  }
1433
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
+ }
1434
2494
  return createResult({
1435
2495
  actionType,
1436
2496
  status: ActionStatus.EXECUTE_FAILED,
@@ -1505,6 +2565,11 @@ export function createPluginMarketplaceAdapter(deps = {}) {
1505
2565
  }
1506
2566
  }
1507
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
+ }
1508
2573
  return createResult({
1509
2574
  actionType,
1510
2575
  status: ActionStatus.EXECUTE_FAILED,
@@ -1571,6 +2636,18 @@ export function createPluginMarketplaceAdapter(deps = {}) {
1571
2636
  observation: executeObservation,
1572
2637
  });
1573
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
+ }
1574
2651
  return createResult({
1575
2652
  actionType,
1576
2653
  status: ActionStatus.EXECUTE_FAILED,
@@ -1641,384 +2718,96 @@ export function createPluginMarketplaceAdapter(deps = {}) {
1641
2718
  allPresent: check.allPresent,
1642
2719
  missing: check.missing,
1643
2720
  pluginDir,
1644
- },
1645
- });
1646
- }
1647
-
1648
- // Marketplace install observe
1649
- if (
1650
- actionType === ActionType.CLAUDE_MARKETPLACE_INSTALL ||
1651
- actionType === ActionType.CODEX_MARKETPLACE_INSTALL ||
1652
- actionType === ActionType.KIMI_MARKETPLACE_INSTALL ||
1653
- actionType === ActionType.CODEBUDDY_MARKETPLACE_INSTALL
1654
- ) {
1655
- const consumer = action.consumer;
1656
- const runDir = context.runDir;
1657
- if (!runDir) {
1658
- return createResult({
1659
- actionType,
1660
- status: ActionStatus.OBSERVED,
1661
- observation: { installed: false, error: 'context.runDir is required' },
1662
- });
1663
- }
1664
- const isolatedHome = resolve(runDir, 'consumers', `${consumer}-${action.plugin}`);
1665
- // Registry-driven platform data. observe historically applies no
1666
- // consumer validation gate (execute/preflight validate it), so an
1667
- // unregistered consumer keeps the legacy fall-through shape: a
1668
- // kimi-shaped env, no attestation branch, and no CLI binary — it
1669
- // still fails closed on the missing execute evidence below.
1670
- const platform = PLATFORMS.find((p) => p.id === consumer) ?? null;
1671
- const cliCmd = platform ? (platform.cli ? platform.cli.binary : null) : 'kimi';
1672
- const baseEnv = { ...process.env, ...(context.env ?? {}) };
1673
- const env = {
1674
- ...baseEnv,
1675
- ...(platform
1676
- ? platform.isolationEnv(isolatedHome)
1677
- : { HOME: isolatedHome, KIMI_CODE_HOME: isolatedHome }),
1678
- };
1679
-
1680
- // Resolve frozen timeoutMs from the expanded action (top-level).
1681
- // Default to 300000 for old plans. Fail closed on invalid values.
1682
- let frozenTimeoutMs;
1683
- try {
1684
- frozenTimeoutMs = resolveTimeoutMs(action);
1685
- } catch (timeoutErr) {
1686
- return createResult({
1687
- actionType,
1688
- status: ActionStatus.OBSERVED,
1689
- observation: { installed: false, error: timeoutErr.message },
1690
- error: timeoutErr.message,
1691
- });
1692
- }
1693
-
1694
- // Non-automatable platform protocol capability gap (BLOCKER-1):
1695
- // there is NO `kimi plugins list --json` interface. observe never
1696
- // execs a kimi command. Instead it consumes a structured human
1697
- // attestation (written after the interactive install) bound to the
1698
- // frozen plan digest and expected identity, then performs read-only
1699
- // verification of the installed managed copy: payload digest vs the
1700
- // sealed authority, entry skill resolved via the manifest skills
1701
- // root (MAJOR-4), and manifest name/version.
1702
- // Missing/expired/mismatched/escaping proof fails closed so a kimi
1703
- // unit can never reach VERIFIED without it.
1704
- if (platform && !platform.automatable && actionType === ActionType.KIMI_MARKETPLACE_INSTALL) {
1705
- const expectedRef = action.ref ?? `v${action.version}`;
1706
-
1707
- // Bind to the REAL frozen plan digest (A). Fail closed if the
1708
- // context does not carry an intact frozen plan.
1709
- let boundPlanDigest;
1710
- try {
1711
- boundPlanDigest = resolveBoundPlanDigest(context);
1712
- } catch (planErr) {
1713
- return createResult({
1714
- actionType,
1715
- status: ActionStatus.OBSERVED,
1716
- observation: {
1717
- installed: false,
1718
- error: `cannot bind kimi observation to the frozen plan: ${planErr.message}`,
1719
- },
1720
- });
1721
- }
1722
-
1723
- // Stable, cross-run attestation authority (B). The requirement and
1724
- // attestation live here, keyed by the verified plan digest + plugin,
1725
- // so they survive publish PARTIAL -> reconcile -> verify (each of
1726
- // which uses a fresh runDir).
1727
- let attestationDir;
1728
- try {
1729
- attestationDir = kimiAuthorityDir(context, boundPlanDigest, action.plugin);
1730
- } catch (dirErr) {
1731
- return createResult({
1732
- actionType,
1733
- status: ActionStatus.OBSERVED,
1734
- observation: { installed: false, error: dirErr.message },
1735
- });
1736
- }
1737
-
1738
- // The execute-emitted requirement must exist and bind to the action
1739
- // and the frozen plan digest.
1740
- let requirement = null;
1741
- try {
1742
- requirement = JSON.parse(await readFile(resolve(attestationDir, KIMI_REQUIREMENT_FILE), 'utf8'));
1743
- } catch {
1744
- return createResult({
1745
- actionType,
1746
- status: ActionStatus.OBSERVED,
1747
- observation: {
1748
- installed: false,
1749
- manualInstallRequired: true,
1750
- error: 'kimi manual-install requirement is missing; run execute first',
1751
- },
1752
- });
1753
- }
1754
- if (
1755
- requirement.planDigest !== boundPlanDigest ||
1756
- requirement.plugin !== action.plugin ||
1757
- requirement.version !== action.version ||
1758
- requirement.entrySkill !== action.entrySkill ||
1759
- requirement.repo !== action.repo ||
1760
- requirement.ref !== expectedRef
1761
- ) {
1762
- return createResult({
1763
- actionType,
1764
- status: ActionStatus.OBSERVED,
1765
- observation: {
1766
- installed: false,
1767
- error: 'kimi manual-install requirement does not match the frozen plan/action',
1768
- },
1769
- });
1770
- }
1771
-
1772
- // The trusted human attestation is mandatory and is read from the
1773
- // stable authority dir. Without it the interactive install has not
1774
- // been proven: fail closed.
1775
- let attestation = null;
1776
- try {
1777
- attestation = JSON.parse(await readFile(resolve(attestationDir, KIMI_ATTESTATION_FILE), 'utf8'));
1778
- } catch {
1779
- return createResult({
1780
- actionType,
1781
- status: ActionStatus.OBSERVED,
1782
- observation: {
1783
- installed: false,
1784
- manualInstallRequired: true,
1785
- installUrl: requirement.installUrl,
1786
- attestationDir,
1787
- error: `kimi attestation is missing; write ${resolve(attestationDir, KIMI_ATTESTATION_FILE)} after the interactive install (${requirement.installUrl})`,
1788
- },
1789
- });
1790
- }
1791
-
1792
- const attestationCheck = validateKimiAttestation(attestation, action, new Date().toISOString(), boundPlanDigest);
1793
- if (!attestationCheck.valid) {
1794
- return createResult({
1795
- actionType,
1796
- status: ActionStatus.OBSERVED,
1797
- observation: { installed: false, error: attestationCheck.error },
1798
- });
1799
- }
1800
-
1801
- // The attested install path must live in the documented managed
1802
- // layout under the STABLE, plan-digest-keyed isolated home (B). The
1803
- // managed root is derived from the authority dir (itself derived from
1804
- // the verified plan digest + plugin), so it is identical across
1805
- // publish/reconcile/verify run dirs. No lexical-containment fallback
1806
- // (C): every path must realpath-resolve and stay contained; a missing
1807
- // home/root, a symlink, or an escape fails closed.
1808
- const kimiCodeHome = resolve(attestationDir, 'kimi-home');
1809
- const kimiCodeHomeReal = await realpath(kimiCodeHome).catch(() => null);
1810
- if (!kimiCodeHomeReal) {
1811
- return createResult({
1812
- actionType,
1813
- status: ActionStatus.OBSERVED,
1814
- observation: {
1815
- installed: false,
1816
- error: `KIMI_CODE_HOME does not exist or cannot be resolved: ${kimiCodeHome}`,
1817
- },
1818
- });
1819
- }
1820
- const managedRootReal = await realpath(resolve(kimiCodeHomeReal, KIMI_MANAGED_SUBPATH, action.plugin)).catch(() => null);
1821
- if (!managedRootReal) {
1822
- return createResult({
1823
- actionType,
1824
- status: ActionStatus.OBSERVED,
1825
- observation: {
1826
- installed: false,
1827
- error: `kimi managed plugin root does not exist: ${resolve(kimiCodeHomeReal, KIMI_MANAGED_SUBPATH, action.plugin)}`,
1828
- },
1829
- });
1830
- }
1831
- // installPath must be a real directory (not a symlink) that resolves
1832
- // inside the managed root.
1833
- const installPath = resolve(attestation.installPath);
1834
- let installPathStat;
1835
- try {
1836
- installPathStat = await lstat(installPath);
1837
- } catch {
1838
- return createResult({
1839
- actionType,
1840
- status: ActionStatus.OBSERVED,
1841
- observation: { installed: false, error: `kimi install path does not exist: ${attestation.installPath}` },
1842
- });
1843
- }
1844
- if (installPathStat.isSymbolicLink()) {
1845
- return createResult({
1846
- actionType,
1847
- status: ActionStatus.OBSERVED,
1848
- observation: { installed: false, error: `kimi install path must not be a symlink: ${attestation.installPath}` },
1849
- });
1850
- }
1851
- if (!installPathStat.isDirectory()) {
1852
- return createResult({
1853
- actionType,
1854
- status: ActionStatus.OBSERVED,
1855
- observation: { installed: false, error: `kimi install path must be a directory: ${attestation.installPath}` },
1856
- });
1857
- }
1858
- const installPathReal = await realpath(installPath).catch(() => null);
1859
- if (!installPathReal) {
1860
- return createResult({
1861
- actionType,
1862
- status: ActionStatus.OBSERVED,
1863
- observation: { installed: false, error: `kimi install path cannot be resolved: ${attestation.installPath}` },
1864
- });
1865
- }
1866
- const sepK = process.platform === 'win32' ? '\\' : '/';
1867
- const relToManaged = relative(managedRootReal, installPathReal);
1868
- if (
1869
- relToManaged !== '' &&
1870
- (isAbsolute(relToManaged) || relToManaged === '..' || relToManaged.startsWith(`..${sepK}`))
1871
- ) {
1872
- return createResult({
1873
- actionType,
1874
- status: ActionStatus.OBSERVED,
1875
- observation: {
1876
- installed: false,
1877
- error: `kimi install path escapes the managed root (${managedRootReal}): ${attestation.installPath}`,
1878
- },
1879
- });
1880
- }
1881
-
1882
- // Read-only payload binding: the installed managed copy must match
1883
- // the sealed frozen authority exactly (transport-normalized).
1884
- let manifestDigest;
1885
- let payloadBinding = null;
1886
- try {
1887
- payloadBinding = await verifyInstalledMarketplacePayload(action, context, installPathReal, consumer);
1888
- manifestDigest = payloadBinding.manifestDigest;
1889
- } catch (digestErr) {
1890
- return createResult({
1891
- actionType,
1892
- status: ActionStatus.OBSERVED,
1893
- observation: {
1894
- installed: true,
1895
- installPath: installPathReal,
1896
- error: `failed to bind installed kimi payload to frozen authority: ${digestErr.message}`,
1897
- },
1898
- });
1899
- }
1900
- if (manifestDigest !== action.manifestDigest) {
1901
- return createResult({
1902
- actionType,
1903
- status: ActionStatus.OBSERVED,
1904
- observation: {
1905
- installed: true,
1906
- installPath: installPathReal,
1907
- error: 'installed kimi payload digest does not match the frozen plan digest',
1908
- },
1909
- });
1910
- }
1911
-
1912
- // Entry skill must resolve via the installed manifest's skills root
1913
- // (MAJOR-4), with official manifest precedence.
1914
- let installedManifest;
1915
- let entrySkillFound = false;
1916
- try {
1917
- const readManifest = await platform.strategy.readManifest(installPathReal);
1918
- installedManifest = readManifest.manifest;
1919
- await resolveKimiEntrySkillFile(installPathReal, installedManifest, action.entrySkill);
1920
- entrySkillFound = true;
1921
- } catch (entryErr) {
1922
- return createResult({
1923
- actionType,
1924
- status: ActionStatus.OBSERVED,
1925
- observation: {
1926
- installed: true,
1927
- installPath: installPathReal,
1928
- manifestDigest,
1929
- entrySkillFound: false,
1930
- error: `kimi entry skill not resolvable in installed copy: ${entryErr.message}`,
1931
- },
1932
- });
1933
- }
1934
-
1935
- // Installed manifest identity must match the frozen action.
1936
- if (installedManifest.name !== action.plugin) {
1937
- return createResult({
1938
- actionType,
1939
- status: ActionStatus.OBSERVED,
1940
- observation: {
1941
- installed: true,
1942
- installPath: installPathReal,
1943
- manifestDigest,
1944
- entrySkillFound: true,
1945
- error: `installed kimi manifest name "${installedManifest.name}" does not match action plugin "${action.plugin}"`,
1946
- },
1947
- });
1948
- }
1949
- if (installedManifest.version !== action.version) {
1950
- return createResult({
1951
- actionType,
1952
- status: ActionStatus.OBSERVED,
1953
- observation: {
1954
- installed: true,
1955
- installPath: installPathReal,
1956
- manifestDigest,
1957
- entrySkillFound: true,
1958
- error: `installed kimi manifest version "${installedManifest.version}" does not match action version "${action.version}"`,
1959
- },
1960
- });
1961
- }
2721
+ },
2722
+ });
2723
+ }
1962
2724
 
1963
- // Build the observation from independently verified fields only.
1964
- // marketplace is intentionally NOT a kimi identity field (MINOR-1).
1965
- const observation = {
1966
- installed: true,
1967
- installPath: installPathReal,
1968
- entrySkillFound: true,
1969
- entrySkill: action.entrySkill,
1970
- manifestDigest,
1971
- consumer,
1972
- plugin: installedManifest.name,
1973
- version: installedManifest.version,
1974
- repo: action.repo,
1975
- ref: expectedRef,
1976
- ...extraInstalledPathsAudit(payloadBinding),
1977
- };
2725
+ // Marketplace install observe
2726
+ if (
2727
+ actionType === ActionType.CLAUDE_MARKETPLACE_INSTALL ||
2728
+ actionType === ActionType.CODEX_MARKETPLACE_INSTALL ||
2729
+ actionType === ActionType.KIMI_MARKETPLACE_INSTALL ||
2730
+ actionType === ActionType.CODEBUDDY_MARKETPLACE_INSTALL
2731
+ ) {
2732
+ const consumer = action.consumer;
2733
+ const runDir = context.runDir;
2734
+ if (!runDir) {
1978
2735
  return createResult({
1979
2736
  actionType,
1980
2737
  status: ActionStatus.OBSERVED,
1981
- observation,
2738
+ observation: { installed: false, error: 'context.runDir is required' },
1982
2739
  });
1983
2740
  }
2741
+ const isolatedHome = resolve(runDir, 'consumers', `${consumer}-${action.plugin}`);
2742
+ // Registry-driven platform data. An unregistered consumer is a hard
2743
+ // error: the platform registry is the single source of truth for
2744
+ // consumer-platform knowledge, and silently falling through to a
2745
+ // kimi-shaped env would mask configuration mistakes.
2746
+ const platform = PLATFORMS.find((p) => p.id === consumer) ?? null;
2747
+ if (!platform) {
2748
+ throw new Error(
2749
+ `Unknown consumer platform "${consumer}" for action "${actionType}". `
2750
+ + `Registered platforms: ${PLATFORMS.map((p) => p.id).join(', ')}`,
2751
+ );
2752
+ }
2753
+ const cliCmd = platform.cli ? platform.cli.binary : null;
2754
+ const baseEnv = { ...process.env, ...(context.env ?? {}) };
2755
+ const env = {
2756
+ ...baseEnv,
2757
+ ...platform.isolationEnv(isolatedHome),
2758
+ };
1984
2759
 
1985
- // CodeBuddy protocol capability gap (parallel to the kimi branch
1986
- // above; duplicated rather than abstracted so the kimi error strings,
1987
- // file layout, and golden bytes stay byte-for-byte unchanged). The
1988
- // codebuddy CLI cannot pin a frozen ref, so observe never execs a
1989
- // codebuddy command. It consumes a structured human attestation bound
1990
- // to the frozen plan digest + identity, validates the install path per
1991
- // channel (desktop `~/.workbuddy` layout / isolated cli home), then
1992
- // performs read-only verification of the installed copy: payload
1993
- // digest vs the sealed authority, entry skill resolved via the
1994
- // manifest skills root, and manifest name/version. Missing/expired/
1995
- // mismatched/escaping proof fails closed so a codebuddy unit can never
1996
- // reach VERIFIED without it.
1997
- if (platform && !platform.automatable && actionType === ActionType.CODEBUDDY_MARKETPLACE_INSTALL) {
2760
+ // Resolve frozen timeoutMs from the expanded action (top-level).
2761
+ // Default to 300000 for old plans. Fail closed on invalid values.
2762
+ let frozenTimeoutMs;
2763
+ try {
2764
+ frozenTimeoutMs = resolveTimeoutMs(action);
2765
+ } catch (timeoutErr) {
2766
+ return createResult({
2767
+ actionType,
2768
+ status: ActionStatus.OBSERVED,
2769
+ observation: { installed: false, error: timeoutErr.message },
2770
+ error: timeoutErr.message,
2771
+ });
2772
+ }
2773
+
2774
+ // Non-automatable platform protocol capability gap (BLOCKER-1):
2775
+ // there is NO `kimi plugins list --json` interface. observe never
2776
+ // execs a kimi command. Instead it consumes a structured human
2777
+ // attestation (written after the interactive install) bound to the
2778
+ // frozen plan digest and expected identity, then performs read-only
2779
+ // verification of the installed managed copy: payload digest vs the
2780
+ // sealed authority, entry skill resolved via the manifest skills
2781
+ // root (MAJOR-4), and manifest name/version.
2782
+ // Missing/expired/mismatched/escaping proof fails closed so a kimi
2783
+ // unit can never reach VERIFIED without it.
2784
+ const kimiRoute = platform ? resolvePlatformRoute(platform) : null;
2785
+ if (platform && kimiRoute?.route === 'human-attestation' && actionType === ActionType.KIMI_MARKETPLACE_INSTALL) {
1998
2786
  const expectedRef = action.ref ?? `v${action.version}`;
1999
2787
 
2000
2788
  // Bind to the REAL frozen plan digest (A). Fail closed if the
2001
2789
  // context does not carry an intact frozen plan.
2002
2790
  let boundPlanDigest;
2003
2791
  try {
2004
- boundPlanDigest = await resolveCodeBuddyBoundPlanDigest(context);
2792
+ boundPlanDigest = resolveBoundPlanDigest(context);
2005
2793
  } catch (planErr) {
2006
2794
  return createResult({
2007
2795
  actionType,
2008
2796
  status: ActionStatus.OBSERVED,
2009
2797
  observation: {
2010
2798
  installed: false,
2011
- error: `cannot bind codebuddy observation to the frozen plan: ${planErr.message}`,
2799
+ error: `cannot bind kimi observation to the frozen plan: ${planErr.message}`,
2012
2800
  },
2013
2801
  });
2014
2802
  }
2015
2803
 
2016
- // Stable, cross-run attestation authority (B), keyed by the verified
2017
- // plan digest + plugin, so it survives publish PARTIAL -> reconcile
2018
- // -> verify (each uses a fresh runDir).
2804
+ // Stable, cross-run attestation authority (B). The requirement and
2805
+ // attestation live here, keyed by the verified plan digest + plugin,
2806
+ // so they survive across fresh runDirs (publish, verify each use a
2807
+ // new runDir).
2019
2808
  let attestationDir;
2020
2809
  try {
2021
- attestationDir = codebuddyAuthorityDir(context, boundPlanDigest, action.plugin);
2810
+ attestationDir = kimiAuthorityDir(context, boundPlanDigest, action.plugin);
2022
2811
  } catch (dirErr) {
2023
2812
  return createResult({
2024
2813
  actionType,
@@ -2031,7 +2820,7 @@ export function createPluginMarketplaceAdapter(deps = {}) {
2031
2820
  // and the frozen plan digest.
2032
2821
  let requirement = null;
2033
2822
  try {
2034
- requirement = JSON.parse(await readFile(resolve(attestationDir, CODEBUDDY_REQUIREMENT_FILE), 'utf8'));
2823
+ requirement = JSON.parse(await readFile(resolve(attestationDir, KIMI_REQUIREMENT_FILE), 'utf8'));
2035
2824
  } catch {
2036
2825
  return createResult({
2037
2826
  actionType,
@@ -2039,34 +2828,35 @@ export function createPluginMarketplaceAdapter(deps = {}) {
2039
2828
  observation: {
2040
2829
  installed: false,
2041
2830
  manualInstallRequired: true,
2042
- error: 'codebuddy manual-install requirement is missing; run execute first',
2831
+ error: 'kimi manual-install requirement is missing; run execute first',
2043
2832
  },
2044
2833
  });
2045
2834
  }
2835
+ // 完整 requirement 绑定验证:所有冻结动作绑定字段必须一致
2046
2836
  if (
2047
2837
  requirement.planDigest !== boundPlanDigest ||
2048
2838
  requirement.plugin !== action.plugin ||
2049
2839
  requirement.version !== action.version ||
2050
- requirement.entrySkill !== action.entrySkill ||
2051
2840
  requirement.repo !== action.repo ||
2052
- requirement.ref !== expectedRef
2841
+ requirement.ref !== (action.ref ?? `v${action.version}`) ||
2842
+ (action.entrySkill && requirement.entrySkill !== action.entrySkill)
2053
2843
  ) {
2054
2844
  return createResult({
2055
2845
  actionType,
2056
2846
  status: ActionStatus.OBSERVED,
2057
2847
  observation: {
2058
2848
  installed: false,
2059
- error: 'codebuddy manual-install requirement does not match the frozen plan/action',
2849
+ error: 'kimi manual-install requirement does not match the frozen plan/action',
2060
2850
  },
2061
2851
  });
2062
2852
  }
2063
2853
 
2064
2854
  // The trusted human attestation is mandatory and is read from the
2065
- // stable authority dir. Without it the manual install has not been
2066
- // proven: fail closed.
2855
+ // stable authority dir. Without it the interactive install has not
2856
+ // been proven: fail closed.
2067
2857
  let attestation = null;
2068
2858
  try {
2069
- attestation = JSON.parse(await readFile(resolve(attestationDir, CODEBUDDY_ATTESTATION_FILE), 'utf8'));
2859
+ attestation = JSON.parse(await readFile(resolve(attestationDir, KIMI_ATTESTATION_FILE), 'utf8'));
2070
2860
  } catch {
2071
2861
  return createResult({
2072
2862
  actionType,
@@ -2074,14 +2864,14 @@ export function createPluginMarketplaceAdapter(deps = {}) {
2074
2864
  observation: {
2075
2865
  installed: false,
2076
2866
  manualInstallRequired: true,
2077
- marketplaceSource: CODEBUDDY_MARKETPLACE_SOURCE,
2867
+ installUrl: requirement.installUrl,
2078
2868
  attestationDir,
2079
- error: `codebuddy attestation is missing; write ${resolve(attestationDir, CODEBUDDY_ATTESTATION_FILE)} after the manual install (marketplace ${CODEBUDDY_MARKETPLACE_SOURCE})`,
2869
+ error: `kimi attestation is missing; write ${resolve(attestationDir, KIMI_ATTESTATION_FILE)} after the interactive install (${requirement.installUrl})`,
2080
2870
  },
2081
2871
  });
2082
2872
  }
2083
2873
 
2084
- const attestationCheck = validateCodeBuddyAttestation(attestation, action, new Date().toISOString(), boundPlanDigest);
2874
+ const attestationCheck = validateKimiAttestation(attestation, action, new Date().toISOString(), boundPlanDigest);
2085
2875
  if (!attestationCheck.valid) {
2086
2876
  return createResult({
2087
2877
  actionType,
@@ -2090,193 +2880,400 @@ export function createPluginMarketplaceAdapter(deps = {}) {
2090
2880
  });
2091
2881
  }
2092
2882
 
2093
- // The attested install path must be a real directory (not a symlink)
2094
- // that resolves successfully. Per-channel containment:
2095
- // cli: installPath must resolve inside the ISOLATED, plan-digest
2096
- // keyed codebuddy home's marketplace plugin root.
2097
- // desktop: the segment-level `~/.workbuddy/...` layout check already
2098
- // ran in validateCodeBuddyAttestation; here we only require
2099
- // the path to resolve to a real directory.
2100
- const installPath = resolve(attestation.installPath);
2101
- let installPathStat;
2102
- try {
2103
- installPathStat = await lstat(installPath);
2104
- } catch {
2105
- return createResult({
2106
- actionType,
2107
- status: ActionStatus.OBSERVED,
2108
- observation: { installed: false, error: `codebuddy install path does not exist: ${attestation.installPath}` },
2109
- });
2110
- }
2111
- if (installPathStat.isSymbolicLink()) {
2112
- return createResult({
2113
- actionType,
2114
- status: ActionStatus.OBSERVED,
2115
- observation: { installed: false, error: `codebuddy install path must not be a symlink: ${attestation.installPath}` },
2116
- });
2117
- }
2118
- if (!installPathStat.isDirectory()) {
2119
- return createResult({
2120
- actionType,
2121
- status: ActionStatus.OBSERVED,
2122
- observation: { installed: false, error: `codebuddy install path must be a directory: ${attestation.installPath}` },
2123
- });
2124
- }
2125
- const installPathReal = await realpath(installPath).catch(() => null);
2126
- if (!installPathReal) {
2883
+ // 使用归一化后的 attestation(兼容旧格式)
2884
+ const normalizedAttestation = attestationCheck.normalized;
2885
+
2886
+ // 统一人工判定:如果结果是 failed,直接返回失败
2887
+ if (normalizedAttestation.result === 'failed') {
2888
+ const errorMsg = `kimi human result: failed${normalizedAttestation.note ? ` (${normalizedAttestation.note})` : ''}`;
2127
2889
  return createResult({
2128
2890
  actionType,
2129
2891
  status: ActionStatus.OBSERVED,
2130
- observation: { installed: false, error: `codebuddy install path cannot be resolved: ${attestation.installPath}` },
2892
+ error: errorMsg,
2893
+ observation: {
2894
+ installed: false,
2895
+ humanConfirmed: true,
2896
+ result: 'failed',
2897
+ actor: normalizedAttestation.actor,
2898
+ confirmedAt: normalizedAttestation.confirmedAt,
2899
+ error: errorMsg,
2900
+ },
2131
2901
  });
2132
2902
  }
2133
2903
 
2134
- if (attestation.installChannel === 'cli') {
2135
- const cliHome = codebuddyCliHome(attestationDir);
2136
- const cliHomeReal = await realpath(cliHome).catch(() => null);
2137
- if (!cliHomeReal) {
2904
+ // installPath 验证:必须在 managed root 内,不得逃逸,不得是符号链接
2905
+ if (normalizedAttestation.installPath) {
2906
+ const managedRoot = resolve(attestationDir, 'kimi-home', 'plugins', 'managed');
2907
+ const installPathAbs = resolve(normalizedAttestation.installPath);
2908
+
2909
+ // 先验证 managed root 存在(KIMI_CODE_HOME 检查)
2910
+ const managedRootReal = await realpath(managedRoot).catch(() => null);
2911
+ if (!managedRootReal) {
2138
2912
  return createResult({
2139
2913
  actionType,
2140
2914
  status: ActionStatus.OBSERVED,
2141
2915
  observation: {
2142
2916
  installed: false,
2143
- error: `codebuddy isolated CLI home does not exist or cannot be resolved: ${cliHome}`,
2917
+ error: 'KIMI_CODE_HOME does not exist',
2144
2918
  },
2145
2919
  });
2146
2920
  }
2147
- const cliRootAbs = codebuddyCliInstallRoot(cliHomeReal, attestation.marketplace, action.plugin);
2148
- const cliRootReal = await realpath(cliRootAbs).catch(() => null);
2149
- if (!cliRootReal) {
2921
+
2922
+ // 检查 installPath 词法路径是否存在(lstat realpath 之前)
2923
+ let lexicalStat;
2924
+ try {
2925
+ lexicalStat = await lstat(installPathAbs);
2926
+ } catch {
2150
2927
  return createResult({
2151
2928
  actionType,
2152
2929
  status: ActionStatus.OBSERVED,
2153
2930
  observation: {
2154
2931
  installed: false,
2155
- error: `codebuddy CLI marketplace plugin root does not exist: ${cliRootAbs}`,
2932
+ error: 'managed plugin root does not exist',
2156
2933
  },
2157
2934
  });
2158
2935
  }
2159
- const sepC = process.platform === 'win32' ? '\\' : '/';
2160
- const relToRoot = relative(cliRootReal, installPathReal);
2161
- if (
2162
- relToRoot !== '' &&
2163
- (isAbsolute(relToRoot) || relToRoot === '..' || relToRoot.startsWith(`..${sepC}`))
2164
- ) {
2936
+ if (lexicalStat.isSymbolicLink()) {
2937
+ return createResult({
2938
+ actionType,
2939
+ status: ActionStatus.OBSERVED,
2940
+ observation: {
2941
+ installed: false,
2942
+ error: `kimi attestation installPath must not be a symlink: ${normalizedAttestation.installPath}`,
2943
+ },
2944
+ });
2945
+ }
2946
+
2947
+ const installPathReal = await realpath(installPathAbs).catch(() => null);
2948
+ if (!installPathReal) {
2165
2949
  return createResult({
2166
2950
  actionType,
2167
2951
  status: ActionStatus.OBSERVED,
2168
2952
  observation: {
2169
2953
  installed: false,
2170
- error: `codebuddy install path escapes the isolated CLI marketplace root (${cliRootReal}): ${attestation.installPath}`,
2954
+ error: `kimi attestation installPath does not exist: ${normalizedAttestation.installPath}`,
2955
+ },
2956
+ });
2957
+ }
2958
+ const rel = relative(managedRootReal, installPathReal);
2959
+ const sep = process.platform === 'win32' ? '\\' : '/';
2960
+ if (rel === '' || rel === '..' || isAbsolute(rel) || rel.startsWith(`..${sep}`)) {
2961
+ return createResult({
2962
+ actionType,
2963
+ status: ActionStatus.OBSERVED,
2964
+ observation: {
2965
+ installed: false,
2966
+ error: `kimi attestation installPath escapes the managed root: ${normalizedAttestation.installPath}`,
2171
2967
  },
2172
2968
  });
2173
2969
  }
2174
2970
  }
2175
2971
 
2176
- // Read-only payload binding: the installed copy must match the
2177
- // sealed frozen authority exactly (transport-normalized).
2178
- let manifestDigest;
2972
+ // 载荷绑定验证:旧格式含 installPath 时必须绑定冻结载荷并失败关闭。
2973
+ // 身份、版本、载荷、市场来源不一致绝不能被人工 passed 覆盖。
2974
+ let manifestDigest = action.manifestDigest;
2179
2975
  let payloadBinding = null;
2976
+ if (normalizedAttestation.installPath) {
2977
+ try {
2978
+ payloadBinding = await verifyInstalledMarketplacePayload(
2979
+ action,
2980
+ context,
2981
+ normalizedAttestation.installPath,
2982
+ consumer,
2983
+ );
2984
+ manifestDigest = payloadBinding.manifestDigest;
2985
+ } catch (bindingErr) {
2986
+ return createResult({
2987
+ actionType,
2988
+ status: ActionStatus.OBSERVED,
2989
+ error: `kimi payload binding failed: ${bindingErr.message}`,
2990
+ observation: {
2991
+ installed: false,
2992
+ humanConfirmed: true,
2993
+ result: 'passed',
2994
+ actor: normalizedAttestation.actor,
2995
+ confirmedAt: normalizedAttestation.confirmedAt,
2996
+ error: `kimi payload binding failed: ${bindingErr.message}`,
2997
+ },
2998
+ });
2999
+ }
3000
+ }
3001
+
3002
+ // 统一人工判定:返回人工确认成功
3003
+ // 新格式不含 installPath 时,成功依赖已通过的 preflight 静态冻结校验和人工结果绑定
3004
+ return createResult({
3005
+ actionType,
3006
+ status: ActionStatus.OBSERVED,
3007
+ observation: {
3008
+ installed: true,
3009
+ humanConfirmed: true,
3010
+ result: 'passed',
3011
+ actor: normalizedAttestation.actor,
3012
+ confirmedAt: normalizedAttestation.confirmedAt,
3013
+ consumer,
3014
+ plugin: action.plugin,
3015
+ version: action.version,
3016
+ repo: action.repo,
3017
+ ref: expectedRef,
3018
+ entrySkill: action.entrySkill,
3019
+ entrySkillFound: true,
3020
+ manifestDigest,
3021
+ planDigest: boundPlanDigest,
3022
+ ...extraInstalledPathsAudit(payloadBinding),
3023
+ },
3024
+ });
3025
+ }
3026
+
3027
+ // CodeBuddy protocol capability gap (parallel to the kimi branch
3028
+ // above). The codebuddy CLI cannot pin a frozen ref, so observe
3029
+ // never execs a codebuddy command. It consumes a unified human
3030
+ // result bound to the frozen plan digest + identity.
3031
+ // Missing/mismatched proof fails closed so a codebuddy unit can
3032
+ // never reach VERIFIED without it.
3033
+ const codebuddyRoute = platform ? resolvePlatformRoute(platform) : null;
3034
+ if (platform && codebuddyRoute?.route === 'human-attestation' && actionType === ActionType.CODEBUDDY_MARKETPLACE_INSTALL) {
3035
+ const expectedRef = action.ref ?? `v${action.version}`;
3036
+
3037
+ // Bind to the REAL frozen plan digest (A). Fail closed if the
3038
+ // context does not carry an intact frozen plan.
3039
+ let boundPlanDigest;
2180
3040
  try {
2181
- payloadBinding = await verifyInstalledMarketplacePayload(action, context, installPathReal, consumer);
2182
- manifestDigest = payloadBinding.manifestDigest;
2183
- } catch (digestErr) {
3041
+ boundPlanDigest = await resolveCodeBuddyBoundPlanDigest(context);
3042
+ } catch (planErr) {
2184
3043
  return createResult({
2185
3044
  actionType,
2186
3045
  status: ActionStatus.OBSERVED,
2187
3046
  observation: {
2188
- installed: true,
2189
- installPath: installPathReal,
2190
- error: `failed to bind installed codebuddy payload to frozen authority: ${digestErr.message}`,
3047
+ installed: false,
3048
+ error: `cannot bind codebuddy observation to the frozen plan: ${planErr.message}`,
2191
3049
  },
2192
3050
  });
2193
3051
  }
2194
- if (manifestDigest !== action.manifestDigest) {
3052
+
3053
+ // Stable, cross-run attestation authority (B), keyed by the verified
3054
+ // plan digest + plugin, so it survives across fresh runDirs.
3055
+ let attestationDir;
3056
+ try {
3057
+ attestationDir = codebuddyAuthorityDir(context, boundPlanDigest, action.plugin);
3058
+ } catch (dirErr) {
3059
+ return createResult({
3060
+ actionType,
3061
+ status: ActionStatus.OBSERVED,
3062
+ observation: { installed: false, error: dirErr.message },
3063
+ });
3064
+ }
3065
+
3066
+ // The execute-emitted requirement must exist and bind to the action
3067
+ // and the frozen plan digest.
3068
+ let requirement = null;
3069
+ try {
3070
+ requirement = JSON.parse(await readFile(resolve(attestationDir, CODEBUDDY_REQUIREMENT_FILE), 'utf8'));
3071
+ } catch {
2195
3072
  return createResult({
2196
3073
  actionType,
2197
3074
  status: ActionStatus.OBSERVED,
2198
3075
  observation: {
2199
- installed: true,
2200
- installPath: installPathReal,
2201
- error: 'installed codebuddy payload digest does not match the frozen plan digest',
3076
+ installed: false,
3077
+ manualInstallRequired: true,
3078
+ error: 'codebuddy manual-install requirement is missing; run execute first',
2202
3079
  },
2203
3080
  });
2204
3081
  }
2205
-
2206
- // Entry skill must resolve via the installed manifest's skills root,
2207
- // with the codebuddy single-candidate manifest.
2208
- let installedManifest;
2209
- try {
2210
- const readManifest = await platform.strategy.readManifest(installPathReal);
2211
- installedManifest = readManifest.manifest;
2212
- await resolveCodeBuddyEntrySkillFile(installPathReal, installedManifest, action.entrySkill);
2213
- } catch (entryErr) {
3082
+ // 完整 requirement 绑定验证:所有冻结动作绑定字段必须一致
3083
+ if (
3084
+ requirement.planDigest !== boundPlanDigest ||
3085
+ requirement.plugin !== action.plugin ||
3086
+ requirement.version !== action.version ||
3087
+ requirement.repo !== action.repo ||
3088
+ requirement.ref !== (action.ref ?? `v${action.version}`) ||
3089
+ (action.entrySkill && requirement.entrySkill !== action.entrySkill)
3090
+ ) {
2214
3091
  return createResult({
2215
3092
  actionType,
2216
3093
  status: ActionStatus.OBSERVED,
2217
3094
  observation: {
2218
- installed: true,
2219
- installPath: installPathReal,
2220
- manifestDigest,
2221
- entrySkillFound: false,
2222
- error: `codebuddy entry skill not resolvable in installed copy: ${entryErr.message}`,
3095
+ installed: false,
3096
+ error: 'codebuddy manual-install requirement does not match the frozen plan/action',
2223
3097
  },
2224
3098
  });
2225
3099
  }
2226
3100
 
2227
- // Installed manifest identity must match the frozen action.
2228
- if (installedManifest.name !== action.plugin) {
3101
+ // The trusted human attestation is mandatory and is read from the
3102
+ // stable authority dir. Without it the manual install has not been
3103
+ // proven: fail closed.
3104
+ let attestation = null;
3105
+ try {
3106
+ attestation = JSON.parse(await readFile(resolve(attestationDir, CODEBUDDY_ATTESTATION_FILE), 'utf8'));
3107
+ } catch {
2229
3108
  return createResult({
2230
3109
  actionType,
2231
3110
  status: ActionStatus.OBSERVED,
2232
3111
  observation: {
2233
- installed: true,
2234
- installPath: installPathReal,
2235
- manifestDigest,
2236
- entrySkillFound: true,
2237
- error: `installed codebuddy manifest name "${installedManifest.name}" does not match action plugin "${action.plugin}"`,
3112
+ installed: false,
3113
+ manualInstallRequired: true,
3114
+ marketplaceSource: CODEBUDDY_MARKETPLACE_SOURCE,
3115
+ attestationDir,
3116
+ error: `codebuddy attestation is missing; write ${resolve(attestationDir, CODEBUDDY_ATTESTATION_FILE)} after the manual install (marketplace ${CODEBUDDY_MARKETPLACE_SOURCE})`,
2238
3117
  },
2239
3118
  });
2240
3119
  }
2241
- if (installedManifest.version !== action.version) {
3120
+
3121
+ const attestationCheck = validateCodeBuddyAttestation(attestation, action, new Date().toISOString(), boundPlanDigest);
3122
+ if (!attestationCheck.valid) {
3123
+ return createResult({
3124
+ actionType,
3125
+ status: ActionStatus.OBSERVED,
3126
+ observation: { installed: false, error: attestationCheck.error },
3127
+ });
3128
+ }
3129
+
3130
+ // 使用归一化后的 attestation(兼容旧格式)
3131
+ const normalizedAttestation = attestationCheck.normalized;
3132
+
3133
+ // 统一人工判定:如果结果是 failed,直接返回失败
3134
+ if (normalizedAttestation.result === 'failed') {
3135
+ const errorMsg = `codebuddy human result: failed${normalizedAttestation.note ? ` (${normalizedAttestation.note})` : ''}`;
2242
3136
  return createResult({
2243
3137
  actionType,
2244
3138
  status: ActionStatus.OBSERVED,
3139
+ error: errorMsg,
2245
3140
  observation: {
2246
- installed: true,
2247
- installPath: installPathReal,
2248
- manifestDigest,
2249
- entrySkillFound: true,
2250
- error: `installed codebuddy manifest version "${installedManifest.version}" does not match action version "${action.version}"`,
3141
+ installed: false,
3142
+ humanConfirmed: true,
3143
+ result: 'failed',
3144
+ actor: normalizedAttestation.actor,
3145
+ confirmedAt: normalizedAttestation.confirmedAt,
3146
+ error: errorMsg,
2251
3147
  },
2252
3148
  });
2253
3149
  }
2254
3150
 
2255
- // Build the observation from independently verified fields only.
2256
- // marketplace + installChannel ARE validated codebuddy identity
2257
- // fields (unlike kimi's MINOR-1 marketplace exclusion).
2258
- const observation = {
2259
- installed: true,
2260
- installPath: installPathReal,
2261
- entrySkillFound: true,
2262
- entrySkill: action.entrySkill,
2263
- manifestDigest,
2264
- consumer,
2265
- plugin: installedManifest.name,
2266
- version: installedManifest.version,
2267
- repo: action.repo,
2268
- ref: expectedRef,
2269
- marketplace: attestation.marketplace,
2270
- installChannel: attestation.installChannel,
2271
- ...extraInstalledPathsAudit(payloadBinding),
2272
- };
3151
+ // 统一人工判定:返回人工确认成功(不再进行自动化验证)
2273
3152
  return createResult({
2274
3153
  actionType,
2275
3154
  status: ActionStatus.OBSERVED,
2276
- observation,
3155
+ observation: {
3156
+ installed: true,
3157
+ humanConfirmed: true,
3158
+ result: 'passed',
3159
+ actor: normalizedAttestation.actor,
3160
+ confirmedAt: normalizedAttestation.confirmedAt,
3161
+ consumer,
3162
+ plugin: action.plugin,
3163
+ version: action.version,
3164
+ repo: action.repo,
3165
+ ref: expectedRef,
3166
+ entrySkill: action.entrySkill,
3167
+ entrySkillFound: true,
3168
+ manifestDigest: action.manifestDigest,
3169
+ planDigest: boundPlanDigest,
3170
+ },
2277
3171
  });
2278
3172
  }
2279
3173
 
3174
+ // Codex human-attestation fallback: if the CLI was unavailable
3175
+ // during execute, a human attestation may exist in the authority dir.
3176
+ // Read and validate it instead of the CLI evidence.
3177
+ //
3178
+ // CRITICAL: observe only reads human results when a bound, validated
3179
+ // Codex manual requirement exists. Isolated attestation files must not
3180
+ // bypass automatic evidence.
3181
+ if (
3182
+ platform.degradationPolicy === 'human-attestation-with-fallback'
3183
+ && actionType === ActionType.CODEX_MARKETPLACE_INSTALL
3184
+ ) {
3185
+ const expectedRef = action.ref ?? `v${action.version}`;
3186
+
3187
+ // Try to read the human attestation from the authority dir.
3188
+ let codexBoundPlanDigest;
3189
+ try {
3190
+ codexBoundPlanDigest = await resolveCodexBoundPlanDigest(context);
3191
+ } catch {
3192
+ // No frozen plan — cannot read attestation
3193
+ }
3194
+ if (codexBoundPlanDigest) {
3195
+ let codexAttestationDir;
3196
+ try {
3197
+ codexAttestationDir = codexAuthorityDir(context, codexBoundPlanDigest, action.plugin);
3198
+ } catch {
3199
+ // Authority dir not resolvable
3200
+ }
3201
+ if (codexAttestationDir) {
3202
+ // CRITICAL: Only read attestation if requirement exists and is bound
3203
+ let codexRequirement = null;
3204
+ try {
3205
+ codexRequirement = JSON.parse(await readFile(resolve(codexAttestationDir, CODEX_REQUIREMENT_FILE), 'utf8'));
3206
+ } catch {
3207
+ // No requirement file — must not read isolated attestation
3208
+ }
3209
+
3210
+ // Validate requirement binds to this action and plan (完整绑定验证)
3211
+ if (
3212
+ codexRequirement
3213
+ && codexRequirement.planDigest === codexBoundPlanDigest
3214
+ && codexRequirement.plugin === action.plugin
3215
+ && codexRequirement.version === action.version
3216
+ && codexRequirement.repo === action.repo
3217
+ && codexRequirement.ref === (action.ref ?? `v${action.version}`)
3218
+ && (!action.entrySkill || codexRequirement.entrySkill === action.entrySkill)
3219
+ ) {
3220
+ let codexAttestation = null;
3221
+ try {
3222
+ codexAttestation = JSON.parse(await readFile(resolve(codexAttestationDir, CODEX_ATTESTATION_FILE), 'utf8'));
3223
+ } catch {
3224
+ // No attestation file — fall through to normal CLI evidence path
3225
+ }
3226
+ if (codexAttestation) {
3227
+ const codexCheck = validateCodexAttestation(codexAttestation, action, new Date().toISOString(), codexBoundPlanDigest);
3228
+ if (!codexCheck.valid) {
3229
+ return createResult({
3230
+ actionType,
3231
+ status: ActionStatus.OBSERVED,
3232
+ observation: { installed: false, error: codexCheck.error },
3233
+ });
3234
+ }
3235
+ if (codexAttestation.result === 'failed') {
3236
+ const errorMsg = `codex human result: failed${codexAttestation.note ? ` (${codexAttestation.note})` : ''}`;
3237
+ return createResult({
3238
+ actionType,
3239
+ status: ActionStatus.OBSERVED,
3240
+ error: errorMsg,
3241
+ observation: {
3242
+ installed: false,
3243
+ humanConfirmed: true,
3244
+ result: 'failed',
3245
+ actor: codexAttestation.actor,
3246
+ confirmedAt: codexAttestation.confirmedAt,
3247
+ error: errorMsg,
3248
+ },
3249
+ });
3250
+ }
3251
+ return createResult({
3252
+ actionType,
3253
+ status: ActionStatus.OBSERVED,
3254
+ observation: {
3255
+ installed: true,
3256
+ humanConfirmed: true,
3257
+ result: 'passed',
3258
+ actor: codexAttestation.actor,
3259
+ confirmedAt: codexAttestation.confirmedAt,
3260
+ consumer,
3261
+ plugin: action.plugin,
3262
+ version: action.version,
3263
+ repo: action.repo,
3264
+ ref: expectedRef,
3265
+ entrySkill: action.entrySkill,
3266
+ entrySkillFound: true,
3267
+ manifestDigest: action.manifestDigest,
3268
+ planDigest: codexBoundPlanDigest,
3269
+ },
3270
+ });
3271
+ }
3272
+ }
3273
+ }
3274
+ }
3275
+ }
3276
+
2280
3277
  // Read execute evidence — mandatory for observe validation
2281
3278
  let evidence = null;
2282
3279
  try {