release-skill 0.2.3 → 0.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codebuddy-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +2 -2
- package/.kimi-plugin/plugin.json +1 -1
- package/CHANGELOG.md +46 -0
- package/CONTRIBUTING.md +27 -0
- package/INSTALL.md +95 -139
- package/INSTALL.zh-CN.md +70 -121
- package/README.md +266 -913
- package/README.zh-CN.md +224 -535
- package/adapters/claude/.claude-plugin/marketplace.json +1 -1
- package/adapters/claude/.claude-plugin/plugin.json +1 -1
- package/adapters/claude/bin/release-skill.bundle.mjs +19805 -17556
- package/adapters/claude/schemas/release-plan.schema.json +228 -0
- package/adapters/claude/schemas/release-project.schema.json +93 -0
- package/adapters/claude/schemas/release-run.schema.json +155 -2
- package/adapters/codex/.codex-plugin/plugin.json +2 -2
- package/adapters/codex/bin/release-skill.bundle.mjs +19805 -17556
- package/adapters/codex/schemas/release-plan.schema.json +228 -0
- package/adapters/codex/schemas/release-project.schema.json +93 -0
- package/adapters/codex/schemas/release-run.schema.json +155 -2
- package/adapters/kimi/.kimi-plugin/plugin.json +1 -1
- package/adapters/kimi/bin/release-skill.bundle.mjs +19805 -17556
- package/adapters/kimi/schemas/release-plan.schema.json +228 -0
- package/adapters/kimi/schemas/release-project.schema.json +93 -0
- package/adapters/kimi/schemas/release-run.schema.json +155 -2
- package/adapters/workbuddy/.codebuddy-plugin/plugin.json +1 -1
- package/adapters/workbuddy/bin/release-skill.bundle.mjs +19805 -17556
- package/adapters/workbuddy/schemas/release-plan.schema.json +228 -0
- package/adapters/workbuddy/schemas/release-project.schema.json +93 -0
- package/adapters/workbuddy/schemas/release-run.schema.json +155 -2
- package/bin/release-skill.bundle.mjs +19805 -17556
- package/package.json +1 -1
- package/schemas/release-plan.schema.json +228 -0
- package/schemas/release-project.schema.json +93 -0
- package/schemas/release-run.schema.json +155 -2
- package/scripts/sync-public-files.mjs +20 -9
- package/src/adapters/plugin-marketplace.mjs +1237 -403
- package/src/commands/prepare.mjs +454 -40
- package/src/commands/publish.mjs +169 -75
- package/src/commands/reconcile.mjs +92 -327
- package/src/commands/setup.mjs +148 -20
- package/src/commands/verify.mjs +454 -20
- package/src/core/baseline.mjs +8 -1
- package/src/core/checkpoints.mjs +50 -7
- package/src/core/config.mjs +15 -0
- package/src/core/errors.mjs +2 -0
- package/src/core/installation-contract.mjs +341 -0
- package/src/core/plan.mjs +158 -6
- package/src/core/skill-resource-closure.mjs +425 -0
- package/src/platforms/codebuddy.mjs +206 -280
- package/src/platforms/codex.mjs +369 -0
- package/src/platforms/kimi.mjs +191 -119
- package/src/platforms/registry.mjs +24 -6
package/src/commands/publish.mjs
CHANGED
|
@@ -41,11 +41,19 @@ import {
|
|
|
41
41
|
reObservePreviousPublicBaseline,
|
|
42
42
|
} from '../core/previous-public-baseline.mjs';
|
|
43
43
|
import { createEvidenceWriter } from '../core/evidence.mjs';
|
|
44
|
+
import {
|
|
45
|
+
CHECKER_VERSION as SKILL_RESOURCE_CHECKER_VERSION,
|
|
46
|
+
assertSkillResourceClosureReceipt,
|
|
47
|
+
checkSkillResourceClosure,
|
|
48
|
+
createSkillResourceClosureReceipt,
|
|
49
|
+
} from '../core/skill-resource-closure.mjs';
|
|
44
50
|
import {
|
|
45
51
|
ADAPTER_ACTION_TYPE_MAP,
|
|
46
52
|
TIER_TABLE,
|
|
47
53
|
groupActionsByTier,
|
|
48
54
|
sortActionsByCheckpointOrder,
|
|
55
|
+
isRemoteWriteAction,
|
|
56
|
+
isMarketplaceAction,
|
|
49
57
|
} from '../core/checkpoints.mjs';
|
|
50
58
|
import { appendRunState, createProductionRunDir, writeRunAtomic, resolveDefaultRunDir } from '../core/run.mjs';
|
|
51
59
|
import {
|
|
@@ -53,6 +61,7 @@ import {
|
|
|
53
61
|
GATE_FAILED,
|
|
54
62
|
BASELINE_CHANGED,
|
|
55
63
|
PARTIAL_RELEASE,
|
|
64
|
+
CONSUMER_VERIFICATION_DEFERRED,
|
|
56
65
|
} from '../core/errors.mjs';
|
|
57
66
|
import { assertTransition, PUBLISHING, PUBLISHED, PARTIAL } from '../core/state-machine.mjs';
|
|
58
67
|
import { matchObservation } from '../adapters/contract.mjs';
|
|
@@ -81,13 +90,6 @@ const ACTION_NOT_ALLOWED = 'ACTION_NOT_ALLOWED';
|
|
|
81
90
|
// CHECKPOINT_ORDER, ADAPTER_ACTION_TYPE_MAP and TIER_TABLE live in
|
|
82
91
|
// ../core/checkpoints.mjs (single source shared with reconcile.mjs; T3.1 §4.7).
|
|
83
92
|
|
|
84
|
-
const MARKETPLACE_TYPES = new Set([
|
|
85
|
-
'claude-marketplace-install',
|
|
86
|
-
'codex-marketplace-install',
|
|
87
|
-
'kimi-marketplace-install',
|
|
88
|
-
'codebuddy-marketplace-install',
|
|
89
|
-
]);
|
|
90
|
-
|
|
91
93
|
// ---------------------------------------------------------------------------
|
|
92
94
|
// Helpers
|
|
93
95
|
// ---------------------------------------------------------------------------
|
|
@@ -359,7 +361,7 @@ async function executeCheckpoint(action, adapterRegistry, context) {
|
|
|
359
361
|
// (T1.1 PROPAGATING handling). A present-but-mismatched
|
|
360
362
|
// observation (CONFLICTING) is never retried — it is an
|
|
361
363
|
// authoritative conflict that must fail closed for human review.
|
|
362
|
-
const observePolicy =
|
|
364
|
+
const observePolicy = isMarketplaceAction(planActionType)
|
|
363
365
|
? clampPolicyToTimeout(DEFAULT_OBSERVE_RETRY_POLICY, action.parameters?.timeoutMs)
|
|
364
366
|
: DEFAULT_OBSERVE_RETRY_POLICY;
|
|
365
367
|
const retryOutcome = await observeWithRetry({
|
|
@@ -525,6 +527,7 @@ export async function publishRelease(options) {
|
|
|
525
527
|
if (productionMode && !isProductionPlan) {
|
|
526
528
|
throw new ReleaseError(GATE_FAILED, 'production publish requires a github-npm-v1 frozen plan');
|
|
527
529
|
}
|
|
530
|
+
const verifiedFrozenSnapshots = new Map();
|
|
528
531
|
if (isProductionPlan) {
|
|
529
532
|
if (!plan.production.assetRoot || plan.production.assetRoot === '.') {
|
|
530
533
|
throw new ReleaseError(GATE_FAILED, 'production plan requires a dedicated assetRoot');
|
|
@@ -570,6 +573,7 @@ export async function publishRelease(options) {
|
|
|
570
573
|
snapshotPath: frozen.path,
|
|
571
574
|
expectedDigest: frozen.manifestDigest,
|
|
572
575
|
});
|
|
576
|
+
verifiedFrozenSnapshots.set(unit.id, snapshot.snapshotDir);
|
|
573
577
|
assertInsideAssetRoot(assetRoot, snapshot.snapshotDir, 'frozen snapshot');
|
|
574
578
|
const git = await verifyFrozenGitRepository({
|
|
575
579
|
root,
|
|
@@ -603,6 +607,60 @@ export async function publishRelease(options) {
|
|
|
603
607
|
await evidence.append({ phase: 'safety-gate', gate: 'frozen-artifacts', status: 'passed' });
|
|
604
608
|
}
|
|
605
609
|
|
|
610
|
+
// =======================================================================
|
|
611
|
+
// Safety Gate 2c: Skill resource closure recheck
|
|
612
|
+
// Re-run the skill resource closure check on each unit's frozen snapshot.
|
|
613
|
+
// Requires findingCount=0 and receipt matches the plan.
|
|
614
|
+
// =======================================================================
|
|
615
|
+
if (plan.skillResourceClosure) {
|
|
616
|
+
await evidence.append({ phase: 'safety-gate', gate: 'skill-resource-closure', status: 'started' });
|
|
617
|
+
const expectedReceipts = plan.skillResourceClosure.unitReceipts ?? [];
|
|
618
|
+
if (plan.skillResourceClosure.checkerVersion !== SKILL_RESOURCE_CHECKER_VERSION) {
|
|
619
|
+
throw new ReleaseError(
|
|
620
|
+
GATE_FAILED,
|
|
621
|
+
`skill resource closure checker version mismatch: plan=${plan.skillResourceClosure.checkerVersion}, current=${SKILL_RESOURCE_CHECKER_VERSION}`,
|
|
622
|
+
);
|
|
623
|
+
}
|
|
624
|
+
if (expectedReceipts.length !== plan.units.length) {
|
|
625
|
+
throw new ReleaseError(GATE_FAILED, 'skill resource closure receipt set does not cover every release unit');
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
for (const [unitId, snapshotDir] of verifiedFrozenSnapshots) {
|
|
629
|
+
const expected = expectedReceipts.find((item) => item.unitId === unitId);
|
|
630
|
+
if (!expected) {
|
|
631
|
+
throw new ReleaseError(GATE_FAILED, `skill resource closure receipt missing for unit "${unitId}"`);
|
|
632
|
+
}
|
|
633
|
+
const closureResult = await checkSkillResourceClosure({
|
|
634
|
+
snapshotDir,
|
|
635
|
+
host: 'root',
|
|
636
|
+
});
|
|
637
|
+
if (closureResult.findings.length > 0) {
|
|
638
|
+
await evidence.append({
|
|
639
|
+
phase: 'safety-gate',
|
|
640
|
+
gate: 'skill-resource-closure',
|
|
641
|
+
status: 'failed',
|
|
642
|
+
unitId,
|
|
643
|
+
findingCount: closureResult.findings.length,
|
|
644
|
+
});
|
|
645
|
+
throw new ReleaseError(
|
|
646
|
+
GATE_FAILED,
|
|
647
|
+
`skill resource closure recheck failed for unit "${unitId}": ${closureResult.findings.length} finding(s)`,
|
|
648
|
+
{ unitId, findings: closureResult.findings },
|
|
649
|
+
);
|
|
650
|
+
}
|
|
651
|
+
const observed = createSkillResourceClosureReceipt(closureResult, { unitId });
|
|
652
|
+
assertSkillResourceClosureReceipt(expected, observed, `unit "${unitId}"`);
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
await evidence.append({
|
|
656
|
+
phase: 'safety-gate',
|
|
657
|
+
gate: 'skill-resource-closure',
|
|
658
|
+
status: 'passed',
|
|
659
|
+
receiptCount: expectedReceipts.length,
|
|
660
|
+
recheckedReceiptCount: verifiedFrozenSnapshots.size,
|
|
661
|
+
});
|
|
662
|
+
}
|
|
663
|
+
|
|
606
664
|
// =======================================================================
|
|
607
665
|
// Safety Gates 3-7: Load and validate approval record (shared)
|
|
608
666
|
// =======================================================================
|
|
@@ -892,21 +950,27 @@ export async function publishRelease(options) {
|
|
|
892
950
|
const orderedActions = sortActionsByCheckpointOrder(publishingPlan.externalActions);
|
|
893
951
|
|
|
894
952
|
// =======================================================================
|
|
895
|
-
//
|
|
953
|
+
// Partition: marketplace actions are deferred to verify phase.
|
|
954
|
+
// They get a stable CONSUMER_VERIFICATION_DEFERRED checkpoint immediately;
|
|
955
|
+
// they never call marketplace adapter preflight/execute/observe/verify.
|
|
956
|
+
// =======================================================================
|
|
957
|
+
const remoteWriteActions = orderedActions.filter((a) => !isMarketplaceAction(a.type));
|
|
958
|
+
|
|
959
|
+
// =======================================================================
|
|
960
|
+
// Safety Gate 10: Global preflight - validate all non-marketplace actions
|
|
961
|
+
// before any execute. Marketplace actions are deferred and skip preflight.
|
|
896
962
|
// =======================================================================
|
|
897
963
|
await evidence.append({ phase: 'safety-gate', gate: 'global-preflight', status: 'started' });
|
|
898
964
|
|
|
899
|
-
for (const action of
|
|
965
|
+
for (const action of remoteWriteActions) {
|
|
900
966
|
if (action.type === 'write-remote-identifier') continue;
|
|
901
967
|
|
|
902
968
|
const adapterActionType = ADAPTER_ACTION_TYPE_MAP[action.type];
|
|
903
969
|
if (!adapterActionType) continue;
|
|
904
970
|
|
|
905
971
|
const adapter = adapterRegistry.getAdapter(adapterActionType);
|
|
906
|
-
const isMarketplace = MARKETPLACE_TYPES.has(action.type);
|
|
907
972
|
const preflightContext = {
|
|
908
973
|
externalWritesAuthorized: false,
|
|
909
|
-
isolatedConsumerWritesAuthorized: isMarketplace,
|
|
910
974
|
plan: publishingPlan,
|
|
911
975
|
baseline: plan.baseline,
|
|
912
976
|
root,
|
|
@@ -918,15 +982,11 @@ export async function publishRelease(options) {
|
|
|
918
982
|
);
|
|
919
983
|
if (preflightResult.status === 'PREFLIGHT_FAILED') {
|
|
920
984
|
// T3.3 §4.6: an "occupied" preflight failure overlaps semantically with
|
|
921
|
-
// "already consistent".
|
|
922
|
-
//
|
|
923
|
-
//
|
|
924
|
-
//
|
|
925
|
-
|
|
926
|
-
// keeps the fail-closed GATE_FAILED. Marketplace preflights are local
|
|
927
|
-
// integrity checks, never remote occupation, so they are NEVER arbitrated
|
|
928
|
-
// -- a CONSISTENT observation must not bypass their completeness gate.
|
|
929
|
-
if (!isMarketplace && hasExpected(action.expected)) {
|
|
985
|
+
// "already consistent". Arbitrate with a single read-only pre-observe
|
|
986
|
+
// using the SAME classifier as the per-checkpoint path: CONSISTENT means
|
|
987
|
+
// the remote already satisfies the frozen plan, so let the action through
|
|
988
|
+
// to its SKIPPED path instead of failing the whole gate.
|
|
989
|
+
if (hasExpected(action.expected)) {
|
|
930
990
|
let arbitration;
|
|
931
991
|
try {
|
|
932
992
|
arbitration = await adapter.observe(
|
|
@@ -961,15 +1021,27 @@ export async function publishRelease(options) {
|
|
|
961
1021
|
await evidence.append({ phase: 'safety-gate', gate: 'global-preflight', status: 'passed' });
|
|
962
1022
|
|
|
963
1023
|
// =======================================================================
|
|
964
|
-
//
|
|
1024
|
+
// Build checkpoints: marketplace actions get a deferred result immediately;
|
|
1025
|
+
// non-marketplace actions start as PENDING and are executed by tiers.
|
|
965
1026
|
// =======================================================================
|
|
966
1027
|
const runPath = join(runDir, 'release-run.json');
|
|
967
|
-
const checkpoints = orderedActions.map((action) =>
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
1028
|
+
const checkpoints = orderedActions.map((action) => {
|
|
1029
|
+
if (isMarketplaceAction(action.type)) {
|
|
1030
|
+
return {
|
|
1031
|
+
actionId: action.id,
|
|
1032
|
+
actionType: action.type,
|
|
1033
|
+
status: 'DEFERRED',
|
|
1034
|
+
phase: 'post-publish-verification',
|
|
1035
|
+
reason: CONSUMER_VERIFICATION_DEFERRED,
|
|
1036
|
+
};
|
|
1037
|
+
}
|
|
1038
|
+
return {
|
|
1039
|
+
actionId: action.id,
|
|
1040
|
+
actionType: action.type,
|
|
1041
|
+
status: 'PENDING',
|
|
1042
|
+
error: null,
|
|
1043
|
+
};
|
|
1044
|
+
});
|
|
973
1045
|
|
|
974
1046
|
const startedAt = clockFn();
|
|
975
1047
|
const buildPersistedState = (status = PUBLISHING, finishedAt) => ({
|
|
@@ -987,10 +1059,14 @@ export async function publishRelease(options) {
|
|
|
987
1059
|
: checkpoint.status === 'FAILED' ? 'failed'
|
|
988
1060
|
: checkpoint.status === 'UNCERTAIN' ? 'uncertain'
|
|
989
1061
|
: checkpoint.status === 'SKIPPED' ? 'skipped'
|
|
1062
|
+
: checkpoint.status === 'DEFERRED' ? 'deferred'
|
|
990
1063
|
: 'pending',
|
|
991
1064
|
...(checkpoint.preObserve ? { preObserve: checkpoint.preObserve } : {}),
|
|
992
1065
|
...(checkpoint.postObserve ? { postObserve: checkpoint.postObserve } : {}),
|
|
993
1066
|
...(checkpoint.error ? { error: { code: 'GATE_FAILED', message: checkpoint.error } } : {}),
|
|
1067
|
+
...(checkpoint.reason === CONSUMER_VERIFICATION_DEFERRED
|
|
1068
|
+
? { reason: CONSUMER_VERIFICATION_DEFERRED, phase: checkpoint.phase ?? 'post-publish-verification' }
|
|
1069
|
+
: {}),
|
|
994
1070
|
})),
|
|
995
1071
|
startedAt,
|
|
996
1072
|
...(finishedAt ? { finishedAt } : {}),
|
|
@@ -1023,7 +1099,9 @@ export async function publishRelease(options) {
|
|
|
1023
1099
|
// order). appendRunState's seq chain (run.mjs) is unchanged.
|
|
1024
1100
|
// =======================================================================
|
|
1025
1101
|
const checkpointByActionId = new Map(checkpoints.map((cp) => [cp.actionId, cp]));
|
|
1026
|
-
|
|
1102
|
+
// Only non-marketplace actions participate in tier execution.
|
|
1103
|
+
// Marketplace actions are already deferred and excluded from tiers.
|
|
1104
|
+
const { tiers, unknown } = groupActionsByTier(remoteWriteActions);
|
|
1027
1105
|
let stopped = false;
|
|
1028
1106
|
|
|
1029
1107
|
// Fail closed on any action type the tier table does not recognize. Such a
|
|
@@ -1075,7 +1153,7 @@ export async function publishRelease(options) {
|
|
|
1075
1153
|
|
|
1076
1154
|
// --- Tier execute: concurrent, allSettled semantics (no fail-fast).
|
|
1077
1155
|
const results = await Promise.all(tierActions.map(async (action) => {
|
|
1078
|
-
const isMarketplace =
|
|
1156
|
+
const isMarketplace = isMarketplaceAction(action.type);
|
|
1079
1157
|
const actionContext = {
|
|
1080
1158
|
externalWritesAuthorized: !isMarketplace,
|
|
1081
1159
|
isolatedConsumerWritesAuthorized: isMarketplace,
|
|
@@ -1136,60 +1214,76 @@ export async function publishRelease(options) {
|
|
|
1136
1214
|
// consistency pass. Both the branch tip and default-branch name are bound
|
|
1137
1215
|
// in the frozen action expectations. A late change keeps the saga PARTIAL
|
|
1138
1216
|
// and must be resolved by reconcile/human review.
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1217
|
+
//
|
|
1218
|
+
// IMPORTANT: the final status determination must read the LATEST checkpoint
|
|
1219
|
+
// state AFTER this consistency check, not use any previously cached boolean.
|
|
1220
|
+
// A late consistency failure that changes a checkpoint from SUCCEEDED to
|
|
1221
|
+
// FAILED must cause PARTIAL, not stale PUBLISHED.
|
|
1222
|
+
{
|
|
1223
|
+
// Build fresh remote-write checkpoint view (reads latest state)
|
|
1224
|
+
const remoteWriteCps = checkpoints.filter((cp) => isRemoteWriteAction(cp.actionType));
|
|
1225
|
+
const allRemoteConsistent = remoteWriteCps.every(
|
|
1226
|
+
(cp) => cp.status === 'SUCCEEDED' || cp.status === 'SKIPPED',
|
|
1227
|
+
);
|
|
1228
|
+
if (allRemoteConsistent) {
|
|
1229
|
+
await evidence.append({ phase: 'safety-gate', gate: 'final-branch-consistency', status: 'started' });
|
|
1230
|
+
for (let index = 0; index < orderedActions.length; index += 1) {
|
|
1231
|
+
const action = orderedActions[index];
|
|
1232
|
+
if (!['push-snapshot', 'set-default-branch'].includes(action.type)) continue;
|
|
1233
|
+
const adapterActionType = ADAPTER_ACTION_TYPE_MAP[action.type];
|
|
1234
|
+
const adapter = adapterRegistry.getAdapter(adapterActionType);
|
|
1235
|
+
let observed;
|
|
1236
|
+
try {
|
|
1237
|
+
observed = await adapter.observe(
|
|
1238
|
+
{ actionType: adapterActionType, ...action.parameters, expected: action.expected },
|
|
1239
|
+
{ externalWritesAuthorized: false, plan: publishingPlan, baseline: plan.baseline, root, runDir },
|
|
1240
|
+
);
|
|
1241
|
+
} catch (error) {
|
|
1242
|
+
observed = { observation: null, error: error.message };
|
|
1243
|
+
}
|
|
1244
|
+
const observation = observed?.observation;
|
|
1245
|
+
const observable = observation && !(observed.error && Object.keys(observation).length === 0);
|
|
1246
|
+
const comparison = observable ? matchObservation(action.expected ?? {}, observation) : { matches: false, mismatches: [] };
|
|
1247
|
+
if (!comparison.matches) {
|
|
1248
|
+
checkpoints[index].status = observable ? 'FAILED' : 'UNCERTAIN';
|
|
1249
|
+
checkpoints[index].error = observable
|
|
1250
|
+
? `final branch consistency mismatch: ${comparison.mismatches.join('; ')}`
|
|
1251
|
+
: `final branch consistency unobservable: ${observed?.error ?? 'empty observation'}`;
|
|
1252
|
+
await evidence.append({
|
|
1253
|
+
phase: 'safety-gate',
|
|
1254
|
+
gate: 'final-branch-consistency',
|
|
1255
|
+
status: 'failed',
|
|
1256
|
+
actionId: action.id,
|
|
1257
|
+
error: checkpoints[index].error,
|
|
1258
|
+
});
|
|
1259
|
+
break;
|
|
1260
|
+
}
|
|
1154
1261
|
}
|
|
1155
|
-
|
|
1156
|
-
const
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
checkpoints[index].status = observable ? 'FAILED' : 'UNCERTAIN';
|
|
1160
|
-
checkpoints[index].error = observable
|
|
1161
|
-
? `final branch consistency mismatch: ${comparison.mismatches.join('; ')}`
|
|
1162
|
-
: `final branch consistency unobservable: ${observed?.error ?? 'empty observation'}`;
|
|
1163
|
-
await evidence.append({
|
|
1164
|
-
phase: 'safety-gate',
|
|
1165
|
-
gate: 'final-branch-consistency',
|
|
1166
|
-
status: 'failed',
|
|
1167
|
-
actionId: action.id,
|
|
1168
|
-
error: checkpoints[index].error,
|
|
1169
|
-
});
|
|
1170
|
-
break;
|
|
1262
|
+
// Re-read fresh state after consistency check
|
|
1263
|
+
const freshRemoteCps = checkpoints.filter((cp) => isRemoteWriteAction(cp.actionType));
|
|
1264
|
+
if (freshRemoteCps.every((cp) => cp.status === 'SUCCEEDED' || cp.status === 'SKIPPED')) {
|
|
1265
|
+
await evidence.append({ phase: 'safety-gate', gate: 'final-branch-consistency', status: 'passed' });
|
|
1171
1266
|
}
|
|
1172
1267
|
}
|
|
1173
|
-
if (checkpoints.every((cp) => cp.status === 'SUCCEEDED' || cp.status === 'SKIPPED')) {
|
|
1174
|
-
await evidence.append({ phase: 'safety-gate', gate: 'final-branch-consistency', status: 'passed' });
|
|
1175
|
-
}
|
|
1176
1268
|
}
|
|
1177
1269
|
|
|
1178
|
-
// Determine overall status
|
|
1179
|
-
|
|
1180
|
-
//
|
|
1181
|
-
//
|
|
1182
|
-
|
|
1183
|
-
const
|
|
1270
|
+
// Determine overall status from the LATEST checkpoint state.
|
|
1271
|
+
// Never use a cached boolean computed before the consistency check.
|
|
1272
|
+
// 远端写入动作的状态决定整体发布状态
|
|
1273
|
+
// 市场安装动作的结果记录在 run 中,但不阻止 PUBLISHED
|
|
1274
|
+
const remoteWriteCheckpointsFinal = checkpoints.filter((cp) => isRemoteWriteAction(cp.actionType));
|
|
1275
|
+
const remoteWriteAllSucceeded = remoteWriteCheckpointsFinal.every(
|
|
1276
|
+
(cp) => cp.status === 'SUCCEEDED' || cp.status === 'SKIPPED',
|
|
1277
|
+
);
|
|
1278
|
+
const remoteWriteHasFailure = remoteWriteCheckpointsFinal.some(
|
|
1279
|
+
(cp) => cp.status === 'FAILED' || cp.status === 'UNCERTAIN',
|
|
1280
|
+
);
|
|
1184
1281
|
|
|
1185
1282
|
let overallStatus;
|
|
1186
|
-
if (
|
|
1283
|
+
if (remoteWriteAllSucceeded) {
|
|
1187
1284
|
overallStatus = PUBLISHED;
|
|
1188
1285
|
publishingPlan.status = PUBLISHED;
|
|
1189
|
-
} else if (
|
|
1190
|
-
// Once any execute was attempted, recovery must go through reconcile,
|
|
1191
|
-
// even when no success was observed. Re-running publish could duplicate
|
|
1192
|
-
// a write accepted just before a transport failure.
|
|
1286
|
+
} else if (remoteWriteHasFailure) {
|
|
1193
1287
|
overallStatus = PARTIAL;
|
|
1194
1288
|
publishingPlan.status = overallStatus;
|
|
1195
1289
|
} else {
|