release-skill 0.2.3 → 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 (53) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.codebuddy-plugin/plugin.json +1 -1
  4. package/.codex-plugin/plugin.json +2 -2
  5. package/.kimi-plugin/plugin.json +1 -1
  6. package/CHANGELOG.md +22 -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 +264 -913
  11. package/README.zh-CN.md +222 -535
  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 +19054 -17573
  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 +19054 -17573
  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 +19054 -17573
  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 +19054 -17573
  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 +19054 -17573
  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 +1190 -435
  39. package/src/commands/prepare.mjs +380 -40
  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 +304 -20
  44. package/src/core/baseline.mjs +8 -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 +129 -6
  50. package/src/platforms/codebuddy.mjs +193 -280
  51. package/src/platforms/codex.mjs +369 -0
  52. package/src/platforms/kimi.mjs +164 -119
  53. package/src/platforms/registry.mjs +24 -6
@@ -15,6 +15,11 @@
15
15
  * the installed managed copy. Missing/expired/mismatched/escaping proof
16
16
  * fails closed, so a kimi unit can never reach VERIFIED without it.
17
17
  *
18
+ * 统一人工结果:收据仅需 platform, version, planDigest,
19
+ * result(passed|failed), actor, confirmedAt 和可选 note。
20
+ * 不再要求 consumer, plugin, conclusion, confirmedBy、隔离 HOME、
21
+ * 安装路径证明、载荷摘要手填或 24 小时过期。
22
+ *
18
23
  * This module is the kimi half of the platform registry's strategy table
19
24
  * (registry.mjs references these functions); the plugin-marketplace adapter
20
25
  * consumes the attestation path from here. The shared adapter primitives
@@ -29,7 +34,7 @@
29
34
  * @module platforms/kimi
30
35
  */
31
36
 
32
- import { readFile, mkdir } from 'node:fs/promises';
37
+ import { readFile } from 'node:fs/promises';
33
38
  import { join, resolve, relative, isAbsolute } from 'node:path';
34
39
 
35
40
  import {
@@ -47,10 +52,6 @@ import { canonicalJson } from '../core/digest.mjs';
47
52
  export const KIMI_REQUIREMENT_FILE = 'release-skill-kimi-manual-install.json';
48
53
  /** Structured human attestation consumed by kimi observe. */
49
54
  export const KIMI_ATTESTATION_FILE = 'release-skill-kimi-attestation.json';
50
- /** Kimi Code managed install layout: $KIMI_CODE_HOME/plugins/managed/<id>/. */
51
- export const KIMI_MANAGED_SUBPATH = join('plugins', 'managed');
52
- /** Maximum attestation validity window (mirrors the 24h approval expiry). */
53
- export const KIMI_MAX_ATTESTATION_VALIDITY_MS = 24 * 60 * 60 * 1000;
54
55
 
55
56
  /** 64-char lowercase hex plan/payload digest pattern. */
56
57
  export const HEX_DIGEST_RE = /^[a-f0-9]{64}$/;
@@ -184,21 +185,20 @@ function buildKimiInstallUrl(repo, ref) {
184
185
  }
185
186
 
186
187
  /**
187
- * Human-facing, actionable manual-install closed-loop instructions for Kimi Code.
188
+ * 统一人工安装说明:面向 Kimi Code 的人工结果流程。
188
189
  *
189
- * @param {{installUrl:string, plugin:string, version:string, ref:string, isolatedHome:string, attestationDir:string}} p
190
+ * @param {{installUrl:string, plugin:string, version:string, ref:string, attestationDir:string}} p
190
191
  * @returns {string[]}
191
192
  */
192
- function buildKimiManualInstructions({ installUrl, plugin, version, ref, isolatedHome, attestationDir }) {
193
+ function buildKimiManualInstructions({ installUrl, plugin, version, ref, attestationDir }) {
193
194
  return [
194
- `Kimi Code has no scriptable plugin-install CLI; installation is a manual, interactive step.`,
195
- `1) publish fails closed at this kimi checkpoint and leaves the run PARTIAL (the automated Git branch/tag, npm, and GitHub Release writes still complete first).`,
196
- `2) Launch Kimi Code with the ISOLATED home from this requirement so the managed copy lands inside it: set HOME="${isolatedHome}" and KIMI_CODE_HOME="${isolatedHome}". The plugin installs to "${isolatedHome}/plugins/managed/${plugin}/".`,
197
- `3) In that isolated Kimi Code session run: /plugins install ${installUrl} (pinned to frozen ref "${ref}", version ${version}; never install the bare repository URL). Confirm the trust prompt for plugin "${plugin}", then run /plugins reload (or /new).`,
198
- `4) Write the attestation JSON to: ${attestationDir}/${KIMI_ATTESTATION_FILE}. planDigest MUST be the frozen plan digest; payloadDigest MUST be the frozen snapshot payload digest; installPath MUST be the isolated managed directory above. attestedAt must not be in the future and expiresAt must be within 24 hours of attestedAt.`,
199
- ` Required fields: consumer="kimi", plugin, version, entrySkill, repo, ref, installPath, planDigest, payloadDigest, attestedBy, attestedAt, expiresAt.`,
200
- `5) Re-run release-skill reconcile (promotes PARTIAL -> PUBLISHED) and then verify (-> VERIFIED). Both read the attestation from this same plan-digest-keyed authority directory, so a fresh run directory does not lose the proof.`,
201
- `An install into the ordinary ~/.kimi-code is NOT acceptable proof: the attested installPath must resolve inside this requirement's isolated KIMI_CODE_HOME managed root, otherwise verification fails closed.`,
195
+ `Kimi Code 没有可脚本化的插件安装命令行工具;安装是手动交互步骤。`,
196
+ `1) publish 完成所有远端写入后进入 PUBLISHED 状态(自动化 Git 分支/标签、npm GitHub Release 写入已完成)。此 kimi 检查点标记为需要人工安装。`,
197
+ `2) Kimi Code 中运行: /plugins install ${installUrl}(锁定到冻结 ref "${ref}",版本 ${version})。确认插件 "${plugin}" 的信任提示,然后运行 /plugins reload(或 /new)。`,
198
+ `3) 将人工结果 JSON 写入: ${attestationDir}/${KIMI_ATTESTATION_FILE}`,
199
+ ` 必填字段: platform="kimi", version, planDigest(冻结计划摘要), result("passed" "failed"), actor(确认人), confirmedAt(ISO 8601 时间戳)`,
200
+ ` 可选字段: note(备注)`,
201
+ `4) 运行 release-skill reconcile(对账远端状态并跳过已完成步骤),然后 release-skill verify(从同一个计划摘要索引的权威目录读取结果,成功后 -> VERIFIED)。`,
202
202
  ];
203
203
  }
204
204
 
@@ -236,82 +236,144 @@ export async function readKimiManifest(pluginRootReal) {
236
236
  }
237
237
 
238
238
  /**
239
- * Validate a structured kimi manual-install attestation against the frozen
240
- * action and the verified frozen plan digest.
239
+ * 统一人工结果验证:验证 kimi 人工结果是否匹配冻结计划。
240
+ *
241
+ * 统一后的结果只需:
242
+ * - 必填:platform, version, planDigest, result(passed|failed), actor, confirmedAt
243
+ * - 可选:note
244
+ *
245
+ * 旧格式兼容(0.2.3 及更早):
246
+ * - consumer → platform (必须是 'kimi')
247
+ * - attestedBy → actor
248
+ * - attestedAt → confirmedAt
249
+ * - payloadDigest → 载荷绑定验证(如果存在)
241
250
  *
242
- * Bindings (fail closed on any mismatch):
243
- * - `planDigest` binds to the REAL frozen plan digest (`boundPlanDigest`, from
244
- * `context.plan.digest`) NOT to `action.manifestDigest`.
245
- * - `payloadDigest` binds separately to `action.manifestDigest` (the sealed
246
- * snapshot payload digest).
247
- * - plugin identity, version, entry skill, repo, and frozen ref must match.
248
- * - Time bounds: `attestedAt` must not be in the future, the validity window
249
- * (`expiresAt - attestedAt`) must not exceed 24h, and the attestation must
250
- * not be expired relative to `isoNow`.
251
+ * 绑定验证(任何不匹配都失败):
252
+ * - planDigest 绑定到真正的冻结计划摘要(boundPlanDigest
253
+ * - version 静态一致性检查
254
+ * - result 只接受 passed failed
251
255
  *
252
- * @param {object} attestation - parsed attestation JSON.
253
- * @param {object} action - the expanded kimi action (top-level fields).
254
- * @param {string} isoNow - current ISO timestamp.
255
- * @param {string} boundPlanDigest - verified frozen plan digest.
256
- * @returns {{valid:boolean, error:string|null}}
256
+ * @param {object} attestation - 解析后的人工结果 JSON
257
+ * @param {object} action - 展开的 kimi 动作(顶层字段)。
258
+ * @param {string} isoNow - 当前 ISO 时间戳(保留签名兼容,不再用于过期检查)。
259
+ * @param {string} boundPlanDigest - 验证过的冻结计划摘要。
260
+ * @returns {{valid:boolean, error:string|null, normalized:object|null}}
257
261
  */
258
262
  export function validateKimiAttestation(attestation, action, isoNow, boundPlanDigest) {
259
263
  if (!attestation || typeof attestation !== 'object' || Array.isArray(attestation)) {
260
- return { valid: false, error: 'kimi attestation is not an object' };
261
- }
262
- const requiredStrings = ['plugin', 'version', 'entrySkill', 'repo', 'ref', 'installPath', 'payloadDigest', 'planDigest', 'attestedBy', 'attestedAt', 'expiresAt'];
263
- for (const field of requiredStrings) {
264
- if (typeof attestation[field] !== 'string' || attestation[field].length === 0) {
265
- return { valid: false, error: `kimi attestation missing required field "${field}"` };
266
- }
264
+ return { valid: false, error: 'kimi attestation is not an object', normalized: null };
267
265
  }
268
- if (attestation.consumer !== 'kimi') {
269
- return { valid: false, error: `kimi attestation consumer "${attestation.consumer}" must be "kimi"` };
270
- }
271
- if (!HEX_DIGEST_RE.test(attestation.planDigest)) {
272
- return { valid: false, error: 'kimi attestation planDigest must be a 64-char lowercase hex digest' };
266
+
267
+ // 旧格式归一化:将旧字段映射到新字段
268
+ const normalized = { ...attestation };
269
+
270
+ // 旧格式识别:完整旧标识组(consumer, attestedBy, attestedAt)全部存在,
271
+ // 且新格式字段组(result, actor, confirmedAt)未混入。
272
+ // 不能通过随意添加一个旧标识字段绕过新格式 result 必填。
273
+ const hasCompleteOldMarkers = !!(normalized.consumer && normalized.attestedBy && normalized.attestedAt);
274
+ const hasNewFormatFields = !!(normalized.result || normalized.actor || normalized.confirmedAt);
275
+ const isOldFormat = hasCompleteOldMarkers && !hasNewFormatFields;
276
+
277
+ // 新格式严格要求 result 字段;仅当确认为旧格式时才允许缺省
278
+ if (!normalized.result && isOldFormat) {
279
+ normalized.result = 'passed';
273
280
  }
274
- if (attestation.planDigest !== boundPlanDigest) {
275
- return { valid: false, error: 'kimi attestation planDigest does not match the frozen plan digest' };
281
+
282
+ // consumer platform (旧格式使用 consumer)
283
+ if (!normalized.platform && normalized.consumer) {
284
+ normalized.platform = normalized.consumer;
276
285
  }
277
- if (attestation.plugin !== action.plugin) {
278
- return { valid: false, error: `kimi attestation plugin "${attestation.plugin}" does not match action plugin "${action.plugin}"` };
286
+ // attestedBy → actor (旧格式使用 attestedBy)
287
+ if (!normalized.actor && normalized.attestedBy) {
288
+ normalized.actor = normalized.attestedBy;
279
289
  }
280
- if (attestation.version !== action.version) {
281
- return { valid: false, error: `kimi attestation version "${attestation.version}" does not match action version "${action.version}"` };
290
+ // attestedAt → confirmedAt (旧格式使用 attestedAt)
291
+ if (!normalized.confirmedAt && normalized.attestedAt) {
292
+ normalized.confirmedAt = normalized.attestedAt;
282
293
  }
283
- if (attestation.entrySkill !== action.entrySkill) {
284
- return { valid: false, error: `kimi attestation entrySkill "${attestation.entrySkill}" does not match action entrySkill "${action.entrySkill}"` };
294
+
295
+ // 统一必填字段
296
+ const requiredStrings = ['platform', 'version', 'planDigest', 'result', 'actor', 'confirmedAt'];
297
+ for (const field of requiredStrings) {
298
+ if (typeof normalized[field] !== 'string' || normalized[field].length === 0) {
299
+ return { valid: false, error: `kimi attestation missing required field "${field}"`, normalized: null };
300
+ }
285
301
  }
286
- if (attestation.repo !== action.repo) {
287
- return { valid: false, error: `kimi attestation repo "${attestation.repo}" does not match action repo "${action.repo}"` };
302
+ if (normalized.platform !== 'kimi') {
303
+ return { valid: false, error: `kimi attestation platform "${normalized.platform}" must be "kimi"`, normalized: null };
288
304
  }
289
- const expectedRef = action.ref ?? `v${action.version}`;
290
- if (attestation.ref !== expectedRef) {
291
- return { valid: false, error: `kimi attestation ref "${attestation.ref}" does not match frozen ref "${expectedRef}"` };
305
+ // result 只接受 passed failed
306
+ if (normalized.result !== 'passed' && normalized.result !== 'failed') {
307
+ return { valid: false, error: `kimi attestation result "${normalized.result}" must be "passed" or "failed"`, normalized: null };
292
308
  }
293
- if (attestation.payloadDigest !== action.manifestDigest) {
294
- return { valid: false, error: 'kimi attestation payloadDigest does not match the frozen payload digest' };
309
+ // planDigest 绑定验证
310
+ if (!HEX_DIGEST_RE.test(normalized.planDigest)) {
311
+ return { valid: false, error: 'kimi attestation planDigest must be a 64-char lowercase hex digest', normalized: null };
295
312
  }
296
- const attestedMs = Date.parse(attestation.attestedAt);
297
- const expiresMs = Date.parse(attestation.expiresAt);
298
- const nowMs = Date.parse(isoNow);
299
- if (!Number.isFinite(attestedMs) || !Number.isFinite(expiresMs) || !Number.isFinite(nowMs)) {
300
- return { valid: false, error: 'kimi attestation attestedAt/expiresAt must be valid ISO timestamps' };
313
+ if (normalized.planDigest !== boundPlanDigest) {
314
+ return { valid: false, error: 'kimi attestation planDigest does not match the frozen plan digest', normalized: null };
301
315
  }
302
- if (attestedMs > nowMs) {
303
- return { valid: false, error: 'kimi attestation attestedAt is in the future' };
316
+ // version 静态一致性检查
317
+ if (normalized.version !== action.version) {
318
+ return { valid: false, error: `kimi attestation version "${normalized.version}" does not match action version "${action.version}"`, normalized: null };
304
319
  }
305
- if (expiresMs <= attestedMs) {
306
- return { valid: false, error: 'kimi attestation expiresAt must be after attestedAt' };
320
+
321
+ // 旧格式额外绑定字段校验:plugin, repo, ref, entrySkill, payloadDigest, installPath
322
+ // 旧格式必须包含完整旧字段组;缺任一旧必填字段失败。
323
+ if (isOldFormat) {
324
+ // plugin 必须匹配
325
+ if (normalized.plugin !== action.plugin) {
326
+ return { valid: false, error: `kimi attestation plugin "${normalized.plugin}" does not match action plugin "${action.plugin}"`, normalized: null };
327
+ }
328
+ // repo 必须匹配
329
+ if (normalized.repo !== action.repo) {
330
+ return { valid: false, error: `kimi attestation repo "${normalized.repo}" does not match action repo "${action.repo}"`, normalized: null };
331
+ }
332
+ // ref 必须匹配
333
+ const expectedRef = action.ref ?? `v${action.version}`;
334
+ if (normalized.ref !== expectedRef) {
335
+ return { valid: false, error: `kimi attestation ref "${normalized.ref}" does not match action ref "${expectedRef}"`, normalized: null };
336
+ }
337
+ // entrySkill 必须匹配
338
+ if (normalized.entrySkill !== action.entrySkill) {
339
+ return { valid: false, error: `kimi attestation entrySkill "${normalized.entrySkill}" does not match action entrySkill "${action.entrySkill}"`, normalized: null };
340
+ }
341
+ // payloadDigest 旧格式必填、格式合法且等于冻结动作 manifestDigest
342
+ if (typeof normalized.payloadDigest !== 'string' || normalized.payloadDigest.length === 0) {
343
+ return { valid: false, error: 'kimi attestation payloadDigest is required for old-format receipts', normalized: null };
344
+ }
345
+ // installPath 旧格式必填:旧格式用 installPath 证明实际安装
346
+ if (typeof normalized.installPath !== 'string' || normalized.installPath.length === 0) {
347
+ return { valid: false, error: 'kimi attestation installPath is required for old-format receipts', normalized: null };
348
+ }
349
+ if (!HEX_DIGEST_RE.test(normalized.payloadDigest)) {
350
+ return { valid: false, error: 'kimi attestation payloadDigest must be a 64-char lowercase hex digest', normalized: null };
351
+ }
352
+ if (action.manifestDigest && normalized.payloadDigest !== action.manifestDigest) {
353
+ return { valid: false, error: 'kimi attestation payloadDigest does not match the frozen manifest digest', normalized: null };
354
+ }
355
+ } else {
356
+ // 新格式 payloadDigest 可选,但如果存在则必须合法
357
+ if (normalized.payloadDigest !== undefined) {
358
+ if (!HEX_DIGEST_RE.test(normalized.payloadDigest)) {
359
+ return { valid: false, error: 'kimi attestation payloadDigest must be a 64-char lowercase hex digest when present', normalized: null };
360
+ }
361
+ if (action.manifestDigest && normalized.payloadDigest !== action.manifestDigest) {
362
+ return { valid: false, error: 'kimi attestation payloadDigest does not match the frozen manifest digest', normalized: null };
363
+ }
364
+ }
307
365
  }
308
- if (expiresMs - attestedMs > KIMI_MAX_ATTESTATION_VALIDITY_MS) {
309
- return { valid: false, error: 'kimi attestation validity must not exceed 24 hours' };
366
+
367
+ // confirmedAt 必须是有效的时间戳
368
+ const confirmedMs = Date.parse(normalized.confirmedAt);
369
+ if (!Number.isFinite(confirmedMs)) {
370
+ return { valid: false, error: 'kimi attestation confirmedAt must be a valid ISO timestamp', normalized: null };
310
371
  }
311
- if (nowMs > expiresMs) {
312
- return { valid: false, error: 'kimi attestation has expired' };
372
+ // 可选字段 note 如果存在必须是字符串
373
+ if (normalized.note !== undefined && typeof normalized.note !== 'string') {
374
+ return { valid: false, error: 'kimi attestation note must be a string when present', normalized: null };
313
375
  }
314
- return { valid: true, error: null };
376
+ return { valid: true, error: null, normalized };
315
377
  }
316
378
 
317
379
  /**
@@ -323,15 +385,8 @@ export function validateKimiAttestation(attestation, action, isoNow, boundPlanDi
323
385
  * read-only verification. Without that proof the checkpoint fails closed and can
324
386
  * never reach VERIFIED.
325
387
  *
326
- * Isolation model (B/C): the kimi home is a STABLE, plan-digest-keyed directory
327
- * under the attestation authority (`<authorityDir>/kimi-home`), not the per-run
328
- * runDir consumer dir. The operator launches Kimi Code with that KIMI_CODE_HOME
329
- * so the managed copy lands at `<kimiHome>/plugins/managed/<plugin>/`, a
330
- * location that is identical across publish/reconcile/verify run dirs. execute
331
- * creates ONLY the managed parent (`plugins/managed`), never `managed/<plugin>`
332
- * (the operator's interactive install creates that). The requirement write is
333
- * idempotent: an identical existing requirement is left untouched, a divergent
334
- * one fails closed.
388
+ * 统一人工判定:不再创建隔离目录,不再要求隔离 HOME。
389
+ * 人工结果只需 platform, version, planDigest, result, actor, confirmedAt。
335
390
  *
336
391
  * Referenced from the registry as the kimi strategy.buildManualRequirement —
337
392
  * the automatable=false manual-requirement path.
@@ -370,8 +425,8 @@ export async function executeKimiManualRequirement(action, context) {
370
425
  const ref = action.ref ?? `v${action.version}`;
371
426
  const installUrl = buildKimiInstallUrl(action.repo, ref);
372
427
 
373
- // (B) Stable, plan-digest-keyed authority dir — the ONLY kimi home, shared
374
- // across publish/reconcile/verify run dirs.
428
+ // (B) Stable, plan-digest-keyed authority dir, shared across
429
+ // publish/reconcile/verify run dirs.
375
430
  let attestationDir;
376
431
  try {
377
432
  attestationDir = kimiAuthorityDir(context, planDigest, action.plugin);
@@ -382,67 +437,61 @@ export async function executeKimiManualRequirement(action, context) {
382
437
  error: dirErr.message,
383
438
  });
384
439
  }
385
- const kimiHome = resolve(attestationDir, 'kimi-home');
386
- const managedParent = resolve(kimiHome, KIMI_MANAGED_SUBPATH); // plugins/managed
387
- // plugins/managed/<plugin> — created by the operator's interactive install.
388
- const managedInstallRoot = resolve(managedParent, action.plugin);
389
440
 
390
441
  const instructions = buildKimiManualInstructions({
391
442
  installUrl,
392
443
  plugin: action.plugin,
393
444
  version: action.version,
394
445
  ref,
395
- isolatedHome: kimiHome,
396
446
  attestationDir,
397
447
  });
398
448
 
449
+ // 统一 requirement 结构:不再包含隔离目录信息
399
450
  const requirement = {
400
451
  kind: 'kimi-manual-install-requirement',
401
- consumer: 'kimi',
452
+ platform: 'kimi',
402
453
  plugin: action.plugin,
403
454
  version: action.version,
404
- entrySkill: action.entrySkill,
405
455
  repo: action.repo,
406
456
  ref,
457
+ entrySkill: action.entrySkill,
407
458
  installUrl,
408
- // (A) planDigest binds to the real frozen plan digest;
409
- // expectedPayloadDigest binds separately to the snapshot payload digest.
410
459
  planDigest,
411
- expectedPayloadDigest: action.manifestDigest,
412
- isolatedHome: kimiHome,
413
- kimiCodeHome: kimiHome,
414
- managedInstallRoot,
415
460
  attestationDir,
416
461
  attestationFile: KIMI_ATTESTATION_FILE,
417
462
  attestationTemplate: {
418
- consumer: 'kimi',
419
- plugin: action.plugin,
463
+ platform: 'kimi',
420
464
  version: action.version,
421
- entrySkill: action.entrySkill,
422
- repo: action.repo,
423
- ref,
424
- installPath: managedInstallRoot,
425
465
  planDigest,
426
- payloadDigest: action.manifestDigest,
427
- attestedBy: '<person responsible for the manual install>',
428
- attestedAt: '<ISO 8601 now; must not be in the future>',
429
- expiresAt: '<ISO 8601; within 24h of attestedAt>',
466
+ result: '<"passed" or "failed">',
467
+ actor: '<person who confirmed the install>',
468
+ confirmedAt: '<ISO 8601 timestamp>',
469
+ note: '<optional note>',
430
470
  },
431
471
  instructions,
432
472
  };
433
473
 
434
- // Create ONLY the managed parent (plugins/managed); never pre-create
435
- // managed/<plugin> the operator's interactive install creates that.
474
+ // Ensure the authority directory exists (no isolated home creation needed).
475
+ const { mkdir } = await import('node:fs/promises');
436
476
  try {
437
- await mkdir(managedParent, { recursive: true, mode: 0o700 });
477
+ await mkdir(attestationDir, { recursive: true, mode: 0o700 });
438
478
  } catch (mkdirErr) {
439
- return createResult({
440
- actionType,
441
- status: ActionStatus.EXECUTE_FAILED,
442
- error: `cannot create kimi managed parent directory: ${mkdirErr.message}`,
443
- });
479
+ // EEXIST is fine; other errors are fatal.
480
+ if (mkdirErr?.code !== 'EEXIST') {
481
+ return createResult({
482
+ actionType,
483
+ status: ActionStatus.EXECUTE_FAILED,
484
+ error: `cannot create kimi attestation directory: ${mkdirErr.message}`,
485
+ });
486
+ }
444
487
  }
445
488
 
489
+ // New manual format: execute only writes the requirement file.
490
+ // The managed home directory (kimi-home/plugins/managed) is NOT created here.
491
+ // Only old-format attestations with installPath trigger managed home verification
492
+ // in the observe path. Creating it unconditionally would violate the invariant
493
+ // that new-format receipts do not create isolated installation artifacts.
494
+
446
495
  // Idempotent requirement write: an identical existing requirement is left
447
496
  // untouched; a divergent existing requirement fails closed (never silently
448
497
  // overwritten). `createdAt` is volatile and excluded from the comparison.
@@ -500,14 +549,10 @@ export async function executeKimiManualRequirement(action, context) {
500
549
  consumer: 'kimi',
501
550
  plugin: action.plugin,
502
551
  version: action.version,
503
- entrySkill: action.entrySkill,
504
- repo: action.repo,
505
552
  ref,
506
553
  installUrl,
507
554
  planDigest,
508
555
  attestationDir,
509
- kimiCodeHome: kimiHome,
510
- managedInstallRoot,
511
556
  instructions,
512
557
  },
513
558
  });
@@ -42,6 +42,10 @@ import {
42
42
  executeCodeBuddyManualRequirement,
43
43
  readCodeBuddyManifest,
44
44
  } from './codebuddy.mjs';
45
+ import {
46
+ executeCodexManualRequirement,
47
+ readCodexManifest,
48
+ } from './codex.mjs';
45
49
 
46
50
  // --- claude strategy -------------------------------------------------------
47
51
 
@@ -201,7 +205,7 @@ const CODEX = Object.freeze({
201
205
  refStrength: 'commit-sha',
202
206
  outputProtocol: 'structured',
203
207
  identityEvidence: 'install-output',
204
- degradationPolicy: 'block',
208
+ degradationPolicy: 'human-attestation-with-fallback',
205
209
  cli: Object.freeze({
206
210
  binary: 'codex',
207
211
  marketplaceAdd: (repo, ref) => ['plugin', 'marketplace', 'add', repo, '--ref', ref, '--json'],
@@ -243,8 +247,9 @@ const CODEX = Object.freeze({
243
247
  extractInstallPath: codexExtractInstallPath,
244
248
  extractListIdentity: codexExtractListIdentity,
245
249
  crossValidateListEntry: codexCrossValidateListEntry,
246
- buildManualRequirement: null,
247
- readManifest: null,
250
+ // codex's human-attestation fallback strategy lives in ./codex.mjs
251
+ buildManualRequirement: executeCodexManualRequirement,
252
+ readManifest: readCodexManifest,
248
253
  }),
249
254
  });
250
255
 
@@ -267,7 +272,7 @@ const KIMI = Object.freeze({
267
272
  marketplaceAddOutput: null,
268
273
  pluginInstallOutput: null,
269
274
  }),
270
- isolationEnv: (home) => ({ HOME: home, KIMI_CODE_HOME: home }),
275
+ isolationEnv: (home) => ({ HOME: home }),
271
276
  // kimi never execs a CLI; its stable home is created by the
272
277
  // manual-requirement strategy under the attestation authority.
273
278
  isolationSubdirs: Object.freeze([]),
@@ -348,7 +353,10 @@ const CODEBUDDY = Object.freeze({
348
353
  manifestPaths: Object.freeze({
349
354
  // Single authoritative manifest (no precedence chain, unlike kimi).
350
355
  plugin: '.codebuddy-plugin/plugin.json',
351
- marketplace: null,
356
+ // CodeBuddy bundled-family uses the skill-family marketplace path.
357
+ // This ensures bundled-family installation contracts always include a
358
+ // marketplace entry without requiring an explicit marketplaceIndexPath.
359
+ marketplace: '.claude-plugin/marketplace.json',
352
360
  }),
353
361
  marketplaceSourceForm: null,
354
362
  // codebuddy installs from a unified marketplace but carries no marketplace
@@ -401,7 +409,7 @@ const VALID_INSTALL_METHODS = new Set(['structured-cli', 'interactive-only', 'hu
401
409
  const VALID_REF_STRENGTHS = new Set(['commit-sha', 'name-ref', 'unfixable']);
402
410
  const VALID_OUTPUT_PROTOCOLS = new Set(['structured', 'text', 'none']);
403
411
  const VALID_IDENTITY_EVIDENCE = new Set(['list-record', 'install-output', 'filesystem-payload', 'human-attestation']);
404
- const VALID_DEGRADATION_POLICIES = new Set(['block', 'human-attestation']);
412
+ const VALID_DEGRADATION_POLICIES = new Set(['block', 'human-attestation', 'human-attestation-with-fallback']);
405
413
 
406
414
  /**
407
415
  * Look up a platform descriptor by id.
@@ -513,6 +521,16 @@ export function assertRegistry(registry = PLATFORMS) {
513
521
  if (typeof platform.strategy.extractInstallPath !== 'function') {
514
522
  throw new Error(`platform registry: automatable platform ${label} needs strategy.extractInstallPath`);
515
523
  }
524
+ // Platforms with degradationPolicy 'human-attestation-with-fallback'
525
+ // (codex) also need manual-requirement strategies for the fallback path.
526
+ if (platform.degradationPolicy === 'human-attestation-with-fallback') {
527
+ if (typeof platform.strategy.buildManualRequirement !== 'function') {
528
+ throw new Error(`platform registry: ${label} with human-attestation-with-fallback needs strategy.buildManualRequirement`);
529
+ }
530
+ if (typeof platform.strategy.readManifest !== 'function') {
531
+ throw new Error(`platform registry: ${label} with human-attestation-with-fallback needs strategy.readManifest`);
532
+ }
533
+ }
516
534
  } else {
517
535
  if (platform.cli !== null) {
518
536
  throw new Error(`platform registry: non-automatable platform ${label} must have cli === null`);