@xemahq/xema 0.25.1 → 0.27.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 (239) hide show
  1. package/README.md +70 -37
  2. package/dist/assets/appliance-base/images.lock.json +126 -126
  3. package/dist/assets/appliance-lean/images.lock.json +123 -123
  4. package/dist/assets/cloud/images.lock.json +126 -126
  5. package/dist/assets/core/images.lock.json +120 -120
  6. package/dist/assets/distribution-assets.manifest.json +15 -16
  7. package/dist/assets/minimal/images.lock.json +108 -108
  8. package/dist/assets/oss/images.lock.json +126 -126
  9. package/dist/assets/starter/images.lock.json +123 -123
  10. package/dist/cli.js +2 -2
  11. package/dist/cli.js.map +1 -1
  12. package/dist/commands/dev.js +1 -0
  13. package/dist/commands/dev.js.map +1 -1
  14. package/dist/commands/down.d.ts.map +1 -1
  15. package/dist/commands/down.js +7 -1
  16. package/dist/commands/down.js.map +1 -1
  17. package/dist/commands/edition.d.ts +3 -0
  18. package/dist/commands/edition.d.ts.map +1 -0
  19. package/dist/commands/edition.js +46 -0
  20. package/dist/commands/edition.js.map +1 -0
  21. package/dist/commands/init.js +2 -2
  22. package/dist/commands/init.js.map +1 -1
  23. package/dist/commands/serve.js +1 -0
  24. package/dist/commands/serve.js.map +1 -1
  25. package/dist/commands/up.js +1 -0
  26. package/dist/commands/up.js.map +1 -1
  27. package/dist/dev/env-defaults.d.ts.map +1 -1
  28. package/dist/dev/env-defaults.js +6 -1
  29. package/dist/dev/env-defaults.js.map +1 -1
  30. package/dist/dev/infra-preflight.d.ts +3 -2
  31. package/dist/dev/infra-preflight.d.ts.map +1 -1
  32. package/dist/dev/infra-preflight.js +18 -8
  33. package/dist/dev/infra-preflight.js.map +1 -1
  34. package/dist/edition/verify-image-evidence.bundle.mjs +54179 -0
  35. package/dist/edition/verify-image-evidence.bundle.provenance.json +2871 -0
  36. package/dist/runtime/backing-registration.d.ts.map +1 -1
  37. package/dist/runtime/backing-registration.js +7 -2
  38. package/dist/runtime/backing-registration.js.map +1 -1
  39. package/dist/runtime/config/schema.d.ts +1 -0
  40. package/dist/runtime/config/schema.d.ts.map +1 -1
  41. package/dist/runtime/custody/local-custody.d.ts +12 -0
  42. package/dist/runtime/custody/local-custody.d.ts.map +1 -0
  43. package/dist/runtime/custody/local-custody.js +22 -0
  44. package/dist/runtime/custody/local-custody.js.map +1 -0
  45. package/dist/runtime/custody/openbao-custody-bootstrap.d.ts +18 -0
  46. package/dist/runtime/custody/openbao-custody-bootstrap.d.ts.map +1 -0
  47. package/dist/runtime/custody/openbao-custody-bootstrap.js +50 -0
  48. package/dist/runtime/custody/openbao-custody-bootstrap.js.map +1 -0
  49. package/dist/runtime/custody/openbao-custody-store.d.ts +18 -0
  50. package/dist/runtime/custody/openbao-custody-store.d.ts.map +1 -0
  51. package/dist/runtime/custody/openbao-custody-store.js +55 -0
  52. package/dist/runtime/custody/openbao-custody-store.js.map +1 -0
  53. package/dist/runtime/custody/openbao-http-client.d.ts +34 -0
  54. package/dist/runtime/custody/openbao-http-client.d.ts.map +1 -0
  55. package/dist/runtime/custody/openbao-http-client.js +154 -0
  56. package/dist/runtime/custody/openbao-http-client.js.map +1 -0
  57. package/dist/runtime/custody/openbao-local-constants.d.ts +22 -0
  58. package/dist/runtime/custody/openbao-local-constants.d.ts.map +1 -0
  59. package/dist/runtime/custody/openbao-local-constants.js +48 -0
  60. package/dist/runtime/custody/openbao-local-constants.js.map +1 -0
  61. package/dist/runtime/custody/openbao-local-runner.d.ts +16 -0
  62. package/dist/runtime/custody/openbao-local-runner.d.ts.map +1 -0
  63. package/dist/runtime/custody/openbao-local-runner.js +86 -0
  64. package/dist/runtime/custody/openbao-local-runner.js.map +1 -0
  65. package/dist/runtime/edition/run-entry.d.ts +9 -0
  66. package/dist/runtime/edition/run-entry.d.ts.map +1 -0
  67. package/dist/runtime/edition/run-entry.js +36 -0
  68. package/dist/runtime/edition/run-entry.js.map +1 -0
  69. package/dist/runtime/edition/verb-map.d.ts +60 -0
  70. package/dist/runtime/edition/verb-map.d.ts.map +1 -0
  71. package/dist/runtime/edition/verb-map.js +146 -0
  72. package/dist/runtime/edition/verb-map.js.map +1 -0
  73. package/dist/runtime/env-seeder.d.ts +3 -0
  74. package/dist/runtime/env-seeder.d.ts.map +1 -1
  75. package/dist/runtime/env-seeder.js +20 -3
  76. package/dist/runtime/env-seeder.js.map +1 -1
  77. package/dist/runtime/launch.d.ts +3 -1
  78. package/dist/runtime/launch.d.ts.map +1 -1
  79. package/dist/runtime/launch.js +37 -8
  80. package/dist/runtime/launch.js.map +1 -1
  81. package/dist/runtime/paths.d.ts +1 -2
  82. package/dist/runtime/paths.d.ts.map +1 -1
  83. package/dist/runtime/paths.js +0 -10
  84. package/dist/runtime/paths.js.map +1 -1
  85. package/dist/runtime/runner/chart-local-provenance.d.ts +20 -0
  86. package/dist/runtime/runner/chart-local-provenance.d.ts.map +1 -0
  87. package/dist/runtime/runner/chart-local-provenance.js +114 -0
  88. package/dist/runtime/runner/chart-local-provenance.js.map +1 -0
  89. package/dist/runtime/runner/chart-preflight.d.ts +8 -8
  90. package/dist/runtime/runner/chart-preflight.d.ts.map +1 -1
  91. package/dist/runtime/runner/chart-preflight.js +30 -69
  92. package/dist/runtime/runner/chart-preflight.js.map +1 -1
  93. package/dist/runtime/runner/chart-pull.d.ts +14 -0
  94. package/dist/runtime/runner/chart-pull.d.ts.map +1 -0
  95. package/dist/runtime/runner/chart-pull.js +96 -0
  96. package/dist/runtime/runner/chart-pull.js.map +1 -0
  97. package/dist/runtime/runner/chart-resolver.d.ts +4 -12
  98. package/dist/runtime/runner/chart-resolver.d.ts.map +1 -1
  99. package/dist/runtime/runner/chart-resolver.js +16 -62
  100. package/dist/runtime/runner/chart-resolver.js.map +1 -1
  101. package/dist/runtime/runner/chart-set-lock.d.ts +6 -20
  102. package/dist/runtime/runner/chart-set-lock.d.ts.map +1 -1
  103. package/dist/runtime/runner/chart-set-lock.js +55 -99
  104. package/dist/runtime/runner/chart-set-lock.js.map +1 -1
  105. package/dist/runtime/runner/helm-cli.d.ts +3 -1
  106. package/dist/runtime/runner/helm-cli.d.ts.map +1 -1
  107. package/dist/runtime/runner/helm-cli.js +9 -7
  108. package/dist/runtime/runner/helm-cli.js.map +1 -1
  109. package/dist/runtime/runner/helm-runner.d.ts +4 -4
  110. package/dist/runtime/runner/helm-runner.d.ts.map +1 -1
  111. package/dist/runtime/runner/helm-runner.js +18 -16
  112. package/dist/runtime/runner/helm-runner.js.map +1 -1
  113. package/dist/runtime/sources/kinds/git.d.ts +1 -2
  114. package/dist/runtime/sources/kinds/git.d.ts.map +1 -1
  115. package/dist/runtime/sources/kinds/git.js +8 -5
  116. package/dist/runtime/sources/kinds/git.js.map +1 -1
  117. package/dist/runtime/sources/manifest-walk.d.ts +3 -1
  118. package/dist/runtime/sources/manifest-walk.d.ts.map +1 -1
  119. package/dist/runtime/sources/manifest-walk.js +8 -3
  120. package/dist/runtime/sources/manifest-walk.js.map +1 -1
  121. package/dist/runtime/sources/schema.d.ts +3 -0
  122. package/dist/runtime/sources/schema.d.ts.map +1 -1
  123. package/dist/runtime/sources/schema.js +6 -1
  124. package/dist/runtime/sources/schema.js.map +1 -1
  125. package/dist/runtime/supervisor.d.ts +3 -0
  126. package/dist/runtime/supervisor.d.ts.map +1 -1
  127. package/dist/runtime/supervisor.js +2 -0
  128. package/dist/runtime/supervisor.js.map +1 -1
  129. package/edition/README.md +315 -0
  130. package/edition/assemble-producer-input.mjs +1251 -0
  131. package/edition/authorization-base.mjs +347 -0
  132. package/edition/biome-content.mjs +268 -0
  133. package/edition/build-biome-index.mjs +175 -0
  134. package/edition/build-platform-service-index.mjs +288 -0
  135. package/edition/build-runtime-image-source-index.mjs +821 -0
  136. package/edition/built-verifier.mjs +55 -0
  137. package/edition/catalogue-edition.mjs +49 -0
  138. package/edition/catalogue-fetch.mjs +64 -0
  139. package/edition/catalogue.mjs +135 -0
  140. package/edition/chart-set-source-commit.mjs +92 -0
  141. package/edition/check-committed-image-evidence.mjs +383 -0
  142. package/edition/checkout-indexed-source-repositories.mjs +105 -0
  143. package/edition/comment-blanker.mjs +75 -0
  144. package/edition/connector-source-authority.mjs +60 -0
  145. package/edition/customer-definition.mjs +183 -0
  146. package/edition/customer-flags.mjs +7 -0
  147. package/edition/develop-customer-edition.mjs +99 -0
  148. package/edition/develop-edition-resolve.mjs +65 -0
  149. package/edition/develop-edition.mjs +274 -0
  150. package/edition/develop-freeze.mjs +173 -0
  151. package/edition/develop-image-coordinate.mjs +54 -0
  152. package/edition/develop-seal.mjs +77 -0
  153. package/edition/develop-snapshot-inputs.mjs +104 -0
  154. package/edition/dist-check.mjs +986 -0
  155. package/edition/distribution-evidence-set.mjs +987 -0
  156. package/edition/distribution-paths.mjs +191 -0
  157. package/edition/edition-artifact.mjs +270 -0
  158. package/edition/edition-biome-manifests.mjs +85 -0
  159. package/edition/edition-channel.mjs +6 -0
  160. package/edition/edition-chart-set.mjs +93 -0
  161. package/edition/edition-config.mjs +207 -0
  162. package/edition/edition-constants.mjs +57 -0
  163. package/edition/edition-contract.json +23 -0
  164. package/edition/edition-list.mjs +35 -0
  165. package/edition/edition-locks.mjs +69 -0
  166. package/edition/edition-protocol.mjs +228 -0
  167. package/edition/edition-workspace.mjs +176 -0
  168. package/edition/evidence-checkout.mjs +418 -0
  169. package/edition/evidence-grammars.mjs +16 -0
  170. package/edition/evidence-trust-pin.mjs +304 -0
  171. package/edition/evidence-trust-policy.mjs +256 -0
  172. package/edition/external-source-walk.mjs +463 -0
  173. package/edition/first-party-source-ids.mjs +59 -0
  174. package/edition/fleet-coordinate-finality.mjs +350 -0
  175. package/edition/git-source-auth.mjs +52 -0
  176. package/edition/image-evidence-replay-state.mjs +242 -0
  177. package/edition/image-evidence.mjs +1225 -0
  178. package/edition/kernel-contract-authority.mjs +443 -0
  179. package/edition/lib.mjs +3018 -0
  180. package/edition/no-follow-file.mjs +94 -0
  181. package/edition/oci-evidence-verbs.mjs +392 -0
  182. package/edition/oci-image-source-evidence.mjs +239 -0
  183. package/edition/oras.mjs +648 -0
  184. package/edition/pin-source.mjs +227 -0
  185. package/edition/plan-image-evidence-refresh.mjs +408 -0
  186. package/edition/platform-lock-readability.mjs +892 -0
  187. package/edition/platform-service-class.mjs +18 -0
  188. package/edition/platform-service-source-authority.mjs +105 -0
  189. package/edition/positive-closure.mjs +162 -0
  190. package/edition/produce-distribution-completeness-evidence.mjs +311 -0
  191. package/edition/produce-image-delivery-entitlement-evidence.mjs +294 -0
  192. package/edition/produce-image-source-evidence.mjs +765 -0
  193. package/edition/producer-evidence-bundle.mjs +157 -0
  194. package/edition/registry-credentials.mjs +193 -0
  195. package/edition/release-catalogue.mjs +39 -0
  196. package/edition/release-channel-guard.mjs +99 -0
  197. package/edition/release-edition-artifact.mjs +158 -0
  198. package/edition/remedy-command.mjs +89 -0
  199. package/edition/repin-evidence-trust.mjs +101 -0
  200. package/edition/required-resource-provider.mjs +58 -0
  201. package/edition/resolve-chart-set.mjs +415 -0
  202. package/edition/resolve-customer-edition.mjs +288 -0
  203. package/edition/resolve-distribution.mjs +2318 -0
  204. package/edition/resolve-edition.mjs +186 -0
  205. package/edition/resolve-images.mjs +1606 -0
  206. package/edition/seal-image-digests.mjs +855 -0
  207. package/edition/sealed-evidence-accept.mjs +82 -0
  208. package/edition/source-authority.mjs +725 -0
  209. package/edition/source-closure.mjs +116 -0
  210. package/edition/strict-json.mjs +121 -0
  211. package/edition/string-order.mjs +5 -0
  212. package/edition/trust-policy-arming.mjs +120 -0
  213. package/edition/trusted-executable.mjs +166 -0
  214. package/edition/verifier-binding.mjs +45 -0
  215. package/edition/verifier-files.mjs +8 -0
  216. package/edition/verifier-kernel-schemas.mjs +22 -0
  217. package/edition/verifier-provenance.mjs +64 -0
  218. package/edition/verify-image-evidence.mjs +430 -0
  219. package/edition/verify-image-platforms.mjs +495 -0
  220. package/edition/why.mjs +56 -0
  221. package/edition/workspace-sections.mjs +195 -0
  222. package/examples/06-biome-sources.yaml +1 -0
  223. package/package.json +18 -7
  224. package/dist/commands/dist.d.ts +0 -16
  225. package/dist/commands/dist.d.ts.map +0 -1
  226. package/dist/commands/dist.js +0 -182
  227. package/dist/commands/dist.js.map +0 -1
  228. package/dist/runtime/distribution/available-biome.d.ts +0 -10
  229. package/dist/runtime/distribution/available-biome.d.ts.map +0 -1
  230. package/dist/runtime/distribution/available-biome.js +0 -154
  231. package/dist/runtime/distribution/available-biome.js.map +0 -1
  232. package/dist/runtime/distribution/compose.d.ts +0 -23
  233. package/dist/runtime/distribution/compose.d.ts.map +0 -1
  234. package/dist/runtime/distribution/compose.js +0 -48
  235. package/dist/runtime/distribution/compose.js.map +0 -1
  236. package/dist/runtime/runner/chart-trust.d.ts +0 -57
  237. package/dist/runtime/runner/chart-trust.d.ts.map +0 -1
  238. package/dist/runtime/runner/chart-trust.js +0 -270
  239. package/dist/runtime/runner/chart-trust.js.map +0 -1
