release-skill 0.8.0 → 0.8.1

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 (107) 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 +34 -7
  7. package/INSTALL.md +2 -2
  8. package/INSTALL.zh-CN.md +2 -2
  9. package/README.md +34 -26
  10. package/README.zh-CN.md +21 -17
  11. package/adapters/claude/.claude-plugin/marketplace.json +1 -1
  12. package/adapters/claude/.claude-plugin/plugin.json +1 -1
  13. package/adapters/claude/bin/error-codes.json +1 -1
  14. package/adapters/claude/bin/foundation-resource-binding.json +1 -1
  15. package/adapters/claude/bin/prebuild-manifest.json +8 -4
  16. package/adapters/claude/bin/prebuilds/darwin-arm64/bound_read.darwin-arm64.node +0 -0
  17. package/adapters/claude/bin/prebuilds/darwin-x64/bound_read.darwin-x64.node +0 -0
  18. package/adapters/claude/bin/prebuilds/linux-arm64-gnu/bound_read.linux-arm64-gnu.node +0 -0
  19. package/adapters/claude/bin/prebuilds/linux-x64-gnu/bound_read.linux-x64-gnu.node +0 -0
  20. package/adapters/claude/bin/registry.json +22 -1
  21. package/adapters/claude/bin/release-skill.bundle.mjs +4904 -3079
  22. package/adapters/claude/bin/rules.json +1 -1
  23. package/adapters/claude/schemas/release-plan.schema.json +3 -2
  24. package/adapters/claude/schemas/release-run.schema.json +35 -1
  25. package/adapters/claude/src/schemas/filesystem-tree-observation.schema.json +55 -0
  26. package/adapters/claude/src/schemas/host-verification-result.schema.json +2 -2
  27. package/adapters/claude/src/schemas/plugin-verification-request.schema.json +374 -0
  28. package/adapters/claude/src/schemas/plugin-verification-result.schema.json +799 -0
  29. package/adapters/claude/src/schemas/watchdog-termination-envelope.schema.json +16 -0
  30. package/adapters/codex/.codex-plugin/plugin.json +2 -2
  31. package/adapters/codex/bin/error-codes.json +1 -1
  32. package/adapters/codex/bin/foundation-resource-binding.json +1 -1
  33. package/adapters/codex/bin/prebuild-manifest.json +8 -4
  34. package/adapters/codex/bin/prebuilds/darwin-arm64/bound_read.darwin-arm64.node +0 -0
  35. package/adapters/codex/bin/prebuilds/darwin-x64/bound_read.darwin-x64.node +0 -0
  36. package/adapters/codex/bin/prebuilds/linux-arm64-gnu/bound_read.linux-arm64-gnu.node +0 -0
  37. package/adapters/codex/bin/prebuilds/linux-x64-gnu/bound_read.linux-x64-gnu.node +0 -0
  38. package/adapters/codex/bin/registry.json +22 -1
  39. package/adapters/codex/bin/release-skill.bundle.mjs +4904 -3079
  40. package/adapters/codex/bin/rules.json +1 -1
  41. package/adapters/codex/schemas/release-plan.schema.json +3 -2
  42. package/adapters/codex/schemas/release-run.schema.json +35 -1
  43. package/adapters/codex/src/schemas/filesystem-tree-observation.schema.json +55 -0
  44. package/adapters/codex/src/schemas/host-verification-result.schema.json +2 -2
  45. package/adapters/codex/src/schemas/plugin-verification-request.schema.json +374 -0
  46. package/adapters/codex/src/schemas/plugin-verification-result.schema.json +799 -0
  47. package/adapters/codex/src/schemas/watchdog-termination-envelope.schema.json +16 -0
  48. package/adapters/kimi/.kimi-plugin/plugin.json +1 -1
  49. package/adapters/kimi/bin/error-codes.json +1 -1
  50. package/adapters/kimi/bin/foundation-resource-binding.json +1 -1
  51. package/adapters/kimi/bin/prebuild-manifest.json +8 -4
  52. package/adapters/kimi/bin/prebuilds/darwin-arm64/bound_read.darwin-arm64.node +0 -0
  53. package/adapters/kimi/bin/prebuilds/darwin-x64/bound_read.darwin-x64.node +0 -0
  54. package/adapters/kimi/bin/prebuilds/linux-arm64-gnu/bound_read.linux-arm64-gnu.node +0 -0
  55. package/adapters/kimi/bin/prebuilds/linux-x64-gnu/bound_read.linux-x64-gnu.node +0 -0
  56. package/adapters/kimi/bin/registry.json +22 -1
  57. package/adapters/kimi/bin/release-skill.bundle.mjs +4904 -3079
  58. package/adapters/kimi/bin/rules.json +1 -1
  59. package/adapters/kimi/schemas/release-plan.schema.json +3 -2
  60. package/adapters/kimi/schemas/release-run.schema.json +35 -1
  61. package/adapters/kimi/src/schemas/filesystem-tree-observation.schema.json +55 -0
  62. package/adapters/kimi/src/schemas/host-verification-result.schema.json +2 -2
  63. package/adapters/kimi/src/schemas/plugin-verification-request.schema.json +374 -0
  64. package/adapters/kimi/src/schemas/plugin-verification-result.schema.json +799 -0
  65. package/adapters/kimi/src/schemas/watchdog-termination-envelope.schema.json +16 -0
  66. package/adapters/workbuddy/.codebuddy-plugin/plugin.json +1 -1
  67. package/adapters/workbuddy/bin/error-codes.json +1 -1
  68. package/adapters/workbuddy/bin/foundation-resource-binding.json +1 -1
  69. package/adapters/workbuddy/bin/prebuild-manifest.json +8 -4
  70. package/adapters/workbuddy/bin/prebuilds/darwin-arm64/bound_read.darwin-arm64.node +0 -0
  71. package/adapters/workbuddy/bin/prebuilds/darwin-x64/bound_read.darwin-x64.node +0 -0
  72. package/adapters/workbuddy/bin/prebuilds/linux-arm64-gnu/bound_read.linux-arm64-gnu.node +0 -0
  73. package/adapters/workbuddy/bin/prebuilds/linux-x64-gnu/bound_read.linux-x64-gnu.node +0 -0
  74. package/adapters/workbuddy/bin/registry.json +22 -1
  75. package/adapters/workbuddy/bin/release-skill.bundle.mjs +4904 -3079
  76. package/adapters/workbuddy/bin/rules.json +1 -1
  77. package/adapters/workbuddy/schemas/release-plan.schema.json +3 -2
  78. package/adapters/workbuddy/schemas/release-run.schema.json +35 -1
  79. package/adapters/workbuddy/src/schemas/filesystem-tree-observation.schema.json +55 -0
  80. package/adapters/workbuddy/src/schemas/host-verification-result.schema.json +2 -2
  81. package/adapters/workbuddy/src/schemas/plugin-verification-request.schema.json +374 -0
  82. package/adapters/workbuddy/src/schemas/plugin-verification-result.schema.json +799 -0
  83. package/adapters/workbuddy/src/schemas/watchdog-termination-envelope.schema.json +16 -0
  84. package/bin/error-codes.json +1 -1
  85. package/bin/foundation-resource-binding.json +1 -1
  86. package/bin/prebuild-manifest.json +8 -4
  87. package/bin/prebuilds/darwin-arm64/bound_read.darwin-arm64.node +0 -0
  88. package/bin/prebuilds/darwin-x64/bound_read.darwin-x64.node +0 -0
  89. package/bin/prebuilds/linux-arm64-gnu/bound_read.linux-arm64-gnu.node +0 -0
  90. package/bin/prebuilds/linux-x64-gnu/bound_read.linux-x64-gnu.node +0 -0
  91. package/bin/registry.json +22 -1
  92. package/bin/release-skill.bundle.mjs +4904 -3079
  93. package/bin/rules.json +1 -1
  94. package/package.json +4 -4
  95. package/platform-manifest.json +2 -2
  96. package/schemas/release-plan.schema.json +3 -2
  97. package/schemas/release-run.schema.json +35 -1
  98. package/src/commands/prepare.mjs +15 -4
  99. package/src/commands/verify.mjs +62 -10
  100. package/src/core/foundation-plugin-verification.mjs +247 -0
  101. package/src/core/skill-resource-closure.mjs +8 -4
  102. package/src/producers/foundation-resource-projection.mjs +3 -3
  103. package/src/schemas/filesystem-tree-observation.schema.json +55 -0
  104. package/src/schemas/host-verification-result.schema.json +2 -2
  105. package/src/schemas/plugin-verification-request.schema.json +374 -0
  106. package/src/schemas/plugin-verification-result.schema.json +799 -0
  107. package/src/schemas/watchdog-termination-envelope.schema.json +16 -0
