release-skill 0.2.3 → 0.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) 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 +46 -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 +266 -913
  11. package/README.zh-CN.md +224 -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 +19805 -17556
  15. package/adapters/claude/schemas/release-plan.schema.json +228 -0
  16. package/adapters/claude/schemas/release-project.schema.json +93 -0
  17. package/adapters/claude/schemas/release-run.schema.json +155 -2
  18. package/adapters/codex/.codex-plugin/plugin.json +2 -2
  19. package/adapters/codex/bin/release-skill.bundle.mjs +19805 -17556
  20. package/adapters/codex/schemas/release-plan.schema.json +228 -0
  21. package/adapters/codex/schemas/release-project.schema.json +93 -0
  22. package/adapters/codex/schemas/release-run.schema.json +155 -2
  23. package/adapters/kimi/.kimi-plugin/plugin.json +1 -1
  24. package/adapters/kimi/bin/release-skill.bundle.mjs +19805 -17556
  25. package/adapters/kimi/schemas/release-plan.schema.json +228 -0
  26. package/adapters/kimi/schemas/release-project.schema.json +93 -0
  27. package/adapters/kimi/schemas/release-run.schema.json +155 -2
  28. package/adapters/workbuddy/.codebuddy-plugin/plugin.json +1 -1
  29. package/adapters/workbuddy/bin/release-skill.bundle.mjs +19805 -17556
  30. package/adapters/workbuddy/schemas/release-plan.schema.json +228 -0
  31. package/adapters/workbuddy/schemas/release-project.schema.json +93 -0
  32. package/adapters/workbuddy/schemas/release-run.schema.json +155 -2
  33. package/bin/release-skill.bundle.mjs +19805 -17556
  34. package/package.json +1 -1
  35. package/schemas/release-plan.schema.json +228 -0
  36. package/schemas/release-project.schema.json +93 -0
  37. package/schemas/release-run.schema.json +155 -2
  38. package/scripts/sync-public-files.mjs +20 -9
  39. package/src/adapters/plugin-marketplace.mjs +1237 -403
  40. package/src/commands/prepare.mjs +454 -40
  41. package/src/commands/publish.mjs +169 -75
  42. package/src/commands/reconcile.mjs +92 -327
  43. package/src/commands/setup.mjs +148 -20
  44. package/src/commands/verify.mjs +454 -20
  45. package/src/core/baseline.mjs +8 -1
  46. package/src/core/checkpoints.mjs +50 -7
  47. package/src/core/config.mjs +15 -0
  48. package/src/core/errors.mjs +2 -0
  49. package/src/core/installation-contract.mjs +341 -0
  50. package/src/core/plan.mjs +158 -6
  51. package/src/core/skill-resource-closure.mjs +425 -0
  52. package/src/platforms/codebuddy.mjs +206 -280
  53. package/src/platforms/codex.mjs +369 -0
  54. package/src/platforms/kimi.mjs +191 -119
  55. package/src/platforms/registry.mjs +24 -6
@@ -39,7 +39,7 @@ import {
39
39
  reObservePreviousPublicBaseline,
40
40
  } from '../core/previous-public-baseline.mjs';
41
41
  import { createEvidenceWriter } from '../core/evidence.mjs';
42
- import { CHECKPOINT_ORDER, ADAPTER_ACTION_TYPE_MAP } from '../core/checkpoints.mjs';
42
+ import { CHECKPOINT_ORDER, ADAPTER_ACTION_TYPE_MAP, isRemoteWriteAction, isMarketplaceAction } from '../core/checkpoints.mjs';
43
43
  import {
44
44
  loadRun,
45
45
  validateRunPlanDigest,
@@ -56,7 +56,7 @@ import {
56
56
  GATE_FAILED,
57
57
  BASELINE_CHANGED,
58
58
  REMOTE_CONFLICT,
59
- POST_PUBLISH_VERIFY_FAILED,
59
+ CONSUMER_VERIFICATION_DEFERRED,
60
60
  } from '../core/errors.mjs';
61
61
  import { assertTransition, PARTIAL, PUBLISHED, BLOCKED } from '../core/state-machine.mjs';
62
62
  import { matchObservation } from '../adapters/contract.mjs';
@@ -70,18 +70,6 @@ import { observeWithRetry, clampPolicyToTimeout, DEFAULT_OBSERVE_RETRY_POLICY }
70
70
  // (single source shared with publish.mjs; T3.1 §4.7). The historical
71
71
  // `Must match publish.mjs` double-write is removed.
72
72
 
73
- /**
74
- * Marketplace action types that are reconstructable isolated consumer checks,
75
- * not permanent remote writes. These use isolatedConsumerWritesAuthorized
76
- * and don't require externalWritesAuthorized even during retry.
77
- */
78
- const MARKETPLACE_TYPES = new Set([
79
- 'claude-marketplace-install',
80
- 'codex-marketplace-install',
81
- 'kimi-marketplace-install',
82
- 'codebuddy-marketplace-install',
83
- ]);
84
-
85
73
  // ---------------------------------------------------------------------------
86
74
  // Helpers
87
75
  // ---------------------------------------------------------------------------
@@ -301,7 +289,7 @@ export async function reconcileRelease(options) {
301
289
  const hasNonMarketplaceRetryCandidate = sourceRun.checkpoints.some((checkpoint) => {
302
290
  if (checkpoint.status === 'succeeded') return false;
303
291
  const action = planActionsById.get(checkpoint.actionId);
304
- return action && !MARKETPLACE_TYPES.has(action.type);
292
+ return action && !isMarketplaceAction(action.type);
305
293
  });
306
294
  if (
307
295
  plan.production?.mode === 'github-npm-v1' &&
@@ -407,7 +395,6 @@ export async function reconcileRelease(options) {
407
395
 
408
396
  // Re-observe every unit's frozen public baseline before any adapter
409
397
  // observation, preflight, or execute call.
410
- const isProductionPlan = plan.production?.mode === 'github-npm-v1';
411
398
  const defaultPpbObserveFn = async (repo, ref, expectedCommit, { githubHost = 'github.com' } = {}) => {
412
399
  try {
413
400
  const { execFile } = await import('node:child_process');
@@ -574,13 +561,8 @@ export async function reconcileRelease(options) {
574
561
  // Non-marketplace actions use observe-based consistency checks.
575
562
  // =======================================================================
576
563
  const actionsToRetry = [];
577
- const marketplaceRetriesToRetry = [];
578
564
  const actionResults = new Map(); // actionId -> final status
579
- // Remote-write retries and marketplace recovery are independent failure
580
- // groups: neither group's failure blocks the other group's retries, and
581
- // either failure keeps the run PARTIAL.
582
565
  let remoteWriteRetryFailed = false;
583
- let marketplaceRetryFailed = false;
584
566
 
585
567
  for (const action of planActions) {
586
568
  const sourceCp = sourceCpMap.get(action.id);
@@ -595,11 +577,13 @@ export async function reconcileRelease(options) {
595
577
  const adapter = adapterRegistry.getAdapter(adapterActionType);
596
578
 
597
579
  // -------------------------------------------------------------------
598
- // Marketplace actions: deferred to the dedicated recovery phase that
599
- // runs AFTER the non-marketplace remote-write retry loop (phase 4).
580
+ // Marketplace actions are deferred to the verify phase. They are
581
+ // recorded as 'deferred' with CONSUMER_VERIFICATION_DEFERRED reason
582
+ // and never participate in reconcile's observe/retry loop.
600
583
  // A marketplace failure must never block pending remote-write retries.
601
584
  // -------------------------------------------------------------------
602
- if (MARKETPLACE_TYPES.has(action.type)) {
585
+ if (isMarketplaceAction(action.type)) {
586
+ actionResults.set(action.id, 'deferred');
603
587
  continue;
604
588
  }
605
589
 
@@ -728,7 +712,7 @@ export async function reconcileRelease(options) {
728
712
 
729
713
  // FAILED or PENDING: observe remote state (PROPAGATING retry, T1.1)
730
714
  const observeInput = { actionType: adapterActionType, ...action.parameters };
731
- const observePolicy = MARKETPLACE_TYPES.has(action.type)
715
+ const observePolicy = isMarketplaceAction(action.type)
732
716
  ? clampPolicyToTimeout(DEFAULT_OBSERVE_RETRY_POLICY, action.parameters?.timeoutMs)
733
717
  : DEFAULT_OBSERVE_RETRY_POLICY;
734
718
  const observeRetry = await observeWithRetry({
@@ -887,7 +871,7 @@ export async function reconcileRelease(options) {
887
871
  // retries require approval.
888
872
  // =======================================================================
889
873
  // Phase 1 only collects non-marketplace retry candidates.
890
- const nonMarketplaceRetries = actionsToRetry.filter((a) => !MARKETPLACE_TYPES.has(a.type));
874
+ const nonMarketplaceRetries = actionsToRetry.filter((a) => !isMarketplaceAction(a.type));
891
875
 
892
876
  if (nonMarketplaceRetries.length > 0) {
893
877
  // Non-marketplace retries require approval and productionConfirmation
@@ -926,19 +910,13 @@ export async function reconcileRelease(options) {
926
910
  const adapterActionType = ADAPTER_ACTION_TYPE_MAP[action.type];
927
911
  const adapter = adapterRegistry.getAdapter(adapterActionType);
928
912
 
929
- // Use appropriate context for each action type
930
- const preflightCtx = MARKETPLACE_TYPES.has(action.type)
931
- ? { ...context, externalWritesAuthorized: false, isolatedConsumerWritesAuthorized: true }
932
- : context;
933
-
934
913
  const preflightResult = await adapter.preflight(
935
914
  { actionType: adapterActionType, ...action.parameters },
936
- preflightCtx,
915
+ context,
937
916
  );
938
917
 
939
918
  if (preflightResult.status === 'PREFLIGHT_FAILED') {
940
- // Mark all retry actions as failed, stop. This only poisons the
941
- // remote-write group; marketplace recovery (phase 4) still runs.
919
+ // Mark all retry actions as failed, stop.
942
920
  for (const retryAction of actionsToRetry) {
943
921
  actionResults.set(retryAction.id, 'failed');
944
922
  }
@@ -980,14 +958,17 @@ export async function reconcileRelease(options) {
980
958
  status,
981
959
  checkpoints: planActions.map((action) => {
982
960
  const value = actionResults.get(action.id);
961
+ const normalized = value === 'deferred' ? 'deferred'
962
+ : value === 'succeeded' ? 'succeeded'
963
+ : value === 'skipped' ? 'skipped'
964
+ : value === 'failed' ? 'failed'
965
+ : value === 'uncertain' ? 'uncertain'
966
+ : 'pending';
983
967
  return {
984
968
  actionId: action.id,
985
969
  actionType: action.type,
986
- status: value === 'succeeded' ? 'succeeded'
987
- : value === 'skipped' ? 'skipped'
988
- : value === 'failed' ? 'failed'
989
- : value === 'uncertain' ? 'uncertain'
990
- : 'pending',
970
+ status: normalized,
971
+ ...(normalized === 'deferred' ? { reason: CONSUMER_VERIFICATION_DEFERRED, phase: 'post-publish-verification' } : {}),
991
972
  };
992
973
  }),
993
974
  startedAt: reconcileStartedAt,
@@ -1015,11 +996,9 @@ export async function reconcileRelease(options) {
1015
996
  const adapterActionType = ADAPTER_ACTION_TYPE_MAP[action.type];
1016
997
  const adapter = adapterRegistry.getAdapter(adapterActionType);
1017
998
 
1018
- // Use appropriate context: marketplace uses isolated consumer writes,
1019
- // non-marketplace uses external writes
1020
- const retryCtx = MARKETPLACE_TYPES.has(action.type)
1021
- ? { ...context, externalWritesAuthorized: false, isolatedConsumerWritesAuthorized: true }
1022
- : context;
999
+ // Only non-marketplace actions enter executeRetryGroup; marketplace
1000
+ // actions are deferred in Phase 1 and never reach this path.
1001
+ const retryCtx = context;
1023
1002
 
1024
1003
  await evidence.append({
1025
1004
  phase: 'reconcile-retry',
@@ -1045,60 +1024,12 @@ export async function reconcileRelease(options) {
1045
1024
  }
1046
1025
 
1047
1026
  if (executeResult.status === 'EXECUTED') {
1048
- // Must verify after execute to check remote/install state
1049
- if (MARKETPLACE_TYPES.has(action.type)) {
1050
- // Marketplace: verify via adapter.verify (observe + matchObservation).
1051
- // Retry the read-only verify while remote state is information-
1052
- // insufficient (PROPAGATING, T1.1); a present mismatch is a
1053
- // real conflict and is never retried.
1054
- const verifyInput = {
1055
- actionType: adapterActionType,
1056
- ...action.parameters,
1057
- expected: action.expected,
1058
- };
1059
- const verifyPolicy = clampPolicyToTimeout(
1060
- DEFAULT_OBSERVE_RETRY_POLICY,
1061
- action.parameters?.timeoutMs,
1062
- );
1063
- const verifyRetry = await observeWithRetry({
1064
- observe: (act, ctx) => adapter.verify(act, ctx),
1065
- action: verifyInput,
1066
- context: retryCtx,
1067
- policy: verifyPolicy,
1068
- sleep: observeRetrySleep,
1069
- onAttempt: (info) => evidence.append({
1070
- phase: 'reconcile-retry-verify',
1071
- actionId: action.id,
1072
- actionType: action.type,
1073
- attempt: info.attempt,
1074
- maxAttempts: info.maxAttempts,
1075
- missing: info.missing,
1076
- delayMs: info.delayMs,
1077
- status: info.missing ? 'propagating' : 'resolved',
1078
- error: info.error ?? null,
1079
- }),
1080
- });
1081
- const verifyResult = verifyRetry.result;
1082
-
1083
- if (verifyResult.status !== 'VERIFIED') {
1084
- actionResults.set(action.id, 'failed');
1085
- markGroupFailed();
1086
- await evidence.append({
1087
- phase: 'reconcile-retry',
1088
- actionId: action.id,
1089
- actionType: action.type,
1090
- status: 'verify-mismatch',
1091
- error: verifyResult.error,
1092
- });
1093
- await persistRetryState(PARTIAL);
1094
- break;
1095
- }
1096
- } else {
1097
- // Non-marketplace: observe after execute to verify remote state.
1098
- // execute succeeded, so a missing/uncertain observe may be a
1099
- // transient propagation delay — retry with bounded backoff
1100
- // (PROPAGATING, T1.1). A present mismatch is a real
1101
- // conflict and is never retried.
1027
+ // Non-marketplace only: observe after execute to verify remote state.
1028
+ // execute succeeded, so a missing/uncertain observe may be a
1029
+ // transient propagation delay retry with bounded backoff
1030
+ // (PROPAGATING, T1.1). A present mismatch is a real
1031
+ // conflict and is never retried.
1032
+ {
1102
1033
  const observeInput = {
1103
1034
  actionType: adapterActionType,
1104
1035
  ...action.parameters,
@@ -1186,39 +1117,35 @@ export async function reconcileRelease(options) {
1186
1117
  // write. Non-marketplace actions must therefore be observed before
1187
1118
  // classifying the checkpoint; unknown remains uncertain and an
1188
1119
  // already-consistent remote is treated as succeeded.
1189
- if (!MARKETPLACE_TYPES.has(action.type)) {
1190
- let observeResult;
1191
- try {
1192
- observeResult = await adapter.observe(
1193
- { actionType: adapterActionType, ...action.parameters, expected: action.expected },
1194
- retryCtx,
1195
- );
1196
- } catch (error) {
1197
- observeResult = { observation: null, error: error.message };
1198
- }
1199
- const observation = observeResult?.observation;
1200
- const missing = observation?.exists === false
1201
- || observation?.remoteCommit === ''
1202
- || observation?.commit === ''
1203
- || observation?.published === false;
1204
- const matches = action.expected && observation
1205
- ? matchObservation(action.expected, observation).matches
1206
- : false;
1207
- if (matches) {
1208
- actionResults.set(action.id, 'succeeded');
1209
- await evidence.append({
1210
- phase: 'reconcile-retry',
1211
- actionId: action.id,
1212
- actionType: action.type,
1213
- status: 'completed-after-execute-failure-observe',
1214
- });
1215
- await persistRetryState(PARTIAL);
1216
- continue;
1217
- }
1218
- actionResults.set(action.id, missing ? 'failed' : 'uncertain');
1219
- } else {
1220
- actionResults.set(action.id, 'failed');
1120
+ let observeResult;
1121
+ try {
1122
+ observeResult = await adapter.observe(
1123
+ { actionType: adapterActionType, ...action.parameters, expected: action.expected },
1124
+ retryCtx,
1125
+ );
1126
+ } catch (error) {
1127
+ observeResult = { observation: null, error: error.message };
1221
1128
  }
1129
+ const observation = observeResult?.observation;
1130
+ const missing = observation?.exists === false
1131
+ || observation?.remoteCommit === ''
1132
+ || observation?.commit === ''
1133
+ || observation?.published === false;
1134
+ const matches = action.expected && observation
1135
+ ? matchObservation(action.expected, observation).matches
1136
+ : false;
1137
+ if (matches) {
1138
+ actionResults.set(action.id, 'succeeded');
1139
+ await evidence.append({
1140
+ phase: 'reconcile-retry',
1141
+ actionId: action.id,
1142
+ actionType: action.type,
1143
+ status: 'completed-after-execute-failure-observe',
1144
+ });
1145
+ await persistRetryState(PARTIAL);
1146
+ continue;
1147
+ }
1148
+ actionResults.set(action.id, missing ? 'failed' : 'uncertain');
1222
1149
  markGroupFailed();
1223
1150
 
1224
1151
  await evidence.append({
@@ -1244,174 +1171,6 @@ export async function reconcileRelease(options) {
1244
1171
  await executeRetryGroup(actionsToRetry, () => { remoteWriteRetryFailed = true; });
1245
1172
  }
1246
1173
 
1247
- // --- Phase 4: Marketplace recovery (isolated consumer checks) ---
1248
- //
1249
- // Deferred until after the remote-write retry loop. Marketplace actions
1250
- // are reconstructable isolated consumer checks, not permanent remote
1251
- // writes: each gets a fresh preflight -> execute -> verify cycle in an
1252
- // isolated directory, using isolatedConsumerWritesAuthorized instead of
1253
- // externalWritesAuthorized. A failed remote-write retry must not
1254
- // fabricate or suppress marketplace outcomes, and a marketplace failure
1255
- // never blocks the remote-write group above.
1256
- for (const action of planActions) {
1257
- if (!MARKETPLACE_TYPES.has(action.type)) continue;
1258
- const sourceCp = sourceCpMap.get(action.id);
1259
- const adapterActionType = ADAPTER_ACTION_TYPE_MAP[action.type];
1260
- const adapter = adapterRegistry.getAdapter(adapterActionType);
1261
-
1262
- const marketplaceContext = {
1263
- externalWritesAuthorized: false,
1264
- isolatedConsumerWritesAuthorized: true,
1265
- plan,
1266
- baseline: plan.baseline,
1267
- root,
1268
- runDir,
1269
- };
1270
-
1271
- const actionInput = {
1272
- actionType: adapterActionType,
1273
- ...action.parameters,
1274
- };
1275
-
1276
- // Preflight (always runs — validates frozen snapshot)
1277
- const preflightResult = await adapter.preflight(actionInput, marketplaceContext);
1278
- if (preflightResult.status === 'PREFLIGHT_FAILED') {
1279
- actionResults.set(action.id, 'failed');
1280
- await evidence.append({
1281
- phase: 'reconcile-marketplace',
1282
- actionId: action.id,
1283
- actionType: action.type,
1284
- decision: 'preflight-failed',
1285
- error: preflightResult.error,
1286
- });
1287
- throw new ReleaseError(
1288
- POST_PUBLISH_VERIFY_FAILED,
1289
- `marketplace preflight failed for action "${action.id}": ${preflightResult.error}`,
1290
- { actionId: action.id },
1291
- );
1292
- }
1293
-
1294
- // Execute (install to isolated consumer directory)
1295
- const executeResult = await adapter.execute(actionInput, marketplaceContext);
1296
- if (executeResult.status !== 'EXECUTED') {
1297
- actionResults.set(action.id, 'failed');
1298
- await evidence.append({
1299
- phase: 'reconcile-marketplace',
1300
- actionId: action.id,
1301
- actionType: action.type,
1302
- decision: 'execute-failed',
1303
- error: executeResult.error,
1304
- });
1305
- // Execute failed — if source was succeeded this is a conflict;
1306
- // if source was failed/pending, add to the marketplace retry list
1307
- if (sourceCp.status === 'succeeded') {
1308
- throw new ReleaseError(
1309
- REMOTE_CONFLICT,
1310
- `marketplace execute failed for SUCCEEDED action "${action.id}": ${executeResult.error}`,
1311
- { actionId: action.id },
1312
- );
1313
- }
1314
- marketplaceRetriesToRetry.push(action);
1315
- continue;
1316
- }
1317
-
1318
- // Verify (observe + match against plan expected state).
1319
- // Retry the read-only verify while remote state is information-
1320
- // insufficient (PROPAGATING, T1.1); a present mismatch is a
1321
- // real conflict and is never retried.
1322
- const verifyPolicy = clampPolicyToTimeout(
1323
- DEFAULT_OBSERVE_RETRY_POLICY,
1324
- action.parameters?.timeoutMs,
1325
- );
1326
- const verifyRetry = await observeWithRetry({
1327
- observe: (act, ctx) => adapter.verify(act, ctx),
1328
- action: { ...actionInput, expected: action.expected },
1329
- context: marketplaceContext,
1330
- policy: verifyPolicy,
1331
- sleep: observeRetrySleep,
1332
- onAttempt: (info) => evidence.append({
1333
- phase: 'reconcile-marketplace-verify',
1334
- actionId: action.id,
1335
- actionType: action.type,
1336
- attempt: info.attempt,
1337
- maxAttempts: info.maxAttempts,
1338
- missing: info.missing,
1339
- delayMs: info.delayMs,
1340
- status: info.missing ? 'propagating' : 'resolved',
1341
- error: info.error ?? null,
1342
- }),
1343
- });
1344
- const verifyResult = verifyRetry.result;
1345
-
1346
- if (verifyResult.status === 'VERIFIED') {
1347
- // Consistent: source succeeded => skipped, source failed => succeeded (recovered)
1348
- const resultStatus = sourceCp.status === 'succeeded' ? 'skipped' : 'succeeded';
1349
- actionResults.set(action.id, resultStatus);
1350
- await evidence.append({
1351
- phase: 'reconcile-marketplace',
1352
- actionId: action.id,
1353
- actionType: action.type,
1354
- decision: sourceCp.status === 'succeeded' ? 'skip-source-succeeded-consistent' : 'recovered',
1355
- sourceStatus: sourceCp.status,
1356
- });
1357
- } else {
1358
- // Verify mismatch
1359
- await evidence.append({
1360
- phase: 'reconcile-marketplace',
1361
- actionId: action.id,
1362
- actionType: action.type,
1363
- decision: 'verify-mismatch',
1364
- error: verifyResult.error,
1365
- });
1366
- if (sourceCp.status === 'succeeded') {
1367
- throw new ReleaseError(
1368
- REMOTE_CONFLICT,
1369
- `marketplace verify mismatch for SUCCEEDED action "${action.id}": ${verifyResult.error}`,
1370
- { actionId: action.id },
1371
- );
1372
- }
1373
- // Source was failed/pending and verify mismatched after recovery => failed.
1374
- // Poisons ONLY the marketplace group; remote-write retries already ran.
1375
- actionResults.set(action.id, 'failed');
1376
- marketplaceRetryFailed = true;
1377
- // Don't add to retry — this is a verify mismatch, not a missing state
1378
- }
1379
- }
1380
-
1381
- // Marketplace retry candidates (isolated execute failed above) are
1382
- // preflighted, then retried once through the shared journaled retry
1383
- // loop. Gated ONLY on the marketplace group's own failure flag.
1384
- if (!marketplaceRetryFailed && marketplaceRetriesToRetry.length > 0) {
1385
- for (const action of marketplaceRetriesToRetry) {
1386
- const adapterActionType = ADAPTER_ACTION_TYPE_MAP[action.type];
1387
- const adapter = adapterRegistry.getAdapter(adapterActionType);
1388
- const preflightResult = await adapter.preflight(
1389
- { actionType: adapterActionType, ...action.parameters },
1390
- { ...context, externalWritesAuthorized: false, isolatedConsumerWritesAuthorized: true },
1391
- );
1392
- if (preflightResult.status === 'PREFLIGHT_FAILED') {
1393
- // Mark all marketplace retry actions as failed, stop
1394
- for (const retryAction of marketplaceRetriesToRetry) {
1395
- actionResults.set(retryAction.id, 'failed');
1396
- }
1397
- marketplaceRetryFailed = true;
1398
- await evidence.append({
1399
- phase: 'reconcile-preflight',
1400
- status: 'failed',
1401
- actionId: action.id,
1402
- error: preflightResult.error,
1403
- });
1404
- break;
1405
- }
1406
- }
1407
- }
1408
- if (!marketplaceRetryFailed && marketplaceRetriesToRetry.length > 0) {
1409
- if (retryStateSequence < 0) {
1410
- await persistRetryState('PUBLISHING');
1411
- }
1412
- await executeRetryGroup(marketplaceRetriesToRetry, () => { marketplaceRetryFailed = true; });
1413
- }
1414
-
1415
1174
  // Mark remaining retry actions as pending if a group stopped early
1416
1175
  const markPendingAfterFailure = (groupActions) => {
1417
1176
  let foundFailed = false;
@@ -1428,14 +1187,11 @@ export async function reconcileRelease(options) {
1428
1187
  if (remoteWriteRetryFailed) {
1429
1188
  markPendingAfterFailure(actionsToRetry);
1430
1189
  }
1431
- if (marketplaceRetryFailed) {
1432
- markPendingAfterFailure(marketplaceRetriesToRetry);
1433
- }
1434
1190
 
1435
1191
  // Final branch/default-branch consistency closes late drift after a retry
1436
1192
  // or after the first observation pass. The set-default action expectation
1437
1193
  // includes both the branch name and its exact planned commit.
1438
- if (!remoteWriteRetryFailed && !marketplaceRetryFailed && planActions.every((action) => ['succeeded', 'skipped'].includes(actionResults.get(action.id)))) {
1194
+ if (!remoteWriteRetryFailed && planActions.filter((a) => isRemoteWriteAction(a.type)).every((action) => ['succeeded', 'skipped'].includes(actionResults.get(action.id)))) {
1439
1195
  await evidence.append({ phase: 'safety-gate', gate: 'final-branch-consistency', status: 'started' });
1440
1196
  for (const action of planActions) {
1441
1197
  if (!['push-snapshot', 'set-default-branch'].includes(action.type)) continue;
@@ -1474,30 +1230,33 @@ export async function reconcileRelease(options) {
1474
1230
  // =======================================================================
1475
1231
  // Determine final status
1476
1232
  // =======================================================================
1477
- const allSucceeded = planActions.every(
1478
- (a) => {
1233
+ // 远端写入动作的状态决定整体发布状态
1234
+ // 市场安装动作的结果记录在 run 中,但不阻止 PUBLISHED
1235
+ const remoteWriteAllSucceeded = planActions
1236
+ .filter((a) => isRemoteWriteAction(a.type))
1237
+ .every((a) => {
1479
1238
  const result = actionResults.get(a.id);
1480
1239
  return result === 'succeeded' || result === 'skipped';
1481
- },
1482
- );
1240
+ });
1483
1241
 
1484
- const effectiveFromStatus = PARTIAL;
1242
+ const remoteWriteFailed = planActions
1243
+ .filter((a) => isRemoteWriteAction(a.type))
1244
+ .some((a) => {
1245
+ const result = actionResults.get(a.id);
1246
+ return result === 'failed' || result === 'uncertain';
1247
+ });
1485
1248
 
1486
- // A failure in either independent retry group keeps the run PARTIAL.
1487
- const retryFailed = remoteWriteRetryFailed || marketplaceRetryFailed;
1249
+ const effectiveFromStatus = PARTIAL;
1488
1250
 
1489
- // Map reconcile outcome to state machine target
1251
+ // 远端写入动作的状态决定 PUBLISHED
1252
+ // 市场安装动作的失败不阻止 PUBLISHED
1490
1253
  let overallStatus;
1491
- if (allSucceeded && !retryFailed) {
1254
+ if (remoteWriteAllSucceeded && !remoteWriteRetryFailed) {
1492
1255
  overallStatus = PUBLISHED;
1493
- } else if (retryFailed) {
1494
- // Once any retry execute was attempted, recovery must remain PARTIAL
1495
- // even when no success was observed. BLOCKED is only pre-execute.
1256
+ } else if (remoteWriteFailed || remoteWriteRetryFailed) {
1496
1257
  overallStatus = PARTIAL;
1497
1258
  } else {
1498
- // All remote checkpoints are consistent, but installation smoke still
1499
- // belongs to the separate verify command.
1500
- overallStatus = PUBLISHED;
1259
+ overallStatus = PARTIAL;
1501
1260
  }
1502
1261
 
1503
1262
  // Only validate state transition if status actually changes
@@ -1515,13 +1274,16 @@ export async function reconcileRelease(options) {
1515
1274
  // Build checkpoints for return value and run file
1516
1275
  const resultCheckpoints = planActions.map((a) => {
1517
1276
  const status = actionResults.get(a.id) ?? 'pending';
1277
+ const normalized = status === 'deferred' ? 'deferred'
1278
+ : status === 'succeeded' ? 'succeeded'
1279
+ : status === 'failed' ? 'failed'
1280
+ : status === 'skipped' ? 'skipped'
1281
+ : status === 'uncertain' ? 'uncertain'
1282
+ : 'pending';
1518
1283
  return {
1519
1284
  actionId: a.id,
1520
- status: status === 'succeeded' ? 'succeeded'
1521
- : status === 'failed' ? 'failed'
1522
- : status === 'skipped' ? 'skipped'
1523
- : status === 'uncertain' ? 'uncertain'
1524
- : 'pending',
1285
+ status: normalized,
1286
+ ...(normalized === 'deferred' ? { reason: CONSUMER_VERIFICATION_DEFERRED, phase: 'post-publish-verification' } : {}),
1525
1287
  };
1526
1288
  });
1527
1289
 
@@ -1543,14 +1305,17 @@ export async function reconcileRelease(options) {
1543
1305
  status: overallStatus,
1544
1306
  checkpoints: planActions.map((a) => {
1545
1307
  const status = actionResults.get(a.id) ?? 'pending';
1308
+ const normalized = status === 'deferred' ? 'deferred'
1309
+ : status === 'succeeded' ? 'succeeded'
1310
+ : status === 'failed' ? 'failed'
1311
+ : status === 'skipped' ? 'skipped'
1312
+ : status === 'uncertain' ? 'uncertain'
1313
+ : 'pending';
1546
1314
  return {
1547
1315
  actionId: a.id,
1548
1316
  actionType: a.type,
1549
- status: status === 'succeeded' ? 'succeeded'
1550
- : status === 'failed' ? 'failed'
1551
- : status === 'skipped' ? 'skipped'
1552
- : status === 'uncertain' ? 'uncertain'
1553
- : 'pending',
1317
+ status: normalized,
1318
+ ...(normalized === 'deferred' ? { reason: CONSUMER_VERIFICATION_DEFERRED, phase: 'post-publish-verification' } : {}),
1554
1319
  };
1555
1320
  }),
1556
1321
  startedAt: reconcileStartedAt,