@@ -0,0 +1,443 @@
1
+ import { execFileSync } from 'node:child_process';
2
+ import { lstatSync, readFileSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+
5
+ import { kernelRepoDir } from './edition-workspace.mjs';
6
+ import { COMMIT_PATTERN } from './evidence-grammars.mjs';
7
+
8
+ const GIT_ENV = Object.freeze({
9
+ GIT_CONFIG_NOSYSTEM: '1',
10
+ GIT_CONFIG_GLOBAL: '/dev/null',
11
+ GIT_TERMINAL_PROMPT: '0',
12
+ GIT_NO_REPLACE_OBJECTS: '1',
13
+ GIT_OPTIONAL_LOCKS: '0',
14
+ GIT_PAGER: 'cat',
15
+ LC_ALL: 'C',
16
+ PATH: '/usr/bin:/bin',
17
+ });
18
+ const GIT_SAFE_CONFIG = Object.freeze([
19
+ '-c',
20
+ 'core.fsmonitor=false',
21
+ '-c',
22
+ 'core.hooksPath=/dev/null',
23
+ '-c',
24
+ 'core.attributesFile=/dev/null',
25
+ '--no-pager',
26
+ ]);
27
+ const GIT_TREE_RECORD_PATTERN =
28
+ /^(100644|100755|120000|160000) (blob|commit) ([a-f0-9]{40,64})\t(.+)$/;
29
+
30
+ function git(repositoryDirectory, args, encoding = 'utf8') {
31
+ return execFileSync(
32
+ '/usr/bin/git',
33
+ [...GIT_SAFE_CONFIG, '-C', repositoryDirectory, ...args],
34
+ {
35
+ encoding,
36
+ stdio: ['ignore', 'pipe', 'pipe'],
37
+ env: GIT_ENV,
38
+ },
39
+ );
40
+ }
41
+
42
+ function exactHeadCommit(repositoryDirectory) {
43
+ const commit = git(repositoryDirectory, [
44
+ 'rev-parse',
45
+ '--verify',
46
+ 'HEAD^{commit}',
47
+ ]).trim();
48
+ if (!COMMIT_PATTERN.test(commit)) {
49
+ throw new Error('kernel contract repository HEAD is not an exact commit');
50
+ }
51
+ return commit;
52
+ }
53
+
54
+ /** True when `ancestor` is reachable from `descendant`. Never throws. */
55
+ function isAncestor(repositoryDirectory, ancestor, descendant) {
56
+ try {
57
+ git(repositoryDirectory, ['merge-base', '--is-ancestor', ancestor, descendant]);
58
+ return true;
59
+ } catch {
60
+ return false;
61
+ }
62
+ }
63
+
64
+ function assertCommitExists(repositoryDirectory, sourceCommit) {
65
+ if (!COMMIT_PATTERN.test(sourceCommit)) {
66
+ throw new Error(
67
+ `kernel contract authority has invalid source commit "${String(sourceCommit)}"`,
68
+ );
69
+ }
70
+ try {
71
+ git(repositoryDirectory, ['cat-file', '-e', `${sourceCommit}^{commit}`]);
72
+ } catch {
73
+ throw new Error(
74
+ `kernel contract authority commit ${sourceCommit} is absent from the local Git object database`,
75
+ );
76
+ }
77
+ }
78
+
79
+ function exactOrigin(repositoryDirectory, schemas) {
80
+ const origin = git(repositoryDirectory, [
81
+ 'remote',
82
+ 'get-url',
83
+ 'origin',
84
+ ]).trim();
85
+ if (origin !== schemas.XEMA_KERNEL_CONTRACT_REPOSITORY_URL) {
86
+ throw new Error(
87
+ `kernel contract repository origin must be exactly ${schemas.XEMA_KERNEL_CONTRACT_REPOSITORY_URL}`,
88
+ );
89
+ }
90
+ return origin;
91
+ }
92
+
93
+ const GIT_MAX_BUFFER = 1024 * 1024 * 1024;
94
+
95
+ /**
96
+ * The blob record of every tree entry at `sourceCommit`, by path: ONE
97
+ * `ls-tree -r`, however many authority sources there are. A path absent from
98
+ * the map is absent from the commit or is not a blob.
99
+ */
100
+ function gitTreeAtCommit(repositoryDirectory, sourceCommit) {
101
+ const records = execFileSync(
102
+ '/usr/bin/git',
103
+ [...GIT_SAFE_CONFIG, '-C', repositoryDirectory, 'ls-tree', '-r', '-z', sourceCommit],
104
+ { encoding: 'buffer', stdio: ['ignore', 'pipe', 'pipe'], env: GIT_ENV, maxBuffer: GIT_MAX_BUFFER },
105
+ )
106
+ .toString('utf8')
107
+ .split('\0')
108
+ .filter(Boolean);
109
+ const tree = new Map();
110
+ for (const record of records) {
111
+ const match = GIT_TREE_RECORD_PATTERN.exec(record);
112
+ if (match) {tree.set(match[4], { mode: match[1], type: match[2], objectId: match[3] });}
113
+ }
114
+ return tree;
115
+ }
116
+
117
+ function missingSource(expectedPath) {
118
+ return new Error(
119
+ `kernel contract authority source "${expectedPath}" is missing or is not an exact Git blob`,
120
+ );
121
+ }
122
+
123
+ /**
124
+ * `{path, mode, objectId}` of each of `paths` at `sourceCommit`, in order.
125
+ * Refuses a path that is missing or is not a blob.
126
+ */
127
+ function gitBlobRecordsAtCommit(repositoryDirectory, sourceCommit, paths) {
128
+ const tree = gitTreeAtCommit(repositoryDirectory, sourceCommit);
129
+ return paths.map((path) => {
130
+ const entry = tree.get(path);
131
+ if (entry === undefined || entry.type !== 'blob') {throw missingSource(path);}
132
+ return { path, mode: entry.mode, objectId: entry.objectId };
133
+ });
134
+ }
135
+
136
+ /**
137
+ * {@link gitBlobRecordsAtCommit} with each blob's exact bytes, read by ONE
138
+ * `cat-file --batch` and matched to the object id it was asked for.
139
+ */
140
+ function gitBlobsAtCommit(repositoryDirectory, sourceCommit, paths) {
141
+ const records = gitBlobRecordsAtCommit(repositoryDirectory, sourceCommit, paths);
142
+ if (records.length === 0) {return [];}
143
+ const output = execFileSync(
144
+ '/usr/bin/git',
145
+ [...GIT_SAFE_CONFIG, '-C', repositoryDirectory, 'cat-file', '--batch'],
146
+ {
147
+ input: Buffer.from(records.map((record) => `${record.objectId}\n`).join(''), 'utf8'),
148
+ encoding: 'buffer',
149
+ stdio: ['pipe', 'pipe', 'pipe'],
150
+ env: GIT_ENV,
151
+ maxBuffer: GIT_MAX_BUFFER,
152
+ },
153
+ );
154
+ let offset = 0;
155
+ const sources = records.map((record) => {
156
+ const headerEnd = output.indexOf(0x0a, offset);
157
+ const header = headerEnd === -1 ? '' : output.subarray(offset, headerEnd).toString('utf8');
158
+ const match = /^([a-f0-9]{40,64}) blob (0|[1-9][0-9]*)$/.exec(header);
159
+ if (!match || match[1] !== record.objectId) {throw missingSource(record.path);}
160
+ const size = Number(match[2]);
161
+ const start = headerEnd + 1;
162
+ if (!Number.isSafeInteger(size) || start + size >= output.length || output[start + size] !== 0x0a) {
163
+ throw missingSource(record.path);
164
+ }
165
+ offset = start + size + 1;
166
+ return { ...record, blob: Buffer.from(output.subarray(start, start + size)) };
167
+ });
168
+ if (offset !== output.length) {
169
+ throw new Error('kernel contract authority sources: git returned more than was asked for');
170
+ }
171
+ return sources;
172
+ }
173
+
174
+ function exactAuthorityJson(value) {
175
+ return JSON.stringify(value);
176
+ }
177
+
178
+ export function assertKernelContractAuthorityEqual(actual, expected, label) {
179
+ if (exactAuthorityJson(actual) !== exactAuthorityJson(expected)) {
180
+ throw new Error(
181
+ `${label} does not exactly match kernel contract authority`,
182
+ );
183
+ }
184
+ return actual;
185
+ }
186
+
187
+ export async function readKernelContractAuthorityAtCommit({
188
+ repositoryDirectory = kernelRepoDir(),
189
+ sourceCommit,
190
+ schemas,
191
+ }) {
192
+ if (!schemas) {
193
+ throw new Error('trusted kernel distribution schemas are required');
194
+ }
195
+ exactOrigin(repositoryDirectory, schemas);
196
+ assertCommitExists(repositoryDirectory, sourceCommit);
197
+ const sources = gitBlobsAtCommit(
198
+ repositoryDirectory,
199
+ sourceCommit,
200
+ schemas.XEMA_KERNEL_CONTRACT_SOURCE_PATHS,
201
+ );
202
+ const contentDigest =
203
+ await schemas.computeKernelContractAuthorityContentDigest(sources);
204
+ return {
205
+ schemaVersion: 1,
206
+ generatorIdentity: schemas.XEMA_KERNEL_CONTRACT_AUTHORITY_GENERATOR,
207
+ repositoryUrl: schemas.XEMA_KERNEL_CONTRACT_REPOSITORY_URL,
208
+ sourceCommit,
209
+ sourcePaths: [...schemas.XEMA_KERNEL_CONTRACT_SOURCE_PATHS],
210
+ contentHashAlgorithm:
211
+ schemas.KernelContractContentHashAlgorithm.LengthFramedGitPathModeBlobV1,
212
+ contentDigest,
213
+ };
214
+ }
215
+
216
+ export function assertExactCleanKernelContractCheckout({
217
+ repositoryDirectory = kernelRepoDir(),
218
+ sourceCommit,
219
+ schemas,
220
+ }) {
221
+ exactOrigin(repositoryDirectory, schemas);
222
+ const head = exactHeadCommit(repositoryDirectory);
223
+ if (head !== sourceCommit) {
224
+ throw new Error(
225
+ `kernel contract repository HEAD ${head} does not match authority commit ${sourceCommit}`,
226
+ );
227
+ }
228
+ const status = git(
229
+ repositoryDirectory,
230
+ ['status', '--porcelain=v1', '-z', '--untracked-files=all'],
231
+ 'buffer',
232
+ );
233
+ if (status.byteLength !== 0) {
234
+ throw new Error(
235
+ 'kernel contract repository must be exactly clean for trusted generation',
236
+ );
237
+ }
238
+ const tracked = git(repositoryDirectory, ['ls-files', '-v', '-z'], 'buffer')
239
+ .toString('utf8')
240
+ .split('\0')
241
+ .filter(Boolean);
242
+ const hidden = tracked.find((record) => !record.startsWith('H '));
243
+ if (hidden) {
244
+ throw new Error(
245
+ `kernel contract repository has skip-worktree, assume-unchanged, or unexpected index state: ${hidden.slice(2)}`,
246
+ );
247
+ }
248
+ // ONE index listing and ONE batched blob read for the whole source set.
249
+ const indexByPath = new Map();
250
+ for (const record of git(repositoryDirectory, ['ls-files', '--stage', '-v', '-z'], 'buffer')
251
+ .toString('utf8')
252
+ .split('\0')
253
+ .filter(Boolean)) {
254
+ const path = record.slice(record.indexOf('\t') + 1);
255
+ indexByPath.set(path, [...(indexByPath.get(path) ?? []), record]);
256
+ }
257
+ const committed = new Map(
258
+ gitBlobsAtCommit(
259
+ repositoryDirectory,
260
+ sourceCommit,
261
+ schemas.XEMA_KERNEL_CONTRACT_SOURCE_PATHS,
262
+ ).map((source) => [source.path, source]),
263
+ );
264
+ for (const path of schemas.XEMA_KERNEL_CONTRACT_SOURCE_PATHS) {
265
+ const indexRecords = indexByPath.get(path) ?? [];
266
+ const indexMatch =
267
+ indexRecords.length === 1
268
+ ? /^H (100644) ([a-f0-9]{40,64}) 0\t(.+)$/.exec(indexRecords[0])
269
+ : null;
270
+ if (!indexMatch || indexMatch[3] !== path) {
271
+ throw new Error(
272
+ `kernel contract source has hidden flags or unexpected index state: ${path}`,
273
+ );
274
+ }
275
+ const source = committed.get(path);
276
+ const worktreePath = join(repositoryDirectory, path);
277
+ const stats = lstatSync(worktreePath);
278
+ if (
279
+ source.mode !== schemas.XEMA_KERNEL_CONTRACT_SOURCE_MODE ||
280
+ source.objectId !== indexMatch[2] ||
281
+ !stats.isFile() ||
282
+ stats.isSymbolicLink() ||
283
+ (stats.mode & 0o777) !== 0o644 ||
284
+ !readFileSync(worktreePath).equals(source.blob)
285
+ ) {
286
+ throw new Error(
287
+ `kernel contract source does not exactly match its committed Git mode/blob: ${path}`,
288
+ );
289
+ }
290
+ }
291
+ return head;
292
+ }
293
+
294
+ export async function buildTrustedKernelContractAuthority({
295
+ repositoryDirectory = kernelRepoDir(),
296
+ schemas,
297
+ }) {
298
+ const sourceCommit = exactHeadCommit(repositoryDirectory);
299
+ assertExactCleanKernelContractCheckout({
300
+ repositoryDirectory,
301
+ sourceCommit,
302
+ schemas,
303
+ });
304
+ const authority = await readKernelContractAuthorityAtCommit({
305
+ repositoryDirectory,
306
+ sourceCommit,
307
+ schemas,
308
+ });
309
+ assertExactCleanKernelContractCheckout({
310
+ repositoryDirectory,
311
+ sourceCommit,
312
+ schemas,
313
+ });
314
+ return authority;
315
+ }
316
+
317
+ /**
318
+ * Is the pinned authority on the kernel history we ship — and has the kernel's
319
+ * contract surface moved since it?
320
+ *
321
+ * THIS USED TO REFUSE ON ANY CONTENT DRIFT, and that made the image-contract
322
+ * pin an input every kernel commit could invalidate. Measured 2026-10-01:
323
+ * `dist-check` was red with "the kernel contract surface CHANGED" because
324
+ * kernel `a348e71` added one member, `ComposedProcess`, to
325
+ * `RuntimeImageSubjectKind` — a kind no committed image lock carries and this
326
+ * repository never mints. The printed remedy, `xema edition index runtime-image-source
327
+ * --repin-contract-authority`, rewrites `contractAuthority` in EVERY `images.lock.json`,
328
+ * which changes each lock's `normalizedImageLockDigest` (bound by the signed
329
+ * completeness statement) and orphans every signed producer statement (whose
330
+ * `predicate.contractAuthority` must equal the pin). Nine sealed editions, plus
331
+ * every customer edition minted under the same pin, would have lost their
332
+ * evidence for a change that altered no image. The same would follow from a
333
+ * new export in `distribution/index.ts`, which is one of the authority's
334
+ * source paths and is touched by every distribution-contract addition.
335
+ *
336
+ * Owner ruling, 2026-10-01: an unrelated change must not force historical
337
+ * editions to re-resolve or re-seal their image lists unless IMAGE SEMANTICS
338
+ * changed. The pin is a DELIBERATE image-contract version, advanced by the
339
+ * repin transaction when an edition needs semantics the pinned contract cannot
340
+ * describe — never by kernel HEAD moving underneath it.
341
+ *
342
+ * So this now REFUSES only what is a defect:
343
+ * - a pin that is not an ancestor of kernel HEAD (a different history — see
344
+ * below), and
345
+ * - (in the caller) a pin whose recorded digest does not re-derive from its
346
+ * own commit's blobs.
347
+ * Drift is RETURNED as the list of moved source paths, and the caller reports
348
+ * it. It is not silent: it is printed on every check, naming each path.
349
+ *
350
+ * What keeps a stale pin from LYING about an image lock: the standalone
351
+ * verifier is built from kernel source that descends from the pin and strict-
352
+ * parses every image lock and producer statement, and producer evidence is
353
+ * refused unless its signed `contractAuthority` equals the pin. A lock that
354
+ * used semantics newer than the pin therefore fails closed at verification,
355
+ * loudly — and that is the moment a repin is genuinely owed.
356
+ *
357
+ * The pin must still be an ancestor of HEAD. A pin on a different history is
358
+ * not "behind", it is unrelated, and recomputing a digest across a fork would
359
+ * compare content from a branch nobody is shipping.
360
+ */
361
+ async function assessAuthorityLineage({ authority, repositoryDirectory, schemas }) {
362
+ const head = exactHeadCommit(repositoryDirectory);
363
+ if (head === authority.sourceCommit) {return { head, changedPaths: [] };}
364
+
365
+ if (!isAncestor(repositoryDirectory, authority.sourceCommit, head)) {
366
+ throw new Error(
367
+ `kernel contract authority commit ${authority.sourceCommit} is not an ancestor of kernel HEAD ` +
368
+ `${head}. The pin is on a different history (a force push, or a checkout of another branch), ` +
369
+ 'so it cannot be compared by content — repin deliberately against the history you intend to ship.',
370
+ );
371
+ }
372
+
373
+ const atHead = await readKernelContractAuthorityAtCommit({
374
+ repositoryDirectory,
375
+ sourceCommit: head,
376
+ schemas,
377
+ });
378
+ if (atHead.contentDigest === authority.contentDigest) {
379
+ return { head, changedPaths: [] };
380
+ }
381
+
382
+ const changedPaths = [];
383
+ const pinned = gitBlobRecordsAtCommit(repositoryDirectory, authority.sourceCommit, authority.sourcePaths);
384
+ const current = gitBlobRecordsAtCommit(repositoryDirectory, head, authority.sourcePaths);
385
+ for (const [index, path] of authority.sourcePaths.entries()) {
386
+ if (pinned[index].objectId !== current[index].objectId) {changedPaths.push(path);}
387
+ }
388
+ return { head, changedPaths };
389
+ }
390
+
391
+ /** One line naming the drift, for callers that report it. */
392
+ export function describeKernelContractAuthorityDrift({ authority, head, changedPaths }) {
393
+ return (
394
+ `kernel contract authority ${authority.sourceCommit} is behind kernel HEAD ${head}: ` +
395
+ `${changedPaths.length} contract source path(s) moved (${changedPaths.join(', ')}). ` +
396
+ 'This is NOT a refusal — image locks stay pinned to the contract they were minted ' +
397
+ 'under. Repin (`xema edition index runtime-image-source --repin-contract-authority`) only ' +
398
+ 'when an edition needs image semantics the pinned contract cannot describe; that ' +
399
+ 'repin re-binds every edition\'s image evidence.'
400
+ );
401
+ }
402
+
403
+ export async function validateKernelContractAuthorityFromGit({
404
+ authority,
405
+ repositoryDirectory = kernelRepoDir(),
406
+ schemas,
407
+ requireHeadMatch = true,
408
+ requireCleanCheckout = false,
409
+ // Called with `{ authority, head, changedPaths }` when the pin is an ancestor
410
+ // of HEAD but the contract surface has moved since it. Reporting, never
411
+ // refusing — see `assessAuthorityLineage`.
412
+ onDrift = () => {},
413
+ }) {
414
+ if (!authority || typeof authority !== 'object') {
415
+ throw new Error('kernel contract authority is required');
416
+ }
417
+ if (requireHeadMatch) {
418
+ const { head, changedPaths } = await assessAuthorityLineage({
419
+ authority,
420
+ repositoryDirectory,
421
+ schemas,
422
+ });
423
+ if (changedPaths.length > 0) {onDrift({ authority, head, changedPaths });}
424
+ }
425
+ if (requireCleanCheckout) {
426
+ assertExactCleanKernelContractCheckout({
427
+ repositoryDirectory,
428
+ sourceCommit: authority.sourceCommit,
429
+ schemas,
430
+ });
431
+ }
432
+ const expected = await readKernelContractAuthorityAtCommit({
433
+ repositoryDirectory,
434
+ sourceCommit: authority.sourceCommit,
435
+ schemas,
436
+ });
437
+ assertKernelContractAuthorityEqual(
438
+ authority,
439
+ expected,
440
+ 'runtime image source index',
441
+ );
442
+ return authority;
443
+ }