@markdown-ai/cli 1.0.0-rc.2 → 1.1.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/cli.js CHANGED
@@ -2976,7 +2976,7 @@ var require_compile = __commonJS({
2976
2976
  const schOrFunc = root.refs[ref];
2977
2977
  if (schOrFunc)
2978
2978
  return schOrFunc;
2979
- let _sch = resolve2.call(this, root, ref);
2979
+ let _sch = resolve4.call(this, root, ref);
2980
2980
  if (_sch === void 0) {
2981
2981
  const schema2 = (_a = root.localRefs) === null || _a === void 0 ? void 0 : _a[ref];
2982
2982
  const { schemaId } = this.opts;
@@ -3003,7 +3003,7 @@ var require_compile = __commonJS({
3003
3003
  function sameSchemaEnv(s1, s2) {
3004
3004
  return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
3005
3005
  }
3006
- function resolve2(root, ref) {
3006
+ function resolve4(root, ref) {
3007
3007
  let sch;
3008
3008
  while (typeof (sch = this.refs[ref]) == "string")
3009
3009
  ref = sch;
@@ -3578,55 +3578,55 @@ var require_fast_uri = __commonJS({
3578
3578
  }
3579
3579
  return uri;
3580
3580
  }
3581
- function resolve2(baseURI, relativeURI, options) {
3581
+ function resolve4(baseURI, relativeURI, options) {
3582
3582
  const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
3583
3583
  const resolved = resolveComponent(parse(baseURI, schemelessOptions), parse(relativeURI, schemelessOptions), schemelessOptions, true);
3584
3584
  schemelessOptions.skipEscape = true;
3585
3585
  return serialize(resolved, schemelessOptions);
3586
3586
  }
3587
- function resolveComponent(base, relative, options, skipNormalization) {
3587
+ function resolveComponent(base, relative3, options, skipNormalization) {
3588
3588
  const target = {};
3589
3589
  if (!skipNormalization) {
3590
3590
  base = parse(serialize(base, options), options);
3591
- relative = parse(serialize(relative, options), options);
3591
+ relative3 = parse(serialize(relative3, options), options);
3592
3592
  }
3593
3593
  options = options || {};
3594
- if (!options.tolerant && relative.scheme) {
3595
- target.scheme = relative.scheme;
3596
- target.userinfo = relative.userinfo;
3597
- target.host = relative.host;
3598
- target.port = relative.port;
3599
- target.path = removeDotSegments(relative.path || "");
3600
- target.query = relative.query;
3594
+ if (!options.tolerant && relative3.scheme) {
3595
+ target.scheme = relative3.scheme;
3596
+ target.userinfo = relative3.userinfo;
3597
+ target.host = relative3.host;
3598
+ target.port = relative3.port;
3599
+ target.path = removeDotSegments(relative3.path || "");
3600
+ target.query = relative3.query;
3601
3601
  } else {
3602
- if (relative.userinfo !== void 0 || relative.host !== void 0 || relative.port !== void 0) {
3603
- target.userinfo = relative.userinfo;
3604
- target.host = relative.host;
3605
- target.port = relative.port;
3606
- target.path = removeDotSegments(relative.path || "");
3607
- target.query = relative.query;
3602
+ if (relative3.userinfo !== void 0 || relative3.host !== void 0 || relative3.port !== void 0) {
3603
+ target.userinfo = relative3.userinfo;
3604
+ target.host = relative3.host;
3605
+ target.port = relative3.port;
3606
+ target.path = removeDotSegments(relative3.path || "");
3607
+ target.query = relative3.query;
3608
3608
  } else {
3609
- if (!relative.path) {
3609
+ if (!relative3.path) {
3610
3610
  target.path = base.path;
3611
- if (relative.query !== void 0) {
3612
- target.query = relative.query;
3611
+ if (relative3.query !== void 0) {
3612
+ target.query = relative3.query;
3613
3613
  } else {
3614
3614
  target.query = base.query;
3615
3615
  }
3616
3616
  } else {
3617
- if (relative.path[0] === "/") {
3618
- target.path = removeDotSegments(relative.path);
3617
+ if (relative3.path[0] === "/") {
3618
+ target.path = removeDotSegments(relative3.path);
3619
3619
  } else {
3620
3620
  if ((base.userinfo !== void 0 || base.host !== void 0 || base.port !== void 0) && !base.path) {
3621
- target.path = "/" + relative.path;
3621
+ target.path = "/" + relative3.path;
3622
3622
  } else if (!base.path) {
3623
- target.path = relative.path;
3623
+ target.path = relative3.path;
3624
3624
  } else {
3625
- target.path = base.path.slice(0, base.path.lastIndexOf("/") + 1) + relative.path;
3625
+ target.path = base.path.slice(0, base.path.lastIndexOf("/") + 1) + relative3.path;
3626
3626
  }
3627
3627
  target.path = removeDotSegments(target.path);
3628
3628
  }
3629
- target.query = relative.query;
3629
+ target.query = relative3.query;
3630
3630
  }
3631
3631
  target.userinfo = base.userinfo;
3632
3632
  target.host = base.host;
@@ -3634,7 +3634,7 @@ var require_fast_uri = __commonJS({
3634
3634
  }
3635
3635
  target.scheme = base.scheme;
3636
3636
  }
3637
- target.fragment = relative.fragment;
3637
+ target.fragment = relative3.fragment;
3638
3638
  return target;
3639
3639
  }
3640
3640
  function equal(uriA, uriB, options) {
@@ -3805,7 +3805,7 @@ var require_fast_uri = __commonJS({
3805
3805
  var fastUri = {
3806
3806
  SCHEMES,
3807
3807
  normalize,
3808
- resolve: resolve2,
3808
+ resolve: resolve4,
3809
3809
  resolveComponent,
3810
3810
  equal,
3811
3811
  serialize,
@@ -7618,9 +7618,6 @@ var require_dist = __commonJS({
7618
7618
  }
7619
7619
  });
7620
7620
 
7621
- // src/cli.ts
7622
- import { existsSync as existsSync2, rmSync as rmSync2 } from "node:fs";
7623
-
7624
7621
  // src/types.ts
7625
7622
  var EXIT = {
7626
7623
  ok: 0,
@@ -7645,22 +7642,264 @@ var MDA_EXTENDED = [
7645
7642
  var TARGET_ORDER = ["SKILL.md", "AGENTS.md", "MCP-SERVER.md"];
7646
7643
  var MCP_BOUNDARY = "\n--MDA-FILE-BOUNDARY--\n";
7647
7644
  function commandResult(ok, command, exitCode, diagnostics, extra = {}) {
7648
- return { ok, command, exitCode, diagnostics, ...extra };
7645
+ const stableDiagnostics = diagnostics.map(stabilizeDiagnosticCode);
7646
+ return {
7647
+ ok,
7648
+ command,
7649
+ exitCode,
7650
+ summary: ok ? `${command} completed` : `${command} failed`,
7651
+ artifacts: [],
7652
+ diagnostics: stableDiagnostics,
7653
+ nextActions: [],
7654
+ ...extra
7655
+ };
7649
7656
  }
7650
7657
  function diag(code, message, extra = {}) {
7651
7658
  return { code, message, severity: "error", ...extra };
7652
7659
  }
7660
+ function stabilizeDiagnosticCode(diagnostic) {
7661
+ return { ...diagnostic, code: stableDiagnosticCode(diagnostic.code) };
7662
+ }
7663
+ function stableDiagnosticCode(code) {
7664
+ if (/^(input|schema|integrity|signature|trust_policy|sigstore|rekor|did_web|llmix|release|compat|filesystem|conformance)\./.test(code)) {
7665
+ return code;
7666
+ }
7667
+ const mapped = DIAGNOSTIC_CODE_MAP[code];
7668
+ if (mapped) return mapped;
7669
+ return `input.${code.replace(/[^a-zA-Z0-9]+/g, "_").replace(/^_+|_+$/g, "").toLowerCase() || "unknown"}`;
7670
+ }
7671
+ var DIAGNOSTIC_CODE_MAP = {
7672
+ "usage-error": "input.usage",
7673
+ "invalid-encoding": "input.invalid_encoding",
7674
+ "unterminated-frontmatter": "input.unterminated_frontmatter",
7675
+ "frontmatter-yaml-parse-error": "input.frontmatter_yaml_parse",
7676
+ "missing-required-frontmatter": "input.missing_required_frontmatter",
7677
+ "missing-required-body": "input.missing_required_body",
7678
+ "invalid-json": "input.invalid_json",
7679
+ "io-error": "filesystem.io",
7680
+ "rollback-error": "filesystem.rollback",
7681
+ "schema-validation-error": "schema.validation",
7682
+ "relationship-footnote-json-parse-error": "schema.relationship_footnote_json_parse",
7683
+ "missing-required-sidecar": "integrity.missing_required_sidecar",
7684
+ "missing-required-integrity": "integrity.missing_required",
7685
+ "unsupported-integrity-algorithm": "integrity.unsupported_algorithm",
7686
+ "integrity-mismatch": "integrity.mismatch",
7687
+ "missing-required-signature": "signature.missing_required",
7688
+ "signature-digest-mismatch": "signature.digest_mismatch",
7689
+ "signature-verification-unavailable": "signature.verification_unavailable",
7690
+ "signing-unavailable": "signature.signing_unavailable",
7691
+ "trust-policy-violation": "trust_policy.violation",
7692
+ "no-trusted-signature": "trust_policy.no_trusted_signature",
7693
+ "insufficient-trusted-signatures": "trust_policy.insufficient_trusted_signatures",
7694
+ "fixture-missing": "conformance.fixture_missing",
7695
+ "conformance-manifest-invalid": "conformance.manifest_invalid",
7696
+ "extraction-mismatch": "conformance.extraction_mismatch",
7697
+ "expected-error-mismatch": "conformance.expected_error_mismatch",
7698
+ "compile-fixture-unavailable": "conformance.compile_fixture_unavailable",
7699
+ "internal-error": "input.internal_error"
7700
+ };
7653
7701
  function usage(command, message, extra = {}) {
7654
- return commandResult(false, command, EXIT.usage, [diag("usage-error", message)], extra);
7702
+ return commandResult(false, command, EXIT.usage, [diag("usage-error", message)], {
7703
+ summary: `${command} usage error`,
7704
+ nextActions: [
7705
+ {
7706
+ id: "show-help",
7707
+ required: true,
7708
+ reason: "Review command usage",
7709
+ command: "mda --help"
7710
+ }
7711
+ ],
7712
+ ...extra
7713
+ });
7655
7714
  }
7656
7715
  function ioError(command, message, extra = {}) {
7657
- return commandResult(false, command, EXIT.io, [diag("io-error", message)], extra);
7716
+ return commandResult(false, command, EXIT.io, [diag("io-error", message)], {
7717
+ summary: `${command} IO error`,
7718
+ nextActions: [
7719
+ {
7720
+ id: "fix-filesystem",
7721
+ required: true,
7722
+ reason: "Fix the filesystem path or permissions and retry",
7723
+ command: `mda ${command}`
7724
+ }
7725
+ ],
7726
+ ...extra
7727
+ });
7658
7728
  }
7659
7729
 
7730
+ // src/cli/help.ts
7731
+ var HELP = `Markdown AI CLI (@markdown-ai/cli)
7732
+
7733
+ Usage:
7734
+ mda
7735
+ mda --help
7736
+ mda init <name> [--out <file>] [--json]
7737
+ mda init --template llmix-preset --module <name> --preset <name> --provider <provider> --model <model> [--out <file>] [--json]
7738
+ mda validate <file> [--target source|SKILL.md|AGENTS.md|MCP-SERVER.md|auto] [--json]
7739
+ mda compile <file.mda> --target <target...> [--out-dir <dir>] [--integrity] [--manifest <path>] [--strict-compat] [--json]
7740
+ mda canonicalize <file> [--target source|SKILL.md|AGENTS.md|MCP-SERVER.md|auto] [--sidecar <path>] [--json]
7741
+ mda integrity compute <file> [--target source|SKILL.md|AGENTS.md|MCP-SERVER.md|auto] [--sidecar <path>] [--algorithm sha256|sha384|sha512] [--write] [--json]
7742
+ mda integrity verify <file> [--target source|SKILL.md|AGENTS.md|MCP-SERVER.md|auto] [--sidecar <path>] [--json]
7743
+ mda sign <file> --profile did-web --did <did> --key-id <key-id> --key-file <path> (--out <file>|--in-place) [--json]
7744
+ mda sign <file> --profile github-actions --repo <owner/repo> --workflow <workflow> --ref <ref> --rekor --offline-sigstore-fixture <path> (--out <file>|--in-place) [--json]
7745
+ mda sign <file> --method did-web --key <path> --identity <domain> (--out <file>|--in-place) [--json]
7746
+ mda verify <file> --policy <path> [--did-document <path>] [--offline-sigstore-fixture <path>] [--target source|SKILL.md|AGENTS.md|MCP-SERVER.md|auto] [--sidecar <path>] [--json]
7747
+ mda release trust policy --target llmix-registry --profile github-actions --repo <owner/repo> --workflow <workflow> --ref <ref> [--out <file>] [--json]
7748
+ mda release trust policy --target llmix-registry --profile did-web --domain <domain> [--min-signatures <n>] [--out <file>] [--json]
7749
+ mda release prepare --target llmix-registry --source <dir> --registry-dir <dir> --policy <path> --out <file> [--did-document <path>] [--offline-sigstore-fixture <path>] [--json]
7750
+ mda release finalize --target llmix-registry --registry-dir <dir> --registry-root <file> --release-plan <file> --policy <path> (--expected-root-digest <digest>|--derive-root-digest) [--minimum-revision <rev>] [--minimum-published-at <iso>] [--high-watermark <value>] --out <file> [--did-document <path>] [--offline-sigstore-fixture <path>] [--json]
7751
+ mda release finalize --target llmix-registry --registry-dir <dir> --manifest <path> --snippet-format json|env|kubernetes|github-actions|terraform|typescript|python|rust --snippet-out <path> [--json]
7752
+ mda doctor release --target llmix-registry --source <dir> --registry-dir <dir> --release-plan <path> --manifest <path> [--did-document <path>] [--offline-sigstore-fixture <path>] [--json]
7753
+ mda conformance [--suite <path>] [--level V|C] [--json]
7754
+
7755
+ Global flags:
7756
+ --json Print stable JSON only on stdout.
7757
+ --quiet Suppress non-essential human output.
7758
+ --verbose Include extra diagnostic context where available.
7759
+ --no-color Disable ANSI color.
7760
+ --no-next Omit human Next: guidance. JSON nextActions are unchanged.
7761
+ -h, --help Print this full help.
7762
+
7763
+ Commands and options:
7764
+ init <name>
7765
+ --out <file> Write the scaffold atomically. Refuses overwrite.
7766
+ --json Return scaffold in JSON instead of raw .mda text.
7767
+ --template llmix-preset Generate an LLMix preset source artifact.
7768
+ --module <name> LLMix module name, e.g. search_summary or _default.
7769
+ --preset <name> LLMix preset name, e.g. openai_fast or _base.
7770
+ --provider <provider> openai, anthropic, google, deepseek, openrouter, deepinfra, novita, together, or sno-gpu.
7771
+ --model <model> Provider model identifier.
7772
+
7773
+ validate <file>
7774
+ --target <target> source, SKILL.md, AGENTS.md, MCP-SERVER.md, or auto. Default: auto.
7775
+
7776
+ compile <file.mda>
7777
+ --target <target...> Required. One or more of SKILL.md, AGENTS.md, MCP-SERVER.md.
7778
+ --out-dir <dir> Output directory. Default: current working directory.
7779
+ --integrity Add sha256 integrity to emitted artifacts.
7780
+ --manifest <path> Write compile evidence with source digest, output digests, capabilities, and warnings.
7781
+ --strict-compat Treat compatibility warnings as compile failures before writing outputs.
7782
+
7783
+ canonicalize <file>
7784
+ --target <target> Default: auto.
7785
+ --sidecar <path> Required only for MCP-SERVER.md multi-file canonical bytes.
7786
+
7787
+ integrity compute <file>
7788
+ --target <target> Default: auto.
7789
+ --sidecar <path> Required only for MCP-SERVER.md.
7790
+ --algorithm <name> sha256, sha384, or sha512. Default: sha256.
7791
+ --write Write the computed digest into frontmatter.integrity. Refuses mismatched existing integrity.
7792
+
7793
+ integrity verify <file>
7794
+ --target <target> Default: auto.
7795
+ --sidecar <path> Required only for MCP-SERVER.md.
7796
+
7797
+ verify <file>
7798
+ --policy <path> Required trust policy JSON.
7799
+ --did-document <path> Local did:web document fixture for deterministic verification.
7800
+ --offline-sigstore-fixture <path>
7801
+ Local Sigstore/Rekor fixture for deterministic GitHub Actions verification.
7802
+ --target <target> Default: auto.
7803
+ --sidecar <path> Required only for MCP-SERVER.md.
7804
+ --offline Unsupported; use explicit profile evidence fixtures instead.
7805
+
7806
+ sign <file>
7807
+ --profile did-web Sign with a did:web key and DSSE PAE integrity payload.
7808
+ --did <did> Required for --profile did-web, e.g. did:web:example.com.
7809
+ --key-id <key-id> Required verification method id in the DID document.
7810
+ --key-file <path> Required private key PEM.
7811
+ --method did-web Compatibility alias for --profile did-web.
7812
+ --key <path> Compatibility alias for --key-file.
7813
+ --identity <domain> Compatibility alias for --did did:web:<domain>.
7814
+ --profile github-actions Sign with explicit GitHub Actions Sigstore/Rekor fixture evidence.
7815
+ --repo <owner/repo> Required GitHub repository for --profile github-actions.
7816
+ --workflow <workflow> Required workflow file or workflow identity.
7817
+ --ref <ref> Required exact Git ref.
7818
+ --rekor Required explicit Rekor evidence acknowledgement.
7819
+ --offline-sigstore-fixture <path>
7820
+ Required local Sigstore/Rekor fixture for deterministic signing.
7821
+ --out <file> Write signed output.
7822
+ --in-place Replace the input file.
7823
+
7824
+ release trust policy
7825
+ --target llmix-registry Required. Generate trust policy for the LLMix registry release target.
7826
+ --profile did-web Generate a schema-valid did:web trust policy.
7827
+ --domain <domain> Trusted did:web domain.
7828
+ --min-signatures <n> Minimum trusted signatures. Default: 1.
7829
+ --profile github-actions Generate a schema-valid Sigstore/Rekor policy.
7830
+ --repo <owner/repo> Trusted GitHub repository.
7831
+ --workflow <workflow> Trusted workflow file or workflow identity.
7832
+ --ref <ref> Trusted exact Git ref, e.g. refs/heads/main.
7833
+ --out <file> Write policy JSON atomically. Refuses overwrite.
7834
+
7835
+ release prepare
7836
+ --target llmix-registry Required. Prepare a verified LLMix registry release plan.
7837
+ --source <dir> Directory containing signed LLMix .mda preset sources.
7838
+ --registry-dir <dir> Target LLMix registry directory. This command reads only and never publishes registry files.
7839
+ --policy <path> Trust policy used to verify every source signature.
7840
+ --did-document <path> Local did:web document fixture when using did:web signatures.
7841
+ --offline-sigstore-fixture <path>
7842
+ Local Sigstore/Rekor fixture when using GitHub Actions signatures.
7843
+ --out <file> Write the deterministic release plan atomically. Refuses overwrite.
7844
+
7845
+ release finalize
7846
+ --target llmix-registry Required. Finalize LLMix registry release trust artifacts.
7847
+ --registry-dir <dir> Published LLMix registry directory. Manifest output is rejected inside this directory.
7848
+ --registry-root <file> Signed registry-root evidence JSON.
7849
+ --release-plan <file> Verified release plan produced before registry publication.
7850
+ --policy <path> Trust policy used to verify registry-root signatures.
7851
+ --expected-root-digest <d> Pin the exact registry-root digest.
7852
+ --derive-root-digest Derive expectedRootDigest from verified registry-root evidence.
7853
+ --minimum-revision <rev> Reject registry roots older than this revision.
7854
+ --minimum-published-at <iso> Reject registry roots published before this timestamp.
7855
+ --high-watermark <value> Reject roots below this monotonic high-watermark.
7856
+ --out <file> Write the external deployment trust manifest. Refuses overwrite.
7857
+ --manifest <path> Existing external trust manifest for snippet generation.
7858
+ --snippet-format <format> json, env, kubernetes, github-actions, terraform, typescript, python, or rust.
7859
+ --snippet-out <file> Write the deployment snippet atomically. Refuses overwrite.
7860
+
7861
+ doctor release
7862
+ --target llmix-registry Required. Check LLMix registry release readiness.
7863
+ --source <dir> Directory containing LLMix .mda preset sources.
7864
+ --registry-dir <dir> Published LLMix registry directory.
7865
+ --release-plan <path> Verified release plan used for the signed registry root.
7866
+ --manifest <path> External deployment trust manifest.
7867
+ --did-document <path> Local DID document for did:web registry-root signature verification.
7868
+ --offline-sigstore-fixture <path>
7869
+ Deterministic Sigstore/Rekor fixture for registry-root verification.
7870
+
7871
+ Examples:
7872
+ mda init hello-skill --out hello.mda
7873
+ mda init --template llmix-preset --module search_summary --preset openai_fast --provider openai --model gpt-5-mini --out search_summary/openai_fast.mda
7874
+ mda validate hello.mda --json
7875
+ mda compile hello.mda --target SKILL.md AGENTS.md MCP-SERVER.md --out-dir out --integrity --manifest out/compile-manifest.json
7876
+ mda canonicalize out/SKILL.md --target SKILL.md --json
7877
+ mda integrity compute out/SKILL.md --target SKILL.md --algorithm sha256 --json
7878
+ mda integrity verify out/SKILL.md --target SKILL.md
7879
+ mda verify signed.md --policy policy.json --json
7880
+ mda release trust policy --target llmix-registry --profile github-actions --repo owner/repo --workflow release.yml --ref refs/heads/main --out release/source-policy.json --json
7881
+ mda release prepare --target llmix-registry --source authoring --registry-dir registry --policy release/source-policy.json --out release/plan.json --json
7882
+ mda release finalize --target llmix-registry --registry-dir registry --registry-root registry/snapshots/current/registry-root.json --release-plan release/plan.json --policy release/root-policy.json --derive-root-digest --out release/llmix-trust.json --json
7883
+ mda release finalize --target llmix-registry --registry-dir registry --manifest release/llmix-trust.json --snippet-format json --snippet-out release/llmix-trust-snippet.json --json
7884
+ mda doctor release --target llmix-registry --source authoring --registry-dir registry --release-plan release/plan.json --manifest release/llmix-trust.json --did-document did.json --json
7885
+ mda conformance --suite conformance --level V --json
7886
+
7887
+ Exit codes:
7888
+ 0 Success.
7889
+ 1 Valid command, but artifact validation or verification failed.
7890
+ 2 CLI usage error: missing argument, unknown flag, ambiguous target.
7891
+ 3 IO or configuration error: missing file, overwrite refusal, unreadable policy.
7892
+ 4 Internal bug or invariant failure.
7893
+ `;
7894
+
7895
+ // src/cli/core-commands.ts
7896
+ import { existsSync as existsSync2, rmSync as rmSync2 } from "node:fs";
7897
+ import { resolve as resolve2 } from "node:path";
7898
+
7660
7899
  // src/mda.ts
7661
7900
  var import__ = __toESM(require__(), 1);
7662
7901
  var import_ajv_formats = __toESM(require_dist(), 1);
7663
- import { createHash } from "node:crypto";
7902
+ import { createHash, randomBytes } from "node:crypto";
7664
7903
  import {
7665
7904
  closeSync,
7666
7905
  existsSync,
@@ -7669,10 +7908,11 @@ import {
7669
7908
  openSync,
7670
7909
  readFileSync,
7671
7910
  readdirSync,
7911
+ renameSync,
7672
7912
  rmSync,
7673
7913
  writeFileSync
7674
7914
  } from "node:fs";
7675
- import { dirname, extname, join, resolve } from "node:path";
7915
+ import { dirname, extname, join, relative, resolve } from "node:path";
7676
7916
  import { fileURLToPath } from "node:url";
7677
7917
 
7678
7918
  // ../../node_modules/.pnpm/js-yaml@4.1.1/node_modules/js-yaml/dist/js-yaml.mjs
@@ -10311,6 +10551,29 @@ description: "Describe what this Markdown AI document does."
10311
10551
  Describe the instructions, workflow, or capability here.
10312
10552
  `;
10313
10553
  }
10554
+ function makeLlmixPresetScaffold(moduleName, presetName, provider, model) {
10555
+ const artifactName = `llmix-${moduleName.replace(/^_/, "").replace(/_/g, "-")}-${presetName.replace(/^_/, "").replace(/_/g, "-")}`;
10556
+ return renderMarkdown(
10557
+ {
10558
+ name: artifactName,
10559
+ description: `LLMix preset ${moduleName}/${presetName}`,
10560
+ metadata: {
10561
+ "snoai-llmix": {
10562
+ module: moduleName,
10563
+ preset: presetName,
10564
+ common: {
10565
+ provider,
10566
+ model
10567
+ }
10568
+ }
10569
+ }
10570
+ },
10571
+ `# ${moduleName}/${presetName}
10572
+
10573
+ Describe when this LLMix preset should be used.
10574
+ `
10575
+ );
10576
+ }
10314
10577
  function resolveTarget(file, target) {
10315
10578
  if (target !== "auto") return { ok: true, target };
10316
10579
  const base = file.split(/[\\/]/).pop() ?? file;
@@ -10390,7 +10653,10 @@ function validateArtifact(file, target) {
10390
10653
  return { ok: false, diagnostics: [diag("missing-required-frontmatter", `${target} requires YAML frontmatter`, { path: file })] };
10391
10654
  }
10392
10655
  if (ext.body.trim().length === 0) {
10393
- return { ok: false, diagnostics: [diag("missing-required-body", "AGENTS.md without frontmatter requires a non-empty body", { path: file })] };
10656
+ return {
10657
+ ok: false,
10658
+ diagnostics: [diag("missing-required-body", "AGENTS.md without frontmatter requires a non-empty body", { path: file })]
10659
+ };
10394
10660
  }
10395
10661
  }
10396
10662
  const fm = ext.kind === "ok" ? ext.frontmatter ?? {} : {};
@@ -10404,6 +10670,7 @@ function validateArtifact(file, target) {
10404
10670
  }
10405
10671
  if (isRecord(fm)) {
10406
10672
  diagnostics.push(...checkSignatureDigestEquality(fm));
10673
+ if (target === "source") diagnostics.push(...validateLlmixNamespace(fm));
10407
10674
  }
10408
10675
  return { ok: diagnostics.length === 0, diagnostics };
10409
10676
  }
@@ -10419,10 +10686,12 @@ function validateJsonAgainst(value, key) {
10419
10686
  const ok = validator(value);
10420
10687
  return {
10421
10688
  ok: Boolean(ok),
10422
- diagnostics: ok ? [] : (validator.errors ?? []).slice(0, 5).map((error) => diag("schema-validation-error", `${error.instancePath || "(root)"} ${error.message}`, {
10423
- schema: error.schemaPath,
10424
- instancePath: error.instancePath
10425
- }))
10689
+ diagnostics: ok ? [] : (validator.errors ?? []).slice(0, 5).map(
10690
+ (error) => diag("schema-validation-error", `${error.instancePath || "(root)"} ${error.message}`, {
10691
+ schema: error.schemaPath,
10692
+ instancePath: error.instancePath
10693
+ })
10694
+ )
10426
10695
  };
10427
10696
  }
10428
10697
  function getSchemas() {
@@ -10498,15 +10767,124 @@ function checkSignatureDigestEquality(fm) {
10498
10767
  });
10499
10768
  return diagnostics;
10500
10769
  }
10770
+ var LLMIX_PROVIDERS = /* @__PURE__ */ new Set(["openai", "anthropic", "google", "deepseek", "openrouter", "deepinfra", "novita", "together", "sno-gpu"]);
10771
+ var LLMIX_COMMON_KEYS = /* @__PURE__ */ new Set([
10772
+ "provider",
10773
+ "model",
10774
+ "maxOutputTokens",
10775
+ "temperature",
10776
+ "topP",
10777
+ "topK",
10778
+ "presencePenalty",
10779
+ "frequencyPenalty",
10780
+ "stopSequences",
10781
+ "seed",
10782
+ "maxRetries",
10783
+ "enableThinking",
10784
+ "keepThinkingOutput"
10785
+ ]);
10786
+ var LLMIX_MODULE_NAME = /^(?:_default|[a-z][a-z0-9_]{0,63})$/;
10787
+ var LLMIX_PRESET_NAME = /^(?:_base[a-z0-9_]*|[a-z][a-z0-9_]{0,63})$/;
10788
+ var LLMIX_NAMESPACE_KEYS = /* @__PURE__ */ new Set(["module", "preset", "common", "providerOptions", "caching"]);
10789
+ var LLMIX_CACHING_KEYS = /* @__PURE__ */ new Set(["strategy", "key", "ttl", "maxItems"]);
10790
+ var LLMIX_CACHING_STRATEGIES = /* @__PURE__ */ new Set(["native", "gateway", "disabled", "redis", "redis-or-memory", "memory"]);
10791
+ function validateLlmixNamespace(fm) {
10792
+ const metadata = isRecord(fm.metadata) ? fm.metadata : null;
10793
+ const namespace = metadata && isRecord(metadata["snoai-llmix"]) ? metadata["snoai-llmix"] : null;
10794
+ if (!namespace) return [];
10795
+ const diagnostics = [];
10796
+ for (const key of Object.keys(namespace)) {
10797
+ if (!LLMIX_NAMESPACE_KEYS.has(key))
10798
+ diagnostics.push(diag("llmix.unknown_namespace_key", `metadata.snoai-llmix.${key} is not a supported key`));
10799
+ }
10800
+ const common2 = isRecord(namespace.common) ? namespace.common : null;
10801
+ if (namespace.module !== void 0 && (typeof namespace.module !== "string" || !LLMIX_MODULE_NAME.test(namespace.module))) {
10802
+ diagnostics.push(diag("llmix.invalid_identifier", "metadata.snoai-llmix.module must be _default or a lowercase snake_case identifier"));
10803
+ }
10804
+ if (namespace.preset !== void 0 && (typeof namespace.preset !== "string" || !LLMIX_PRESET_NAME.test(namespace.preset))) {
10805
+ diagnostics.push(diag("llmix.invalid_identifier", "metadata.snoai-llmix.preset must be _base* or a lowercase snake_case identifier"));
10806
+ }
10807
+ if (!common2) {
10808
+ diagnostics.push(diag("llmix.missing_common", "metadata.snoai-llmix.common is required"));
10809
+ } else {
10810
+ for (const key of Object.keys(common2)) {
10811
+ if (!LLMIX_COMMON_KEYS.has(key))
10812
+ diagnostics.push(diag("llmix.unknown_common_key", `metadata.snoai-llmix.common.${key} is not supported`));
10813
+ }
10814
+ if (typeof common2.provider !== "string" || !LLMIX_PROVIDERS.has(common2.provider)) {
10815
+ diagnostics.push(diag("llmix.invalid_provider", "metadata.snoai-llmix.common.provider must be a supported provider"));
10816
+ }
10817
+ if (typeof common2.model !== "string" || common2.model.trim().length === 0) {
10818
+ diagnostics.push(diag("llmix.invalid_model", "metadata.snoai-llmix.common.model must be a non-empty string"));
10819
+ }
10820
+ validateOptionalNumber(common2, "temperature", 0, 2, diagnostics);
10821
+ validateOptionalNumber(common2, "topP", 0, 1, diagnostics);
10822
+ validateOptionalPositiveInteger(common2, "maxOutputTokens", diagnostics);
10823
+ validateOptionalPositiveInteger(common2, "topK", diagnostics);
10824
+ validateOptionalNonNegativeInteger(common2, "maxRetries", diagnostics);
10825
+ }
10826
+ if (namespace.providerOptions !== void 0) {
10827
+ if (!isRecord(namespace.providerOptions))
10828
+ diagnostics.push(diag("llmix.invalid_provider_options", "metadata.snoai-llmix.providerOptions must be an object"));
10829
+ else {
10830
+ for (const [provider, options] of Object.entries(namespace.providerOptions)) {
10831
+ if (!LLMIX_PROVIDERS.has(provider) || !isRecord(options)) {
10832
+ diagnostics.push(
10833
+ diag("llmix.invalid_provider_options", `metadata.snoai-llmix.providerOptions.${provider} must be a supported provider object`)
10834
+ );
10835
+ }
10836
+ }
10837
+ }
10838
+ }
10839
+ if (namespace.caching !== void 0) {
10840
+ if (!isRecord(namespace.caching)) diagnostics.push(diag("llmix.invalid_caching", "metadata.snoai-llmix.caching must be an object"));
10841
+ else {
10842
+ for (const key of Object.keys(namespace.caching)) {
10843
+ if (!LLMIX_CACHING_KEYS.has(key))
10844
+ diagnostics.push(diag("llmix.unknown_caching_key", `metadata.snoai-llmix.caching.${key} is not supported`));
10845
+ }
10846
+ if (typeof namespace.caching.strategy !== "string" || !LLMIX_CACHING_STRATEGIES.has(namespace.caching.strategy)) {
10847
+ diagnostics.push(diag("llmix.invalid_caching", "metadata.snoai-llmix.caching.strategy must be a supported strategy"));
10848
+ }
10849
+ validateOptionalPositiveInteger(namespace.caching, "ttl", diagnostics);
10850
+ validateOptionalPositiveInteger(namespace.caching, "maxItems", diagnostics);
10851
+ }
10852
+ }
10853
+ return diagnostics;
10854
+ }
10855
+ function validateOptionalNumber(record, key, min, max, diagnostics) {
10856
+ if (record[key] === void 0) return;
10857
+ if (typeof record[key] !== "number" || !Number.isFinite(record[key]) || Number(record[key]) < min || Number(record[key]) > max) {
10858
+ diagnostics.push(diag("llmix.invalid_common", `metadata.snoai-llmix.common.${key} must be a number from ${min} to ${max}`));
10859
+ }
10860
+ }
10861
+ function validateOptionalPositiveInteger(record, key, diagnostics) {
10862
+ if (record[key] === void 0) return;
10863
+ if (!Number.isInteger(record[key]) || Number(record[key]) <= 0) {
10864
+ diagnostics.push(diag("llmix.invalid_common", `${key} must be a positive integer`));
10865
+ }
10866
+ }
10867
+ function validateOptionalNonNegativeInteger(record, key, diagnostics) {
10868
+ if (record[key] === void 0) return;
10869
+ if (!Number.isInteger(record[key]) || Number(record[key]) < 0) {
10870
+ diagnostics.push(diag("llmix.invalid_common", `${key} must be a non-negative integer`));
10871
+ }
10872
+ }
10501
10873
  function canonicalizeFromFile(file, target, sidecar) {
10502
10874
  if (target === "MCP-SERVER.md" && !sidecar) {
10503
- return { ok: false, exitCode: EXIT.usage, diagnostics: [diag("missing-required-sidecar", "MCP-SERVER.md canonicalization requires --sidecar <path>")], files: [file] };
10875
+ return {
10876
+ ok: false,
10877
+ exitCode: EXIT.usage,
10878
+ diagnostics: [diag("missing-required-sidecar", "MCP-SERVER.md canonicalization requires --sidecar <path>")],
10879
+ files: [file]
10880
+ };
10504
10881
  }
10505
10882
  const markdown = canonicalizeMarkdownFile(file);
10506
10883
  if (!markdown.ok) return { ok: false, exitCode: EXIT.failure, diagnostics: markdown.diagnostics, files: [file] };
10507
10884
  if (target !== "MCP-SERVER.md") return { ok: true, bytes: markdown.bytes, files: [file] };
10508
10885
  const sidecarRead = readJson(sidecar);
10509
- if (!sidecarRead.ok) return { ok: false, exitCode: EXIT.io, diagnostics: [diag("io-error", sidecarRead.message)], files: [file, sidecar] };
10886
+ if (!sidecarRead.ok)
10887
+ return { ok: false, exitCode: EXIT.io, diagnostics: [diag("io-error", sidecarRead.message)], files: [file, sidecar] };
10510
10888
  return {
10511
10889
  ok: true,
10512
10890
  bytes: Buffer.concat([markdown.bytes, Buffer.from(MCP_BOUNDARY), Buffer.from(jcs(sidecarRead.value))]),
@@ -10553,11 +10931,10 @@ function compileTargets(frontmatter, body, targets, outDir, includeIntegrity) {
10553
10931
  if (includeIntegrity) {
10554
10932
  fm.integrity = {
10555
10933
  algorithm: "sha256",
10556
- digest: computeDigest(Buffer.concat([
10557
- canonicalizeRenderedMarkdown(fm, body),
10558
- Buffer.from(MCP_BOUNDARY),
10559
- Buffer.from(jcs(sidecar))
10560
- ]), "sha256")
10934
+ digest: computeDigest(
10935
+ Buffer.concat([canonicalizeRenderedMarkdown(fm, body), Buffer.from(MCP_BOUNDARY), Buffer.from(jcs(sidecar))]),
10936
+ "sha256"
10937
+ )
10561
10938
  };
10562
10939
  bytes = renderMarkdown(fm, body);
10563
10940
  }
@@ -10602,6 +10979,9 @@ function renderMarkdown(frontmatter, body) {
10602
10979
  ${rendered}---
10603
10980
  ${body}`;
10604
10981
  }
10982
+ function renderArtifact(frontmatter, body) {
10983
+ return renderMarkdown(frontmatter, body);
10984
+ }
10605
10985
  function canonicalizeRenderedMarkdown(frontmatter, body) {
10606
10986
  const fm = { ...frontmatter };
10607
10987
  delete fm.integrity;
@@ -10640,18 +11020,33 @@ function runConformanceSuite(suite, level) {
10640
11020
  }
10641
11021
  const manifest = jsYaml.load(manifestRead.text);
10642
11022
  if (!isRecord(manifest) || !Array.isArray(manifest.fixtures)) {
10643
- return { ok: false, diagnostics: [diag("conformance-manifest-invalid", "manifest.yaml must contain fixtures[]")], passCount: 0, failCount: 1, fixtures: [] };
11023
+ return {
11024
+ ok: false,
11025
+ diagnostics: [diag("conformance-manifest-invalid", "manifest.yaml must contain fixtures[]")],
11026
+ passCount: 0,
11027
+ failCount: 1,
11028
+ fixtures: []
11029
+ };
10644
11030
  }
10645
11031
  const fixtures = [];
11032
+ let compileFixtureCount = 0;
10646
11033
  for (const entry of manifest.fixtures) {
10647
11034
  if (!isRecord(entry) || typeof entry.id !== "string") continue;
10648
11035
  if (entry.verdict === "equal") {
11036
+ compileFixtureCount += 1;
10649
11037
  if (level === "V") continue;
10650
- fixtures.push({ id: entry.id, ok: false, diagnostics: [diag("compile-fixture-unavailable", "No compiler-level fixtures are present yet")] });
11038
+ fixtures.push(runCompileConformanceFixture(suite, entry));
10651
11039
  continue;
10652
11040
  }
10653
11041
  fixtures.push(runConformanceFixture(suite, entry));
10654
11042
  }
11043
+ if (level === "C" && compileFixtureCount === 0) {
11044
+ fixtures.push({
11045
+ id: "level-c-compile-coverage",
11046
+ ok: false,
11047
+ diagnostics: [diag("conformance.compile_fixtures_missing", "Level C requires at least one compile/equality fixture")]
11048
+ });
11049
+ }
10655
11050
  const failures = fixtures.filter((f) => !f.ok);
10656
11051
  return {
10657
11052
  ok: failures.length === 0,
@@ -10661,6 +11056,71 @@ function runConformanceSuite(suite, level) {
10661
11056
  fixtures
10662
11057
  };
10663
11058
  }
11059
+ function runCompileConformanceFixture(suite, entry) {
11060
+ const id = String(entry.id);
11061
+ const input = typeof entry.input === "string" ? resolve(suite, entry.input) : null;
11062
+ const expectedDir = typeof entry.expected_dir === "string" ? resolve(suite, entry.expected_dir) : null;
11063
+ const targetValues = Array.isArray(entry.targets) ? entry.targets.map((value) => normalizeCompileTarget(String(value))) : [];
11064
+ const targets = targetValues.filter((target) => target !== null);
11065
+ const diagnostics = [];
11066
+ if (!input) diagnostics.push(diag("conformance.manifest_invalid", "Compile fixture requires input"));
11067
+ if (!expectedDir) diagnostics.push(diag("conformance.manifest_invalid", "Compile fixture requires expected_dir"));
11068
+ if (targets.length === 0 || targets.length !== targetValues.length) {
11069
+ diagnostics.push(diag("conformance.manifest_invalid", "Compile fixture requires valid targets"));
11070
+ }
11071
+ if (diagnostics.length > 0) return { id, ok: false, diagnostics };
11072
+ const read = readArtifact(input);
11073
+ if (!read.ok) return { id, ok: false, diagnostics: [read.diagnostic] };
11074
+ if (read.extract.kind !== "ok" || !isRecord(read.extract.frontmatter)) {
11075
+ return { id, ok: false, diagnostics: [diag("conformance.compile_input_invalid", "Compile fixture input must be source frontmatter")] };
11076
+ }
11077
+ const validation = validateArtifact(input, "source");
11078
+ if (!validation.ok) {
11079
+ return {
11080
+ id,
11081
+ ok: false,
11082
+ diagnostics: validation.diagnostics.map((diagnostic) => ({ ...diagnostic, code: "conformance.compile_input_invalid" }))
11083
+ };
11084
+ }
11085
+ const outRoot = "__mda_conformance_out__";
11086
+ const staged = compileTargets(read.extract.frontmatter, read.extract.body, targets, outRoot, false);
11087
+ if (!staged.ok) {
11088
+ return {
11089
+ id,
11090
+ ok: false,
11091
+ diagnostics: staged.diagnostics.map((diagnostic) => ({ ...diagnostic, code: "conformance.compile_failed" }))
11092
+ };
11093
+ }
11094
+ const actual = /* @__PURE__ */ new Map();
11095
+ for (const output of staged.outputs) actual.set(relative(outRoot, output.path).replace(/\\/g, "/"), output.bytes);
11096
+ const expectedFiles = listExpectedTree(expectedDir);
11097
+ if (!expectedFiles.ok) return { id, ok: false, diagnostics: [expectedFiles.diagnostic] };
11098
+ const expected = /* @__PURE__ */ new Map();
11099
+ for (const file of expectedFiles.files) {
11100
+ const rel = relative(expectedDir, file).replace(/\\/g, "/");
11101
+ const text = readText(file);
11102
+ if (!text.ok) return { id, ok: false, diagnostics: [diag("conformance.expected_missing", text.message)] };
11103
+ expected.set(rel, text.text);
11104
+ }
11105
+ for (const rel of expected.keys()) {
11106
+ if (!actual.has(rel)) diagnostics.push(diag("conformance.compile_output_missing", `Expected output was not emitted: ${rel}`));
11107
+ }
11108
+ for (const rel of actual.keys()) {
11109
+ if (!expected.has(rel)) diagnostics.push(diag("conformance.compile_output_extra", `Unexpected output was emitted: ${rel}`));
11110
+ }
11111
+ for (const [rel, expectedText] of expected.entries()) {
11112
+ const actualText = actual.get(rel);
11113
+ if (actualText === void 0) continue;
11114
+ const expectedNormalized = normalizeConformanceOutput(rel, expectedText);
11115
+ const actualNormalized = normalizeConformanceOutput(rel, actualText);
11116
+ if (!expectedNormalized.ok) diagnostics.push(expectedNormalized.diagnostic);
11117
+ else if (!actualNormalized.ok) diagnostics.push(actualNormalized.diagnostic);
11118
+ else if (expectedNormalized.bytes !== actualNormalized.bytes) {
11119
+ diagnostics.push(diag("conformance.compile_output_mismatch", `Compiled output differs from expected fixture: ${rel}`));
11120
+ }
11121
+ }
11122
+ return { id, ok: diagnostics.length === 0, diagnostics };
11123
+ }
10664
11124
  function runConformanceFixture(suite, entry) {
10665
11125
  const id = String(entry.id);
10666
11126
  const fixturePath = resolve(suite, String(entry.path));
@@ -10682,7 +11142,8 @@ function runConformanceFixture(suite, entry) {
10682
11142
  if (!read.ok) diagnostics.push(read.diagnostic);
10683
11143
  else {
10684
11144
  const got = read.extract.kind === "error" ? read.extract.code : read.extract.kind === "no-frontmatter" ? "no-frontmatter" : "ok";
10685
- if (extractionExpected && got !== extractionExpected) diagnostics.push(diag("extraction-mismatch", `Expected ${extractionExpected}, got ${got}`));
11145
+ if (extractionExpected && got !== extractionExpected)
11146
+ diagnostics.push(diag("extraction-mismatch", `Expected ${extractionExpected}, got ${got}`));
10686
11147
  if (extractionExpected && got === extractionExpected && schemaPaths.length === 0) {
10687
11148
  return { id, ok: true, diagnostics: [] };
10688
11149
  }
@@ -10722,13 +11183,19 @@ function runTrustedRuntimeConformance(suite, entry) {
10722
11183
  if (!policy.ok) return [diag("trust-policy-violation", policy.message)];
10723
11184
  const policyValidation = validateJsonAgainst(policy.value, "trustPolicy");
10724
11185
  if (!policyValidation.ok) return policyValidation.diagnostics.map((d) => ({ ...d, code: "trust-policy-violation" }));
10725
- const artifact = readArtifact(resolve(suite, String(entry.path)));
10726
- if (!artifact.ok || artifact.extract.kind !== "ok" || !isRecord(artifact.extract.frontmatter)) return [diag("missing-required-integrity", "trusted-runtime requires frontmatter")];
10727
- return checkTrustedRuntimePolicy(artifact.extract.frontmatter, policy.value, Array.isArray(entry["verified-identities"]) ? entry["verified-identities"] : []);
11186
+ const artifact2 = readArtifact(resolve(suite, String(entry.path)));
11187
+ if (!artifact2.ok || artifact2.extract.kind !== "ok" || !isRecord(artifact2.extract.frontmatter))
11188
+ return [diag("missing-required-integrity", "trusted-runtime requires frontmatter")];
11189
+ return checkTrustedRuntimePolicy(
11190
+ artifact2.extract.frontmatter,
11191
+ policy.value,
11192
+ Array.isArray(entry["verified-identities"]) ? entry["verified-identities"] : []
11193
+ );
10728
11194
  }
10729
11195
  function checkTrustedRuntimePolicy(fm, policy, verifiedIdentities) {
10730
11196
  if (!isRecord(fm.integrity)) return [diag("missing-required-integrity", "trusted-runtime requires integrity")];
10731
- if (!Array.isArray(fm.signatures) || fm.signatures.length === 0) return [diag("missing-required-signature", "trusted-runtime requires signatures[]")];
11197
+ if (!Array.isArray(fm.signatures) || fm.signatures.length === 0)
11198
+ return [diag("missing-required-signature", "trusted-runtime requires signatures[]")];
10732
11199
  const digestErrors = checkSignatureDigestEquality(fm);
10733
11200
  if (digestErrors.length) return digestErrors;
10734
11201
  const trusted = /* @__PURE__ */ new Set();
@@ -10740,7 +11207,9 @@ function checkTrustedRuntimePolicy(fm, policy, verifiedIdentities) {
10740
11207
  }
10741
11208
  if (sig.signer.startsWith("sigstore-oidc:")) {
10742
11209
  const issuer = sig.signer.slice("sigstore-oidc:".length);
10743
- const identity = verifiedIdentities.find((item) => isRecord(item) && item.type === "sigstore-oidc" && item["signature-index"] === index);
11210
+ const identity = verifiedIdentities.find(
11211
+ (item) => isRecord(item) && item.type === "sigstore-oidc" && item["signature-index"] === index
11212
+ );
10744
11213
  if (isRecord(identity) && identity.issuer === issuer && policyAllowsSigstore(policy, identity)) {
10745
11214
  trusted.add(`sigstore-oidc:${identity.issuer}
10746
11215
  ${identity.subject}`);
@@ -10749,23 +11218,28 @@ ${identity.subject}`);
10749
11218
  }
10750
11219
  if (trusted.size === 0) return [diag("no-trusted-signature", "no signature matched the trust policy")];
10751
11220
  const minSignatures = isRecord(policy) && Number.isInteger(policy.minSignatures) ? Number(policy.minSignatures) : 1;
10752
- if (trusted.size < minSignatures) return [diag("insufficient-trusted-signatures", `${trusted.size} trusted signer identities < ${minSignatures}`)];
11221
+ if (trusted.size < minSignatures)
11222
+ return [diag("insufficient-trusted-signatures", `${trusted.size} trusted signer identities < ${minSignatures}`)];
10753
11223
  return [];
10754
11224
  }
10755
11225
  function policyAllowsDidWeb(policy, domain) {
10756
11226
  return isRecord(policy) && Array.isArray(policy.trustedSigners) && policy.trustedSigners.some((s) => isRecord(s) && s.type === "did-web" && s.domain === domain);
10757
11227
  }
10758
11228
  function policyAllowsSigstore(policy, identity) {
10759
- return isRecord(policy) && Array.isArray(policy.trustedSigners) && policy.trustedSigners.some((s) => isRecord(s) && s.type === "sigstore-oidc" && s.issuer === identity.issuer && s.subject === identity.subject);
11229
+ return isRecord(policy) && Array.isArray(policy.trustedSigners) && policy.trustedSigners.some(
11230
+ (s) => isRecord(s) && s.type === "sigstore-oidc" && s.issuer === identity.issuer && s.subject === identity.subject
11231
+ );
10760
11232
  }
10761
11233
  function atomicWrite(path, bytes) {
10762
11234
  const destination = resolve(path);
10763
11235
  const dir = dirname(destination);
10764
11236
  mkdirSync(dir, { recursive: true });
10765
- const temp = join(dir, ".mda-tmp");
11237
+ const temp = join(dir, `.mda-${process.pid}-${Date.now()}-${randomBytes(8).toString("hex")}.tmp`);
10766
11238
  let fd = null;
11239
+ let createdTemp = false;
10767
11240
  try {
10768
11241
  fd = openSync(temp, "wx", 384);
11242
+ createdTemp = true;
10769
11243
  writeFileSync(fd, bytes);
10770
11244
  closeSync(fd);
10771
11245
  fd = null;
@@ -10777,7 +11251,32 @@ function atomicWrite(path, bytes) {
10777
11251
  } catch {
10778
11252
  }
10779
11253
  }
10780
- rmSync(temp, { force: true });
11254
+ if (createdTemp) rmSync(temp, { force: true });
11255
+ }
11256
+ }
11257
+ function atomicReplace(path, bytes) {
11258
+ const destination = resolve(path);
11259
+ const dir = dirname(destination);
11260
+ mkdirSync(dir, { recursive: true });
11261
+ const temp = join(dir, `.mda-${process.pid}-${Date.now()}-${randomBytes(8).toString("hex")}.tmp`);
11262
+ let fd = null;
11263
+ let createdTemp = false;
11264
+ try {
11265
+ fd = openSync(temp, "wx", 384);
11266
+ createdTemp = true;
11267
+ writeFileSync(fd, bytes);
11268
+ closeSync(fd);
11269
+ fd = null;
11270
+ renameSync(temp, destination);
11271
+ createdTemp = false;
11272
+ } finally {
11273
+ if (fd !== null) {
11274
+ try {
11275
+ closeSync(fd);
11276
+ } catch {
11277
+ }
11278
+ }
11279
+ if (createdTemp) rmSync(temp, { force: true });
10781
11280
  }
10782
11281
  }
10783
11282
  function readText(path) {
@@ -10787,6 +11286,47 @@ function readText(path) {
10787
11286
  return { ok: false, message: error instanceof Error ? error.message : String(error) };
10788
11287
  }
10789
11288
  }
11289
+ function listExpectedTree(root) {
11290
+ const files = [];
11291
+ const walk2 = (dir) => {
11292
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
11293
+ const path = join(dir, entry.name);
11294
+ if (entry.isDirectory()) walk2(path);
11295
+ else if (entry.isFile()) files.push(path);
11296
+ }
11297
+ };
11298
+ try {
11299
+ walk2(root);
11300
+ return { ok: true, files: files.sort() };
11301
+ } catch (error) {
11302
+ return {
11303
+ ok: false,
11304
+ diagnostic: diag("conformance.expected_missing", error instanceof Error ? error.message : String(error), { path: root })
11305
+ };
11306
+ }
11307
+ }
11308
+ function normalizeConformanceOutput(path, text) {
11309
+ if (path.endsWith(".json")) {
11310
+ try {
11311
+ return { ok: true, bytes: jcs(JSON.parse(text)) };
11312
+ } catch (error) {
11313
+ return {
11314
+ ok: false,
11315
+ diagnostic: diag("conformance.expected_invalid_json", error instanceof Error ? error.message : String(error), { path })
11316
+ };
11317
+ }
11318
+ }
11319
+ if (path.endsWith(".md") || path.endsWith(".mda")) {
11320
+ const extract = extractFrontmatterStrict(Buffer.from(text));
11321
+ if (extract.kind === "error") return { ok: false, diagnostic: diag(extract.code, extract.message, { path }) };
11322
+ if (extract.kind === "no-frontmatter") return { ok: true, bytes: normalizeCanonicalBody(extract.body) };
11323
+ return { ok: true, bytes: `---
11324
+ ${jcs(extract.frontmatter)}
11325
+ ---
11326
+ ${normalizeCanonicalBody(extract.body.replace(/^\n+/, ""))}` };
11327
+ }
11328
+ return { ok: true, bytes: text.replace(/\r\n/g, "\n").replace(/\r/g, "\n") };
11329
+ }
10790
11330
  function readJson(path) {
10791
11331
  try {
10792
11332
  return { ok: true, value: JSON.parse(readFileSync(path, "utf8")) };
@@ -10826,117 +11366,31 @@ function isRecord(value) {
10826
11366
  return typeof value === "object" && value !== null && !Array.isArray(value);
10827
11367
  }
10828
11368
 
10829
- // src/cli.ts
10830
- var HELP = `Markdown AI CLI (@markdown-ai/cli)
10831
-
10832
- Usage:
10833
- mda
10834
- mda --help
10835
- mda init <name> [--out <file>] [--json]
10836
- mda validate <file> [--target source|SKILL.md|AGENTS.md|MCP-SERVER.md|auto] [--json]
10837
- mda compile <file.mda> --target <target...> [--out-dir <dir>] [--integrity] [--json]
10838
- mda canonicalize <file> [--target source|SKILL.md|AGENTS.md|MCP-SERVER.md|auto] [--sidecar <path>] [--json]
10839
- mda integrity compute <file> [--target source|SKILL.md|AGENTS.md|MCP-SERVER.md|auto] [--sidecar <path>] [--algorithm sha256|sha384|sha512] [--json]
10840
- mda integrity verify <file> [--target source|SKILL.md|AGENTS.md|MCP-SERVER.md|auto] [--sidecar <path>] [--json]
10841
- mda sign <file> --method did-web --key <path> --identity <domain> (--out <file>|--in-place) [--json]
10842
- mda verify <file> --policy <path> [--target source|SKILL.md|AGENTS.md|MCP-SERVER.md|auto] [--sidecar <path>] [--json]
10843
- mda conformance [--suite <path>] [--level V|C] [--json]
10844
-
10845
- Global flags:
10846
- --json Print stable JSON only on stdout.
10847
- --quiet Suppress non-essential human output.
10848
- --verbose Include extra diagnostic context where available.
10849
- --no-color Disable ANSI color.
10850
- -h, --help Print this full help.
10851
-
10852
- Commands and options:
10853
- init <name>
10854
- --out <file> Write the scaffold atomically. Refuses overwrite.
10855
- --json Return scaffold in JSON instead of raw .mda text.
10856
-
10857
- validate <file>
10858
- --target <target> source, SKILL.md, AGENTS.md, MCP-SERVER.md, or auto. Default: auto.
10859
-
10860
- compile <file.mda>
10861
- --target <target...> Required. One or more of SKILL.md, AGENTS.md, MCP-SERVER.md.
10862
- --out-dir <dir> Output directory. Default: current working directory.
10863
- --integrity Add sha256 integrity to emitted artifacts.
10864
-
10865
- canonicalize <file>
10866
- --target <target> Default: auto.
10867
- --sidecar <path> Required only for MCP-SERVER.md multi-file canonical bytes.
10868
-
10869
- integrity compute <file>
10870
- --target <target> Default: auto.
10871
- --sidecar <path> Required only for MCP-SERVER.md.
10872
- --algorithm <name> sha256, sha384, or sha512. Default: sha256.
10873
-
10874
- integrity verify <file>
10875
- --target <target> Default: auto.
10876
- --sidecar <path> Required only for MCP-SERVER.md.
10877
-
10878
- verify <file>
10879
- --policy <path> Required trust policy JSON.
10880
- --target <target> Default: auto.
10881
- --sidecar <path> Required only for MCP-SERVER.md.
10882
- --offline Unsupported in this MVP; exits with usage error.
10883
-
10884
- sign <file>
10885
- --method did-web Required. Signing is not yet stable in this MVP.
10886
- --key <path> Required.
10887
- --identity <domain> Required.
10888
- --out <file> Write signed output.
10889
- --in-place Replace the input file.
10890
-
10891
- Examples:
10892
- mda init hello-skill --out hello.mda
10893
- mda validate hello.mda --json
10894
- mda compile hello.mda --target SKILL.md AGENTS.md MCP-SERVER.md --out-dir out --integrity
10895
- mda canonicalize out/SKILL.md --target SKILL.md --json
10896
- mda integrity compute out/SKILL.md --target SKILL.md --algorithm sha256 --json
10897
- mda integrity verify out/SKILL.md --target SKILL.md
10898
- mda verify signed.md --policy policy.json --json
10899
- mda conformance --suite conformance --level V --json
11369
+ // src/cli/constants.ts
11370
+ var DIGEST_ALGORITHMS = /* @__PURE__ */ new Set(["sha256", "sha384", "sha512"]);
11371
+ var CLI_VERSION = "1.1.0";
11372
+ var LLMIX_PROVIDERS2 = /* @__PURE__ */ new Set([
11373
+ "openai",
11374
+ "anthropic",
11375
+ "google",
11376
+ "deepseek",
11377
+ "openrouter",
11378
+ "deepinfra",
11379
+ "novita",
11380
+ "together",
11381
+ "sno-gpu"
11382
+ ]);
11383
+ var LLMIX_MODULE_NAME2 = /^(?:_default|[a-z][a-z0-9_]{0,63})$/;
11384
+ var LLMIX_PRESET_NAME2 = /^(?:_base[a-z0-9_]*|[a-z][a-z0-9_]{0,63})$/;
11385
+ var INTEGRITY_PAYLOAD_TYPE = "application/vnd.mda.integrity+json";
11386
+ var GITHUB_ACTIONS_ISSUER = "https://token.actions.githubusercontent.com";
11387
+ var GITHUB_REPOSITORY = /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/;
11388
+ var GITHUB_REF = /^refs\/(?:heads|tags)\/\S+$/;
11389
+ var SIGSTORE_REKOR_URL = "https://rekor.sigstore.dev";
11390
+ var DIGEST_PATTERN = /^sha(?:256|384|512):[a-f0-9]+$/;
11391
+ var LLMIX_SNIPPET_FORMATS = /* @__PURE__ */ new Set(["json", "env", "kubernetes", "github-actions", "terraform", "typescript", "python", "rust"]);
10900
11392
 
10901
- Exit codes:
10902
- 0 Success.
10903
- 1 Valid command, but artifact validation or verification failed.
10904
- 2 CLI usage error: missing argument, unknown flag, ambiguous target.
10905
- 3 IO or configuration error: missing file, overwrite refusal, unreadable policy.
10906
- 4 Internal bug or invariant failure.
10907
- `;
10908
- var DIGEST_ALGORITHMS = /* @__PURE__ */ new Set(["sha256", "sha384", "sha512"]);
10909
- async function main() {
10910
- const { globals, args } = splitGlobals(process.argv.slice(2));
10911
- try {
10912
- if (args.length === 0 || args.includes("--help") || args.includes("-h")) {
10913
- process.stdout.write(HELP);
10914
- process.exit(EXIT.ok);
10915
- }
10916
- const command = args[0];
10917
- const rest = args.slice(1);
10918
- const result = await runCommand(command, rest, globals);
10919
- writeResult(result, globals);
10920
- process.exit(result.exitCode);
10921
- } catch (error) {
10922
- const result = commandResult(false, "internal", EXIT.internal, [
10923
- diag("internal-error", error instanceof Error ? error.message : String(error))
10924
- ]);
10925
- writeResult(result, globals);
10926
- process.exit(result.exitCode);
10927
- }
10928
- }
10929
- async function runCommand(command, args, globals) {
10930
- if (command === "init") return runInit(args, globals);
10931
- if (command === "validate") return runValidate(args);
10932
- if (command === "compile") return runCompile(args);
10933
- if (command === "canonicalize") return runCanonicalize(args, globals);
10934
- if (command === "integrity") return runIntegrity(args);
10935
- if (command === "verify") return runVerify(args);
10936
- if (command === "sign") return runSign(args);
10937
- if (command === "conformance") return runConformance(args);
10938
- return usage("root", `Unknown command: ${command}`);
10939
- }
11393
+ // src/cli/shared.ts
10940
11394
  function writeResult(result, globals) {
10941
11395
  if (globals.json) {
10942
11396
  process.stdout.write(`${JSON.stringify(result, null, 2)}
@@ -10950,25 +11404,83 @@ function writeResult(result, globals) {
10950
11404
  `);
10951
11405
  else process.stdout.write(`ok: ${result.command}
10952
11406
  `);
11407
+ writeNextActions(result, globals);
10953
11408
  return;
10954
11409
  }
10955
11410
  for (const d of result.diagnostics) {
10956
11411
  process.stderr.write(`${d.code}: ${d.message}
10957
11412
  `);
10958
11413
  }
11414
+ writeNextActions(result, globals, process.stderr);
10959
11415
  }
10960
11416
  function splitGlobals(argv) {
10961
- const globals = { json: false, quiet: false, verbose: false, color: true };
11417
+ const globals = { json: false, quiet: false, verbose: false, color: true, noNext: false };
10962
11418
  const args = [];
10963
11419
  for (const arg of argv) {
10964
11420
  if (arg === "--json") globals.json = true;
10965
11421
  else if (arg === "--quiet") globals.quiet = true;
10966
11422
  else if (arg === "--verbose") globals.verbose = true;
10967
11423
  else if (arg === "--no-color") globals.color = false;
11424
+ else if (arg === "--no-next") globals.noNext = true;
10968
11425
  else args.push(arg);
10969
11426
  }
10970
11427
  return { globals, args };
10971
11428
  }
11429
+ function writeNextActions(result, globals, stream = process.stdout) {
11430
+ if (globals.noNext || result.nextActions.length === 0) return;
11431
+ stream.write("Next:\n");
11432
+ for (const action of result.nextActions) {
11433
+ const marker = action.required ? "-" : "- optional:";
11434
+ if (action.command) stream.write(`${marker} ${action.reason}: ${action.command}
11435
+ `);
11436
+ else if (action.external) stream.write(`${marker} ${action.reason}: ${action.external}
11437
+ `);
11438
+ else stream.write(`${marker} ${action.reason}
11439
+ `);
11440
+ }
11441
+ }
11442
+ function nextAction(id, reason, command, required = true) {
11443
+ return { id, required, reason, command };
11444
+ }
11445
+ function externalNextAction(id, reason, external, required = true) {
11446
+ return { id, required, reason, external };
11447
+ }
11448
+ function artifact(kind, path, target, digest) {
11449
+ return { kind, path, target, digest };
11450
+ }
11451
+ function nextAfterValidate(file, target) {
11452
+ if (target === "source") {
11453
+ return [
11454
+ nextAction(
11455
+ "compile-source",
11456
+ "Compile the source into runtime Markdown",
11457
+ `mda compile ${file} --target SKILL.md AGENTS.md --out-dir out --integrity`
11458
+ )
11459
+ ];
11460
+ }
11461
+ return [nextAction("verify-integrity", "Verify declared integrity before use", `mda integrity verify ${file} --target ${target}`, false)];
11462
+ }
11463
+ function nextAfterCompile(paths) {
11464
+ const markdown = paths.find((path) => path.endsWith("SKILL.md") || path.endsWith("AGENTS.md") || path.endsWith("MCP-SERVER.md"));
11465
+ if (!markdown) return [];
11466
+ const target = targetForPath(markdown);
11467
+ if (!target) return [];
11468
+ return [
11469
+ nextAction("validate-output", "Validate the first compiled output", `mda validate ${markdown} --target ${target}`),
11470
+ nextAction(
11471
+ "verify-output-integrity",
11472
+ "Verify compiled output integrity before publishing",
11473
+ `mda integrity verify ${markdown} --target ${target}`,
11474
+ false
11475
+ )
11476
+ ];
11477
+ }
11478
+ function targetForPath(path) {
11479
+ if (path.endsWith("SKILL.md")) return "SKILL.md";
11480
+ if (path.endsWith("AGENTS.md")) return "AGENTS.md";
11481
+ if (path.endsWith("MCP-SERVER.md")) return "MCP-SERVER.md";
11482
+ return void 0;
11483
+ }
10972
11484
  function parseOptions(args) {
10973
11485
  const positional = [];
10974
11486
  const options = /* @__PURE__ */ new Map();
@@ -10979,7 +11491,47 @@ function parseOptions(args) {
10979
11491
  positional.push(arg);
10980
11492
  continue;
10981
11493
  }
10982
- if (["--out", "--target", "--out-dir", "--sidecar", "--algorithm", "--suite", "--level", "--policy", "--method", "--key", "--identity"].includes(arg)) {
11494
+ if ([
11495
+ "--out",
11496
+ "--target",
11497
+ "--out-dir",
11498
+ "--sidecar",
11499
+ "--algorithm",
11500
+ "--suite",
11501
+ "--level",
11502
+ "--policy",
11503
+ "--did-document",
11504
+ "--profile",
11505
+ "--did",
11506
+ "--key-id",
11507
+ "--key-file",
11508
+ "--method",
11509
+ "--key",
11510
+ "--identity",
11511
+ "--template",
11512
+ "--module",
11513
+ "--preset",
11514
+ "--provider",
11515
+ "--model",
11516
+ "--domain",
11517
+ "--min-signatures",
11518
+ "--repo",
11519
+ "--workflow",
11520
+ "--ref",
11521
+ "--offline-sigstore-fixture",
11522
+ "--source",
11523
+ "--registry-dir",
11524
+ "--registry-root",
11525
+ "--release-plan",
11526
+ "--manifest",
11527
+ "--format",
11528
+ "--snippet-format",
11529
+ "--snippet-out",
11530
+ "--expected-root-digest",
11531
+ "--minimum-revision",
11532
+ "--minimum-published-at",
11533
+ "--high-watermark"
11534
+ ].includes(arg)) {
10983
11535
  const values = [];
10984
11536
  i += 1;
10985
11537
  while (i < args.length && !args[i].startsWith("--")) {
@@ -10992,7 +11544,7 @@ function parseOptions(args) {
10992
11544
  options.set(arg, [...options.get(arg) ?? [], ...values]);
10993
11545
  continue;
10994
11546
  }
10995
- if (["--integrity", "--in-place", "--offline"].includes(arg)) {
11547
+ if (["--integrity", "--in-place", "--offline", "--write", "--rekor", "--derive-root-digest", "--strict-compat"].includes(arg)) {
10996
11548
  flags.add(arg);
10997
11549
  continue;
10998
11550
  }
@@ -11015,10 +11567,15 @@ function unknownOptions(parsed, allowed) {
11015
11567
  }
11016
11568
  return null;
11017
11569
  }
11570
+
11571
+ // src/cli/core-commands.ts
11018
11572
  function runInit(args, globals) {
11019
11573
  const parsed = parseOptions(args);
11020
- const err = unknownOptions(parsed, ["--out"]);
11574
+ const err = unknownOptions(parsed, ["--out", "--template", "--module", "--preset", "--provider", "--model"]);
11021
11575
  if (err) return usage("init", err);
11576
+ const template = oneOption(parsed.options, "--template");
11577
+ if (template && template !== "llmix-preset") return usage("init", `Unsupported template: ${template}`);
11578
+ if (template === "llmix-preset") return runInitLlmixPreset(parsed, globals);
11022
11579
  if (parsed.positional.length !== 1) return usage("init", "Expected exactly one name: mda init <name>");
11023
11580
  const name = parsed.positional[0];
11024
11581
  const scaffold = makeScaffold(name);
@@ -11039,15 +11596,115 @@ function runInit(args, globals) {
11039
11596
  }
11040
11597
  }
11041
11598
  if (globals.json) {
11042
- return commandResult(true, "init", EXIT.ok, [], { name, scaffold, out, written: Boolean(out) });
11599
+ return commandResult(true, "init", EXIT.ok, [], {
11600
+ summary: out ? `Created MDA source at ${out}` : "Generated MDA source scaffold",
11601
+ artifacts: out ? [artifact("mda-source", out, "source")] : [],
11602
+ nextActions: out ? [nextAction("validate-source", "Validate the source file", `mda validate ${out} --target source`)] : [externalNextAction("save-source", "Save this scaffold to a .mda file", "write the scaffold bytes to disk")],
11603
+ name,
11604
+ scaffold,
11605
+ out,
11606
+ written: Boolean(out)
11607
+ });
11043
11608
  }
11044
11609
  return commandResult(true, "init", EXIT.ok, [], {
11610
+ summary: out ? `Created MDA source at ${out}` : "Generated MDA source scaffold",
11611
+ artifacts: out ? [artifact("mda-source", out, "source")] : [],
11612
+ nextActions: out ? [nextAction("validate-source", "Validate the source file", `mda validate ${out} --target source`)] : [],
11045
11613
  message: out ? `wrote ${out}` : scaffold,
11046
11614
  name,
11047
11615
  out,
11048
11616
  written: Boolean(out)
11049
11617
  });
11050
11618
  }
11619
+ function runInitLlmixPreset(parsed, globals) {
11620
+ if (parsed.positional.length !== 0) return usage("init", "LLMix preset init does not take a positional name");
11621
+ const moduleName = oneOption(parsed.options, "--module");
11622
+ const presetName = oneOption(parsed.options, "--preset");
11623
+ const provider = oneOption(parsed.options, "--provider");
11624
+ const model = oneOption(parsed.options, "--model");
11625
+ const out = oneOption(parsed.options, "--out");
11626
+ const diagnostics = [];
11627
+ if (!moduleName || !LLMIX_MODULE_NAME2.test(moduleName))
11628
+ diagnostics.push(diag("llmix.invalid_identifier", "--module must be _default or a lowercase snake_case identifier"));
11629
+ if (!presetName || !LLMIX_PRESET_NAME2.test(presetName))
11630
+ diagnostics.push(diag("llmix.invalid_identifier", "--preset must be _base* or a lowercase snake_case identifier"));
11631
+ if (!provider || !LLMIX_PROVIDERS2.has(provider))
11632
+ diagnostics.push(diag("llmix.invalid_provider", "--provider must be a supported LLMix provider"));
11633
+ if (!model || model.trim().length === 0)
11634
+ diagnostics.push(diag("llmix.invalid_model", "--model must be a non-empty provider model identifier"));
11635
+ if (diagnostics.length > 0) {
11636
+ return commandResult(false, "init", EXIT.usage, diagnostics, {
11637
+ summary: "LLMix preset scaffold options are invalid",
11638
+ nextActions: [
11639
+ nextAction(
11640
+ "fix-llmix-init",
11641
+ "Fix the LLMix preset options and retry",
11642
+ "mda init --template llmix-preset --module search_summary --preset openai_fast --provider openai --model gpt-5-mini --out search_summary/openai_fast.mda"
11643
+ )
11644
+ ]
11645
+ });
11646
+ }
11647
+ const scaffold = makeLlmixPresetScaffold(moduleName, presetName, provider, model);
11648
+ if (out) {
11649
+ if (existsSync2(out)) {
11650
+ return ioError("init", `Refusing to overwrite existing file: ${out}`, {
11651
+ module: moduleName,
11652
+ preset: presetName,
11653
+ scaffold: globals.json ? scaffold : void 0,
11654
+ out,
11655
+ written: false
11656
+ });
11657
+ }
11658
+ try {
11659
+ atomicWrite(out, scaffold);
11660
+ } catch (error) {
11661
+ return ioError("init", error instanceof Error ? error.message : String(error), {
11662
+ module: moduleName,
11663
+ preset: presetName,
11664
+ out,
11665
+ written: false
11666
+ });
11667
+ }
11668
+ }
11669
+ const nextActions = out ? [
11670
+ nextAction("validate-llmix-source", "Validate the LLMix source file", `mda validate ${out} --target source`),
11671
+ nextAction("write-integrity", "Record integrity before release", `mda integrity compute ${out} --target source --write`, false)
11672
+ ] : [
11673
+ externalNextAction(
11674
+ "save-llmix-source",
11675
+ "Save this scaffold to a .mda file under the module directory",
11676
+ "write the scaffold bytes to disk"
11677
+ )
11678
+ ];
11679
+ if (globals.json) {
11680
+ return commandResult(true, "init", EXIT.ok, [], {
11681
+ summary: out ? `Created LLMix preset source at ${out}` : "Generated LLMix preset scaffold",
11682
+ artifacts: out ? [artifact("mda-source", out, "source")] : [],
11683
+ nextActions,
11684
+ template: "llmix-preset",
11685
+ module: moduleName,
11686
+ preset: presetName,
11687
+ provider,
11688
+ model,
11689
+ scaffold,
11690
+ out,
11691
+ written: Boolean(out)
11692
+ });
11693
+ }
11694
+ return commandResult(true, "init", EXIT.ok, [], {
11695
+ summary: out ? `Created LLMix preset source at ${out}` : "Generated LLMix preset scaffold",
11696
+ artifacts: out ? [artifact("mda-source", out, "source")] : [],
11697
+ nextActions: out ? nextActions : [],
11698
+ message: out ? `wrote ${out}` : scaffold,
11699
+ template: "llmix-preset",
11700
+ module: moduleName,
11701
+ preset: presetName,
11702
+ provider,
11703
+ model,
11704
+ out,
11705
+ written: Boolean(out)
11706
+ });
11707
+ }
11051
11708
  function runValidate(args) {
11052
11709
  const parsed = parseOptions(args);
11053
11710
  const err = unknownOptions(parsed, ["--target"]);
@@ -11060,6 +11717,15 @@ function runValidate(args) {
11060
11717
  if (!targetResult.ok) return targetResult.result("validate", file);
11061
11718
  const validation = validateArtifact(file, targetResult.target);
11062
11719
  return commandResult(validation.ok, "validate", validation.ok ? EXIT.ok : EXIT.failure, validation.diagnostics, {
11720
+ summary: validation.ok ? `${targetResult.target} validation passed` : `${targetResult.target} validation failed`,
11721
+ artifacts: [artifact("validated-artifact", file, targetResult.target)],
11722
+ nextActions: validation.ok ? nextAfterValidate(file, targetResult.target) : [
11723
+ nextAction(
11724
+ "fix-validation",
11725
+ "Fix the reported diagnostics and re-run validation",
11726
+ `mda validate ${file} --target ${targetResult.target}`
11727
+ )
11728
+ ],
11063
11729
  file,
11064
11730
  target: targetResult.target
11065
11731
  });
@@ -11077,13 +11743,35 @@ function runCanonicalize(args, globals) {
11077
11743
  const sidecar = oneOption(parsed.options, "--sidecar");
11078
11744
  const can = canonicalizeFromFile(file, targetResult.target, sidecar);
11079
11745
  if (!can.ok) {
11080
- return commandResult(false, "canonicalize", can.exitCode, can.diagnostics, { file, target: targetResult.target, files: can.files });
11746
+ return commandResult(false, "canonicalize", can.exitCode, can.diagnostics, {
11747
+ summary: "Canonicalization failed",
11748
+ nextActions: [
11749
+ nextAction(
11750
+ "fix-canonicalization",
11751
+ "Fix the reported diagnostics and retry",
11752
+ `mda canonicalize ${file} --target ${targetResult.target}`
11753
+ )
11754
+ ],
11755
+ file,
11756
+ target: targetResult.target,
11757
+ files: can.files
11758
+ });
11081
11759
  }
11082
11760
  if (!globals.json) {
11083
11761
  process.stdout.write(can.bytes);
11084
11762
  return commandResult(true, "canonicalize", EXIT.ok, [], { suppressOutput: true });
11085
11763
  }
11086
11764
  return commandResult(true, "canonicalize", EXIT.ok, [], {
11765
+ summary: "Canonical bytes generated",
11766
+ artifacts: [artifact("canonical-bytes", file, targetResult.target)],
11767
+ nextActions: [
11768
+ nextAction(
11769
+ "compute-integrity",
11770
+ "Compute an integrity digest for these bytes",
11771
+ `mda integrity compute ${file} --target ${targetResult.target}`,
11772
+ false
11773
+ )
11774
+ ],
11087
11775
  file,
11088
11776
  target: targetResult.target,
11089
11777
  files: can.files,
@@ -11095,7 +11783,7 @@ function runIntegrity(args) {
11095
11783
  const sub = args[0];
11096
11784
  if (sub !== "compute" && sub !== "verify") return usage("integrity", "Expected subcommand: integrity compute|verify");
11097
11785
  const parsed = parseOptions(args.slice(1));
11098
- const err = unknownOptions(parsed, ["--target", "--sidecar", "--algorithm"]);
11786
+ const err = unknownOptions(parsed, ["--target", "--sidecar", "--algorithm", "--write"]);
11099
11787
  if (err) return usage(`integrity ${sub}`, err);
11100
11788
  if (parsed.positional.length !== 1) return usage(`integrity ${sub}`, `Expected one file: mda integrity ${sub} <file>`);
11101
11789
  const file = parsed.positional[0];
@@ -11105,12 +11793,125 @@ function runIntegrity(args) {
11105
11793
  if (!targetResult.ok) return targetResult.result(`integrity ${sub}`, file);
11106
11794
  const sidecar = oneOption(parsed.options, "--sidecar");
11107
11795
  const can = canonicalizeFromFile(file, targetResult.target, sidecar);
11108
- if (!can.ok) return commandResult(false, `integrity ${sub}`, can.exitCode, can.diagnostics, { file, target: targetResult.target, files: can.files });
11796
+ if (!can.ok)
11797
+ return commandResult(false, `integrity ${sub}`, can.exitCode, can.diagnostics, {
11798
+ summary: `integrity ${sub} failed`,
11799
+ nextActions: [
11800
+ nextAction(
11801
+ "fix-integrity-input",
11802
+ "Fix the reported diagnostics and retry",
11803
+ `mda integrity ${sub} ${file} --target ${targetResult.target}`
11804
+ )
11805
+ ],
11806
+ file,
11807
+ target: targetResult.target,
11808
+ files: can.files
11809
+ });
11109
11810
  if (sub === "compute") {
11811
+ if (parsed.flags.has("--write") && parsed.options.has("--sidecar") && targetResult.target !== "MCP-SERVER.md") {
11812
+ return usage("integrity compute", "--sidecar is only valid with MCP-SERVER.md");
11813
+ }
11110
11814
  const algorithm = oneOption(parsed.options, "--algorithm") ?? "sha256";
11111
11815
  if (!DIGEST_ALGORITHMS.has(algorithm)) return usage("integrity compute", `Unsupported algorithm: ${algorithm}`);
11112
11816
  const digest = computeDigest(can.bytes, algorithm);
11817
+ if (parsed.flags.has("--write")) {
11818
+ const ext2 = readArtifact(file);
11819
+ if (!ext2.ok || ext2.extract.kind !== "ok" || !isRecord(ext2.extract.frontmatter)) {
11820
+ return commandResult(
11821
+ false,
11822
+ "integrity compute",
11823
+ EXIT.failure,
11824
+ [diag("missing-required-frontmatter", "Integrity write requires frontmatter")],
11825
+ {
11826
+ summary: "Integrity write failed",
11827
+ nextActions: [
11828
+ nextAction(
11829
+ "fix-frontmatter",
11830
+ "Add frontmatter and retry integrity write",
11831
+ `mda validate ${file} --target ${targetResult.target}`
11832
+ )
11833
+ ],
11834
+ file,
11835
+ target: targetResult.target,
11836
+ files: can.files
11837
+ }
11838
+ );
11839
+ }
11840
+ const existing = ext2.extract.frontmatter.integrity;
11841
+ if (isRecord(existing) && (existing.algorithm !== algorithm || existing.digest !== digest)) {
11842
+ return commandResult(
11843
+ false,
11844
+ "integrity compute",
11845
+ EXIT.failure,
11846
+ [
11847
+ diag(
11848
+ "integrity.existing_mismatch",
11849
+ "Existing integrity differs from the computed digest; remove it intentionally before rewriting"
11850
+ )
11851
+ ],
11852
+ {
11853
+ summary: "Existing integrity does not match computed digest",
11854
+ nextActions: [
11855
+ nextAction(
11856
+ "inspect-integrity",
11857
+ "Inspect or remove the stale integrity field before retrying",
11858
+ `mda integrity verify ${file} --target ${targetResult.target}`
11859
+ )
11860
+ ],
11861
+ file,
11862
+ target: targetResult.target,
11863
+ files: can.files,
11864
+ algorithm,
11865
+ digest,
11866
+ written: false
11867
+ }
11868
+ );
11869
+ }
11870
+ let written = false;
11871
+ if (!isRecord(existing)) {
11872
+ const frontmatter = { ...ext2.extract.frontmatter, integrity: { algorithm, digest } };
11873
+ try {
11874
+ atomicReplace(file, renderArtifact(frontmatter, ext2.extract.body));
11875
+ written = true;
11876
+ } catch (error) {
11877
+ return ioError("integrity compute", error instanceof Error ? error.message : String(error), {
11878
+ file,
11879
+ target: targetResult.target,
11880
+ algorithm,
11881
+ digest,
11882
+ written: false
11883
+ });
11884
+ }
11885
+ }
11886
+ return commandResult(true, "integrity compute", EXIT.ok, [], {
11887
+ summary: written ? `Wrote ${algorithm} integrity` : "Integrity already matches",
11888
+ artifacts: [artifact("integrity-updated", file, targetResult.target, digest)],
11889
+ nextActions: [
11890
+ nextAction(
11891
+ "verify-integrity",
11892
+ "Verify the recorded integrity before release",
11893
+ `mda integrity verify ${file} --target ${targetResult.target}`
11894
+ )
11895
+ ],
11896
+ message: written ? `wrote integrity to ${file}` : "integrity already matches",
11897
+ file,
11898
+ target: targetResult.target,
11899
+ files: can.files,
11900
+ algorithm,
11901
+ digest,
11902
+ written
11903
+ });
11904
+ }
11113
11905
  return commandResult(true, "integrity compute", EXIT.ok, [], {
11906
+ summary: `Computed ${algorithm} digest`,
11907
+ artifacts: [artifact("canonical-digest", file, targetResult.target, digest)],
11908
+ nextActions: [
11909
+ externalNextAction(
11910
+ "record-integrity",
11911
+ "Record this digest in the artifact integrity field",
11912
+ "update frontmatter.integrity before publishing"
11913
+ )
11914
+ ],
11114
11915
  message: digest,
11115
11916
  file,
11116
11917
  target: targetResult.target,
@@ -11121,46 +11922,100 @@ function runIntegrity(args) {
11121
11922
  }
11122
11923
  const ext = readArtifact(file);
11123
11924
  if (!ext.ok || ext.extract.kind !== "ok" || !isRecord(ext.extract.frontmatter)) {
11124
- return commandResult(false, "integrity verify", EXIT.failure, [diag("missing-required-frontmatter", "Integrity verification requires frontmatter")], {
11125
- file,
11126
- target: targetResult.target,
11127
- files: can.files
11128
- });
11925
+ return commandResult(
11926
+ false,
11927
+ "integrity verify",
11928
+ EXIT.failure,
11929
+ [diag("missing-required-frontmatter", "Integrity verification requires frontmatter")],
11930
+ {
11931
+ file,
11932
+ target: targetResult.target,
11933
+ files: can.files
11934
+ }
11935
+ );
11129
11936
  }
11130
11937
  const integrity = ext.extract.frontmatter.integrity;
11131
11938
  if (!isRecord(integrity) || typeof integrity.algorithm !== "string" || typeof integrity.digest !== "string") {
11132
- return commandResult(false, "integrity verify", EXIT.failure, [diag("missing-required-integrity", "Artifact has no declared integrity")], {
11133
- file,
11134
- target: targetResult.target,
11135
- files: can.files
11136
- });
11939
+ return commandResult(
11940
+ false,
11941
+ "integrity verify",
11942
+ EXIT.failure,
11943
+ [diag("missing-required-integrity", "Artifact has no declared integrity")],
11944
+ {
11945
+ file,
11946
+ target: targetResult.target,
11947
+ files: can.files
11948
+ }
11949
+ );
11137
11950
  }
11138
11951
  if (!DIGEST_ALGORITHMS.has(integrity.algorithm)) {
11139
- return commandResult(false, "integrity verify", EXIT.failure, [diag("unsupported-integrity-algorithm", `Unsupported integrity algorithm: ${integrity.algorithm}`)], {
11140
- file,
11141
- target: targetResult.target,
11142
- files: can.files,
11143
- algorithm: integrity.algorithm
11144
- });
11952
+ return commandResult(
11953
+ false,
11954
+ "integrity verify",
11955
+ EXIT.failure,
11956
+ [diag("unsupported-integrity-algorithm", `Unsupported integrity algorithm: ${integrity.algorithm}`)],
11957
+ {
11958
+ file,
11959
+ target: targetResult.target,
11960
+ files: can.files,
11961
+ algorithm: integrity.algorithm
11962
+ }
11963
+ );
11145
11964
  }
11146
11965
  const expected = computeDigest(can.bytes, integrity.algorithm);
11147
11966
  const ok = expected === integrity.digest;
11148
- return commandResult(ok, "integrity verify", ok ? EXIT.ok : EXIT.failure, ok ? [] : [
11149
- diag("integrity-mismatch", `Declared digest ${integrity.digest} does not match recomputed ${expected}`)
11150
- ], {
11151
- file,
11152
- target: targetResult.target,
11153
- files: can.files,
11154
- algorithm: integrity.algorithm,
11155
- expected,
11156
- declared: integrity.digest
11157
- });
11967
+ return commandResult(
11968
+ ok,
11969
+ "integrity verify",
11970
+ ok ? EXIT.ok : EXIT.failure,
11971
+ ok ? [] : [diag("integrity-mismatch", `Declared digest ${integrity.digest} does not match recomputed ${expected}`)],
11972
+ {
11973
+ summary: ok ? "Integrity verification passed" : "Integrity verification failed",
11974
+ artifacts: [artifact("verified-artifact", file, targetResult.target, expected)],
11975
+ nextActions: ok ? [
11976
+ nextAction(
11977
+ "validate-artifact",
11978
+ "Validate the artifact before use",
11979
+ `mda validate ${file} --target ${targetResult.target}`,
11980
+ false
11981
+ )
11982
+ ] : [
11983
+ nextAction(
11984
+ "recompute-integrity",
11985
+ "Recompute the digest after fixing the file",
11986
+ `mda integrity compute ${file} --target ${targetResult.target}`
11987
+ )
11988
+ ],
11989
+ file,
11990
+ target: targetResult.target,
11991
+ files: can.files,
11992
+ algorithm: integrity.algorithm,
11993
+ expected,
11994
+ declared: integrity.digest
11995
+ }
11996
+ );
11158
11997
  }
11159
11998
  function runCompile(args) {
11160
11999
  const parsed = parseOptions(args);
11161
- const err = unknownOptions(parsed, ["--target", "--out-dir", "--integrity", "--method", "--key", "--identity", "--out", "--in-place"]);
12000
+ const signingOptions = [
12001
+ "--method",
12002
+ "--key",
12003
+ "--identity",
12004
+ "--profile",
12005
+ "--did",
12006
+ "--key-id",
12007
+ "--key-file",
12008
+ "--repo",
12009
+ "--workflow",
12010
+ "--ref",
12011
+ "--rekor",
12012
+ "--offline-sigstore-fixture",
12013
+ "--out",
12014
+ "--in-place"
12015
+ ];
12016
+ const err = unknownOptions(parsed, ["--target", "--out-dir", "--integrity", "--manifest", "--strict-compat", ...signingOptions]);
11162
12017
  if (err) return usage("compile", err);
11163
- if (parsed.options.has("--method") || parsed.options.has("--key") || parsed.options.has("--identity") || parsed.options.has("--out") || parsed.flags.has("--in-place")) {
12018
+ if (signingOptions.some((option) => parsed.options.has(option) || parsed.flags.has(option))) {
11164
12019
  return usage("compile", "Compile does not sign artifacts. Run mda sign as a separate explicit step.");
11165
12020
  }
11166
12021
  if (parsed.positional.length !== 1) return usage("compile", "Expected one source file: mda compile <file.mda> --target <target...>");
@@ -11170,24 +12025,117 @@ function runCompile(args) {
11170
12025
  const file = parsed.positional[0];
11171
12026
  const sourceValidation = validateArtifact(file, "source");
11172
12027
  if (!sourceValidation.ok) {
11173
- return commandResult(false, "compile", EXIT.failure, sourceValidation.diagnostics, { file, target: "source" });
12028
+ return commandResult(false, "compile", EXIT.failure, sourceValidation.diagnostics, {
12029
+ summary: "Source validation failed before compile",
12030
+ nextActions: [
12031
+ nextAction("validate-source", "Fix source validation errors and re-run validation", `mda validate ${file} --target source`)
12032
+ ],
12033
+ file,
12034
+ target: "source"
12035
+ });
11174
12036
  }
11175
12037
  const read = readArtifact(file);
11176
12038
  if (!read.ok || read.extract.kind !== "ok" || !isRecord(read.extract.frontmatter)) {
11177
- return commandResult(false, "compile", EXIT.failure, [diag("missing-required-frontmatter", "Source must contain frontmatter")], { file, target: "source" });
12039
+ return commandResult(false, "compile", EXIT.failure, [diag("missing-required-frontmatter", "Source must contain frontmatter")], {
12040
+ summary: "Source frontmatter is required before compile",
12041
+ nextActions: [
12042
+ nextAction("fix-source-frontmatter", "Add source frontmatter and re-run validation", `mda validate ${file} --target source`)
12043
+ ],
12044
+ file,
12045
+ target: "source"
12046
+ });
11178
12047
  }
11179
12048
  const outDir = oneOption(parsed.options, "--out-dir") ?? process.cwd();
12049
+ const manifestPath = oneOption(parsed.options, "--manifest");
12050
+ const strictCompat = parsed.flags.has("--strict-compat");
11180
12051
  const includeIntegrity = parsed.flags.has("--integrity") || isRecord(read.extract.frontmatter.integrity);
11181
12052
  const staged = compileTargets(read.extract.frontmatter, read.extract.body, targets, outDir, includeIntegrity);
11182
- if (!staged.ok) return commandResult(false, "compile", EXIT.failure, staged.diagnostics, { file, outDir, planned: staged.planned });
12053
+ if (!staged.ok)
12054
+ return commandResult(false, "compile", EXIT.failure, staged.diagnostics, {
12055
+ summary: "Compile planning failed",
12056
+ nextActions: [
12057
+ nextAction(
12058
+ "fix-compile-input",
12059
+ "Fix the reported diagnostics and retry compile",
12060
+ `mda compile ${file} --target ${targets.join(" ")} --out-dir ${outDir}`
12061
+ )
12062
+ ],
12063
+ file,
12064
+ outDir,
12065
+ planned: manifestPath ? [...staged.planned, manifestPath] : staged.planned
12066
+ });
12067
+ const compatibilityWarnings = compileCompatibilityWarnings(read.extract.frontmatter, targets);
12068
+ if (strictCompat && compatibilityWarnings.length > 0) {
12069
+ return commandResult(
12070
+ false,
12071
+ "compile",
12072
+ EXIT.failure,
12073
+ compatibilityWarnings.map((warning) => ({ ...warning, severity: "error" })),
12074
+ {
12075
+ summary: "Compile compatibility checks failed",
12076
+ nextActions: [
12077
+ nextAction(
12078
+ "fix-compile-compatibility",
12079
+ "Resolve compatibility diagnostics or rerun without --strict-compat",
12080
+ `mda compile ${file} --target ${targets.join(" ")} --out-dir ${outDir}`
12081
+ )
12082
+ ],
12083
+ file,
12084
+ outDir,
12085
+ planned: manifestPath ? [...staged.outputs.map((o) => o.path), manifestPath] : staged.outputs.map((o) => o.path),
12086
+ written: []
12087
+ }
12088
+ );
12089
+ }
11183
12090
  const existing = staged.outputs.find((o) => existsSync2(o.path));
11184
- if (existing) return ioError("compile", `Refusing to overwrite existing file: ${existing.path}`, { file, outDir, planned: staged.outputs.map((o) => o.path), written: [] });
12091
+ if (existing)
12092
+ return ioError("compile", `Refusing to overwrite existing file: ${existing.path}`, {
12093
+ file,
12094
+ outDir,
12095
+ planned: staged.outputs.map((o) => o.path),
12096
+ written: []
12097
+ });
12098
+ if (manifestPath && staged.outputs.some((output) => resolve2(output.path) === resolve2(manifestPath))) {
12099
+ return ioError("compile", `Manifest path conflicts with a compiled output: ${manifestPath}`, {
12100
+ file,
12101
+ outDir,
12102
+ planned: staged.outputs.map((o) => o.path),
12103
+ written: []
12104
+ });
12105
+ }
12106
+ if (manifestPath && existsSync2(manifestPath)) {
12107
+ return ioError("compile", `Refusing to overwrite existing file: ${manifestPath}`, {
12108
+ file,
12109
+ outDir,
12110
+ planned: [...staged.outputs.map((o) => o.path), manifestPath],
12111
+ written: []
12112
+ });
12113
+ }
12114
+ const sourceDigest = canonicalizeFromFile(file, "source", null);
12115
+ if (!sourceDigest.ok) {
12116
+ return commandResult(false, "compile", sourceDigest.exitCode, sourceDigest.diagnostics, {
12117
+ summary: "Source canonicalization failed before compile manifest generation",
12118
+ nextActions: [
12119
+ nextAction("fix-source-canonicalization", "Fix the source and retry compile", `mda compile ${file} --target ${targets.join(" ")}`)
12120
+ ],
12121
+ file,
12122
+ outDir,
12123
+ planned: staged.outputs.map((o) => o.path),
12124
+ written: []
12125
+ });
12126
+ }
12127
+ const manifest = manifestPath ? makeCompileManifest(file, sourceDigest.bytes, read.extract.frontmatter, targets, staged.outputs, compatibilityWarnings) : null;
11185
12128
  const written = [];
11186
12129
  try {
11187
12130
  for (const output of staged.outputs) {
11188
12131
  atomicWrite(output.path, output.bytes);
11189
12132
  written.push(output.path);
11190
12133
  }
12134
+ if (manifestPath && manifest) {
12135
+ atomicWrite(manifestPath, `${JSON.stringify(manifest, null, 2)}
12136
+ `);
12137
+ written.push(manifestPath);
12138
+ }
11191
12139
  } catch (error) {
11192
12140
  const rolledBack = [];
11193
12141
  const rollbackDiagnostics = [];
@@ -11196,80 +12144,144 @@ function runCompile(args) {
11196
12144
  rmSync2(path, { force: true });
11197
12145
  rolledBack.push(path);
11198
12146
  } catch (rollbackError) {
11199
- rollbackDiagnostics.push(diag("rollback-error", rollbackError instanceof Error ? rollbackError.message : String(rollbackError), { path }));
12147
+ rollbackDiagnostics.push(
12148
+ diag("rollback-error", rollbackError instanceof Error ? rollbackError.message : String(rollbackError), { path })
12149
+ );
11200
12150
  }
11201
12151
  }
11202
- return commandResult(false, "compile", EXIT.io, [
11203
- diag("io-error", error instanceof Error ? error.message : String(error)),
11204
- ...rollbackDiagnostics
11205
- ], {
11206
- file,
11207
- outDir,
11208
- planned: staged.outputs.map((o) => o.path),
11209
- written,
11210
- rolledBack
11211
- });
12152
+ return commandResult(
12153
+ false,
12154
+ "compile",
12155
+ EXIT.io,
12156
+ [diag("io-error", error instanceof Error ? error.message : String(error)), ...rollbackDiagnostics],
12157
+ {
12158
+ summary: "Compile failed while writing outputs",
12159
+ nextActions: [
12160
+ nextAction(
12161
+ "retry-compile",
12162
+ "Fix the filesystem error and retry compile",
12163
+ `mda compile ${file} --target ${targets.join(" ")} --out-dir ${outDir}`
12164
+ )
12165
+ ],
12166
+ file,
12167
+ outDir,
12168
+ planned: manifestPath ? [...staged.outputs.map((o) => o.path), manifestPath] : staged.outputs.map((o) => o.path),
12169
+ written,
12170
+ rolledBack
12171
+ }
12172
+ );
11212
12173
  }
11213
- return commandResult(true, "compile", EXIT.ok, [], {
12174
+ return commandResult(true, "compile", EXIT.ok, compatibilityWarnings, {
12175
+ summary: `Compiled ${staged.outputs.length} file(s)`,
12176
+ artifacts: [
12177
+ ...staged.outputs.map((o) => artifact("compiled-output", o.path, targetForPath(o.path))),
12178
+ ...manifestPath ? [artifact("compile-manifest", manifestPath)] : []
12179
+ ],
12180
+ nextActions: nextAfterCompile(staged.outputs.map((o) => o.path)),
11214
12181
  message: `wrote ${staged.outputs.length} file(s)`,
11215
12182
  file,
11216
12183
  target: "source",
11217
12184
  outDir,
11218
- planned: staged.outputs.map((o) => o.path),
11219
- written: staged.outputs.map((o) => o.path)
12185
+ planned: manifestPath ? [...staged.outputs.map((o) => o.path), manifestPath] : staged.outputs.map((o) => o.path),
12186
+ written
11220
12187
  });
11221
12188
  }
11222
- function runVerify(args) {
11223
- const parsed = parseOptions(args);
11224
- const err = unknownOptions(parsed, ["--target", "--sidecar", "--policy", "--offline"]);
11225
- if (err) return usage("verify", err);
11226
- if (parsed.flags.has("--offline")) return usage("verify", "verify --offline is not a stable MVP option");
11227
- if (parsed.positional.length !== 1) return usage("verify", "Expected one file: mda verify <file> --policy <path>");
11228
- const policyPath = oneOption(parsed.options, "--policy");
11229
- if (!policyPath) return usage("verify", "--policy <path> is required");
11230
- const file = parsed.positional[0];
11231
- const requestedTarget = parseTarget(oneOption(parsed.options, "--target") ?? "auto");
11232
- if (!requestedTarget) return usage("verify", "--target must be source, SKILL.md, AGENTS.md, MCP-SERVER.md, or auto");
11233
- const targetResult = resolveTarget(file, requestedTarget);
11234
- if (!targetResult.ok) return targetResult.result("verify", file);
11235
- const policy = readJson(policyPath);
11236
- if (!policy.ok) return ioError("verify", policy.message, { file, policy: policyPath });
11237
- const policyValidation = validateJsonAgainst(policy.value, "trustPolicy");
11238
- if (!policyValidation.ok) return commandResult(false, "verify", EXIT.failure, policyValidation.diagnostics, { file, policy: policyPath });
11239
- const validation = validateArtifact(file, targetResult.target);
11240
- if (!validation.ok) return commandResult(false, "verify", EXIT.failure, validation.diagnostics, { file, target: targetResult.target, policy: policyPath });
11241
- const integrityArgs = ["verify", file, "--target", targetResult.target];
11242
- const sidecar = oneOption(parsed.options, "--sidecar");
11243
- if (sidecar) integrityArgs.push("--sidecar", sidecar);
11244
- const integrity = runIntegrity(integrityArgs);
11245
- if (!integrity.ok) return commandResult(false, "verify", EXIT.failure, integrity.diagnostics, { file, target: targetResult.target, policy: policyPath });
11246
- const artifact = readArtifact(file);
11247
- if (!artifact.ok || artifact.extract.kind !== "ok" || !isRecord(artifact.extract.frontmatter)) {
11248
- return commandResult(false, "verify", EXIT.failure, [diag("missing-required-frontmatter", "Verification requires frontmatter")], { file, target: targetResult.target, policy: policyPath });
12189
+ function makeCompileManifest(file, sourceCanonicalBytes, frontmatter, targets, outputs, warnings) {
12190
+ const outputEntries = outputs.map((output) => ({
12191
+ path: output.path,
12192
+ target: targetForPath(output.path) ?? "mcp-server-sidecar",
12193
+ digest: computeDigest(Buffer.from(output.bytes), "sha256"),
12194
+ byteLength: Buffer.byteLength(output.bytes)
12195
+ }));
12196
+ return {
12197
+ kind: "mda-compile-manifest",
12198
+ version: 1,
12199
+ compiler: {
12200
+ name: "@markdown-ai/cli",
12201
+ version: CLI_VERSION
12202
+ },
12203
+ source: {
12204
+ path: file,
12205
+ target: "source",
12206
+ digest: computeDigest(sourceCanonicalBytes, "sha256")
12207
+ },
12208
+ targetProfile: targets,
12209
+ outputs: outputEntries,
12210
+ outputDigests: Object.fromEntries(outputEntries.map((output) => [output.path, output.digest])),
12211
+ emittedScripts: emittedScripts(frontmatter),
12212
+ emittedResources: outputEntries,
12213
+ capabilitySummary: capabilitySummary(frontmatter),
12214
+ signerIdentity: firstSignerIdentity(frontmatter),
12215
+ warnings: warnings.map(({ code, message, severity }) => ({ code, message, severity }))
12216
+ };
12217
+ }
12218
+ function compileCompatibilityWarnings(frontmatter, targets) {
12219
+ const warnings = [];
12220
+ const capabilities = capabilitySummary(frontmatter);
12221
+ const warn = (code, message) => warnings.push({ ...diag(code, message), severity: "warning" });
12222
+ if (frontmatter["allowed-tools"] !== void 0 && targets.some((target) => target !== "SKILL.md")) {
12223
+ warn("compat.target_feature_loss", "allowed-tools is native only for SKILL.md and is moved to a vendor namespace for other targets");
12224
+ warn("compat.script_permission_mismatch", "Compiled non-SKILL targets cannot enforce SKILL.md allowed-tools permissions directly");
12225
+ }
12226
+ if (capabilities.requires.network !== void 0)
12227
+ warn("compat.network_degradation", "Markdown targets cannot enforce declared network capability requirements");
12228
+ if (capabilities.requires.filesystem !== void 0)
12229
+ warn("compat.filesystem_degradation", "Markdown targets cannot enforce declared filesystem capability requirements");
12230
+ if (capabilities.requires.shell !== void 0 || capabilities.tools.some((tool) => /bash|shell/i.test(tool))) {
12231
+ warn("compat.shell_degradation", "Markdown targets cannot enforce declared shell capability requirements");
12232
+ }
12233
+ if (capabilities.llmix) {
12234
+ warn(
12235
+ "compat.unsupported_runtime_policy",
12236
+ "LLMix runtime policy must be enforced by LLMix deployment checks, not compiled Markdown alone"
12237
+ );
12238
+ warn("compat.llmix_namespace_not_consumed", "General Markdown runtimes do not consume metadata.snoai-llmix directly");
12239
+ }
12240
+ return warnings;
12241
+ }
12242
+ function capabilitySummary(frontmatter) {
12243
+ const metadata = isRecord(frontmatter.metadata) ? frontmatter.metadata : {};
12244
+ const mda = isRecord(metadata.mda) ? metadata.mda : {};
12245
+ const requires = firstRecord(frontmatter.requires, mda.requires);
12246
+ const dependsOn = Array.isArray(frontmatter["depends-on"]) ? frontmatter["depends-on"] : Array.isArray(mda["depends-on"]) ? mda["depends-on"] : [];
12247
+ const tools = [
12248
+ typeof frontmatter["allowed-tools"] === "string" ? frontmatter["allowed-tools"] : null,
12249
+ ...stringArray(requires.tools)
12250
+ ].filter((tool) => typeof tool === "string");
12251
+ return {
12252
+ requires,
12253
+ dependsOnCount: dependsOn.length,
12254
+ tools,
12255
+ llmix: isRecord(metadata["snoai-llmix"])
12256
+ };
12257
+ }
12258
+ function emittedScripts(frontmatter) {
12259
+ const capabilities = capabilitySummary(frontmatter);
12260
+ return capabilities.tools.filter((tool) => /bash|shell|python|node|tsx|ts-node/i.test(tool));
12261
+ }
12262
+ function firstSignerIdentity(frontmatter) {
12263
+ if (!Array.isArray(frontmatter.signatures)) return null;
12264
+ for (const signature of frontmatter.signatures) {
12265
+ if (!isRecord(signature) || typeof signature.signer !== "string") continue;
12266
+ return {
12267
+ signer: signature.signer,
12268
+ keyId: typeof signature.keyId === "string" ? signature.keyId : null,
12269
+ payloadDigest: typeof signature["payload-digest"] === "string" ? signature["payload-digest"] : null
12270
+ };
11249
12271
  }
11250
- if (!Array.isArray(artifact.extract.frontmatter.signatures) || artifact.extract.frontmatter.signatures.length === 0) {
11251
- return commandResult(false, "verify", EXIT.failure, [diag("missing-required-signature", "Verification requires signatures[]")], { file, target: targetResult.target, policy: policyPath });
12272
+ return null;
12273
+ }
12274
+ function firstRecord(...values) {
12275
+ for (const value of values) {
12276
+ if (isRecord(value)) return value;
11252
12277
  }
11253
- return commandResult(false, "verify", EXIT.failure, [
11254
- diag("signature-verification-unavailable", "Integrity and policy shape were checked, but did:web/Sigstore cryptographic verification is not implemented in this MVP")
11255
- ], { file, target: targetResult.target, policy: policyPath });
12278
+ return {};
11256
12279
  }
11257
- function runSign(args) {
11258
- const parsed = parseOptions(args);
11259
- const err = unknownOptions(parsed, ["--method", "--key", "--identity", "--out", "--in-place"]);
11260
- if (err) return usage("sign", err);
11261
- if (parsed.positional.length !== 1) return usage("sign", "Expected one file: mda sign <file> --method did-web --key <path> --identity <domain> (--out <file>|--in-place)");
11262
- const method = oneOption(parsed.options, "--method");
11263
- if (method !== "did-web") return usage("sign", "--method did-web is required");
11264
- if (!oneOption(parsed.options, "--key")) return usage("sign", "--key <path> is required");
11265
- if (!oneOption(parsed.options, "--identity")) return usage("sign", "--identity <domain> is required");
11266
- const out = oneOption(parsed.options, "--out");
11267
- const inPlace = parsed.flags.has("--in-place");
11268
- if (out && inPlace || !out && !inPlace) return usage("sign", "Choose exactly one output mode: --out <file> or --in-place");
11269
- return commandResult(false, "sign", EXIT.failure, [
11270
- diag("signing-unavailable", "did:web signing is intentionally unavailable until deterministic verification fixtures are implemented")
11271
- ], { file: parsed.positional[0], method });
12280
+ function stringArray(value) {
12281
+ return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
11272
12282
  }
12283
+
12284
+ // src/cli/conformance-command.ts
11273
12285
  function runConformance(args) {
11274
12286
  const parsed = parseOptions(args);
11275
12287
  const err = unknownOptions(parsed, ["--suite", "--level"]);
@@ -11280,6 +12292,21 @@ function runConformance(args) {
11280
12292
  if (level !== "V" && level !== "C") return usage("conformance", "--level must be V or C");
11281
12293
  const report = runConformanceSuite(suite, level);
11282
12294
  return commandResult(report.ok, "conformance", report.ok ? EXIT.ok : EXIT.failure, report.diagnostics, {
12295
+ summary: report.ok ? `Conformance Level ${level} passed` : `Conformance Level ${level} failed`,
12296
+ nextActions: report.ok && level === "V" ? [
12297
+ nextAction(
12298
+ "run-level-c",
12299
+ "Run compile conformance before release evidence",
12300
+ `mda conformance --suite ${suite} --level C`,
12301
+ false
12302
+ )
12303
+ ] : report.ok ? [] : [
12304
+ nextAction(
12305
+ "fix-conformance",
12306
+ "Fix failing fixtures and re-run conformance",
12307
+ `mda conformance --suite ${suite} --level ${level}`
12308
+ )
12309
+ ],
11283
12310
  suite,
11284
12311
  level,
11285
12312
  passCount: report.passCount,
@@ -11288,6 +12315,2195 @@ function runConformance(args) {
11288
12315
  });
11289
12316
  }
11290
12317
 
12318
+ // src/cli/llmix-commands.ts
12319
+ import { existsSync as existsSync4, readdirSync as readdirSync2, realpathSync } from "node:fs";
12320
+ import { basename, dirname as dirname2, join as join2, relative as relative2, resolve as resolve3, sep } from "node:path";
12321
+
12322
+ // src/cli/security-commands.ts
12323
+ import { createPrivateKey, createPublicKey, sign as cryptoSign, verify as cryptoVerify } from "node:crypto";
12324
+ import { existsSync as existsSync3, readFileSync as readFileSync2 } from "node:fs";
12325
+ function integrityPayloadBytes(integrity) {
12326
+ return Buffer.from(jcs({ integrity: { algorithm: integrity.algorithm, digest: integrity.digest } }), "utf8");
12327
+ }
12328
+ function dssePae(payloadType, payload) {
12329
+ return Buffer.concat([
12330
+ Buffer.from(`DSSEv1 ${Buffer.byteLength(payloadType, "utf8")} ${payloadType} ${payload.length} `, "utf8"),
12331
+ payload
12332
+ ]);
12333
+ }
12334
+ function didWebDomainFromDid(did) {
12335
+ if (!did.startsWith("did:web:")) return null;
12336
+ const domain = did.slice("did:web:".length).split(":")[0];
12337
+ if (!/^[A-Za-z0-9](?:[A-Za-z0-9.-]{0,251}[A-Za-z0-9])?$/.test(domain)) return null;
12338
+ return domain;
12339
+ }
12340
+ function signerDomain(signer) {
12341
+ return signer.startsWith("did-web:") ? signer.slice("did-web:".length) : null;
12342
+ }
12343
+ function sigstoreIssuer(signer) {
12344
+ return signer.startsWith("sigstore-oidc:") ? signer.slice("sigstore-oidc:".length) : null;
12345
+ }
12346
+ function keyAlgorithm(key) {
12347
+ return key.asymmetricKeyType === "ed25519" ? "ed25519" : null;
12348
+ }
12349
+ function publicKeyFingerprint(key) {
12350
+ return computeDigest(key.export({ format: "der", type: "spki" }), "sha256");
12351
+ }
12352
+ function policyAllowsDidWeb2(policy, domain) {
12353
+ return isRecord(policy) && Array.isArray(policy.trustedSigners) && policy.trustedSigners.some((entry) => isRecord(entry) && entry.type === "did-web" && entry.domain === domain);
12354
+ }
12355
+ function policyAllowsSigstore2(policy, fixture) {
12356
+ if (!isRecord(policy) || !Array.isArray(policy.trustedSigners)) return false;
12357
+ return policy.trustedSigners.some((entry) => {
12358
+ if (!isRecord(entry) || entry.type !== "sigstore-oidc") return false;
12359
+ if (entry.issuer !== fixture.issuer || entry.subject !== fixture.subject) return false;
12360
+ if (entry.repository !== void 0 && entry.repository !== fixture.repository) return false;
12361
+ if (entry.workflow !== void 0 && entry.workflow !== fixture.workflow) return false;
12362
+ if (entry.ref !== void 0 && entry.ref !== fixture.ref) return false;
12363
+ if (entry.environment !== void 0 && entry.environment !== fixture.environment) return false;
12364
+ if (entry.jobWorkflowRef !== void 0 && entry.jobWorkflowRef !== fixture.jobWorkflowRef) return false;
12365
+ return true;
12366
+ });
12367
+ }
12368
+ function trustPolicyDiagnostics(policy, diagnostics) {
12369
+ if (!isRecord(policy) || !Array.isArray(policy.trustedSigners)) return diagnostics;
12370
+ const hasEnvironmentOnlyGithubActions = policy.trustedSigners.some((entry) => {
12371
+ if (!isRecord(entry) || entry.type !== "sigstore-oidc") return false;
12372
+ if (entry.issuer !== GITHUB_ACTIONS_ISSUER || typeof entry.environment !== "string") return false;
12373
+ const hasRefBinding = typeof entry.repository === "string" && typeof entry.workflow === "string" && typeof entry.ref === "string";
12374
+ const hasJobWorkflowBinding = typeof entry.repository === "string" && typeof entry.jobWorkflowRef === "string";
12375
+ return !hasRefBinding && !hasJobWorkflowBinding;
12376
+ });
12377
+ if (!hasEnvironmentOnlyGithubActions) return diagnostics;
12378
+ return [
12379
+ diag(
12380
+ "trust_policy.environment_only_not_supported",
12381
+ "GitHub Actions trust policy must bind repository and exact ref or jobWorkflowRef; environment alone is not supported"
12382
+ ),
12383
+ ...diagnostics
12384
+ ];
12385
+ }
12386
+ function policyMentionsSigstoreIssuer(policy, issuer) {
12387
+ return isRecord(policy) && Array.isArray(policy.trustedSigners) && policy.trustedSigners.some((entry) => isRecord(entry) && entry.type === "sigstore-oidc" && entry.issuer === issuer);
12388
+ }
12389
+ function trustPolicyMinSignatures(policy) {
12390
+ return isRecord(policy) && Number.isInteger(policy.minSignatures) ? Number(policy.minSignatures) : 1;
12391
+ }
12392
+ function asSignatureEntry(value) {
12393
+ if (!isRecord(value)) return null;
12394
+ if (typeof value.signer !== "string") return null;
12395
+ if (typeof value["key-id"] !== "string") return null;
12396
+ if (typeof value["payload-digest"] !== "string") return null;
12397
+ if (typeof value.algorithm !== "string") return null;
12398
+ if (typeof value.signature !== "string") return null;
12399
+ return value;
12400
+ }
12401
+ function payloadTypeDiagnostic(signature) {
12402
+ if (signature["payload-type"] === void 0 || signature["payload-type"] === INTEGRITY_PAYLOAD_TYPE) return null;
12403
+ return diag("signature.payload_type_mismatch", `Signature payload-type must be ${INTEGRITY_PAYLOAD_TYPE}`);
12404
+ }
12405
+ function verifySignatureEntries(signatures, integrity, policy, didDocumentPath, sigstoreFixturePath) {
12406
+ const payload = integrityPayloadBytes(integrity);
12407
+ const sigstoreFixture = sigstoreFixturePath ? readSigstoreFixture(sigstoreFixturePath, false) : null;
12408
+ const trusted = /* @__PURE__ */ new Set();
12409
+ const trustedSignerIdentities = [];
12410
+ const rejectedTrusted = [];
12411
+ for (const rawSignature of signatures) {
12412
+ const signature = asSignatureEntry(rawSignature);
12413
+ if (!signature) return { malformed: true, trusted, trustedSignerIdentities, rejectedTrusted };
12414
+ const domain = signerDomain(signature.signer);
12415
+ if (domain) {
12416
+ if (!policyAllowsDidWeb2(policy, domain)) continue;
12417
+ if (signature["payload-digest"] !== integrity.digest) {
12418
+ rejectedTrusted.push(diag("signature.digest_mismatch", "Signature payload digest does not match the expected integrity digest"));
12419
+ continue;
12420
+ }
12421
+ const payloadTypeError2 = payloadTypeDiagnostic(signature);
12422
+ if (payloadTypeError2) {
12423
+ rejectedTrusted.push(payloadTypeError2);
12424
+ continue;
12425
+ }
12426
+ const keyResult = publicKeyFromDidDocument(didDocumentPath, signature["key-id"], `did:web:${domain}`);
12427
+ if (!keyResult.ok) {
12428
+ rejectedTrusted.push(...keyResult.diagnostics);
12429
+ continue;
12430
+ }
12431
+ if (signature.algorithm !== keyAlgorithm(keyResult.key)) {
12432
+ rejectedTrusted.push(
12433
+ diag("signature.unsupported_algorithm", `Signature algorithm ${signature.algorithm} does not match the DID public key`)
12434
+ );
12435
+ continue;
12436
+ }
12437
+ const ok2 = cryptoVerify(null, dssePae(INTEGRITY_PAYLOAD_TYPE, payload), keyResult.key, Buffer.from(signature.signature, "base64"));
12438
+ if (!ok2) {
12439
+ rejectedTrusted.push(diag("signature.verification_failed", `Signature verification failed for ${signature["key-id"]}`));
12440
+ continue;
12441
+ }
12442
+ const identityKey2 = `did-web:${domain}
12443
+ ${keyResult.keyFingerprint}`;
12444
+ if (!trusted.has(identityKey2)) {
12445
+ trusted.add(identityKey2);
12446
+ trustedSignerIdentities.push({
12447
+ type: "did-web",
12448
+ signer: `did-web:${domain}`,
12449
+ keyId: keyResult.methodId,
12450
+ payloadDigest: signature["payload-digest"]
12451
+ });
12452
+ }
12453
+ continue;
12454
+ }
12455
+ const issuer = sigstoreIssuer(signature.signer);
12456
+ if (!issuer || !policyMentionsSigstoreIssuer(policy, issuer)) continue;
12457
+ if (signature["payload-digest"] !== integrity.digest) {
12458
+ rejectedTrusted.push(diag("signature.digest_mismatch", "Signature payload digest does not match the expected integrity digest"));
12459
+ continue;
12460
+ }
12461
+ const payloadTypeError = payloadTypeDiagnostic(signature);
12462
+ if (payloadTypeError) {
12463
+ rejectedTrusted.push(payloadTypeError);
12464
+ continue;
12465
+ }
12466
+ if (!sigstoreFixture) {
12467
+ rejectedTrusted.push(
12468
+ diag("sigstore.fixture_required", "--offline-sigstore-fixture <path> is required for deterministic Sigstore verification")
12469
+ );
12470
+ continue;
12471
+ }
12472
+ if (!sigstoreFixture.ok) {
12473
+ rejectedTrusted.push(...sigstoreFixture.diagnostics);
12474
+ continue;
12475
+ }
12476
+ const fixture = sigstoreFixture.fixture;
12477
+ if (fixture.issuer !== issuer || fixture.keyId !== signature["key-id"] || !policyAllowsSigstore2(policy, fixture)) {
12478
+ rejectedTrusted.push(diag("sigstore.identity_mismatch", "Sigstore fixture identity does not match the signature and trust policy"));
12479
+ continue;
12480
+ }
12481
+ const policyRekor = isRecord(policy) && isRecord(policy.rekor) ? policy.rekor : null;
12482
+ if (!policyRekor || policyRekor.url !== fixture.rekor.url) {
12483
+ rejectedTrusted.push(diag("rekor.policy_mismatch", "Trust policy Rekor URL does not match the Sigstore fixture"));
12484
+ continue;
12485
+ }
12486
+ if (fixture.expectedPayloadDigest !== integrity.digest || fixture.rekor.payloadDigest !== integrity.digest) {
12487
+ rejectedTrusted.push(
12488
+ diag("sigstore.fixture_digest_mismatch", "Sigstore fixture digest does not match the expected integrity digest")
12489
+ );
12490
+ continue;
12491
+ }
12492
+ if (signature["rekor-log-id"] === void 0 || signature["rekor-log-index"] === void 0) {
12493
+ rejectedTrusted.push(diag("rekor.evidence_missing", "Signature is missing required Rekor evidence"));
12494
+ continue;
12495
+ }
12496
+ if (signature["rekor-log-id"] !== fixture.rekor.logId || signature["rekor-log-index"] !== fixture.rekor.logIndex) {
12497
+ rejectedTrusted.push(diag("rekor.evidence_mismatch", "Signature Rekor evidence does not match the Sigstore fixture"));
12498
+ continue;
12499
+ }
12500
+ let publicKey;
12501
+ try {
12502
+ publicKey = createPublicKey(fixture.publicKeyPem);
12503
+ } catch (error) {
12504
+ rejectedTrusted.push(diag("sigstore.fixture_invalid", error instanceof Error ? error.message : String(error)));
12505
+ continue;
12506
+ }
12507
+ if (signature.algorithm !== fixture.algorithm || signature.algorithm !== keyAlgorithm(publicKey)) {
12508
+ rejectedTrusted.push(diag("signature.unsupported_algorithm", "Signature algorithm does not match the Sigstore fixture public key"));
12509
+ continue;
12510
+ }
12511
+ const ok = cryptoVerify(null, dssePae(INTEGRITY_PAYLOAD_TYPE, payload), publicKey, Buffer.from(signature.signature, "base64"));
12512
+ if (!ok) {
12513
+ rejectedTrusted.push(diag("signature.verification_failed", `Signature verification failed for ${signature["key-id"]}`));
12514
+ continue;
12515
+ }
12516
+ const identityKey = `sigstore-oidc:${fixture.issuer}
12517
+ ${fixture.subject}
12518
+ ${signature["key-id"]}`;
12519
+ if (!trusted.has(identityKey)) {
12520
+ trusted.add(identityKey);
12521
+ trustedSignerIdentities.push({
12522
+ type: "sigstore-oidc",
12523
+ signer: `sigstore-oidc:${fixture.issuer}`,
12524
+ subject: fixture.subject,
12525
+ keyId: signature["key-id"],
12526
+ payloadDigest: signature["payload-digest"],
12527
+ rekorLogId: fixture.rekor.logId,
12528
+ rekorLogIndex: fixture.rekor.logIndex
12529
+ });
12530
+ }
12531
+ }
12532
+ return { malformed: false, trusted, trustedSignerIdentities, rejectedTrusted };
12533
+ }
12534
+ function readSigstoreFixture(path, requirePrivateKey) {
12535
+ const read = readJson(path);
12536
+ if (!read.ok) return { ok: false, diagnostics: [diag("sigstore.fixture_unavailable", read.message)] };
12537
+ const value = read.value;
12538
+ const errors = [];
12539
+ const requireString = (name) => {
12540
+ const field = isRecord(value) ? value[name] : void 0;
12541
+ if (typeof field !== "string" || field.length === 0)
12542
+ errors.push(diag("sigstore.fixture_invalid", `${name} must be a non-empty string`));
12543
+ return typeof field === "string" ? field : "";
12544
+ };
12545
+ if (!isRecord(value)) {
12546
+ return { ok: false, diagnostics: [diag("sigstore.fixture_invalid", "Sigstore fixture must be a JSON object")] };
12547
+ }
12548
+ const fixture = {
12549
+ issuer: requireString("issuer"),
12550
+ subject: requireString("subject"),
12551
+ repository: requireString("repository"),
12552
+ workflow: requireString("workflow"),
12553
+ ref: requireString("ref"),
12554
+ keyId: requireString("keyId"),
12555
+ algorithm: "ed25519",
12556
+ publicKeyPem: requireString("publicKeyPem"),
12557
+ expectedPayloadDigest: requireString("expectedPayloadDigest"),
12558
+ rekor: {
12559
+ url: "",
12560
+ logId: "",
12561
+ logIndex: -1,
12562
+ payloadDigest: ""
12563
+ }
12564
+ };
12565
+ if (typeof value.environment === "string" && value.environment.length > 0) fixture.environment = value.environment;
12566
+ if (typeof value.jobWorkflowRef === "string" && value.jobWorkflowRef.length > 0) fixture.jobWorkflowRef = value.jobWorkflowRef;
12567
+ if (typeof value.privateKeyPem === "string" && value.privateKeyPem.length > 0) fixture.privateKeyPem = value.privateKeyPem;
12568
+ if (value.algorithm !== "ed25519") errors.push(diag("sigstore.fixture_invalid", "algorithm must be ed25519"));
12569
+ if (!GITHUB_REPOSITORY.test(fixture.repository)) errors.push(diag("sigstore.fixture_invalid", "repository must be owner/repo"));
12570
+ if (!GITHUB_REF.test(fixture.ref)) errors.push(diag("sigstore.fixture_invalid", "ref must be an exact refs/heads/* or refs/tags/* ref"));
12571
+ if (!DIGEST_PATTERN.test(fixture.expectedPayloadDigest))
12572
+ errors.push(diag("sigstore.fixture_invalid", "expectedPayloadDigest must be a sha256/sha384/sha512 digest"));
12573
+ if (requirePrivateKey && !fixture.privateKeyPem) errors.push(diag("sigstore.fixture_invalid", "privateKeyPem is required for signing"));
12574
+ if (!isRecord(value.rekor)) {
12575
+ errors.push(diag("sigstore.fixture_invalid", "rekor must be an object"));
12576
+ } else {
12577
+ fixture.rekor = {
12578
+ url: typeof value.rekor.url === "string" ? value.rekor.url : "",
12579
+ logId: typeof value.rekor.logId === "string" ? value.rekor.logId : "",
12580
+ logIndex: Number(value.rekor.logIndex),
12581
+ payloadDigest: typeof value.rekor.payloadDigest === "string" ? value.rekor.payloadDigest : ""
12582
+ };
12583
+ if (!fixture.rekor.url) errors.push(diag("sigstore.fixture_invalid", "rekor.url must be a non-empty string"));
12584
+ if (!fixture.rekor.logId) errors.push(diag("sigstore.fixture_invalid", "rekor.logId must be a non-empty string"));
12585
+ if (!Number.isInteger(fixture.rekor.logIndex) || fixture.rekor.logIndex < 0)
12586
+ errors.push(diag("sigstore.fixture_invalid", "rekor.logIndex must be a non-negative integer"));
12587
+ if (!DIGEST_PATTERN.test(fixture.rekor.payloadDigest))
12588
+ errors.push(diag("sigstore.fixture_invalid", "rekor.payloadDigest must be a sha256/sha384/sha512 digest"));
12589
+ }
12590
+ return errors.length > 0 ? { ok: false, diagnostics: errors } : { ok: true, fixture };
12591
+ }
12592
+ function publicKeyFromDidDocument(didDocumentPath, keyId, expectedDid) {
12593
+ if (!didDocumentPath) {
12594
+ return {
12595
+ ok: false,
12596
+ diagnostics: [diag("did_web.document_unavailable", "--did-document <path> is required for local did:web verification")]
12597
+ };
12598
+ }
12599
+ const document = readJson(didDocumentPath);
12600
+ if (!document.ok) {
12601
+ return { ok: false, diagnostics: [diag("did_web.document_unavailable", document.message)] };
12602
+ }
12603
+ if (!isRecord(document.value) || !Array.isArray(document.value.verificationMethod)) {
12604
+ return { ok: false, diagnostics: [diag("did_web.document_invalid", "DID document must contain verificationMethod[]")] };
12605
+ }
12606
+ if (document.value.id !== expectedDid) {
12607
+ return { ok: false, diagnostics: [diag("did_web.document_invalid", `DID document id must be ${expectedDid}`)] };
12608
+ }
12609
+ if (keyId.startsWith("did:") && !keyId.startsWith(`${expectedDid}#`)) {
12610
+ return { ok: false, diagnostics: [diag("did_web.document_invalid", `DID key ${keyId} is not anchored to ${expectedDid}`)] };
12611
+ }
12612
+ const fragment = keyId.startsWith("#") ? keyId : keyId.includes("#") ? `#${keyId.split("#").pop()}` : keyId;
12613
+ const fullKeyId = fragment.startsWith("#") ? `${expectedDid}${fragment}` : null;
12614
+ const method = document.value.verificationMethod.find(
12615
+ (entry) => isRecord(entry) && (entry.id === keyId || entry.id === fragment || entry.id === fullKeyId)
12616
+ );
12617
+ if (!isRecord(method)) {
12618
+ return { ok: false, diagnostics: [diag("did_web.key_not_found", `DID document does not contain key id ${keyId}`)] };
12619
+ }
12620
+ if (typeof method.id !== "string" || !method.id.startsWith(`${expectedDid}#`) && !method.id.startsWith("#")) {
12621
+ return { ok: false, diagnostics: [diag("did_web.document_invalid", `DID key ${keyId} is not anchored to ${expectedDid}`)] };
12622
+ }
12623
+ if (method.controller !== expectedDid) {
12624
+ return { ok: false, diagnostics: [diag("did_web.document_invalid", `DID key ${keyId} controller must be ${expectedDid}`)] };
12625
+ }
12626
+ const methodId = method.id.startsWith("#") ? `${expectedDid}${method.id}` : method.id;
12627
+ try {
12628
+ if (isRecord(method.publicKeyJwk)) {
12629
+ const key = createPublicKey({ key: method.publicKeyJwk, format: "jwk" });
12630
+ return {
12631
+ ok: true,
12632
+ key,
12633
+ methodId,
12634
+ keyFingerprint: publicKeyFingerprint(key)
12635
+ };
12636
+ }
12637
+ if (typeof method.publicKeyPem === "string") {
12638
+ const key = createPublicKey(method.publicKeyPem);
12639
+ return { ok: true, key, methodId, keyFingerprint: publicKeyFingerprint(key) };
12640
+ }
12641
+ return { ok: false, diagnostics: [diag("did_web.key_not_found", `DID key ${keyId} has no supported public key material`)] };
12642
+ } catch (error) {
12643
+ return { ok: false, diagnostics: [diag("did_web.key_invalid", error instanceof Error ? error.message : String(error))] };
12644
+ }
12645
+ }
12646
+ function runVerify(args) {
12647
+ const parsed = parseOptions(args);
12648
+ const err = unknownOptions(parsed, ["--target", "--sidecar", "--policy", "--did-document", "--offline-sigstore-fixture", "--offline"]);
12649
+ if (err) return usage("verify", err);
12650
+ if (parsed.flags.has("--offline")) return usage("verify", "verify --offline is not a stable MVP option");
12651
+ if (parsed.positional.length !== 1) return usage("verify", "Expected one file: mda verify <file> --policy <path>");
12652
+ const policyPath = oneOption(parsed.options, "--policy");
12653
+ if (!policyPath) return usage("verify", "--policy <path> is required");
12654
+ const file = parsed.positional[0];
12655
+ const requestedTarget = parseTarget(oneOption(parsed.options, "--target") ?? "auto");
12656
+ if (!requestedTarget) return usage("verify", "--target must be source, SKILL.md, AGENTS.md, MCP-SERVER.md, or auto");
12657
+ const targetResult = resolveTarget(file, requestedTarget);
12658
+ if (!targetResult.ok) return targetResult.result("verify", file);
12659
+ const policy = readJson(policyPath);
12660
+ if (!policy.ok) return ioError("verify", policy.message, { file, policy: policyPath });
12661
+ const policyValidation = validateJsonAgainst(policy.value, "trustPolicy");
12662
+ if (!policyValidation.ok)
12663
+ return commandResult(false, "verify", EXIT.failure, trustPolicyDiagnostics(policy.value, policyValidation.diagnostics), {
12664
+ file,
12665
+ policy: policyPath
12666
+ });
12667
+ const validation = validateArtifact(file, targetResult.target);
12668
+ if (!validation.ok)
12669
+ return commandResult(false, "verify", EXIT.failure, validation.diagnostics, { file, target: targetResult.target, policy: policyPath });
12670
+ const integrityArgs = ["verify", file, "--target", targetResult.target];
12671
+ const sidecar = oneOption(parsed.options, "--sidecar");
12672
+ if (sidecar) integrityArgs.push("--sidecar", sidecar);
12673
+ const integrity = runIntegrity(integrityArgs);
12674
+ if (!integrity.ok)
12675
+ return commandResult(false, "verify", EXIT.failure, integrity.diagnostics, { file, target: targetResult.target, policy: policyPath });
12676
+ const signedArtifact = readArtifact(file);
12677
+ if (!signedArtifact.ok || signedArtifact.extract.kind !== "ok" || !isRecord(signedArtifact.extract.frontmatter)) {
12678
+ return commandResult(false, "verify", EXIT.failure, [diag("missing-required-frontmatter", "Verification requires frontmatter")], {
12679
+ file,
12680
+ target: targetResult.target,
12681
+ policy: policyPath
12682
+ });
12683
+ }
12684
+ if (!Array.isArray(signedArtifact.extract.frontmatter.signatures) || signedArtifact.extract.frontmatter.signatures.length === 0) {
12685
+ return commandResult(false, "verify", EXIT.failure, [diag("missing-required-signature", "Verification requires signatures[]")], {
12686
+ file,
12687
+ target: targetResult.target,
12688
+ policy: policyPath
12689
+ });
12690
+ }
12691
+ const integrityField = signedArtifact.extract.frontmatter.integrity;
12692
+ if (!isRecord(integrityField) || typeof integrityField.algorithm !== "string" || typeof integrityField.digest !== "string") {
12693
+ return commandResult(false, "verify", EXIT.failure, [diag("missing-required-integrity", "Verification requires integrity")], {
12694
+ file,
12695
+ target: targetResult.target,
12696
+ policy: policyPath
12697
+ });
12698
+ }
12699
+ const didDocumentPath = oneOption(parsed.options, "--did-document");
12700
+ const sigstoreFixturePath = oneOption(parsed.options, "--offline-sigstore-fixture");
12701
+ const signatureVerification = verifySignatureEntries(
12702
+ signedArtifact.extract.frontmatter.signatures,
12703
+ { algorithm: integrityField.algorithm, digest: integrityField.digest },
12704
+ policy.value,
12705
+ didDocumentPath,
12706
+ sigstoreFixturePath
12707
+ );
12708
+ if (signatureVerification.malformed) {
12709
+ return commandResult(false, "verify", EXIT.failure, [diag("signature.invalid_entry", "Signature entry is malformed")], {
12710
+ file,
12711
+ target: targetResult.target,
12712
+ policy: policyPath
12713
+ });
12714
+ }
12715
+ const { trusted, trustedSignerIdentities, rejectedTrusted } = signatureVerification;
12716
+ if (trusted.size === 0) {
12717
+ const hasSigstoreRejection = rejectedTrusted.some((d) => d.code.startsWith("sigstore.") || d.code.startsWith("rekor."));
12718
+ return commandResult(
12719
+ false,
12720
+ "verify",
12721
+ EXIT.failure,
12722
+ rejectedTrusted.length > 0 ? rejectedTrusted : [diag("no-trusted-signature", "no signature matched the trust policy")],
12723
+ {
12724
+ summary: rejectedTrusted.length > 0 ? "No trusted signature could be verified" : "No trusted signature matched the policy",
12725
+ nextActions: rejectedTrusted.length > 0 ? hasSigstoreRejection ? [
12726
+ nextAction(
12727
+ "provide-sigstore-fixture",
12728
+ "Provide the explicit Sigstore/Rekor fixture matching this release identity",
12729
+ `mda verify ${file} --policy ${policyPath} --offline-sigstore-fixture <sigstore-fixture.json>`
12730
+ )
12731
+ ] : [
12732
+ nextAction(
12733
+ "fix-did-document",
12734
+ "Provide the DID document containing the signature key",
12735
+ `mda verify ${file} --policy ${policyPath} --did-document <did-document.json>`
12736
+ )
12737
+ ] : [
12738
+ nextAction(
12739
+ "sign-with-trusted-identity",
12740
+ "Sign the artifact with a did:web identity allowed by the policy",
12741
+ `mda sign ${file} --profile did-web --did did:web:<domain> --key-id did:web:<domain>#<key> --key-file <private-key> --out signed.mda`
12742
+ )
12743
+ ],
12744
+ file,
12745
+ target: targetResult.target,
12746
+ policy: policyPath,
12747
+ rejectedSignatures: rejectedTrusted.length
12748
+ }
12749
+ );
12750
+ }
12751
+ const minSignatures = trustPolicyMinSignatures(policy.value);
12752
+ if (trusted.size < minSignatures) {
12753
+ return commandResult(
12754
+ false,
12755
+ "verify",
12756
+ EXIT.failure,
12757
+ [diag("insufficient-trusted-signatures", `${trusted.size} trusted signer identities < ${minSignatures}`)],
12758
+ {
12759
+ summary: "Trusted signature threshold was not met",
12760
+ nextActions: [
12761
+ nextAction(
12762
+ "add-signatures",
12763
+ "Add enough trusted signatures to satisfy minSignatures",
12764
+ `mda sign ${file} --profile did-web --did did:web:<domain> --key-id did:web:<domain>#<key> --key-file <private-key> --out signed.mda`
12765
+ )
12766
+ ],
12767
+ file,
12768
+ target: targetResult.target,
12769
+ policy: policyPath,
12770
+ trustedSignatures: trusted.size,
12771
+ rejectedSignatures: rejectedTrusted.length,
12772
+ minSignatures,
12773
+ trustedSignerIdentities
12774
+ }
12775
+ );
12776
+ }
12777
+ return commandResult(true, "verify", EXIT.ok, [], {
12778
+ summary: "Signature verification passed",
12779
+ artifacts: [artifact("verified-signature", file, targetResult.target, String(integrityField.digest))],
12780
+ nextActions: [
12781
+ externalNextAction(
12782
+ "publish-artifact",
12783
+ "Use this verified artifact in the release flow",
12784
+ "continue to release-plan or trust-manifest generation",
12785
+ false
12786
+ )
12787
+ ],
12788
+ message: `verified ${trusted.size} trusted signature(s)`,
12789
+ file,
12790
+ target: targetResult.target,
12791
+ policy: policyPath,
12792
+ trustedSignatures: trusted.size,
12793
+ rejectedSignatures: rejectedTrusted.length,
12794
+ minSignatures,
12795
+ trustedSignerIdentities,
12796
+ payloadDigest: integrityField.digest
12797
+ });
12798
+ }
12799
+ function runSign(args) {
12800
+ const parsed = parseOptions(args);
12801
+ const err = unknownOptions(parsed, [
12802
+ "--target",
12803
+ "--sidecar",
12804
+ "--profile",
12805
+ "--did",
12806
+ "--key-id",
12807
+ "--key-file",
12808
+ "--method",
12809
+ "--key",
12810
+ "--identity",
12811
+ "--repo",
12812
+ "--workflow",
12813
+ "--ref",
12814
+ "--offline-sigstore-fixture",
12815
+ "--out",
12816
+ "--in-place",
12817
+ "--rekor"
12818
+ ]);
12819
+ if (err) return usage("sign", err);
12820
+ if (parsed.positional.length !== 1)
12821
+ return usage(
12822
+ "sign",
12823
+ "Expected one file: mda sign <file> --profile did-web --did <did> --key-id <key-id> --key-file <path> (--out <file>|--in-place)"
12824
+ );
12825
+ const file = parsed.positional[0];
12826
+ const profile = oneOption(parsed.options, "--profile");
12827
+ const method = oneOption(parsed.options, "--method");
12828
+ if (profile === "github-actions") return runGithubActionsSign(parsed);
12829
+ if (profile && profile !== "did-web") return usage("sign", `Unsupported signing profile: ${profile}`);
12830
+ if (method && method !== "did-web") return usage("sign", "--method did-web is the only compatibility alias");
12831
+ if (!profile && !method) return usage("sign", "--profile did-web is required");
12832
+ if (parsed.options.has("--repo") || parsed.options.has("--workflow") || parsed.options.has("--ref") || parsed.options.has("--offline-sigstore-fixture") || parsed.flags.has("--rekor")) {
12833
+ return usage("sign", "GitHub Actions signing options require --profile github-actions");
12834
+ }
12835
+ const identity = oneOption(parsed.options, "--identity");
12836
+ const did = oneOption(parsed.options, "--did") ?? (identity ? `did:web:${identity}` : null);
12837
+ if (!did) return usage("sign", "--did <did> is required");
12838
+ const domain = didWebDomainFromDid(did);
12839
+ if (!domain) return usage("sign", "--did must be a did:web DID with a valid domain");
12840
+ const keyId = oneOption(parsed.options, "--key-id") ?? `${did}#default`;
12841
+ const keyFile = oneOption(parsed.options, "--key-file") ?? oneOption(parsed.options, "--key");
12842
+ if (!keyFile)
12843
+ return commandResult(false, "sign", EXIT.usage, [diag("did_web.key_input_missing", "--key-file <path> is required")], {
12844
+ summary: "sign usage error",
12845
+ nextActions: [
12846
+ nextAction(
12847
+ "provide-did-web-key",
12848
+ "Provide the explicit did:web private key file",
12849
+ `mda sign ${file} --profile did-web --did ${did} --key-id ${keyId} --key-file <private-key> --out signed.mda`
12850
+ )
12851
+ ],
12852
+ file,
12853
+ written: false
12854
+ });
12855
+ const out = oneOption(parsed.options, "--out");
12856
+ const inPlace = parsed.flags.has("--in-place");
12857
+ if (out && inPlace || !out && !inPlace) return usage("sign", "Choose exactly one output mode: --out <file> or --in-place");
12858
+ if (out && existsSync3(out)) return ioError("sign", `Refusing to overwrite existing file: ${out}`, { file, out, written: false });
12859
+ const requestedTarget = parseTarget(oneOption(parsed.options, "--target") ?? "auto");
12860
+ if (!requestedTarget) return usage("sign", "--target must be source, SKILL.md, AGENTS.md, MCP-SERVER.md, or auto");
12861
+ const targetResult = resolveTarget(file, requestedTarget);
12862
+ if (!targetResult.ok) return targetResult.result("sign", file);
12863
+ const sidecar = oneOption(parsed.options, "--sidecar");
12864
+ const integrityArgs = ["verify", file, "--target", targetResult.target];
12865
+ if (sidecar) integrityArgs.push("--sidecar", sidecar);
12866
+ const integrityResult = runIntegrity(integrityArgs);
12867
+ if (!integrityResult.ok) {
12868
+ return commandResult(false, "sign", EXIT.failure, integrityResult.diagnostics, {
12869
+ summary: "Signing requires valid recorded integrity",
12870
+ nextActions: [
12871
+ nextAction(
12872
+ "write-integrity",
12873
+ "Record integrity before signing",
12874
+ `mda integrity compute ${file} --target ${targetResult.target} --write`
12875
+ )
12876
+ ],
12877
+ file,
12878
+ target: targetResult.target,
12879
+ written: false
12880
+ });
12881
+ }
12882
+ const input = readArtifact(file);
12883
+ if (!input.ok || input.extract.kind !== "ok" || !isRecord(input.extract.frontmatter)) {
12884
+ return commandResult(false, "sign", EXIT.failure, [diag("missing-required-frontmatter", "Signing requires frontmatter")], {
12885
+ file,
12886
+ target: targetResult.target,
12887
+ written: false
12888
+ });
12889
+ }
12890
+ const integrity = input.extract.frontmatter.integrity;
12891
+ if (!isRecord(integrity) || typeof integrity.digest !== "string") {
12892
+ return commandResult(false, "sign", EXIT.failure, [diag("missing-required-integrity", "Signing requires integrity")], {
12893
+ file,
12894
+ target: targetResult.target,
12895
+ written: false
12896
+ });
12897
+ }
12898
+ if (input.extract.frontmatter.signatures !== void 0 && !Array.isArray(input.extract.frontmatter.signatures)) {
12899
+ return commandResult(false, "sign", EXIT.failure, [diag("signature.invalid_entry", "Existing signatures must be an array")], {
12900
+ file,
12901
+ target: targetResult.target,
12902
+ written: false
12903
+ });
12904
+ }
12905
+ let privateKey;
12906
+ try {
12907
+ privateKey = createPrivateKey(readFileSync2(keyFile));
12908
+ } catch (error) {
12909
+ return ioError("sign", error instanceof Error ? error.message : String(error), { file, keyFile, written: false });
12910
+ }
12911
+ const algorithm = keyAlgorithm(privateKey);
12912
+ if (!algorithm) {
12913
+ return commandResult(
12914
+ false,
12915
+ "sign",
12916
+ EXIT.failure,
12917
+ [diag("signature.unsupported_algorithm", "Only Ed25519 did:web keys are supported in this release")],
12918
+ { file, keyFile, written: false }
12919
+ );
12920
+ }
12921
+ const payload = integrityPayloadBytes(integrity);
12922
+ const signatureBytes = cryptoSign(null, dssePae(INTEGRITY_PAYLOAD_TYPE, payload), privateKey);
12923
+ const signature = {
12924
+ signer: `did-web:${domain}`,
12925
+ "key-id": keyId,
12926
+ "payload-digest": integrity.digest,
12927
+ algorithm,
12928
+ signature: signatureBytes.toString("base64"),
12929
+ "payload-type": INTEGRITY_PAYLOAD_TYPE
12930
+ };
12931
+ const frontmatter = {
12932
+ ...input.extract.frontmatter,
12933
+ signatures: [...Array.isArray(input.extract.frontmatter.signatures) ? input.extract.frontmatter.signatures : [], signature]
12934
+ };
12935
+ const destination = inPlace ? file : out;
12936
+ try {
12937
+ if (inPlace) atomicReplace(file, renderArtifact(frontmatter, input.extract.body));
12938
+ else atomicWrite(destination, renderArtifact(frontmatter, input.extract.body));
12939
+ } catch (error) {
12940
+ return ioError("sign", error instanceof Error ? error.message : String(error), { file, out: destination, written: false });
12941
+ }
12942
+ return commandResult(true, "sign", EXIT.ok, [], {
12943
+ summary: "did:web signature written",
12944
+ artifacts: [artifact("signed-artifact", destination, targetResult.target, String(integrity.digest))],
12945
+ nextActions: [
12946
+ nextAction(
12947
+ "verify-signature",
12948
+ "Verify the signed artifact with a trust policy",
12949
+ `mda verify ${destination} --policy <policy.json> --did-document <did-document.json>`
12950
+ )
12951
+ ],
12952
+ message: `signed ${destination}`,
12953
+ file,
12954
+ target: targetResult.target,
12955
+ profile: "did-web",
12956
+ signer: `did-web:${domain}`,
12957
+ keyId,
12958
+ payloadDigest: integrity.digest,
12959
+ out: destination,
12960
+ written: true
12961
+ });
12962
+ }
12963
+ function runGithubActionsSign(parsed) {
12964
+ if (parsed.options.has("--method") || parsed.options.has("--did") || parsed.options.has("--key-id") || parsed.options.has("--key-file") || parsed.options.has("--key") || parsed.options.has("--identity")) {
12965
+ return usage("sign", "did:web signing options cannot be combined with --profile github-actions");
12966
+ }
12967
+ const file = parsed.positional[0];
12968
+ const repo = oneOption(parsed.options, "--repo");
12969
+ const workflow = oneOption(parsed.options, "--workflow");
12970
+ const ref = oneOption(parsed.options, "--ref");
12971
+ const fixturePath = oneOption(parsed.options, "--offline-sigstore-fixture");
12972
+ if (!repo || !GITHUB_REPOSITORY.test(repo)) return usage("sign", "--repo must be a GitHub repository in owner/repo form");
12973
+ if (!workflow || workflow.trim() !== workflow || workflow.length === 0)
12974
+ return usage("sign", "--workflow must be a non-empty workflow file or identity");
12975
+ if (!ref || !GITHUB_REF.test(ref)) return usage("sign", "--ref must be an exact Git ref such as refs/heads/main or refs/tags/v1.1.0");
12976
+ if (!parsed.flags.has("--rekor")) return usage("sign", "--rekor is required for --profile github-actions");
12977
+ if (!fixturePath) return usage("sign", "--offline-sigstore-fixture <path> is required for --profile github-actions");
12978
+ const out = oneOption(parsed.options, "--out");
12979
+ const inPlace = parsed.flags.has("--in-place");
12980
+ if (out && inPlace || !out && !inPlace) return usage("sign", "Choose exactly one output mode: --out <file> or --in-place");
12981
+ if (out && existsSync3(out)) return ioError("sign", `Refusing to overwrite existing file: ${out}`, { file, out, written: false });
12982
+ const requestedTarget = parseTarget(oneOption(parsed.options, "--target") ?? "auto");
12983
+ if (!requestedTarget) return usage("sign", "--target must be source, SKILL.md, AGENTS.md, MCP-SERVER.md, or auto");
12984
+ const targetResult = resolveTarget(file, requestedTarget);
12985
+ if (!targetResult.ok) return targetResult.result("sign", file);
12986
+ const sidecar = oneOption(parsed.options, "--sidecar");
12987
+ const integrityArgs = ["verify", file, "--target", targetResult.target];
12988
+ if (sidecar) integrityArgs.push("--sidecar", sidecar);
12989
+ const integrityResult = runIntegrity(integrityArgs);
12990
+ if (!integrityResult.ok) {
12991
+ return commandResult(false, "sign", EXIT.failure, integrityResult.diagnostics, {
12992
+ summary: "Signing requires valid recorded integrity",
12993
+ nextActions: [
12994
+ nextAction(
12995
+ "write-integrity",
12996
+ "Record integrity before signing",
12997
+ `mda integrity compute ${file} --target ${targetResult.target} --write`
12998
+ )
12999
+ ],
13000
+ file,
13001
+ target: targetResult.target,
13002
+ written: false
13003
+ });
13004
+ }
13005
+ const input = readArtifact(file);
13006
+ if (!input.ok || input.extract.kind !== "ok" || !isRecord(input.extract.frontmatter)) {
13007
+ return commandResult(false, "sign", EXIT.failure, [diag("missing-required-frontmatter", "Signing requires frontmatter")], {
13008
+ file,
13009
+ target: targetResult.target,
13010
+ written: false
13011
+ });
13012
+ }
13013
+ const integrity = input.extract.frontmatter.integrity;
13014
+ if (!isRecord(integrity) || typeof integrity.digest !== "string") {
13015
+ return commandResult(false, "sign", EXIT.failure, [diag("missing-required-integrity", "Signing requires integrity")], {
13016
+ file,
13017
+ target: targetResult.target,
13018
+ written: false
13019
+ });
13020
+ }
13021
+ if (input.extract.frontmatter.signatures !== void 0 && !Array.isArray(input.extract.frontmatter.signatures)) {
13022
+ return commandResult(false, "sign", EXIT.failure, [diag("signature.invalid_entry", "Existing signatures must be an array")], {
13023
+ file,
13024
+ target: targetResult.target,
13025
+ written: false
13026
+ });
13027
+ }
13028
+ const fixtureResult = readSigstoreFixture(fixturePath, true);
13029
+ if (!fixtureResult.ok) {
13030
+ return commandResult(false, "sign", EXIT.failure, fixtureResult.diagnostics, {
13031
+ summary: "Sigstore fixture is invalid",
13032
+ file,
13033
+ target: targetResult.target,
13034
+ fixture: fixturePath,
13035
+ written: false
13036
+ });
13037
+ }
13038
+ const fixture = fixtureResult.fixture;
13039
+ const expectedSubject = `repo:${repo}:ref:${ref}`;
13040
+ if (fixture.issuer !== GITHUB_ACTIONS_ISSUER || fixture.subject !== expectedSubject || fixture.repository !== repo || fixture.workflow !== workflow || fixture.ref !== ref) {
13041
+ return commandResult(
13042
+ false,
13043
+ "sign",
13044
+ EXIT.failure,
13045
+ [diag("sigstore.identity_mismatch", "Sigstore fixture identity does not match the requested GitHub Actions release identity")],
13046
+ { file, target: targetResult.target, fixture: fixturePath, written: false }
13047
+ );
13048
+ }
13049
+ if (fixture.expectedPayloadDigest !== integrity.digest || fixture.rekor.payloadDigest !== integrity.digest) {
13050
+ return commandResult(
13051
+ false,
13052
+ "sign",
13053
+ EXIT.failure,
13054
+ [diag("sigstore.fixture_digest_mismatch", "Sigstore fixture digest does not match artifact integrity digest")],
13055
+ { file, target: targetResult.target, fixture: fixturePath, written: false }
13056
+ );
13057
+ }
13058
+ let privateKey;
13059
+ let publicKey;
13060
+ try {
13061
+ privateKey = createPrivateKey(fixture.privateKeyPem);
13062
+ publicKey = createPublicKey(fixture.publicKeyPem);
13063
+ } catch (error) {
13064
+ return commandResult(
13065
+ false,
13066
+ "sign",
13067
+ EXIT.failure,
13068
+ [diag("sigstore.fixture_invalid", error instanceof Error ? error.message : String(error))],
13069
+ {
13070
+ file,
13071
+ target: targetResult.target,
13072
+ fixture: fixturePath,
13073
+ written: false
13074
+ }
13075
+ );
13076
+ }
13077
+ if (keyAlgorithm(privateKey) !== fixture.algorithm || keyAlgorithm(publicKey) !== fixture.algorithm) {
13078
+ return commandResult(
13079
+ false,
13080
+ "sign",
13081
+ EXIT.failure,
13082
+ [diag("signature.unsupported_algorithm", "Only Ed25519 GitHub Actions fixture keys are supported in this release")],
13083
+ { file, target: targetResult.target, fixture: fixturePath, written: false }
13084
+ );
13085
+ }
13086
+ const payload = integrityPayloadBytes(integrity);
13087
+ const signatureBytes = cryptoSign(null, dssePae(INTEGRITY_PAYLOAD_TYPE, payload), privateKey);
13088
+ if (!cryptoVerify(null, dssePae(INTEGRITY_PAYLOAD_TYPE, payload), publicKey, signatureBytes)) {
13089
+ return commandResult(
13090
+ false,
13091
+ "sign",
13092
+ EXIT.failure,
13093
+ [diag("sigstore.fixture_invalid", "Sigstore fixture public key does not match private key")],
13094
+ {
13095
+ file,
13096
+ target: targetResult.target,
13097
+ fixture: fixturePath,
13098
+ written: false
13099
+ }
13100
+ );
13101
+ }
13102
+ const signature = {
13103
+ signer: `sigstore-oidc:${fixture.issuer}`,
13104
+ "key-id": fixture.keyId,
13105
+ "payload-digest": integrity.digest,
13106
+ algorithm: fixture.algorithm,
13107
+ signature: signatureBytes.toString("base64"),
13108
+ "payload-type": INTEGRITY_PAYLOAD_TYPE,
13109
+ "rekor-log-id": fixture.rekor.logId,
13110
+ "rekor-log-index": fixture.rekor.logIndex
13111
+ };
13112
+ const frontmatter = {
13113
+ ...input.extract.frontmatter,
13114
+ signatures: [...Array.isArray(input.extract.frontmatter.signatures) ? input.extract.frontmatter.signatures : [], signature]
13115
+ };
13116
+ const destination = inPlace ? file : out;
13117
+ try {
13118
+ if (inPlace) atomicReplace(file, renderArtifact(frontmatter, input.extract.body));
13119
+ else atomicWrite(destination, renderArtifact(frontmatter, input.extract.body));
13120
+ } catch (error) {
13121
+ return ioError("sign", error instanceof Error ? error.message : String(error), { file, out: destination, written: false });
13122
+ }
13123
+ return commandResult(true, "sign", EXIT.ok, [], {
13124
+ summary: "GitHub Actions Sigstore/Rekor signature written",
13125
+ artifacts: [artifact("signed-artifact", destination, targetResult.target, String(integrity.digest))],
13126
+ nextActions: [
13127
+ nextAction(
13128
+ "verify-github-actions-signature",
13129
+ "Verify the signed release artifact with the pinned trust policy and fixture",
13130
+ `mda verify ${destination} --policy <policy.json> --offline-sigstore-fixture ${fixturePath}`
13131
+ )
13132
+ ],
13133
+ message: `signed ${destination}`,
13134
+ file,
13135
+ target: targetResult.target,
13136
+ profile: "github-actions",
13137
+ signer: `sigstore-oidc:${fixture.issuer}`,
13138
+ keyId: fixture.keyId,
13139
+ payloadDigest: integrity.digest,
13140
+ rekorLogId: fixture.rekor.logId,
13141
+ rekorLogIndex: fixture.rekor.logIndex,
13142
+ out: destination,
13143
+ written: true
13144
+ });
13145
+ }
13146
+
13147
+ // src/cli/llmix-commands.ts
13148
+ var LLMIX_REGISTRY_TARGET = "llmix-registry";
13149
+ function runLlmix(args) {
13150
+ return migratedCommand("llmix", llmixMigrationReplacement(args));
13151
+ }
13152
+ function runRelease(args) {
13153
+ if (args[0] === "trust" && args[1] === "policy") return runLlmixTrustPolicy(args.slice(2), "release trust policy");
13154
+ if (args[0] === "prepare") return runLlmixReleasePlan(args.slice(1), "release prepare");
13155
+ if (args[0] === "finalize") {
13156
+ if (args.includes("--manifest") || args.includes("--snippet-format") || args.includes("--snippet-out")) {
13157
+ return runLlmixTrustSnippets(args.slice(1), "release finalize");
13158
+ }
13159
+ return runLlmixTrustManifest(args.slice(1), "release finalize");
13160
+ }
13161
+ return usage(
13162
+ "release",
13163
+ "Expected subcommand: release trust policy --target llmix-registry | release prepare --target llmix-registry | release finalize --target llmix-registry"
13164
+ );
13165
+ }
13166
+ function runLlmixTrustPolicy(args, command = "release trust policy") {
13167
+ const parsed = parseOptions(args);
13168
+ const err = unknownOptions(parsed, ["--target", "--profile", "--domain", "--min-signatures", "--out", "--repo", "--workflow", "--ref"]);
13169
+ if (err) return usage(command, err);
13170
+ const targetErr = releaseTargetError(command, parsed);
13171
+ if (targetErr) return targetErr;
13172
+ if (parsed.positional.length !== 0) return usage(command, `${command} takes no positional arguments`);
13173
+ const profile = oneOption(parsed.options, "--profile");
13174
+ if (!profile) return usage(command, "--profile <profile> is required");
13175
+ if (profile === "did-web") return runDidWebTrustPolicy(parsed.options, command);
13176
+ if (profile === "github-actions") return runGithubActionsTrustPolicy(parsed.options, command);
13177
+ return commandResult(
13178
+ false,
13179
+ command,
13180
+ EXIT.failure,
13181
+ [diag("trust_policy.unsupported_profile", `Unsupported trust policy profile: ${profile}`)],
13182
+ {
13183
+ summary: "Trust policy profile is not supported",
13184
+ nextActions: [
13185
+ nextAction(
13186
+ "use-did-web-policy-profile",
13187
+ "Use did:web for local deterministic signing",
13188
+ "mda release trust policy --target llmix-registry --profile did-web --domain example.com --out release-trust-policy.json"
13189
+ ),
13190
+ nextAction(
13191
+ "use-github-actions-policy-profile",
13192
+ "Use GitHub Actions Sigstore/Rekor for CI release signing",
13193
+ "mda release trust policy --target llmix-registry --profile github-actions --repo owner/repo --workflow release.yml --ref refs/heads/main --out release-trust-policy.json"
13194
+ )
13195
+ ]
13196
+ }
13197
+ );
13198
+ }
13199
+ function runLlmixReleasePlan(args, command = "release prepare") {
13200
+ const parsed = parseOptions(args);
13201
+ const err = unknownOptions(parsed, [
13202
+ "--target",
13203
+ "--source",
13204
+ "--registry-dir",
13205
+ "--policy",
13206
+ "--out",
13207
+ "--did-document",
13208
+ "--offline-sigstore-fixture"
13209
+ ]);
13210
+ if (err) return usage(command, err);
13211
+ const targetErr = releaseTargetError(command, parsed);
13212
+ if (targetErr) return targetErr;
13213
+ if (parsed.positional.length !== 0) return usage(command, `${command} takes no positional arguments`);
13214
+ const sourceDir = oneOption(parsed.options, "--source");
13215
+ const registryDir = oneOption(parsed.options, "--registry-dir");
13216
+ const policyPath = oneOption(parsed.options, "--policy");
13217
+ const out = oneOption(parsed.options, "--out");
13218
+ if (!sourceDir) return usage(command, "--source <dir> is required");
13219
+ if (!registryDir) return usage(command, "--registry-dir <dir> is required");
13220
+ if (!policyPath) return usage(command, "--policy <path> is required");
13221
+ if (!out) return usage(command, "--out <file> is required");
13222
+ if (existsSync4(out))
13223
+ return ioError(command, `Refusing to overwrite existing file: ${out}`, { sourceDir, registryDir, policy: policyPath });
13224
+ const policy = readJson(policyPath);
13225
+ if (!policy.ok) return ioError(command, policy.message, { sourceDir, registryDir, policy: policyPath });
13226
+ const policyValidation = validateJsonAgainst(policy.value, "trustPolicy");
13227
+ if (!policyValidation.ok)
13228
+ return commandResult(false, command, EXIT.failure, policyValidation.diagnostics, {
13229
+ sourceDir,
13230
+ registryDir,
13231
+ policy: policyPath
13232
+ });
13233
+ const sourceRoot = resolve3(sourceDir);
13234
+ const scanned = scanMdaSources(sourceRoot);
13235
+ if (!scanned.ok) return ioError(command, scanned.message, { sourceDir, registryDir, policy: policyPath });
13236
+ if (scanned.files.length === 0) {
13237
+ return commandResult(false, command, EXIT.failure, [diag("llmix.no_sources", "No .mda sources found under --source")], {
13238
+ summary: "Release prepare blocked",
13239
+ nextActions: [
13240
+ nextAction(
13241
+ "add-llmix-source",
13242
+ "Add signed LLMix .mda preset sources and retry",
13243
+ `mda release prepare --target llmix-registry --source ${sourceDir} --registry-dir ${registryDir} --policy ${policyPath} --out ${out}`
13244
+ )
13245
+ ],
13246
+ sourceDir,
13247
+ registryDir,
13248
+ policy: policyPath,
13249
+ written: false
13250
+ });
13251
+ }
13252
+ const diagnostics = [];
13253
+ const sources = [];
13254
+ const plannedRegistryEntries = /* @__PURE__ */ new Map();
13255
+ const didDocument = oneOption(parsed.options, "--did-document");
13256
+ const sigstoreFixture = oneOption(parsed.options, "--offline-sigstore-fixture");
13257
+ for (const file of scanned.files) {
13258
+ const sourceRelativePath = relativePath(sourceRoot, file);
13259
+ const validation = validateArtifact(file, "source");
13260
+ if (!validation.ok) {
13261
+ diagnostics.push(...validation.diagnostics.map((d) => ({ ...d, path: file })));
13262
+ continue;
13263
+ }
13264
+ const integrity = runIntegrity(["verify", file, "--target", "source"]);
13265
+ if (!integrity.ok) {
13266
+ diagnostics.push(...integrity.diagnostics.map((d) => ({ ...d, path: file })));
13267
+ continue;
13268
+ }
13269
+ const verifyArgs = [file, "--target", "source", "--policy", policyPath];
13270
+ if (didDocument) verifyArgs.push("--did-document", didDocument);
13271
+ if (sigstoreFixture) verifyArgs.push("--offline-sigstore-fixture", sigstoreFixture);
13272
+ const verified = runVerify(verifyArgs);
13273
+ if (!verified.ok) {
13274
+ diagnostics.push(...verified.diagnostics.map((d) => ({ ...d, path: file })));
13275
+ continue;
13276
+ }
13277
+ const artifactRead = readArtifact(file);
13278
+ if (!artifactRead.ok || artifactRead.extract.kind !== "ok" || !isRecord(artifactRead.extract.frontmatter)) {
13279
+ diagnostics.push(diag("llmix.source_unreadable", "Source frontmatter could not be read after verification", { path: file }));
13280
+ continue;
13281
+ }
13282
+ const identity = llmixPresetIdentity(artifactRead.extract.frontmatter);
13283
+ if (!identity) {
13284
+ diagnostics.push(
13285
+ diag("llmix.release_identity_missing", "Source is missing a valid metadata.snoai-llmix module and preset", { path: file })
13286
+ );
13287
+ continue;
13288
+ }
13289
+ const canonical = canonicalizeFromFile(file, "source", null);
13290
+ if (!canonical.ok) {
13291
+ diagnostics.push(...canonical.diagnostics.map((d) => ({ ...d, path: file })));
13292
+ continue;
13293
+ }
13294
+ const signerIdentity = firstTrustedSignerIdentity(verified.trustedSignerIdentities);
13295
+ if (!signerIdentity) {
13296
+ diagnostics.push(
13297
+ diag("trust_policy.no_trusted_signature", "Verified source did not return a trusted signer identity", { path: file })
13298
+ );
13299
+ continue;
13300
+ }
13301
+ const expectedRegistryEntryIdentity = `${identity.module}/${identity.preset}`;
13302
+ const existingSourcePath = plannedRegistryEntries.get(expectedRegistryEntryIdentity);
13303
+ if (existingSourcePath) {
13304
+ diagnostics.push(
13305
+ diag(
13306
+ "llmix.duplicate_registry_entry",
13307
+ `Multiple sources target registry entry ${expectedRegistryEntryIdentity}; first source is ${existingSourcePath}`,
13308
+ { path: file }
13309
+ )
13310
+ );
13311
+ continue;
13312
+ }
13313
+ plannedRegistryEntries.set(expectedRegistryEntryIdentity, sourceRelativePath);
13314
+ sources.push({
13315
+ module: identity.module,
13316
+ preset: identity.preset,
13317
+ sourcePath: sourceRelativePath,
13318
+ canonicalSourceDigest: computeDigest(canonical.bytes, "sha256"),
13319
+ signaturePayloadDigest: signerIdentity.payloadDigest,
13320
+ signerIdentity,
13321
+ expectedRegistryEntryIdentity,
13322
+ expectedRegistryEntryPath: `${identity.module}/${identity.preset}.json`
13323
+ });
13324
+ }
13325
+ if (diagnostics.length > 0) {
13326
+ return commandResult(false, command, EXIT.failure, diagnostics, {
13327
+ summary: "Release prepare blocked",
13328
+ nextActions: [
13329
+ nextAction(
13330
+ "fix-source-validation",
13331
+ "Fix validation, integrity, and signature diagnostics before release planning",
13332
+ `mda validate <source.mda> --target source`
13333
+ ),
13334
+ nextAction(
13335
+ "verify-source-signature",
13336
+ "Verify each signed preset with the selected trust policy",
13337
+ `mda verify <signed.mda> --target source --policy ${policyPath}`
13338
+ )
13339
+ ],
13340
+ sourceDir,
13341
+ registryDir,
13342
+ policy: policyPath,
13343
+ sourceCount: scanned.files.length,
13344
+ written: false
13345
+ });
13346
+ }
13347
+ sources.sort(
13348
+ (left, right) => String(left.expectedRegistryEntryIdentity).localeCompare(String(right.expectedRegistryEntryIdentity)) || String(left.sourcePath).localeCompare(String(right.sourcePath))
13349
+ );
13350
+ const sourceSetDigest = computeDigest(Buffer.from(jcs({ sources }), "utf8"), "sha256");
13351
+ const releasePlan = {
13352
+ version: 1,
13353
+ kind: "llmix-release-plan",
13354
+ sourceDir,
13355
+ registryDir,
13356
+ policy: policyPath,
13357
+ sourceSetDigest,
13358
+ sources,
13359
+ checklist: [
13360
+ { id: "source-validation", ok: true, count: sources.length },
13361
+ { id: "integrity-verification", ok: true, count: sources.length },
13362
+ { id: "signature-verification", ok: true, count: sources.length },
13363
+ {
13364
+ id: "registry-publish",
13365
+ ok: false,
13366
+ external: true,
13367
+ reason: "Publish with LLMix trustedRuntime=true using this verified source set."
13368
+ }
13369
+ ],
13370
+ publish: {
13371
+ external: true,
13372
+ trustedRuntime: true,
13373
+ next: "Run the LLMix registry publisher with this verified source set, then sign the registry root before generating the trust manifest."
13374
+ }
13375
+ };
13376
+ try {
13377
+ atomicWrite(out, `${JSON.stringify(releasePlan, null, 2)}
13378
+ `);
13379
+ } catch (error) {
13380
+ return ioError(command, error instanceof Error ? error.message : String(error), {
13381
+ sourceDir,
13382
+ registryDir,
13383
+ policy: policyPath,
13384
+ out,
13385
+ written: false
13386
+ });
13387
+ }
13388
+ return commandResult(true, command, EXIT.ok, [], {
13389
+ summary: `Prepared LLMix registry release plan for ${sources.length} source(s)`,
13390
+ artifacts: [artifact("llmix-release-plan", out, void 0, sourceSetDigest)],
13391
+ nextActions: [
13392
+ externalNextAction(
13393
+ "publish-llmix-registry",
13394
+ "Publish the verified source set with LLMix trustedRuntime=true",
13395
+ "use the LLMix registry publisher, then sign the registry root"
13396
+ ),
13397
+ externalNextAction(
13398
+ "prepare-trust-manifest-inputs",
13399
+ "After registry publication, collect the signed registry root and root trust policy for trust manifest generation",
13400
+ "wait for the signed registry root evidence before running the trust manifest step",
13401
+ false
13402
+ )
13403
+ ],
13404
+ message: `wrote ${out}`,
13405
+ sourceDir,
13406
+ registryDir,
13407
+ policy: policyPath,
13408
+ out,
13409
+ sourceSetDigest,
13410
+ sourceCount: sources.length,
13411
+ releasePlan,
13412
+ written: true
13413
+ });
13414
+ }
13415
+ function runLlmixTrustManifest(args, command = "release finalize") {
13416
+ const parsed = parseOptions(args);
13417
+ const err = unknownOptions(parsed, [
13418
+ "--target",
13419
+ "--registry-dir",
13420
+ "--registry-root",
13421
+ "--release-plan",
13422
+ "--policy",
13423
+ "--expected-root-digest",
13424
+ "--derive-root-digest",
13425
+ "--minimum-revision",
13426
+ "--minimum-published-at",
13427
+ "--high-watermark",
13428
+ "--out",
13429
+ "--did-document",
13430
+ "--offline-sigstore-fixture"
13431
+ ]);
13432
+ if (err) return usage(command, err);
13433
+ const targetErr = releaseTargetError(command, parsed);
13434
+ if (targetErr) return targetErr;
13435
+ if (parsed.positional.length !== 0) return usage(command, `${command} takes no positional arguments`);
13436
+ const registryDir = oneOption(parsed.options, "--registry-dir");
13437
+ const registryRootPath = oneOption(parsed.options, "--registry-root");
13438
+ const releasePlanPath = oneOption(parsed.options, "--release-plan");
13439
+ const policyPath = oneOption(parsed.options, "--policy");
13440
+ const out = oneOption(parsed.options, "--out");
13441
+ const expectedRootDigestOption = oneOption(parsed.options, "--expected-root-digest");
13442
+ const deriveRootDigest = parsed.flags.has("--derive-root-digest");
13443
+ if (!registryDir) return usage(command, "--registry-dir <dir> is required");
13444
+ if (!registryRootPath) return usage(command, "--registry-root <file> is required");
13445
+ if (!releasePlanPath) return usage(command, "--release-plan <file> is required");
13446
+ if (!policyPath) return usage(command, "--policy <path> is required");
13447
+ if (!out) return usage(command, "--out <file> is required");
13448
+ if (Boolean(expectedRootDigestOption) === deriveRootDigest)
13449
+ return usage(command, "Choose exactly one: --expected-root-digest <digest> or --derive-root-digest");
13450
+ if (expectedRootDigestOption && !DIGEST_PATTERN.test(expectedRootDigestOption))
13451
+ return usage(command, "--expected-root-digest must be a sha256/sha384/sha512 digest");
13452
+ if (existsSync4(out))
13453
+ return ioError(command, `Refusing to overwrite existing file: ${out}`, {
13454
+ registryDir,
13455
+ registryRoot: registryRootPath,
13456
+ out
13457
+ });
13458
+ if (pathResolvesInsideDir(out, registryDir)) {
13459
+ return commandResult(
13460
+ false,
13461
+ command,
13462
+ EXIT.failure,
13463
+ [diag("release.trust_artifact_inside_registry", "--out must resolve outside --registry-dir")],
13464
+ {
13465
+ summary: "Release trust artifact output must be outside the registry directory",
13466
+ nextActions: [
13467
+ nextAction(
13468
+ "write-external-manifest",
13469
+ "Write the deployment trust manifest outside config/llm or the selected registry directory",
13470
+ `mda release finalize --target llmix-registry --registry-dir ${registryDir} --registry-root ${registryRootPath} --release-plan ${releasePlanPath} --policy ${policyPath} --derive-root-digest --out release/llmix-trust.json`
13471
+ )
13472
+ ],
13473
+ registryDir,
13474
+ out,
13475
+ written: false
13476
+ }
13477
+ );
13478
+ }
13479
+ if (!pathResolvesInsideDir(registryRootPath, registryDir)) {
13480
+ return commandResult(
13481
+ false,
13482
+ command,
13483
+ EXIT.failure,
13484
+ [diag("release.registry_root_outside_registry", "--registry-root must resolve inside --registry-dir")],
13485
+ {
13486
+ summary: "Registry-root evidence must belong to the selected registry directory",
13487
+ nextActions: [
13488
+ nextAction(
13489
+ "use-registry-root-from-registry",
13490
+ "Pass the signed registry-root evidence from the selected registry directory",
13491
+ `mda release finalize --target llmix-registry --registry-dir ${registryDir} --registry-root ${registryDir}/snapshots/current/registry-root.json --release-plan ${releasePlanPath} --policy ${policyPath} --derive-root-digest --out ${out}`
13492
+ )
13493
+ ],
13494
+ registryDir,
13495
+ registryRoot: registryRootPath,
13496
+ out,
13497
+ written: false
13498
+ }
13499
+ );
13500
+ }
13501
+ const policy = readJson(policyPath);
13502
+ if (!policy.ok) return ioError(command, policy.message, { registryDir, registryRoot: registryRootPath, policy: policyPath });
13503
+ const policyValidation = validateJsonAgainst(policy.value, "trustPolicy");
13504
+ if (!policyValidation.ok)
13505
+ return commandResult(false, command, EXIT.failure, policyValidation.diagnostics, {
13506
+ registryDir,
13507
+ registryRoot: registryRootPath,
13508
+ policy: policyPath,
13509
+ written: false
13510
+ });
13511
+ const releasePlan = readJson(releasePlanPath);
13512
+ if (!releasePlan.ok) return ioError(command, releasePlan.message, { releasePlan: releasePlanPath, written: false });
13513
+ const registryRoot = readJson(registryRootPath);
13514
+ if (!registryRoot.ok) return ioError(command, registryRoot.message, { registryRoot: registryRootPath, written: false });
13515
+ const diagnostics = [];
13516
+ const rootEvidence = validateRegistryRootEvidence(registryRoot.value);
13517
+ diagnostics.push(...rootEvidence.diagnostics);
13518
+ const releasePlanEvidence = validateReleasePlanEvidence(releasePlan.value);
13519
+ diagnostics.push(...releasePlanEvidence.diagnostics);
13520
+ if (rootEvidence.ok && releasePlanEvidence.ok) {
13521
+ const expectedRootDigest = expectedRootDigestOption ?? rootEvidence.rootDigest;
13522
+ if (expectedRootDigest !== rootEvidence.rootDigest) {
13523
+ diagnostics.push(diag("llmix.root_digest_mismatch", "Expected root digest does not match signed registry-root evidence"));
13524
+ }
13525
+ diagnostics.push(
13526
+ ...freshnessDiagnostics(rootEvidence.root, {
13527
+ minimumRevision: oneOption(parsed.options, "--minimum-revision"),
13528
+ minimumPublishedAt: oneOption(parsed.options, "--minimum-published-at"),
13529
+ highWatermark: oneOption(parsed.options, "--high-watermark")
13530
+ })
13531
+ );
13532
+ diagnostics.push(...sourceSetDiagnostics(rootEvidence.root, releasePlanEvidence.releasePlan));
13533
+ const signatureVerification = verifySignatureEntries(
13534
+ rootEvidence.root.signatures,
13535
+ rootEvidence.root.integrity,
13536
+ policy.value,
13537
+ oneOption(parsed.options, "--did-document"),
13538
+ oneOption(parsed.options, "--offline-sigstore-fixture")
13539
+ );
13540
+ if (signatureVerification.malformed) {
13541
+ diagnostics.push(diag("signature.invalid_entry", "Registry-root signature entry is malformed"));
13542
+ } else if (signatureVerification.trusted.size === 0) {
13543
+ diagnostics.push(
13544
+ ...signatureVerification.rejectedTrusted.length > 0 ? signatureVerification.rejectedTrusted : [diag("trust_policy.no_trusted_signature", "No registry-root signature matched the trust policy")]
13545
+ );
13546
+ } else {
13547
+ const minSignatures = trustPolicyMinSignatures(policy.value);
13548
+ if (signatureVerification.trusted.size < minSignatures) {
13549
+ diagnostics.push(
13550
+ diag(
13551
+ "trust_policy.insufficient_trusted_signatures",
13552
+ `${signatureVerification.trusted.size} trusted signer identities < ${minSignatures}`
13553
+ )
13554
+ );
13555
+ }
13556
+ }
13557
+ if (diagnostics.length === 0) {
13558
+ const trustedSignerIdentity = firstTrustedSignerIdentity(signatureVerification.trustedSignerIdentities);
13559
+ const manifest = {
13560
+ version: 1,
13561
+ kind: "llmix-trust-manifest",
13562
+ expectedRootDigest,
13563
+ sourceSetDigest: rootEvidence.root.sourceSetDigest,
13564
+ releasePlanDigest: computeDigest(Buffer.from(jcs(releasePlan.value), "utf8"), "sha256"),
13565
+ registryRootTrustPolicy: policy.value,
13566
+ rekorPolicy: isRecord(policy.value) && isRecord(policy.value.rekor) ? policy.value.rekor : null,
13567
+ minimumRevision: oneOption(parsed.options, "--minimum-revision") ?? null,
13568
+ minimumPublishedAt: oneOption(parsed.options, "--minimum-published-at") ?? null,
13569
+ highWatermark: oneOption(parsed.options, "--high-watermark") ?? rootEvidence.root.highWatermark,
13570
+ registryRootSignerIdentity: trustedSignerIdentity,
13571
+ registryRoot: {
13572
+ path: registryRootPath,
13573
+ revision: rootEvidence.root.revision,
13574
+ publishedAt: rootEvidence.root.publishedAt,
13575
+ highWatermark: rootEvidence.root.highWatermark
13576
+ },
13577
+ releasePlan: { path: releasePlanPath, sourceCount: releasePlanEvidence.releasePlan.sources.length }
13578
+ };
13579
+ try {
13580
+ atomicWrite(out, `${JSON.stringify(manifest, null, 2)}
13581
+ `);
13582
+ } catch (error) {
13583
+ return ioError(command, error instanceof Error ? error.message : String(error), { out, written: false });
13584
+ }
13585
+ return commandResult(true, command, EXIT.ok, [], {
13586
+ summary: "Finalized external LLMix deployment trust manifest",
13587
+ artifacts: [artifact("llmix-trust-manifest", out, void 0, expectedRootDigest)],
13588
+ nextActions: [
13589
+ externalNextAction(
13590
+ "install-external-trust-manifest",
13591
+ "Install this external trust manifest in the deployment configuration outside config/llm",
13592
+ `copy ${out} into the deployment config path consumed by secure LLMix startup`,
13593
+ false
13594
+ ),
13595
+ externalNextAction(
13596
+ "deploy-signed-registry",
13597
+ "Deploy only the signed registry files covered by this manifest and release plan",
13598
+ `publish ${registryDir} with registry root ${registryRootPath} and release plan ${releasePlanPath}`,
13599
+ false
13600
+ )
13601
+ ],
13602
+ message: `wrote ${out}`,
13603
+ registryDir,
13604
+ registryRoot: registryRootPath,
13605
+ releasePlan: releasePlanPath,
13606
+ out,
13607
+ expectedRootDigest,
13608
+ sourceSetDigest: rootEvidence.root.sourceSetDigest,
13609
+ written: true
13610
+ });
13611
+ }
13612
+ }
13613
+ return commandResult(false, command, EXIT.failure, diagnostics, {
13614
+ summary: "Release finalize blocked",
13615
+ nextActions: [
13616
+ nextAction(
13617
+ "fix-registry-root",
13618
+ "Fix registry-root evidence, signature, freshness, or source-set mismatches before writing deployment anchors",
13619
+ `mda release finalize --target llmix-registry --registry-dir ${registryDir} --registry-root ${registryRootPath} --release-plan ${releasePlanPath} --policy ${policyPath} --derive-root-digest --out ${out}`
13620
+ )
13621
+ ],
13622
+ registryDir,
13623
+ registryRoot: registryRootPath,
13624
+ releasePlan: releasePlanPath,
13625
+ out,
13626
+ written: false
13627
+ });
13628
+ }
13629
+ function runLlmixTrustSnippets(args, command = "release finalize") {
13630
+ const parsed = parseOptions(args);
13631
+ const err = unknownOptions(parsed, ["--target", "--registry-dir", "--manifest", "--snippet-format", "--snippet-out"]);
13632
+ if (err) return usage(command, err);
13633
+ const targetErr = releaseTargetError(command, parsed);
13634
+ if (targetErr) return targetErr;
13635
+ if (parsed.positional.length !== 0) return usage(command, `${command} takes no positional arguments`);
13636
+ const registryDir = oneOption(parsed.options, "--registry-dir");
13637
+ const manifestPath = oneOption(parsed.options, "--manifest");
13638
+ const format = oneOption(parsed.options, "--snippet-format");
13639
+ const out = oneOption(parsed.options, "--snippet-out");
13640
+ if (!registryDir) {
13641
+ return commandResult(
13642
+ false,
13643
+ command,
13644
+ EXIT.failure,
13645
+ [diag("release.registry_dir_required", "--registry-dir <dir> is required for snippet output")],
13646
+ {
13647
+ summary: "Release snippet generation blocked",
13648
+ nextActions: [
13649
+ nextAction(
13650
+ "add-registry-dir",
13651
+ "Pass the registry directory so MDA can prove trust artifacts remain outside it",
13652
+ `mda release finalize --target llmix-registry --registry-dir <registry-dir> --manifest ${manifestPath ?? "<manifest>"} --snippet-format ${format ?? "env"} --snippet-out ${out ?? "release/trust.env"}`
13653
+ )
13654
+ ],
13655
+ manifest: manifestPath,
13656
+ format,
13657
+ out,
13658
+ written: false
13659
+ }
13660
+ );
13661
+ }
13662
+ if (!manifestPath) return usage(command, "--manifest <path> is required");
13663
+ if (!format) return usage(command, "--snippet-format <format> is required");
13664
+ if (!LLMIX_SNIPPET_FORMATS.has(format))
13665
+ return usage(command, "--snippet-format must be json, env, kubernetes, github-actions, terraform, typescript, python, or rust");
13666
+ if (!out) return usage(command, "--snippet-out <file> is required");
13667
+ if (existsSync4(out)) {
13668
+ return ioError(command, `Refusing to overwrite existing file: ${out}`, {
13669
+ manifest: manifestPath,
13670
+ format,
13671
+ out,
13672
+ written: false
13673
+ });
13674
+ }
13675
+ if (pathResolvesInsideDir(manifestPath, registryDir) || pathResolvesInsideDir(out, registryDir)) {
13676
+ return commandResult(
13677
+ false,
13678
+ command,
13679
+ EXIT.failure,
13680
+ [diag("release.trust_artifact_inside_registry", "--manifest and --snippet-out must resolve outside --registry-dir")],
13681
+ {
13682
+ summary: "Release trust artifacts must be outside the registry directory",
13683
+ nextActions: [
13684
+ nextAction(
13685
+ "write-external-snippet",
13686
+ "Write deployment snippets outside config/llm or the selected registry directory",
13687
+ `mda release finalize --target llmix-registry --registry-dir ${registryDir} --manifest release/llmix-trust.json --snippet-format ${format} --snippet-out release/trust.${format}`
13688
+ )
13689
+ ],
13690
+ registryDir,
13691
+ manifest: manifestPath,
13692
+ format,
13693
+ out,
13694
+ written: false
13695
+ }
13696
+ );
13697
+ }
13698
+ const manifestRead = readJson(manifestPath);
13699
+ if (!manifestRead.ok) return ioError(command, manifestRead.message, { manifest: manifestPath, out, written: false });
13700
+ const manifest = validateTrustManifestEvidence(manifestRead.value);
13701
+ if (!manifest.ok) {
13702
+ return commandResult(false, command, EXIT.failure, manifest.diagnostics, {
13703
+ summary: "Release snippet generation blocked",
13704
+ nextActions: [
13705
+ nextAction(
13706
+ "regenerate-trust-manifest",
13707
+ "Regenerate a valid external trust manifest before producing deployment snippets",
13708
+ "mda release finalize --target llmix-registry --registry-dir <registry> --registry-root <registry-root.json> --release-plan <release-plan.json> --policy <policy.json> --derive-root-digest --out release/llmix-trust.json"
13709
+ )
13710
+ ],
13711
+ manifest: manifestPath,
13712
+ format,
13713
+ out,
13714
+ written: false
13715
+ });
13716
+ }
13717
+ const content = renderLlmixTrustSnippet(format, manifestPath, manifest.manifest);
13718
+ try {
13719
+ atomicWrite(out, content);
13720
+ } catch (error) {
13721
+ return ioError(command, error instanceof Error ? error.message : String(error), {
13722
+ manifest: manifestPath,
13723
+ format,
13724
+ out,
13725
+ written: false
13726
+ });
13727
+ }
13728
+ return commandResult(true, command, EXIT.ok, [], {
13729
+ summary: `Wrote ${format} LLMix deployment trust snippet`,
13730
+ artifacts: [artifact("llmix-trust-snippet", out)],
13731
+ nextActions: [
13732
+ externalNextAction(
13733
+ "install-deployment-snippet",
13734
+ "Install this snippet in deployment configuration outside config/llm",
13735
+ `wire ${out} into the deployment environment that starts secure LLMix`,
13736
+ false
13737
+ ),
13738
+ nextAction(
13739
+ "run-release-doctor",
13740
+ "Check the source, registry, and manifest before deployment",
13741
+ `mda doctor release --target llmix-registry --source <source-dir> --registry-dir <registry-dir> --release-plan ${manifest.manifest.releasePlan.path} --manifest ${manifestPath}`,
13742
+ false
13743
+ )
13744
+ ],
13745
+ message: `wrote ${out}`,
13746
+ manifest: manifestPath,
13747
+ format,
13748
+ out,
13749
+ written: true
13750
+ });
13751
+ }
13752
+ function runDoctor(args) {
13753
+ if (args[0] === "llmix") return migratedCommand("doctor llmix", "mda doctor release --target llmix-registry");
13754
+ if (args[0] !== "release") return usage("doctor", "Expected subcommand: doctor release --target llmix-registry");
13755
+ return runDoctorRelease(args.slice(1));
13756
+ }
13757
+ function runDoctorRelease(args) {
13758
+ const command = "doctor release";
13759
+ const parsed = parseOptions(args);
13760
+ const err = unknownOptions(parsed, [
13761
+ "--target",
13762
+ "--source",
13763
+ "--registry-dir",
13764
+ "--release-plan",
13765
+ "--manifest",
13766
+ "--did-document",
13767
+ "--offline-sigstore-fixture"
13768
+ ]);
13769
+ if (err) return usage(command, err);
13770
+ const targetErr = releaseTargetError(command, parsed);
13771
+ if (targetErr) return targetErr;
13772
+ if (parsed.positional.length !== 0) return usage(command, `${command} takes no positional arguments`);
13773
+ const sourceDir = oneOption(parsed.options, "--source");
13774
+ const registryDir = oneOption(parsed.options, "--registry-dir");
13775
+ const releasePlanPath = oneOption(parsed.options, "--release-plan");
13776
+ const manifestPath = oneOption(parsed.options, "--manifest");
13777
+ const didDocumentPath = oneOption(parsed.options, "--did-document");
13778
+ const sigstoreFixturePath = oneOption(parsed.options, "--offline-sigstore-fixture");
13779
+ if (!sourceDir) return usage(command, "--source <dir> is required");
13780
+ if (!registryDir) return usage(command, "--registry-dir <dir> is required");
13781
+ if (!releasePlanPath) return usage(command, "--release-plan <path> is required");
13782
+ if (!manifestPath) return usage(command, "--manifest <path> is required");
13783
+ if (!existsSync4(releasePlanPath)) {
13784
+ return commandResult(false, command, EXIT.failure, [diag("llmix.release_plan_missing", "Release plan is missing")], {
13785
+ summary: "Release doctor found readiness issues",
13786
+ nextActions: [
13787
+ nextAction(
13788
+ "create-release-plan",
13789
+ "Prepare the verified release plan before final deployment checks",
13790
+ `mda release prepare --target llmix-registry --source ${sourceDir} --registry-dir ${registryDir} --policy <policy.json> --out ${releasePlanPath}`
13791
+ )
13792
+ ],
13793
+ sourceDir,
13794
+ registryDir,
13795
+ releasePlan: releasePlanPath,
13796
+ manifest: manifestPath,
13797
+ readOnly: true,
13798
+ written: false
13799
+ });
13800
+ }
13801
+ if (!existsSync4(manifestPath)) {
13802
+ return commandResult(false, command, EXIT.failure, [diag("llmix.manifest_missing", "Trust manifest is missing")], {
13803
+ summary: "Release doctor found readiness issues",
13804
+ nextActions: [
13805
+ nextAction(
13806
+ "create-trust-manifest",
13807
+ "Generate the external deployment trust manifest before release",
13808
+ `mda release finalize --target llmix-registry --registry-dir ${registryDir} --registry-root <registry-root.json> --release-plan ${releasePlanPath} --policy <policy.json> --derive-root-digest --out ${manifestPath}`
13809
+ )
13810
+ ],
13811
+ sourceDir,
13812
+ registryDir,
13813
+ releasePlan: releasePlanPath,
13814
+ manifest: manifestPath,
13815
+ readOnly: true,
13816
+ written: false
13817
+ });
13818
+ }
13819
+ const manifestRead = readJson(manifestPath);
13820
+ if (!manifestRead.ok)
13821
+ return ioError(command, manifestRead.message, {
13822
+ sourceDir,
13823
+ registryDir,
13824
+ releasePlan: releasePlanPath,
13825
+ manifest: manifestPath,
13826
+ readOnly: true,
13827
+ written: false
13828
+ });
13829
+ const diagnostics = [];
13830
+ const checks = [];
13831
+ if (pathResolvesInsideDir(manifestPath, registryDir)) {
13832
+ diagnostics.push(diag("release.trust_artifact_inside_registry", "--manifest must resolve outside --registry-dir"));
13833
+ checks.push({ id: "manifest-placement", ok: false });
13834
+ } else {
13835
+ checks.push({ id: "manifest-placement", ok: true });
13836
+ }
13837
+ const requestedReleasePlanRead = readJson(releasePlanPath);
13838
+ if (!requestedReleasePlanRead.ok) diagnostics.push(diag("filesystem.io", requestedReleasePlanRead.message, { path: releasePlanPath }));
13839
+ const requestedReleasePlanEvidence = requestedReleasePlanRead.ok ? validateReleasePlanEvidence(requestedReleasePlanRead.value) : null;
13840
+ if (requestedReleasePlanEvidence) diagnostics.push(...requestedReleasePlanEvidence.diagnostics);
13841
+ checks.push({ id: "release-plan-input", ok: Boolean(requestedReleasePlanEvidence?.ok) });
13842
+ const manifest = validateTrustManifestEvidence(manifestRead.value);
13843
+ diagnostics.push(...manifest.diagnostics);
13844
+ checks.push({ id: "trust-manifest-shape", ok: manifest.ok });
13845
+ if (manifest.ok) {
13846
+ const registryRootInsideRegistry = pathResolvesInsideDir(manifest.manifest.registryRoot.path, registryDir);
13847
+ if (!registryRootInsideRegistry) {
13848
+ diagnostics.push(
13849
+ diag("release.registry_root_outside_registry", "Trust manifest registryRoot.path must resolve inside --registry-dir")
13850
+ );
13851
+ }
13852
+ checks.push({ id: "registry-root-placement", ok: registryRootInsideRegistry });
13853
+ const registryRootRead = registryRootInsideRegistry ? readJson(manifest.manifest.registryRoot.path) : null;
13854
+ if (registryRootRead && !registryRootRead.ok)
13855
+ diagnostics.push(diag("filesystem.io", registryRootRead.message, { path: manifest.manifest.registryRoot.path }));
13856
+ if (manifest.manifest.releasePlan.path !== releasePlanPath)
13857
+ diagnostics.push(diag("llmix.release_plan_digest_mismatch", "Trust manifest releasePlan.path does not match --release-plan"));
13858
+ const releasePlanRead = readJson(releasePlanPath);
13859
+ if (!releasePlanRead.ok) diagnostics.push(diag("filesystem.io", releasePlanRead.message, { path: manifest.manifest.releasePlan.path }));
13860
+ const rootEvidence = registryRootRead?.ok ? validateRegistryRootEvidence(registryRootRead.value) : null;
13861
+ const releasePlanEvidence = releasePlanRead.ok ? validateReleasePlanEvidence(releasePlanRead.value) : null;
13862
+ if (rootEvidence) diagnostics.push(...rootEvidence.diagnostics);
13863
+ if (releasePlanEvidence) diagnostics.push(...releasePlanEvidence.diagnostics);
13864
+ checks.push({ id: "registry-root-evidence", ok: Boolean(rootEvidence?.ok) });
13865
+ checks.push({ id: "release-plan-evidence", ok: Boolean(releasePlanEvidence?.ok) });
13866
+ if (rootEvidence?.ok && releasePlanEvidence?.ok) {
13867
+ if (manifest.manifest.expectedRootDigest !== rootEvidence.rootDigest)
13868
+ diagnostics.push(diag("llmix.root_digest_mismatch", "Trust manifest expectedRootDigest does not match registry-root evidence"));
13869
+ if (manifest.manifest.sourceSetDigest !== rootEvidence.root.sourceSetDigest)
13870
+ diagnostics.push(diag("llmix.source_set_digest_mismatch", "Trust manifest sourceSetDigest does not match registry-root evidence"));
13871
+ if (manifest.manifest.releasePlanDigest !== computeDigest(Buffer.from(jcs(releasePlanRead.value), "utf8"), "sha256"))
13872
+ diagnostics.push(diag("llmix.release_plan_digest_mismatch", "Trust manifest releasePlanDigest does not match release plan"));
13873
+ diagnostics.push(...sourceSetDiagnostics(rootEvidence.root, releasePlanEvidence.releasePlan));
13874
+ diagnostics.push(
13875
+ ...freshnessDiagnostics(rootEvidence.root, {
13876
+ minimumRevision: manifest.manifest.minimumRevision,
13877
+ minimumPublishedAt: manifest.manifest.minimumPublishedAt,
13878
+ highWatermark: manifest.manifest.highWatermark
13879
+ })
13880
+ );
13881
+ const signatureVerification = verifySignatureEntries(
13882
+ rootEvidence.root.signatures,
13883
+ rootEvidence.root.integrity,
13884
+ manifest.manifest.registryRootTrustPolicy,
13885
+ didDocumentPath,
13886
+ sigstoreFixturePath
13887
+ );
13888
+ if (signatureVerification.malformed) {
13889
+ diagnostics.push(diag("signature.invalid_entry", "Registry-root signature entry is malformed"));
13890
+ } else if (signatureVerification.trusted.size < trustPolicyMinSignatures(manifest.manifest.registryRootTrustPolicy)) {
13891
+ diagnostics.push(
13892
+ ...signatureVerification.rejectedTrusted.length > 0 ? signatureVerification.rejectedTrusted : [diag("trust_policy.no_trusted_signature", "Registry-root signatures do not match the embedded trust policy")]
13893
+ );
13894
+ }
13895
+ }
13896
+ checks.push({
13897
+ id: "registry-root-trust",
13898
+ ok: diagnostics.length === 0 && Boolean(rootEvidence?.ok && releasePlanEvidence?.ok)
13899
+ });
13900
+ }
13901
+ const sourceReadiness = doctorSourceReadiness(sourceDir);
13902
+ diagnostics.push(...sourceReadiness.diagnostics);
13903
+ checks.push({ id: "source-readiness", ok: sourceReadiness.ok });
13904
+ if (diagnostics.length > 0) {
13905
+ return commandResult(false, command, EXIT.failure, diagnostics, {
13906
+ summary: "Release doctor found readiness issues",
13907
+ nextActions: [
13908
+ nextAction(
13909
+ "fix-release-state",
13910
+ "Fix the reported source, registry, manifest, freshness, or placement issue and run doctor again",
13911
+ `mda doctor release --target llmix-registry --source ${sourceDir} --registry-dir ${registryDir} --release-plan ${releasePlanPath} --manifest ${manifestPath}`
13912
+ )
13913
+ ],
13914
+ sourceDir,
13915
+ registryDir,
13916
+ releasePlan: releasePlanPath,
13917
+ manifest: manifestPath,
13918
+ checks,
13919
+ readOnly: true,
13920
+ written: false
13921
+ });
13922
+ }
13923
+ return commandResult(true, command, EXIT.ok, [], {
13924
+ summary: "Release state is ready for secure LLMix deployment",
13925
+ nextActions: [
13926
+ externalNextAction(
13927
+ "deploy-secure-llmix",
13928
+ "Deploy the signed registry with the external trust manifest and generated deployment snippet",
13929
+ "use the deployment system that mounts the manifest outside config/llm",
13930
+ false
13931
+ )
13932
+ ],
13933
+ sourceDir,
13934
+ registryDir,
13935
+ releasePlan: releasePlanPath,
13936
+ manifest: manifestPath,
13937
+ checks,
13938
+ sourceCount: sourceReadiness.sourceCount,
13939
+ readOnly: true,
13940
+ written: false
13941
+ });
13942
+ }
13943
+ function validateTrustManifestEvidence(value) {
13944
+ const diagnostics = [];
13945
+ if (!isRecord(value)) {
13946
+ return { ok: false, diagnostics: [diag("llmix.trust_manifest_invalid", "Trust manifest must be a JSON object")] };
13947
+ }
13948
+ if (value.kind !== "llmix-trust-manifest")
13949
+ diagnostics.push(diag("llmix.trust_manifest_invalid", "Trust manifest kind must be llmix-trust-manifest"));
13950
+ if (value.version !== 1) diagnostics.push(diag("llmix.trust_manifest_invalid", "Trust manifest version must be 1"));
13951
+ if (typeof value.expectedRootDigest !== "string" || !DIGEST_PATTERN.test(value.expectedRootDigest))
13952
+ diagnostics.push(diag("llmix.trust_manifest_invalid", "Trust manifest expectedRootDigest must be a digest"));
13953
+ if (typeof value.sourceSetDigest !== "string" || !DIGEST_PATTERN.test(value.sourceSetDigest))
13954
+ diagnostics.push(diag("llmix.trust_manifest_invalid", "Trust manifest sourceSetDigest must be a digest"));
13955
+ if (typeof value.releasePlanDigest !== "string" || !DIGEST_PATTERN.test(value.releasePlanDigest))
13956
+ diagnostics.push(diag("llmix.trust_manifest_invalid", "Trust manifest releasePlanDigest must be a digest"));
13957
+ if (!isNullableString(value.minimumRevision))
13958
+ diagnostics.push(diag("llmix.trust_manifest_invalid", "Trust manifest minimumRevision must be a string or null"));
13959
+ if (!isNullableString(value.minimumPublishedAt))
13960
+ diagnostics.push(diag("llmix.trust_manifest_invalid", "Trust manifest minimumPublishedAt must be a string or null"));
13961
+ if (!isNullableString(value.highWatermark))
13962
+ diagnostics.push(diag("llmix.trust_manifest_invalid", "Trust manifest highWatermark must be a string or null"));
13963
+ if (!isRecord(value.registryRoot)) {
13964
+ diagnostics.push(diag("llmix.trust_manifest_invalid", "Trust manifest registryRoot must be an object"));
13965
+ } else {
13966
+ if (typeof value.registryRoot.path !== "string" || value.registryRoot.path.length === 0)
13967
+ diagnostics.push(diag("llmix.trust_manifest_invalid", "Trust manifest registryRoot.path must be a non-empty string"));
13968
+ if (typeof value.registryRoot.revision !== "string" || value.registryRoot.revision.length === 0)
13969
+ diagnostics.push(diag("llmix.trust_manifest_invalid", "Trust manifest registryRoot.revision must be a non-empty string"));
13970
+ if (typeof value.registryRoot.publishedAt !== "string" || Number.isNaN(Date.parse(value.registryRoot.publishedAt)))
13971
+ diagnostics.push(diag("llmix.trust_manifest_invalid", "Trust manifest registryRoot.publishedAt must be an ISO timestamp"));
13972
+ if (typeof value.registryRoot.highWatermark !== "string" || value.registryRoot.highWatermark.length === 0)
13973
+ diagnostics.push(diag("llmix.trust_manifest_invalid", "Trust manifest registryRoot.highWatermark must be a non-empty string"));
13974
+ }
13975
+ if (!isRecord(value.releasePlan)) {
13976
+ diagnostics.push(diag("llmix.trust_manifest_invalid", "Trust manifest releasePlan must be an object"));
13977
+ } else {
13978
+ if (typeof value.releasePlan.path !== "string" || value.releasePlan.path.length === 0)
13979
+ diagnostics.push(diag("llmix.trust_manifest_invalid", "Trust manifest releasePlan.path must be a non-empty string"));
13980
+ const sourceCount = value.releasePlan.sourceCount;
13981
+ if (typeof sourceCount !== "number" || !Number.isInteger(sourceCount) || sourceCount < 0)
13982
+ diagnostics.push(diag("llmix.trust_manifest_invalid", "Trust manifest releasePlan.sourceCount must be a non-negative integer"));
13983
+ }
13984
+ if (diagnostics.length > 0) return { ok: false, diagnostics };
13985
+ return { ok: true, diagnostics, manifest: value };
13986
+ }
13987
+ function isNullableString(value) {
13988
+ return value === null || typeof value === "string";
13989
+ }
13990
+ function renderLlmixTrustSnippet(format, manifestPath, manifest) {
13991
+ const vars = {
13992
+ LLMIX_TRUST_MANIFEST: manifestPath,
13993
+ LLMIX_EXPECTED_ROOT_DIGEST: manifest.expectedRootDigest,
13994
+ LLMIX_SOURCE_SET_DIGEST: manifest.sourceSetDigest,
13995
+ LLMIX_RELEASE_PLAN_DIGEST: manifest.releasePlanDigest,
13996
+ LLMIX_REGISTRY_ROOT: manifest.registryRoot.path,
13997
+ LLMIX_RELEASE_PLAN: manifest.releasePlan.path,
13998
+ LLMIX_HIGH_WATERMARK: manifest.highWatermark ?? ""
13999
+ };
14000
+ if (format === "json") return `${JSON.stringify(vars, null, 2)}
14001
+ `;
14002
+ if (format === "env")
14003
+ return `${Object.entries(vars).map(([key, value]) => `${key}=${JSON.stringify(value)}`).join("\n")}
14004
+ `;
14005
+ if (format === "kubernetes") {
14006
+ return [
14007
+ "apiVersion: v1",
14008
+ "kind: ConfigMap",
14009
+ "metadata:",
14010
+ " name: llmix-trust",
14011
+ "data:",
14012
+ ...Object.entries(vars).map(([key, value]) => ` ${key}: ${JSON.stringify(value)}`),
14013
+ ""
14014
+ ].join("\n");
14015
+ }
14016
+ if (format === "github-actions") {
14017
+ return ["env:", ...Object.entries(vars).map(([key, value]) => ` ${key}: ${JSON.stringify(value)}`), ""].join("\n");
14018
+ }
14019
+ if (format === "terraform") {
14020
+ return [
14021
+ "locals {",
14022
+ " llmix_trust = {",
14023
+ ...Object.entries(vars).map(([key, value]) => ` ${key} = ${JSON.stringify(value)}`),
14024
+ " }",
14025
+ "}",
14026
+ ""
14027
+ ].join("\n");
14028
+ }
14029
+ if (format === "typescript") return `export const llmixTrust = ${JSON.stringify(vars, null, 2)} as const;
14030
+ `;
14031
+ if (format === "python") return `LLMIX_TRUST = ${JSON.stringify(vars, null, 2)}
14032
+ `;
14033
+ return `${Object.entries(vars).map(([key, value]) => `pub const ${key}: &str = ${JSON.stringify(value)};`).join("\n")}
14034
+ `;
14035
+ }
14036
+ function doctorSourceReadiness(sourceDir) {
14037
+ const diagnostics = [];
14038
+ const scanned = scanMdaSources(resolve3(sourceDir));
14039
+ if (!scanned.ok) return { ok: false, diagnostics: [diag("filesystem.io", scanned.message)], sourceCount: 0 };
14040
+ if (scanned.files.length === 0) {
14041
+ return {
14042
+ ok: false,
14043
+ diagnostics: [diag("llmix.no_sources", "No .mda sources found under --source")],
14044
+ sourceCount: 0
14045
+ };
14046
+ }
14047
+ for (const file of scanned.files) {
14048
+ const validation = validateArtifact(file, "source");
14049
+ if (!validation.ok) diagnostics.push(...validation.diagnostics.map((d) => ({ ...d, path: file })));
14050
+ const integrity = runIntegrity(["verify", file, "--target", "source"]);
14051
+ if (!integrity.ok) diagnostics.push(...integrity.diagnostics.map((d) => ({ ...d, path: file })));
14052
+ }
14053
+ return { ok: diagnostics.length === 0, diagnostics, sourceCount: scanned.files.length };
14054
+ }
14055
+ function validateRegistryRootEvidence(value) {
14056
+ const diagnostics = [];
14057
+ if (!isRecord(value)) {
14058
+ return { ok: false, diagnostics: [diag("llmix.registry_root_invalid", "Registry-root evidence must be a JSON object")] };
14059
+ }
14060
+ if (value.kind !== "llmix-registry-root")
14061
+ diagnostics.push(diag("llmix.registry_root_invalid", "Registry-root kind must be llmix-registry-root"));
14062
+ if (value.version !== 1) diagnostics.push(diag("llmix.registry_root_invalid", "Registry-root version must be 1"));
14063
+ if (typeof value.revision !== "string" || value.revision.length === 0)
14064
+ diagnostics.push(diag("llmix.registry_root_invalid", "Registry-root revision must be a non-empty string"));
14065
+ if (typeof value.publishedAt !== "string" || Number.isNaN(Date.parse(value.publishedAt)))
14066
+ diagnostics.push(diag("llmix.registry_root_invalid", "Registry-root publishedAt must be an ISO timestamp"));
14067
+ if (typeof value.highWatermark !== "string" || value.highWatermark.length === 0)
14068
+ diagnostics.push(diag("llmix.registry_root_invalid", "Registry-root highWatermark must be a non-empty string"));
14069
+ if (typeof value.sourceSetDigest !== "string" || !DIGEST_PATTERN.test(value.sourceSetDigest))
14070
+ diagnostics.push(diag("llmix.registry_root_invalid", "Registry-root sourceSetDigest must be a digest"));
14071
+ const sources = Array.isArray(value.sources) ? value.sources : null;
14072
+ if (!sources) {
14073
+ diagnostics.push(diag("llmix.registry_root_invalid", "Registry-root sources must be an array"));
14074
+ } else {
14075
+ for (const [index, source] of sources.entries()) {
14076
+ if (!isRecord(source)) diagnostics.push(diag("llmix.registry_root_invalid", `Registry-root sources[${index}] must be a JSON object`));
14077
+ }
14078
+ }
14079
+ if (!isRecord(value.integrity) || value.integrity.algorithm !== "sha256" || typeof value.integrity.digest !== "string") {
14080
+ diagnostics.push(diag("llmix.registry_root_invalid", "Registry-root integrity must contain sha256 digest"));
14081
+ }
14082
+ if (!Array.isArray(value.signatures) || value.signatures.length === 0)
14083
+ diagnostics.push(diag("missing-required-signature", "Registry-root evidence requires signatures[]"));
14084
+ const rootDigest = computeDigest(Buffer.from(jcs(unsignedRegistryRoot(value)), "utf8"), "sha256");
14085
+ if (isRecord(value.integrity) && typeof value.integrity.digest === "string" && value.integrity.digest !== rootDigest) {
14086
+ diagnostics.push(diag("integrity.mismatch", "Registry-root integrity digest does not match canonical evidence bytes"));
14087
+ }
14088
+ if (diagnostics.length > 0) return { ok: false, diagnostics };
14089
+ const root = {
14090
+ revision: value.revision,
14091
+ publishedAt: value.publishedAt,
14092
+ highWatermark: value.highWatermark,
14093
+ sourceSetDigest: value.sourceSetDigest,
14094
+ sources,
14095
+ integrity: value.integrity,
14096
+ signatures: value.signatures
14097
+ };
14098
+ return {
14099
+ ok: true,
14100
+ diagnostics,
14101
+ rootDigest,
14102
+ root
14103
+ };
14104
+ }
14105
+ function unsignedRegistryRoot(root) {
14106
+ const unsigned = {};
14107
+ for (const [key, value] of Object.entries(root)) {
14108
+ if (key !== "integrity" && key !== "signatures") unsigned[key] = value;
14109
+ }
14110
+ return unsigned;
14111
+ }
14112
+ function validateReleasePlanEvidence(value) {
14113
+ const diagnostics = [];
14114
+ if (!isRecord(value)) {
14115
+ return { ok: false, diagnostics: [diag("llmix.release_plan_invalid", "Release plan must be a JSON object")] };
14116
+ }
14117
+ if (value.kind !== "llmix-release-plan")
14118
+ diagnostics.push(diag("llmix.release_plan_invalid", "Release plan kind must be llmix-release-plan"));
14119
+ if (typeof value.sourceSetDigest !== "string" || !DIGEST_PATTERN.test(value.sourceSetDigest))
14120
+ diagnostics.push(diag("llmix.release_plan_invalid", "Release plan sourceSetDigest must be a digest"));
14121
+ const sources = Array.isArray(value.sources) ? value.sources : null;
14122
+ if (!sources) {
14123
+ diagnostics.push(diag("llmix.release_plan_invalid", "Release plan sources must be an array"));
14124
+ } else {
14125
+ for (const [index, source] of sources.entries()) {
14126
+ if (!isRecord(source)) diagnostics.push(diag("llmix.release_plan_invalid", `Release plan sources[${index}] must be a JSON object`));
14127
+ }
14128
+ }
14129
+ if (diagnostics.length > 0) return { ok: false, diagnostics };
14130
+ const releasePlan = {
14131
+ sourceSetDigest: value.sourceSetDigest,
14132
+ sources
14133
+ };
14134
+ return {
14135
+ ok: true,
14136
+ diagnostics,
14137
+ releasePlan
14138
+ };
14139
+ }
14140
+ function freshnessDiagnostics(root, requirements) {
14141
+ const diagnostics = [];
14142
+ if (requirements.minimumRevision) {
14143
+ const comparison = compareMonotonicRequirement(root.revision, requirements.minimumRevision, "revision");
14144
+ if (!comparison.ok) diagnostics.push(...comparison.diagnostics);
14145
+ else if (comparison.value < 0)
14146
+ diagnostics.push(diag("llmix.freshness_revision_rollback", "Registry-root revision is below minimumRevision"));
14147
+ }
14148
+ if (requirements.minimumPublishedAt) {
14149
+ const publishedAt = Date.parse(root.publishedAt);
14150
+ const minimumPublishedAt = Date.parse(requirements.minimumPublishedAt);
14151
+ if (Number.isNaN(minimumPublishedAt))
14152
+ diagnostics.push(diag("llmix.freshness_invalid", "--minimum-published-at must be an ISO timestamp"));
14153
+ else if (publishedAt < minimumPublishedAt)
14154
+ diagnostics.push(diag("llmix.freshness_published_at_rollback", "Registry-root publishedAt is below minimumPublishedAt"));
14155
+ }
14156
+ if (requirements.highWatermark) {
14157
+ const comparison = compareMonotonicRequirement(root.highWatermark, requirements.highWatermark, "highWatermark");
14158
+ if (!comparison.ok) diagnostics.push(...comparison.diagnostics);
14159
+ else if (comparison.value < 0)
14160
+ diagnostics.push(diag("llmix.high_watermark_rollback", "Registry-root highWatermark is below the required high-watermark"));
14161
+ }
14162
+ return diagnostics;
14163
+ }
14164
+ function compareMonotonicRequirement(actual, requirement, label) {
14165
+ const actualValue = parseMonotonicValue(actual, `registry-root ${label}`);
14166
+ const requiredValue = parseMonotonicValue(requirement, `required ${label}`);
14167
+ const diagnostics = [...actualValue.diagnostics, ...requiredValue.diagnostics];
14168
+ if (!actualValue.ok || !requiredValue.ok) return { ok: false, diagnostics };
14169
+ if (actualValue.value.kind !== requiredValue.value.kind) {
14170
+ return {
14171
+ ok: false,
14172
+ diagnostics: [diag("llmix.freshness_invalid", `${label} and required ${label} must use the same monotonic format`)]
14173
+ };
14174
+ }
14175
+ return { ok: true, value: compareMonotonicValues(actualValue.value, requiredValue.value) };
14176
+ }
14177
+ function parseMonotonicValue(value, label) {
14178
+ if (/^(0|[1-9][0-9]*)$/.test(value)) {
14179
+ return { ok: true, diagnostics: [], value: { kind: "integer", value } };
14180
+ }
14181
+ const compactUtc = value.match(/^([0-9]{4})-([0-9]{2})-([0-9]{2})T([0-9]{2})([0-9]{2})([0-9]{2})Z$/);
14182
+ const millis = compactUtc ? Date.UTC(
14183
+ Number(compactUtc[1]),
14184
+ Number(compactUtc[2]) - 1,
14185
+ Number(compactUtc[3]),
14186
+ Number(compactUtc[4]),
14187
+ Number(compactUtc[5]),
14188
+ Number(compactUtc[6])
14189
+ ) : Date.parse(value);
14190
+ if (!Number.isNaN(millis)) {
14191
+ return { ok: true, diagnostics: [], value: { kind: "timestamp", value: millis } };
14192
+ }
14193
+ return {
14194
+ ok: false,
14195
+ diagnostics: [
14196
+ diag(
14197
+ "llmix.freshness_invalid",
14198
+ `${label} must be a decimal integer, ISO timestamp, or compact UTC timestamp like 2026-05-09T120000Z`
14199
+ )
14200
+ ]
14201
+ };
14202
+ }
14203
+ function compareMonotonicValues(actual, requirement) {
14204
+ if (actual.kind === "timestamp" && requirement.kind === "timestamp") return actual.value - requirement.value;
14205
+ if (actual.kind === "integer" && requirement.kind === "integer") {
14206
+ if (actual.value.length !== requirement.value.length) return actual.value.length - requirement.value.length;
14207
+ return actual.value.localeCompare(requirement.value);
14208
+ }
14209
+ return 0;
14210
+ }
14211
+ function sourceSetDiagnostics(root, releasePlan) {
14212
+ const diagnostics = [];
14213
+ if (root.sourceSetDigest !== releasePlan.sourceSetDigest) {
14214
+ diagnostics.push(diag("llmix.source_set_digest_mismatch", "Registry-root sourceSetDigest does not match the release plan"));
14215
+ }
14216
+ const rootSources = /* @__PURE__ */ new Map();
14217
+ for (const source of root.sources) {
14218
+ const identity = registryEntryIdentity(source);
14219
+ if (!identity) {
14220
+ diagnostics.push(diag("llmix.registry_root_identity_mismatch", "Registry-root source is missing registry entry identity"));
14221
+ continue;
14222
+ }
14223
+ if (rootSources.has(identity)) {
14224
+ diagnostics.push(diag("llmix.registry_root_duplicate_preset", `Registry-root has duplicate preset ${identity}`));
14225
+ continue;
14226
+ }
14227
+ rootSources.set(identity, source);
14228
+ }
14229
+ const releaseIdentities = /* @__PURE__ */ new Set();
14230
+ for (const source of releasePlan.sources) {
14231
+ const identity = registryEntryIdentity(source);
14232
+ if (!identity) {
14233
+ diagnostics.push(diag("llmix.release_plan_invalid", "Release plan source is missing registry entry identity"));
14234
+ continue;
14235
+ }
14236
+ if (releaseIdentities.has(identity)) {
14237
+ diagnostics.push(diag("llmix.release_plan_duplicate_preset", `Release plan has duplicate preset ${identity}`));
14238
+ continue;
14239
+ }
14240
+ releaseIdentities.add(identity);
14241
+ const rootSource = rootSources.get(identity);
14242
+ if (!rootSource) {
14243
+ diagnostics.push(diag("llmix.registry_root_missing_preset", `Registry-root is missing preset ${identity}`));
14244
+ continue;
14245
+ }
14246
+ if (rootSource.canonicalSourceDigest !== source.canonicalSourceDigest) {
14247
+ diagnostics.push(diag("llmix.registry_root_stale_digest", `Registry-root digest for ${identity} does not match the release plan`));
14248
+ }
14249
+ if (rootSource.registryEntryPath !== source.expectedRegistryEntryPath) {
14250
+ diagnostics.push(diag("llmix.registry_root_identity_mismatch", `Registry-root path for ${identity} does not match the release plan`));
14251
+ }
14252
+ }
14253
+ for (const identity of rootSources.keys()) {
14254
+ if (!releaseIdentities.has(identity))
14255
+ diagnostics.push(diag("llmix.registry_root_extra_preset", `Registry-root has extra preset ${identity}`));
14256
+ }
14257
+ return diagnostics;
14258
+ }
14259
+ function registryEntryIdentity(source) {
14260
+ const identity = source.registryEntryIdentity ?? source.expectedRegistryEntryIdentity;
14261
+ return typeof identity === "string" && identity.length > 0 ? identity : null;
14262
+ }
14263
+ function pathResolvesInsideDir(candidate, rootDir) {
14264
+ const root = realPathIfPossible(rootDir);
14265
+ const resolvedCandidate = realPathCandidate(candidate);
14266
+ return resolvedCandidate === root || resolvedCandidate.startsWith(`${root}${sep}`);
14267
+ }
14268
+ function realPathCandidate(candidate) {
14269
+ const resolved = resolve3(candidate);
14270
+ try {
14271
+ return realpathSync(resolved);
14272
+ } catch {
14273
+ const parent = realPathIfPossible(dirname2(resolved));
14274
+ return join2(parent, basename(resolved));
14275
+ }
14276
+ }
14277
+ function realPathIfPossible(path) {
14278
+ try {
14279
+ return realpathSync(path);
14280
+ } catch {
14281
+ return resolve3(path);
14282
+ }
14283
+ }
14284
+ function scanMdaSources(root) {
14285
+ const files = [];
14286
+ try {
14287
+ const visit = (dir) => {
14288
+ for (const entry of readdirSync2(dir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
14289
+ const path = join2(dir, entry.name);
14290
+ if (entry.isDirectory()) visit(path);
14291
+ else if (entry.isFile() && entry.name.endsWith(".mda")) files.push(path);
14292
+ }
14293
+ };
14294
+ visit(root);
14295
+ return { ok: true, files };
14296
+ } catch (error) {
14297
+ return { ok: false, message: error instanceof Error ? error.message : String(error) };
14298
+ }
14299
+ }
14300
+ function relativePath(root, file) {
14301
+ return relative2(root, file).split(sep).join("/");
14302
+ }
14303
+ function llmixPresetIdentity(frontmatter) {
14304
+ const metadata = isRecord(frontmatter.metadata) ? frontmatter.metadata : null;
14305
+ const namespace = metadata && isRecord(metadata["snoai-llmix"]) ? metadata["snoai-llmix"] : null;
14306
+ if (!namespace || typeof namespace.module !== "string" || typeof namespace.preset !== "string") return null;
14307
+ if (!LLMIX_MODULE_NAME2.test(namespace.module) || !LLMIX_PRESET_NAME2.test(namespace.preset)) return null;
14308
+ return { module: namespace.module, preset: namespace.preset };
14309
+ }
14310
+ function firstTrustedSignerIdentity(value) {
14311
+ if (!Array.isArray(value)) return null;
14312
+ for (const entry of value) {
14313
+ if (!isRecord(entry)) continue;
14314
+ if (entry.type !== "did-web" && entry.type !== "sigstore-oidc") continue;
14315
+ if (typeof entry.signer !== "string" || typeof entry.keyId !== "string" || typeof entry.payloadDigest !== "string") continue;
14316
+ const identity = {
14317
+ type: entry.type,
14318
+ signer: entry.signer,
14319
+ keyId: entry.keyId,
14320
+ payloadDigest: entry.payloadDigest
14321
+ };
14322
+ if (typeof entry.subject === "string") identity.subject = entry.subject;
14323
+ if (typeof entry.rekorLogId === "string") identity.rekorLogId = entry.rekorLogId;
14324
+ if (typeof entry.rekorLogIndex === "number") identity.rekorLogIndex = entry.rekorLogIndex;
14325
+ return identity;
14326
+ }
14327
+ return null;
14328
+ }
14329
+ function releaseTargetError(command, parsed) {
14330
+ if ("error" in parsed && parsed.error) return usage(command, parsed.error);
14331
+ const target = oneOption(parsed.options, "--target");
14332
+ if (!target) return usage(command, "--target llmix-registry is required");
14333
+ if (target === LLMIX_REGISTRY_TARGET) return null;
14334
+ return commandResult(false, command, EXIT.failure, [diag("release.unsupported_target", `Unsupported release target: ${target}`)], {
14335
+ summary: "Release target is not supported",
14336
+ nextActions: [
14337
+ nextAction(
14338
+ "use-llmix-registry-target",
14339
+ "Use the supported LLMix registry release target",
14340
+ `mda ${command} --target ${LLMIX_REGISTRY_TARGET}`
14341
+ )
14342
+ ],
14343
+ target,
14344
+ supportedTargets: [LLMIX_REGISTRY_TARGET]
14345
+ });
14346
+ }
14347
+ function migratedCommand(command, replacement) {
14348
+ return commandResult(false, command, EXIT.failure, [diag("release.command_migrated", `Use ${replacement} instead of mda ${command}`)], {
14349
+ summary: "Command moved to the generic release workflow",
14350
+ nextActions: [nextAction("use-release-command", "Use the generic release command surface", replacement)]
14351
+ });
14352
+ }
14353
+ function llmixMigrationReplacement(args) {
14354
+ if (args[0] === "release" && args[1] === "plan") return "mda release prepare --target llmix-registry";
14355
+ if (args[0] === "trust" && args[1] === "policy") return "mda release trust policy --target llmix-registry";
14356
+ if (args[0] === "trust" && args[1] === "manifest") return "mda release finalize --target llmix-registry";
14357
+ if (args[0] === "trust" && args[1] === "snippets") {
14358
+ return "mda release finalize --target llmix-registry --registry-dir <registry-dir> --manifest <manifest> --snippet-format <format> --snippet-out <path>";
14359
+ }
14360
+ return "mda release --help";
14361
+ }
14362
+ function runDidWebTrustPolicy(options, command) {
14363
+ const profile = "did-web";
14364
+ const domain = oneOption(options, "--domain");
14365
+ if (!domain || didWebDomainFromDid(`did:web:${domain}`) !== domain) return usage(command, "--domain must be a valid did:web domain");
14366
+ const minRaw = oneOption(options, "--min-signatures") ?? "1";
14367
+ const minSignatures = Number(minRaw);
14368
+ if (!Number.isInteger(minSignatures) || minSignatures < 1) return usage(command, "--min-signatures must be a positive integer");
14369
+ const policy = { version: 1, minSignatures, trustedSigners: [{ type: "did-web", domain }] };
14370
+ const validation = validateJsonAgainst(policy, "trustPolicy");
14371
+ if (!validation.ok) return commandResult(false, command, EXIT.failure, validation.diagnostics, { profile, domain, minSignatures });
14372
+ const out = oneOption(options, "--out");
14373
+ const write = writeTrustPolicy(command, out, policy, { profile, domain, minSignatures });
14374
+ if (!write.ok) return write;
14375
+ return commandResult(true, command, EXIT.ok, [], {
14376
+ summary: out ? `Wrote did:web trust policy to ${out}` : "Generated did:web trust policy",
14377
+ artifacts: out ? [artifact("trust-policy", out)] : [],
14378
+ nextActions: out ? [
14379
+ nextAction(
14380
+ "verify-signed-preset",
14381
+ "Verify a signed preset with this policy",
14382
+ `mda verify signed.mda --target source --policy ${out} --did-document did-web-document.json`
14383
+ )
14384
+ ] : [externalNextAction("save-trust-policy", "Save this policy JSON before release verification", "write the policy bytes to disk")],
14385
+ message: out ? `wrote ${out}` : JSON.stringify(policy, null, 2),
14386
+ profile,
14387
+ domain,
14388
+ minSignatures,
14389
+ policy,
14390
+ out,
14391
+ written: Boolean(out)
14392
+ });
14393
+ }
14394
+ function runGithubActionsTrustPolicy(options, command) {
14395
+ const profile = "github-actions";
14396
+ const repo = oneOption(options, "--repo");
14397
+ const workflow = oneOption(options, "--workflow");
14398
+ const ref = oneOption(options, "--ref");
14399
+ if (!repo || !GITHUB_REPOSITORY.test(repo)) return usage(command, "--repo must be a GitHub repository in owner/repo form");
14400
+ if (!workflow || workflow.trim() !== workflow || workflow.length === 0)
14401
+ return usage(command, "--workflow must be a non-empty workflow file or identity");
14402
+ if (!ref || !GITHUB_REF.test(ref)) return usage(command, "--ref must be an exact Git ref such as refs/heads/main or refs/tags/v1.1.0");
14403
+ const subject = `repo:${repo}:ref:${ref}`;
14404
+ const policy = {
14405
+ version: 1,
14406
+ trustedSigners: [
14407
+ {
14408
+ type: "sigstore-oidc",
14409
+ issuer: GITHUB_ACTIONS_ISSUER,
14410
+ subject,
14411
+ repository: repo,
14412
+ workflow,
14413
+ ref
14414
+ }
14415
+ ],
14416
+ rekor: { url: SIGSTORE_REKOR_URL }
14417
+ };
14418
+ const validation = validateJsonAgainst(policy, "trustPolicy");
14419
+ if (!validation.ok) return commandResult(false, command, EXIT.failure, validation.diagnostics, { profile, repo, workflow, ref });
14420
+ const out = oneOption(options, "--out");
14421
+ const write = writeTrustPolicy(command, out, policy, { profile, repo, workflow, ref });
14422
+ if (!write.ok) return write;
14423
+ return commandResult(true, command, EXIT.ok, [], {
14424
+ summary: out ? `Wrote GitHub Actions trust policy to ${out}` : "Generated GitHub Actions trust policy",
14425
+ artifacts: out ? [artifact("trust-policy", out)] : [],
14426
+ nextActions: out ? [
14427
+ nextAction(
14428
+ "sign-github-actions-release",
14429
+ "Sign the release artifact with GitHub Actions Sigstore/Rekor evidence",
14430
+ `mda sign release.mda --profile github-actions --repo ${repo} --workflow ${workflow} --ref ${ref} --rekor --offline-sigstore-fixture sigstore-fixture.json --out signed-release.mda`
14431
+ ),
14432
+ nextAction(
14433
+ "verify-github-actions-release",
14434
+ "Verify the signed release with this pinned policy",
14435
+ `mda verify signed-release.mda --policy ${out} --offline-sigstore-fixture sigstore-fixture.json`
14436
+ )
14437
+ ] : [externalNextAction("save-trust-policy", "Save this policy JSON before release verification", "write the policy bytes to disk")],
14438
+ message: out ? `wrote ${out}` : JSON.stringify(policy, null, 2),
14439
+ profile,
14440
+ repo,
14441
+ workflow,
14442
+ ref,
14443
+ policy,
14444
+ out,
14445
+ written: Boolean(out)
14446
+ });
14447
+ }
14448
+ function writeTrustPolicy(command, out, policy, context) {
14449
+ if (!out) {
14450
+ return commandResult(true, command, EXIT.ok, [], { written: false });
14451
+ }
14452
+ if (existsSync4(out))
14453
+ return commandResult(false, command, EXIT.io, [diag("filesystem.io", `Refusing to overwrite existing file: ${out}`)], {
14454
+ ...context,
14455
+ out,
14456
+ written: false
14457
+ });
14458
+ try {
14459
+ atomicWrite(out, `${JSON.stringify(policy, null, 2)}
14460
+ `);
14461
+ } catch (error) {
14462
+ return ioError(command, error instanceof Error ? error.message : String(error), {
14463
+ ...context,
14464
+ out,
14465
+ written: false
14466
+ });
14467
+ }
14468
+ return commandResult(true, command, EXIT.ok, [], { ...context, out, written: true });
14469
+ }
14470
+
14471
+ // src/cli/main.ts
14472
+ async function main() {
14473
+ const { globals, args } = splitGlobals(process.argv.slice(2));
14474
+ try {
14475
+ if (args.length === 0 || args.includes("--help") || args.includes("-h")) {
14476
+ process.stdout.write(HELP);
14477
+ process.exit(EXIT.ok);
14478
+ }
14479
+ const command = args[0];
14480
+ const rest = args.slice(1);
14481
+ const result = await runCommand(command, rest, globals);
14482
+ writeResult(result, globals);
14483
+ process.exit(result.exitCode);
14484
+ } catch (error) {
14485
+ const result = commandResult(false, "internal", EXIT.internal, [
14486
+ diag("internal-error", error instanceof Error ? error.message : String(error))
14487
+ ]);
14488
+ writeResult(result, globals);
14489
+ process.exit(result.exitCode);
14490
+ }
14491
+ }
14492
+ async function runCommand(command, args, globals) {
14493
+ if (command === "init") return runInit(args, globals);
14494
+ if (command === "validate") return runValidate(args);
14495
+ if (command === "compile") return runCompile(args);
14496
+ if (command === "canonicalize") return runCanonicalize(args, globals);
14497
+ if (command === "integrity") return runIntegrity(args);
14498
+ if (command === "verify") return runVerify(args);
14499
+ if (command === "sign") return runSign(args);
14500
+ if (command === "release") return runRelease(args);
14501
+ if (command === "llmix") return runLlmix(args);
14502
+ if (command === "doctor") return runDoctor(args);
14503
+ if (command === "conformance") return runConformance(args);
14504
+ return usage("root", `Unknown command: ${command}`);
14505
+ }
14506
+
11291
14507
  // src/index.ts
11292
14508
  void main();
11293
14509
  /*! Bundled license information: