@xemahq/xema 0.26.0 → 0.28.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.
Files changed (230) hide show
  1. package/README.md +70 -37
  2. package/dist/assets/distribution-assets.manifest.json +1 -2
  3. package/dist/cli.js +2 -2
  4. package/dist/cli.js.map +1 -1
  5. package/dist/commands/dev.js +1 -0
  6. package/dist/commands/dev.js.map +1 -1
  7. package/dist/commands/down.d.ts.map +1 -1
  8. package/dist/commands/down.js +7 -1
  9. package/dist/commands/down.js.map +1 -1
  10. package/dist/commands/edition.d.ts +3 -0
  11. package/dist/commands/edition.d.ts.map +1 -0
  12. package/dist/commands/edition.js +46 -0
  13. package/dist/commands/edition.js.map +1 -0
  14. package/dist/commands/init.js +2 -2
  15. package/dist/commands/init.js.map +1 -1
  16. package/dist/commands/serve.js +1 -0
  17. package/dist/commands/serve.js.map +1 -1
  18. package/dist/commands/up.js +1 -0
  19. package/dist/commands/up.js.map +1 -1
  20. package/dist/dev/env-defaults.d.ts.map +1 -1
  21. package/dist/dev/env-defaults.js +5 -2
  22. package/dist/dev/env-defaults.js.map +1 -1
  23. package/dist/dev/infra-preflight.d.ts +3 -2
  24. package/dist/dev/infra-preflight.d.ts.map +1 -1
  25. package/dist/dev/infra-preflight.js +18 -8
  26. package/dist/dev/infra-preflight.js.map +1 -1
  27. package/dist/edition/verify-image-evidence.bundle.mjs +54283 -0
  28. package/dist/edition/verify-image-evidence.bundle.provenance.json +2877 -0
  29. package/dist/runtime/config/schema.d.ts +1 -0
  30. package/dist/runtime/config/schema.d.ts.map +1 -1
  31. package/dist/runtime/custody/local-custody.d.ts +12 -0
  32. package/dist/runtime/custody/local-custody.d.ts.map +1 -0
  33. package/dist/runtime/custody/local-custody.js +22 -0
  34. package/dist/runtime/custody/local-custody.js.map +1 -0
  35. package/dist/runtime/custody/openbao-custody-bootstrap.d.ts +18 -0
  36. package/dist/runtime/custody/openbao-custody-bootstrap.d.ts.map +1 -0
  37. package/dist/runtime/custody/openbao-custody-bootstrap.js +50 -0
  38. package/dist/runtime/custody/openbao-custody-bootstrap.js.map +1 -0
  39. package/dist/runtime/custody/openbao-custody-store.d.ts +18 -0
  40. package/dist/runtime/custody/openbao-custody-store.d.ts.map +1 -0
  41. package/dist/runtime/custody/openbao-custody-store.js +55 -0
  42. package/dist/runtime/custody/openbao-custody-store.js.map +1 -0
  43. package/dist/runtime/custody/openbao-http-client.d.ts +34 -0
  44. package/dist/runtime/custody/openbao-http-client.d.ts.map +1 -0
  45. package/dist/runtime/custody/openbao-http-client.js +154 -0
  46. package/dist/runtime/custody/openbao-http-client.js.map +1 -0
  47. package/dist/runtime/custody/openbao-local-constants.d.ts +22 -0
  48. package/dist/runtime/custody/openbao-local-constants.d.ts.map +1 -0
  49. package/dist/runtime/custody/openbao-local-constants.js +48 -0
  50. package/dist/runtime/custody/openbao-local-constants.js.map +1 -0
  51. package/dist/runtime/custody/openbao-local-runner.d.ts +16 -0
  52. package/dist/runtime/custody/openbao-local-runner.d.ts.map +1 -0
  53. package/dist/runtime/custody/openbao-local-runner.js +86 -0
  54. package/dist/runtime/custody/openbao-local-runner.js.map +1 -0
  55. package/dist/runtime/edition/run-entry.d.ts +9 -0
  56. package/dist/runtime/edition/run-entry.d.ts.map +1 -0
  57. package/dist/runtime/edition/run-entry.js +36 -0
  58. package/dist/runtime/edition/run-entry.js.map +1 -0
  59. package/dist/runtime/edition/verb-map.d.ts +60 -0
  60. package/dist/runtime/edition/verb-map.d.ts.map +1 -0
  61. package/dist/runtime/edition/verb-map.js +146 -0
  62. package/dist/runtime/edition/verb-map.js.map +1 -0
  63. package/dist/runtime/env-seeder.d.ts +2 -0
  64. package/dist/runtime/env-seeder.d.ts.map +1 -1
  65. package/dist/runtime/env-seeder.js +18 -3
  66. package/dist/runtime/env-seeder.js.map +1 -1
  67. package/dist/runtime/launch.d.ts +2 -1
  68. package/dist/runtime/launch.d.ts.map +1 -1
  69. package/dist/runtime/launch.js +31 -7
  70. package/dist/runtime/launch.js.map +1 -1
  71. package/dist/runtime/paths.d.ts +1 -2
  72. package/dist/runtime/paths.d.ts.map +1 -1
  73. package/dist/runtime/paths.js +0 -10
  74. package/dist/runtime/paths.js.map +1 -1
  75. package/dist/runtime/runner/chart-local-provenance.d.ts +20 -0
  76. package/dist/runtime/runner/chart-local-provenance.d.ts.map +1 -0
  77. package/dist/runtime/runner/chart-local-provenance.js +114 -0
  78. package/dist/runtime/runner/chart-local-provenance.js.map +1 -0
  79. package/dist/runtime/runner/chart-preflight.d.ts +8 -8
  80. package/dist/runtime/runner/chart-preflight.d.ts.map +1 -1
  81. package/dist/runtime/runner/chart-preflight.js +30 -69
  82. package/dist/runtime/runner/chart-preflight.js.map +1 -1
  83. package/dist/runtime/runner/chart-pull.d.ts +14 -0
  84. package/dist/runtime/runner/chart-pull.d.ts.map +1 -0
  85. package/dist/runtime/runner/chart-pull.js +96 -0
  86. package/dist/runtime/runner/chart-pull.js.map +1 -0
  87. package/dist/runtime/runner/chart-resolver.d.ts +4 -12
  88. package/dist/runtime/runner/chart-resolver.d.ts.map +1 -1
  89. package/dist/runtime/runner/chart-resolver.js +16 -62
  90. package/dist/runtime/runner/chart-resolver.js.map +1 -1
  91. package/dist/runtime/runner/chart-set-lock.d.ts +6 -20
  92. package/dist/runtime/runner/chart-set-lock.d.ts.map +1 -1
  93. package/dist/runtime/runner/chart-set-lock.js +55 -99
  94. package/dist/runtime/runner/chart-set-lock.js.map +1 -1
  95. package/dist/runtime/runner/helm-cli.d.ts +3 -1
  96. package/dist/runtime/runner/helm-cli.d.ts.map +1 -1
  97. package/dist/runtime/runner/helm-cli.js +9 -7
  98. package/dist/runtime/runner/helm-cli.js.map +1 -1
  99. package/dist/runtime/runner/helm-runner.d.ts +4 -4
  100. package/dist/runtime/runner/helm-runner.d.ts.map +1 -1
  101. package/dist/runtime/runner/helm-runner.js +18 -16
  102. package/dist/runtime/runner/helm-runner.js.map +1 -1
  103. package/dist/runtime/sources/kinds/git.d.ts +1 -2
  104. package/dist/runtime/sources/kinds/git.d.ts.map +1 -1
  105. package/dist/runtime/sources/kinds/git.js +8 -5
  106. package/dist/runtime/sources/kinds/git.js.map +1 -1
  107. package/dist/runtime/sources/manifest-walk.d.ts +3 -1
  108. package/dist/runtime/sources/manifest-walk.d.ts.map +1 -1
  109. package/dist/runtime/sources/manifest-walk.js +8 -3
  110. package/dist/runtime/sources/manifest-walk.js.map +1 -1
  111. package/dist/runtime/sources/schema.d.ts +3 -0
  112. package/dist/runtime/sources/schema.d.ts.map +1 -1
  113. package/dist/runtime/sources/schema.js +6 -1
  114. package/dist/runtime/sources/schema.js.map +1 -1
  115. package/dist/runtime/supervisor.d.ts +2 -0
  116. package/dist/runtime/supervisor.d.ts.map +1 -1
  117. package/dist/runtime/supervisor.js +1 -0
  118. package/dist/runtime/supervisor.js.map +1 -1
  119. package/edition/README.md +313 -0
  120. package/edition/assemble-producer-input.mjs +1251 -0
  121. package/edition/authorization-base.mjs +347 -0
  122. package/edition/biome-content.mjs +268 -0
  123. package/edition/build-biome-index.mjs +175 -0
  124. package/edition/build-platform-service-index.mjs +288 -0
  125. package/edition/build-runtime-image-source-index.mjs +821 -0
  126. package/edition/built-verifier.mjs +55 -0
  127. package/edition/catalogue-edition.mjs +49 -0
  128. package/edition/catalogue-fetch.mjs +64 -0
  129. package/edition/catalogue.mjs +135 -0
  130. package/edition/chart-set-source-commit.mjs +92 -0
  131. package/edition/check-committed-image-evidence.mjs +383 -0
  132. package/edition/checkout-indexed-source-repositories.mjs +105 -0
  133. package/edition/comment-blanker.mjs +75 -0
  134. package/edition/connector-source-authority.mjs +60 -0
  135. package/edition/customer-definition.mjs +183 -0
  136. package/edition/customer-flags.mjs +7 -0
  137. package/edition/develop-customer-edition.mjs +99 -0
  138. package/edition/develop-edition-resolve.mjs +65 -0
  139. package/edition/develop-edition.mjs +274 -0
  140. package/edition/develop-freeze.mjs +173 -0
  141. package/edition/develop-image-coordinate.mjs +54 -0
  142. package/edition/develop-seal.mjs +77 -0
  143. package/edition/develop-snapshot-inputs.mjs +104 -0
  144. package/edition/dist-check.mjs +986 -0
  145. package/edition/distribution-evidence-set.mjs +987 -0
  146. package/edition/distribution-paths.mjs +191 -0
  147. package/edition/edition-artifact.mjs +284 -0
  148. package/edition/edition-biome-manifests.mjs +85 -0
  149. package/edition/edition-channel.mjs +6 -0
  150. package/edition/edition-chart-set.mjs +93 -0
  151. package/edition/edition-config.mjs +205 -0
  152. package/edition/edition-constants.mjs +57 -0
  153. package/edition/edition-contract.json +30 -0
  154. package/edition/edition-list.mjs +35 -0
  155. package/edition/edition-locks.mjs +69 -0
  156. package/edition/edition-protocol.mjs +228 -0
  157. package/edition/edition-workspace.mjs +187 -0
  158. package/edition/evidence-checkout.mjs +439 -0
  159. package/edition/evidence-grammars.mjs +16 -0
  160. package/edition/evidence-trust-pin.mjs +304 -0
  161. package/edition/evidence-trust-policy.mjs +294 -0
  162. package/edition/external-source-walk.mjs +463 -0
  163. package/edition/first-party-source-ids.mjs +59 -0
  164. package/edition/fleet-coordinate-finality.mjs +350 -0
  165. package/edition/git-source-auth.mjs +52 -0
  166. package/edition/image-delivery-entitlement-descriptor.mjs +106 -0
  167. package/edition/image-evidence-replay-state.mjs +242 -0
  168. package/edition/image-evidence.mjs +1233 -0
  169. package/edition/kernel-contract-authority.mjs +443 -0
  170. package/edition/lib.mjs +3021 -0
  171. package/edition/no-follow-file.mjs +94 -0
  172. package/edition/oci-evidence-verbs.mjs +392 -0
  173. package/edition/oci-image-source-evidence.mjs +239 -0
  174. package/edition/oras.mjs +648 -0
  175. package/edition/pin-source.mjs +227 -0
  176. package/edition/plan-image-evidence-refresh.mjs +408 -0
  177. package/edition/platform-lock-readability.mjs +892 -0
  178. package/edition/platform-service-class.mjs +18 -0
  179. package/edition/platform-service-source-authority.mjs +105 -0
  180. package/edition/positive-closure.mjs +162 -0
  181. package/edition/produce-distribution-completeness-evidence.mjs +311 -0
  182. package/edition/produce-image-delivery-entitlement-evidence.mjs +294 -0
  183. package/edition/produce-image-source-evidence.mjs +765 -0
  184. package/edition/producer-evidence-bundle.mjs +157 -0
  185. package/edition/registry-credentials.mjs +193 -0
  186. package/edition/release-catalogue.mjs +39 -0
  187. package/edition/release-channel-guard.mjs +109 -0
  188. package/edition/release-edition-artifact.mjs +157 -0
  189. package/edition/remedy-command.mjs +89 -0
  190. package/edition/repin-evidence-trust.mjs +101 -0
  191. package/edition/required-resource-provider.mjs +58 -0
  192. package/edition/resolve-chart-set.mjs +415 -0
  193. package/edition/resolve-customer-edition.mjs +288 -0
  194. package/edition/resolve-distribution.mjs +2318 -0
  195. package/edition/resolve-edition.mjs +186 -0
  196. package/edition/resolve-images.mjs +1606 -0
  197. package/edition/seal-image-digests.mjs +855 -0
  198. package/edition/sealed-evidence-accept.mjs +82 -0
  199. package/edition/source-authority.mjs +725 -0
  200. package/edition/source-closure.mjs +116 -0
  201. package/edition/strict-json.mjs +121 -0
  202. package/edition/string-order.mjs +5 -0
  203. package/edition/trust-policy-arming.mjs +120 -0
  204. package/edition/trusted-executable.mjs +166 -0
  205. package/edition/verifier-binding.mjs +45 -0
  206. package/edition/verifier-files.mjs +8 -0
  207. package/edition/verifier-kernel-schemas.mjs +22 -0
  208. package/edition/verifier-provenance.mjs +64 -0
  209. package/edition/verify-image-evidence.mjs +458 -0
  210. package/edition/verify-image-platforms.mjs +495 -0
  211. package/edition/why.mjs +56 -0
  212. package/edition/workspace-sections.mjs +195 -0
  213. package/examples/06-biome-sources.yaml +1 -0
  214. package/package.json +14 -6
  215. package/dist/commands/dist.d.ts +0 -16
  216. package/dist/commands/dist.d.ts.map +0 -1
  217. package/dist/commands/dist.js +0 -182
  218. package/dist/commands/dist.js.map +0 -1
  219. package/dist/runtime/distribution/available-biome.d.ts +0 -10
  220. package/dist/runtime/distribution/available-biome.d.ts.map +0 -1
  221. package/dist/runtime/distribution/available-biome.js +0 -156
  222. package/dist/runtime/distribution/available-biome.js.map +0 -1
  223. package/dist/runtime/distribution/compose.d.ts +0 -23
  224. package/dist/runtime/distribution/compose.d.ts.map +0 -1
  225. package/dist/runtime/distribution/compose.js +0 -48
  226. package/dist/runtime/distribution/compose.js.map +0 -1
  227. package/dist/runtime/runner/chart-trust.d.ts +0 -57
  228. package/dist/runtime/runner/chart-trust.d.ts.map +0 -1
  229. package/dist/runtime/runner/chart-trust.js +0 -270
  230. package/dist/runtime/runner/chart-trust.js.map +0 -1