package/bin/rules.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "kind": "skill-family.contracts.rules",
4
- "contractsVersion": "1.12.0",
4
+ "contractsVersion": "1.13.0",
5
5
  "budget": {
6
6
  "firstVersionMax": 20,
7
7
  "absoluteMax": 30,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "release-skill",
3
- "version": "0.8.0",
3
+ "version": "0.8.1",
4
4
  "description": "Safe preparation and frozen GitHub/npm production publishing with full happy end verification",
5
5
  "author": {
6
6
  "name": "广州市风荷科技有限公司"
@@ -79,9 +79,9 @@
79
79
  "ajv-formats": "3.0.1",
80
80
  "libnpmpublish": "11.2.0",
81
81
  "npm-registry-fetch": "18.0.2",
82
- "skill-family-contracts": "0.12.0",
83
- "skill-family-engineering-kit": "0.12.0",
84
- "skill-family-harness-node": "0.12.0",
82
+ "skill-family-contracts": "0.13.0",
83
+ "skill-family-engineering-kit": "0.13.0",
84
+ "skill-family-harness-node": "0.13.0",
85
85
  "yaml": "2.9.0"
86
86
  },
87
87
  "devDependencies": {
@@ -2,11 +2,11 @@
2
2
  "schemaVersion": "1.0.0",
3
3
  "platformId": "release-skill",
4
4
  "platformName": "release-skill Plugin Platform",
5
- "version": "0.8.0",
5
+ "version": "0.8.1",
6
6
  "logicalRoot": "skills-src/",
7
7
  "logicalRootDigest": "3142e4454f192de6bb3de09141dfb7798a16acd3b8de820a70a2bf185f82fd38",
8
8
  "projectionDigest": "43e29d004faac5800912a95abc013690e72f5cfd3b42e33cbc28fef39a39bfa3",
9
- "projectionId": "release-skill:platform:0.8.0",
9
+ "projectionId": "release-skill:platform:0.8.1",
10
10
  "manifestRequired": true,
11
11
  "projectionSources": {
12
12
  "skills-src-root": {
@@ -1051,9 +1051,10 @@
1051
1051
  "humanConsumersStrategy": {
1052
1052
  "type": "string",
1053
1053
  "enum": [
1054
- "manualFollowUps"
1054
+ "manualFollowUps",
1055
+ "foundationPayloadThenManualFollowUps"
1055
1056
  ],
1056
- "description": "Human consumer strategy marker. 'manualFollowUps': Kimi/CodeBuddy installations are non-blocking post-release manual tasks; verify returns VERIFIED with manualFollowUps list and does not block on attestation. Absent: legacy attestation closed loop (backward compatibility)."
1057
+ "description": "Human consumer strategy marker. 'foundationPayloadThenManualFollowUps': verify first observes the complete frozen plugin through Foundation, then preserves non-blocking Kimi/CodeBuddy marketplace/invocation follow-ups. 'manualFollowUps': legacy 0.8 behavior without the Foundation payload observation. Absent: legacy attestation closed loop."
1057
1058
  },
1058
1059
  "workflowKind": {
1059
1060
  "type": "string",
@@ -530,7 +530,7 @@
530
530
  },
531
531
  "manualFollowUps": {
532
532
  "type": "array",
533
- "description": "Non-blocking manual follow-up tasks for human consumer platforms (Kimi/CodeBuddy) when plan declares humanConsumersStrategy: 'manualFollowUps'",
533
+ "description": "Non-blocking marketplace installation and invocation follow-ups for Kimi/CodeBuddy. Under foundationPayloadThenManualFollowUps, the frozen payload was observed first but these real-host tasks remain unverified.",
534
534
  "items": {
535
535
  "type": "object",
536
536
  "required": [
@@ -571,6 +571,40 @@
571
571
  }
572
572
  }
573
573
  },
574
+ "foundationPluginVerificationReceipts": {
575
+ "type": "array",
576
+ "description": "Foundation complete-plugin local payload observations bound to verify actions. These receipts do not prove marketplace installation or host invocation.",
577
+ "items": {
578
+ "type": "object",
579
+ "additionalProperties": false,
580
+ "required": [
581
+ "planDigest",
582
+ "actionId",
583
+ "unitId",
584
+ "requestDigest",
585
+ "hostId",
586
+ "goal",
587
+ "sourceType",
588
+ "foundationStatus",
589
+ "payloadMatches",
590
+ "observationDigest",
591
+ "observedAt"
592
+ ],
593
+ "properties": {
594
+ "planDigest": { "type": "string", "pattern": "^[a-f0-9]{64}$" },
595
+ "actionId": { "type": "string", "minLength": 1 },
596
+ "unitId": { "type": "string", "minLength": 1 },
597
+ "requestDigest": { "type": "string", "pattern": "^[a-f0-9]{64}$" },
598
+ "hostId": { "enum": ["kimi-code", "workbuddy"] },
599
+ "goal": { "const": "install-only" },
600
+ "sourceType": { "const": "local-staged" },
601
+ "foundationStatus": { "const": "observed" },
602
+ "payloadMatches": { "const": true },
603
+ "observationDigest": { "type": "string", "pattern": "^[a-f0-9]{64}$" },
604
+ "observedAt": { "type": "string", "format": "date-time" }
605
+ }
606
+ }
607
+ },
574
608
  "skillResourceClosureReceipts": {
575
609
  "type": "array",
576
610
  "description": "Per-surface skill resource closure verification receipts from verify phase",
@@ -58,6 +58,7 @@ import {
58
58
  import { ReleaseError, GATE_FAILED, CONFIG_INVALID, CONFIG_MISSING, FORBIDDEN_CONTENT_DETECTED, RELEASE_DOCS_STALE, DIRTY_SOURCE_INPUT, BUNDLE_STALE } from '../core/errors.mjs';
59
59
  import { assertBundleFreshness } from '../core/bundle-freshness.mjs';
60
60
  import { assertAdapterFreshness, assertSelfBootstrapFacts } from '../core/derived-artifact-gates.mjs';
61
+ import { isFoundationPluginVerificationEligible } from '../core/foundation-plugin-verification.mjs';
61
62
  import { PKG_ROOT } from '../core/pkg-root.mjs';
62
63
  import { writeFrozenMarker, FROZEN_MARKER_FILENAME } from '../core/frozen-marker.mjs';
63
64
  import {
@@ -4235,11 +4236,21 @@ export async function prepareRelease(options) {
4235
4236
  });
4236
4237
  }
4237
4238
 
4238
- // Detect human consumer platforms (Kimi/CodeBuddy) in the plan.
4239
- // When present, new plans mark them as non-blocking manual follow-up tasks.
4240
- const hasHumanConsumerActions = externalActions.some(
4239
+ // Detect human consumer platforms (Kimi/CodeBuddy) in the plan. Foundation
4240
+ // 0.13 can observe their complete local payload only when production has
4241
+ // frozen a bundled-family source. Standalone marketplace sources remain on
4242
+ // the legacy manual-follow-up path because Foundation exposes no public
4243
+ // Kimi/WorkBuddy public-channel driver in this release.
4244
+ const humanConsumerActions = externalActions.filter(
4241
4245
  (a) => a.type === 'kimi-marketplace-install' || a.type === 'codebuddy-marketplace-install',
4242
4246
  );
4247
+ const humanConsumersStrategy = humanConsumerActions.length === 0
4248
+ ? null
4249
+ : humanConsumerActions.some((action) => (
4250
+ isFoundationPluginVerificationEligible({ action, units })
4251
+ ))
4252
+ ? 'foundationPayloadThenManualFollowUps'
4253
+ : 'manualFollowUps';
4243
4254
 
4244
4255
  // --- Fold the validated postPublish declaration into the frozen plan ---
4245
4256
  // Bindings: tag (tagTemplate rendered at the resolved target version —
@@ -4331,7 +4342,7 @@ export async function prepareRelease(options) {
4331
4342
  : {}),
4332
4343
  verificationGates: config.verificationGates ?? [],
4333
4344
  snapshotDigest: overallSnapshotDigest,
4334
- ...(hasHumanConsumerActions ? { humanConsumersStrategy: 'manualFollowUps' } : {}),
4345
+ ...(humanConsumersStrategy ? { humanConsumersStrategy } : {}),
4335
4346
  ...(production ? {
4336
4347
  production: {
4337
4348
  mode: 'github-npm-v1',
@@ -62,6 +62,10 @@ import {
62
62
  import { loadProjectConfig } from '../core/config.mjs';
63
63
  import { assertTransition, PUBLISHED, VERIFIED } from '../core/state-machine.mjs';
64
64
  import { clearFrozenMarker } from '../core/frozen-marker.mjs';
65
+ import {
66
+ isFoundationPluginVerificationEligible,
67
+ verifyFrozenPluginWithFoundation,
68
+ } from '../core/foundation-plugin-verification.mjs';
65
69
  import { resolveUnitScopedPath } from '../snapshot/public-path.mjs';
66
70
  import {
67
71
  normalizeRegistry,
@@ -944,6 +948,7 @@ export async function verifyRelease(options) {
944
948
  previousVerifyRun,
945
949
  execFn,
946
950
  configPath: configPathOpt,
951
+ runPluginVerificationFn,
947
952
  } = options ?? {};
948
953
 
949
954
  const clockFn = typeof clockOpt === 'function' ? clockOpt : defaultClock;
@@ -1351,10 +1356,15 @@ export async function verifyRelease(options) {
1351
1356
  }
1352
1357
  }
1353
1358
 
1354
- // When the plan declares humanConsumersStrategy: 'manualFollowUps',
1355
- // Kimi/CodeBuddy installations are non-blocking post-release manual tasks.
1356
- // Skip attestation collection and blocking for these platforms.
1357
- const isHumanConsumerFollowUp = plan.humanConsumersStrategy === 'manualFollowUps';
1359
+ // Kimi/CodeBuddy remain non-blocking post-release manual tasks. New plans
1360
+ // additionally require a Foundation observation of the complete frozen
1361
+ // payload before the manual marketplace/invocation follow-up is emitted.
1362
+ const isHumanConsumerFollowUp = [
1363
+ 'manualFollowUps',
1364
+ 'foundationPayloadThenManualFollowUps',
1365
+ ].includes(plan.humanConsumersStrategy);
1366
+ const requiresFoundationPayloadObservation = plan.humanConsumersStrategy
1367
+ === 'foundationPayloadThenManualFollowUps';
1358
1368
 
1359
1369
  const missingManualAttestations = isHumanConsumerFollowUp
1360
1370
  ? []
@@ -1382,6 +1392,7 @@ export async function verifyRelease(options) {
1382
1392
 
1383
1393
  // Collect non-blocking manual follow-up tasks for human consumer platforms
1384
1394
  const manualFollowUps = [];
1395
+ const foundationPluginVerificationReceipts = [];
1385
1396
 
1386
1397
  // Every action is identity-bound and adapters receive read-only or
1387
1398
  // per-action isolated consumer paths. Run independent checks concurrently;
@@ -1414,13 +1425,40 @@ export async function verifyRelease(options) {
1414
1425
  }
1415
1426
 
1416
1427
  if (isMarketplaceAction(action.type)) {
1417
- // When humanConsumersStrategy is 'manualFollowUps', Kimi/CodeBuddy
1418
- // installations are non-blocking manual follow-up tasks. Skip adapter
1419
- // processing and collect them as manualFollowUps.
1428
+ // Both human-consumer strategies keep marketplace installation and
1429
+ // invocation as non-blocking manual follow-ups. The new strategy first
1430
+ // requires Foundation to observe the identity-bound frozen payload.
1420
1431
  const isHumanConsumerPlatform = action.type === 'kimi-marketplace-install'
1421
1432
  || action.type === 'codebuddy-marketplace-install';
1422
1433
  if (isHumanConsumerFollowUp && isHumanConsumerPlatform) {
1423
1434
  const platform = action.type === 'kimi-marketplace-install' ? 'kimi' : 'codebuddy';
1435
+ const observesFoundationPayload = requiresFoundationPayloadObservation
1436
+ && isFoundationPluginVerificationEligible({ action, units: plan.units });
1437
+ if (observesFoundationPayload) {
1438
+ const actionRunDir = join(runDir, 'foundation-plugin-verification', action.id);
1439
+ await mkdir(join(actionRunDir, 'install'), { recursive: true, mode: 0o700 });
1440
+ await mkdir(join(actionRunDir, 'temporary'), { recursive: true, mode: 0o700 });
1441
+ await mkdir(join(actionRunDir, 'evidence'), { recursive: true, mode: 0o700 });
1442
+ const receipt = await verifyFrozenPluginWithFoundation({
1443
+ plan,
1444
+ action,
1445
+ root,
1446
+ runDir: actionRunDir,
1447
+ ...(runPluginVerificationFn ? { runPluginVerificationFn } : {}),
1448
+ clock: clockFn,
1449
+ });
1450
+ foundationPluginVerificationReceipts.push(receipt);
1451
+ await evidence.append({
1452
+ phase: 'verify-marketplace',
1453
+ actionId: action.id,
1454
+ actionType: action.type,
1455
+ status: 'FOUNDATION_PAYLOAD_OBSERVED',
1456
+ requestDigest: receipt.requestDigest,
1457
+ hostId: receipt.hostId,
1458
+ payloadMatches: receipt.payloadMatches,
1459
+ observationDigest: receipt.observationDigest,
1460
+ });
1461
+ }
1424
1462
  manualFollowUps.push({
1425
1463
  actionId: action.id,
1426
1464
  platform,
@@ -1428,20 +1466,26 @@ export async function verifyRelease(options) {
1428
1466
  version: action.parameters?.version,
1429
1467
  unitId: action.unitId,
1430
1468
  verifiedBySystem: false,
1431
- reason: 'human consumer platform — installation is a manual post-release task; not verified by system',
1469
+ reason: observesFoundationPayload
1470
+ ? 'complete frozen payload observed by Foundation; marketplace installation and host invocation remain manual'
1471
+ : 'human consumer platform — installation is a manual post-release task; not verified by system',
1432
1472
  });
1433
1473
  adapterChecks.push({
1434
1474
  actionId: action.id,
1435
1475
  actionType: action.type,
1436
1476
  status: 'SKIPPED',
1437
- reason: 'manual-follow-up',
1477
+ reason: observesFoundationPayload
1478
+ ? 'foundation-payload-observed-manual-follow-up'
1479
+ : 'manual-follow-up',
1438
1480
  });
1439
1481
  await evidence.append({
1440
1482
  phase: 'verify-marketplace',
1441
1483
  actionId: action.id,
1442
1484
  actionType: action.type,
1443
1485
  status: 'SKIPPED',
1444
- reason: 'manual-follow-up',
1486
+ reason: observesFoundationPayload
1487
+ ? 'foundation-payload-observed-manual-follow-up'
1488
+ : 'manual-follow-up',
1445
1489
  });
1446
1490
  return;
1447
1491
  }
@@ -1727,6 +1771,8 @@ export async function verifyRelease(options) {
1727
1771
  }));
1728
1772
  adapterChecks.sort((a, b) => a.actionId.localeCompare(b.actionId));
1729
1773
  consumerVerificationReceipts.sort((a, b) => a.actionId.localeCompare(b.actionId));
1774
+ foundationPluginVerificationReceipts.sort((a, b) => a.actionId.localeCompare(b.actionId));
1775
+ manualFollowUps.sort((a, b) => a.actionId.localeCompare(b.actionId));
1730
1776
  consumerGateResults.sort((a, b) => a.id.localeCompare(b.id));
1731
1777
  const rejectedAction = actionResults.find((result) => result.status === 'rejected');
1732
1778
  if (rejectedAction) throw rejectedAction.reason;
@@ -2034,6 +2080,9 @@ export async function verifyRelease(options) {
2034
2080
  gateResults: consumerGateResults,
2035
2081
  consumerVerificationReceipts,
2036
2082
  skillResourceClosureReceipts,
2083
+ ...(foundationPluginVerificationReceipts.length > 0
2084
+ ? { foundationPluginVerificationReceipts }
2085
+ : {}),
2037
2086
  ...(manualFollowUps.length > 0 ? { manualFollowUps } : {}),
2038
2087
  startedAt: clockFn(),
2039
2088
  finishedAt: clockFn(),
@@ -2132,6 +2181,9 @@ export async function verifyRelease(options) {
2132
2181
  recoveryActionCode: null,
2133
2182
  smokeTest,
2134
2183
  gateResults: consumerGateResults,
2184
+ ...(foundationPluginVerificationReceipts.length > 0
2185
+ ? { foundationPluginVerificationReceipts }
2186
+ : {}),
2135
2187
  ...(manualFollowUps.length > 0 ? { manualFollowUps } : {}),
2136
2188
  ...(baselineAdvance ? { baselineAdvance } : {}),
2137
2189
  };
@@ -0,0 +1,247 @@
1
+ /**
2
+ * Release-domain thin adapter for Foundation 0.13 complete-plugin observation.
3
+ *
4
+ * The frozen snapshot remains the only payload authority. This module derives
5
+ * transient Foundation inputs from that tree, calls the released Kit API, and
6
+ * returns only the fields needed to bind the mechanism observation to a
7
+ * release action. It does not prove a marketplace install or host invocation.
8
+ *
9
+ * @module core/foundation-plugin-verification
10
+ */
11
+
12
+ import { join } from 'node:path';
13
+
14
+ import { digestDocument } from 'skill-family-contracts';
15
+ import {
16
+ bundledHostProfilesRoot,
17
+ describeHost,
18
+ runPluginVerification,
19
+ } from 'skill-family-engineering-kit';
20
+ import {
21
+ createFilesystemRootBinding,
22
+ readFileBound,
23
+ } from 'skill-family-harness-node';
24
+
25
+ import { getPlatform, normalizeHostId } from '../platforms/registry.mjs';
26
+ import { verifyFrozenSnapshot } from '../snapshot/frozen.mjs';
27
+ import { ReleaseError, POST_PUBLISH_VERIFY_FAILED } from './errors.mjs';
28
+
29
+ // Foundation's public plugin-verification request freezes the built-in driver
30
+ // contract at 1.0.0. The real API integration test intentionally exercises
31
+ // this value so an upstream contract change fails closed during adoption.
32
+ const FOUNDATION_PLUGIN_DRIVER_VERSION = '1.0.0';
33
+
34
+ function fail(message, details = {}) {
35
+ return new ReleaseError(POST_PUBLISH_VERIFY_FAILED, message, details);
36
+ }
37
+
38
+ function projectSourceMembers(entries) {
39
+ const directories = new Set();
40
+ for (const entry of entries) {
41
+ const parts = entry.path.split('/');
42
+ for (let count = 1; count < parts.length; count += 1) {
43
+ directories.add(parts.slice(0, count).join('/'));
44
+ }
45
+ }
46
+ return [
47
+ ...[...directories].map((path) => ({ path, type: 'directory' })),
48
+ ...entries.map((entry) => ({
49
+ path: entry.path,
50
+ type: 'file',
51
+ sha256: entry.contentDigest,
52
+ bytes: entry.size,
53
+ executable: (entry.mode & 0o111) !== 0,
54
+ })),
55
+ ].sort((left, right) => left.path < right.path ? -1 : left.path > right.path ? 1 : 0);
56
+ }
57
+
58
+ /**
59
+ * Return whether one human-consumer action has the complete frozen identity
60
+ * required by Foundation's local-staged plugin verification.
61
+ *
62
+ * This predicate is deliberately plan-derived. It does not persist a second
63
+ * action list, and the verifier repeats the same checks before every call.
64
+ */
65
+ export function isFoundationPluginVerificationEligible({ action, units = [] } = {}) {
66
+ const platformId = action?.parameters?.consumer;
67
+ const expectedType = platformId === 'kimi'
68
+ ? 'kimi-marketplace-install'
69
+ : platformId === 'codebuddy'
70
+ ? 'codebuddy-marketplace-install'
71
+ : null;
72
+ if (!expectedType || action?.type !== expectedType) return false;
73
+
74
+ const unit = units.find((candidate) => candidate.id === action.unitId);
75
+ const snapshot = unit?.frozenSnapshot;
76
+ const sourceDescriptor = action.parameters?.sourceDescriptor;
77
+ if (
78
+ !snapshot?.path
79
+ || !snapshot.commit
80
+ || !snapshot.manifestDigest
81
+ || sourceDescriptor?.form !== 'bundled-family'
82
+ || sourceDescriptor.pluginSubpath !== '.'
83
+ || sourceDescriptor.commit !== snapshot.commit
84
+ || sourceDescriptor.payloadDigest !== snapshot.manifestDigest
85
+ || action.parameters?.sourceCommit !== snapshot.commit
86
+ || action.parameters?.snapshotPath !== snapshot.path
87
+ || action.parameters?.manifestDigest !== snapshot.manifestDigest
88
+ ) return false;
89
+
90
+ const platform = getPlatform(platformId);
91
+ return (unit.distributions ?? []).some((distribution) => (
92
+ distribution.type === platform.distributionType
93
+ && typeof distribution.installationContract?.manifestRelativePath === 'string'
94
+ && distribution.installationContract.manifestRelativePath.length > 0
95
+ ));
96
+ }
97
+
98
+ function assertResultMatchesRequest(result, request) {
99
+ if (
100
+ result?.status !== 'observed'
101
+ || result.requestDigest !== digestDocument(request)
102
+ || result.goal !== request.goal
103
+ || digestDocument(result.source) !== digestDocument(request.source)
104
+ || digestDocument(result.host) !== digestDocument(request.host)
105
+ || result.facts?.install?.payloadMatches !== true
106
+ || result.facts.install.sourceMode !== 'local-staged'
107
+ || result.facts.install.retained !== true
108
+ || result.facts.invocation !== null
109
+ ) {
110
+ throw fail('Foundation complete-plugin observation did not satisfy the frozen local payload contract', {
111
+ foundationStatus: result?.status ?? null,
112
+ foundationReason: result?.reason ?? null,
113
+ payloadMatches: result?.facts?.install?.payloadMatches ?? null,
114
+ requiredActions: result?.requiredActions ?? [],
115
+ });
116
+ }
117
+ }
118
+
119
+ /**
120
+ * Observe one frozen Kimi or CodeBuddy plugin payload through Foundation.
121
+ *
122
+ * @param {object} input
123
+ * @param {object} input.plan
124
+ * @param {object} input.action
125
+ * @param {string} input.root
126
+ * @param {string} input.runDir - Fresh per-action directory containing empty
127
+ * install, temporary and evidence children.
128
+ * @param {Function} [input.runPluginVerificationFn]
129
+ * @param {Function} [input.clock]
130
+ * @returns {Promise<object>} local payload-observation receipt
131
+ */
132
+ export async function verifyFrozenPluginWithFoundation({
133
+ plan,
134
+ action,
135
+ root,
136
+ runDir,
137
+ runPluginVerificationFn = runPluginVerification,
138
+ clock = () => new Date().toISOString(),
139
+ } = {}) {
140
+ const unit = (plan?.units ?? []).find((candidate) => candidate.id === action?.unitId);
141
+ if (!unit?.frozenSnapshot?.path || !unit.frozenSnapshot.commit) {
142
+ throw fail('Foundation plugin observation requires a frozen unit snapshot', {
143
+ actionId: action?.id,
144
+ unitId: action?.unitId,
145
+ });
146
+ }
147
+
148
+ if (!isFoundationPluginVerificationEligible({ action, units: plan.units })) {
149
+ throw fail('Foundation local plugin observation requires one identity-bound bundled-family payload', {
150
+ actionId: action?.id,
151
+ sourceForm: action?.parameters?.sourceDescriptor?.form ?? null,
152
+ pluginSubpath: action?.parameters?.sourceDescriptor?.pluginSubpath ?? null,
153
+ });
154
+ }
155
+
156
+ const platformId = action?.parameters?.consumer;
157
+ const platform = getPlatform(platformId);
158
+ if (!['kimi', 'codebuddy'].includes(platform.id)) {
159
+ throw fail('Foundation local plugin observation is not configured for this release platform', {
160
+ actionId: action?.id,
161
+ platform: platform.id,
162
+ });
163
+ }
164
+ const sourceDescriptor = action.parameters.sourceDescriptor;
165
+ const distribution = (unit.distributions ?? []).find(
166
+ (candidate) => candidate.type === platform.distributionType,
167
+ );
168
+ const manifestRelativePath = distribution?.installationContract?.manifestRelativePath;
169
+ if (!manifestRelativePath) {
170
+ throw fail('Foundation plugin observation requires the frozen installation manifest path', {
171
+ actionId: action.id,
172
+ distributionType: platform.distributionType,
173
+ });
174
+ }
175
+
176
+ const frozen = await verifyFrozenSnapshot({
177
+ root,
178
+ snapshotPath: unit.frozenSnapshot.path,
179
+ expectedDigest: unit.frozenSnapshot.manifestDigest,
180
+ });
181
+ const sourceRoot = frozen.snapshotDir;
182
+ const sourceMembers = projectSourceMembers(frozen.entries);
183
+ const sourceManifest = frozen.entries.find((entry) => entry.path === manifestRelativePath);
184
+ if (!sourceManifest) {
185
+ throw fail('Foundation plugin observation manifest is not part of the frozen payload', {
186
+ actionId: action.id,
187
+ manifestRelativePath,
188
+ });
189
+ }
190
+
191
+ const hostsRoot = bundledHostProfilesRoot();
192
+ const hostId = await normalizeHostId(platform.buildAdapter.name);
193
+ const descriptor = await describeHost({ hostId, hostsRoot });
194
+ const hostsBinding = await createFilesystemRootBinding(hostsRoot);
195
+ const descriptorFile = await readFileBound(hostsRoot, `${hostId}/host-descriptor.json`, {
196
+ rootBinding: hostsBinding,
197
+ });
198
+ if (!descriptor.verification?.driverId) {
199
+ throw fail('Foundation host has no complete-plugin verification driver', { hostId });
200
+ }
201
+
202
+ const request = {
203
+ schemaVersion: 1,
204
+ kind: 'skill-family.plugin-verification-request',
205
+ operation: 'plugin-verification',
206
+ verificationSetId: `${plan.digest}:${action.id}`,
207
+ goal: 'install-only',
208
+ source: {
209
+ type: 'local-staged',
210
+ candidateRef: sourceDescriptor.commit,
211
+ sourceManifestSha256: sourceManifest.contentDigest,
212
+ membersDigest: digestDocument(sourceMembers),
213
+ },
214
+ host: {
215
+ hostId,
216
+ descriptorSha256: descriptorFile.sha256,
217
+ driverId: descriptor.verification.driverId,
218
+ driverVersion: FOUNDATION_PLUGIN_DRIVER_VERSION,
219
+ },
220
+ install: { mode: 'fresh-tree' },
221
+ };
222
+ const bindings = {
223
+ sourceRoot,
224
+ sourceManifestRelPath: manifestRelativePath,
225
+ sourceMembers,
226
+ installContainerRoot: join(runDir, 'install'),
227
+ temporaryRoot: join(runDir, 'temporary'),
228
+ privateEvidenceRoot: join(runDir, 'evidence'),
229
+ };
230
+
231
+ const result = await runPluginVerificationFn({ request, bindings, hostsRoot });
232
+ assertResultMatchesRequest(result, request);
233
+
234
+ return {
235
+ planDigest: plan.digest,
236
+ actionId: action.id,
237
+ unitId: unit.id,
238
+ requestDigest: result.requestDigest,
239
+ hostId,
240
+ goal: request.goal,
241
+ sourceType: request.source.type,
242
+ foundationStatus: result.status,
243
+ payloadMatches: result.facts.install.payloadMatches,
244
+ observationDigest: result.facts.install.observationDigest,
245
+ observedAt: clock(),
246
+ };
247
+ }
@@ -376,10 +376,14 @@ export function evaluateConsumerSkillResourceClosureReceipts(plan, receipts) {
376
376
  'kimi-plugin',
377
377
  'codebuddy-plugin',
378
378
  ]);
379
- // When plan declares humanConsumersStrategy: 'manualFollowUps', Kimi/CodeBuddy
380
- // are non-blocking manual follow-up tasks and are not verified by the system.
381
- // Exclude them from the expected receipt set.
382
- const humanConsumerTypes = plan.humanConsumersStrategy === 'manualFollowUps'
379
+ // Kimi/CodeBuddy remain non-blocking manual marketplace/invocation tasks in
380
+ // both strategy generations. The newer strategy adds a Foundation frozen
381
+ // payload observation but still does not establish an installed host
382
+ // surface, so these distributions stay outside this receipt set.
383
+ const humanConsumerTypes = [
384
+ 'manualFollowUps',
385
+ 'foundationPayloadThenManualFollowUps',
386
+ ].includes(plan.humanConsumersStrategy)
383
387
  ? new Set(['kimi-plugin', 'codebuddy-plugin'])
384
388
  : new Set();
385
389
  const expectedKeys = [];
@@ -55,9 +55,9 @@ import { digestBytes, publishFileOrReplace, readFileStrict, withTemporaryWorkspa
55
55
  import { canonicalJson } from '../core/digest.mjs';
56
56
 
57
57
  export const FOUNDATION_PACKAGES = Object.freeze({
58
- 'skill-family-contracts': '0.12.0',
59
- 'skill-family-engineering-kit': '0.12.0',
60
- 'skill-family-harness-node': '0.12.0',
58
+ 'skill-family-contracts': '0.13.0',
59
+ 'skill-family-engineering-kit': '0.13.0',
60
+ 'skill-family-harness-node': '0.13.0',
61
61
  });
62
62
 
63
63
  export const BINDING_RECORD_PATH = 'bin/foundation-resource-binding.json';
@@ -0,0 +1,55 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://contracts.skill-family.example/v1/filesystem-tree-observation.json",
4
+ "title": "Filesystem tree observation",
5
+ "description": "A complete same-pass fact observation of a bound filesystem tree (FND-DES-014 section 6.1). Private data only: it is returned by the public read-only harness entry, but never enters a public plugin verification result. The record keeps the original root binding so consumers re-verify the same bound root and never re-bind a replacement. Members are canonical relative paths sorted by Unicode code point; directories carry identity/type/permission facts only; file bytes come from the same native descriptor that supplies statMode.",
6
+ "type": "object",
7
+ "additionalProperties": false,
8
+ "required": ["schemaVersion", "kind", "rootBinding", "members", "membersDigest"],
9
+ "properties": {
10
+ "schemaVersion": { "const": 1 },
11
+ "kind": { "const": "skill-family.filesystem-tree-observation" },
12
+ "rootBinding": { "$ref": "https://contracts.skill-family.example/v1/filesystem-root-binding.json" },
13
+ "members": {
14
+ "type": "array",
15
+ "uniqueItems": true,
16
+ "items": { "$ref": "#/$defs/member" }
17
+ },
18
+ "membersDigest": {
19
+ "type": "string",
20
+ "pattern": "^[0-9a-f]{64}$"
21
+ }
22
+ },
23
+ "$defs": {
24
+ "statMode": { "type": "integer", "minimum": 0, "maximum": 65535 },
25
+ "member": {
26
+ "oneOf": [
27
+ { "$ref": "#/$defs/fileMember" },
28
+ { "$ref": "#/$defs/directoryMember" }
29
+ ]
30
+ },
31
+ "fileMember": {
32
+ "type": "object",
33
+ "additionalProperties": false,
34
+ "required": ["path", "type", "sha256", "bytes", "statMode", "contentBase64"],
35
+ "properties": {
36
+ "path": { "type": "string", "minLength": 1 },
37
+ "type": { "const": "file" },
38
+ "sha256": { "type": "string", "pattern": "^[0-9a-f]{64}$" },
39
+ "bytes": { "type": "integer", "minimum": 0 },
40
+ "statMode": { "$ref": "#/$defs/statMode" },
41
+ "contentBase64": { "type": "string" }
42
+ }
43
+ },
44
+ "directoryMember": {
45
+ "type": "object",
46
+ "additionalProperties": false,
47
+ "required": ["path", "type", "statMode"],
48
+ "properties": {
49
+ "path": { "type": "string", "minLength": 1 },
50
+ "type": { "const": "directory" },
51
+ "statMode": { "$ref": "#/$defs/statMode" }
52
+ }
53
+ }
54
+ }
55
+ }
@@ -103,8 +103,8 @@
103
103
  "spawned": { "type": "boolean" },
104
104
  "exitStatus": { "type": ["integer", "null"], "minimum": 0 },
105
105
  "processStatus": { "type": ["string", "null"], "enum": ["PLANNED", "STARTING", "RUNNING", "EXITED", "FAILED_TO_START", "TIMED_OUT", "TERMINATED", null] },
106
- "terminationReason": { "$ref": "https://contracts.skill-family.example/v1/watchdog-termination-envelope.json#/properties/terminationReason" },
107
- "watchdogReason": { "$ref": "https://contracts.skill-family.example/v1/watchdog-termination-envelope.json#/properties/watchdogReason" },
106
+ "terminationReason": { "$ref": "https://contracts.skill-family.example/v1/watchdog-termination-envelope.json#/properties/terminationReason", "not": { "const": "output_limit_exceeded" } },
107
+ "watchdogReason": { "$ref": "https://contracts.skill-family.example/v1/watchdog-termination-envelope.json#/properties/watchdogReason", "not": { "const": "output_limit_exceeded" } },
108
108
  "runnerModelOverrideAbsent": { "type": "boolean" }
109
109
  }
110
110
  },