release-skill 0.1.4 → 0.1.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.
@@ -690,6 +690,7 @@ function buildExternalActions(unitResults, resolvedVersions, productionAssets) {
690
690
  const claudeDist = (unit.distributions ?? []).find((d) => d.type === 'claude-plugin');
691
691
  if (claudeDist) {
692
692
  const identity = marketplaceIdentity(claudeDist);
693
+ const claudeTimeoutMs = Number.isInteger(claudeDist.timeoutMs) ? claudeDist.timeoutMs : 300000;
693
694
  actions.push({
694
695
  id: `claude-marketplace-install-${unit.id}`,
695
696
  type: 'claude-marketplace-install',
@@ -702,6 +703,7 @@ function buildExternalActions(unitResults, resolvedVersions, productionAssets) {
702
703
  repo: unit.publicRepo,
703
704
  version,
704
705
  entrySkill: identity.entrySkill,
706
+ timeoutMs: claudeTimeoutMs,
705
707
  },
706
708
  expected: {
707
709
  installed: true,
@@ -716,6 +718,7 @@ function buildExternalActions(unitResults, resolvedVersions, productionAssets) {
716
718
  const codexDist = (unit.distributions ?? []).find((d) => d.type === 'codex-plugin');
717
719
  if (codexDist) {
718
720
  const identity = marketplaceIdentity(codexDist);
721
+ const codexTimeoutMs = Number.isInteger(codexDist.timeoutMs) ? codexDist.timeoutMs : 300000;
719
722
  actions.push({
720
723
  id: `codex-marketplace-install-${unit.id}`,
721
724
  type: 'codex-marketplace-install',
@@ -728,6 +731,7 @@ function buildExternalActions(unitResults, resolvedVersions, productionAssets) {
728
731
  repo: unit.publicRepo,
729
732
  version,
730
733
  entrySkill: identity.entrySkill,
734
+ timeoutMs: codexTimeoutMs,
731
735
  },
732
736
  expected: {
733
737
  installed: true,
@@ -884,6 +888,7 @@ function buildExternalActions(unitResults, resolvedVersions, productionAssets) {
884
888
  const claudeDist = (unit.distributions ?? []).find((d) => d.type === 'claude-plugin');
885
889
  if (claudeDist) {
886
890
  const identity = marketplaceIdentity(claudeDist);
891
+ const claudeTimeoutMs = Number.isInteger(claudeDist.timeoutMs) ? claudeDist.timeoutMs : 300000;
887
892
  actions.push({
888
893
  id: `claude-marketplace-install-${unit.id}`,
889
894
  type: 'claude-marketplace-install',
@@ -899,6 +904,7 @@ function buildExternalActions(unitResults, resolvedVersions, productionAssets) {
899
904
  entrySkill: identity.entrySkill,
900
905
  snapshotPath: asset.snapshotPath,
901
906
  manifestDigest: asset.manifestDigest,
907
+ timeoutMs: claudeTimeoutMs,
902
908
  },
903
909
  expected: {
904
910
  installed: true,
@@ -918,6 +924,7 @@ function buildExternalActions(unitResults, resolvedVersions, productionAssets) {
918
924
  const codexDist = (unit.distributions ?? []).find((d) => d.type === 'codex-plugin');
919
925
  if (codexDist) {
920
926
  const identity = marketplaceIdentity(codexDist);
927
+ const codexTimeoutMs = Number.isInteger(codexDist.timeoutMs) ? codexDist.timeoutMs : 300000;
921
928
  actions.push({
922
929
  id: `codex-marketplace-install-${unit.id}`,
923
930
  type: 'codex-marketplace-install',
@@ -933,6 +940,7 @@ function buildExternalActions(unitResults, resolvedVersions, productionAssets) {
933
940
  entrySkill: identity.entrySkill,
934
941
  snapshotPath: asset.snapshotPath,
935
942
  manifestDigest: asset.manifestDigest,
943
+ timeoutMs: codexTimeoutMs,
936
944
  },
937
945
  expected: {
938
946
  installed: true,
@@ -222,7 +222,10 @@ export async function reconcileRelease(options) {
222
222
  // =======================================================================
223
223
  await evidence.append({ phase: 'safety-gate', gate: 'action-completeness', status: 'started' });
224
224
 
225
- const completenessResult = validatePlanActionCompleteness(plan);
225
+ // Use legacyCompatibility: old PARTIAL plans (pre-v0.1.5) lack
226
+ // parameters.timeoutMs. Reconcile must still pass these plans,
227
+ // while strict mode (prepare/approve/publish) rejects them.
228
+ const completenessResult = validatePlanActionCompleteness(plan, { legacyCompatibility: true });
226
229
  if (!completenessResult.passed) {
227
230
  await evidence.append({
228
231
  phase: 'safety-gate',
@@ -669,7 +669,10 @@ export async function verifyRelease(options) {
669
669
  }
670
670
 
671
671
  // Validate plan action completeness before checkpoint mapping
672
- const completenessResult = validatePlanActionCompleteness(plan);
672
+ // Use legacyCompatibility: old plans (pre-v0.1.5) lack
673
+ // parameters.timeoutMs. Verify must still pass these plans,
674
+ // while strict mode (prepare/approve/publish) rejects them.
675
+ const completenessResult = validatePlanActionCompleteness(plan, { legacyCompatibility: true });
673
676
  if (!completenessResult.passed) {
674
677
  throw new ReleaseError(
675
678
  GATE_FAILED,
package/src/core/plan.mjs CHANGED
@@ -312,9 +312,13 @@ const REQUIRED_ACTION_TYPES = ['push-snapshot', 'create-tag', 'github-release'];
312
312
  * (e.g. v0.0.10 must not match expected version 0.0.1).
313
313
  *
314
314
  * @param {object} plan - A validated release plan object.
315
+ * @param {object} [options]
316
+ * @param {boolean} [options.legacyCompatibility] - When true, relax
317
+ * timeoutMs requirement for old plans (reconcile/verify paths).
318
+ * New prepare/approve/publish must not set this flag.
315
319
  * @returns {{ passed: boolean, details: { failures: string[], expectedCount: number, actualCount: number } }}
316
320
  */
317
- export function validatePlanActionCompleteness(plan) {
321
+ export function validatePlanActionCompleteness(plan, options = {}) {
318
322
  const failures = [];
319
323
 
320
324
  if (!plan || typeof plan !== 'object') {
@@ -670,6 +674,33 @@ export function validatePlanActionCompleteness(plan) {
670
674
  _checkRequired(action, 'parameters.ref', action.parameters?.ref, expectedTag, unitId, failures);
671
675
  _checkRequired(action, 'parameters.manifestDigest', action.parameters?.manifestDigest, frozen?.manifestDigest, unitId, failures);
672
676
  }
677
+ // timeoutMs is mandatory for all marketplace install actions.
678
+ // Legacy plans (pre-v0.1.5) lack this field; legacyCompatibility
679
+ // relaxes the check for reconcile/verify paths only, and only when
680
+ // the property is genuinely absent (undefined). An explicit null is
681
+ // NOT "absent" -- it is an invalid value and always fails closed,
682
+ // like strings or out-of-range numbers, even in legacyCompatibility.
683
+ {
684
+ const raw = action.parameters?.timeoutMs;
685
+ if (raw === undefined) {
686
+ if (!options.legacyCompatibility) {
687
+ failures.push(
688
+ `unit "${unitId}", action "${action.id}": parameters.timeoutMs is missing, expected a valid timeout (30000-900000)`,
689
+ );
690
+ }
691
+ } else {
692
+ // Field is present -- always validate range/type, even in legacy mode
693
+ if (typeof raw !== 'number' || !Number.isFinite(raw) || !Number.isInteger(raw)) {
694
+ failures.push(
695
+ `unit "${unitId}", action "${action.id}": parameters.timeoutMs must be a finite integer, got: ${JSON.stringify(raw)}`,
696
+ );
697
+ } else if (raw < 30000 || raw > 900000) {
698
+ failures.push(
699
+ `unit "${unitId}", action "${action.id}": parameters.timeoutMs must be between 30000 and 900000, got: ${raw}`,
700
+ );
701
+ }
702
+ }
703
+ }
673
704
 
674
705
  // Expected checks
675
706
  _checkRequired(action, 'expected.installed', action.expected?.installed, true, unitId, failures);
@@ -746,6 +777,33 @@ export function validatePlanActionCompleteness(plan) {
746
777
  _checkRequired(action, 'parameters.ref', action.parameters?.ref, expectedTag, unitId, failures);
747
778
  _checkRequired(action, 'parameters.manifestDigest', action.parameters?.manifestDigest, frozen?.manifestDigest, unitId, failures);
748
779
  }
780
+ // timeoutMs is mandatory for all marketplace install actions.
781
+ // Legacy plans (pre-v0.1.5) lack this field; legacyCompatibility
782
+ // relaxes the check for reconcile/verify paths only, and only when
783
+ // the property is genuinely absent (undefined). An explicit null is
784
+ // NOT "absent" -- it is an invalid value and always fails closed,
785
+ // like strings or out-of-range numbers, even in legacyCompatibility.
786
+ {
787
+ const raw = action.parameters?.timeoutMs;
788
+ if (raw === undefined) {
789
+ if (!options.legacyCompatibility) {
790
+ failures.push(
791
+ `unit "${unitId}", action "${action.id}": parameters.timeoutMs is missing, expected a valid timeout (30000-900000)`,
792
+ );
793
+ }
794
+ } else {
795
+ // Field is present -- always validate range/type, even in legacy mode
796
+ if (typeof raw !== 'number' || !Number.isFinite(raw) || !Number.isInteger(raw)) {
797
+ failures.push(
798
+ `unit "${unitId}", action "${action.id}": parameters.timeoutMs must be a finite integer, got: ${JSON.stringify(raw)}`,
799
+ );
800
+ } else if (raw < 30000 || raw > 900000) {
801
+ failures.push(
802
+ `unit "${unitId}", action "${action.id}": parameters.timeoutMs must be between 30000 and 900000, got: ${raw}`,
803
+ );
804
+ }
805
+ }
806
+ }
749
807
 
750
808
  // Expected checks
751
809
  _checkRequired(action, 'expected.installed', action.expected?.installed, true, unitId, failures);