@@ -0,0 +1,892 @@
1
+ #!/usr/bin/env node
2
+ // INVOKER: `seal-image-digests.mjs`, before it writes a single digest.
3
+ //
4
+ // ── THE ASSERTION THAT WAS MISSING ──────────────────────────────────────────
5
+ //
6
+ // An edition's sealed lock must be PARSEABLE BY THE CONTRACTS VERSION THE
7
+ // IMAGES OF THAT EDITION ACTUALLY RESOLVE. Nothing asserted that, and on
8
+ // 2026-09-17 the gap took out three presenca-dev services at once:
9
+ //
10
+ // Invalid distribution.lock.json: [biomes.87.source] Unrecognized key:
11
+ // "imageNamePrefix"
12
+ // at parseDistributionLock (@xemahq/contracts@0.9.2/…)
13
+ //
14
+ // `DistributionLockSchema` is `.strict()`. The plane resolves a lock with the
15
+ // contracts build in `repos/xema-kernel-contracts`, which runs AHEAD of what
16
+ // ships; every image parses the same bytes with the version ITS OWN repository
17
+ // declares, and a caret on a `0.x` line is MINOR-LOCKED. So the plane happily
18
+ // sealed an artifact its own images could not read — and the edition sealed,
19
+ // published, installed and verified perfectly first.
20
+ //
21
+ // ── TWO NARROWINGS, AND THE SECOND OVERSHOOT THAT MADE THEM NECESSARY ───────
22
+ //
23
+ // THE ORIGINAL OF THIS FILE ASKED `repos/xema-base` ALONE. That is the WEAKEST
24
+ // subject available — it declares the NEWEST range in the fleet, so a check
25
+ // anchored on it parses with the most permissive schema any image resolves and
26
+ // reports OK while the repositories that would actually crashloop are
27
+ // invisible. The header called that "a correct measurement of the wrong
28
+ // subject" and widened to EVERY PINNED REPOSITORY THAT DECLARES A RANGE.
29
+ //
30
+ // THE WIDENING OVERSHOT ONTO A DIFFERENT WRONG SUBJECT, and it is recorded
31
+ // here rather than quietly corrected, because the next person to touch this
32
+ // will be tempted exactly the same way: a subject that is too NARROW produces
33
+ // a false green, so the instinct is to widen — and a subject that is too WIDE
34
+ // produces a false RED, which blocks honest work and teaches everybody that
35
+ // the seal gate is noise. Both are the wrong subject. Neither is fixed by
36
+ // moving further along the same axis.
37
+ //
38
+ // Measured 2026-09-27 while it was refusing a legitimate customer edition
39
+ // seal, it named two repositories and BOTH were wrong, one per defect:
40
+ //
41
+ // • CORPUS. `repositoryPins` is FLEET-WIDE, so it graded repositories that
42
+ // contribute no image to the edition being sealed. `repos/xema-operator`
43
+ // has ZERO subjects in a customer edition's `images.lock.json` — it
44
+ // ships nothing there and therefore cannot crashloop there — and the seal
45
+ // was refused on its `^0.10.0`. The file's own stated invariant is "the
46
+ // images OF THAT EDITION", and `repositoryPins` is not that set.
47
+ //
48
+ // • PREDICATE. "declares a contracts range" is a FALSE PROXY for "parses a
49
+ // lock". `@xemahq/contracts` is a TYPES package that nearly every
50
+ // first-party repository depends on for unrelated reasons. This file
51
+ // already contained the principle that refutes the proxy — "a repository
52
+ // that never parses a lock cannot be made to fail on one, and inventing a
53
+ // requirement for it would assert something untrue" — and applied it only
54
+ // to pins with NO contracts dependency at all. `submodules/xema-host-web`
55
+ // ships images into a customer edition and declares `^0.10.0`, and at
56
+ // its pinned commit `9b4f9ddda7` it calls the lock parser ZERO times.
57
+ //
58
+ // So the subject is now the INTERSECTION, and both halves are DERIVED:
59
+ //
60
+ // graded ⟺ contributes ≥1 image subject to THIS edition
61
+ // AND its pinned commit CALLS the lock parser
62
+ //
63
+ // Measured across the pinned commits with a positive control that fires in
64
+ // every repository (`git grep -l export` → 10224 files in `xema-base`, 6225 in
65
+ // `xema-host-web`; a first pass read `xema-base` as 0 and that was a 1 MB
66
+ // `execFileSync` buffer overflow, not a finding — hence `MAX_BUFFER` below):
67
+ //
68
+ // repos/xema-base parseDistributionLock in 3 files ← a reader
69
+ // repos/xema-operator parseDistributionLock in 5 files ← a reader
70
+ // repos/xema-community 0 repos/xema-connectors 0
71
+ // repos/xema-cultivars 0 repos/xema-docs 0
72
+ // repos/xema-store-api 0 submodules/xema-host-web 0
73
+ // the five generic submodules 0
74
+ //
75
+ // TWO of thirteen pins parse a lock. The other eleven were being graded on a
76
+ // question they cannot fail.
77
+ //
78
+ // NEITHER NARROWING WEAKENS THE 2026-09-17 CASE, which is the only thing that
79
+ // matters about them: the service that crashlooped was `biome-host-api`, out
80
+ // of `repos/xema-base`, which ships images into that edition AND is one of the
81
+ // two readers. It is graded by both rules, and `assertPlatformCanParseLock`
82
+ // refuses that lock exactly as before. Asserted in the contract tests rather
83
+ // than argued here.
84
+ //
85
+ // ── WHY A REPOSITORY WITH NO IMAGE IN THIS EDITION IS NOT AN ERROR ──────────
86
+ //
87
+ // Same reason a non-reader is not: the question does not arise. It is the
88
+ // reading that makes this a seal-time gate rather than a fleet audit — the
89
+ // artifact being sealed is ONE edition's lock, and the population that can be
90
+ // broken by it is the images of that edition.
91
+ //
92
+ // KNOWN LIMIT, stated rather than discovered: a subject whose `repo` has NO
93
+ // entry in `repositoryPins` is outside this check, because the pin index is
94
+ // where a commit to read the declaration AT comes from. A customer edition
95
+ // has one today — `presenca-biomes`, a customer repository pinned elsewhere.
96
+ // It was outside this gate before the narrowing too, so nothing was lost here;
97
+ // it is named so nobody reads the corpus line as covering it.
98
+ //
99
+ // AND VENDORED SOURCE IS CORRECTLY OUT, which is worth saying because it looks
100
+ // like a gap: `repos/xema-connectors` contributes an image subject to NO
101
+ // edition — its biomes are vendored INTO another repository's image — so it is
102
+ // graded nowhere now, where it was graded everywhere before. That is right
103
+ // rather than lax. An image built out of `repos/xema-base` resolves
104
+ // `xema-base`'s contracts version whatever source was vendored into it, so the
105
+ // version that decides whether that image can read the lock is the BUILDING
106
+ // repository's, and grading the vendored one would assert something about a
107
+ // resolution that does not happen.
108
+ //
109
+ // ── WHAT COUNTS AS CALLING THE PARSER ───────────────────────────────────────
110
+ //
111
+ // A CALL, NEVER A MENTION, and the distinction is live in this corpus rather
112
+ // than theoretical: `submodules/xema-host-web`'s `src/lib/nav-registry.ts:1135`
113
+ // and `repos/xema-cultivars`'s `decision-api.client.ts:53` each name
114
+ // `distribution.lock.json` in a comment, and neither parses one. Source is
115
+ // comment-blanked (`blankAllComments`) before any assertion.
116
+ //
117
+ // GENERATED CLIENT MIRRORS ARE EXCLUDED. A generated DTO naming a lock type is
118
+ // an orval projection of somebody else's API, not a parser — measured at the
119
+ // committed pins, `xema-operator` carries 15 lock-naming files under
120
+ // `packages/clients/**` and `xema-host-web` 5 under
121
+ // `src/lib/api/orval/clients/**`. Neither ever calls `parseDistributionLock`,
122
+ // so today the exclusion changes no verdict; it is here because the day a
123
+ // generated model is named after the schema is the day it would.
124
+ //
125
+ // IT ERRS TOWARD GRADING, deliberately, because the two errors are not
126
+ // symmetric: over-grading is a seal that waits for a human, and under-grading
127
+ // is the outage. So a CALL to `parseDistributionLock` or `sealDistributionLock`
128
+ // counts, and so does ANY executable reference to `DistributionLockSchema` —
129
+ // holding the strict schema is holding the failure. An imported-but-never-called
130
+ // function is not a reader: it cannot fail at runtime, and a compiler refuses
131
+ // it long before a boot does.
132
+ //
133
+ // ── WHICH SCHEMA A READER PARSES WITH (2026-10-01) ──────────────────────────
134
+ //
135
+ // A pure consumer of a lock now parses it with the kernel's tolerant READER
136
+ // (`parseDistributionLockForReading`, `@xemahq/contracts` >= 0.27.0): every
137
+ // field it knows is validated exactly as the writer validates it, and a key a
138
+ // later edition adds is IGNORED. Grading such a repository with the strict
139
+ // writer would refuse every additive lock change for a reader that can read
140
+ // it — the false red this file already warns about. Not seeing it at all would
141
+ // be worse: once xema-base and xema-operator moved, NEITHER called the strict
142
+ // parser any more, so the graded set went empty and every seal was refused.
143
+ //
144
+ // So detection now sorts every parsing file into one of THREE ways to read:
145
+ //
146
+ // • STRICT — `parseDistributionLock(`, `sealDistributionLock(`, any
147
+ // executable `DistributionLockSchema`;
148
+ // • READER — `parseDistributionLockForReading(`, `DistributionLockReaderSchema`;
149
+ // • SDK — `loadDistributionLockFromEnv(`, `readDistributionLockFile(`,
150
+ // `FirstPartyCapabilityDomainsModule`: `@xemahq/xema-service-nest` parses
151
+ // on the repository's behalf, with WHICHEVER schema the version that
152
+ // repository's lockfile resolves uses. Earlier SDK versions use the strict
153
+ // writer, so this is asked of the PUBLISHED SDK tarball at the version the
154
+ // pinned `pnpm-lock.yaml` records — never inferred from a range, which
155
+ // resolves the highest admitted version rather than the one that shipped.
156
+ //
157
+ // A repository is graded with the STRICTEST way any of its files reads: one
158
+ // strict call anywhere, or an SDK version that is strict (or cannot be read
159
+ // from the lockfile), and the whole repository is graded strict. Erring toward
160
+ // strict is the same asymmetry as erring toward grading: over-strictness is a
161
+ // seal that waits for a human, under-strictness is the outage. Every case is
162
+ // still graded against the PUBLISHED version's own parser, so a reader on a
163
+ // contracts version that predates a whole schemaVersion is still refused.
164
+ //
165
+ // ── WHY IT ASKS THE SCHEMA RATHER THAN ENCODING A LIST ──────────────────────
166
+ //
167
+ // A hand-kept ledger of "keys added after version V" is a change-tax: every new
168
+ // kernel field would have to be remembered here, and the one nobody remembers
169
+ // is exactly the one that ships. So this RESOLVES each repository's own
170
+ // contracts version and parses the candidate lock with THAT schema. The
171
+ // question is asked, not restated.
172
+ //
173
+ // The version comes from the pin, not from a guess: `runtime-image-source-index
174
+ // .json#repositoryPins` names the exact commit each repository's subjects are
175
+ // built from, and that commit's own manifests declare the range. Reading the
176
+ // declaration AT THE PINNED COMMIT is what makes this true of the images that
177
+ // actually ship rather than of somebody's checkout. The reader detection reads
178
+ // that same commit, for the same reason.
179
+ //
180
+ // ── WHERE A RANGE IS DECLARED, IN PRECEDENCE ORDER ──────────────────────────
181
+ //
182
+ // 1. root `package.json` → `pnpm.overrides['@xemahq/contracts']`
183
+ // 2. `pnpm-workspace.yaml` → the default `catalog:` entry
184
+ // 3. root `package.json` → a LITERAL dependency range (not `catalog:`)
185
+ //
186
+ // An override outranks the catalog unconditionally, which is not hypothetical:
187
+ // `repos/xema-operator` declares one. (3) is what reaches the npm-managed
188
+ // repositories that have no pnpm workspace at all — `submodules/xema-host-web`
189
+ // is the live case, and reading only a catalog misses it entirely.
190
+ //
191
+ // KNOWN LIMIT, stated rather than discovered: a workspace MEMBER that declares
192
+ // its own literal range instead of `catalog:` is outside this read. Measured at
193
+ // today's pins, none does — the only literals in any member manifest are OPEN
194
+ // `peerDependencies` floors (`>=0.4.0`, `>=0.16.0`), which constrain nothing
195
+ // that gets installed.
196
+ //
197
+ // ── FAILURE POSTURE ─────────────────────────────────────────────────────────
198
+ //
199
+ // REFUSES, and never degrades. A missing checkout, an unfetched pin, an
200
+ // unreachable registry and a package exporting no `parseDistributionLock` each
201
+ // throw: "every image can read this lock" is a claim, and a claim that cannot
202
+ // be checked has not been established.
203
+ //
204
+ // THREE EMPTINESS REFUSALS, and the narrowing makes them MORE important rather
205
+ // than less — a scan over nothing is not a pass, and there are now two more
206
+ // ways to arrive at nothing than there were:
207
+ //
208
+ // • an empty `repositoryPins` — nothing has been graded at all;
209
+ // • an edition whose image lock has no subjects — there is no population;
210
+ // • an empty GRADED SET — every pin was narrowed away. That would mean no
211
+ // image in this edition reads the lock, which is false of every edition
212
+ // that exists (all ten carry `repos/xema-base`), so it is a broken
213
+ // derivation rather than a clean bill of health.
214
+ //
215
+ // And one refusal for the case the two narrowings could otherwise hide: a
216
+ // repository that PARSES a lock and declares NO contracts range. Its version
217
+ // cannot be resolved, so its readability cannot be established, and skipping
218
+ // it would be the silent half of the original defect. No pin is in that state
219
+ // today; the arm is fail-closed rather than speculative.
220
+
221
+ import { execFileSync } from 'node:child_process';
222
+ import { existsSync, mkdtempSync, readFileSync, readdirSync } from 'node:fs';
223
+ import { tmpdir } from 'node:os';
224
+ import { join } from 'node:path';
225
+
226
+ import { blankAllComments } from './comment-blanker.mjs';
227
+ import { sourcesRoot } from './edition-workspace.mjs';
228
+
229
+ const CONTRACTS_PACKAGE = '@xemahq/contracts';
230
+ /** `'@xemahq/contracts': ^0.22.0` and `"@xemahq/contracts": ^0.10.0` alike. */
231
+ const CATALOG_ENTRY = /^\s+['"]?@xemahq\/contracts['"]?:\s*(\S+)\s*$/u;
232
+ /** A column-0 YAML key ends the `catalog:` block. */
233
+ const TOP_LEVEL_KEY = /^\S/u;
234
+
235
+ /**
236
+ * 256 MB, and it is load-bearing rather than defensive. `execFileSync`
237
+ * defaults to 1 MB; `git grep -l` over `repos/xema-base` at a pinned commit
238
+ * exceeds that, and the overflow surfaces as a THROW — which, caught, reads as
239
+ * "this repository matched nothing". A first measurement of the reader corpus
240
+ * reported `xema-base` as a non-reader for exactly that reason, and only a
241
+ * positive control that also came back 0 exposed it.
242
+ */
243
+ const MAX_BUFFER = 256 * 1024 * 1024;
244
+
245
+ /** Source a repository could plausibly parse a lock from. */
246
+ const SOURCE_PATHSPECS = ['*.ts', '*.tsx', '*.mts', '*.cts', '*.js', '*.mjs', '*.cjs'];
247
+
248
+ /**
249
+ * Generated client mirrors. Orval output, never authored, and a generated DTO
250
+ * named after a lock type is a projection of an API rather than a parser.
251
+ */
252
+ const GENERATED_CLIENT_MIRRORS = [/(^|\/)packages\/clients\//u, /(^|\/)orval\/clients\//u];
253
+
254
+ /** How a repository parses a distribution lock. Closed set. */
255
+ export const LockReadMode = Object.freeze({
256
+ /** The strict WRITER schema — an unknown key refuses the whole lock. */
257
+ Strict: 'strict',
258
+ /** The tolerant READER schema — an unknown additive key is ignored. */
259
+ Reader: 'reader',
260
+ });
261
+
262
+ /** Executable shapes that parse a lock with the strict WRITER schema. */
263
+ const STRICT_PARSER_PATTERNS = [
264
+ /\bparseDistributionLock\s*\(/u,
265
+ /\bsealDistributionLock\s*\(/u,
266
+ /\bDistributionLockSchema\b/u,
267
+ ];
268
+ /** Executable shapes that parse a lock with the tolerant READER schema. */
269
+ const READER_PARSER_PATTERNS = [
270
+ /\bparseDistributionLockForReading\s*\(/u,
271
+ /\bDistributionLockReaderSchema\b/u,
272
+ ];
273
+ /**
274
+ * Executable shapes that have `@xemahq/xema-service-nest` parse the lock on the
275
+ * repository's behalf — with whichever schema that SDK version uses.
276
+ */
277
+ const SDK_PARSER_PATTERNS = [
278
+ /\bloadDistributionLockFromEnv\s*\(/u,
279
+ /\breadDistributionLockFile\s*\(/u,
280
+ /\bFirstPartyCapabilityDomainsModule\b/u,
281
+ ];
282
+ /**
283
+ * Cheap `git grep -F` prefilter — every regex above is anchored on one of
284
+ * these (`parseDistributionLockForReading` contains `parseDistributionLock`).
285
+ */
286
+ const PARSER_NEEDLES = [
287
+ 'parseDistributionLock',
288
+ 'sealDistributionLock',
289
+ 'DistributionLockSchema',
290
+ 'DistributionLockReaderSchema',
291
+ 'loadDistributionLockFromEnv',
292
+ 'readDistributionLockFile',
293
+ 'FirstPartyCapabilityDomainsModule',
294
+ ];
295
+
296
+ const SDK_PACKAGE = '@xemahq/xema-service-nest';
297
+ /** Where the SDK's boot-time lock read lives inside its published tarball. */
298
+ const SDK_LOCK_READER_MODULE = join('dist', 'lib', 'distribution-lock', 'distribution-lock-env.js');
299
+ /** Every resolved SDK version in a pnpm lockfile's `packages:`/`snapshots:` keys. */
300
+ const SDK_LOCK_ENTRY = /^ {2}'?@xemahq\/xema-service-nest@(\d+\.\d+\.\d+[^('\s:]*)/gmu;
301
+
302
+ /** One download per contracts VERSION and MODE per process. */
303
+ const parserCache = new Map();
304
+ /** One download per SDK VERSION per process. */
305
+ const sdkModeCache = new Map();
306
+
307
+ function run(command, args, options = {}) {
308
+ return execFileSync(command, args, {
309
+ encoding: 'utf8',
310
+ stdio: ['ignore', 'pipe', 'pipe'],
311
+ maxBuffer: MAX_BUFFER,
312
+ ...options,
313
+ }).trim();
314
+ }
315
+
316
+ /** `git show <commit>:<path>`, or `undefined` when that path is not in the tree. */
317
+ function showAtCommit(directory, commit, path) {
318
+ try {
319
+ return run('git', ['-C', directory, 'show', `${commit}:${path}`]);
320
+ } catch {
321
+ return undefined;
322
+ }
323
+ }
324
+
325
+ /**
326
+ * The checkout and the pinned commit, or a refusal.
327
+ *
328
+ * @throws when either is absent. Both mean the question cannot be answered,
329
+ * and an unanswered question is not a pass.
330
+ */
331
+ function pinnedCheckout({ repoRoot, repo, commit }) {
332
+ const directory = join(repoRoot, repo);
333
+ // A submodule's `.git` is a FILE holding a `gitdir:` pointer, not a
334
+ // directory, so this must stay an existence test rather than a directory one.
335
+ if (!existsSync(join(directory, '.git'))) {
336
+ throw new Error(
337
+ `platform lock readability: "${repo}" is not checked out at ${directory}, so the ` +
338
+ 'contracts version its images resolve cannot be read. The assertion is not ' +
339
+ 'optional — refusing rather than sealing a lock nothing has proven readable. ' +
340
+ 'In CI, run checkout-indexed-source-repositories.mjs before sealing.',
341
+ );
342
+ }
343
+ try {
344
+ run('git', ['-C', directory, 'cat-file', '-e', `${commit}^{commit}`]);
345
+ } catch {
346
+ throw new Error(
347
+ `platform lock readability: ${repo} does not have its pinned commit ${commit}. ` +
348
+ 'Fetch that exact commit before sealing — a lock cannot be proven readable ' +
349
+ 'against a revision this checkout does not hold.',
350
+ );
351
+ }
352
+ return directory;
353
+ }
354
+
355
+ function catalogRange(workspaceYaml) {
356
+ if (workspaceYaml === undefined) {return undefined;}
357
+ let inCatalog = false;
358
+ for (const line of workspaceYaml.split('\n')) {
359
+ if (TOP_LEVEL_KEY.test(line)) {
360
+ inCatalog = line.startsWith('catalog:');
361
+ continue;
362
+ }
363
+ if (!inCatalog) {continue;}
364
+ const match = CATALOG_ENTRY.exec(line);
365
+ if (match) {return match[1].replace(/^['"]|['"]$/gu, '');}
366
+ }
367
+ return undefined;
368
+ }
369
+
370
+ function manifestRange(packageJson) {
371
+ if (packageJson === undefined) {return undefined;}
372
+ let parsed;
373
+ try {
374
+ parsed = JSON.parse(packageJson);
375
+ } catch {
376
+ return undefined;
377
+ }
378
+ const override = parsed?.pnpm?.overrides?.[CONTRACTS_PACKAGE];
379
+ if (typeof override === 'string' && override.length > 0) {
380
+ return { range: override, source: 'pnpm.overrides' };
381
+ }
382
+ for (const field of ['dependencies', 'devDependencies']) {
383
+ const declared = parsed?.[field]?.[CONTRACTS_PACKAGE];
384
+ // `catalog:` is a POINTER at the workspace catalog, never a range. Reading
385
+ // it as one would resolve the literal string against the registry.
386
+ if (typeof declared === 'string' && declared.length > 0 && declared !== 'catalog:') {
387
+ return { range: declared, source: `package.json ${field}` };
388
+ }
389
+ }
390
+ return undefined;
391
+ }
392
+
393
+ /** True for a path under a generated client mirror, which is never a parser. */
394
+ export function isGeneratedClientMirror(path) {
395
+ return GENERATED_CLIENT_MIRRORS.some((pattern) => pattern.test(path));
396
+ }
397
+
398
+ /**
399
+ * The contracts range one pinned repository declares, and where it declares it.
400
+ *
401
+ * `undefined` means the repository declares none — see the header: that is a
402
+ * repository which parses no lock, and it is not an error.
403
+ *
404
+ * @throws when the checkout or the pinned commit is absent.
405
+ */
406
+ export function declaredContractsRange({ repoRoot = sourcesRoot(), repo, commit }) {
407
+ const directory = pinnedCheckout({ repoRoot, repo, commit });
408
+ const fromManifest = manifestRange(showAtCommit(directory, commit, 'package.json'));
409
+ if (fromManifest?.source === 'pnpm.overrides') {return fromManifest;}
410
+ const fromCatalog = catalogRange(
411
+ showAtCommit(directory, commit, 'pnpm-workspace.yaml'),
412
+ );
413
+ if (fromCatalog !== undefined) {
414
+ return { range: fromCatalog, source: 'pnpm-workspace.yaml catalog' };
415
+ }
416
+ return fromManifest;
417
+ }
418
+
419
+ /**
420
+ * Does this repository, AT ITS PINNED COMMIT, actually parse a distribution
421
+ * lock?
422
+ *
423
+ * Returns `{ isReader, files, mentionOnly }`. `files` are the executable call
424
+ * sites; `mentionOnly` are files that name a parser symbol in PROSE alone and
425
+ * are reported so an over-blank shows up as a visible difference rather than
426
+ * as a silently smaller corpus.
427
+ *
428
+ * @throws when the checkout or the pinned commit is absent, and when `git
429
+ * grep` fails for any reason other than "no match" (exit 1). A search that did
430
+ * not run is not a search that found nothing.
431
+ */
432
+ export function parsesDistributionLock({ repoRoot = sourcesRoot(), repo, commit }) {
433
+ const directory = pinnedCheckout({ repoRoot, repo, commit });
434
+ let listing;
435
+ try {
436
+ listing = run('git', [
437
+ '-C',
438
+ directory,
439
+ 'grep',
440
+ '--name-only',
441
+ '--fixed-strings',
442
+ ...PARSER_NEEDLES.flatMap((needle) => ['-e', needle]),
443
+ commit,
444
+ '--',
445
+ ...SOURCE_PATHSPECS,
446
+ ]);
447
+ } catch (error) {
448
+ if (error.status === 1) {listing = '';}
449
+ else {
450
+ throw new Error(
451
+ `platform lock readability: could not search ${repo}@${commit.slice(0, 10)} for ` +
452
+ `lock-parser call sites (git grep exited ${error.status}). A search that did ` +
453
+ `not run is not a search that found nothing.`,
454
+ );
455
+ }
456
+ }
457
+ const candidates = listing
458
+ .split('\n')
459
+ .map((line) => line.trim())
460
+ // `git grep <commit>` prefixes every path with `<commit>:`.
461
+ .map((line) => (line.startsWith(`${commit}:`) ? line.slice(commit.length + 1) : line))
462
+ .filter((path) => path.length > 0 && !isGeneratedClientMirror(path));
463
+
464
+ const files = [];
465
+ const strictFiles = [];
466
+ const readerFiles = [];
467
+ const sdkFiles = [];
468
+ const mentionOnly = [];
469
+ for (const path of candidates) {
470
+ const source = showAtCommit(directory, commit, path);
471
+ if (source === undefined) {continue;}
472
+ const executable = blankAllComments(source);
473
+ const strict = STRICT_PARSER_PATTERNS.some((pattern) => pattern.test(executable));
474
+ const reader = READER_PARSER_PATTERNS.some((pattern) => pattern.test(executable));
475
+ const sdk = SDK_PARSER_PATTERNS.some((pattern) => pattern.test(executable));
476
+ if (strict) {strictFiles.push(path);}
477
+ if (reader) {readerFiles.push(path);}
478
+ if (sdk) {sdkFiles.push(path);}
479
+ if (strict || reader || sdk) {files.push(path);}
480
+ else {mentionOnly.push(path);}
481
+ }
482
+ return {
483
+ isReader: files.length > 0,
484
+ files,
485
+ strictFiles,
486
+ readerFiles,
487
+ sdkFiles,
488
+ mentionOnly,
489
+ };
490
+ }
491
+
492
+ /**
493
+ * Every `@xemahq/xema-service-nest` version the pinned `pnpm-lock.yaml`
494
+ * resolves — the versions that SHIP, not the highest a range admits.
495
+ * Empty when the repository has no pnpm lockfile or resolves no SDK.
496
+ */
497
+ export function lockedSdkVersions({ repoRoot = sourcesRoot(), repo, commit }) {
498
+ const directory = pinnedCheckout({ repoRoot, repo, commit });
499
+ const lockfile = showAtCommit(directory, commit, 'pnpm-lock.yaml');
500
+ if (lockfile === undefined) {return [];}
501
+ const versions = new Set();
502
+ for (const match of lockfile.matchAll(SDK_LOCK_ENTRY)) {versions.add(match[1]);}
503
+ return [...versions].sort();
504
+ }
505
+
506
+ /**
507
+ * Does the PUBLISHED SDK at `version` parse the lock with the kernel READER?
508
+ * Asked of the tarball's own executable source (comments blanked), so a
509
+ * docblock that names the reader cannot answer for a body that does not call it.
510
+ */
511
+ export function sdkReadsWithReader(version) {
512
+ const cached = sdkModeCache.get(version);
513
+ if (cached !== undefined) {return cached;}
514
+ const directory = mkdtempSync(join(tmpdir(), 'xema-service-nest-'));
515
+ run('npm', ['pack', `${SDK_PACKAGE}@${version}`], { cwd: directory });
516
+ const tarball = readdirSync(directory).find((name) => name.endsWith('.tgz'));
517
+ if (tarball === undefined) {
518
+ throw new Error(
519
+ `platform lock readability: \`npm pack ${SDK_PACKAGE}@${version}\` produced no tarball.`,
520
+ );
521
+ }
522
+ run('tar', ['xzf', join(directory, tarball), '-C', directory]);
523
+ const module = join(directory, 'package', SDK_LOCK_READER_MODULE);
524
+ if (!existsSync(module)) {
525
+ throw new Error(
526
+ `platform lock readability: ${SDK_PACKAGE}@${version} ships no ${SDK_LOCK_READER_MODULE}, ` +
527
+ 'so the schema its boot-time lock read uses cannot be established.',
528
+ );
529
+ }
530
+ const executable = blankAllComments(readFileSync(module, 'utf8'));
531
+ const readsWithReader = /\bparseDistributionLockForReading\s*\(/u.test(executable);
532
+ sdkModeCache.set(version, readsWithReader);
533
+ return readsWithReader;
534
+ }
535
+
536
+ /**
537
+ * The schema a repository is graded with: the STRICTEST way any of its files
538
+ * reads a lock. See the header — erring strict is a seal that waits for a
539
+ * human; erring lenient is the outage.
540
+ */
541
+ export function lockReadMode({
542
+ repoRoot = sourcesRoot(),
543
+ repo,
544
+ commit,
545
+ detected,
546
+ sdkUsesReader = sdkReadsWithReader,
547
+ }) {
548
+ if (detected.strictFiles.length > 0) {
549
+ return {
550
+ mode: LockReadMode.Strict,
551
+ reason: `calls the strict writer schema in ${detected.strictFiles[0]}`,
552
+ };
553
+ }
554
+ if (detected.sdkFiles.length > 0) {
555
+ const versions = lockedSdkVersions({ repoRoot, repo, commit });
556
+ if (versions.length === 0) {
557
+ return {
558
+ mode: LockReadMode.Strict,
559
+ reason:
560
+ `reads through ${SDK_PACKAGE} (${detected.sdkFiles[0]}) but its pinned ` +
561
+ 'pnpm-lock.yaml resolves no SDK version, so the SDK is assumed strict',
562
+ };
563
+ }
564
+ const strictSdk = versions.filter((version) => !sdkUsesReader(version));
565
+ if (strictSdk.length > 0) {
566
+ return {
567
+ mode: LockReadMode.Strict,
568
+ reason: `reads through ${SDK_PACKAGE}@${strictSdk.join(', ')}, which parses with the strict writer schema`,
569
+ };
570
+ }
571
+ return {
572
+ mode: LockReadMode.Reader,
573
+ reason: `reads through ${SDK_PACKAGE}@${versions.join(', ')}, which parses with the reader`,
574
+ };
575
+ }
576
+ return {
577
+ mode: LockReadMode.Reader,
578
+ reason: `parses with the reader in ${detected.readerFiles[0]}`,
579
+ };
580
+ }
581
+
582
+ /**
583
+ * The set of repositories that contribute at least one IMAGE SUBJECT to this
584
+ * edition. This — not `repositoryPins` — is the population an edition's lock
585
+ * can break.
586
+ *
587
+ * @throws on an image lock with no subjects, and on a subject that names no
588
+ * repository: neither can be graded, and neither is a pass.
589
+ */
590
+ export function editionImageRepos({ imageLock, distributionId }) {
591
+ const subjects = imageLock?.subjects;
592
+ if (!Array.isArray(subjects) || subjects.length === 0) {
593
+ throw new Error(
594
+ `platform lock readability: "${distributionId}" has no image subjects, so the ` +
595
+ 'population of images that must read this lock is unknown. A scan over nothing ' +
596
+ 'is not a pass.',
597
+ );
598
+ }
599
+ const repos = new Set();
600
+ for (const subject of subjects) {
601
+ if (typeof subject?.repo !== 'string' || subject.repo.length === 0) {
602
+ throw new Error(
603
+ `platform lock readability: "${distributionId}" carries an image subject ` +
604
+ `(${subject?.id ?? 'unnamed'}) that names no source repository, so whether its ` +
605
+ 'image can read this lock cannot be established.',
606
+ );
607
+ }
608
+ repos.add(subject.repo);
609
+ }
610
+ return repos;
611
+ }
612
+
613
+ /**
614
+ * The repositories this edition's lock must be readable by, grouped by the
615
+ * contracts version each resolves.
616
+ *
617
+ * Narrowed in two stages, corpus then predicate, cheapest first: the corpus
618
+ * test is pure set membership and needs no checkout, so a pin that ships
619
+ * nothing here is never read from disk at all.
620
+ */
621
+ export function planReadabilityChecks({
622
+ imageLock,
623
+ distributionId,
624
+ runtimeSourceIndex,
625
+ repoRoot = sourcesRoot(),
626
+ resolveVersion = resolvedContractsVersion,
627
+ detectReader = parsesDistributionLock,
628
+ resolveReadMode = lockReadMode,
629
+ }) {
630
+ const pins = runtimeSourceIndex?.repositoryPins;
631
+ if (!Array.isArray(pins) || pins.length === 0) {
632
+ throw new Error(
633
+ 'platform lock readability: the runtime image source index declares no ' +
634
+ 'repositoryPins, so no image has been graded. A scan over nothing is not a pass.',
635
+ );
636
+ }
637
+ const shipping = editionImageRepos({ imageLock, distributionId });
638
+
639
+ const contributing = [];
640
+ const notInThisEdition = [];
641
+ for (const pin of pins) {
642
+ if (shipping.has(pin.repo)) {contributing.push(pin);}
643
+ else {notInThisEdition.push(pin.repo);}
644
+ }
645
+ // A subject repository with no pin is outside this check by construction —
646
+ // there is no commit to read a declaration at. Reported, never inferred.
647
+ const unpinnedSubjectRepos = [...shipping]
648
+ .filter((repo) => !pins.some((pin) => pin.repo === repo))
649
+ .sort();
650
+
651
+ const readers = [];
652
+ const parseNoLock = [];
653
+ for (const pin of contributing) {
654
+ const detected = detectReader({ repoRoot, repo: pin.repo, commit: pin.sourceCommit });
655
+ if (detected.isReader) {readers.push({ pin, files: detected.files, detected });}
656
+ else {parseNoLock.push({ repo: pin.repo, mentionOnly: detected.mentionOnly });}
657
+ }
658
+
659
+ const declaring = [];
660
+ for (const { pin, files, detected } of readers) {
661
+ const declared = declaredContractsRange({
662
+ repoRoot,
663
+ repo: pin.repo,
664
+ commit: pin.sourceCommit,
665
+ });
666
+ if (declared === undefined) {
667
+ // A READER with no declared range. The two narrowings above both remove
668
+ // a repository on the grounds that a question does not arise for it;
669
+ // this one is the case where it does arise and cannot be answered.
670
+ throw new Error(
671
+ `platform lock readability: ${pin.repo}@${pin.sourceCommit.slice(0, 10)} PARSES a ` +
672
+ `distribution lock (${files.join(', ')}) but declares no ${CONTRACTS_PACKAGE} ` +
673
+ 'range, so the version its images resolve — and therefore whether they can read ' +
674
+ 'this lock — cannot be established. Declare the range, or establish why that ' +
675
+ 'source does not ship in an image. Skipping it would be the silent half of the ' +
676
+ 'defect this gate exists for.',
677
+ );
678
+ }
679
+ const { mode, reason } = resolveReadMode({
680
+ repoRoot,
681
+ repo: pin.repo,
682
+ commit: pin.sourceCommit,
683
+ detected,
684
+ });
685
+ declaring.push({
686
+ repo: pin.repo,
687
+ commit: pin.sourceCommit,
688
+ range: declared.range,
689
+ source: declared.source,
690
+ files,
691
+ mode,
692
+ modeReason: reason,
693
+ version: resolveVersion(declared.range),
694
+ });
695
+ }
696
+
697
+ if (declaring.length === 0) {
698
+ throw new Error(
699
+ `platform lock readability: no pinned repository contributing an image to ` +
700
+ `"${distributionId}" parses a distribution lock, so this seal has been graded ` +
701
+ 'against nothing. Every edition that exists ships images out of at least one pinned repository ' +
702
+ 'that IS a reader — so an empty graded set is a broken derivation, not a clean ' +
703
+ 'bill of health. A scan over nothing is not a pass.',
704
+ );
705
+ }
706
+
707
+ // Keyed by VERSION AND MODE: one published version read two ways is two
708
+ // parsers, and grading both with one would be wrong in one direction or the
709
+ // other.
710
+ const byVersion = new Map();
711
+ for (const entry of declaring) {
712
+ const key = readabilityKey(entry.version, entry.mode);
713
+ const bucket = byVersion.get(key);
714
+ if (bucket) {bucket.push(entry);}
715
+ else {byVersion.set(key, [entry]);}
716
+ }
717
+ return {
718
+ pinCount: pins.length,
719
+ contributing: contributing.map((pin) => pin.repo),
720
+ notInThisEdition,
721
+ unpinnedSubjectRepos,
722
+ parseNoLock,
723
+ declaring,
724
+ byVersion,
725
+ };
726
+ }
727
+
728
+ /** The `byVersion` key: `<version>` for a strict reader, `<version>+reader` for a tolerant one. */
729
+ export function readabilityKey(version, mode) {
730
+ return mode === LockReadMode.Reader ? `${version}+reader` : version;
731
+ }
732
+
733
+ /**
734
+ * The version a range ACTUALLY resolves, asked of the registry.
735
+ *
736
+ * `npm view <pkg>@<range> version` answers with the highest published version
737
+ * the range admits, which is the whole point: `^0.10.0` is minor-locked and
738
+ * resolves 0.10.0 even though 0.22.0 exists, and hand-reasoning about that is
739
+ * how the defect this file exists for was argued away twice.
740
+ */
741
+ export function resolvedContractsVersion(range) {
742
+ const output = run('npm', [
743
+ 'view',
744
+ `${CONTRACTS_PACKAGE}@${range}`,
745
+ 'version',
746
+ '--json',
747
+ ]);
748
+ const parsed = JSON.parse(output);
749
+ const version = Array.isArray(parsed) ? parsed[parsed.length - 1] : parsed;
750
+ if (typeof version !== 'string' || version.length === 0) {
751
+ throw new Error(
752
+ `platform lock readability: the registry returned no version for ` +
753
+ `"${CONTRACTS_PACKAGE}@${range}". An unanswered question is not a pass.`,
754
+ );
755
+ }
756
+ return version;
757
+ }
758
+
759
+ /**
760
+ * The lock parser as that exact published version implements it — the strict
761
+ * `parseDistributionLock`, or the tolerant `parseDistributionLockForReading`
762
+ * for a repository graded as a READER.
763
+ */
764
+ export async function loadPublishedLockParser(version, mode = LockReadMode.Strict) {
765
+ const cacheKey = readabilityKey(version, mode);
766
+ const cached = parserCache.get(cacheKey);
767
+ if (cached) {return cached;}
768
+ const directory = mkdtempSync(join(tmpdir(), 'xema-contracts-'));
769
+ run('npm', ['pack', `${CONTRACTS_PACKAGE}@${version}`], { cwd: directory });
770
+ const tarball = readdirSync(directory).find((name) => name.endsWith('.tgz'));
771
+ if (tarball === undefined) {
772
+ throw new Error(
773
+ `platform lock readability: \`npm pack ${CONTRACTS_PACKAGE}@${version}\` produced no tarball.`,
774
+ );
775
+ }
776
+ run('tar', ['xzf', join(directory, tarball), '-C', directory]);
777
+ // The tarball ships no `node_modules`, and the schema module requires `zod`
778
+ // at load. Installing the package's OWN declared runtime dependencies is what
779
+ // makes this a faithful reproduction of the platform's parse rather than a
780
+ // parse with whatever happens to be resolvable from here.
781
+ run('npm', ['install', '--omit=dev', '--no-audit', '--no-fund', '--silent'], {
782
+ cwd: join(directory, 'package'),
783
+ });
784
+ const entry = join(directory, 'package', 'dist', 'distribution', 'index.js');
785
+ const loaded = await import(`file://${entry}`);
786
+ const exportName =
787
+ mode === LockReadMode.Reader ? 'parseDistributionLockForReading' : 'parseDistributionLock';
788
+ const parse = loaded[exportName];
789
+ if (typeof parse !== 'function') {
790
+ throw new Error(
791
+ `platform lock readability: ${CONTRACTS_PACKAGE}@${version} exports no ` +
792
+ `${exportName}, so the platform-side parse cannot be reproduced.`,
793
+ );
794
+ }
795
+ parserCache.set(cacheKey, parse);
796
+ return parse;
797
+ }
798
+
799
+ /**
800
+ * REFUSE a lock an image of THIS EDITION cannot parse, naming the repository,
801
+ * its declared range, the version that range resolves, the file that parses
802
+ * and the offending key — none of which the raw zod message carries on its own.
803
+ */
804
+ export async function assertPlatformCanParseLock({
805
+ lock,
806
+ imageLock,
807
+ distributionId,
808
+ runtimeSourceIndex,
809
+ repoRoot = sourcesRoot(),
810
+ resolveVersion = resolvedContractsVersion,
811
+ detectReader = parsesDistributionLock,
812
+ resolveReadMode = lockReadMode,
813
+ loadParser = loadPublishedLockParser,
814
+ }) {
815
+ const plan = planReadabilityChecks({
816
+ imageLock,
817
+ distributionId,
818
+ runtimeSourceIndex,
819
+ repoRoot,
820
+ resolveVersion,
821
+ detectReader,
822
+ resolveReadMode,
823
+ });
824
+ for (const [_key, repos] of [...plan.byVersion].sort(([a], [b]) =>
825
+ a < b ? -1 : a > b ? 1 : 0,
826
+ )) {
827
+ const { version, mode } = repos[0];
828
+ const parse = await loadParser(version, mode);
829
+ try {
830
+ parse(JSON.parse(JSON.stringify(lock)));
831
+ } catch (error) {
832
+ const detail = String(error.message).replace(/\s+/gu, ' ').trim();
833
+ const named = repos
834
+ .map(
835
+ (r) =>
836
+ `${r.repo}@${r.commit.slice(0, 10)} declares ${r.range} (${r.source}) and parses a ` +
837
+ `lock in ${r.files.length} file${r.files.length === 1 ? '' : 's'}, e.g. ${r.files[0]} ` +
838
+ `— graded ${r.mode}: ${r.modeReason}`,
839
+ )
840
+ .join('\n ');
841
+ throw new Error(
842
+ `REFUSING TO SEAL "${distributionId}": images built from ${repos.length} pinned ` +
843
+ `repositor${repos.length === 1 ? 'y' : 'ies'} SHIP INTO THIS EDITION, PARSE THIS ` +
844
+ `LOCK, and cannot read it.\n\n ${detail}\n\n` +
845
+ ` ${named}\n\n` +
846
+ ` — and all of those RESOLVE ${CONTRACTS_PACKAGE}@${version} and read it with the ` +
847
+ `${mode === LockReadMode.Reader ? 'tolerant READER' : 'strict WRITER'} schema, because a caret on a ` +
848
+ `0.x line is MINOR-LOCKED.\n\n` +
849
+ `The plane resolved this lock with the contracts build in ` +
850
+ `the kernel checkout, which runs AHEAD of what ships, and ` +
851
+ `DistributionLockSchema is strict — so a key added after ${version} makes the whole ` +
852
+ `lock unreadable AT BOOT, for every service that reads it strictly, not only the biome ` +
853
+ `that carries it; and even the tolerant reader refuses a schemaVersion or a malformed ` +
854
+ `known field that ${version} does not admit.\n\n` +
855
+ `A strict-graded repository is cured by moving its readers to ` +
856
+ `parseDistributionLockForReading (or an ${SDK_PACKAGE} that uses it).\n\n` +
857
+ `Either stop persisting that key (a resolver INPUT does not belong in a sealed ` +
858
+ `artifact — see external-source-walk.mjs), or raise those repositories' contracts ` +
859
+ `ranges past the version that introduced it and rebuild their images before sealing. ` +
860
+ `Sealing now would publish an edition that installs and then crashloops.`,
861
+ );
862
+ }
863
+ }
864
+ // THE CORPUS LINE. A run that graded nothing and a run that graded everything
865
+ // exit the same way; the counts are the only thing that tells them apart —
866
+ // and after the narrowing there are two more ways to grade nothing than
867
+ // there were, so it now prints BOTH narrowings rather than one total.
868
+ const summary = [...plan.byVersion]
869
+ .map(([key, repos]) => `${key}×${repos.length}`)
870
+ .sort()
871
+ .join(', ');
872
+ const mentionOnly = plan.parseNoLock.filter((entry) => entry.mentionOnly.length > 0);
873
+ const named = (repos) => (repos.length > 0 ? ` [${repos.join(', ')}]` : '');
874
+ console.log(
875
+ `[dist:seal-digests] platform readability: OK — "${distributionId}" GRADED against ` +
876
+ `${plan.declaring.length} of ${plan.pinCount} pinned repositories (${summary}): ` +
877
+ `${plan.declaring.map((d) => `${d.repo}@${d.version} (${d.mode})`).join(', ')}. ` +
878
+ `NARROWED: ${plan.contributing.length} pins ship an image into this edition` +
879
+ `${named(plan.notInThisEdition.map((r) => `-${r}`))}` +
880
+ `, and of those ${plan.declaring.length} parse a distribution lock` +
881
+ `${named(plan.parseNoLock.map((e) => `-${e.repo}`))}` +
882
+ `${mentionOnly.length > 0 ? `; ${mentionOnly.map((e) => e.repo).join(', ')} name a parser symbol in PROSE only` : ''}` +
883
+ `${plan.unpinnedSubjectRepos.length > 0 ? `. OUTSIDE THIS CHECK: ${plan.unpinnedSubjectRepos.join(', ')} contribute an image subject but hold no repositoryPins entry` : ''}.`,
884
+ );
885
+ return {
886
+ declaring: plan.declaring,
887
+ contributing: plan.contributing,
888
+ notInThisEdition: plan.notInThisEdition,
889
+ parseNoLock: plan.parseNoLock,
890
+ unpinnedSubjectRepos: plan.unpinnedSubjectRepos,
891
+ };
892
+ }