@runveric/cli 0.1.2 → 0.2.0

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/dist/runveric.mjs CHANGED
@@ -212,13 +212,17 @@ var require_wire_identity = __commonJS({
212
212
  Object.defineProperty(exports, "__esModule", {
213
213
  value: true
214
214
  });
215
- exports.PROBLEM_TYPE_BASE = exports.RUNNER_PROTOCOL_VERSION = exports.PACKAGE_WORK_ITEM_VERSION = exports.ACTION_MANIFEST_API_VERSION = exports.PACKAGE_EXECUTION_PROTOCOL_VERSION = exports.PACKAGE_RESULT_PROTOCOL_VERSION = void 0;
215
+ exports.PROBLEM_TYPE_BASE = exports.CATALOG_ARTIFACT_MEDIA_TYPE = exports.CATALOG_ACTION_ARTIFACT_SCHEMA_V1 = exports.CATALOG_RELEASE_SCHEMA_V1 = exports.EVIDENCE_PACK_SCHEMA_VERSION = exports.RUNNER_PROTOCOL_VERSION = exports.PACKAGE_WORK_ITEM_VERSION = exports.ACTION_MANIFEST_API_VERSION = exports.PACKAGE_EXECUTION_PROTOCOL_VERSION = exports.PACKAGE_RESULT_PROTOCOL_VERSION = void 0;
216
216
  exports.problemType = problemType;
217
217
  exports.PACKAGE_RESULT_PROTOCOL_VERSION = "runveric.dev/package-result/v1";
218
218
  exports.PACKAGE_EXECUTION_PROTOCOL_VERSION = "runveric.dev/package-execution/v1";
219
219
  exports.ACTION_MANIFEST_API_VERSION = "runveric.dev/action/v1";
220
220
  exports.PACKAGE_WORK_ITEM_VERSION = "runveric.dev/package-work-item/v1";
221
221
  exports.RUNNER_PROTOCOL_VERSION = "runveric.dev/runner-channel/v2";
222
+ exports.EVIDENCE_PACK_SCHEMA_VERSION = "runveric.dev/evidence-pack/v1";
223
+ exports.CATALOG_RELEASE_SCHEMA_V1 = "runveric.dev/catalog-release/v1";
224
+ exports.CATALOG_ACTION_ARTIFACT_SCHEMA_V1 = "runveric.dev/catalog-action/v1";
225
+ exports.CATALOG_ARTIFACT_MEDIA_TYPE = "application/vnd.runveric.catalog-artifact+json";
222
226
  exports.PROBLEM_TYPE_BASE = "https://runveric.dev/errors";
223
227
  function problemType(slug) {
224
228
  return `${exports.PROBLEM_TYPE_BASE}/${slug}`;
@@ -301,6 +305,13 @@ var require_permissions = __commonJS({
301
305
  Object.defineProperty(exports, "__esModule", {
302
306
  value: true
303
307
  });
308
+ exports.PLATFORM_PERMISSIONS = void 0;
309
+ exports.PLATFORM_PERMISSIONS = [
310
+ // Which catalog release is live for every workspace (docs/121). Pinning one changes what EVERY
311
+ // workspace can run, so it is neither `policy:manage` (one workspace's own governance) nor
312
+ // `catalog:read` (the right to browse).
313
+ "catalog:release"
314
+ ];
304
315
  }
305
316
  });
306
317
 
@@ -443,6 +454,41 @@ var require_groups = __commonJS({
443
454
  }
444
455
  });
445
456
 
457
+ // ../contracts/dist/catalog-policy.js
458
+ var require_catalog_policy = __commonJS({
459
+ "../contracts/dist/catalog-policy.js"(exports) {
460
+ "use strict";
461
+ Object.defineProperty(exports, "__esModule", {
462
+ value: true
463
+ });
464
+ exports.CATALOG_RULE_EFFECTS = exports.CATALOG_DEFAULT_MODE_WHEN_UNSET = exports.CATALOG_DEFAULT_MODES = void 0;
465
+ exports.effectiveCatalogAccess = effectiveCatalogAccess;
466
+ var CATALOG_DEFAULT_MODE_SET = {
467
+ allow_all: true,
468
+ deny_all: true
469
+ };
470
+ exports.CATALOG_DEFAULT_MODES = Object.keys(CATALOG_DEFAULT_MODE_SET);
471
+ exports.CATALOG_DEFAULT_MODE_WHEN_UNSET = "allow_all";
472
+ var CATALOG_RULE_EFFECT_SET = {
473
+ allow: true,
474
+ deny: true
475
+ };
476
+ exports.CATALOG_RULE_EFFECTS = Object.keys(CATALOG_RULE_EFFECT_SET);
477
+ var RULE_ACCESS = {
478
+ allow: "allowed",
479
+ deny: "blocked"
480
+ };
481
+ var UNRULED_ACCESS = {
482
+ allow_all: "allowed",
483
+ deny_all: "blocked"
484
+ };
485
+ function effectiveCatalogAccess(defaultMode, rule) {
486
+ return rule !== null ? RULE_ACCESS[rule] : UNRULED_ACCESS[defaultMode];
487
+ }
488
+ __name(effectiveCatalogAccess, "effectiveCatalogAccess");
489
+ }
490
+ });
491
+
446
492
  // ../contracts/dist/catalog.js
447
493
  var require_catalog = __commonJS({
448
494
  "../contracts/dist/catalog.js"(exports) {
@@ -558,6 +604,315 @@ var require_catalog = __commonJS({
558
604
  }
559
605
  });
560
606
 
607
+ // ../contracts/dist/catalog-release.js
608
+ var require_catalog_release = __commonJS({
609
+ "../contracts/dist/catalog-release.js"(exports) {
610
+ "use strict";
611
+ Object.defineProperty(exports, "__esModule", {
612
+ value: true
613
+ });
614
+ exports.ARTIFACT_DIGEST_PATTERN = exports.ARTIFACT_REPOSITORY_PATTERN = exports.CATALOG_ACTION_ARTIFACT_SCHEMA_VERSIONS = exports.CATALOG_RELEASE_SCHEMA_VERSIONS = void 0;
615
+ var wire_identity_1 = require_wire_identity();
616
+ exports.CATALOG_RELEASE_SCHEMA_VERSIONS = [
617
+ wire_identity_1.CATALOG_RELEASE_SCHEMA_V1
618
+ ];
619
+ exports.CATALOG_ACTION_ARTIFACT_SCHEMA_VERSIONS = [
620
+ wire_identity_1.CATALOG_ACTION_ARTIFACT_SCHEMA_V1
621
+ ];
622
+ exports.ARTIFACT_REPOSITORY_PATTERN = /^[a-z0-9][a-z0-9.-]*(:[0-9]+)?(\/[a-z0-9][a-z0-9._-]*)+$/;
623
+ exports.ARTIFACT_DIGEST_PATTERN = /^sha256:[0-9a-f]{64}$/;
624
+ }
625
+ });
626
+
627
+ // ../contracts/dist/action-repository.js
628
+ var require_action_repository = __commonJS({
629
+ "../contracts/dist/action-repository.js"(exports) {
630
+ "use strict";
631
+ Object.defineProperty(exports, "__esModule", {
632
+ value: true
633
+ });
634
+ exports.ACTION_REPOSITORY_PREFIX = void 0;
635
+ exports.actionKeyRefusal = actionKeyRefusal;
636
+ exports.isActionKey = isActionKey;
637
+ exports.actionRepositoryName = actionRepositoryName;
638
+ exports.actionKeyOf = actionKeyOf;
639
+ exports.actionPackageRefusal = actionPackageRefusal3;
640
+ exports.ACTION_REPOSITORY_PREFIX = "action-";
641
+ var WORD = /^[a-z][a-z0-9]*$/;
642
+ var VERB = /^[a-z][a-z0-9]*(?:[A-Z][a-z0-9]+)*$/;
643
+ function actionKeyRefusal(key) {
644
+ const shape = "an action key is <provider>.<resource>.<verb>: provider and resource are one lowercase word (a-z, then a-z or 0-9), the verb is camelCase with no two capitals in a row \u2014 so the key maps to exactly one repository name and back.";
645
+ const segments = key.split(".");
646
+ if (segments.length !== 3) {
647
+ return `"${key}" is not an action key: it has ${segments.length} dot-separated segment(s), not 3; ${shape}`;
648
+ }
649
+ const [provider, resource, verb] = segments;
650
+ const not = /* @__PURE__ */ __name((what) => `"${key}" is not an action key: ${what}; ${shape}`, "not");
651
+ if (!WORD.test(provider)) return not(`its provider "${provider}" is not one lowercase word`);
652
+ if (!WORD.test(resource)) return not(`its resource "${resource}" is not one lowercase word`);
653
+ if (!VERB.test(verb)) return not(`its verb "${verb}" is not camelCase without two capitals in a row`);
654
+ return null;
655
+ }
656
+ __name(actionKeyRefusal, "actionKeyRefusal");
657
+ function isActionKey(key) {
658
+ return actionKeyRefusal(key) === null;
659
+ }
660
+ __name(isActionKey, "isActionKey");
661
+ var kebab = /* @__PURE__ */ __name((verb) => verb.replace(/[A-Z]/g, (capital) => `-${capital.toLowerCase()}`), "kebab");
662
+ function actionRepositoryName(key) {
663
+ const refusal = actionKeyRefusal(key);
664
+ if (refusal !== null) throw new Error(refusal);
665
+ const [provider, resource, verb] = key.split(".");
666
+ return `${exports.ACTION_REPOSITORY_PREFIX}${provider}-${resource}-${kebab(verb)}`;
667
+ }
668
+ __name(actionRepositoryName, "actionRepositoryName");
669
+ function actionKeyOf(name) {
670
+ if (!name.startsWith(exports.ACTION_REPOSITORY_PREFIX)) return null;
671
+ const [provider, resource, first2, ...rest] = name.slice(exports.ACTION_REPOSITORY_PREFIX.length).split("-");
672
+ if (provider === void 0 || resource === void 0 || first2 === void 0) return null;
673
+ const verb = first2 + rest.map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join("");
674
+ const key = `${provider}.${resource}.${verb}`;
675
+ return isActionKey(key) && actionRepositoryName(key) === name ? key : null;
676
+ }
677
+ __name(actionKeyOf, "actionKeyOf");
678
+ function actionPackageRefusal3(key, artifactRef) {
679
+ const keyRefusal = actionKeyRefusal(key);
680
+ if (keyRefusal !== null) return `${keyRefusal} No package can carry it.`;
681
+ const cut = artifactRef.lastIndexOf("/");
682
+ const named = artifactRef.slice(cut + 1);
683
+ const expected = actionRepositoryName(key);
684
+ if (named === expected) return null;
685
+ return `action "${key}" is published only from a package named "${expected}" (${artifactRef.slice(0, cut + 1)}${expected}), not "${named}". An action's package carries its key's name, so an artifact cannot claim an identity its package does not carry.`;
686
+ }
687
+ __name(actionPackageRefusal3, "actionPackageRefusal");
688
+ }
689
+ });
690
+
691
+ // ../contracts/dist/catalog-release-spec.js
692
+ var require_catalog_release_spec = __commonJS({
693
+ "../contracts/dist/catalog-release-spec.js"(exports) {
694
+ "use strict";
695
+ Object.defineProperty(exports, "__esModule", {
696
+ value: true
697
+ });
698
+ exports.CatalogReleaseSpecError = void 0;
699
+ exports.splitArtifactReference = splitArtifactReference;
700
+ exports.parseCatalogReleaseSpec = parseCatalogReleaseSpec2;
701
+ var CatalogReleaseSpecError = class CatalogReleaseSpecError extends Error {
702
+ static {
703
+ __name(this, "CatalogReleaseSpecError");
704
+ }
705
+ constructor(message) {
706
+ super(message);
707
+ this.name = "CatalogReleaseSpecError";
708
+ }
709
+ };
710
+ exports.CatalogReleaseSpecError = CatalogReleaseSpecError;
711
+ function splitArtifactReference(reference) {
712
+ const at = reference.indexOf("@");
713
+ if (at <= 0 || at === reference.length - 1) return null;
714
+ return {
715
+ artifactRef: reference.slice(0, at),
716
+ digest: reference.slice(at + 1)
717
+ };
718
+ }
719
+ __name(splitArtifactReference, "splitArtifactReference");
720
+ var isMapping2 = /* @__PURE__ */ __name((value) => typeof value === "object" && value !== null && !Array.isArray(value), "isMapping");
721
+ function parseCatalogReleaseSpec2(value) {
722
+ if (!isMapping2(value)) {
723
+ throw new CatalogReleaseSpecError("release.yaml is not a mapping of `version` and `actions`.");
724
+ }
725
+ const unknown = Object.keys(value).filter((key) => key !== "version" && key !== "actions");
726
+ if (unknown.length > 0) {
727
+ throw new CatalogReleaseSpecError(`release.yaml carries ${unknown.map((key) => `"${key}"`).join(", ")}; it has exactly two fields, \`version\` and \`actions\`.`);
728
+ }
729
+ const { version: version2, actions } = value;
730
+ if (typeof version2 !== "string" || version2.trim().length === 0) {
731
+ throw new CatalogReleaseSpecError(`release.yaml's version is ${JSON.stringify(version2 ?? null)}, not a label. Write it as a quoted string \u2014 version: "2026.10.1" \u2014 because YAML reads an unquoted 2026.10 as a number.`);
732
+ }
733
+ if (actions === null || actions === void 0 || isMapping2(actions) && Object.keys(actions).length === 0) {
734
+ throw new CatalogReleaseSpecError("release.yaml declares no action. An empty release is refused: pinned, it would declare a catalog with nothing in it.");
735
+ }
736
+ if (!isMapping2(actions)) {
737
+ throw new CatalogReleaseSpecError("release.yaml's actions is not a mapping of action key to <repository>@<digest>.");
738
+ }
739
+ return {
740
+ version: version2,
741
+ actions: Object.entries(actions).map(([key, reference]) => {
742
+ const split = typeof reference === "string" ? splitArtifactReference(reference) : null;
743
+ if (split === null) {
744
+ throw new CatalogReleaseSpecError(`release.yaml's ${key} is ${JSON.stringify(reference)}, not <repository>@<digest>.`);
745
+ }
746
+ return {
747
+ key,
748
+ ...split
749
+ };
750
+ })
751
+ };
752
+ }
753
+ __name(parseCatalogReleaseSpec2, "parseCatalogReleaseSpec");
754
+ }
755
+ });
756
+
757
+ // ../contracts/dist/catalog-lock.js
758
+ var require_catalog_lock = __commonJS({
759
+ "../contracts/dist/catalog-lock.js"(exports) {
760
+ "use strict";
761
+ Object.defineProperty(exports, "__esModule", {
762
+ value: true
763
+ });
764
+ exports.CatalogLockError = exports.CATALOG_LOCK_FILE = exports.CATALOG_LOCK_SCHEMA_V1 = void 0;
765
+ exports.parseCatalogLock = parseCatalogLock;
766
+ exports.catalogLockText = catalogLockText;
767
+ exports.authoredElsewhere = authoredElsewhere;
768
+ var action_repository_1 = require_action_repository();
769
+ var catalog_release_1 = require_catalog_release();
770
+ exports.CATALOG_LOCK_SCHEMA_V1 = "runveric.catalog-lock/v1";
771
+ exports.CATALOG_LOCK_FILE = "release.lock.json";
772
+ var CatalogLockError = class CatalogLockError extends Error {
773
+ static {
774
+ __name(this, "CatalogLockError");
775
+ }
776
+ constructor(message) {
777
+ super(message);
778
+ this.name = "CatalogLockError";
779
+ }
780
+ };
781
+ exports.CatalogLockError = CatalogLockError;
782
+ var SHA256_HEX = /^[0-9a-f]{64}$/;
783
+ function objectAt(path, value, keys) {
784
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
785
+ throw new CatalogLockError(`${path} is not an object.`);
786
+ }
787
+ const unknown = Object.keys(value).filter((key) => !keys.includes(key));
788
+ if (unknown.length > 0) {
789
+ throw new CatalogLockError(`${path} carries ${unknown.map((key) => `"${key}"`).join(", ")}, which a lock does not have. A lock is written by catalog:vendor and never edited; re-vendor instead.`);
790
+ }
791
+ return value;
792
+ }
793
+ __name(objectAt, "objectAt");
794
+ function stringAt(path, value, pattern, expected) {
795
+ if (typeof value !== "string" || value.length === 0) {
796
+ throw new CatalogLockError(`${path} is not a non-empty string.`);
797
+ }
798
+ if (pattern !== void 0 && !pattern.test(value)) {
799
+ throw new CatalogLockError(`${path} is "${value}", which is not ${expected}.`);
800
+ }
801
+ return value;
802
+ }
803
+ __name(stringAt, "stringAt");
804
+ function parseCatalogLock(value) {
805
+ const root = objectAt("the lock", value, [
806
+ "schemaVersion",
807
+ "release",
808
+ "actions"
809
+ ]);
810
+ if (root.schemaVersion !== exports.CATALOG_LOCK_SCHEMA_V1) {
811
+ throw new CatalogLockError(`the lock declares schemaVersion ${JSON.stringify(root.schemaVersion)}; this build reads ${exports.CATALOG_LOCK_SCHEMA_V1} only.`);
812
+ }
813
+ const release = objectAt("release", root.release, [
814
+ "artifactRef",
815
+ "digest",
816
+ "version"
817
+ ]);
818
+ const lock = {
819
+ schemaVersion: exports.CATALOG_LOCK_SCHEMA_V1,
820
+ release: {
821
+ artifactRef: stringAt("release.artifactRef", release.artifactRef, catalog_release_1.ARTIFACT_REPOSITORY_PATTERN, "a registry repository"),
822
+ digest: stringAt("release.digest", release.digest, catalog_release_1.ARTIFACT_DIGEST_PATTERN, "a sha256 digest"),
823
+ version: stringAt("release.version", release.version)
824
+ },
825
+ actions: []
826
+ };
827
+ if (!Array.isArray(root.actions) || root.actions.length === 0) {
828
+ throw new CatalogLockError("actions is not a non-empty array. A release names at least one action.");
829
+ }
830
+ root.actions.forEach((entry, index) => {
831
+ const path = `actions[${index}]`;
832
+ const action = objectAt(path, entry, [
833
+ "key",
834
+ "artifactRef",
835
+ "digest",
836
+ "manifestSha256",
837
+ "provenance"
838
+ ]);
839
+ const key = stringAt(`${path}.key`, action.key);
840
+ const artifactRef = stringAt(`${path}.artifactRef`, action.artifactRef, catalog_release_1.ARTIFACT_REPOSITORY_PATTERN, "a registry repository");
841
+ const misnamed = (0, action_repository_1.actionPackageRefusal)(key, artifactRef);
842
+ if (misnamed !== null) throw new CatalogLockError(`${path}: ${misnamed}`);
843
+ const previous = lock.actions[lock.actions.length - 1];
844
+ if (previous !== void 0 && !(previous.key < key)) {
845
+ throw new CatalogLockError(`${path}.key "${key}" follows "${previous.key}": a lock names each action once, sorted by key.`);
846
+ }
847
+ const provenance2 = objectAt(`${path}.provenance`, action.provenance, [
848
+ "repository",
849
+ "commit"
850
+ ]);
851
+ lock.actions.push({
852
+ key,
853
+ artifactRef,
854
+ digest: stringAt(`${path}.digest`, action.digest, catalog_release_1.ARTIFACT_DIGEST_PATTERN, "a sha256 digest"),
855
+ manifestSha256: stringAt(`${path}.manifestSha256`, action.manifestSha256, SHA256_HEX, "64 lowercase hex characters"),
856
+ provenance: {
857
+ repository: stringAt(`${path}.provenance.repository`, provenance2.repository),
858
+ commit: stringAt(`${path}.provenance.commit`, provenance2.commit)
859
+ }
860
+ });
861
+ });
862
+ return lock;
863
+ }
864
+ __name(parseCatalogLock, "parseCatalogLock");
865
+ function catalogLockText(lock) {
866
+ const ordered = {
867
+ schemaVersion: lock.schemaVersion,
868
+ release: {
869
+ artifactRef: lock.release.artifactRef,
870
+ digest: lock.release.digest,
871
+ version: lock.release.version
872
+ },
873
+ actions: [
874
+ ...lock.actions
875
+ ].sort((a, b) => a.key < b.key ? -1 : a.key > b.key ? 1 : 0).map((action) => ({
876
+ key: action.key,
877
+ artifactRef: action.artifactRef,
878
+ digest: action.digest,
879
+ manifestSha256: action.manifestSha256,
880
+ provenance: {
881
+ repository: action.provenance.repository,
882
+ commit: action.provenance.commit
883
+ }
884
+ }))
885
+ };
886
+ return `${JSON.stringify(ordered, null, 2)}
887
+ `;
888
+ }
889
+ __name(catalogLockText, "catalogLockText");
890
+ function authoredElsewhere(lock, key) {
891
+ const namespace = lock.release.artifactRef.slice(0, lock.release.artifactRef.lastIndexOf("/"));
892
+ const organisation = namespace.slice(namespace.lastIndexOf("/") + 1);
893
+ return `${key} is authored in ${organisation}/${(0, action_repository_1.actionRepositoryName)(key)} \u2014 change it there, publish, assemble, vendor`;
894
+ }
895
+ __name(authoredElsewhere, "authoredElsewhere");
896
+ }
897
+ });
898
+
899
+ // ../contracts/dist/catalog-manifest-placeholders.js
900
+ var require_catalog_manifest_placeholders = __commonJS({
901
+ "../contracts/dist/catalog-manifest-placeholders.js"(exports) {
902
+ "use strict";
903
+ Object.defineProperty(exports, "__esModule", {
904
+ value: true
905
+ });
906
+ exports.MANIFEST_PLACEHOLDER_PATTERN = exports.MANIFEST_PLACEHOLDERS = void 0;
907
+ var brand_1 = require_brand();
908
+ exports.MANIFEST_PLACEHOLDERS = {
909
+ BRAND_SHORT_NAME: brand_1.BRAND_SHORT_NAME,
910
+ BRAND_NAME: brand_1.BRAND_NAME
911
+ };
912
+ exports.MANIFEST_PLACEHOLDER_PATTERN = /\{\{\s*([A-Za-z_][A-Za-z0-9_]*)\s*\}\}/g;
913
+ }
914
+ });
915
+
561
916
  // ../contracts/dist/change-control.js
562
917
  var require_change_control = __commonJS({
563
918
  "../contracts/dist/change-control.js"(exports) {
@@ -857,8 +1212,8 @@ var require_package_provenance = __commonJS({
857
1212
  pair.accepted,
858
1213
  pair.revoked
859
1214
  ]);
860
- function poolProvenanceAuditAction(provenance, accepted) {
861
- const pair = exports.POOL_PROVENANCE_AUDIT_ACTIONS[provenance];
1215
+ function poolProvenanceAuditAction(provenance2, accepted) {
1216
+ const pair = exports.POOL_PROVENANCE_AUDIT_ACTIONS[provenance2];
862
1217
  return accepted ? pair.accepted : pair.revoked;
863
1218
  }
864
1219
  __name(poolProvenanceAuditAction, "poolProvenanceAuditAction");
@@ -1286,7 +1641,7 @@ var require_validation = __commonJS({
1286
1641
  Object.defineProperty(exports, "__esModule", {
1287
1642
  value: true
1288
1643
  });
1289
- exports.TARGET_COMPARISON_KINDS = exports.TARGETS_NO_SCOPE = exports.TARGETS_NOT_COUNTED = exports.TARGET_COUNT_ABSENCES = void 0;
1644
+ exports.TARGET_COMPARISON_KINDS = exports.DECISION_CODES = exports.REFUSAL_CODES = exports.CHECK_LEVELS = exports.TARGETS_NO_SCOPE = exports.TARGETS_NOT_COUNTED = exports.TARGET_COUNT_ABSENCES = void 0;
1290
1645
  exports.isTargetCountAbsence = isTargetCountAbsence;
1291
1646
  exports.singleTarget = singleTarget;
1292
1647
  exports.noTargetsResolved = noTargetsResolved;
@@ -1296,6 +1651,9 @@ var require_validation = __commonJS({
1296
1651
  exports.eligibleTargetCount = eligibleTargetCount;
1297
1652
  exports.targetsValidationCheck = targetsValidationCheck;
1298
1653
  exports.targetSummaryOf = targetSummaryOf;
1654
+ exports.isCheckLevel = isCheckLevel;
1655
+ exports.isRefusalCode = isRefusalCode;
1656
+ exports.isDecisionCode = isDecisionCode;
1299
1657
  exports.scopeFailureDetail = scopeFailureDetail;
1300
1658
  exports.targetsUnavailableFrom = targetsUnavailableFrom;
1301
1659
  exports.compareTargets = compareTargets;
@@ -1412,6 +1770,38 @@ var require_validation = __commonJS({
1412
1770
  return exports.TARGETS_NOT_COUNTED;
1413
1771
  }
1414
1772
  __name(targetSummaryOf, "targetSummaryOf");
1773
+ exports.CHECK_LEVELS = [
1774
+ "info",
1775
+ "warn",
1776
+ "error"
1777
+ ];
1778
+ function isCheckLevel(value) {
1779
+ return typeof value === "string" && exports.CHECK_LEVELS.includes(value);
1780
+ }
1781
+ __name(isCheckLevel, "isCheckLevel");
1782
+ exports.REFUSAL_CODES = [
1783
+ "disk.notUnattached",
1784
+ "disk.locked",
1785
+ "disk.shared",
1786
+ "disk.inRecoveryPlan",
1787
+ "disk.keyUnusable",
1788
+ "disk.unverifiable"
1789
+ ];
1790
+ function isRefusalCode(value) {
1791
+ return typeof value === "string" && exports.REFUSAL_CODES.includes(value);
1792
+ }
1793
+ __name(isRefusalCode, "isRefusalCode");
1794
+ exports.DECISION_CODES = [
1795
+ "disk.recentlyDetached",
1796
+ "disk.customerKey",
1797
+ "disk.customerKeyUnverified",
1798
+ "disk.guestEncrypted",
1799
+ "disk.noSnapshot"
1800
+ ];
1801
+ function isDecisionCode(value) {
1802
+ return typeof value === "string" && exports.DECISION_CODES.includes(value);
1803
+ }
1804
+ __name(isDecisionCode, "isDecisionCode");
1415
1805
  function scopeFailureDetail(error) {
1416
1806
  const raw = error instanceof Error ? error.message : String(error);
1417
1807
  const oneLine = raw.replace(/\s+/g, " ").trim().replace(/[.!]+$/, "");
@@ -1482,7 +1872,7 @@ var require_execution = __commonJS({
1482
1872
  Object.defineProperty(exports, "__esModule", {
1483
1873
  value: true
1484
1874
  });
1485
- exports.ACCEPTING_A_PROVENANCE_IS_NOT_A_CAPABILITY = exports.RUNNER_PACKAGE_RUNTIME_NOT_PINNED = exports.CONNECTOR_UNAVAILABLE_MESSAGE = exports.ACTION_RUNNABILITY_CAUSE_KINDS = exports.EXECUTION_LOG_ACTIVITY_PHRASE = exports.EXECUTION_LOG_CONNECTION_PHRASE = exports.EXECUTION_LOG_STREAM_PHASES = exports.EXECUTION_LOG_ACTIVITIES = exports.targetsValidationCheck = exports.targetSummarySentence = exports.targetSummaryOf = exports.targetSummaryLabel = exports.targetsUnavailableFrom = exports.targetsUnavailable = exports.targetComparisonSentence = exports.targetComparisonLevel = exports.targetComparisonLabel = exports.TARGETS_NOT_COUNTED = exports.TARGETS_NO_SCOPE = exports.TARGET_COUNT_ABSENCES = exports.TARGET_COMPARISON_KINDS = exports.singleTarget = exports.scopeFailureDetail = exports.noTargetsResolved = exports.isTargetCountAbsence = exports.eligibleTargetCount = exports.compareTargets = exports.SUBSCRIPTION_ID_FIELD_RENAMED = void 0;
1875
+ exports.ACCEPTING_A_PROVENANCE_IS_NOT_A_CAPABILITY = exports.RUNNER_PACKAGE_RUNTIME_NOT_PINNED = exports.CONNECTOR_UNAVAILABLE_MESSAGE = exports.ACTION_RUNNABILITY_CAUSE_KINDS = exports.EXECUTION_LOG_ACTIVITY_PHRASE = exports.EXECUTION_LOG_CONNECTION_PHRASE = exports.EXECUTION_LOG_STREAM_PHASES = exports.EXECUTION_LOG_ACTIVITIES = exports.targetsValidationCheck = exports.targetSummarySentence = exports.targetSummaryOf = exports.targetSummaryLabel = exports.targetsUnavailableFrom = exports.targetsUnavailable = exports.targetComparisonSentence = exports.targetComparisonLevel = exports.targetComparisonLabel = exports.TARGETS_NOT_COUNTED = exports.TARGETS_NO_SCOPE = exports.TARGET_COUNT_ABSENCES = exports.TARGET_COMPARISON_KINDS = exports.singleTarget = exports.scopeFailureDetail = exports.noTargetsResolved = exports.isTargetCountAbsence = exports.eligibleTargetCount = exports.compareTargets = exports.REFUSAL_CODES = exports.isRefusalCode = exports.isDecisionCode = exports.isCheckLevel = exports.DECISION_CODES = exports.CHECK_LEVELS = exports.SUBSCRIPTION_ID_FIELD_RENAMED = void 0;
1486
1876
  exports.executionLogStreamSentence = executionLogStreamSentence;
1487
1877
  exports.actionRunnabilityCopy = actionRunnabilityCopy;
1488
1878
  exports.actionRunnabilityHeadline = actionRunnabilityHeadline;
@@ -1493,6 +1883,42 @@ var require_execution = __commonJS({
1493
1883
  var brand_1 = require_brand();
1494
1884
  exports.SUBSCRIPTION_ID_FIELD_RENAMED = "`subscriptionId` is not accepted on this route. It named an `azure_subscription.id` while this route has always resolved a `connection_scope.id`. The two were the same value while one shim gave a scope the registry row's id; a scope discovered since has an id of its own, and the registry id is either nobody's scope or another connector's. Send `connectionScopeId` \u2014 the value `ExecutionDto.connectionScopeId` reports for a run.";
1495
1885
  var validation_1 = require_validation();
1886
+ Object.defineProperty(exports, "CHECK_LEVELS", {
1887
+ enumerable: true,
1888
+ get: /* @__PURE__ */ __name(function() {
1889
+ return validation_1.CHECK_LEVELS;
1890
+ }, "get")
1891
+ });
1892
+ Object.defineProperty(exports, "DECISION_CODES", {
1893
+ enumerable: true,
1894
+ get: /* @__PURE__ */ __name(function() {
1895
+ return validation_1.DECISION_CODES;
1896
+ }, "get")
1897
+ });
1898
+ Object.defineProperty(exports, "isCheckLevel", {
1899
+ enumerable: true,
1900
+ get: /* @__PURE__ */ __name(function() {
1901
+ return validation_1.isCheckLevel;
1902
+ }, "get")
1903
+ });
1904
+ Object.defineProperty(exports, "isDecisionCode", {
1905
+ enumerable: true,
1906
+ get: /* @__PURE__ */ __name(function() {
1907
+ return validation_1.isDecisionCode;
1908
+ }, "get")
1909
+ });
1910
+ Object.defineProperty(exports, "isRefusalCode", {
1911
+ enumerable: true,
1912
+ get: /* @__PURE__ */ __name(function() {
1913
+ return validation_1.isRefusalCode;
1914
+ }, "get")
1915
+ });
1916
+ Object.defineProperty(exports, "REFUSAL_CODES", {
1917
+ enumerable: true,
1918
+ get: /* @__PURE__ */ __name(function() {
1919
+ return validation_1.REFUSAL_CODES;
1920
+ }, "get")
1921
+ });
1496
1922
  Object.defineProperty(exports, "compareTargets", {
1497
1923
  enumerable: true,
1498
1924
  get: /* @__PURE__ */ __name(function() {
@@ -1634,6 +2060,7 @@ var require_execution = __commonJS({
1634
2060
  }
1635
2061
  __name(executionLogStreamSentence, "executionLogStreamSentence");
1636
2062
  var ACTION_RUNNABILITY_CAUSE_KIND_SET = {
2063
+ catalog_blocked: true,
1637
2064
  connector_unregistered: true,
1638
2065
  package: true,
1639
2066
  runner_pool: true,
@@ -1643,8 +2070,12 @@ var require_execution = __commonJS({
1643
2070
  };
1644
2071
  exports.ACTION_RUNNABILITY_CAUSE_KINDS = Object.keys(ACTION_RUNNABILITY_CAUSE_KIND_SET);
1645
2072
  exports.CONNECTOR_UNAVAILABLE_MESSAGE = "This action is not yet available to run.";
2073
+ var CATALOG_BLOCKED_CAUSE = "This workspace does not permit this action.";
2074
+ var CATALOG_BLOCKED_REMEDY = "An administrator of this workspace decides which catalog actions may run here, and can allow it.";
1646
2075
  function actionRunnabilityCopy(cause) {
1647
2076
  switch (cause.kind) {
2077
+ case "catalog_blocked":
2078
+ return `${CATALOG_BLOCKED_CAUSE} ${CATALOG_BLOCKED_REMEDY}`;
1648
2079
  case "package":
1649
2080
  return (0, packages_2.actionUnrunnableCopy)(cause.detail);
1650
2081
  case "runner_pool": {
@@ -1676,7 +2107,7 @@ var require_execution = __commonJS({
1676
2107
  }
1677
2108
  __name(describeRoutingPlaces, "describeRoutingPlaces");
1678
2109
  function runnerPoolClauses(cause) {
1679
- const provenance = cause.provenance ? `is ${packages_1.PACKAGE_PROVENANCE_CLAUSES[cause.provenance]}` : (
2110
+ const provenance2 = cause.provenance ? `is ${packages_1.PACKAGE_PROVENANCE_CLAUSES[cause.provenance]}` : (
1680
2111
  // every other value this sentence could take is a claim about where bytes came from.
1681
2112
  "has no recorded provenance"
1682
2113
  );
@@ -1694,7 +2125,7 @@ var require_execution = __commonJS({
1694
2125
  * credential it acts with. A refusal that dropped those would be telling an admin the
1695
2126
  * decision no longer matters.
1696
2127
  */
1697
- cause: `${provenance}, and would run on the private runner pool "${cause.poolName}", because that is where runs using this connection execute \u2014 on a machine you own, in a container that shares its host's kernel and acts with this connection's credential.`,
2128
+ cause: `${provenance2}, and would run on the private runner pool "${cause.poolName}", because that is where runs using this connection execute \u2014 on a machine you own, in a container that shares its host's kernel and acts with this connection's credential.`,
1698
2129
  remedy: `${acceptedClause(cause.accepts)} ${POOL_CONSENT_REMEDY}`
1699
2130
  };
1700
2131
  }
@@ -1733,6 +2164,8 @@ var require_execution = __commonJS({
1733
2164
  var POOL_CONSENT_REMEDY = `${brand_1.BRAND_SHORT_NAME} does not vouch for what runs on your machines \u2014 the owner of this pool does. An administrator can accept this provenance under Settings \u2192 Runners, and until they do, nothing of this provenance will be sent there. Built-in actions still run in that pool.`;
1734
2165
  function actionRunnabilityHeadline(cause) {
1735
2166
  switch (cause.kind) {
2167
+ case "catalog_blocked":
2168
+ return "this workspace does not permit its action \u2014 an administrator of this workspace can allow it";
1736
2169
  case "package":
1737
2170
  return (0, packages_2.actionUnrunnableHeadline)(cause.detail);
1738
2171
  case "runner_pool":
@@ -1750,6 +2183,8 @@ var require_execution = __commonJS({
1750
2183
  __name(actionRunnabilityHeadline, "actionRunnabilityHeadline");
1751
2184
  function actionRunnabilityRefusal(cause) {
1752
2185
  switch (cause.kind) {
2186
+ case "catalog_blocked":
2187
+ return `${CATALOG_BLOCKED_CAUSE} ${CATALOG_BLOCKED_REMEDY} Nothing was started.`;
1753
2188
  case "package":
1754
2189
  return (0, packages_2.actionUnrunnableRefusal)(cause.detail, cause);
1755
2190
  case "runner_pool": {
@@ -1774,6 +2209,80 @@ var require_execution = __commonJS({
1774
2209
  }
1775
2210
  });
1776
2211
 
2212
+ // ../contracts/dist/decisions.js
2213
+ var require_decisions = __commonJS({
2214
+ "../contracts/dist/decisions.js"(exports) {
2215
+ "use strict";
2216
+ Object.defineProperty(exports, "__esModule", {
2217
+ value: true
2218
+ });
2219
+ exports.DECISION_ACKNOWLEDGEMENT = void 0;
2220
+ exports.recordedDecisionsOf = recordedDecisionsOf;
2221
+ exports.raisedDecisionCodes = raisedDecisionCodes;
2222
+ exports.decisionMismatch = decisionMismatch;
2223
+ exports.decisionMismatchSentence = decisionMismatchSentence;
2224
+ var validation_1 = require_validation();
2225
+ function recordedDecisionsOf(stored) {
2226
+ if (!Array.isArray(stored)) return [];
2227
+ const read = [];
2228
+ for (const entry of stored) {
2229
+ if (typeof entry !== "object" || entry === null) continue;
2230
+ const { code, acknowledgedBy, acknowledgedAt } = entry;
2231
+ if (!(0, validation_1.isDecisionCode)(code)) continue;
2232
+ if (typeof acknowledgedBy !== "string" || acknowledgedBy.length === 0) continue;
2233
+ if (typeof acknowledgedAt !== "string" || Number.isNaN(Date.parse(acknowledgedAt))) continue;
2234
+ if (read.some((decision) => decision.code === code)) continue;
2235
+ read.push({
2236
+ code,
2237
+ acknowledgedBy,
2238
+ acknowledgedAt
2239
+ });
2240
+ }
2241
+ return read;
2242
+ }
2243
+ __name(recordedDecisionsOf, "recordedDecisionsOf");
2244
+ function raisedDecisionCodes(checks) {
2245
+ const raised = [];
2246
+ for (const check of checks) {
2247
+ if (check.decision && !raised.includes(check.decision)) raised.push(check.decision);
2248
+ }
2249
+ return raised;
2250
+ }
2251
+ __name(raisedDecisionCodes, "raisedDecisionCodes");
2252
+ function decisionMismatch(raised, acknowledged) {
2253
+ const unacknowledged = raised.filter((code) => !acknowledged.includes(code));
2254
+ const notRaised = acknowledged.filter((code) => !raised.includes(code));
2255
+ return unacknowledged.length === 0 && notRaised.length === 0 ? null : {
2256
+ unacknowledged,
2257
+ notRaised
2258
+ };
2259
+ }
2260
+ __name(decisionMismatch, "decisionMismatch");
2261
+ function decisionMismatchSentence(mismatch) {
2262
+ const parts = [];
2263
+ if (mismatch.unacknowledged.length > 0) {
2264
+ parts.push(`the plan raised ${quoteCodes(mismatch.unacknowledged)}, which ${mismatch.unacknowledged.length === 1 ? "was" : "were"} not acknowledged`);
2265
+ }
2266
+ if (mismatch.notRaised.length > 0) {
2267
+ parts.push(`${quoteCodes(mismatch.notRaised)} ${mismatch.notRaised.length === 1 ? "was" : "were"} acknowledged but the plan does not raise ${mismatch.notRaised.length === 1 ? "it" : "them"}`);
2268
+ }
2269
+ return `This run was not submitted: ${parts.join(", and ")}. The checks were run again at submit and decide what needs acknowledging \u2014 review the plan and submit again.`;
2270
+ }
2271
+ __name(decisionMismatchSentence, "decisionMismatchSentence");
2272
+ function quoteCodes(codes) {
2273
+ return codes.map((code) => `"${code}"`).join(", ");
2274
+ }
2275
+ __name(quoteCodes, "quoteCodes");
2276
+ exports.DECISION_ACKNOWLEDGEMENT = {
2277
+ "disk.recentlyDetached": "Delete it even though it was detached recently",
2278
+ "disk.customerKey": "Delete it \u2014 restoring later depends on this key staying enabled",
2279
+ "disk.customerKeyUnverified": "Delete it without confirmation that the key is enabled",
2280
+ "disk.guestEncrypted": "Delete it \u2014 restoring later depends on the guest's encryption secrets",
2281
+ "disk.noSnapshot": "Delete it with no snapshot \u2014 this cannot be undone"
2282
+ };
2283
+ }
2284
+ });
2285
+
1777
2286
  // ../contracts/dist/execution-site.js
1778
2287
  var require_execution_site = __commonJS({
1779
2288
  "../contracts/dist/execution-site.js"(exports) {
@@ -1858,7 +2367,13 @@ var require_resources = __commonJS({
1858
2367
  Object.defineProperty(exports, "__esModule", {
1859
2368
  value: true
1860
2369
  });
1861
- exports.DISCOVERY_TYPE_PATTERN = exports.DISCOVERABLE_RESOURCE_TYPES = exports.DISCOVERY_VOCABULARY = void 0;
2370
+ exports.DISCOVERY_TYPE_PATTERN = exports.LEGACY_DISCOVERY_PROVIDER = exports.DISCOVERY_VOCABULARY = void 0;
2371
+ exports.discoveryTypesOf = discoveryTypesOf;
2372
+ exports.declaredDiscoveryTypes = declaredDiscoveryTypes;
2373
+ exports.liveDiscoveryTypes = liveDiscoveryTypes;
2374
+ exports.canonicalDiscoveryId = canonicalDiscoveryId;
2375
+ exports.resolveDiscoveryType = resolveDiscoveryType;
2376
+ exports.isSecretReference = isSecretReference;
1862
2377
  exports.parseDiscoveryType = parseDiscoveryType;
1863
2378
  exports.formatDiscoveryType = formatDiscoveryType;
1864
2379
  exports.discoveryTypeSpec = discoveryTypeSpec;
@@ -1874,16 +2389,20 @@ var require_resources = __commonJS({
1874
2389
  resourceGroup: {
1875
2390
  requires: [],
1876
2391
  routable: false,
1877
- identity: null
2392
+ identity: null,
2393
+ secretRef: false
1878
2394
  },
1879
2395
  // ROUTABLE BUT NOT NAMABLE, and the two are different questions. A rule matches a vault by
1880
2396
  // its NAME and its tags, which discovery supplies; an ARM vault id needs the resource group
1881
2397
  // this vocabulary never asks for (`requires` is empty so vaults list subscription-wide). See
1882
2398
  // `identity` on the spec above — null is the honest answer, not an omission.
2399
+ // NOT a secret reference: a vault CONTAINS secrets, and naming one reveals nothing a run
2400
+ // could read. The secret inside it is the reference (`keyVaultSecret` below).
1883
2401
  keyVault: {
1884
2402
  requires: [],
1885
2403
  routable: true,
1886
- identity: null
2404
+ identity: null,
2405
+ secretRef: false
1887
2406
  },
1888
2407
  // ★ A DATA-PLANE URI, NOT AN ARM PATH, AND IT IS WHAT DISCOVERY ALREADY RETURNS. The Key
1889
2408
  // Vault SDK reports `SecretProperties.id` as `https://<vault>.vault.azure.net/secrets/<name>`
@@ -1895,27 +2414,33 @@ var require_resources = __commonJS({
1895
2414
  "vault"
1896
2415
  ],
1897
2416
  routable: true,
1898
- identity: "https://{vault}.vault.azure.net/secrets/{name}"
2417
+ identity: "https://{vault}.vault.azure.net/secrets/{name}",
2418
+ // ★ THE ONE TYPE TODAY WHOSE VALUE POINTS AT A SECRET (docs/122 §3.5). The parameter holds
2419
+ // the secret's NAME within its vault; nothing in the platform ever reads the value behind it.
2420
+ secretRef: true
1899
2421
  },
1900
2422
  // A catalogue value. Nothing is reached by naming a region.
1901
2423
  location: {
1902
2424
  requires: [],
1903
2425
  routable: false,
1904
- identity: null
2426
+ identity: null,
2427
+ secretRef: false
1905
2428
  },
1906
2429
  managedDisk: {
1907
2430
  requires: [
1908
2431
  "resourceGroup"
1909
2432
  ],
1910
2433
  routable: true,
1911
- identity: "/subscriptions/{subscription}/resourceGroups/{resourceGroup}/providers/Microsoft.Compute/disks/{name}"
2434
+ identity: "/subscriptions/{subscription}/resourceGroups/{resourceGroup}/providers/Microsoft.Compute/disks/{name}",
2435
+ secretRef: false
1912
2436
  },
1913
2437
  sqlServer: {
1914
2438
  requires: [
1915
2439
  "resourceGroup"
1916
2440
  ],
1917
2441
  routable: true,
1918
- identity: "/subscriptions/{subscription}/resourceGroups/{resourceGroup}/providers/Microsoft.Sql/servers/{name}"
2442
+ identity: "/subscriptions/{subscription}/resourceGroups/{resourceGroup}/providers/Microsoft.Sql/servers/{name}",
2443
+ secretRef: false
1919
2444
  },
1920
2445
  sqlDatabase: {
1921
2446
  requires: [
@@ -1923,7 +2448,8 @@ var require_resources = __commonJS({
1923
2448
  "server"
1924
2449
  ],
1925
2450
  routable: true,
1926
- identity: "/subscriptions/{subscription}/resourceGroups/{resourceGroup}/providers/Microsoft.Sql/servers/{server}/databases/{name}"
2451
+ identity: "/subscriptions/{subscription}/resourceGroups/{resourceGroup}/providers/Microsoft.Sql/servers/{server}/databases/{name}",
2452
+ secretRef: false
1927
2453
  },
1928
2454
  // The one type where LISTING and IDENTIFYING are different questions. It lists
1929
2455
  // SUBSCRIPTION-WIDE when no resource group is given (`virtualMachines.listAll()`,
@@ -1943,7 +2469,8 @@ var require_resources = __commonJS({
1943
2469
  "resourceGroup"
1944
2470
  ],
1945
2471
  routable: true,
1946
- identity: "/subscriptions/{subscription}/resourceGroups/{resourceGroup}/providers/Microsoft.Compute/virtualMachines/{name}"
2472
+ identity: "/subscriptions/{subscription}/resourceGroups/{resourceGroup}/providers/Microsoft.Compute/virtualMachines/{name}",
2473
+ secretRef: false
1947
2474
  },
1948
2475
  // The sizes ONE machine can actually be resized to right now — not the region's SKU
1949
2476
  // catalogue. `requires` is genuinely both fields' answer here: the safe set cannot be
@@ -1957,16 +2484,53 @@ var require_resources = __commonJS({
1957
2484
  "vm"
1958
2485
  ],
1959
2486
  routable: false,
1960
- identity: null
2487
+ identity: null,
2488
+ secretRef: false
1961
2489
  }
1962
2490
  };
1963
- exports.DISCOVERY_VOCABULARY = {
2491
+ var DECLARED_DISCOVERY = {
1964
2492
  azure: AZURE_DISCOVERY_TYPES,
1965
2493
  aws: {},
1966
2494
  gcp: {},
1967
2495
  terraform: {}
1968
2496
  };
1969
- exports.DISCOVERABLE_RESOURCE_TYPES = Object.keys(AZURE_DISCOVERY_TYPES);
2497
+ exports.DISCOVERY_VOCABULARY = DECLARED_DISCOVERY;
2498
+ exports.LEGACY_DISCOVERY_PROVIDER = "azure";
2499
+ function discoveryTypesOf(provider) {
2500
+ return Object.keys(exports.DISCOVERY_VOCABULARY[provider]).map((type) => formatDiscoveryType(provider, type));
2501
+ }
2502
+ __name(discoveryTypesOf, "discoveryTypesOf");
2503
+ function declaredDiscoveryTypes() {
2504
+ return enums_1.PROVIDERS.flatMap(discoveryTypesOf);
2505
+ }
2506
+ __name(declaredDiscoveryTypes, "declaredDiscoveryTypes");
2507
+ function liveDiscoveryTypes(registered) {
2508
+ const providers = new Set(registered);
2509
+ return enums_1.PROVIDERS.filter((provider) => providers.has(provider)).flatMap(discoveryTypesOf);
2510
+ }
2511
+ __name(liveDiscoveryTypes, "liveDiscoveryTypes");
2512
+ function canonicalDiscoveryId(value) {
2513
+ const parsed = parseDiscoveryType(value.trim(), exports.LEGACY_DISCOVERY_PROVIDER);
2514
+ return parsed ? formatDiscoveryType(parsed.provider, parsed.type) : null;
2515
+ }
2516
+ __name(canonicalDiscoveryId, "canonicalDiscoveryId");
2517
+ function resolveDiscoveryType(value) {
2518
+ const parsed = parseDiscoveryType(value.trim(), exports.LEGACY_DISCOVERY_PROVIDER);
2519
+ if (!parsed) return null;
2520
+ const spec = discoveryTypeSpec(parsed.provider, parsed.type);
2521
+ if (!spec) return null;
2522
+ return {
2523
+ id: formatDiscoveryType(parsed.provider, parsed.type),
2524
+ provider: parsed.provider,
2525
+ type: parsed.type,
2526
+ spec
2527
+ };
2528
+ }
2529
+ __name(resolveDiscoveryType, "resolveDiscoveryType");
2530
+ function isSecretReference(discover) {
2531
+ return discover ? resolveDiscoveryType(discover)?.spec.secretRef === true : false;
2532
+ }
2533
+ __name(isSecretReference, "isSecretReference");
1970
2534
  exports.DISCOVERY_TYPE_PATTERN = /^(?:([a-z][a-z0-9]*)\/)?([a-z][a-zA-Z0-9]{0,63})$/;
1971
2535
  function parseDiscoveryType(value, fallbackProvider) {
1972
2536
  const match = exports.DISCOVERY_TYPE_PATTERN.exec(value);
@@ -1990,7 +2554,9 @@ var require_resources = __commonJS({
1990
2554
  }
1991
2555
  __name(formatDiscoveryType, "formatDiscoveryType");
1992
2556
  function discoveryTypeSpec(provider, type) {
1993
- return exports.DISCOVERY_VOCABULARY[provider]?.[type];
2557
+ if (!Object.prototype.hasOwnProperty.call(exports.DISCOVERY_VOCABULARY, provider)) return void 0;
2558
+ const vocabulary = exports.DISCOVERY_VOCABULARY[provider];
2559
+ return Object.prototype.hasOwnProperty.call(vocabulary, type) ? vocabulary[type] : void 0;
1994
2560
  }
1995
2561
  __name(discoveryTypeSpec, "discoveryTypeSpec");
1996
2562
  function dependsOnLookup(keys, dependsOn) {
@@ -2291,6 +2857,28 @@ var require_runs = __commonJS({
2291
2857
  }
2292
2858
  });
2293
2859
 
2860
+ // ../contracts/dist/evidence-pack.js
2861
+ var require_evidence_pack = __commonJS({
2862
+ "../contracts/dist/evidence-pack.js"(exports) {
2863
+ "use strict";
2864
+ Object.defineProperty(exports, "__esModule", {
2865
+ value: true
2866
+ });
2867
+ exports.EVIDENCE_EXPORTED_AUDIT_ACTION = exports.EVIDENCE_DIGEST_HEADER = void 0;
2868
+ exports.evidencePackFilename = evidencePackFilename;
2869
+ exports.EVIDENCE_DIGEST_HEADER = "X-Evidence-Digest";
2870
+ exports.EVIDENCE_EXPORTED_AUDIT_ACTION = "operation.evidence_exported";
2871
+ function evidencePackFilename(operationKey, from, to) {
2872
+ return `${operationKey}-evidence-${compactInstant(from)}-${compactInstant(to)}.json`;
2873
+ }
2874
+ __name(evidencePackFilename, "evidencePackFilename");
2875
+ function compactInstant(iso) {
2876
+ return iso.replace(/\.\d+(?=Z|[+-]\d{2}:?\d{2}$)/, "").replace(/[-:]/g, "");
2877
+ }
2878
+ __name(compactInstant, "compactInstant");
2879
+ }
2880
+ });
2881
+
2294
2882
  // ../contracts/dist/package-egress.js
2295
2883
  var require_package_egress = __commonJS({
2296
2884
  "../contracts/dist/package-egress.js"(exports) {
@@ -2555,9 +3143,9 @@ var require_runners = __commonJS({
2555
3143
  authored: false
2556
3144
  };
2557
3145
  exports.RUNNER_POOL_DEFAULT_ACCEPTS_PROVENANCE = package_provenance_1.PACKAGE_PROVENANCE_KINDS.filter((kind) => exports.RUNNER_POOL_ACCEPTS_PROVENANCE_BY_DEFAULT[kind]);
2558
- function poolAcceptsProvenance(accepts, provenance) {
2559
- if (provenance === null) return false;
2560
- return accepts.includes(provenance);
3146
+ function poolAcceptsProvenance(accepts, provenance2) {
3147
+ if (provenance2 === null) return false;
3148
+ return accepts.includes(provenance2);
2561
3149
  }
2562
3150
  __name(poolAcceptsProvenance, "poolAcceptsProvenance");
2563
3151
  function containerNameFor(runnerName) {
@@ -3385,11 +3973,11 @@ var require_workspace_domain = __commonJS({
3385
3973
  "held_elsewhere"
3386
3974
  ];
3387
3975
  exports.DOMAIN_VERIFICATION_RECORD_PREFIX = "runveric-domain-verification=";
3388
- function domainVerificationRecord(domain, token) {
3976
+ function domainVerificationRecord(domain, token2) {
3389
3977
  return {
3390
3978
  name: domain,
3391
3979
  type: "TXT",
3392
- value: `${exports.DOMAIN_VERIFICATION_RECORD_PREFIX}${token}`
3980
+ value: `${exports.DOMAIN_VERIFICATION_RECORD_PREFIX}${token2}`
3393
3981
  };
3394
3982
  }
3395
3983
  __name(domainVerificationRecord, "domainVerificationRecord");
@@ -3572,9 +4160,16 @@ var require_dist = __commonJS({
3572
4160
  __exportStar(require_tenant_users(), exports);
3573
4161
  __exportStar(require_invitations(), exports);
3574
4162
  __exportStar(require_groups(), exports);
4163
+ __exportStar(require_catalog_policy(), exports);
3575
4164
  __exportStar(require_catalog(), exports);
4165
+ __exportStar(require_catalog_release(), exports);
4166
+ __exportStar(require_action_repository(), exports);
4167
+ __exportStar(require_catalog_release_spec(), exports);
4168
+ __exportStar(require_catalog_lock(), exports);
4169
+ __exportStar(require_catalog_manifest_placeholders(), exports);
3576
4170
  __exportStar(require_change_control(), exports);
3577
4171
  __exportStar(require_execution(), exports);
4172
+ __exportStar(require_decisions(), exports);
3578
4173
  __exportStar(require_execution_site(), exports);
3579
4174
  __exportStar(require_run_placement(), exports);
3580
4175
  __exportStar(require_connections(), exports);
@@ -3585,6 +4180,7 @@ var require_dist = __commonJS({
3585
4180
  __exportStar(require_scheduling(), exports);
3586
4181
  __exportStar(require_operations(), exports);
3587
4182
  __exportStar(require_runs(), exports);
4183
+ __exportStar(require_evidence_pack(), exports);
3588
4184
  __exportStar(require_package_tier(), exports);
3589
4185
  __exportStar(require_package_provenance(), exports);
3590
4186
  __exportStar(require_packages(), exports);
@@ -5095,7 +5691,7 @@ var require_authoring_lint = __commonJS({
5095
5691
  if (!isRecord(permission) || !Array.isArray(permission.actions)) return;
5096
5692
  permission.actions.forEach((action, actionIndex) => {
5097
5693
  if (typeof action !== "string" || action.includes("*")) return;
5098
- if (permissionTokens(action).some((token) => haystack.includes(token))) return;
5694
+ if (permissionTokens(action).some((token2) => haystack.includes(token2))) return;
5099
5695
  findings.push({
5100
5696
  severity: "warning",
5101
5697
  rule: "package/permission-possibly-unused",
@@ -6941,67 +7537,7 @@ var require_dist2 = __commonJS({
6941
7537
  });
6942
7538
 
6943
7539
  // package.json
6944
- var version = "0.1.2";
6945
-
6946
- // src/check.ts
6947
- var import_action_manifest = __toESM(require_dist2());
6948
- import { resolve } from "path";
6949
-
6950
- // src/package-dir.ts
6951
- import { readdir, readFile, stat } from "fs/promises";
6952
- import { join, relative, sep } from "path";
6953
- var IGNORED_DIRECTORIES = /* @__PURE__ */ new Set([
6954
- ".git",
6955
- ".runveric",
6956
- "node_modules",
6957
- ".venv",
6958
- "__pycache__",
6959
- ".DS_Store"
6960
- ]);
6961
- var MAX_FILES = 2e3;
6962
- var MAX_TOTAL_BYTES = 16 * 1024 * 1024;
6963
- var PackageDirectoryError = class extends Error {
6964
- static {
6965
- __name(this, "PackageDirectoryError");
6966
- }
6967
- };
6968
- async function readPackageDirectory(root) {
6969
- const properties = await stat(root).catch(() => null);
6970
- if (!properties?.isDirectory()) {
6971
- throw new PackageDirectoryError(`${root} is not a directory.`);
6972
- }
6973
- const files = /* @__PURE__ */ new Map();
6974
- let totalBytes = 0;
6975
- const walk = /* @__PURE__ */ __name(async (directory) => {
6976
- for (const entry of await readdir(directory, {
6977
- withFileTypes: true
6978
- })) {
6979
- if (IGNORED_DIRECTORIES.has(entry.name)) continue;
6980
- const full = join(directory, entry.name);
6981
- if (entry.isDirectory()) {
6982
- await walk(full);
6983
- continue;
6984
- }
6985
- if (!entry.isFile()) continue;
6986
- if (files.size >= MAX_FILES) {
6987
- throw new PackageDirectoryError(`This directory holds more than ${MAX_FILES} files. That is almost certainly not a single action package \u2014 run this from the package directory itself.`);
6988
- }
6989
- const contents = await readFile(full);
6990
- totalBytes += contents.byteLength;
6991
- if (totalBytes > MAX_TOTAL_BYTES) {
6992
- throw new PackageDirectoryError(`This directory holds more than ${MAX_TOTAL_BYTES / (1024 * 1024)}MB. An action package is source, not artifacts \u2014 add build output to .gitignore.`);
6993
- }
6994
- files.set(toPackagePath(root, full), contents);
6995
- }
6996
- }, "walk");
6997
- await walk(root);
6998
- return files;
6999
- }
7000
- __name(readPackageDirectory, "readPackageDirectory");
7001
- function toPackagePath(root, full) {
7002
- return relative(root, full).split(sep).join("/");
7003
- }
7004
- __name(toPackagePath, "toPackagePath");
7540
+ var version = "0.2.0";
7005
7541
 
7006
7542
  // src/report.ts
7007
7543
  var useColour = process.stdout.isTTY === true && !process.env.NO_COLOR;
@@ -7110,10 +7646,515 @@ function printResult(result) {
7110
7646
  }
7111
7647
  __name(printResult, "printResult");
7112
7648
 
7113
- // src/check.ts
7649
+ // src/catalog/publish-action.ts
7650
+ var import_contracts2 = __toESM(require_dist());
7651
+ import { readFileSync } from "fs";
7652
+ import { dirname, resolve } from "path";
7653
+ import { parse as parseYaml } from "yaml";
7654
+
7655
+ // src/catalog/artifact.ts
7114
7656
  var import_contracts = __toESM(require_dist());
7657
+ import { execFileSync } from "child_process";
7658
+ import { createHash } from "crypto";
7659
+ function parseRepository(ref, flag) {
7660
+ if (!import_contracts.ARTIFACT_REPOSITORY_PATTERN.test(ref)) {
7661
+ throw new Error(`${flag} "${ref}" is not a bare registry repository (host[:port]/path, lowercase, no scheme, no tag, no digest). A catalog artifact is addressed by digest; tags are for humans.`);
7662
+ }
7663
+ const cut = ref.indexOf("/");
7664
+ return {
7665
+ ref,
7666
+ host: ref.slice(0, cut),
7667
+ path: ref.slice(cut + 1),
7668
+ namespace: ref.slice(0, ref.lastIndexOf("/"))
7669
+ };
7670
+ }
7671
+ __name(parseRepository, "parseRepository");
7672
+ var isDigest = /* @__PURE__ */ __name((value) => import_contracts.ARTIFACT_DIGEST_PATTERN.test(value), "isDigest");
7673
+ var TAG_PATTERN = /^[A-Za-z0-9_][A-Za-z0-9._-]{0,127}$/;
7674
+ function requireTag(tag, origin2) {
7675
+ if (!TAG_PATTERN.test(tag)) {
7676
+ throw new Error(`${origin2} gives the tag "${tag}", which a registry cannot hold: letters, digits, "_", "." and "-", starting with a letter, digit or "_", at most 128 characters. Pass --tag.`);
7677
+ }
7678
+ }
7679
+ __name(requireTag, "requireTag");
7680
+ var LOOPBACK = /^(localhost|127\.0\.0\.1|\[::1\])(:[0-9]+)?$/;
7681
+ var origin = /* @__PURE__ */ __name((host) => `${LOOPBACK.test(host) ? "http" : "https"}://${host}`, "origin");
7682
+ var sha256 = /* @__PURE__ */ __name((bytes) => `sha256:${createHash("sha256").update(bytes).digest("hex")}`, "sha256");
7683
+ var EMPTY_CONFIG = Buffer.from("{}");
7684
+ var EMPTY_CONFIG_DESCRIPTOR = {
7685
+ mediaType: "application/vnd.oci.empty.v1+json",
7686
+ digest: sha256(EMPTY_CONFIG),
7687
+ size: EMPTY_CONFIG.length
7688
+ };
7689
+ var OCI_MANIFEST = "application/vnd.oci.image.manifest.v1+json";
7690
+ function provenance(flags, dir) {
7691
+ const git = /* @__PURE__ */ __name((...args) => {
7692
+ try {
7693
+ return execFileSync("git", [
7694
+ "-C",
7695
+ dir,
7696
+ ...args
7697
+ ], {
7698
+ encoding: "utf8",
7699
+ stdio: [
7700
+ "ignore",
7701
+ "pipe",
7702
+ "ignore"
7703
+ ]
7704
+ }).trim();
7705
+ } catch {
7706
+ return "";
7707
+ }
7708
+ }, "git");
7709
+ const actions = process.env.GITHUB_SERVER_URL && process.env.GITHUB_REPOSITORY ? `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}` : "";
7710
+ const repository = flags.repository ?? (actions || git("remote", "get-url", "origin"));
7711
+ const commit = flags.commit ?? (process.env.GITHUB_SHA || git("rev-parse", "HEAD"));
7712
+ if (!repository || !commit) {
7713
+ throw new Error("Provenance is required and could not be determined. Pass --repository <url> and --commit <sha> (in GitHub Actions, GITHUB_REPOSITORY and GITHUB_SHA supply them).");
7714
+ }
7715
+ return {
7716
+ repository,
7717
+ commit,
7718
+ publishedAt: (/* @__PURE__ */ new Date()).toISOString()
7719
+ };
7720
+ }
7721
+ __name(provenance, "provenance");
7722
+ function buildArtifact(document) {
7723
+ const documentBytes = Buffer.from(`${JSON.stringify(document, null, 2)}
7724
+ `);
7725
+ const manifest = {
7726
+ schemaVersion: 2,
7727
+ mediaType: OCI_MANIFEST,
7728
+ artifactType: import_contracts.CATALOG_ARTIFACT_MEDIA_TYPE,
7729
+ config: EMPTY_CONFIG_DESCRIPTOR,
7730
+ layers: [
7731
+ {
7732
+ mediaType: import_contracts.CATALOG_ARTIFACT_MEDIA_TYPE,
7733
+ digest: sha256(documentBytes),
7734
+ size: documentBytes.length
7735
+ }
7736
+ ],
7737
+ /*
7738
+ * ★ SOURCE AND REVISION ARE FOR THE REGISTRY, NOT THE RESOLVER (docs/127 A.1). GHCR connects
7739
+ * a package to its repository through `org.opencontainers.image.source`, and that link is what
7740
+ * lets the repository's workflow token keep publishing to the package its first run created.
7741
+ * The resolver reads none of these — the document's own provenance is the record — so an
7742
+ * artifact published without them still resolves.
7743
+ */
7744
+ annotations: {
7745
+ "org.opencontainers.image.created": document.provenance.publishedAt,
7746
+ "org.opencontainers.image.source": document.provenance.repository,
7747
+ "org.opencontainers.image.revision": document.provenance.commit
7748
+ }
7749
+ };
7750
+ const manifestBytes = Buffer.from(JSON.stringify(manifest));
7751
+ return {
7752
+ documentBytes,
7753
+ manifestBytes,
7754
+ manifestDigest: sha256(manifestBytes)
7755
+ };
7756
+ }
7757
+ __name(buildArtifact, "buildArtifact");
7758
+ function basicCredential() {
7759
+ const pat = process.env.GHCR_TOKEN;
7760
+ if (pat) return Buffer.from(`x:${pat}`).toString("base64");
7761
+ const workflow = process.env.GITHUB_TOKEN;
7762
+ if (workflow) return Buffer.from(`${process.env.GITHUB_ACTOR || "x"}:${workflow}`).toString("base64");
7763
+ return void 0;
7764
+ }
7765
+ __name(basicCredential, "basicCredential");
7766
+ var tokens = /* @__PURE__ */ new Map();
7767
+ async function token(repository, challenge) {
7768
+ const field = /* @__PURE__ */ __name((name) => new RegExp(`${name}="([^"]*)"`).exec(challenge ?? "")?.[1], "field");
7769
+ const realm = field("realm");
7770
+ if (!realm) throw new Error(`${repository.host} asked for credentials without a Bearer challenge.`);
7771
+ const url = new URL(realm);
7772
+ const service = field("service");
7773
+ if (service) url.searchParams.set("service", service);
7774
+ url.searchParams.set("scope", `repository:${repository.path}:pull,push`);
7775
+ const credential = basicCredential();
7776
+ const answer = await fetch(url, {
7777
+ headers: credential ? {
7778
+ Authorization: `Basic ${credential}`
7779
+ } : {}
7780
+ });
7781
+ if (!answer.ok) {
7782
+ throw new Error(`${repository.host} refused a push token for ${repository.path}: HTTP ${answer.status}` + (credential ? "." : " \u2014 set GHCR_TOKEN to a token with write:packages, or in a workflow GITHUB_TOKEN with packages: write."));
7783
+ }
7784
+ const body = await answer.json();
7785
+ const value = body.token ?? body.access_token ?? "";
7786
+ tokens.set(repository.path, value);
7787
+ return value;
7788
+ }
7789
+ __name(token, "token");
7790
+ async function call(repository, method, path, { headers = {}, body } = {}) {
7791
+ const url = path.startsWith("http") ? path : `${origin(repository.host)}/v2/${repository.path}/${path}`;
7792
+ const send = /* @__PURE__ */ __name((bearer) => fetch(url, {
7793
+ method,
7794
+ body,
7795
+ redirect: "follow",
7796
+ headers: {
7797
+ ...headers,
7798
+ ...bearer ? {
7799
+ Authorization: `Bearer ${bearer}`
7800
+ } : {}
7801
+ }
7802
+ }), "send");
7803
+ let answer = await send(tokens.get(repository.path));
7804
+ if (answer.status === 401) answer = await send(await token(repository, answer.headers.get("www-authenticate")));
7805
+ return answer;
7806
+ }
7807
+ __name(call, "call");
7808
+ async function pushBlob(repository, bytes) {
7809
+ const digest = sha256(bytes);
7810
+ if ((await call(repository, "HEAD", `blobs/${digest}`)).ok) return digest;
7811
+ const started = await call(repository, "POST", "blobs/uploads/");
7812
+ if (started.status !== 202) throw new Error(`${repository.host} refused an upload: HTTP ${started.status}`);
7813
+ const location = new URL(started.headers.get("location") ?? "", `${origin(repository.host)}/`);
7814
+ location.searchParams.set("digest", digest);
7815
+ const put = await call(repository, "PUT", location.toString(), {
7816
+ headers: {
7817
+ "Content-Type": "application/octet-stream",
7818
+ "Content-Length": String(bytes.length)
7819
+ },
7820
+ body: bytes
7821
+ });
7822
+ if (put.status !== 201) throw new Error(`${repository.host} rejected blob ${digest}: HTTP ${put.status} ${await put.text()}`);
7823
+ return digest;
7824
+ }
7825
+ __name(pushBlob, "pushBlob");
7826
+ async function manifestExists(repository, digest) {
7827
+ const answer = await call(repository, "HEAD", `manifests/${digest}`, {
7828
+ headers: {
7829
+ Accept: OCI_MANIFEST
7830
+ }
7831
+ });
7832
+ return answer.ok;
7833
+ }
7834
+ __name(manifestExists, "manifestExists");
7835
+ async function pushArtifact(repository, artifact, tag) {
7836
+ await pushBlob(repository, EMPTY_CONFIG);
7837
+ await pushBlob(repository, artifact.documentBytes);
7838
+ for (const reference of [
7839
+ artifact.manifestDigest,
7840
+ tag
7841
+ ]) {
7842
+ const put = await call(repository, "PUT", `manifests/${reference}`, {
7843
+ headers: {
7844
+ "Content-Type": OCI_MANIFEST
7845
+ },
7846
+ body: artifact.manifestBytes
7847
+ });
7848
+ if (put.status !== 201) {
7849
+ throw new Error(`${repository.host} rejected the manifest (${reference}): HTTP ${put.status} ${await put.text()}`);
7850
+ }
7851
+ }
7852
+ const asked = await call(repository, "HEAD", `manifests/${tag}`, {
7853
+ headers: {
7854
+ Accept: OCI_MANIFEST
7855
+ }
7856
+ });
7857
+ const reported = asked.headers.get("docker-content-digest");
7858
+ if (!asked.ok || !reported) {
7859
+ throw new Error(`${repository.host} could not say what :${tag} resolves to (HTTP ${asked.status}).`);
7860
+ }
7861
+ if (reported !== artifact.manifestDigest) {
7862
+ throw new Error(`${repository.host} reports :${tag} as ${reported}, but the bytes sent hash to ${artifact.manifestDigest}. Something re-encoded the manifest; nothing printed here would name what is stored. Refusing to report a digest.`);
7863
+ }
7864
+ return reported;
7865
+ }
7866
+ __name(pushArtifact, "pushArtifact");
7867
+ var defaultTag = /* @__PURE__ */ __name(() => (/* @__PURE__ */ new Date()).toISOString().replace(/[-:]/g, "").replace(/\..*$/, "").toLowerCase(), "defaultTag");
7868
+
7869
+ // src/catalog/flags.ts
7870
+ function parseCatalogFlags(argv, known, repeated = []) {
7871
+ const single = {};
7872
+ const many = {};
7873
+ for (let index = 0; index < argv.length; index += 1) {
7874
+ const argument = argv[index];
7875
+ const separator = argument.indexOf("=");
7876
+ const name = separator === -1 ? argument.slice(2) : argument.slice(2, separator);
7877
+ if (!argument.startsWith("--") || !known.includes(name)) throw new Error(`Unknown argument: ${argument}`);
7878
+ let value;
7879
+ if (separator !== -1) {
7880
+ value = argument.slice(separator + 1);
7881
+ } else {
7882
+ value = argv[index + 1];
7883
+ index += 1;
7884
+ }
7885
+ if (value === void 0 || value === "" || value.startsWith("--")) throw new Error(`--${name} needs a value.`);
7886
+ if (repeated.includes(name)) (many[name] ??= []).push(value);
7887
+ else single[name] = value;
7888
+ }
7889
+ return {
7890
+ single,
7891
+ many
7892
+ };
7893
+ }
7894
+ __name(parseCatalogFlags, "parseCatalogFlags");
7895
+
7896
+ // src/catalog/publish-action.ts
7897
+ var PUBLISH_ACTION_USAGE = "runveric catalog publish-action --manifest <file.yaml> --to <registry>/<namespace>/<repository>\n [--tag <tag>] [--repository <source url>] [--commit <sha>]";
7898
+ var isMapping = /* @__PURE__ */ __name((value) => typeof value === "object" && value !== null && !Array.isArray(value), "isMapping");
7899
+ async function runPublishAction(argv) {
7900
+ const { single: flags } = parseCatalogFlags(argv, [
7901
+ "manifest",
7902
+ "to",
7903
+ "tag",
7904
+ "repository",
7905
+ "commit"
7906
+ ]);
7907
+ if (!flags.manifest || !flags.to) throw new Error("--manifest and --to are required.");
7908
+ const repository = parseRepository(flags.to, "--to");
7909
+ const path = resolve(flags.manifest);
7910
+ const manifest = readFileSync(path, "utf8");
7911
+ let parsed;
7912
+ try {
7913
+ parsed = parseYaml(manifest);
7914
+ } catch (cause) {
7915
+ throw new Error(`${flags.manifest} is not valid YAML. (${cause instanceof Error ? cause.message : String(cause)})`);
7916
+ }
7917
+ const fields = isMapping(parsed) ? parsed : {};
7918
+ const key = typeof fields.key === "string" && fields.key.length > 0 ? fields.key : null;
7919
+ if (key === null) throw new Error(`${flags.manifest} declares no top-level \`key:\` \u2014 is it a catalog manifest?`);
7920
+ const misnamed = (0, import_contracts2.actionPackageRefusal)(key, repository.ref);
7921
+ if (misnamed !== null) throw new Error(`--to: ${misnamed}`);
7922
+ let tag;
7923
+ if (fields.declares === "definition+version") {
7924
+ const version2 = typeof fields.version === "string" ? fields.version : null;
7925
+ if (version2 === null) throw new Error(`${flags.manifest} declares "definition+version" and no version:.`);
7926
+ if (flags.tag !== void 0 && flags.tag !== version2) {
7927
+ throw new Error(`${flags.manifest} declares version ${version2}, and the tag is ${flags.tag}. Tag the commit ${version2}, or change version: in ${flags.manifest} first.`);
7928
+ }
7929
+ tag = version2;
7930
+ } else {
7931
+ tag = flags.tag ?? defaultTag();
7932
+ }
7933
+ requireTag(tag, flags.tag !== void 0 ? "--tag" : `${flags.manifest}'s version`);
7934
+ const artifact = buildArtifact({
7935
+ schemaVersion: import_contracts2.CATALOG_ACTION_ARTIFACT_SCHEMA_V1,
7936
+ manifest,
7937
+ provenance: provenance(flags, dirname(path))
7938
+ });
7939
+ const digest = await pushArtifact(repository, artifact, tag);
7940
+ process.stderr.write(`\u2713 ${key} published to ${repository.ref}:${tag}
7941
+ `);
7942
+ process.stderr.write(` the registry reports ${digest} \u2014 the digest a release must name.
7943
+ `);
7944
+ process.stdout.write(`${key}=${repository.ref}@${digest}
7945
+ `);
7946
+ }
7947
+ __name(runPublishAction, "runPublishAction");
7948
+
7949
+ // src/catalog/publish-release.ts
7950
+ var import_contracts3 = __toESM(require_dist());
7951
+ import { readFileSync as readFileSync2 } from "fs";
7952
+ import { parse as parseYaml2 } from "yaml";
7953
+ var PUBLISH_RELEASE_USAGE = "runveric catalog publish-release (--from <release.yaml> | --action <key>=<repository>@<digest> \u2026)\n --to <registry>/<namespace>/<repository> [--version <label>]\n [--tag <tag>] [--repository <source url>] [--commit <sha>]";
7954
+ function entriesFromFlags(values) {
7955
+ return values.map((value) => {
7956
+ const match = /^([^=]+)=([^@]+)@(.+)$/.exec(value);
7957
+ if (!match) throw new Error(`--action "${value}" is not <key>=<repository>@<digest>.`);
7958
+ const [, key = "", artifactRef = "", digest = ""] = match;
7959
+ return {
7960
+ key,
7961
+ artifactRef,
7962
+ digest,
7963
+ label: `--action ${key}`
7964
+ };
7965
+ });
7966
+ }
7967
+ __name(entriesFromFlags, "entriesFromFlags");
7968
+ function specFromFile(path) {
7969
+ let value;
7970
+ try {
7971
+ value = parseYaml2(readFileSync2(path, "utf8"));
7972
+ } catch (cause) {
7973
+ throw new Error(`--from ${path} could not be read as YAML. (${cause instanceof Error ? cause.message : String(cause)})`);
7974
+ }
7975
+ const spec = (0, import_contracts3.parseCatalogReleaseSpec)(value);
7976
+ return {
7977
+ version: spec.version,
7978
+ entries: spec.actions.map((action) => ({
7979
+ ...action,
7980
+ label: `${path}'s ${action.key}`
7981
+ }))
7982
+ };
7983
+ }
7984
+ __name(specFromFile, "specFromFile");
7985
+ async function runPublishRelease(argv) {
7986
+ const { single: flags, many } = parseCatalogFlags(argv, [
7987
+ "to",
7988
+ "version",
7989
+ "action",
7990
+ "from",
7991
+ "tag",
7992
+ "repository",
7993
+ "commit"
7994
+ ], [
7995
+ "action"
7996
+ ]);
7997
+ const actionFlags = many.action ?? [];
7998
+ if (flags.from !== void 0 && actionFlags.length > 0) {
7999
+ throw new Error("--from and --action are alternatives: name the release's actions in release.yaml or as flags, not both.");
8000
+ }
8001
+ if (flags.from !== void 0 && flags.version !== void 0) {
8002
+ throw new Error("--version is read from release.yaml when --from is given; pass one or the other.");
8003
+ }
8004
+ if (!flags.to || flags.from === void 0 && (!flags.version || actionFlags.length === 0)) {
8005
+ throw new Error("--to and either --from, or --version and at least one --action, are required.");
8006
+ }
8007
+ const release = parseRepository(flags.to, "--to");
8008
+ const { version: version2, entries } = flags.from !== void 0 ? specFromFile(flags.from) : {
8009
+ version: flags.version,
8010
+ entries: entriesFromFlags(actionFlags)
8011
+ };
8012
+ const tag = flags.tag ?? (flags.from !== void 0 ? version2 : defaultTag());
8013
+ requireTag(tag, flags.tag !== void 0 ? "--tag" : `${flags.from}'s version`);
8014
+ const seen = /* @__PURE__ */ new Set();
8015
+ const actions = entries.map((entry) => {
8016
+ if (seen.has(entry.key)) throw new Error(`${entry.label} names "${entry.key}" twice.`);
8017
+ seen.add(entry.key);
8018
+ const repository = parseRepository(entry.artifactRef, entry.label);
8019
+ if (repository.namespace !== release.namespace) {
8020
+ throw new Error(`${entry.label} lives in "${repository.namespace}", outside the release's namespace "${release.namespace}". The server refuses a release that sends it to another host or org.`);
8021
+ }
8022
+ if (!isDigest(entry.digest)) {
8023
+ throw new Error(`${entry.label}: "${entry.digest}" is not a digest. A release names artifacts by digest only.`);
8024
+ }
8025
+ const misnamed = (0, import_contracts3.actionPackageRefusal)(entry.key, entry.artifactRef);
8026
+ if (misnamed !== null) throw new Error(`${entry.label}: ${misnamed}`);
8027
+ return {
8028
+ ...entry,
8029
+ repository
8030
+ };
8031
+ });
8032
+ for (const action of actions) {
8033
+ if (!await manifestExists(action.repository, action.digest)) {
8034
+ throw new Error(`${action.artifactRef}@${action.digest} (${action.key}) is not in the registry.`);
8035
+ }
8036
+ }
8037
+ const artifact = buildArtifact({
8038
+ schemaVersion: import_contracts3.CATALOG_RELEASE_SCHEMA_V1,
8039
+ version: version2,
8040
+ provenance: provenance(flags, process.cwd()),
8041
+ actions: actions.map(({ key, artifactRef, digest: digest2 }) => ({
8042
+ key,
8043
+ artifactRef,
8044
+ digest: digest2
8045
+ }))
8046
+ });
8047
+ const digest = await pushArtifact(release, artifact, tag);
8048
+ process.stderr.write(`\u2713 catalog release ${version2} (${actions.length} action artifact(s)) published to ${release.ref}:${tag}
8049
+ `);
8050
+ process.stderr.write(` the registry reports ${digest} \u2014 the digest to plan and pin.
8051
+ `);
8052
+ process.stdout.write(`${release.ref}@${digest}
8053
+ `);
8054
+ }
8055
+ __name(runPublishRelease, "runPublishRelease");
8056
+
8057
+ // src/catalog/index.ts
8058
+ var CATALOG_USAGE = `runveric catalog \u2014 publish first-party catalog artifacts (docs/127)
8059
+
8060
+ ${PUBLISH_ACTION_USAGE}
8061
+ Publish one action's manifest, verbatim, as an artifact. stdout: <key>=<repository>@<digest>
8062
+
8063
+ ${PUBLISH_RELEASE_USAGE}
8064
+ Publish a release naming each action artifact by digest. stdout: <repository>@<digest>
8065
+
8066
+ Credentials: GHCR_TOKEN (write:packages), or in a workflow GITHUB_TOKEN with packages: write.
8067
+ Provenance: --repository/--commit, else GITHUB_SERVER_URL/GITHUB_REPOSITORY and GITHUB_SHA, else git.`;
8068
+ var SUBCOMMANDS = {
8069
+ "publish-action": runPublishAction,
8070
+ "publish-release": runPublishRelease
8071
+ };
8072
+ async function runCatalog(argv) {
8073
+ const [subcommand, ...rest] = argv;
8074
+ if (subcommand === void 0 || subcommand === "--help" || rest.includes("--help")) {
8075
+ process.stdout.write(`${CATALOG_USAGE}
8076
+ `);
8077
+ return subcommand === void 0 ? 1 : 0;
8078
+ }
8079
+ const run3 = SUBCOMMANDS[subcommand];
8080
+ if (run3 === void 0) {
8081
+ printError(`\u2717 "catalog ${subcommand}" is not a command. There are two: ${Object.keys(SUBCOMMANDS).join(", ")}.`);
8082
+ return 1;
8083
+ }
8084
+ try {
8085
+ await run3(rest);
8086
+ return 0;
8087
+ } catch (error) {
8088
+ printError(`\u2717 ${error instanceof Error ? error.message : String(error)}`);
8089
+ return 1;
8090
+ }
8091
+ }
8092
+ __name(runCatalog, "runCatalog");
8093
+
8094
+ // src/check.ts
8095
+ var import_action_manifest = __toESM(require_dist2());
8096
+ import { resolve as resolve2 } from "path";
8097
+
8098
+ // src/package-dir.ts
8099
+ import { readdir, readFile, stat } from "fs/promises";
8100
+ import { join, relative, sep } from "path";
8101
+ var IGNORED_DIRECTORIES = /* @__PURE__ */ new Set([
8102
+ ".git",
8103
+ ".runveric",
8104
+ "node_modules",
8105
+ ".venv",
8106
+ "__pycache__",
8107
+ ".DS_Store"
8108
+ ]);
8109
+ var MAX_FILES = 2e3;
8110
+ var MAX_TOTAL_BYTES = 16 * 1024 * 1024;
8111
+ var PackageDirectoryError = class extends Error {
8112
+ static {
8113
+ __name(this, "PackageDirectoryError");
8114
+ }
8115
+ };
8116
+ async function readPackageDirectory(root) {
8117
+ const properties = await stat(root).catch(() => null);
8118
+ if (!properties?.isDirectory()) {
8119
+ throw new PackageDirectoryError(`${root} is not a directory.`);
8120
+ }
8121
+ const files = /* @__PURE__ */ new Map();
8122
+ let totalBytes = 0;
8123
+ const walk = /* @__PURE__ */ __name(async (directory) => {
8124
+ for (const entry of await readdir(directory, {
8125
+ withFileTypes: true
8126
+ })) {
8127
+ if (IGNORED_DIRECTORIES.has(entry.name)) continue;
8128
+ const full = join(directory, entry.name);
8129
+ if (entry.isDirectory()) {
8130
+ await walk(full);
8131
+ continue;
8132
+ }
8133
+ if (!entry.isFile()) continue;
8134
+ if (files.size >= MAX_FILES) {
8135
+ throw new PackageDirectoryError(`This directory holds more than ${MAX_FILES} files. That is almost certainly not a single action package \u2014 run this from the package directory itself.`);
8136
+ }
8137
+ const contents = await readFile(full);
8138
+ totalBytes += contents.byteLength;
8139
+ if (totalBytes > MAX_TOTAL_BYTES) {
8140
+ throw new PackageDirectoryError(`This directory holds more than ${MAX_TOTAL_BYTES / (1024 * 1024)}MB. An action package is source, not artifacts \u2014 add build output to .gitignore.`);
8141
+ }
8142
+ files.set(toPackagePath(root, full), contents);
8143
+ }
8144
+ }, "walk");
8145
+ await walk(root);
8146
+ return files;
8147
+ }
8148
+ __name(readPackageDirectory, "readPackageDirectory");
8149
+ function toPackagePath(root, full) {
8150
+ return relative(root, full).split(sep).join("/");
8151
+ }
8152
+ __name(toPackagePath, "toPackagePath");
8153
+
8154
+ // src/check.ts
8155
+ var import_contracts4 = __toESM(require_dist());
7115
8156
  async function runCheck(options) {
7116
- const directory = resolve(options.directory ?? ".");
8157
+ const directory = resolve2(options.directory ?? ".");
7117
8158
  let files;
7118
8159
  try {
7119
8160
  files = await readPackageDirectory(directory);
@@ -7135,7 +8176,7 @@ async function runCheck(options) {
7135
8176
  print(style.red("This package would be REFUSED at import."));
7136
8177
  } else if (report.lintBlocked) {
7137
8178
  print();
7138
- print(style.red("This package would be ACCEPTED at import \u2014 and then misbehave.") + ` The errors above are not things ${import_contracts.BRAND_SHORT_NAME} checks; they are things that go wrong at run time, silently.`);
8179
+ print(style.red("This package would be ACCEPTED at import \u2014 and then misbehave.") + ` The errors above are not things ${import_contracts4.BRAND_SHORT_NAME} checks; they are things that go wrong at run time, silently.`);
7139
8180
  }
7140
8181
  if (report.advisoryCount > 0) {
7141
8182
  print();
@@ -7155,11 +8196,11 @@ async function runCheck(options) {
7155
8196
  __name(runCheck, "runCheck");
7156
8197
 
7157
8198
  // src/init.ts
7158
- var import_contracts2 = __toESM(require_dist());
8199
+ var import_contracts5 = __toESM(require_dist());
7159
8200
  var import_action_manifest2 = __toESM(require_dist2());
7160
8201
  import { mkdir, writeFile } from "fs/promises";
7161
8202
  import { createInterface } from "readline/promises";
7162
- import { dirname, join as join2, resolve as resolve2 } from "path";
8203
+ import { dirname as dirname2, join as join2, resolve as resolve3 } from "path";
7163
8204
  async function runInit(options) {
7164
8205
  const interactive = process.stdin.isTTY === true && options.yes !== true;
7165
8206
  const ask = interactive ? prompter() : null;
@@ -7171,7 +8212,7 @@ async function runInit(options) {
7171
8212
  const interpreter = await resolveInterpreter(options.interpreter, ask);
7172
8213
  if (!interpreter) return 1;
7173
8214
  const author = options.author?.trim() || await ask?.("Author", id.split("/")[0]) || id.split("/")[0];
7174
- const directory = resolve2(options.directory ?? id.split("/")[1] ?? "action");
8215
+ const directory = resolve3(options.directory ?? id.split("/")[1] ?? "action");
7175
8216
  const files = (0, import_action_manifest2.scaffoldActionPackage)({
7176
8217
  id,
7177
8218
  name,
@@ -7181,7 +8222,7 @@ async function runInit(options) {
7181
8222
  });
7182
8223
  for (const [path, contents] of files) {
7183
8224
  const target = join2(directory, path);
7184
- await mkdir(dirname(target), {
8225
+ await mkdir(dirname2(target), {
7185
8226
  recursive: true
7186
8227
  });
7187
8228
  await writeFile(target, contents, {
@@ -7224,9 +8265,9 @@ async function resolveId(supplied, ask) {
7224
8265
  }
7225
8266
  __name(resolveId, "resolveId");
7226
8267
  async function resolveInterpreter(supplied, ask) {
7227
- const value = supplied?.trim() || await ask?.(`Interpreter (${import_contracts2.PACKAGE_INTERPRETERS.join(" / ")})`, "bash") || "bash";
8268
+ const value = supplied?.trim() || await ask?.(`Interpreter (${import_contracts5.PACKAGE_INTERPRETERS.join(" / ")})`, "bash") || "bash";
7228
8269
  if (!isInterpreter(value)) {
7229
- printError(style.red(`"${value}" is not an interpreter ${import_contracts2.BRAND_SHORT_NAME} runs.`) + ` The runner image ships ${import_contracts2.PACKAGE_INTERPRETERS.join(", ")}.`);
8270
+ printError(style.red(`"${value}" is not an interpreter ${import_contracts5.BRAND_SHORT_NAME} runs.`) + ` The runner image ships ${import_contracts5.PACKAGE_INTERPRETERS.join(", ")}.`);
7230
8271
  return null;
7231
8272
  }
7232
8273
  return value;
@@ -7247,7 +8288,7 @@ function prompter() {
7247
8288
  }
7248
8289
  __name(prompter, "prompter");
7249
8290
  function isInterpreter(value) {
7250
- return import_contracts2.PACKAGE_INTERPRETERS.includes(value);
8291
+ return import_contracts5.PACKAGE_INTERPRETERS.includes(value);
7251
8292
  }
7252
8293
  __name(isInterpreter, "isInterpreter");
7253
8294
  function titleCase(value) {
@@ -7257,8 +8298,8 @@ __name(titleCase, "titleCase");
7257
8298
 
7258
8299
  // src/publish.ts
7259
8300
  var import_action_manifest3 = __toESM(require_dist2());
7260
- var import_contracts3 = __toESM(require_dist());
7261
- import { resolve as resolve3 } from "path";
8301
+ var import_contracts6 = __toESM(require_dist());
8302
+ import { resolve as resolve4 } from "path";
7262
8303
 
7263
8304
  // src/renamed-variable.ts
7264
8305
  function renamedVariableRefusal(current, retired, env = process.env) {
@@ -7269,15 +8310,15 @@ function renamedVariableRefusal(current, retired, env = process.env) {
7269
8310
  __name(renamedVariableRefusal, "renamedVariableRefusal");
7270
8311
 
7271
8312
  // src/publish.ts
7272
- var TOKEN_ENV = import_contracts3.AUTHORING_CLI_ENV.token;
7273
- var URL_ENV = import_contracts3.AUTHORING_CLI_ENV.url;
8313
+ var TOKEN_ENV = import_contracts6.AUTHORING_CLI_ENV.token;
8314
+ var URL_ENV = import_contracts6.AUTHORING_CLI_ENV.url;
7274
8315
  async function runPublish(options) {
7275
- const directory = resolve3(options.directory ?? ".");
8316
+ const directory = resolve4(options.directory ?? ".");
7276
8317
  const baseUrl = (options.to ?? process.env[URL_ENV] ?? "").trim().replace(/\/+$/, "");
7277
- const token = (options.token ?? process.env[TOKEN_ENV] ?? "").trim();
8318
+ const token2 = (options.token ?? process.env[TOKEN_ENV] ?? "").trim();
7278
8319
  const stale = [
7279
- options.to === void 0 ? renamedVariableRefusal(URL_ENV, import_contracts3.RETIRED_AUTHORING_CLI_ENV.url) : null,
7280
- options.token === void 0 ? renamedVariableRefusal(TOKEN_ENV, import_contracts3.RETIRED_AUTHORING_CLI_ENV.token) : null
8320
+ options.to === void 0 ? renamedVariableRefusal(URL_ENV, import_contracts6.RETIRED_AUTHORING_CLI_ENV.url) : null,
8321
+ options.token === void 0 ? renamedVariableRefusal(TOKEN_ENV, import_contracts6.RETIRED_AUTHORING_CLI_ENV.token) : null
7281
8322
  ].filter((sentence) => sentence !== null);
7282
8323
  if (stale.length > 0) {
7283
8324
  for (const sentence of stale) printError(style.red(sentence));
@@ -7287,8 +8328,8 @@ async function runPublish(options) {
7287
8328
  printError(style.red("Where to?") + ` Pass --to https://your-runveric.example.com, or set ${URL_ENV}.`);
7288
8329
  return 1;
7289
8330
  }
7290
- if (!token) {
7291
- printError(style.red(`Publishing needs ${import_contracts3.BRAND_ARTICLE} ${import_contracts3.BRAND_SHORT_NAME} access token.`) + ` Set ${TOKEN_ENV}, or pass --token. (init, check and test need no account at all.)`);
8331
+ if (!token2) {
8332
+ printError(style.red(`Publishing needs ${import_contracts6.BRAND_ARTICLE} ${import_contracts6.BRAND_SHORT_NAME} access token.`) + ` Set ${TOKEN_ENV}, or pass --token. (init, check and test need no account at all.)`);
7292
8333
  return 1;
7293
8334
  }
7294
8335
  let files;
@@ -7308,7 +8349,7 @@ async function runPublish(options) {
7308
8349
  errors: report.errorCount,
7309
8350
  warnings: report.warningCount
7310
8351
  });
7311
- printError(style.red("\nNot published.") + (report.validation.ok ? ` ${import_contracts3.BRAND_SHORT_NAME} would accept this, but the problems above make it misbehave at run time. Fix them, or pass --force if you disagree.` : " Fix the errors above and publish again."));
8352
+ printError(style.red("\nNot published.") + (report.validation.ok ? ` ${import_contracts6.BRAND_SHORT_NAME} would accept this, but the problems above make it misbehave at run time. Fix them, or pass --force if you disagree.` : " Fix the errors above and publish again."));
7312
8353
  return 1;
7313
8354
  }
7314
8355
  if (report.advisoryCount > 0) {
@@ -7335,7 +8376,7 @@ async function runPublish(options) {
7335
8376
  method: "POST",
7336
8377
  headers: {
7337
8378
  "content-type": "application/json",
7338
- authorization: `Bearer ${token}`
8379
+ authorization: `Bearer ${token2}`
7339
8380
  },
7340
8381
  body
7341
8382
  });
@@ -7355,7 +8396,7 @@ Publish refused (${response.status}).`));
7355
8396
  try {
7356
8397
  version2 = JSON.parse(text);
7357
8398
  } catch {
7358
- printError(style.red(`${import_contracts3.BRAND_SHORT_NAME} accepted the package but returned a response we cannot read.`));
8399
+ printError(style.red(`${import_contracts6.BRAND_SHORT_NAME} accepted the package but returned a response we cannot read.`));
7359
8400
  return 1;
7360
8401
  }
7361
8402
  print();
@@ -7364,7 +8405,7 @@ Publish refused (${response.status}).`));
7364
8405
  print(` content digest ${version2.contentDigest}`);
7365
8406
  print(` tier ${version2.tier} ${style.grey(`(${version2.provenance})`)}`);
7366
8407
  print();
7367
- print(style.grey(`It is an immutable version now. Adopt it in ${import_contracts3.BRAND_SHORT_NAME} to put it in the catalog \u2014 publishing does not adopt, so nobody can run it until somebody chooses to.`));
8408
+ print(style.grey(`It is an immutable version now. Adopt it in ${import_contracts6.BRAND_SHORT_NAME} to put it in the catalog \u2014 publishing does not adopt, so nobody can run it until somebody chooses to.`));
7368
8409
  print();
7369
8410
  return 0;
7370
8411
  }
@@ -7376,11 +8417,11 @@ function formatProblem(text) {
7376
8417
  } catch {
7377
8418
  return ` ${text}`;
7378
8419
  }
7379
- const problem = (0, import_contracts3.parseProblemDocument)(parsed);
8420
+ const problem = (0, import_contracts6.parseProblemDocument)(parsed);
7380
8421
  if (!problem) return ` ${text}`;
7381
8422
  const lines = [];
7382
8423
  if (problem.detail) lines.push(` ${problem.detail}`);
7383
- for (const error of (0, import_contracts3.problemFieldErrors)(problem)) {
8424
+ for (const error of (0, import_contracts6.problemFieldErrors)(problem)) {
7384
8425
  lines.push(error.field ? ` ${style.bold(error.field)}: ${error.message}` : ` ${error.message}`);
7385
8426
  }
7386
8427
  return lines.length > 0 ? lines.join("\n") : ` ${text}`;
@@ -7388,12 +8429,12 @@ function formatProblem(text) {
7388
8429
  __name(formatProblem, "formatProblem");
7389
8430
 
7390
8431
  // src/runner-image.ts
7391
- var import_contracts4 = __toESM(require_dist());
8432
+ var import_contracts7 = __toESM(require_dist());
7392
8433
  import { execFile } from "child_process";
7393
8434
  import { promisify } from "util";
7394
8435
  var run = promisify(execFile);
7395
8436
  var LOCAL_RUNNER_IMAGE = "runveric-package-runner:local";
7396
- var RUNNER_IMAGE_ENV = import_contracts4.AUTHORING_CLI_ENV.runnerImage;
8437
+ var RUNNER_IMAGE_ENV = import_contracts7.AUTHORING_CLI_ENV.runnerImage;
7397
8438
  var RenamedVariableError = class extends Error {
7398
8439
  static {
7399
8440
  __name(this, "RenamedVariableError");
@@ -7410,7 +8451,7 @@ function resolveRunnerImage(flag) {
7410
8451
  reference: configured,
7411
8452
  origin: "environment"
7412
8453
  };
7413
- const stale = renamedVariableRefusal(RUNNER_IMAGE_ENV, import_contracts4.RETIRED_AUTHORING_CLI_ENV.runnerImage);
8454
+ const stale = renamedVariableRefusal(RUNNER_IMAGE_ENV, import_contracts7.RETIRED_AUTHORING_CLI_ENV.runnerImage);
7414
8455
  if (stale) throw new RenamedVariableError(stale);
7415
8456
  return {
7416
8457
  reference: LOCAL_RUNNER_IMAGE,
@@ -7425,7 +8466,7 @@ function runnerImageNote(image) {
7425
8466
  case "environment":
7426
8467
  return `from ${RUNNER_IMAGE_ENV}`;
7427
8468
  case "local-build":
7428
- return `locally built \u2014 NOT the image your ${import_contracts4.BRAND_SHORT_NAME} runs; set ${RUNNER_IMAGE_ENV} to match it`;
8469
+ return `locally built \u2014 NOT the image your ${import_contracts7.BRAND_SHORT_NAME} runs; set ${RUNNER_IMAGE_ENV} to match it`;
7429
8470
  }
7430
8471
  }
7431
8472
  __name(runnerImageNote, "runnerImageNote");
@@ -7442,7 +8483,7 @@ async function assertDockerAvailable() {
7442
8483
  "{{.Server.Version}}"
7443
8484
  ]);
7444
8485
  } catch {
7445
- throw new DockerMissingError(`Docker is not available. \`runveric test\` runs your package in the real ${import_contracts4.BRAND_SHORT_NAME} runner image, which needs a container runtime \u2014 install Docker Desktop (or start it), then try again. \`runveric check\` needs nothing and still works.`);
8486
+ throw new DockerMissingError(`Docker is not available. \`runveric test\` runs your package in the real ${import_contracts7.BRAND_SHORT_NAME} runner image, which needs a container runtime \u2014 install Docker Desktop (or start it), then try again. \`runveric check\` needs nothing and still works.`);
7446
8487
  }
7447
8488
  }
7448
8489
  __name(assertDockerAvailable, "assertDockerAvailable");
@@ -7465,7 +8506,7 @@ var import_action_manifest4 = __toESM(require_dist2());
7465
8506
  import { spawn } from "child_process";
7466
8507
  import { mkdir as mkdir2, mkdtemp, readFile as readFile2, rm, stat as stat2, writeFile as writeFile2 } from "fs/promises";
7467
8508
  import { tmpdir } from "os";
7468
- import { dirname as dirname2, join as join3, resolve as resolve4 } from "path";
8509
+ import { dirname as dirname3, join as join3, resolve as resolve5 } from "path";
7469
8510
  import { StringDecoder } from "string_decoder";
7470
8511
 
7471
8512
  // src/azure-credential.ts
@@ -7488,7 +8529,7 @@ async function acquireAzureCredential() {
7488
8529
  if (!account.id || !account.tenantId) {
7489
8530
  throw new AzureCredentialError("`az account show` returned no subscription. Run `az login` and `az account set --subscription <id>` first.");
7490
8531
  }
7491
- const token = await azJson([
8532
+ const token2 = await azJson([
7492
8533
  "account",
7493
8534
  "get-access-token",
7494
8535
  "--resource",
@@ -7496,11 +8537,11 @@ async function acquireAzureCredential() {
7496
8537
  "--output",
7497
8538
  "json"
7498
8539
  ]);
7499
- if (!token.accessToken) {
8540
+ if (!token2.accessToken) {
7500
8541
  throw new AzureCredentialError("`az account get-access-token` returned no access token.");
7501
8542
  }
7502
8543
  return {
7503
- token: token.accessToken,
8544
+ token: token2.accessToken,
7504
8545
  // The identical names the credential adapter puts in a lease, so a package reads the same
7505
8546
  // environment here and in production.
7506
8547
  env: {
@@ -7535,7 +8576,7 @@ ${message.trim()}`);
7535
8576
  __name(azJson, "azJson");
7536
8577
 
7537
8578
  // src/test.ts
7538
- var import_contracts5 = __toESM(require_dist());
8579
+ var import_contracts8 = __toESM(require_dist());
7539
8580
  var PHASES = [
7540
8581
  "validate",
7541
8582
  "run",
@@ -7549,10 +8590,10 @@ var CONTAINER = {
7549
8590
  creds: "/runveric/creds"
7550
8591
  };
7551
8592
  async function runTest(options) {
7552
- const directory = resolve4(options.directory ?? ".");
8593
+ const directory = resolve5(options.directory ?? ".");
7553
8594
  const phase = options.phase ?? "run";
7554
8595
  if (!PHASES.includes(phase)) {
7555
- printError(style.red(`"${options.phase}" is not a phase.`) + ` ${import_contracts5.BRAND_SHORT_NAME} runs ${PHASES.join(", ")} \u2014 one at a time, so you can read each one's result.`);
8596
+ printError(style.red(`"${options.phase}" is not a phase.`) + ` ${import_contracts8.BRAND_SHORT_NAME} runs ${PHASES.join(", ")} \u2014 one at a time, so you can read each one's result.`);
7556
8597
  return 1;
7557
8598
  }
7558
8599
  let files;
@@ -7624,9 +8665,9 @@ async function runTest(options) {
7624
8665
  print(` runner image ${image.reference} ${style.grey(`(${runnerImageNote(image)})`)}`);
7625
8666
  print(` interpreter ${interpreter}@${manifest.runtime.version}`);
7626
8667
  print(` entrypoint ${entrypoint}${import_action_manifest4.PACKAGE_INTERPRETER_LAUNCH[interpreter].entryFile}`);
7627
- print(` identity ${credential ? `${credential.account.name} ${style.grey(`\u2014 YOUR access, not ${import_contracts5.BRAND_SHORT_NAME}'s brokered least-privilege identity`)}` : style.grey("none (--no-credential)")}`);
8668
+ print(` identity ${credential ? `${credential.account.name} ${style.grey(`\u2014 YOUR access, not ${import_contracts8.BRAND_SHORT_NAME}'s brokered least-privilege identity`)}` : style.grey("none (--no-credential)")}`);
7628
8669
  if (manifest.requiresNetwork) {
7629
- print(` network ${style.yellow("open")} ${style.grey(`\u2014 your allowlist (${(manifest.egressAllowlist ?? []).join(", ")}) is NOT enforced here; ${import_contracts5.BRAND_SHORT_NAME} enforces it`)}`);
8670
+ print(` network ${style.yellow("open")} ${style.grey(`\u2014 your allowlist (${(manifest.egressAllowlist ?? []).join(", ")}) is NOT enforced here; ${import_contracts8.BRAND_SHORT_NAME} enforces it`)}`);
7630
8671
  } else {
7631
8672
  print(` network ${style.grey("none \u2014 this package declares requiresNetwork: false")}`);
7632
8673
  }
@@ -7728,13 +8769,13 @@ async function materialize(workspace, input) {
7728
8769
  })));
7729
8770
  for (const [path, contents] of input.files) {
7730
8771
  const target = join3(layout.packageRoot, path);
7731
- await mkdir2(dirname2(target), {
8772
+ await mkdir2(dirname3(target), {
7732
8773
  recursive: true
7733
8774
  });
7734
8775
  await writeFile2(target, contents);
7735
8776
  }
7736
8777
  const context = {
7737
- protocolVersion: import_contracts5.PACKAGE_EXECUTION_PROTOCOL_VERSION,
8778
+ protocolVersion: import_contracts8.PACKAGE_EXECUTION_PROTOCOL_VERSION,
7738
8779
  // A recognisable id rather than a random uuid: it appears in the package's own log lines,
7739
8780
  // and "local-test" tells the reader instantly that this was not a real run.
7740
8781
  executionId: "local-test",
@@ -7760,7 +8801,7 @@ async function runContainer(options) {
7760
8801
  const launch = import_action_manifest4.PACKAGE_INTERPRETER_LAUNCH[options.interpreter];
7761
8802
  const script = `${CONTAINER.packageRoot}/${options.entrypoint}${launch.entryFile}`;
7762
8803
  const environment = {
7763
- RUNVERIC_PROTOCOL_VERSION: import_contracts5.PACKAGE_RESULT_PROTOCOL_VERSION,
8804
+ RUNVERIC_PROTOCOL_VERSION: import_contracts8.PACKAGE_RESULT_PROTOCOL_VERSION,
7764
8805
  RUNVERIC_PHASE: options.phase,
7765
8806
  RUNVERIC_CONTEXT_FILE: `${CONTAINER.input}/context.json`,
7766
8807
  RUNVERIC_PARAMETERS_FILE: `${CONTAINER.input}/parameters.json`,
@@ -7887,13 +8928,13 @@ function emitLine(line, stderr, onEvent) {
7887
8928
  __name(emitLine, "emitLine");
7888
8929
  async function reportOutcome(options) {
7889
8930
  if (options.exit.timedOut) {
7890
- printError(style.red("\nThe package did not finish within 15 minutes and was killed.") + ` ${import_contracts5.BRAND_SHORT_NAME} enforces the \`executionTimeout\` in your manifest; this is the local ceiling.`);
8931
+ printError(style.red("\nThe package did not finish within 15 minutes and was killed.") + ` ${import_contracts8.BRAND_SHORT_NAME} enforces the \`executionTimeout\` in your manifest; this is the local ceiling.`);
7891
8932
  return 1;
7892
8933
  }
7893
8934
  const exists = await stat2(options.resultPath).catch(() => null);
7894
8935
  if (!exists?.isFile() || exists.size === 0) {
7895
8936
  printError(style.red("\nThe package wrote no result.") + `
7896
- result.json is the only channel ${import_contracts5.BRAND_SHORT_NAME} reads. Exiting ${options.exit.code ?? "with a signal"} without writing $RUNVERIC_RESULT_FILE is a FAILED run, whatever the script printed.
8937
+ result.json is the only channel ${import_contracts8.BRAND_SHORT_NAME} reads. Exiting ${options.exit.code ?? "with a signal"} without writing $RUNVERIC_RESULT_FILE is a FAILED run, whatever the script printed.
7897
8938
  Write it as the last thing your script does, on every path \u2014 including the ones that fail.`);
7898
8939
  return 1;
7899
8940
  }
@@ -7901,7 +8942,7 @@ async function reportOutcome(options) {
7901
8942
  try {
7902
8943
  parsed = JSON.parse(await readFile2(options.resultPath, "utf8"));
7903
8944
  } catch {
7904
- printError(style.red("\nresult.json is not valid JSON.") + ` ${import_contracts5.BRAND_SHORT_NAME} refuses the run rather than guessing what the package meant.`);
8945
+ printError(style.red("\nresult.json is not valid JSON.") + ` ${import_contracts8.BRAND_SHORT_NAME} refuses the run rather than guessing what the package meant.`);
7905
8946
  return 1;
7906
8947
  }
7907
8948
  let result;
@@ -7909,14 +8950,14 @@ async function reportOutcome(options) {
7909
8950
  result = (0, import_action_manifest4.validatePackageResult)(parsed, options.phase);
7910
8951
  } catch (error) {
7911
8952
  printError(style.red(`
7912
- ${import_contracts5.BRAND_SHORT_NAME} would REFUSE this result.`) + `
8953
+ ${import_contracts8.BRAND_SHORT_NAME} would REFUSE this result.`) + `
7913
8954
  ${error instanceof Error ? error.message : String(error)}
7914
8955
  This is the same check the control plane runs, so fixing it here fixes it there.`);
7915
8956
  return 1;
7916
8957
  }
7917
8958
  printResult(result);
7918
8959
  if (options.exit.code !== 0) {
7919
- print(style.yellow(`The package exited ${options.exit.code} but wrote a valid result. ${import_contracts5.BRAND_SHORT_NAME} reads the result, not the exit code \u2014 but a non-zero exit usually means something failed after the result was written.`));
8960
+ print(style.yellow(`The package exited ${options.exit.code} but wrote a valid result. ${import_contracts8.BRAND_SHORT_NAME} reads the result, not the exit code \u2014 but a non-zero exit usually means something failed after the result was written.`));
7920
8961
  print();
7921
8962
  }
7922
8963
  return result.status === "succeeded" ? 0 : 1;
@@ -7925,7 +8966,7 @@ __name(reportOutcome, "reportOutcome");
7925
8966
  async function readJsonFile(path) {
7926
8967
  let source;
7927
8968
  try {
7928
- source = await readFile2(resolve4(path), "utf8");
8969
+ source = await readFile2(resolve5(path), "utf8");
7929
8970
  } catch {
7930
8971
  throw new Error(`Could not read ${path}.`);
7931
8972
  }
@@ -7943,9 +8984,9 @@ async function readJsonFile(path) {
7943
8984
  __name(readJsonFile, "readJsonFile");
7944
8985
 
7945
8986
  // src/main.ts
7946
- var import_contracts6 = __toESM(require_dist());
8987
+ var import_contracts9 = __toESM(require_dist());
7947
8988
  var USAGE = `
7948
- ${style.bold("runveric")} \u2014 author ${import_contracts6.BRAND_ARTICLE} ${import_contracts6.BRAND_SHORT_NAME} action, without pushing anything.
8989
+ ${style.bold("runveric")} \u2014 author ${import_contracts9.BRAND_ARTICLE} ${import_contracts9.BRAND_SHORT_NAME} action, without pushing anything.
7949
8990
 
7950
8991
  ${style.bold("USAGE")}
7951
8992
  runveric <command> [options]
@@ -7953,8 +8994,9 @@ ${style.bold("USAGE")}
7953
8994
  ${style.bold("COMMANDS")}
7954
8995
  init [directory] Scaffold a working action. It passes check and test immediately.
7955
8996
  check Validate the package. Offline \u2014 no account, no cloud, no network.
7956
- test Run one phase in the real ${import_contracts6.BRAND_SHORT_NAME} runner image.
7957
- publish Upload it to ${import_contracts6.BRAND_SHORT_NAME} as an authored version. Needs an account.
8997
+ test Run one phase in the real ${import_contracts9.BRAND_SHORT_NAME} runner image.
8998
+ publish Upload it to ${import_contracts9.BRAND_SHORT_NAME} as an authored version. Needs an account.
8999
+ catalog <command> Publish first-party catalog artifacts. Not for authoring: see \`runveric catalog --help\`.
7958
9000
 
7959
9001
  ${style.bold("init")}
7960
9002
  --id <publisher/name> The action id. Required.
@@ -7977,9 +9019,9 @@ ${style.bold("test")}
7977
9019
  --keep Keep the materialized /runveric tree for inspection.
7978
9020
 
7979
9021
  ${style.bold("publish")}
7980
- --to <url> Your ${import_contracts6.BRAND_SHORT_NAME} URL. Defaults to $${URL_ENV}.
9022
+ --to <url> Your ${import_contracts9.BRAND_SHORT_NAME} URL. Defaults to $${URL_ENV}.
7981
9023
  --token <token> Access token. Defaults to $${TOKEN_ENV}.
7982
- --force Publish despite lint errors. Never bypasses ${import_contracts6.BRAND_SHORT_NAME}'s validation.
9024
+ --force Publish despite lint errors. Never bypasses ${import_contracts9.BRAND_SHORT_NAME}'s validation.
7983
9025
 
7984
9026
  ${style.bold("EVERYWHERE")}
7985
9027
  --dir <path> The package directory. Defaults to the current directory.
@@ -8022,7 +9064,9 @@ __name(parseArgv, "parseArgv");
8022
9064
  var first = /* @__PURE__ */ __name((parsed, name) => parsed.flags.get(name)?.[0], "first");
8023
9065
  var bool = /* @__PURE__ */ __name((parsed, name) => parsed.flags.get(name)?.[0] === "true", "bool");
8024
9066
  async function main() {
8025
- const parsed = parseArgv(process.argv.slice(2));
9067
+ const argv = process.argv.slice(2);
9068
+ if (argv[0] === "catalog") return runCatalog(argv.slice(1));
9069
+ const parsed = parseArgv(argv);
8026
9070
  if (parsed.flags.has("version")) {
8027
9071
  print(version);
8028
9072
  return 0;
@@ -8090,7 +9134,7 @@ async function main() {
8090
9134
  force: bool(parsed, "force")
8091
9135
  });
8092
9136
  default:
8093
- printError(style.red(`"${parsed.command}" is not a command.`) + " There are four: init, check, test, publish.");
9137
+ printError(style.red(`"${parsed.command}" is not a command.`) + " There are four: init, check, test, publish \u2014 and catalog, which publishes first-party catalog artifacts.");
8094
9138
  return 1;
8095
9139
  }
8096
9140
  }