@runveric/cli 0.2.0 → 0.4.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,16 +212,18 @@ var require_wire_identity = __commonJS({
212
212
  Object.defineProperty(exports, "__esModule", {
213
213
  value: true
214
214
  });
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;
215
+ exports.PROBLEM_TYPE_BASE = exports.CATALOG_ARTIFACT_MEDIA_TYPE = exports.CATALOG_ACTION_ARTIFACT_SCHEMA_V2 = exports.CATALOG_ACTION_ARTIFACT_SCHEMA_V1 = exports.CATALOG_RELEASE_SCHEMA_V1 = exports.EVIDENCE_PACK_SCHEMA_VERSION = exports.ACTION_PROTOCOL_V1 = 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.ACTION_PROTOCOL_V1 = "runveric.dev/action-protocol/v1";
222
223
  exports.EVIDENCE_PACK_SCHEMA_VERSION = "runveric.dev/evidence-pack/v1";
223
224
  exports.CATALOG_RELEASE_SCHEMA_V1 = "runveric.dev/catalog-release/v1";
224
225
  exports.CATALOG_ACTION_ARTIFACT_SCHEMA_V1 = "runveric.dev/catalog-action/v1";
226
+ exports.CATALOG_ACTION_ARTIFACT_SCHEMA_V2 = "runveric.dev/catalog-action/v2";
225
227
  exports.CATALOG_ARTIFACT_MEDIA_TYPE = "application/vnd.runveric.catalog-artifact+json";
226
228
  exports.PROBLEM_TYPE_BASE = "https://runveric.dev/errors";
227
229
  function problemType(slug) {
@@ -611,16 +613,134 @@ var require_catalog_release = __commonJS({
611
613
  Object.defineProperty(exports, "__esModule", {
612
614
  value: true
613
615
  });
614
- exports.ARTIFACT_DIGEST_PATTERN = exports.ARTIFACT_REPOSITORY_PATTERN = exports.CATALOG_ACTION_ARTIFACT_SCHEMA_VERSIONS = exports.CATALOG_RELEASE_SCHEMA_VERSIONS = void 0;
616
+ exports.CatalogReleaseStateError = exports.CATALOG_SYNC_STALE_AFTER_MINUTES = exports.CATALOG_SYNC_INTERVAL_MINUTES = exports.CATALOG_SYNC_SOURCES = exports.CATALOG_SYNC_RUN_OUTCOMES = exports.ARTIFACT_DIGEST_PATTERN = exports.ARTIFACT_REPOSITORY_PATTERN = exports.CATALOG_ACTION_ARTIFACT_SCHEMA_VERSIONS = exports.CATALOG_RELEASE_SCHEMA_VERSIONS = void 0;
617
+ exports.parseCatalogReleaseState = parseCatalogReleaseState;
615
618
  var wire_identity_1 = require_wire_identity();
616
619
  exports.CATALOG_RELEASE_SCHEMA_VERSIONS = [
617
620
  wire_identity_1.CATALOG_RELEASE_SCHEMA_V1
618
621
  ];
619
622
  exports.CATALOG_ACTION_ARTIFACT_SCHEMA_VERSIONS = [
620
- wire_identity_1.CATALOG_ACTION_ARTIFACT_SCHEMA_V1
623
+ wire_identity_1.CATALOG_ACTION_ARTIFACT_SCHEMA_V1,
624
+ wire_identity_1.CATALOG_ACTION_ARTIFACT_SCHEMA_V2
621
625
  ];
622
626
  exports.ARTIFACT_REPOSITORY_PATTERN = /^[a-z0-9][a-z0-9.-]*(:[0-9]+)?(\/[a-z0-9][a-z0-9._-]*)+$/;
623
627
  exports.ARTIFACT_DIGEST_PATTERN = /^sha256:[0-9a-f]{64}$/;
628
+ exports.CATALOG_SYNC_RUN_OUTCOMES = [
629
+ "applied",
630
+ "refused",
631
+ "superseded"
632
+ ];
633
+ exports.CATALOG_SYNC_SOURCES = [
634
+ "directory",
635
+ "release"
636
+ ];
637
+ exports.CATALOG_SYNC_INTERVAL_MINUTES = 15;
638
+ exports.CATALOG_SYNC_STALE_AFTER_MINUTES = exports.CATALOG_SYNC_INTERVAL_MINUTES * 2;
639
+ var CatalogReleaseStateError = class CatalogReleaseStateError extends Error {
640
+ static {
641
+ __name(this, "CatalogReleaseStateError");
642
+ }
643
+ constructor(message) {
644
+ super(message);
645
+ this.name = "CatalogReleaseStateError";
646
+ }
647
+ };
648
+ exports.CatalogReleaseStateError = CatalogReleaseStateError;
649
+ function stateObject(path, value) {
650
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
651
+ throw new CatalogReleaseStateError(`${path} is not an object.`);
652
+ }
653
+ return value;
654
+ }
655
+ __name(stateObject, "stateObject");
656
+ function stateString(path, value, pattern, expected = "a non-empty string") {
657
+ if (typeof value !== "string" || value === "" || pattern !== void 0 && !pattern.test(value)) {
658
+ throw new CatalogReleaseStateError(`${path} is ${JSON.stringify(value)}, not ${expected}.`);
659
+ }
660
+ return value;
661
+ }
662
+ __name(stateString, "stateString");
663
+ function stateInstant(path, value) {
664
+ const text = stateString(path, value);
665
+ if (Number.isNaN(Date.parse(text))) {
666
+ throw new CatalogReleaseStateError(`${path} is "${text}", which is not a timestamp.`);
667
+ }
668
+ return text;
669
+ }
670
+ __name(stateInstant, "stateInstant");
671
+ var orNull = /* @__PURE__ */ __name((value, read) => value === null ? null : read(value), "orNull");
672
+ function stateMember(path, value, members) {
673
+ if (typeof value === "string" && members.includes(value)) return value;
674
+ throw new CatalogReleaseStateError(`${path} is ${JSON.stringify(value)}; this build reads ${members.join(", ")}.`);
675
+ }
676
+ __name(stateMember, "stateMember");
677
+ function parseSyncRun(value) {
678
+ const run3 = stateObject("lastSync", value);
679
+ const source = stateMember("lastSync.source", run3.source, exports.CATALOG_SYNC_SOURCES);
680
+ const release = orNull(run3.release, (raw) => {
681
+ const fields = stateObject("lastSync.release", raw);
682
+ return {
683
+ artifactRef: stateString("lastSync.release.artifactRef", fields.artifactRef, exports.ARTIFACT_REPOSITORY_PATTERN, "a registry repository"),
684
+ digest: stateString("lastSync.release.digest", fields.digest, exports.ARTIFACT_DIGEST_PATTERN, "a sha256 digest"),
685
+ version: orNull(fields.version, (label) => stateString("lastSync.release.version", label))
686
+ };
687
+ });
688
+ if (source === "release" !== (release !== null)) {
689
+ throw new CatalogReleaseStateError(`lastSync.source is "${source}" and lastSync.release is ${release === null ? "null" : "set"}; a run names a release exactly when a release declared it.`);
690
+ }
691
+ return {
692
+ startedAt: stateInstant("lastSync.startedAt", run3.startedAt),
693
+ finishedAt: stateInstant("lastSync.finishedAt", run3.finishedAt),
694
+ imageTag: orNull(run3.imageTag, (tag) => stateString("lastSync.imageTag", tag)),
695
+ source,
696
+ release,
697
+ outcome: stateMember("lastSync.outcome", run3.outcome, exports.CATALOG_SYNC_RUN_OUTCOMES),
698
+ detail: stateString("lastSync.detail", run3.detail)
699
+ };
700
+ }
701
+ __name(parseSyncRun, "parseSyncRun");
702
+ function parsePinned(value) {
703
+ const pin = stateObject("release", value);
704
+ return {
705
+ artifactRef: stateString("release.artifactRef", pin.artifactRef, exports.ARTIFACT_REPOSITORY_PATTERN, "a registry repository"),
706
+ digest: stateString("release.digest", pin.digest, exports.ARTIFACT_DIGEST_PATTERN, "a sha256 digest"),
707
+ version: stateString("release.version", pin.version),
708
+ provenance: orNull(pin.provenance, (raw) => {
709
+ const fields = stateObject("release.provenance", raw);
710
+ return {
711
+ repository: stateString("release.provenance.repository", fields.repository),
712
+ commit: stateString("release.provenance.commit", fields.commit)
713
+ };
714
+ }),
715
+ pinnedBy: stateString("release.pinnedBy", pin.pinnedBy),
716
+ pinnedByName: orNull(pin.pinnedByName, (name) => stateString("release.pinnedByName", name)),
717
+ pinnedAt: stateInstant("release.pinnedAt", pin.pinnedAt)
718
+ };
719
+ }
720
+ __name(parsePinned, "parsePinned");
721
+ function parseCatalogReleaseState(value) {
722
+ const state = stateObject("the catalog release state", value);
723
+ for (const field of [
724
+ "release",
725
+ "imageTag",
726
+ "lastSync",
727
+ "liveSince"
728
+ ]) {
729
+ if (!(field in state)) throw new CatalogReleaseStateError(`${field} is missing.`);
730
+ }
731
+ const release = orNull(state.release, parsePinned);
732
+ const liveSince = orNull(state.liveSince, (instant) => stateInstant("liveSince", instant));
733
+ if (release === null && liveSince !== null) {
734
+ throw new CatalogReleaseStateError("liveSince is set with nothing pinned; only a pin goes live.");
735
+ }
736
+ return {
737
+ release,
738
+ imageTag: orNull(state.imageTag, (tag) => stateString("imageTag", tag)),
739
+ lastSync: orNull(state.lastSync, parseSyncRun),
740
+ liveSince
741
+ };
742
+ }
743
+ __name(parseCatalogReleaseState, "parseCatalogReleaseState");
624
744
  }
625
745
  });
626
746
 
@@ -631,12 +751,13 @@ var require_action_repository = __commonJS({
631
751
  Object.defineProperty(exports, "__esModule", {
632
752
  value: true
633
753
  });
634
- exports.ACTION_REPOSITORY_PREFIX = void 0;
754
+ exports.ACTION_SOURCE_REPOSITORY_PREFIX = exports.ACTION_REPOSITORY_PREFIX = void 0;
635
755
  exports.actionKeyRefusal = actionKeyRefusal;
636
756
  exports.isActionKey = isActionKey;
637
757
  exports.actionRepositoryName = actionRepositoryName;
758
+ exports.actionSourceRepository = actionSourceRepository2;
638
759
  exports.actionKeyOf = actionKeyOf;
639
- exports.actionPackageRefusal = actionPackageRefusal3;
760
+ exports.actionPackageRefusal = actionPackageRefusal4;
640
761
  exports.ACTION_REPOSITORY_PREFIX = "action-";
641
762
  var WORD = /^[a-z][a-z0-9]*$/;
642
763
  var VERB = /^[a-z][a-z0-9]*(?:[A-Z][a-z0-9]+)*$/;
@@ -666,6 +787,11 @@ var require_action_repository = __commonJS({
666
787
  return `${exports.ACTION_REPOSITORY_PREFIX}${provider}-${resource}-${kebab(verb)}`;
667
788
  }
668
789
  __name(actionRepositoryName, "actionRepositoryName");
790
+ exports.ACTION_SOURCE_REPOSITORY_PREFIX = "https://github.com/runveric/";
791
+ function actionSourceRepository2(key) {
792
+ return `${exports.ACTION_SOURCE_REPOSITORY_PREFIX}${actionRepositoryName(key)}`;
793
+ }
794
+ __name(actionSourceRepository2, "actionSourceRepository");
669
795
  function actionKeyOf(name) {
670
796
  if (!name.startsWith(exports.ACTION_REPOSITORY_PREFIX)) return null;
671
797
  const [provider, resource, first2, ...rest] = name.slice(exports.ACTION_REPOSITORY_PREFIX.length).split("-");
@@ -675,7 +801,7 @@ var require_action_repository = __commonJS({
675
801
  return isActionKey(key) && actionRepositoryName(key) === name ? key : null;
676
802
  }
677
803
  __name(actionKeyOf, "actionKeyOf");
678
- function actionPackageRefusal3(key, artifactRef) {
804
+ function actionPackageRefusal4(key, artifactRef) {
679
805
  const keyRefusal = actionKeyRefusal(key);
680
806
  if (keyRefusal !== null) return `${keyRefusal} No package can carry it.`;
681
807
  const cut = artifactRef.lastIndexOf("/");
@@ -684,7 +810,7 @@ var require_action_repository = __commonJS({
684
810
  if (named === expected) return null;
685
811
  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
812
  }
687
- __name(actionPackageRefusal3, "actionPackageRefusal");
813
+ __name(actionPackageRefusal4, "actionPackageRefusal");
688
814
  }
689
815
  });
690
816
 
@@ -697,7 +823,7 @@ var require_catalog_release_spec = __commonJS({
697
823
  });
698
824
  exports.CatalogReleaseSpecError = void 0;
699
825
  exports.splitArtifactReference = splitArtifactReference;
700
- exports.parseCatalogReleaseSpec = parseCatalogReleaseSpec2;
826
+ exports.parseCatalogReleaseSpec = parseCatalogReleaseSpec3;
701
827
  var CatalogReleaseSpecError = class CatalogReleaseSpecError extends Error {
702
828
  static {
703
829
  __name(this, "CatalogReleaseSpecError");
@@ -717,9 +843,9 @@ var require_catalog_release_spec = __commonJS({
717
843
  };
718
844
  }
719
845
  __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)) {
846
+ var isMapping3 = /* @__PURE__ */ __name((value) => typeof value === "object" && value !== null && !Array.isArray(value), "isMapping");
847
+ function parseCatalogReleaseSpec3(value) {
848
+ if (!isMapping3(value)) {
723
849
  throw new CatalogReleaseSpecError("release.yaml is not a mapping of `version` and `actions`.");
724
850
  }
725
851
  const unknown = Object.keys(value).filter((key) => key !== "version" && key !== "actions");
@@ -730,10 +856,10 @@ var require_catalog_release_spec = __commonJS({
730
856
  if (typeof version2 !== "string" || version2.trim().length === 0) {
731
857
  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
858
  }
733
- if (actions === null || actions === void 0 || isMapping2(actions) && Object.keys(actions).length === 0) {
859
+ if (actions === null || actions === void 0 || isMapping3(actions) && Object.keys(actions).length === 0) {
734
860
  throw new CatalogReleaseSpecError("release.yaml declares no action. An empty release is refused: pinned, it would declare a catalog with nothing in it.");
735
861
  }
736
- if (!isMapping2(actions)) {
862
+ if (!isMapping3(actions)) {
737
863
  throw new CatalogReleaseSpecError("release.yaml's actions is not a mapping of action key to <repository>@<digest>.");
738
864
  }
739
865
  return {
@@ -750,7 +876,7 @@ var require_catalog_release_spec = __commonJS({
750
876
  })
751
877
  };
752
878
  }
753
- __name(parseCatalogReleaseSpec2, "parseCatalogReleaseSpec");
879
+ __name(parseCatalogReleaseSpec3, "parseCatalogReleaseSpec");
754
880
  }
755
881
  });
756
882
 
@@ -761,7 +887,7 @@ var require_catalog_lock = __commonJS({
761
887
  Object.defineProperty(exports, "__esModule", {
762
888
  value: true
763
889
  });
764
- exports.CatalogLockError = exports.CATALOG_LOCK_FILE = exports.CATALOG_LOCK_SCHEMA_V1 = void 0;
890
+ exports.MANIFEST_SHA256_PATTERN = exports.CatalogLockError = exports.CATALOG_LOCK_ENGINE_EXECUTION = exports.CATALOG_LOCK_FILE = exports.CATALOG_LOCK_SCHEMA_V1 = void 0;
765
891
  exports.parseCatalogLock = parseCatalogLock;
766
892
  exports.catalogLockText = catalogLockText;
767
893
  exports.authoredElsewhere = authoredElsewhere;
@@ -769,6 +895,7 @@ var require_catalog_lock = __commonJS({
769
895
  var catalog_release_1 = require_catalog_release();
770
896
  exports.CATALOG_LOCK_SCHEMA_V1 = "runveric.catalog-lock/v1";
771
897
  exports.CATALOG_LOCK_FILE = "release.lock.json";
898
+ exports.CATALOG_LOCK_ENGINE_EXECUTION = "engine";
772
899
  var CatalogLockError = class CatalogLockError extends Error {
773
900
  static {
774
901
  __name(this, "CatalogLockError");
@@ -779,7 +906,7 @@ var require_catalog_lock = __commonJS({
779
906
  }
780
907
  };
781
908
  exports.CatalogLockError = CatalogLockError;
782
- var SHA256_HEX = /^[0-9a-f]{64}$/;
909
+ exports.MANIFEST_SHA256_PATTERN = /^[0-9a-f]{64}$/;
783
910
  function objectAt(path, value, keys) {
784
911
  if (typeof value !== "object" || value === null || Array.isArray(value)) {
785
912
  throw new CatalogLockError(`${path} is not an object.`);
@@ -834,7 +961,9 @@ var require_catalog_lock = __commonJS({
834
961
  "artifactRef",
835
962
  "digest",
836
963
  "manifestSha256",
837
- "provenance"
964
+ "provenance",
965
+ "executes",
966
+ "imageDigest"
838
967
  ]);
839
968
  const key = stringAt(`${path}.key`, action.key);
840
969
  const artifactRef = stringAt(`${path}.artifactRef`, action.artifactRef, catalog_release_1.ARTIFACT_REPOSITORY_PATTERN, "a registry repository");
@@ -848,14 +977,24 @@ var require_catalog_lock = __commonJS({
848
977
  "repository",
849
978
  "commit"
850
979
  ]);
980
+ if (action.executes === void 0 !== (action.imageDigest === void 0)) {
981
+ throw new CatalogLockError(`${path} carries ${action.executes === void 0 ? "imageDigest without executes" : "executes without imageDigest"}; an engine action records both, a built-in neither.`);
982
+ }
983
+ if (action.executes !== void 0 && action.executes !== exports.CATALOG_LOCK_ENGINE_EXECUTION) {
984
+ throw new CatalogLockError(`${path}.executes is ${JSON.stringify(action.executes)}; the one value it takes is "${exports.CATALOG_LOCK_ENGINE_EXECUTION}".`);
985
+ }
851
986
  lock.actions.push({
852
987
  key,
853
988
  artifactRef,
854
989
  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"),
990
+ manifestSha256: stringAt(`${path}.manifestSha256`, action.manifestSha256, exports.MANIFEST_SHA256_PATTERN, "64 lowercase hex characters"),
856
991
  provenance: {
857
992
  repository: stringAt(`${path}.provenance.repository`, provenance2.repository),
858
993
  commit: stringAt(`${path}.provenance.commit`, provenance2.commit)
994
+ },
995
+ ...action.executes === void 0 ? {} : {
996
+ executes: exports.CATALOG_LOCK_ENGINE_EXECUTION,
997
+ imageDigest: stringAt(`${path}.imageDigest`, action.imageDigest, catalog_release_1.ARTIFACT_DIGEST_PATTERN, "a sha256 digest")
859
998
  }
860
999
  });
861
1000
  });
@@ -880,6 +1019,10 @@ var require_catalog_lock = __commonJS({
880
1019
  provenance: {
881
1020
  repository: action.provenance.repository,
882
1021
  commit: action.provenance.commit
1022
+ },
1023
+ ...action.executes === void 0 || action.imageDigest === void 0 ? {} : {
1024
+ executes: action.executes,
1025
+ imageDigest: action.imageDigest
883
1026
  }
884
1027
  }))
885
1028
  };
@@ -1654,6 +1797,7 @@ var require_validation = __commonJS({
1654
1797
  exports.isCheckLevel = isCheckLevel;
1655
1798
  exports.isRefusalCode = isRefusalCode;
1656
1799
  exports.isDecisionCode = isDecisionCode;
1800
+ exports.validationCheckOf = validationCheckOf;
1657
1801
  exports.scopeFailureDetail = scopeFailureDetail;
1658
1802
  exports.targetsUnavailableFrom = targetsUnavailableFrom;
1659
1803
  exports.compareTargets = compareTargets;
@@ -1796,12 +1940,69 @@ var require_validation = __commonJS({
1796
1940
  "disk.customerKey",
1797
1941
  "disk.customerKeyUnverified",
1798
1942
  "disk.guestEncrypted",
1799
- "disk.noSnapshot"
1943
+ "disk.noSnapshot",
1944
+ // A confidential VM's disk: its snapshot keeps the VM's guest state, region-bound, and restoring
1945
+ // one is unverified (docs/126 §6.6.1) — the operator's call, never a refusal.
1946
+ "disk.confidential"
1800
1947
  ];
1801
1948
  function isDecisionCode(value) {
1802
1949
  return typeof value === "string" && exports.DECISION_CODES.includes(value);
1803
1950
  }
1804
1951
  __name(isDecisionCode, "isDecisionCode");
1952
+ function validationCheckOf(value) {
1953
+ const raw = typeof value === "object" && value !== null ? value : {};
1954
+ const level = raw.level;
1955
+ if (!isCheckLevel(level)) {
1956
+ return {
1957
+ refusal: `A check level of "${String(level)}" is not in the vocabulary.`
1958
+ };
1959
+ }
1960
+ if (typeof raw.title !== "string" || raw.title.length === 0) {
1961
+ return {
1962
+ refusal: "Every check must carry a title."
1963
+ };
1964
+ }
1965
+ if (raw.decision !== void 0 && raw.refusal !== void 0) {
1966
+ return {
1967
+ refusal: `A check carrying both a decision ("${String(raw.decision)}") and a refusal ("${String(raw.refusal)}") is not a check any producer of ours can build.`
1968
+ };
1969
+ }
1970
+ if (raw.decision !== void 0) {
1971
+ if (!isDecisionCode(raw.decision) || level === "info") {
1972
+ return {
1973
+ refusal: `A check's decision of "${String(raw.decision)}" at level "${level}" is not one this channel accepts.`
1974
+ };
1975
+ }
1976
+ return {
1977
+ check: {
1978
+ level,
1979
+ title: raw.title,
1980
+ decision: raw.decision
1981
+ }
1982
+ };
1983
+ }
1984
+ if (raw.refusal !== void 0) {
1985
+ if (!isRefusalCode(raw.refusal) || level !== "error") {
1986
+ return {
1987
+ refusal: `A check's refusal of "${String(raw.refusal)}" at level "${level}" is not one this channel accepts.`
1988
+ };
1989
+ }
1990
+ return {
1991
+ check: {
1992
+ level,
1993
+ title: raw.title,
1994
+ refusal: raw.refusal
1995
+ }
1996
+ };
1997
+ }
1998
+ return {
1999
+ check: {
2000
+ level,
2001
+ title: raw.title
2002
+ }
2003
+ };
2004
+ }
2005
+ __name(validationCheckOf, "validationCheckOf");
1805
2006
  function scopeFailureDetail(error) {
1806
2007
  const raw = error instanceof Error ? error.message : String(error);
1807
2008
  const oneLine = raw.replace(/\s+/g, " ").trim().replace(/[.!]+$/, "");
@@ -1872,7 +2073,7 @@ var require_execution = __commonJS({
1872
2073
  Object.defineProperty(exports, "__esModule", {
1873
2074
  value: true
1874
2075
  });
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;
2076
+ 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.validationCheckOf = 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;
1876
2077
  exports.executionLogStreamSentence = executionLogStreamSentence;
1877
2078
  exports.actionRunnabilityCopy = actionRunnabilityCopy;
1878
2079
  exports.actionRunnabilityHeadline = actionRunnabilityHeadline;
@@ -2033,6 +2234,12 @@ var require_execution = __commonJS({
2033
2234
  return validation_1.targetsValidationCheck;
2034
2235
  }, "get")
2035
2236
  });
2237
+ Object.defineProperty(exports, "validationCheckOf", {
2238
+ enumerable: true,
2239
+ get: /* @__PURE__ */ __name(function() {
2240
+ return validation_1.validationCheckOf;
2241
+ }, "get")
2242
+ });
2036
2243
  exports.EXECUTION_LOG_ACTIVITIES = [
2037
2244
  "executing",
2038
2245
  "queued",
@@ -2216,11 +2423,15 @@ var require_decisions = __commonJS({
2216
2423
  Object.defineProperty(exports, "__esModule", {
2217
2424
  value: true
2218
2425
  });
2219
- exports.DECISION_ACKNOWLEDGEMENT = void 0;
2426
+ exports.DECISION_ACKNOWLEDGEMENT = exports.TARGET_DISPOSITIONS = exports.UNDECIDED_AT_RUN = void 0;
2220
2427
  exports.recordedDecisionsOf = recordedDecisionsOf;
2428
+ exports.settleDecisionChecks = settleDecisionChecks;
2221
2429
  exports.raisedDecisionCodes = raisedDecisionCodes;
2222
2430
  exports.decisionMismatch = decisionMismatch;
2223
2431
  exports.decisionMismatchSentence = decisionMismatchSentence;
2432
+ exports.targetDecisionsMismatchSentence = targetDecisionsMismatchSentence;
2433
+ exports.decisionsMadeAtSubmit = decisionsMadeAtSubmit;
2434
+ exports.isTargetDisposition = isTargetDisposition;
2224
2435
  var validation_1 = require_validation();
2225
2436
  function recordedDecisionsOf(stored) {
2226
2437
  if (!Array.isArray(stored)) return [];
@@ -2241,6 +2452,22 @@ var require_decisions = __commonJS({
2241
2452
  return read;
2242
2453
  }
2243
2454
  __name(recordedDecisionsOf, "recordedDecisionsOf");
2455
+ exports.UNDECIDED_AT_RUN = "Nobody acknowledged this before the run, so it was not acted on.";
2456
+ function settleDecisionChecks(checks, inHand) {
2457
+ if (inHand.phase === "plan") return [
2458
+ ...checks
2459
+ ];
2460
+ return checks.map((check) => {
2461
+ if (!check.decision || check.level === "error") return check;
2462
+ if (inHand.recorded.some((decision) => decision.code === check.decision)) return check;
2463
+ return {
2464
+ ...check,
2465
+ level: "error",
2466
+ title: `${check.title} ${exports.UNDECIDED_AT_RUN}`
2467
+ };
2468
+ });
2469
+ }
2470
+ __name(settleDecisionChecks, "settleDecisionChecks");
2244
2471
  function raisedDecisionCodes(checks) {
2245
2472
  const raised = [];
2246
2473
  for (const check of checks) {
@@ -2258,27 +2485,57 @@ var require_decisions = __commonJS({
2258
2485
  };
2259
2486
  }
2260
2487
  __name(decisionMismatch, "decisionMismatch");
2261
- function decisionMismatchSentence(mismatch) {
2488
+ function decisionMismatchSentence(mismatch, checks = []) {
2489
+ return `This run was not submitted: the checks were run again at submit, and they no longer match what was acknowledged. ${mismatchParts(mismatch, checks).join(" ")} Review the plan and submit again.`;
2490
+ }
2491
+ __name(decisionMismatchSentence, "decisionMismatchSentence");
2492
+ function targetDecisionsMismatchSentence(mismatches) {
2493
+ const parts = mismatches.map((target) => target.unknown ? `${target.label}: no longer one of this step's targets.` : `${target.label}: ${mismatchParts(target, target.checks).join(" ")}`);
2494
+ const count = mismatches.length;
2495
+ return `This run was not started: the checks were run again, and for ${count} ${count === 1 ? "target" : "targets"} they no longer match what was decided. ${parts.join(" ")} Review the plan and start the run again.`;
2496
+ }
2497
+ __name(targetDecisionsMismatchSentence, "targetDecisionsMismatchSentence");
2498
+ function mismatchParts(mismatch, checks) {
2262
2499
  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`);
2500
+ for (const code of mismatch.unacknowledged) {
2501
+ const sentence = checks.find((check) => check.decision === code)?.title ?? exports.DECISION_ACKNOWLEDGEMENT[code];
2502
+ parts.push(`Not acknowledged: ${withStop(sentence)}`);
2265
2503
  }
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"}`);
2504
+ for (const code of mismatch.notRaised) {
2505
+ parts.push(`Acknowledged, but no longer raised: \u201C${exports.DECISION_ACKNOWLEDGEMENT[code]}\u201D.`);
2268
2506
  }
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.`;
2507
+ return parts;
2270
2508
  }
2271
- __name(decisionMismatchSentence, "decisionMismatchSentence");
2272
- function quoteCodes(codes) {
2273
- return codes.map((code) => `"${code}"`).join(", ");
2509
+ __name(mismatchParts, "mismatchParts");
2510
+ function decisionsMadeAtSubmit(codes, by, at) {
2511
+ return codes.map((code) => ({
2512
+ code,
2513
+ acknowledgedBy: by,
2514
+ acknowledgedAt: at.toISOString()
2515
+ }));
2516
+ }
2517
+ __name(decisionsMadeAtSubmit, "decisionsMadeAtSubmit");
2518
+ exports.TARGET_DISPOSITIONS = [
2519
+ "ready",
2520
+ "left_out",
2521
+ "refused"
2522
+ ];
2523
+ function isTargetDisposition(value) {
2524
+ return typeof value === "string" && exports.TARGET_DISPOSITIONS.includes(value);
2525
+ }
2526
+ __name(isTargetDisposition, "isTargetDisposition");
2527
+ function withStop(sentence) {
2528
+ const trimmed = sentence.trim();
2529
+ return /[.!?]$/.test(trimmed) ? trimmed : `${trimmed}.`;
2274
2530
  }
2275
- __name(quoteCodes, "quoteCodes");
2531
+ __name(withStop, "withStop");
2276
2532
  exports.DECISION_ACKNOWLEDGEMENT = {
2277
2533
  "disk.recentlyDetached": "Delete it even though it was detached recently",
2278
2534
  "disk.customerKey": "Delete it \u2014 restoring later depends on this key staying enabled",
2279
2535
  "disk.customerKeyUnverified": "Delete it without confirmation that the key is enabled",
2280
2536
  "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"
2537
+ "disk.noSnapshot": "Delete it with no snapshot \u2014 this cannot be undone",
2538
+ "disk.confidential": "Delete it, accepting that restoring a confidential VM disk is not verified"
2282
2539
  };
2283
2540
  }
2284
2541
  });
@@ -2642,8 +2899,59 @@ var require_governance = __commonJS({
2642
2899
  value: true
2643
2900
  });
2644
2901
  exports.ANY_POLICY_MATCH = void 0;
2902
+ exports.approvalAssessmentOf = approvalAssessmentOf;
2903
+ exports.planAssessmentOf = planAssessmentOf;
2645
2904
  exports.approvalOutcome = approvalOutcome;
2646
2905
  exports.policyScopeSentence = policyScopeSentence;
2906
+ var decisions_1 = require_decisions();
2907
+ function approvalAssessmentOf(checks, decided) {
2908
+ const assessment = {
2909
+ facts: [],
2910
+ warnings: [],
2911
+ decisions: []
2912
+ };
2913
+ for (const check of checks) {
2914
+ if (check.decision) {
2915
+ const answer = decided.find((decision) => decision.code === check.decision);
2916
+ if (answer) assessment.decisions.push({
2917
+ ...answer,
2918
+ title: check.title
2919
+ });
2920
+ } else if (check.level === "warn") {
2921
+ assessment.warnings.push(check.title);
2922
+ } else if (check.level === "info") {
2923
+ assessment.facts.push(check.title);
2924
+ }
2925
+ }
2926
+ return assessment;
2927
+ }
2928
+ __name(approvalAssessmentOf, "approvalAssessmentOf");
2929
+ function planAssessmentOf(value) {
2930
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return null;
2931
+ const a = value;
2932
+ if (!Array.isArray(a.facts) || !Array.isArray(a.warnings) || !Array.isArray(a.decisions)) {
2933
+ return null;
2934
+ }
2935
+ const decisions = [];
2936
+ for (const entry of a.decisions) {
2937
+ const [decision] = (0, decisions_1.recordedDecisionsOf)([
2938
+ entry
2939
+ ]);
2940
+ const title = typeof entry === "object" && entry !== null ? entry.title : null;
2941
+ if (!decision || typeof title !== "string") return null;
2942
+ decisions.push({
2943
+ ...decision,
2944
+ title
2945
+ });
2946
+ }
2947
+ const strings = /* @__PURE__ */ __name((list) => list.filter((item) => typeof item === "string"), "strings");
2948
+ return {
2949
+ facts: strings(a.facts),
2950
+ warnings: strings(a.warnings),
2951
+ decisions
2952
+ };
2953
+ }
2954
+ __name(planAssessmentOf, "planAssessmentOf");
2647
2955
  exports.ANY_POLICY_MATCH = {
2648
2956
  environments: [],
2649
2957
  riskLevels: [],
@@ -2754,7 +3062,10 @@ var require_operations = __commonJS({
2754
3062
  Object.defineProperty(exports, "__esModule", {
2755
3063
  value: true
2756
3064
  });
2757
- exports.OPERATION_RUN_TARGET_STATUSES = exports.OPERATION_RUN_STEP_STATUSES = exports.OPERATION_RUN_STATUSES = exports.STEP_FAILURE_POLICIES = exports.OPERATION_STEP_KINDS = exports.DRY_RUN_OUTCOMES = exports.OPERATION_VERIFICATION_LIFECYCLES = exports.OPERATION_HEALTH_STATES = exports.OPERATION_LIFECYCLES = void 0;
3065
+ exports.PREVIEW_UNCHECKED_SENTENCE = exports.PREVIEW_UNCHECKED_REASONS = exports.OPERATION_RUN_TARGET_STATUSES = exports.OPERATION_RUN_STEP_STATUSES = exports.OPERATION_RUN_STATUSES = exports.STEP_FAILURE_POLICIES = exports.OPERATION_STEP_KINDS = exports.DRY_RUN_OUTCOMES = exports.OPERATION_VERIFICATION_LIFECYCLES = exports.OPERATION_HEALTH_STATES = exports.OPERATION_LIFECYCLES = void 0;
3066
+ exports.targetPlanOf = targetPlanOf;
3067
+ var governance_1 = require_governance();
3068
+ var decisions_1 = require_decisions();
2758
3069
  exports.OPERATION_LIFECYCLES = [
2759
3070
  "draft",
2760
3071
  "published",
@@ -2821,6 +3132,35 @@ var require_operations = __commonJS({
2821
3132
  "rolled_back",
2822
3133
  "rollback_failed"
2823
3134
  ];
3135
+ function targetPlanOf(stored) {
3136
+ if (!Array.isArray(stored)) return null;
3137
+ const plan = [];
3138
+ for (const entry of stored) {
3139
+ if (typeof entry !== "object" || entry === null || Array.isArray(entry)) continue;
3140
+ const { ref, label, disposition, decisions, assessment, refusal } = entry;
3141
+ if (typeof ref !== "string" || ref.length === 0) continue;
3142
+ if (typeof label !== "string" || !(0, decisions_1.isTargetDisposition)(disposition)) continue;
3143
+ if (refusal !== null && typeof refusal !== "string") continue;
3144
+ plan.push({
3145
+ ref,
3146
+ label,
3147
+ disposition,
3148
+ decisions: (0, decisions_1.recordedDecisionsOf)(decisions),
3149
+ assessment: (0, governance_1.planAssessmentOf)(assessment),
3150
+ refusal: refusal ?? null
3151
+ });
3152
+ }
3153
+ return plan;
3154
+ }
3155
+ __name(targetPlanOf, "targetPlanOf");
3156
+ exports.PREVIEW_UNCHECKED_REASONS = [
3157
+ "depends_on_earlier_output",
3158
+ "connector_plans_no_checks"
3159
+ ];
3160
+ exports.PREVIEW_UNCHECKED_SENTENCE = {
3161
+ depends_on_earlier_output: "Not checked before the run: this step's parameters come from an earlier step's outputs, which exist only once that step has run. Each target is checked when its execution is submitted, and a target that would need a decision is not acted on.",
3162
+ connector_plans_no_checks: "This action runs no checks of its own before the run."
3163
+ };
2824
3164
  }
2825
3165
  });
2826
3166
 
@@ -2943,7 +3283,7 @@ var require_package_execution = __commonJS({
2943
3283
  Object.defineProperty(exports, "__esModule", {
2944
3284
  value: true
2945
3285
  });
2946
- exports.PACKAGE_CONTAINER_ENV = exports.PACKAGE_PHASES = void 0;
3286
+ exports.RUN_TOKEN_REJECTED = exports.RUN_TOKEN_HEADER = exports.PACKAGE_RUNNER_ENTRYPOINT = exports.PACKAGE_CONTAINER_ENV = exports.PACKAGE_PHASES = void 0;
2947
3287
  exports.PACKAGE_PHASES = [
2948
3288
  "validate",
2949
3289
  "run",
@@ -2965,6 +3305,11 @@ var require_package_execution = __commonJS({
2965
3305
  egressControlUrl: "RUNVERIC_EGRESS_CONTROL_URL",
2966
3306
  egressControlToken: "RUNVERIC_EGRESS_CONTROL_TOKEN"
2967
3307
  };
3308
+ exports.PACKAGE_RUNNER_ENTRYPOINT = [
3309
+ "/opt/runveric/runner/launch.sh"
3310
+ ];
3311
+ exports.RUN_TOKEN_HEADER = "x-runveric-run-token";
3312
+ exports.RUN_TOKEN_REJECTED = "This run token is not valid for this execution.";
2968
3313
  }
2969
3314
  });
2970
3315
 
@@ -3215,6 +3560,803 @@ var require_runners = __commonJS({
3215
3560
  }
3216
3561
  });
3217
3562
 
3563
+ // ../contracts/dist/scoped-credential.js
3564
+ var require_scoped_credential = __commonJS({
3565
+ "../contracts/dist/scoped-credential.js"(exports) {
3566
+ "use strict";
3567
+ Object.defineProperty(exports, "__esModule", {
3568
+ value: true
3569
+ });
3570
+ exports.RunnerStepCredentialExpiredError = exports.RunnerStepCredentialError = exports.STEP_CREDENTIAL_FAILURE = exports.ENGINE_ACTION_TOKEN_HOLDER = exports.RUNNER_STEP_TOKEN_HOLDER = exports.StaticScopedCredential = void 0;
3571
+ var StaticScopedCredential = class StaticScopedCredential {
3572
+ static {
3573
+ __name(this, "StaticScopedCredential");
3574
+ }
3575
+ now;
3576
+ holder;
3577
+ byScope;
3578
+ constructor(tokens2, now = Date.now, holder = exports.RUNNER_STEP_TOKEN_HOLDER) {
3579
+ this.now = now;
3580
+ this.holder = holder;
3581
+ this.byScope = new Map(tokens2.map((token2) => [
3582
+ token2.scope,
3583
+ token2
3584
+ ]));
3585
+ }
3586
+ async getToken(scopes) {
3587
+ const requested = typeof scopes === "string" ? [
3588
+ scopes
3589
+ ] : scopes;
3590
+ for (const scope of requested) {
3591
+ const token2 = this.byScope.get(scope);
3592
+ if (token2) {
3593
+ if (token2.expiresOnTimestamp <= this.now()) {
3594
+ throw new RunnerStepCredentialExpiredError(scope, token2.expiresOnTimestamp, this.now(), this.holder);
3595
+ }
3596
+ return {
3597
+ token: token2.token,
3598
+ expiresOnTimestamp: token2.expiresOnTimestamp
3599
+ };
3600
+ }
3601
+ }
3602
+ throw new RunnerStepCredentialError(requested, [
3603
+ ...this.byScope.keys()
3604
+ ], this.holder);
3605
+ }
3606
+ };
3607
+ exports.StaticScopedCredential = StaticScopedCredential;
3608
+ exports.RUNNER_STEP_TOKEN_HOLDER = {
3609
+ noun: "step",
3610
+ whereDeclared: "The channel mints exactly what a connector's azureAudiences declares, so this connector reaches an Azure resource it does not say it reaches.",
3611
+ whyNoMore: "A step container is handed minted bytes and nothing it can use to get another, so the step ran longer than its own credential and cannot continue."
3612
+ };
3613
+ exports.ENGINE_ACTION_TOKEN_HOLDER = {
3614
+ noun: "action",
3615
+ whereDeclared: "The engine mints exactly the audiences an action's manifest declares in execution.audiences, so this action reaches an Azure resource it does not say it reaches.",
3616
+ whyNoMore: "An action is handed minted bytes and nothing it can use to get another, so it ran longer than its own credential and cannot continue."
3617
+ };
3618
+ exports.STEP_CREDENTIAL_FAILURE = "runvericStepCredentialFailure";
3619
+ var RunnerStepCredentialError = class RunnerStepCredentialError extends Error {
3620
+ static {
3621
+ __name(this, "RunnerStepCredentialError");
3622
+ }
3623
+ constructor(requested, available, holder = exports.RUNNER_STEP_TOKEN_HOLDER) {
3624
+ super(`This ${holder.noun} was authorized for ${available.length > 0 ? available.map(quote).join(", ") : "no scopes"} and asked for ${requested.map(quote).join(", ")}. ${holder.whereDeclared}`);
3625
+ this.name = "RunnerStepCredentialError";
3626
+ }
3627
+ /** Not a fact about the resource — see {@link STEP_CREDENTIAL_FAILURE}. */
3628
+ [exports.STEP_CREDENTIAL_FAILURE] = true;
3629
+ };
3630
+ exports.RunnerStepCredentialError = RunnerStepCredentialError;
3631
+ var RunnerStepCredentialExpiredError = class RunnerStepCredentialExpiredError extends Error {
3632
+ static {
3633
+ __name(this, "RunnerStepCredentialExpiredError");
3634
+ }
3635
+ constructor(scope, expiresOnTimestamp, now, holder = exports.RUNNER_STEP_TOKEN_HOLDER) {
3636
+ super(`This ${holder.noun}'s token for ${quote(scope)} expired ${Math.round((now - expiresOnTimestamp) / 1e3)}s ago, at ${new Date(expiresOnTimestamp).toISOString()}. ${holder.whyNoMore}`);
3637
+ this.name = "RunnerStepCredentialExpiredError";
3638
+ }
3639
+ /** Not a fact about the resource — see {@link STEP_CREDENTIAL_FAILURE}. */
3640
+ [exports.STEP_CREDENTIAL_FAILURE] = true;
3641
+ };
3642
+ exports.RunnerStepCredentialExpiredError = RunnerStepCredentialExpiredError;
3643
+ function quote(scope) {
3644
+ return `"${scope}"`;
3645
+ }
3646
+ __name(quote, "quote");
3647
+ }
3648
+ });
3649
+
3650
+ // ../contracts/dist/action-protocol.js
3651
+ var require_action_protocol = __commonJS({
3652
+ "../contracts/dist/action-protocol.js"(exports) {
3653
+ "use strict";
3654
+ Object.defineProperty(exports, "__esModule", {
3655
+ value: true
3656
+ });
3657
+ exports.ActionProtocolError = exports.JWT_SHAPE = exports.REDACTED_SECRET = exports.ENGINE_RUN_ROUTES = exports.ACTION_PROBE_ENV = exports.ACTION_RUNTIME_PROBE_FLAG = exports.ACTION_CONTAINER_ENV = exports.MAX_PLAN_VALIDATE_TARGETS = exports.ACTION_PROTOCOL_LIMITS = exports.ACTION_EXECUTION_KINDS = exports.ENGINE_CONNECTOR_KEY = exports.ACTION_CONTRACTS = void 0;
3658
+ exports.isActionContract = isActionContract;
3659
+ exports.engineRunUrl = engineRunUrl;
3660
+ exports.actionAudienceRefusal = actionAudienceRefusal;
3661
+ exports.actionTokenScope = actionTokenScope;
3662
+ exports.redactSecrets = redactSecrets;
3663
+ exports.redactJwtShaped = redactJwtShaped;
3664
+ exports.carriesJwtShaped = carriesJwtShaped;
3665
+ exports.jsonByteLength = jsonByteLength;
3666
+ exports.parseActionInvocation = parseActionInvocation;
3667
+ exports.parseActionEvent = parseActionEvent;
3668
+ exports.tooManyEvents = tooManyEvents;
3669
+ exports.parseActionCheckpoint = parseActionCheckpoint;
3670
+ exports.parseCheckpointAck = parseCheckpointAck;
3671
+ exports.parseActionReport = parseActionReport;
3672
+ var wire_identity_1 = require_wire_identity();
3673
+ var package_execution_1 = require_package_execution();
3674
+ var validation_1 = require_validation();
3675
+ exports.ACTION_CONTRACTS = [
3676
+ wire_identity_1.ACTION_PROTOCOL_V1
3677
+ ];
3678
+ function isActionContract(value) {
3679
+ return typeof value === "string" && exports.ACTION_CONTRACTS.includes(value);
3680
+ }
3681
+ __name(isActionContract, "isActionContract");
3682
+ exports.ENGINE_CONNECTOR_KEY = "runveric.engine";
3683
+ exports.ACTION_EXECUTION_KINDS = [
3684
+ "engine"
3685
+ ];
3686
+ exports.ACTION_PROTOCOL_LIMITS = {
3687
+ maxEvents: 500,
3688
+ maxEventBytes: 2048,
3689
+ maxReportBytes: 262144,
3690
+ maxRestorePointBytes: 65536
3691
+ };
3692
+ exports.MAX_PLAN_VALIDATE_TARGETS = 200;
3693
+ exports.ACTION_CONTAINER_ENV = {
3694
+ runToken: "RUNVERIC_ACTION_RUN_TOKEN",
3695
+ callbackBaseUrl: "RUNVERIC_ACTION_CALLBACK_BASE_URL",
3696
+ runId: "RUNVERIC_ACTION_RUN_ID",
3697
+ imageDigest: "RUNVERIC_ACTION_IMAGE_DIGEST"
3698
+ };
3699
+ exports.ACTION_RUNTIME_PROBE_FLAG = "--probe";
3700
+ exports.ACTION_PROBE_ENV = {
3701
+ identities: "RUNVERIC_PROBE_IDENTITIES"
3702
+ };
3703
+ exports.ENGINE_RUN_ROUTES = {
3704
+ base: "internal/engine-runs",
3705
+ invocation: "invocation",
3706
+ events: "events",
3707
+ checkpoint: "checkpoint",
3708
+ report: "report"
3709
+ };
3710
+ function engineRunUrl(callbackBaseUrl, runId, message) {
3711
+ const origin2 = callbackBaseUrl.replace(/\/+$/, "");
3712
+ return `${origin2}/${exports.ENGINE_RUN_ROUTES.base}/${encodeURIComponent(runId)}/${exports.ENGINE_RUN_ROUTES[message]}`;
3713
+ }
3714
+ __name(engineRunUrl, "engineRunUrl");
3715
+ function actionAudienceRefusal(value) {
3716
+ if (typeof value === "string" && AUDIENCE_ORIGIN.test(value)) return null;
3717
+ return `"${String(value)}" is not an audience. An audience is the https origin of the resource a token is for \u2014 lowercase, with no port, path or trailing slash, e.g. "https://management.azure.com".`;
3718
+ }
3719
+ __name(actionAudienceRefusal, "actionAudienceRefusal");
3720
+ var AUDIENCE_ORIGIN = /^https:\/\/[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)+$/;
3721
+ function actionTokenScope(audience) {
3722
+ return `${audience}/.default`;
3723
+ }
3724
+ __name(actionTokenScope, "actionTokenScope");
3725
+ exports.REDACTED_SECRET = "[redacted]";
3726
+ function redactSecrets(value, secrets) {
3727
+ const present = secrets.filter((secret) => secret.length > 0);
3728
+ if (present.length === 0) return value;
3729
+ return mapStrings(value, (text) => present.reduce((out, secret) => out.split(secret).join(exports.REDACTED_SECRET), text));
3730
+ }
3731
+ __name(redactSecrets, "redactSecrets");
3732
+ exports.JWT_SHAPE = /eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]*/;
3733
+ function redactJwtShaped(value) {
3734
+ const everyMatch = new RegExp(exports.JWT_SHAPE.source, "g");
3735
+ return mapStrings(value, (text) => text.replace(everyMatch, exports.REDACTED_SECRET));
3736
+ }
3737
+ __name(redactJwtShaped, "redactJwtShaped");
3738
+ function carriesJwtShaped(value) {
3739
+ let found = false;
3740
+ mapStrings(value, (text) => {
3741
+ found ||= exports.JWT_SHAPE.test(text);
3742
+ return text;
3743
+ });
3744
+ return found;
3745
+ }
3746
+ __name(carriesJwtShaped, "carriesJwtShaped");
3747
+ function mapStrings(value, scrub) {
3748
+ const walk = /* @__PURE__ */ __name((node) => {
3749
+ if (typeof node === "string") return scrub(node);
3750
+ if (Array.isArray(node)) return node.map(walk);
3751
+ if (node !== null && typeof node === "object") {
3752
+ return Object.fromEntries(Object.entries(node).map(([key, item]) => [
3753
+ scrub(key),
3754
+ walk(item)
3755
+ ]));
3756
+ }
3757
+ return node;
3758
+ }, "walk");
3759
+ return walk(value);
3760
+ }
3761
+ __name(mapStrings, "mapStrings");
3762
+ function jsonByteLength(value) {
3763
+ const text = JSON.stringify(value) ?? "";
3764
+ let bytes = 0;
3765
+ for (const char of text) {
3766
+ const point = char.codePointAt(0) ?? 0;
3767
+ bytes += point < 128 ? 1 : point < 2048 ? 2 : point < 65536 ? 3 : 4;
3768
+ }
3769
+ return bytes;
3770
+ }
3771
+ __name(jsonByteLength, "jsonByteLength");
3772
+ var ActionProtocolError = class ActionProtocolError extends Error {
3773
+ static {
3774
+ __name(this, "ActionProtocolError");
3775
+ }
3776
+ constructor(message) {
3777
+ super(message);
3778
+ this.name = "ActionProtocolError";
3779
+ }
3780
+ };
3781
+ exports.ActionProtocolError = ActionProtocolError;
3782
+ function parseActionInvocation(body) {
3783
+ const raw = objectOf(body, "The invocation");
3784
+ onlyKeys(raw, INVOCATION_KEYS, "The invocation");
3785
+ if (!isActionContract(raw.protocol)) {
3786
+ throw new ActionProtocolError(`The invocation speaks ${String(raw.protocol)}, and this runtime speaks ${exports.ACTION_CONTRACTS.join(", ")}.`);
3787
+ }
3788
+ const phase = raw.phase;
3789
+ if (typeof phase !== "string" || !package_execution_1.PACKAGE_PHASES.includes(phase)) {
3790
+ throw new ActionProtocolError(`An invocation phase of "${String(phase)}" is not in the vocabulary.`);
3791
+ }
3792
+ const actionRaw = objectOf(raw.action, "The invocation's action");
3793
+ onlyKeys(actionRaw, [
3794
+ "key",
3795
+ "version",
3796
+ "image"
3797
+ ], "The invocation's action");
3798
+ const action = {
3799
+ key: nonEmpty(actionRaw.key, "The action's key"),
3800
+ version: nonEmpty(actionRaw.version, "The action's version"),
3801
+ image: digestPinned(actionRaw.image)
3802
+ };
3803
+ const executionRaw = objectOf(raw.execution, "The invocation's execution");
3804
+ onlyKeys(executionRaw, [
3805
+ "id",
3806
+ "tenantId",
3807
+ "correlationId",
3808
+ "dryRun"
3809
+ ], "The invocation's execution");
3810
+ if (typeof executionRaw.dryRun !== "boolean") {
3811
+ throw new ActionProtocolError("The invocation's execution must say whether it is a dry run.");
3812
+ }
3813
+ const execution = {
3814
+ id: nonEmpty(executionRaw.id, "The execution's id"),
3815
+ tenantId: nonEmpty(executionRaw.tenantId, "The execution's tenant"),
3816
+ correlationId: nonEmpty(executionRaw.correlationId, "The execution's correlation id"),
3817
+ dryRun: executionRaw.dryRun
3818
+ };
3819
+ const limits = parseLimits(raw.limits);
3820
+ const decisions = parseDecisions(raw.decisions);
3821
+ const targets = parseTargets(raw.targets);
3822
+ const credentials = {
3823
+ tokens: parseTokens(objectOf(raw.credentials, "The invocation's credentials"))
3824
+ };
3825
+ const restorePoint = nullableObject(raw.restorePoint, "The invocation's restore point");
3826
+ const priorResult = nullableObject(raw.priorResult, "The invocation's prior result");
3827
+ if (restorePoint !== null && jsonByteLength(restorePoint) > limits.maxRestorePointBytes) {
3828
+ throw new ActionProtocolError(`The invocation's restore point is larger than ${limits.maxRestorePointBytes} bytes.`);
3829
+ }
3830
+ const base = {
3831
+ protocol: raw.protocol,
3832
+ action,
3833
+ execution,
3834
+ targets,
3835
+ credentials,
3836
+ limits
3837
+ };
3838
+ if (phase === "validate") {
3839
+ if (raw.mode !== "plan" && raw.mode !== "run") {
3840
+ throw new ActionProtocolError(`A validate invocation's mode of "${String(raw.mode)}" is not "plan" or "run".`);
3841
+ }
3842
+ if (raw.mode !== decisions.phase) {
3843
+ throw new ActionProtocolError(`A validate invocation in ${raw.mode} mode carries ${decisions.phase}-phase decisions; the two are the same fact.`);
3844
+ }
3845
+ assertTargetCount(targets.length, raw.mode === "plan" ? exports.MAX_PLAN_VALIDATE_TARGETS : 1, `A ${raw.mode}-mode validate`);
3846
+ if (restorePoint !== null || priorResult !== null) {
3847
+ throw new ActionProtocolError("A validate invocation carries no restore point and no prior result.");
3848
+ }
3849
+ return {
3850
+ ...base,
3851
+ phase,
3852
+ mode: raw.mode,
3853
+ decisions,
3854
+ restorePoint: null,
3855
+ priorResult: null
3856
+ };
3857
+ }
3858
+ if (raw.mode !== void 0) {
3859
+ throw new ActionProtocolError(`A ${phase} invocation has no mode \u2014 only a validate is asked in a mode.`);
3860
+ }
3861
+ if (decisions.phase !== "run") {
3862
+ throw new ActionProtocolError(`A ${phase} invocation carries a run's decisions, never a plan's.`);
3863
+ }
3864
+ assertTargetCount(targets.length, 1, `A ${phase}`);
3865
+ if (phase === "run") {
3866
+ if (priorResult !== null) {
3867
+ throw new ActionProtocolError("A run invocation carries no prior result \u2014 only a rollback reads one.");
3868
+ }
3869
+ return {
3870
+ ...base,
3871
+ phase,
3872
+ decisions,
3873
+ restorePoint,
3874
+ priorResult: null
3875
+ };
3876
+ }
3877
+ return {
3878
+ ...base,
3879
+ phase: "rollback",
3880
+ decisions,
3881
+ restorePoint,
3882
+ priorResult
3883
+ };
3884
+ }
3885
+ __name(parseActionInvocation, "parseActionInvocation");
3886
+ function parseActionEvent(body, limits, alreadyReceived) {
3887
+ if (alreadyReceived >= limits.maxEvents) throw tooManyEvents(limits);
3888
+ const raw = objectOf(body, "An event");
3889
+ onlyKeys(raw, [
3890
+ "level",
3891
+ "message",
3892
+ "data"
3893
+ ], "An event");
3894
+ if (jsonByteLength(raw) > limits.maxEventBytes) {
3895
+ throw new ActionProtocolError(`An event is larger than ${limits.maxEventBytes} bytes.`);
3896
+ }
3897
+ if (typeof raw.level !== "string" || !Object.hasOwn(EVENT_LEVELS, raw.level)) {
3898
+ throw new ActionProtocolError(`An event level of "${String(raw.level)}" is not in the vocabulary.`);
3899
+ }
3900
+ const message = nonEmpty(raw.message, "An event's message");
3901
+ if (raw.data === void 0) return {
3902
+ level: raw.level,
3903
+ message
3904
+ };
3905
+ return {
3906
+ level: raw.level,
3907
+ message,
3908
+ data: jsonObject(raw.data, "An event's data")
3909
+ };
3910
+ }
3911
+ __name(parseActionEvent, "parseActionEvent");
3912
+ function tooManyEvents(limits) {
3913
+ return new ActionProtocolError(`The action sent more than ${limits.maxEvents} events.`);
3914
+ }
3915
+ __name(tooManyEvents, "tooManyEvents");
3916
+ function parseActionCheckpoint(body, limits) {
3917
+ const raw = objectOf(body, "A checkpoint");
3918
+ onlyKeys(raw, [
3919
+ "kind",
3920
+ "record"
3921
+ ], "A checkpoint");
3922
+ if (raw.kind !== "restore_point") {
3923
+ throw new ActionProtocolError(`A checkpoint of kind "${String(raw.kind)}" is not one this contract knows.`);
3924
+ }
3925
+ const record = jsonObject(raw.record, "A checkpoint's record");
3926
+ if (jsonByteLength(record) > limits.maxRestorePointBytes) {
3927
+ throw new ActionProtocolError(`A restore point is larger than ${limits.maxRestorePointBytes} bytes.`);
3928
+ }
3929
+ return {
3930
+ kind: "restore_point",
3931
+ record
3932
+ };
3933
+ }
3934
+ __name(parseActionCheckpoint, "parseActionCheckpoint");
3935
+ function parseCheckpointAck(body) {
3936
+ const raw = objectOf(body, "A checkpoint acknowledgement");
3937
+ onlyKeys(raw, [
3938
+ "acknowledged"
3939
+ ], "A checkpoint acknowledgement");
3940
+ if (raw.acknowledged !== true) {
3941
+ throw new ActionProtocolError("The checkpoint was not acknowledged.");
3942
+ }
3943
+ return {
3944
+ acknowledged: true
3945
+ };
3946
+ }
3947
+ __name(parseCheckpointAck, "parseCheckpointAck");
3948
+ function parseActionReport(body, sent) {
3949
+ const raw = objectOf(body, "The report");
3950
+ onlyKeys(raw, [
3951
+ "phase",
3952
+ "checks",
3953
+ "outcome"
3954
+ ], "The report");
3955
+ if (jsonByteLength(raw) > sent.limits.maxReportBytes) {
3956
+ throw new ActionProtocolError(`The report is larger than ${sent.limits.maxReportBytes} bytes.`);
3957
+ }
3958
+ if (raw.phase !== sent.phase) {
3959
+ throw new ActionProtocolError(`The report is for phase "${String(raw.phase)}", but this invocation is for "${sent.phase}".`);
3960
+ }
3961
+ if (sent.phase === "validate") {
3962
+ if (raw.checks === void 0 === (raw.outcome === void 0)) {
3963
+ throw new ActionProtocolError("A validate report carries checks, or \u2014 when they could not run \u2014 a failed outcome; exactly one of the two.");
3964
+ }
3965
+ if (raw.outcome !== void 0) {
3966
+ const outcome = parseOutcome(raw.outcome);
3967
+ if (outcome.status !== "failed") {
3968
+ throw new ActionProtocolError(`A validate report's outcome can only say the checks failed to run, never "${outcome.status}" \u2014 the engine decides what checks mean.`);
3969
+ }
3970
+ return {
3971
+ phase: "validate",
3972
+ outcome
3973
+ };
3974
+ }
3975
+ const byRef = objectOf(raw.checks, "A validate report's checks");
3976
+ const expected = new Set(sent.targets.map((target) => target.ref));
3977
+ for (const ref of Object.keys(byRef)) {
3978
+ if (!expected.has(ref)) {
3979
+ throw new ActionProtocolError(`The report answers for "${ref}", which is not one of the targets it was given.`);
3980
+ }
3981
+ }
3982
+ const checks = sent.targets.map(({ ref }) => {
3983
+ if (!Object.hasOwn(byRef, ref)) {
3984
+ throw new ActionProtocolError(`The report says nothing about "${ref}", one of the targets it was given.`);
3985
+ }
3986
+ const list = byRef[ref];
3987
+ if (!Array.isArray(list)) {
3988
+ throw new ActionProtocolError(`The checks for "${ref}" must be an array.`);
3989
+ }
3990
+ return {
3991
+ ref,
3992
+ checks: list.map((entry) => {
3993
+ const parsed = (0, validation_1.validationCheckOf)(entry);
3994
+ if ("refusal" in parsed) throw new ActionProtocolError(`The checks for "${ref}": ${parsed.refusal}`);
3995
+ return parsed.check;
3996
+ })
3997
+ };
3998
+ });
3999
+ return {
4000
+ phase: "validate",
4001
+ checks
4002
+ };
4003
+ }
4004
+ if (raw.checks !== void 0) {
4005
+ throw new ActionProtocolError(`A ${sent.phase} report carries an outcome, never checks \u2014 those are the validate phase's answer.`);
4006
+ }
4007
+ return {
4008
+ phase: sent.phase,
4009
+ outcome: parseOutcome(raw.outcome)
4010
+ };
4011
+ }
4012
+ __name(parseActionReport, "parseActionReport");
4013
+ var INVOCATION_KEYS = [
4014
+ "protocol",
4015
+ "action",
4016
+ "phase",
4017
+ "mode",
4018
+ "execution",
4019
+ "targets",
4020
+ "decisions",
4021
+ "restorePoint",
4022
+ "priorResult",
4023
+ "credentials",
4024
+ "limits"
4025
+ ];
4026
+ var EVENT_LEVELS = {
4027
+ info: true,
4028
+ warn: true,
4029
+ error: true,
4030
+ success: true
4031
+ };
4032
+ var OUTCOME_STATUSES = {
4033
+ succeeded: true,
4034
+ failed: true,
4035
+ cancelled: true
4036
+ };
4037
+ function parseOutcome(value) {
4038
+ if (value === void 0) {
4039
+ throw new ActionProtocolError("A run or rollback report must carry an outcome.");
4040
+ }
4041
+ const raw = objectOf(value, "An outcome");
4042
+ onlyKeys(raw, [
4043
+ "status",
4044
+ "message",
4045
+ "data"
4046
+ ], "An outcome");
4047
+ if (typeof raw.status !== "string" || !Object.hasOwn(OUTCOME_STATUSES, raw.status)) {
4048
+ throw new ActionProtocolError(`An outcome status of "${String(raw.status)}" is not in the vocabulary.`);
4049
+ }
4050
+ const status = raw.status;
4051
+ const message = nonEmpty(raw.message, "An outcome's message");
4052
+ if (raw.data === void 0) return {
4053
+ status,
4054
+ message
4055
+ };
4056
+ return {
4057
+ status,
4058
+ message,
4059
+ data: jsonObject(raw.data, "An outcome's data")
4060
+ };
4061
+ }
4062
+ __name(parseOutcome, "parseOutcome");
4063
+ function parseLimits(value) {
4064
+ const raw = objectOf(value, "The invocation's limits");
4065
+ onlyKeys(raw, [
4066
+ "deadline",
4067
+ "maxEvents",
4068
+ "maxEventBytes",
4069
+ "maxReportBytes",
4070
+ "maxRestorePointBytes"
4071
+ ], "The invocation's limits");
4072
+ return {
4073
+ deadline: isoInstant(raw.deadline, "The deadline"),
4074
+ maxEvents: positiveInteger(raw.maxEvents, "maxEvents"),
4075
+ maxEventBytes: positiveInteger(raw.maxEventBytes, "maxEventBytes"),
4076
+ maxReportBytes: positiveInteger(raw.maxReportBytes, "maxReportBytes"),
4077
+ maxRestorePointBytes: positiveInteger(raw.maxRestorePointBytes, "maxRestorePointBytes")
4078
+ };
4079
+ }
4080
+ __name(parseLimits, "parseLimits");
4081
+ function parseDecisions(value) {
4082
+ const raw = objectOf(value, "The invocation's decisions");
4083
+ if (raw.phase === "plan") {
4084
+ onlyKeys(raw, [
4085
+ "phase"
4086
+ ], "A plan's decisions");
4087
+ return {
4088
+ phase: "plan"
4089
+ };
4090
+ }
4091
+ if (raw.phase !== "run") {
4092
+ throw new ActionProtocolError(`A decisions phase of "${String(raw.phase)}" is not "plan" or "run".`);
4093
+ }
4094
+ onlyKeys(raw, [
4095
+ "phase",
4096
+ "recorded"
4097
+ ], "A run's decisions");
4098
+ if (!Array.isArray(raw.recorded)) {
4099
+ throw new ActionProtocolError("A run's decisions must carry the recorded list, even when it is empty.");
4100
+ }
4101
+ const recorded = raw.recorded.map((entry) => {
4102
+ const decision = objectOf(entry, "A recorded decision");
4103
+ onlyKeys(decision, [
4104
+ "code",
4105
+ "acknowledgedBy",
4106
+ "acknowledgedAt"
4107
+ ], "A recorded decision");
4108
+ if (!(0, validation_1.isDecisionCode)(decision.code)) {
4109
+ throw new ActionProtocolError(`A recorded decision of "${String(decision.code)}" is not in the vocabulary.`);
4110
+ }
4111
+ return {
4112
+ code: decision.code,
4113
+ acknowledgedBy: nonEmpty(decision.acknowledgedBy, "Who acknowledged a decision"),
4114
+ acknowledgedAt: isoInstant(decision.acknowledgedAt, "When a decision was acknowledged")
4115
+ };
4116
+ });
4117
+ if (new Set(recorded.map((decision) => decision.code)).size !== recorded.length) {
4118
+ throw new ActionProtocolError("A run's decisions record one code twice.");
4119
+ }
4120
+ return {
4121
+ phase: "run",
4122
+ recorded
4123
+ };
4124
+ }
4125
+ __name(parseDecisions, "parseDecisions");
4126
+ function parseTargets(value) {
4127
+ if (!Array.isArray(value)) {
4128
+ throw new ActionProtocolError("The invocation's targets must be an array.");
4129
+ }
4130
+ const targets = value.map((entry) => {
4131
+ const raw = objectOf(entry, "A target");
4132
+ onlyKeys(raw, [
4133
+ "ref",
4134
+ "parameters"
4135
+ ], "A target");
4136
+ return {
4137
+ ref: nonEmpty(raw.ref, "A target's ref"),
4138
+ parameters: jsonObject(raw.parameters, "A target's parameters")
4139
+ };
4140
+ });
4141
+ if (new Set(targets.map((target) => target.ref)).size !== targets.length) {
4142
+ throw new ActionProtocolError("The invocation names one target twice.");
4143
+ }
4144
+ return targets;
4145
+ }
4146
+ __name(parseTargets, "parseTargets");
4147
+ function assertTargetCount(count, max, what) {
4148
+ if (count < 1 || count > max) {
4149
+ throw new ActionProtocolError(max === 1 ? `${what} acts on exactly one target; this invocation names ${count}.` : `${what} checks between 1 and ${max} targets; this invocation names ${count}.`);
4150
+ }
4151
+ }
4152
+ __name(assertTargetCount, "assertTargetCount");
4153
+ function parseTokens(credentials) {
4154
+ onlyKeys(credentials, [
4155
+ "tokens"
4156
+ ], "The invocation's credentials");
4157
+ if (!Array.isArray(credentials.tokens)) {
4158
+ throw new ActionProtocolError("The invocation's credentials must carry a tokens array, even when it is empty.");
4159
+ }
4160
+ const tokens2 = credentials.tokens.map((entry) => {
4161
+ const raw = objectOf(entry, "A token");
4162
+ onlyKeys(raw, [
4163
+ "audience",
4164
+ "token",
4165
+ "expiresOn"
4166
+ ], "A token");
4167
+ const refusal = actionAudienceRefusal(raw.audience);
4168
+ if (refusal !== null) throw new ActionProtocolError(refusal);
4169
+ return {
4170
+ audience: raw.audience,
4171
+ token: nonEmpty(raw.token, "A token's value"),
4172
+ expiresOn: isoInstant(raw.expiresOn, "A token's expiry")
4173
+ };
4174
+ });
4175
+ if (new Set(tokens2.map((token2) => token2.audience)).size !== tokens2.length) {
4176
+ throw new ActionProtocolError("The invocation carries two tokens for one audience.");
4177
+ }
4178
+ return tokens2;
4179
+ }
4180
+ __name(parseTokens, "parseTokens");
4181
+ var DIGEST_PINNED_IMAGE = /^[a-z0-9][a-z0-9._\-/:]*@sha256:[0-9a-f]{64}$/;
4182
+ function digestPinned(value) {
4183
+ if (typeof value !== "string" || !DIGEST_PINNED_IMAGE.test(value)) {
4184
+ throw new ActionProtocolError(`The action's image "${String(value)}" is not addressed by digest. Nothing on the execution path resolves a tag (docs/128 \xA713.2).`);
4185
+ }
4186
+ return value;
4187
+ }
4188
+ __name(digestPinned, "digestPinned");
4189
+ function objectOf(value, what) {
4190
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
4191
+ throw new ActionProtocolError(`${what} must be an object.`);
4192
+ }
4193
+ return value;
4194
+ }
4195
+ __name(objectOf, "objectOf");
4196
+ function onlyKeys(raw, allowed, what) {
4197
+ for (const key of Object.keys(raw)) {
4198
+ if (!allowed.includes(key)) {
4199
+ throw new ActionProtocolError(`${what} carries "${key}", which this contract does not have.`);
4200
+ }
4201
+ }
4202
+ }
4203
+ __name(onlyKeys, "onlyKeys");
4204
+ function nonEmpty(value, what) {
4205
+ if (typeof value !== "string" || value.length === 0) {
4206
+ throw new ActionProtocolError(`${what} must be a non-empty string.`);
4207
+ }
4208
+ return value;
4209
+ }
4210
+ __name(nonEmpty, "nonEmpty");
4211
+ function positiveInteger(value, what) {
4212
+ if (typeof value !== "number" || !Number.isInteger(value) || value < 1) {
4213
+ throw new ActionProtocolError(`The limit "${what}" must be a positive integer.`);
4214
+ }
4215
+ return value;
4216
+ }
4217
+ __name(positiveInteger, "positiveInteger");
4218
+ function isoInstant(value, what) {
4219
+ if (typeof value !== "string" || Number.isNaN(Date.parse(value))) {
4220
+ throw new ActionProtocolError(`${what} must be an ISO-8601 instant.`);
4221
+ }
4222
+ return value;
4223
+ }
4224
+ __name(isoInstant, "isoInstant");
4225
+ function nullableObject(value, what) {
4226
+ return value === null ? null : jsonObject(value, what);
4227
+ }
4228
+ __name(nullableObject, "nullableObject");
4229
+ function jsonObject(value, what) {
4230
+ if (!isJsonObject(value)) {
4231
+ throw new ActionProtocolError(`${what} must be a JSON object.`);
4232
+ }
4233
+ return value;
4234
+ }
4235
+ __name(jsonObject, "jsonObject");
4236
+ function isJsonObject(value) {
4237
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
4238
+ const prototype = Object.getPrototypeOf(value);
4239
+ if (prototype !== Object.prototype && prototype !== null) return false;
4240
+ return Object.values(value).every(isJsonValue);
4241
+ }
4242
+ __name(isJsonObject, "isJsonObject");
4243
+ function isJsonValue(value) {
4244
+ if (value === null || typeof value === "string" || typeof value === "boolean") return true;
4245
+ if (typeof value === "number") return Number.isFinite(value);
4246
+ if (Array.isArray(value)) return value.every(isJsonValue);
4247
+ return isJsonObject(value);
4248
+ }
4249
+ __name(isJsonValue, "isJsonValue");
4250
+ }
4251
+ });
4252
+
4253
+ // ../contracts/dist/action-signing.js
4254
+ var require_action_signing = __commonJS({
4255
+ "../contracts/dist/action-signing.js"(exports) {
4256
+ "use strict";
4257
+ Object.defineProperty(exports, "__esModule", {
4258
+ value: true
4259
+ });
4260
+ exports.ACTION_SIGNATURE_REPOSITORY = exports.IMAGE_REVISION_LABEL = exports.IMAGE_SOURCE_LABEL = exports.COSIGN_SIGN_PREDICATE_TYPE = exports.IN_TOTO_STATEMENT_TYPE = exports.DSSE_IN_TOTO_PAYLOAD_TYPE = exports.SIGSTORE_BUNDLE_MEDIA_TYPE = void 0;
4261
+ exports.SIGSTORE_BUNDLE_MEDIA_TYPE = "application/vnd.dev.sigstore.bundle.v0.3+json";
4262
+ exports.DSSE_IN_TOTO_PAYLOAD_TYPE = "application/vnd.in-toto+json";
4263
+ exports.IN_TOTO_STATEMENT_TYPE = "https://in-toto.io/Statement/v1";
4264
+ exports.COSIGN_SIGN_PREDICATE_TYPE = "https://sigstore.dev/cosign/sign/v1";
4265
+ exports.IMAGE_SOURCE_LABEL = "org.opencontainers.image.source";
4266
+ exports.IMAGE_REVISION_LABEL = "org.opencontainers.image.revision";
4267
+ exports.ACTION_SIGNATURE_REPOSITORY = "ghcr.io/runveric/action-signatures";
4268
+ }
4269
+ });
4270
+
4271
+ // ../contracts/dist/oci-media-types.js
4272
+ var require_oci_media_types = __commonJS({
4273
+ "../contracts/dist/oci-media-types.js"(exports) {
4274
+ "use strict";
4275
+ Object.defineProperty(exports, "__esModule", {
4276
+ value: true
4277
+ });
4278
+ exports.IMAGE_MANIFEST_MEDIA_TYPES = exports.MANIFEST_MEDIA_TYPES = exports.DOCKER_MANIFEST_LIST = exports.OCI_IMAGE_INDEX = exports.DOCKER_IMAGE_MANIFEST = exports.OCI_IMAGE_MANIFEST = void 0;
4279
+ exports.OCI_IMAGE_MANIFEST = "application/vnd.oci.image.manifest.v1+json";
4280
+ exports.DOCKER_IMAGE_MANIFEST = "application/vnd.docker.distribution.manifest.v2+json";
4281
+ exports.OCI_IMAGE_INDEX = "application/vnd.oci.image.index.v1+json";
4282
+ exports.DOCKER_MANIFEST_LIST = "application/vnd.docker.distribution.manifest.list.v2+json";
4283
+ exports.MANIFEST_MEDIA_TYPES = [
4284
+ exports.OCI_IMAGE_MANIFEST,
4285
+ exports.DOCKER_IMAGE_MANIFEST,
4286
+ exports.OCI_IMAGE_INDEX,
4287
+ exports.DOCKER_MANIFEST_LIST
4288
+ ];
4289
+ exports.IMAGE_MANIFEST_MEDIA_TYPES = [
4290
+ exports.OCI_IMAGE_MANIFEST,
4291
+ exports.DOCKER_IMAGE_MANIFEST
4292
+ ];
4293
+ }
4294
+ });
4295
+
4296
+ // ../contracts/dist/action-image.js
4297
+ var require_action_image = __commonJS({
4298
+ "../contracts/dist/action-image.js"(exports) {
4299
+ "use strict";
4300
+ Object.defineProperty(exports, "__esModule", {
4301
+ value: true
4302
+ });
4303
+ exports.MAX_IMAGE_MANIFEST_BYTES = exports.ACTION_RUNTIME_IMAGE = exports.ACTION_RUNTIME_REPOSITORY = void 0;
4304
+ exports.imageConfigDescriptor = imageConfigDescriptor2;
4305
+ exports.actionImageRefusal = actionImageRefusal2;
4306
+ var action_repository_1 = require_action_repository();
4307
+ var action_signing_1 = require_action_signing();
4308
+ var oci_media_types_1 = require_oci_media_types();
4309
+ exports.ACTION_RUNTIME_REPOSITORY = "ghcr.io/runveric/action-runtime";
4310
+ exports.ACTION_RUNTIME_IMAGE = `${exports.ACTION_RUNTIME_REPOSITORY}@sha256:55126390dc54a79668e8475757f6de6dabc8c052242079d48d888eca4fdd5963`;
4311
+ exports.MAX_IMAGE_MANIFEST_BYTES = 64 * 1024;
4312
+ var isRecord = /* @__PURE__ */ __name((value) => typeof value === "object" && value !== null && !Array.isArray(value), "isRecord");
4313
+ function imageConfigDescriptor2(manifest) {
4314
+ if (!isRecord(manifest) || typeof manifest.mediaType !== "string") return null;
4315
+ if (!oci_media_types_1.IMAGE_MANIFEST_MEDIA_TYPES.includes(manifest.mediaType) || manifest.schemaVersion !== 2) return null;
4316
+ const config = manifest.config;
4317
+ if (!isRecord(config) || typeof config.digest !== "string") return null;
4318
+ if (typeof config.size !== "number" || !Number.isInteger(config.size) || config.size < 0) return null;
4319
+ return {
4320
+ digest: config.digest,
4321
+ size: config.size
4322
+ };
4323
+ }
4324
+ __name(imageConfigDescriptor2, "imageConfigDescriptor");
4325
+ function actionImageRefusal2(input) {
4326
+ const { key, image, manifest, config, provenance: provenance2 } = input;
4327
+ const mediaType = isRecord(manifest) ? manifest.mediaType : void 0;
4328
+ if (typeof mediaType !== "string" || !oci_media_types_1.IMAGE_MANIFEST_MEDIA_TYPES.includes(mediaType)) {
4329
+ return `${image} is ${typeof mediaType === "string" ? `a ${mediaType}` : "a manifest of no declared type"}, not one image. An engine action's digest names exactly one linux/amd64 image manifest \u2014 never an index of several.`;
4330
+ }
4331
+ if (imageConfigDescriptor2(manifest) === null) {
4332
+ return `${image} is not an image manifest \u2014 it names no config descriptor (schemaVersion 2, a digest and a size).`;
4333
+ }
4334
+ if (!isRecord(config)) {
4335
+ return `${image}'s config is not a JSON object, so it cannot say what it was built for.`;
4336
+ }
4337
+ const platform = `${String(config.os)}/${String(config.architecture)}`;
4338
+ if (platform !== "linux/amd64") {
4339
+ return `${image} is built for ${platform}; an engine action runs on linux/amd64, which is what Container Apps Jobs run.`;
4340
+ }
4341
+ const labels = isRecord(config.config) && isRecord(config.config.Labels) ? config.config.Labels : {};
4342
+ const expected = (0, action_repository_1.actionSourceRepository)(key);
4343
+ const source = labels[action_signing_1.IMAGE_SOURCE_LABEL];
4344
+ if (source !== expected) {
4345
+ return `the image of action "${key}" says it was built in ${source === void 0 ? "no repository (it carries no " + action_signing_1.IMAGE_SOURCE_LABEL + " label)" : JSON.stringify(source)}, not ${expected}. A signature binds a digest and never a name, so an image built for another action cannot run under this one's manifest.`;
4346
+ }
4347
+ if (provenance2.repository !== expected) {
4348
+ return `the artifact of action "${key}" says it was authored in ${JSON.stringify(provenance2.repository)}, not ${expected}, which its image names.`;
4349
+ }
4350
+ const revision = labels[action_signing_1.IMAGE_REVISION_LABEL];
4351
+ if (revision !== provenance2.commit) {
4352
+ return `the image of action "${key}" says it was built at ${revision === void 0 ? "no commit (it carries no " + action_signing_1.IMAGE_REVISION_LABEL + " label)" : JSON.stringify(revision)}, not ${provenance2.commit}, the commit its artifact was published from. An image and the manifest it runs under come from one commit, so neither can be swapped for another's.`;
4353
+ }
4354
+ return null;
4355
+ }
4356
+ __name(actionImageRefusal2, "actionImageRefusal");
4357
+ }
4358
+ });
4359
+
3218
4360
  // ../contracts/dist/runner-pool-presence.js
3219
4361
  var require_runner_pool_presence = __commonJS({
3220
4362
  "../contracts/dist/runner-pool-presence.js"(exports) {
@@ -4190,6 +5332,11 @@ var require_dist = __commonJS({
4190
5332
  __exportStar(require_runner_install(), exports);
4191
5333
  __exportStar(require_authoring_cli(), exports);
4192
5334
  __exportStar(require_runners(), exports);
5335
+ __exportStar(require_scoped_credential(), exports);
5336
+ __exportStar(require_action_protocol(), exports);
5337
+ __exportStar(require_action_signing(), exports);
5338
+ __exportStar(require_oci_media_types(), exports);
5339
+ __exportStar(require_action_image(), exports);
4193
5340
  __exportStar(require_runner_pool_presence(), exports);
4194
5341
  __exportStar(require_routable_target(), exports);
4195
5342
  __exportStar(require_runner_pool_routing(), exports);
@@ -4427,6 +5574,7 @@ var require_manifest_validator = __commonJS({
4427
5574
  exports.canonicalJson = canonicalJson;
4428
5575
  exports.sha256Hex = sha256Hex;
4429
5576
  exports.isSafeRelativePath = isSafeRelativePath;
5577
+ exports.isEgressDestination = isEgressDestination;
4430
5578
  var node_crypto_1 = __require("crypto");
4431
5579
  var node_net_1 = __require("net");
4432
5580
  var contracts_1 = require_dist();
@@ -5838,7 +6986,7 @@ var require_parameters_location = __commonJS({
5838
6986
  });
5839
6987
  exports.resolveParametersFile = resolveParametersFile;
5840
6988
  exports.resolveParametersFileOf = resolveParametersFileOf;
5841
- exports.readManifest = readManifest;
6989
+ exports.readManifest = readManifest2;
5842
6990
  var yaml_1 = __require("yaml");
5843
6991
  var manifest_validator_1 = require_manifest_validator();
5844
6992
  var package_validation_1 = require_package_validation();
@@ -5849,7 +6997,7 @@ var require_parameters_location = __commonJS({
5849
6997
  reason: "manifest_missing"
5850
6998
  };
5851
6999
  }
5852
- const manifest = readManifest(manifestSource);
7000
+ const manifest = readManifest2(manifestSource);
5853
7001
  if (!manifest) return {
5854
7002
  resolved: false,
5855
7003
  reason: "manifest_unparseable"
@@ -5877,7 +7025,7 @@ var require_parameters_location = __commonJS({
5877
7025
  return resolveParametersFile(files.get(package_validation_1.MANIFEST_FILE));
5878
7026
  }
5879
7027
  __name(resolveParametersFileOf, "resolveParametersFileOf");
5880
- function readManifest(source) {
7028
+ function readManifest2(source) {
5881
7029
  try {
5882
7030
  const document = (0, yaml_1.parseDocument)(source);
5883
7031
  if (document.errors.length > 0) return void 0;
@@ -5887,7 +7035,7 @@ var require_parameters_location = __commonJS({
5887
7035
  return void 0;
5888
7036
  }
5889
7037
  }
5890
- __name(readManifest, "readManifest");
7038
+ __name(readManifest2, "readManifest");
5891
7039
  }
5892
7040
  });
5893
7041
 
@@ -7219,7 +8367,8 @@ var require_dist2 = __commonJS({
7219
8367
  Object.defineProperty(exports, "__esModule", {
7220
8368
  value: true
7221
8369
  });
7222
- exports.scaffoldActionPackage = exports.reservedImportPaths = exports.generateImportedPackage = exports.authoredEntrypointOf = exports.authoredBodyPath = exports.powerShellDetection = exports.parseProbeOutput = exports.mapDeclaredParameters = exports.POWERSHELL_PARAM_PROBE = exports.pathSegments = exports.offsetToLocation = exports.locate = exports.interpreterForPath = exports.interpreterEntryFileList = exports.PACKAGE_INTERPRETER_EXTENSIONS = exports.PACKAGE_INTERPRETER_LAUNCH = exports.lintActionPackage = exports.sortFindings = exports.mergeFindings = exports.findingFromValidation = exports.resolveParametersFileOf = exports.resolveParametersFile = exports.readManifest = exports.checkActionPackage = exports.validateActionPackage = exports.REQUIRED_PACKAGE_FILES = exports.MANIFEST_FILE = exports.validatePackageResult = exports.PackageProtocolError = exports.normalizePackageRuntime = exports.checkOciImageReference = exports.PackageRuntimeDeclarationError = exports.PARAMETER_CONTROLS = exports.PARAMETER_PROPERTY_FIELDS = exports.PARAMETER_SCHEMA_FIELDS = exports.CREDENTIAL_SCOPE_PATTERN = exports.SPDX_ID_PATTERN = exports.DURATION_PATTERN = exports.SEMVER_PATTERN = exports.PACKAGE_ID_PATTERN = exports.MANIFEST_FIELDS = exports.RISK_LEVELS = exports.PROVIDERS = exports.ACTION_CATEGORIES = exports.isSafeRelativePath = exports.sha256Hex = exports.canonicalJson = exports.validateParameterSchema = exports.validateManifest = exports.parseAndValidateManifest = void 0;
8370
+ exports.reservedImportPaths = exports.generateImportedPackage = exports.authoredEntrypointOf = exports.authoredBodyPath = exports.powerShellDetection = exports.parseProbeOutput = exports.mapDeclaredParameters = exports.POWERSHELL_PARAM_PROBE = exports.pathSegments = exports.offsetToLocation = exports.locate = exports.interpreterForPath = exports.interpreterEntryFileList = exports.PACKAGE_INTERPRETER_EXTENSIONS = exports.PACKAGE_INTERPRETER_LAUNCH = exports.lintActionPackage = exports.sortFindings = exports.mergeFindings = exports.findingFromValidation = exports.resolveParametersFileOf = exports.resolveParametersFile = exports.readManifest = exports.checkActionPackage = exports.validateActionPackage = exports.REQUIRED_PACKAGE_FILES = exports.MANIFEST_FILE = exports.validatePackageResult = exports.PackageProtocolError = exports.normalizePackageRuntime = exports.checkOciImageReference = exports.PackageRuntimeDeclarationError = exports.PARAMETER_CONTROLS = exports.PARAMETER_PROPERTY_FIELDS = exports.PARAMETER_SCHEMA_FIELDS = exports.CREDENTIAL_SCOPE_PATTERN = exports.SPDX_ID_PATTERN = exports.DURATION_PATTERN = exports.SEMVER_PATTERN = exports.PACKAGE_ID_PATTERN = exports.MANIFEST_FIELDS = exports.RISK_LEVELS = exports.PROVIDERS = exports.ACTION_CATEGORIES = exports.isEgressDestination = exports.isSafeRelativePath = exports.sha256Hex = exports.canonicalJson = exports.validateParameterSchema = exports.validateManifest = exports.parseAndValidateManifest = void 0;
8371
+ exports.scaffoldActionPackage = void 0;
7223
8372
  var manifest_validator_1 = require_manifest_validator();
7224
8373
  Object.defineProperty(exports, "parseAndValidateManifest", {
7225
8374
  enumerable: true,
@@ -7257,6 +8406,12 @@ var require_dist2 = __commonJS({
7257
8406
  return manifest_validator_1.isSafeRelativePath;
7258
8407
  }, "get")
7259
8408
  });
8409
+ Object.defineProperty(exports, "isEgressDestination", {
8410
+ enumerable: true,
8411
+ get: /* @__PURE__ */ __name(function() {
8412
+ return manifest_validator_1.isEgressDestination;
8413
+ }, "get")
8414
+ });
7260
8415
  var manifest_schema_1 = require_manifest_schema();
7261
8416
  Object.defineProperty(exports, "ACTION_CATEGORIES", {
7262
8417
  enumerable: true,
@@ -7537,7 +8692,7 @@ var require_dist2 = __commonJS({
7537
8692
  });
7538
8693
 
7539
8694
  // package.json
7540
- var version = "0.2.0";
8695
+ var version = "0.4.0";
7541
8696
 
7542
8697
  // src/report.ts
7543
8698
  var useColour = process.stdout.isTTY === true && !process.env.NO_COLOR;
@@ -7647,7 +8802,7 @@ function printResult(result) {
7647
8802
  __name(printResult, "printResult");
7648
8803
 
7649
8804
  // src/catalog/publish-action.ts
7650
- var import_contracts2 = __toESM(require_dist());
8805
+ var import_contracts3 = __toESM(require_dist());
7651
8806
  import { readFileSync } from "fs";
7652
8807
  import { dirname, resolve } from "path";
7653
8808
  import { parse as parseYaml } from "yaml";
@@ -7686,7 +8841,7 @@ var EMPTY_CONFIG_DESCRIPTOR = {
7686
8841
  digest: sha256(EMPTY_CONFIG),
7687
8842
  size: EMPTY_CONFIG.length
7688
8843
  };
7689
- var OCI_MANIFEST = "application/vnd.oci.image.manifest.v1+json";
8844
+ var MANIFEST_ACCEPT = import_contracts.MANIFEST_MEDIA_TYPES.join(", ");
7690
8845
  function provenance(flags, dir) {
7691
8846
  const git = /* @__PURE__ */ __name((...args) => {
7692
8847
  try {
@@ -7724,7 +8879,7 @@ function buildArtifact(document) {
7724
8879
  `);
7725
8880
  const manifest = {
7726
8881
  schemaVersion: 2,
7727
- mediaType: OCI_MANIFEST,
8882
+ mediaType: import_contracts.OCI_IMAGE_MANIFEST,
7728
8883
  artifactType: import_contracts.CATALOG_ARTIFACT_MEDIA_TYPE,
7729
8884
  config: EMPTY_CONFIG_DESCRIPTOR,
7730
8885
  layers: [
@@ -7764,14 +8919,14 @@ function basicCredential() {
7764
8919
  }
7765
8920
  __name(basicCredential, "basicCredential");
7766
8921
  var tokens = /* @__PURE__ */ new Map();
7767
- async function token(repository, challenge) {
8922
+ async function token(repository, challenge, access) {
7768
8923
  const field = /* @__PURE__ */ __name((name) => new RegExp(`${name}="([^"]*)"`).exec(challenge ?? "")?.[1], "field");
7769
8924
  const realm = field("realm");
7770
8925
  if (!realm) throw new Error(`${repository.host} asked for credentials without a Bearer challenge.`);
7771
8926
  const url = new URL(realm);
7772
8927
  const service = field("service");
7773
8928
  if (service) url.searchParams.set("service", service);
7774
- url.searchParams.set("scope", `repository:${repository.path}:pull,push`);
8929
+ url.searchParams.set("scope", `repository:${repository.path}:${access}`);
7775
8930
  const credential = basicCredential();
7776
8931
  const answer = await fetch(url, {
7777
8932
  headers: credential ? {
@@ -7779,15 +8934,15 @@ async function token(repository, challenge) {
7779
8934
  } : {}
7780
8935
  });
7781
8936
  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."));
8937
+ throw new Error(`${repository.host} refused a ${access === "pull" ? "pull" : "push"} token for ${repository.path}: HTTP ${answer.status}` + (credential || access === "pull" ? "." : " \u2014 set GHCR_TOKEN to a token with write:packages, or in a workflow GITHUB_TOKEN with packages: write."));
7783
8938
  }
7784
8939
  const body = await answer.json();
7785
8940
  const value = body.token ?? body.access_token ?? "";
7786
- tokens.set(repository.path, value);
8941
+ tokens.set(`${repository.path} ${access}`, value);
7787
8942
  return value;
7788
8943
  }
7789
8944
  __name(token, "token");
7790
- async function call(repository, method, path, { headers = {}, body } = {}) {
8945
+ async function call(repository, method, path, { headers = {}, body, access = "pull,push" } = {}) {
7791
8946
  const url = path.startsWith("http") ? path : `${origin(repository.host)}/v2/${repository.path}/${path}`;
7792
8947
  const send = /* @__PURE__ */ __name((bearer) => fetch(url, {
7793
8948
  method,
@@ -7800,8 +8955,8 @@ async function call(repository, method, path, { headers = {}, body } = {}) {
7800
8955
  } : {}
7801
8956
  }
7802
8957
  }), "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")));
8958
+ let answer = await send(tokens.get(`${repository.path} ${access}`));
8959
+ if (answer.status === 401) answer = await send(await token(repository, answer.headers.get("www-authenticate"), access));
7805
8960
  return answer;
7806
8961
  }
7807
8962
  __name(call, "call");
@@ -7826,12 +8981,54 @@ __name(pushBlob, "pushBlob");
7826
8981
  async function manifestExists(repository, digest) {
7827
8982
  const answer = await call(repository, "HEAD", `manifests/${digest}`, {
7828
8983
  headers: {
7829
- Accept: OCI_MANIFEST
8984
+ Accept: import_contracts.OCI_IMAGE_MANIFEST
7830
8985
  }
7831
8986
  });
7832
8987
  return answer.ok;
7833
8988
  }
7834
8989
  __name(manifestExists, "manifestExists");
8990
+ async function readVerified(repository, kind, digest, size = null) {
8991
+ if (!isDigest(digest)) throw new Error(`"${digest}" is not a digest, so ${repository.ref} cannot be asked for it.`);
8992
+ const answer = await call(repository, "GET", `${kind}/${digest}`, {
8993
+ access: "pull",
8994
+ headers: kind === "manifests" ? {
8995
+ Accept: MANIFEST_ACCEPT
8996
+ } : {}
8997
+ });
8998
+ if (!answer.ok) {
8999
+ throw new Error(`${repository.ref}@${digest} could not be read: HTTP ${answer.status}${answer.status === 404 ? " \u2014 it is not in the registry" : ""}.`);
9000
+ }
9001
+ const bytes = Buffer.from(await answer.arrayBuffer());
9002
+ const served = sha256(bytes);
9003
+ if (served !== digest || size !== null && bytes.length !== size) {
9004
+ throw new Error(`${repository.host} served ${bytes.length} bytes hashing to ${served} for ${repository.ref}@${digest}${size === null ? "" : ` (${size} bytes named)`}. Refused: a digest this command did not compute is not a pin.`);
9005
+ }
9006
+ return bytes;
9007
+ }
9008
+ __name(readVerified, "readVerified");
9009
+ var readManifest = /* @__PURE__ */ __name((repository, digest) => readVerified(repository, "manifests", digest), "readManifest");
9010
+ var readBlob = /* @__PURE__ */ __name((repository, digest, size) => readVerified(repository, "blobs", digest, size), "readBlob");
9011
+ function parseJsonBytes(bytes, what) {
9012
+ try {
9013
+ return JSON.parse(new TextDecoder("utf-8", {
9014
+ fatal: true
9015
+ }).decode(bytes));
9016
+ } catch (cause) {
9017
+ throw new Error(`${what} is not UTF-8 JSON. (${cause instanceof Error ? cause.message : String(cause)})`);
9018
+ }
9019
+ }
9020
+ __name(parseJsonBytes, "parseJsonBytes");
9021
+ async function readArtifactDocument(repository, digest) {
9022
+ const what = `${repository.ref}@${digest}`;
9023
+ const manifest = parseJsonBytes(await readManifest(repository, digest), `the manifest of ${what}`);
9024
+ const layers = Array.isArray(manifest?.layers) ? manifest.layers : [];
9025
+ const layer = layers[0];
9026
+ if (manifest?.mediaType !== import_contracts.OCI_IMAGE_MANIFEST || layers.length !== 1 || layer?.mediaType !== import_contracts.CATALOG_ARTIFACT_MEDIA_TYPE || typeof layer.digest !== "string" || typeof layer.size !== "number") {
9027
+ throw new Error(`${what} is not a catalog artifact: expected one OCI manifest with exactly one ${import_contracts.CATALOG_ARTIFACT_MEDIA_TYPE} layer.`);
9028
+ }
9029
+ return parseJsonBytes(await readBlob(repository, layer.digest, layer.size), `the document of ${what}`);
9030
+ }
9031
+ __name(readArtifactDocument, "readArtifactDocument");
7835
9032
  async function pushArtifact(repository, artifact, tag) {
7836
9033
  await pushBlob(repository, EMPTY_CONFIG);
7837
9034
  await pushBlob(repository, artifact.documentBytes);
@@ -7841,7 +9038,7 @@ async function pushArtifact(repository, artifact, tag) {
7841
9038
  ]) {
7842
9039
  const put = await call(repository, "PUT", `manifests/${reference}`, {
7843
9040
  headers: {
7844
- "Content-Type": OCI_MANIFEST
9041
+ "Content-Type": import_contracts.OCI_IMAGE_MANIFEST
7845
9042
  },
7846
9043
  body: artifact.manifestBytes
7847
9044
  });
@@ -7851,7 +9048,7 @@ async function pushArtifact(repository, artifact, tag) {
7851
9048
  }
7852
9049
  const asked = await call(repository, "HEAD", `manifests/${tag}`, {
7853
9050
  headers: {
7854
- Accept: OCI_MANIFEST
9051
+ Accept: import_contracts.OCI_IMAGE_MANIFEST
7855
9052
  }
7856
9053
  });
7857
9054
  const reported = asked.headers.get("docker-content-digest");
@@ -7868,7 +9065,7 @@ var defaultTag = /* @__PURE__ */ __name(() => (/* @__PURE__ */ new Date()).toISO
7868
9065
 
7869
9066
  // src/catalog/flags.ts
7870
9067
  function parseCatalogFlags(argv, known, repeated = []) {
7871
- const single = {};
9068
+ const single2 = {};
7872
9069
  const many = {};
7873
9070
  for (let index = 0; index < argv.length; index += 1) {
7874
9071
  const argument = argv[index];
@@ -7884,28 +9081,67 @@ function parseCatalogFlags(argv, known, repeated = []) {
7884
9081
  }
7885
9082
  if (value === void 0 || value === "" || value.startsWith("--")) throw new Error(`--${name} needs a value.`);
7886
9083
  if (repeated.includes(name)) (many[name] ??= []).push(value);
7887
- else single[name] = value;
9084
+ else single2[name] = value;
7888
9085
  }
7889
9086
  return {
7890
- single,
9087
+ single: single2,
7891
9088
  many
7892
9089
  };
7893
9090
  }
7894
9091
  __name(parseCatalogFlags, "parseCatalogFlags");
7895
9092
 
9093
+ // src/catalog/image.ts
9094
+ var import_contracts2 = __toESM(require_dist());
9095
+ function parseImageReference(value, origin2) {
9096
+ const at = value.lastIndexOf("@");
9097
+ const digest = at === -1 ? "" : value.slice(at + 1);
9098
+ if (at === -1 || !isDigest(digest)) {
9099
+ throw new Error(`${origin2} "${value}" is not <repository>@<digest>. An engine action's image is named by its digest \u2014 sha256: and 64 lowercase hex \u2014 never by a tag, which a registry can repoint.`);
9100
+ }
9101
+ const repository = parseRepository(value.slice(0, at), origin2);
9102
+ return {
9103
+ repository,
9104
+ digest,
9105
+ ref: `${repository.ref}@${digest}`
9106
+ };
9107
+ }
9108
+ __name(parseImageReference, "parseImageReference");
9109
+ async function imageRefusal(image, key, provenance2) {
9110
+ const manifestBytes = await readManifest(image.repository, image.digest);
9111
+ if (manifestBytes.length > import_contracts2.MAX_IMAGE_MANIFEST_BYTES) {
9112
+ return `the image manifest of ${image.ref} is ${manifestBytes.length} bytes; the limit is ${import_contracts2.MAX_IMAGE_MANIFEST_BYTES}.`;
9113
+ }
9114
+ const manifest = parseJsonBytes(manifestBytes, `the image manifest of ${image.ref}`);
9115
+ const descriptor = (0, import_contracts2.imageConfigDescriptor)(manifest);
9116
+ const config = descriptor === null ? null : parseJsonBytes(await readBlob(image.repository, descriptor.digest, descriptor.size), `the image config of ${image.ref}`);
9117
+ return (0, import_contracts2.actionImageRefusal)({
9118
+ key,
9119
+ image: image.ref,
9120
+ manifest,
9121
+ config,
9122
+ provenance: provenance2
9123
+ });
9124
+ }
9125
+ __name(imageRefusal, "imageRefusal");
9126
+
7896
9127
  // 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>]";
9128
+ var PUBLISH_ACTION_USAGE = "runveric catalog publish-action --manifest <file.yaml> --to <registry>/<namespace>/<repository>\n [--image <repository>@<digest>] [--tag <tag>]\n [--repository <source url>] [--commit <sha>]";
7898
9129
  var isMapping = /* @__PURE__ */ __name((value) => typeof value === "object" && value !== null && !Array.isArray(value), "isMapping");
7899
9130
  async function runPublishAction(argv) {
7900
9131
  const { single: flags } = parseCatalogFlags(argv, [
7901
9132
  "manifest",
7902
9133
  "to",
9134
+ "image",
7903
9135
  "tag",
7904
9136
  "repository",
7905
9137
  "commit"
7906
9138
  ]);
7907
9139
  if (!flags.manifest || !flags.to) throw new Error("--manifest and --to are required.");
7908
9140
  const repository = parseRepository(flags.to, "--to");
9141
+ const image = flags.image === void 0 ? null : parseImageReference(flags.image, "--image");
9142
+ if (image !== null && image.repository.ref !== repository.ref) {
9143
+ throw new Error(`--image ${image.ref} is outside the artifact's repository ${repository.ref}. An engine action's image lives in its own package, so its artifact names it by digest alone (docs/128 \xA717.2 D2) \u2014 push the image to ${repository.ref}.`);
9144
+ }
7909
9145
  const path = resolve(flags.manifest);
7910
9146
  const manifest = readFileSync(path, "utf8");
7911
9147
  let parsed;
@@ -7917,8 +9153,15 @@ async function runPublishAction(argv) {
7917
9153
  const fields = isMapping(parsed) ? parsed : {};
7918
9154
  const key = typeof fields.key === "string" && fields.key.length > 0 ? fields.key : null;
7919
9155
  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);
9156
+ const misnamed = (0, import_contracts3.actionPackageRefusal)(key, repository.ref);
7921
9157
  if (misnamed !== null) throw new Error(`--to: ${misnamed}`);
9158
+ const executes = isMapping(fields.execution);
9159
+ if (executes && image === null) {
9160
+ throw new Error(`${flags.manifest} declares execution, so it is an engine action: publish it with --image <repository>@<digest>, the image its workflow built. Without one the server refuses it at plan.`);
9161
+ }
9162
+ if (!executes && image !== null) {
9163
+ throw new Error(`${flags.manifest} declares no execution, so nothing would ever run --image ${image.ref}. A built-in action is published without one.`);
9164
+ }
7922
9165
  let tag;
7923
9166
  if (fields.declares === "definition+version") {
7924
9167
  const version2 = typeof fields.version === "string" ? fields.version : null;
@@ -7931,13 +9174,25 @@ async function runPublishAction(argv) {
7931
9174
  tag = flags.tag ?? defaultTag();
7932
9175
  }
7933
9176
  requireTag(tag, flags.tag !== void 0 ? "--tag" : `${flags.manifest}'s version`);
7934
- const artifact = buildArtifact({
7935
- schemaVersion: import_contracts2.CATALOG_ACTION_ARTIFACT_SCHEMA_V1,
9177
+ const recorded = provenance(flags, dirname(path));
9178
+ if (image !== null) {
9179
+ const refusal = await imageRefusal(image, key, recorded);
9180
+ if (refusal !== null) throw new Error(`--image: ${refusal}`);
9181
+ }
9182
+ const artifact = buildArtifact(image === null ? {
9183
+ schemaVersion: import_contracts3.CATALOG_ACTION_ARTIFACT_SCHEMA_V1,
9184
+ manifest,
9185
+ provenance: recorded
9186
+ } : {
9187
+ schemaVersion: import_contracts3.CATALOG_ACTION_ARTIFACT_SCHEMA_V2,
7936
9188
  manifest,
7937
- provenance: provenance(flags, dirname(path))
9189
+ provenance: recorded,
9190
+ image: {
9191
+ digest: image.digest
9192
+ }
7938
9193
  });
7939
9194
  const digest = await pushArtifact(repository, artifact, tag);
7940
- process.stderr.write(`\u2713 ${key} published to ${repository.ref}:${tag}
9195
+ process.stderr.write(`\u2713 ${key} published to ${repository.ref}:${tag}${image === null ? "" : `, naming the image ${image.digest}`}
7941
9196
  `);
7942
9197
  process.stderr.write(` the registry reports ${digest} \u2014 the digest a release must name.
7943
9198
  `);
@@ -7947,7 +9202,7 @@ async function runPublishAction(argv) {
7947
9202
  __name(runPublishAction, "runPublishAction");
7948
9203
 
7949
9204
  // src/catalog/publish-release.ts
7950
- var import_contracts3 = __toESM(require_dist());
9205
+ var import_contracts4 = __toESM(require_dist());
7951
9206
  import { readFileSync as readFileSync2 } from "fs";
7952
9207
  import { parse as parseYaml2 } from "yaml";
7953
9208
  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>]";
@@ -7972,7 +9227,7 @@ function specFromFile(path) {
7972
9227
  } catch (cause) {
7973
9228
  throw new Error(`--from ${path} could not be read as YAML. (${cause instanceof Error ? cause.message : String(cause)})`);
7974
9229
  }
7975
- const spec = (0, import_contracts3.parseCatalogReleaseSpec)(value);
9230
+ const spec = (0, import_contracts4.parseCatalogReleaseSpec)(value);
7976
9231
  return {
7977
9232
  version: spec.version,
7978
9233
  entries: spec.actions.map((action) => ({
@@ -8022,7 +9277,7 @@ async function runPublishRelease(argv) {
8022
9277
  if (!isDigest(entry.digest)) {
8023
9278
  throw new Error(`${entry.label}: "${entry.digest}" is not a digest. A release names artifacts by digest only.`);
8024
9279
  }
8025
- const misnamed = (0, import_contracts3.actionPackageRefusal)(entry.key, entry.artifactRef);
9280
+ const misnamed = (0, import_contracts4.actionPackageRefusal)(entry.key, entry.artifactRef);
8026
9281
  if (misnamed !== null) throw new Error(`${entry.label}: ${misnamed}`);
8027
9282
  return {
8028
9283
  ...entry,
@@ -8035,7 +9290,7 @@ async function runPublishRelease(argv) {
8035
9290
  }
8036
9291
  }
8037
9292
  const artifact = buildArtifact({
8038
- schemaVersion: import_contracts3.CATALOG_RELEASE_SCHEMA_V1,
9293
+ schemaVersion: import_contracts4.CATALOG_RELEASE_SCHEMA_V1,
8039
9294
  version: version2,
8040
9295
  provenance: provenance(flags, process.cwd()),
8041
9296
  actions: actions.map(({ key, artifactRef, digest: digest2 }) => ({
@@ -8054,20 +9309,124 @@ async function runPublishRelease(argv) {
8054
9309
  }
8055
9310
  __name(runPublishRelease, "runPublishRelease");
8056
9311
 
9312
+ // src/catalog/verify-image.ts
9313
+ var import_contracts5 = __toESM(require_dist());
9314
+ import { readFileSync as readFileSync3 } from "fs";
9315
+ import { parse as parseYaml3 } from "yaml";
9316
+ var VERIFY_IMAGE_USAGE = "runveric catalog verify-image (<repository>@<digest> --key <action key> --commit <sha> | --from <release.yaml>)";
9317
+ var COMMIT = /^[0-9a-f]{40}$/;
9318
+ var isMapping2 = /* @__PURE__ */ __name((value) => typeof value === "object" && value !== null && !Array.isArray(value), "isMapping");
9319
+ function single(value, flags) {
9320
+ if (!flags.key || !flags.commit) throw new Error(`verify-image ${value} needs --key <action key> and --commit <sha>.`);
9321
+ if (!COMMIT.test(flags.commit)) throw new Error(`--commit "${flags.commit}" is not a 40-hex commit.`);
9322
+ const image = parseImageReference(value, "verify-image");
9323
+ const misnamed = (0, import_contracts5.actionPackageRefusal)(flags.key, image.repository.ref);
9324
+ if (misnamed !== null) throw new Error(misnamed);
9325
+ return {
9326
+ key: flags.key,
9327
+ image,
9328
+ provenance: {
9329
+ repository: (0, import_contracts5.actionSourceRepository)(flags.key),
9330
+ commit: flags.commit
9331
+ },
9332
+ label: `--key ${flags.key}`
9333
+ };
9334
+ }
9335
+ __name(single, "single");
9336
+ async function fromRelease(path) {
9337
+ let value;
9338
+ try {
9339
+ value = parseYaml3(readFileSync3(path, "utf8"));
9340
+ } catch (cause) {
9341
+ throw new Error(`--from ${path} could not be read as YAML. (${cause instanceof Error ? cause.message : String(cause)})`);
9342
+ }
9343
+ const candidates = [];
9344
+ for (const action of (0, import_contracts5.parseCatalogReleaseSpec)(value).actions) {
9345
+ const label = `${path}'s ${action.key}`;
9346
+ const misnamed = (0, import_contracts5.actionPackageRefusal)(action.key, action.artifactRef);
9347
+ if (misnamed !== null) throw new Error(`${label}: ${misnamed}`);
9348
+ if (!isDigest(action.digest)) throw new Error(`${label}: "${action.digest}" is not a digest.`);
9349
+ const repository = parseRepository(action.artifactRef, label);
9350
+ const read = await readArtifactDocument(repository, action.digest);
9351
+ const document = isMapping2(read) ? read : {};
9352
+ const schemaVersion = document.schemaVersion;
9353
+ if (schemaVersion === import_contracts5.CATALOG_ACTION_ARTIFACT_SCHEMA_V1) continue;
9354
+ if (schemaVersion !== import_contracts5.CATALOG_ACTION_ARTIFACT_SCHEMA_V2) {
9355
+ throw new Error(`${label}: ${repository.ref}@${action.digest} declares schemaVersion ${JSON.stringify(schemaVersion)}; this CLI reads ${import_contracts5.CATALOG_ACTION_ARTIFACT_SCHEMA_V1} and ${import_contracts5.CATALOG_ACTION_ARTIFACT_SCHEMA_V2}.`);
9356
+ }
9357
+ const image = isMapping2(document.image) ? document.image.digest : void 0;
9358
+ const recorded = isMapping2(document.provenance) ? document.provenance : {};
9359
+ if (typeof image !== "string" || typeof recorded.repository !== "string" || typeof recorded.commit !== "string") {
9360
+ throw new Error(`${label}: its ${import_contracts5.CATALOG_ACTION_ARTIFACT_SCHEMA_V2} artifact carries no image digest or no provenance.`);
9361
+ }
9362
+ candidates.push({
9363
+ key: action.key,
9364
+ // ★ The image lives in the artifact's own package (D2) — the artifact names a digest, never a place.
9365
+ image: parseImageReference(`${repository.ref}@${image}`, label),
9366
+ provenance: {
9367
+ repository: recorded.repository,
9368
+ commit: recorded.commit
9369
+ },
9370
+ label
9371
+ });
9372
+ }
9373
+ return candidates;
9374
+ }
9375
+ __name(fromRelease, "fromRelease");
9376
+ async function runVerifyImage(argv) {
9377
+ const image = argv[0] !== void 0 && !argv[0].startsWith("--") ? argv[0] : void 0;
9378
+ const { single: flags } = parseCatalogFlags(image === void 0 ? argv : argv.slice(1), [
9379
+ "key",
9380
+ "commit",
9381
+ "from"
9382
+ ]);
9383
+ if (image !== void 0 && flags.from !== void 0) {
9384
+ throw new Error("An image and --from are alternatives: verify one image, or every engine action release.yaml names.");
9385
+ }
9386
+ if (image === void 0 && flags.from === void 0) {
9387
+ throw new Error(`Name an image or a release. Usage: ${VERIFY_IMAGE_USAGE}`);
9388
+ }
9389
+ if (flags.from !== void 0 && (flags.key !== void 0 || flags.commit !== void 0)) {
9390
+ throw new Error("--key and --commit are read from each artifact when --from is given; pass one or the other.");
9391
+ }
9392
+ const candidates = image !== void 0 ? [
9393
+ single(image, flags)
9394
+ ] : await fromRelease(flags.from);
9395
+ for (const candidate of candidates) {
9396
+ const refusal = await imageRefusal(candidate.image, candidate.key, candidate.provenance);
9397
+ if (refusal !== null) throw new Error(`${candidate.label}: ${refusal} Nothing was signed.`);
9398
+ }
9399
+ for (const candidate of candidates) {
9400
+ process.stderr.write(`\u2713 ${candidate.key}: ${candidate.image.ref} \u2014 built in ${candidate.provenance.repository} at ${candidate.provenance.commit}
9401
+ `);
9402
+ process.stdout.write(`${candidate.image.ref}
9403
+ `);
9404
+ }
9405
+ if (candidates.length === 0) process.stderr.write(`\u2713 ${flags.from} names no engine action \u2014 no image to sign.
9406
+ `);
9407
+ }
9408
+ __name(runVerifyImage, "runVerifyImage");
9409
+
8057
9410
  // src/catalog/index.ts
8058
9411
  var CATALOG_USAGE = `runveric catalog \u2014 publish first-party catalog artifacts (docs/127)
8059
9412
 
8060
9413
  ${PUBLISH_ACTION_USAGE}
8061
- Publish one action's manifest, verbatim, as an artifact. stdout: <key>=<repository>@<digest>
9414
+ Publish one action's manifest, verbatim, as an artifact \u2014 with --image, an engine action's
9415
+ artifact naming its image by digest. stdout: <key>=<repository>@<digest>
8062
9416
 
8063
9417
  ${PUBLISH_RELEASE_USAGE}
8064
9418
  Publish a release naming each action artifact by digest. stdout: <repository>@<digest>
8065
9419
 
9420
+ ${VERIFY_IMAGE_USAGE}
9421
+ Judge an engine action's image by the server's rule before it is signed.
9422
+ stdout: one <repository>@<digest> per image that passed
9423
+
8066
9424
  Credentials: GHCR_TOKEN (write:packages), or in a workflow GITHUB_TOKEN with packages: write.
8067
9425
  Provenance: --repository/--commit, else GITHUB_SERVER_URL/GITHUB_REPOSITORY and GITHUB_SHA, else git.`;
8068
9426
  var SUBCOMMANDS = {
8069
9427
  "publish-action": runPublishAction,
8070
- "publish-release": runPublishRelease
9428
+ "publish-release": runPublishRelease,
9429
+ "verify-image": runVerifyImage
8071
9430
  };
8072
9431
  async function runCatalog(argv) {
8073
9432
  const [subcommand, ...rest] = argv;
@@ -8078,7 +9437,7 @@ async function runCatalog(argv) {
8078
9437
  }
8079
9438
  const run3 = SUBCOMMANDS[subcommand];
8080
9439
  if (run3 === void 0) {
8081
- printError(`\u2717 "catalog ${subcommand}" is not a command. There are two: ${Object.keys(SUBCOMMANDS).join(", ")}.`);
9440
+ printError(`\u2717 "catalog ${subcommand}" is not a command. There are ${Object.keys(SUBCOMMANDS).length}: ${Object.keys(SUBCOMMANDS).join(", ")}.`);
8082
9441
  return 1;
8083
9442
  }
8084
9443
  try {
@@ -8152,7 +9511,7 @@ function toPackagePath(root, full) {
8152
9511
  __name(toPackagePath, "toPackagePath");
8153
9512
 
8154
9513
  // src/check.ts
8155
- var import_contracts4 = __toESM(require_dist());
9514
+ var import_contracts6 = __toESM(require_dist());
8156
9515
  async function runCheck(options) {
8157
9516
  const directory = resolve2(options.directory ?? ".");
8158
9517
  let files;
@@ -8176,7 +9535,7 @@ async function runCheck(options) {
8176
9535
  print(style.red("This package would be REFUSED at import."));
8177
9536
  } else if (report.lintBlocked) {
8178
9537
  print();
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.`);
9538
+ print(style.red("This package would be ACCEPTED at import \u2014 and then misbehave.") + ` The errors above are not things ${import_contracts6.BRAND_SHORT_NAME} checks; they are things that go wrong at run time, silently.`);
8180
9539
  }
8181
9540
  if (report.advisoryCount > 0) {
8182
9541
  print();
@@ -8196,7 +9555,7 @@ async function runCheck(options) {
8196
9555
  __name(runCheck, "runCheck");
8197
9556
 
8198
9557
  // src/init.ts
8199
- var import_contracts5 = __toESM(require_dist());
9558
+ var import_contracts7 = __toESM(require_dist());
8200
9559
  var import_action_manifest2 = __toESM(require_dist2());
8201
9560
  import { mkdir, writeFile } from "fs/promises";
8202
9561
  import { createInterface } from "readline/promises";
@@ -8265,9 +9624,9 @@ async function resolveId(supplied, ask) {
8265
9624
  }
8266
9625
  __name(resolveId, "resolveId");
8267
9626
  async function resolveInterpreter(supplied, ask) {
8268
- const value = supplied?.trim() || await ask?.(`Interpreter (${import_contracts5.PACKAGE_INTERPRETERS.join(" / ")})`, "bash") || "bash";
9627
+ const value = supplied?.trim() || await ask?.(`Interpreter (${import_contracts7.PACKAGE_INTERPRETERS.join(" / ")})`, "bash") || "bash";
8269
9628
  if (!isInterpreter(value)) {
8270
- printError(style.red(`"${value}" is not an interpreter ${import_contracts5.BRAND_SHORT_NAME} runs.`) + ` The runner image ships ${import_contracts5.PACKAGE_INTERPRETERS.join(", ")}.`);
9629
+ printError(style.red(`"${value}" is not an interpreter ${import_contracts7.BRAND_SHORT_NAME} runs.`) + ` The runner image ships ${import_contracts7.PACKAGE_INTERPRETERS.join(", ")}.`);
8271
9630
  return null;
8272
9631
  }
8273
9632
  return value;
@@ -8288,7 +9647,7 @@ function prompter() {
8288
9647
  }
8289
9648
  __name(prompter, "prompter");
8290
9649
  function isInterpreter(value) {
8291
- return import_contracts5.PACKAGE_INTERPRETERS.includes(value);
9650
+ return import_contracts7.PACKAGE_INTERPRETERS.includes(value);
8292
9651
  }
8293
9652
  __name(isInterpreter, "isInterpreter");
8294
9653
  function titleCase(value) {
@@ -8298,7 +9657,7 @@ __name(titleCase, "titleCase");
8298
9657
 
8299
9658
  // src/publish.ts
8300
9659
  var import_action_manifest3 = __toESM(require_dist2());
8301
- var import_contracts6 = __toESM(require_dist());
9660
+ var import_contracts8 = __toESM(require_dist());
8302
9661
  import { resolve as resolve4 } from "path";
8303
9662
 
8304
9663
  // src/renamed-variable.ts
@@ -8310,15 +9669,15 @@ function renamedVariableRefusal(current, retired, env = process.env) {
8310
9669
  __name(renamedVariableRefusal, "renamedVariableRefusal");
8311
9670
 
8312
9671
  // src/publish.ts
8313
- var TOKEN_ENV = import_contracts6.AUTHORING_CLI_ENV.token;
8314
- var URL_ENV = import_contracts6.AUTHORING_CLI_ENV.url;
9672
+ var TOKEN_ENV = import_contracts8.AUTHORING_CLI_ENV.token;
9673
+ var URL_ENV = import_contracts8.AUTHORING_CLI_ENV.url;
8315
9674
  async function runPublish(options) {
8316
9675
  const directory = resolve4(options.directory ?? ".");
8317
9676
  const baseUrl = (options.to ?? process.env[URL_ENV] ?? "").trim().replace(/\/+$/, "");
8318
9677
  const token2 = (options.token ?? process.env[TOKEN_ENV] ?? "").trim();
8319
9678
  const stale = [
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
9679
+ options.to === void 0 ? renamedVariableRefusal(URL_ENV, import_contracts8.RETIRED_AUTHORING_CLI_ENV.url) : null,
9680
+ options.token === void 0 ? renamedVariableRefusal(TOKEN_ENV, import_contracts8.RETIRED_AUTHORING_CLI_ENV.token) : null
8322
9681
  ].filter((sentence) => sentence !== null);
8323
9682
  if (stale.length > 0) {
8324
9683
  for (const sentence of stale) printError(style.red(sentence));
@@ -8329,7 +9688,7 @@ async function runPublish(options) {
8329
9688
  return 1;
8330
9689
  }
8331
9690
  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.)`);
9691
+ printError(style.red(`Publishing needs ${import_contracts8.BRAND_ARTICLE} ${import_contracts8.BRAND_SHORT_NAME} access token.`) + ` Set ${TOKEN_ENV}, or pass --token. (init, check and test need no account at all.)`);
8333
9692
  return 1;
8334
9693
  }
8335
9694
  let files;
@@ -8349,7 +9708,7 @@ async function runPublish(options) {
8349
9708
  errors: report.errorCount,
8350
9709
  warnings: report.warningCount
8351
9710
  });
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."));
9711
+ printError(style.red("\nNot published.") + (report.validation.ok ? ` ${import_contracts8.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."));
8353
9712
  return 1;
8354
9713
  }
8355
9714
  if (report.advisoryCount > 0) {
@@ -8396,7 +9755,7 @@ Publish refused (${response.status}).`));
8396
9755
  try {
8397
9756
  version2 = JSON.parse(text);
8398
9757
  } catch {
8399
- printError(style.red(`${import_contracts6.BRAND_SHORT_NAME} accepted the package but returned a response we cannot read.`));
9758
+ printError(style.red(`${import_contracts8.BRAND_SHORT_NAME} accepted the package but returned a response we cannot read.`));
8400
9759
  return 1;
8401
9760
  }
8402
9761
  print();
@@ -8405,7 +9764,7 @@ Publish refused (${response.status}).`));
8405
9764
  print(` content digest ${version2.contentDigest}`);
8406
9765
  print(` tier ${version2.tier} ${style.grey(`(${version2.provenance})`)}`);
8407
9766
  print();
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.`));
9767
+ print(style.grey(`It is an immutable version now. Adopt it in ${import_contracts8.BRAND_SHORT_NAME} to put it in the catalog \u2014 publishing does not adopt, so nobody can run it until somebody chooses to.`));
8409
9768
  print();
8410
9769
  return 0;
8411
9770
  }
@@ -8417,11 +9776,11 @@ function formatProblem(text) {
8417
9776
  } catch {
8418
9777
  return ` ${text}`;
8419
9778
  }
8420
- const problem = (0, import_contracts6.parseProblemDocument)(parsed);
9779
+ const problem = (0, import_contracts8.parseProblemDocument)(parsed);
8421
9780
  if (!problem) return ` ${text}`;
8422
9781
  const lines = [];
8423
9782
  if (problem.detail) lines.push(` ${problem.detail}`);
8424
- for (const error of (0, import_contracts6.problemFieldErrors)(problem)) {
9783
+ for (const error of (0, import_contracts8.problemFieldErrors)(problem)) {
8425
9784
  lines.push(error.field ? ` ${style.bold(error.field)}: ${error.message}` : ` ${error.message}`);
8426
9785
  }
8427
9786
  return lines.length > 0 ? lines.join("\n") : ` ${text}`;
@@ -8429,12 +9788,12 @@ function formatProblem(text) {
8429
9788
  __name(formatProblem, "formatProblem");
8430
9789
 
8431
9790
  // src/runner-image.ts
8432
- var import_contracts7 = __toESM(require_dist());
9791
+ var import_contracts9 = __toESM(require_dist());
8433
9792
  import { execFile } from "child_process";
8434
9793
  import { promisify } from "util";
8435
9794
  var run = promisify(execFile);
8436
9795
  var LOCAL_RUNNER_IMAGE = "runveric-package-runner:local";
8437
- var RUNNER_IMAGE_ENV = import_contracts7.AUTHORING_CLI_ENV.runnerImage;
9796
+ var RUNNER_IMAGE_ENV = import_contracts9.AUTHORING_CLI_ENV.runnerImage;
8438
9797
  var RenamedVariableError = class extends Error {
8439
9798
  static {
8440
9799
  __name(this, "RenamedVariableError");
@@ -8451,7 +9810,7 @@ function resolveRunnerImage(flag) {
8451
9810
  reference: configured,
8452
9811
  origin: "environment"
8453
9812
  };
8454
- const stale = renamedVariableRefusal(RUNNER_IMAGE_ENV, import_contracts7.RETIRED_AUTHORING_CLI_ENV.runnerImage);
9813
+ const stale = renamedVariableRefusal(RUNNER_IMAGE_ENV, import_contracts9.RETIRED_AUTHORING_CLI_ENV.runnerImage);
8455
9814
  if (stale) throw new RenamedVariableError(stale);
8456
9815
  return {
8457
9816
  reference: LOCAL_RUNNER_IMAGE,
@@ -8466,7 +9825,7 @@ function runnerImageNote(image) {
8466
9825
  case "environment":
8467
9826
  return `from ${RUNNER_IMAGE_ENV}`;
8468
9827
  case "local-build":
8469
- return `locally built \u2014 NOT the image your ${import_contracts7.BRAND_SHORT_NAME} runs; set ${RUNNER_IMAGE_ENV} to match it`;
9828
+ return `locally built \u2014 NOT the image your ${import_contracts9.BRAND_SHORT_NAME} runs; set ${RUNNER_IMAGE_ENV} to match it`;
8470
9829
  }
8471
9830
  }
8472
9831
  __name(runnerImageNote, "runnerImageNote");
@@ -8483,7 +9842,7 @@ async function assertDockerAvailable() {
8483
9842
  "{{.Server.Version}}"
8484
9843
  ]);
8485
9844
  } catch {
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.`);
9845
+ throw new DockerMissingError(`Docker is not available. \`runveric test\` runs your package in the real ${import_contracts9.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.`);
8487
9846
  }
8488
9847
  }
8489
9848
  __name(assertDockerAvailable, "assertDockerAvailable");
@@ -8576,7 +9935,7 @@ ${message.trim()}`);
8576
9935
  __name(azJson, "azJson");
8577
9936
 
8578
9937
  // src/test.ts
8579
- var import_contracts8 = __toESM(require_dist());
9938
+ var import_contracts10 = __toESM(require_dist());
8580
9939
  var PHASES = [
8581
9940
  "validate",
8582
9941
  "run",
@@ -8593,7 +9952,7 @@ async function runTest(options) {
8593
9952
  const directory = resolve5(options.directory ?? ".");
8594
9953
  const phase = options.phase ?? "run";
8595
9954
  if (!PHASES.includes(phase)) {
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.`);
9955
+ printError(style.red(`"${options.phase}" is not a phase.`) + ` ${import_contracts10.BRAND_SHORT_NAME} runs ${PHASES.join(", ")} \u2014 one at a time, so you can read each one's result.`);
8597
9956
  return 1;
8598
9957
  }
8599
9958
  let files;
@@ -8665,9 +10024,9 @@ async function runTest(options) {
8665
10024
  print(` runner image ${image.reference} ${style.grey(`(${runnerImageNote(image)})`)}`);
8666
10025
  print(` interpreter ${interpreter}@${manifest.runtime.version}`);
8667
10026
  print(` entrypoint ${entrypoint}${import_action_manifest4.PACKAGE_INTERPRETER_LAUNCH[interpreter].entryFile}`);
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)")}`);
10027
+ print(` identity ${credential ? `${credential.account.name} ${style.grey(`\u2014 YOUR access, not ${import_contracts10.BRAND_SHORT_NAME}'s brokered least-privilege identity`)}` : style.grey("none (--no-credential)")}`);
8669
10028
  if (manifest.requiresNetwork) {
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`)}`);
10029
+ print(` network ${style.yellow("open")} ${style.grey(`\u2014 your allowlist (${(manifest.egressAllowlist ?? []).join(", ")}) is NOT enforced here; ${import_contracts10.BRAND_SHORT_NAME} enforces it`)}`);
8671
10030
  } else {
8672
10031
  print(` network ${style.grey("none \u2014 this package declares requiresNetwork: false")}`);
8673
10032
  }
@@ -8775,7 +10134,7 @@ async function materialize(workspace, input) {
8775
10134
  await writeFile2(target, contents);
8776
10135
  }
8777
10136
  const context = {
8778
- protocolVersion: import_contracts8.PACKAGE_EXECUTION_PROTOCOL_VERSION,
10137
+ protocolVersion: import_contracts10.PACKAGE_EXECUTION_PROTOCOL_VERSION,
8779
10138
  // A recognisable id rather than a random uuid: it appears in the package's own log lines,
8780
10139
  // and "local-test" tells the reader instantly that this was not a real run.
8781
10140
  executionId: "local-test",
@@ -8801,7 +10160,7 @@ async function runContainer(options) {
8801
10160
  const launch = import_action_manifest4.PACKAGE_INTERPRETER_LAUNCH[options.interpreter];
8802
10161
  const script = `${CONTAINER.packageRoot}/${options.entrypoint}${launch.entryFile}`;
8803
10162
  const environment = {
8804
- RUNVERIC_PROTOCOL_VERSION: import_contracts8.PACKAGE_RESULT_PROTOCOL_VERSION,
10163
+ RUNVERIC_PROTOCOL_VERSION: import_contracts10.PACKAGE_RESULT_PROTOCOL_VERSION,
8805
10164
  RUNVERIC_PHASE: options.phase,
8806
10165
  RUNVERIC_CONTEXT_FILE: `${CONTAINER.input}/context.json`,
8807
10166
  RUNVERIC_PARAMETERS_FILE: `${CONTAINER.input}/parameters.json`,
@@ -8928,13 +10287,13 @@ function emitLine(line, stderr, onEvent) {
8928
10287
  __name(emitLine, "emitLine");
8929
10288
  async function reportOutcome(options) {
8930
10289
  if (options.exit.timedOut) {
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.`);
10290
+ printError(style.red("\nThe package did not finish within 15 minutes and was killed.") + ` ${import_contracts10.BRAND_SHORT_NAME} enforces the \`executionTimeout\` in your manifest; this is the local ceiling.`);
8932
10291
  return 1;
8933
10292
  }
8934
10293
  const exists = await stat2(options.resultPath).catch(() => null);
8935
10294
  if (!exists?.isFile() || exists.size === 0) {
8936
10295
  printError(style.red("\nThe package wrote no result.") + `
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.
10296
+ result.json is the only channel ${import_contracts10.BRAND_SHORT_NAME} reads. Exiting ${options.exit.code ?? "with a signal"} without writing $RUNVERIC_RESULT_FILE is a FAILED run, whatever the script printed.
8938
10297
  Write it as the last thing your script does, on every path \u2014 including the ones that fail.`);
8939
10298
  return 1;
8940
10299
  }
@@ -8942,7 +10301,7 @@ async function reportOutcome(options) {
8942
10301
  try {
8943
10302
  parsed = JSON.parse(await readFile2(options.resultPath, "utf8"));
8944
10303
  } catch {
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.`);
10304
+ printError(style.red("\nresult.json is not valid JSON.") + ` ${import_contracts10.BRAND_SHORT_NAME} refuses the run rather than guessing what the package meant.`);
8946
10305
  return 1;
8947
10306
  }
8948
10307
  let result;
@@ -8950,14 +10309,14 @@ async function reportOutcome(options) {
8950
10309
  result = (0, import_action_manifest4.validatePackageResult)(parsed, options.phase);
8951
10310
  } catch (error) {
8952
10311
  printError(style.red(`
8953
- ${import_contracts8.BRAND_SHORT_NAME} would REFUSE this result.`) + `
10312
+ ${import_contracts10.BRAND_SHORT_NAME} would REFUSE this result.`) + `
8954
10313
  ${error instanceof Error ? error.message : String(error)}
8955
10314
  This is the same check the control plane runs, so fixing it here fixes it there.`);
8956
10315
  return 1;
8957
10316
  }
8958
10317
  printResult(result);
8959
10318
  if (options.exit.code !== 0) {
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.`));
10319
+ print(style.yellow(`The package exited ${options.exit.code} but wrote a valid result. ${import_contracts10.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.`));
8961
10320
  print();
8962
10321
  }
8963
10322
  return result.status === "succeeded" ? 0 : 1;
@@ -8984,9 +10343,9 @@ async function readJsonFile(path) {
8984
10343
  __name(readJsonFile, "readJsonFile");
8985
10344
 
8986
10345
  // src/main.ts
8987
- var import_contracts9 = __toESM(require_dist());
10346
+ var import_contracts11 = __toESM(require_dist());
8988
10347
  var USAGE = `
8989
- ${style.bold("runveric")} \u2014 author ${import_contracts9.BRAND_ARTICLE} ${import_contracts9.BRAND_SHORT_NAME} action, without pushing anything.
10348
+ ${style.bold("runveric")} \u2014 author ${import_contracts11.BRAND_ARTICLE} ${import_contracts11.BRAND_SHORT_NAME} action, without pushing anything.
8990
10349
 
8991
10350
  ${style.bold("USAGE")}
8992
10351
  runveric <command> [options]
@@ -8994,8 +10353,8 @@ ${style.bold("USAGE")}
8994
10353
  ${style.bold("COMMANDS")}
8995
10354
  init [directory] Scaffold a working action. It passes check and test immediately.
8996
10355
  check Validate the package. Offline \u2014 no account, no cloud, no network.
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.
10356
+ test Run one phase in the real ${import_contracts11.BRAND_SHORT_NAME} runner image.
10357
+ publish Upload it to ${import_contracts11.BRAND_SHORT_NAME} as an authored version. Needs an account.
8999
10358
  catalog <command> Publish first-party catalog artifacts. Not for authoring: see \`runveric catalog --help\`.
9000
10359
 
9001
10360
  ${style.bold("init")}
@@ -9019,9 +10378,9 @@ ${style.bold("test")}
9019
10378
  --keep Keep the materialized /runveric tree for inspection.
9020
10379
 
9021
10380
  ${style.bold("publish")}
9022
- --to <url> Your ${import_contracts9.BRAND_SHORT_NAME} URL. Defaults to $${URL_ENV}.
10381
+ --to <url> Your ${import_contracts11.BRAND_SHORT_NAME} URL. Defaults to $${URL_ENV}.
9023
10382
  --token <token> Access token. Defaults to $${TOKEN_ENV}.
9024
- --force Publish despite lint errors. Never bypasses ${import_contracts9.BRAND_SHORT_NAME}'s validation.
10383
+ --force Publish despite lint errors. Never bypasses ${import_contracts11.BRAND_SHORT_NAME}'s validation.
9025
10384
 
9026
10385
  ${style.bold("EVERYWHERE")}
9027
10386
  --dir <path> The package directory. Defaults to the current directory.