@xemahq/xema 0.40.2 → 0.40.3

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.
@@ -2383,229 +2383,229 @@
2383
2383
  },
2384
2384
  {
2385
2385
  "package": "@xemahq/xema",
2386
- "version": "0.40.2",
2386
+ "version": "0.40.3",
2387
2387
  "path": "edition/adapter-host-versions.mjs",
2388
2388
  "sha256": "sha256:1b7ebd3592f4da1225fba74aaba912e7d85fbceeb1626f7dcdac83491cbb4d69"
2389
2389
  },
2390
2390
  {
2391
2391
  "package": "@xemahq/xema",
2392
- "version": "0.40.2",
2392
+ "version": "0.40.3",
2393
2393
  "path": "edition/authorization-base.mjs",
2394
2394
  "sha256": "sha256:bbeac7ff01376cb89ed2140526d4795d8e559b3f01612b404a701f19aaa24c6a"
2395
2395
  },
2396
2396
  {
2397
2397
  "package": "@xemahq/xema",
2398
- "version": "0.40.2",
2398
+ "version": "0.40.3",
2399
2399
  "path": "edition/build-ledger.mjs",
2400
2400
  "sha256": "sha256:05202cbdd606aee086c8e9c62d28be68c429653a8b1d73a289ed0fe4ff14da85"
2401
2401
  },
2402
2402
  {
2403
2403
  "package": "@xemahq/xema",
2404
- "version": "0.40.2",
2404
+ "version": "0.40.3",
2405
2405
  "path": "edition/connector-source-authority.mjs",
2406
2406
  "sha256": "sha256:ea94475f4324737673accd7de1303c44db1cbc4dac61a865a06cd83abe310164"
2407
2407
  },
2408
2408
  {
2409
2409
  "package": "@xemahq/xema",
2410
- "version": "0.40.2",
2410
+ "version": "0.40.3",
2411
2411
  "path": "edition/distribution-paths.mjs",
2412
2412
  "sha256": "sha256:d78a5c8405bc6c25d36fcec61b0030e1fdacac7d528e698adb96f906f4b70279"
2413
2413
  },
2414
2414
  {
2415
2415
  "package": "@xemahq/xema",
2416
- "version": "0.40.2",
2416
+ "version": "0.40.3",
2417
2417
  "path": "edition/edition-artifact.mjs",
2418
2418
  "sha256": "sha256:287e4cf042626963e07396c706350e9668adbcacd238a7ad4db1d68bba3858b2"
2419
2419
  },
2420
2420
  {
2421
2421
  "package": "@xemahq/xema",
2422
- "version": "0.40.2",
2422
+ "version": "0.40.3",
2423
2423
  "path": "edition/edition-channel.mjs",
2424
2424
  "sha256": "sha256:3c0c1564dc50be133cad500452d953353aa5beb703142c05a17bbce8175ba9f3"
2425
2425
  },
2426
2426
  {
2427
2427
  "package": "@xemahq/xema",
2428
- "version": "0.40.2",
2428
+ "version": "0.40.3",
2429
2429
  "path": "edition/edition-chart-set.mjs",
2430
2430
  "sha256": "sha256:f9f16b58279c8c2619a39c5cc92bdbc334543d1a324304cd5d3b120d66dc8f3a"
2431
2431
  },
2432
2432
  {
2433
2433
  "package": "@xemahq/xema",
2434
- "version": "0.40.2",
2434
+ "version": "0.40.3",
2435
2435
  "path": "edition/edition-config.mjs",
2436
2436
  "sha256": "sha256:e3997bd9a0082e02d01760868b2a4f41ed3081e904126b30fdf86e7938dc6137"
2437
2437
  },
2438
2438
  {
2439
2439
  "package": "@xemahq/xema",
2440
- "version": "0.40.2",
2440
+ "version": "0.40.3",
2441
2441
  "path": "edition/edition-constants.mjs",
2442
2442
  "sha256": "sha256:71ae51028847ab3430ffec9b229b0516a7705b75a6de1fcfa665f5e1abf8e9bb"
2443
2443
  },
2444
2444
  {
2445
2445
  "package": "@xemahq/xema",
2446
- "version": "0.40.2",
2446
+ "version": "0.40.3",
2447
2447
  "path": "edition/edition-contract-parse.mjs",
2448
2448
  "sha256": "sha256:c575e96b70bda82899931eb0b18567988e2e13e664fa498bcbef96b571dcabb4"
2449
2449
  },
2450
2450
  {
2451
2451
  "package": "@xemahq/xema",
2452
- "version": "0.40.2",
2452
+ "version": "0.40.3",
2453
2453
  "path": "edition/edition-contract.json",
2454
2454
  "sha256": "sha256:aa1a05f1728813a7bdb1e02d9305e9adf641d695e6767305210c67a1d81b38a7"
2455
2455
  },
2456
2456
  {
2457
2457
  "package": "@xemahq/xema",
2458
- "version": "0.40.2",
2458
+ "version": "0.40.3",
2459
2459
  "path": "edition/edition-protocol.mjs",
2460
2460
  "sha256": "sha256:c488ac72ff3f23c0f28f8db159bf84d2c86ec5cda3633db1fe91ca3f21b12214"
2461
2461
  },
2462
2462
  {
2463
2463
  "package": "@xemahq/xema",
2464
- "version": "0.40.2",
2464
+ "version": "0.40.3",
2465
2465
  "path": "edition/edition-source-pins.mjs",
2466
2466
  "sha256": "sha256:b141474fff33b06734fb62cd2552752b991c8267bb1c9bccb22a81dd80e91c6c"
2467
2467
  },
2468
2468
  {
2469
2469
  "package": "@xemahq/xema",
2470
- "version": "0.40.2",
2470
+ "version": "0.40.3",
2471
2471
  "path": "edition/edition-workspace.mjs",
2472
2472
  "sha256": "sha256:18ba727e6b13ca252def25cc6bd9adf894cbc2018198c41ce522637eeac7f871"
2473
2473
  },
2474
2474
  {
2475
2475
  "package": "@xemahq/xema",
2476
- "version": "0.40.2",
2476
+ "version": "0.40.3",
2477
2477
  "path": "edition/evidence-checkout.mjs",
2478
2478
  "sha256": "sha256:31fb669769b31de4c6f2b2501bb96959548172ef166e7568edbbfed7f5079a11"
2479
2479
  },
2480
2480
  {
2481
2481
  "package": "@xemahq/xema",
2482
- "version": "0.40.2",
2482
+ "version": "0.40.3",
2483
2483
  "path": "edition/evidence-grammars.mjs",
2484
2484
  "sha256": "sha256:4dae22b61ade99219a482b971c9780d68a3d37d021e39cdfe860b245364b8b9f"
2485
2485
  },
2486
2486
  {
2487
2487
  "package": "@xemahq/xema",
2488
- "version": "0.40.2",
2488
+ "version": "0.40.3",
2489
2489
  "path": "edition/evidence-trust-pin.mjs",
2490
2490
  "sha256": "sha256:b9dd5c01903394436530783614efb0cd357d982ed2befd844a39328479773cbc"
2491
2491
  },
2492
2492
  {
2493
2493
  "package": "@xemahq/xema",
2494
- "version": "0.40.2",
2494
+ "version": "0.40.3",
2495
2495
  "path": "edition/git-json.mjs",
2496
2496
  "sha256": "sha256:d0d25cda0c19007becabc9a2827c3dcf759b295cd0b41596eb4480c9549a1f21"
2497
2497
  },
2498
2498
  {
2499
2499
  "package": "@xemahq/xema",
2500
- "version": "0.40.2",
2500
+ "version": "0.40.3",
2501
2501
  "path": "edition/image-delivery-entitlement-descriptor.mjs",
2502
2502
  "sha256": "sha256:4726b66116f01bf2523fafe2b7a356591eca8fb5ca6bd8b154dd4821873e921b"
2503
2503
  },
2504
2504
  {
2505
2505
  "package": "@xemahq/xema",
2506
- "version": "0.40.2",
2506
+ "version": "0.40.3",
2507
2507
  "path": "edition/image-evidence-replay-state.mjs",
2508
2508
  "sha256": "sha256:eba0cf1d38af0ab2f00c5099fa8bc2edc543136ce92f895544d4d284b3d6af0f"
2509
2509
  },
2510
2510
  {
2511
2511
  "package": "@xemahq/xema",
2512
- "version": "0.40.2",
2512
+ "version": "0.40.3",
2513
2513
  "path": "edition/image-evidence.mjs",
2514
2514
  "sha256": "sha256:433202eb2db84510acd11de6fe5ede81c8d96b8bc72991444940f7f84a48897f"
2515
2515
  },
2516
2516
  {
2517
2517
  "package": "@xemahq/xema",
2518
- "version": "0.40.2",
2518
+ "version": "0.40.3",
2519
2519
  "path": "edition/kernel-capability-domain.mjs",
2520
2520
  "sha256": "sha256:3ecf887dd8d399e91a8285f6f115f6e418fb59bbf3f004539fe65ec667219356"
2521
2521
  },
2522
2522
  {
2523
2523
  "package": "@xemahq/xema",
2524
- "version": "0.40.2",
2524
+ "version": "0.40.3",
2525
2525
  "path": "edition/kernel-contract-authority.mjs",
2526
2526
  "sha256": "sha256:20abdde62a7b037c01ea9bbe1ff1e6094e6b0efd0add2f656f1938a04409bf82"
2527
2527
  },
2528
2528
  {
2529
2529
  "package": "@xemahq/xema",
2530
- "version": "0.40.2",
2530
+ "version": "0.40.3",
2531
2531
  "path": "edition/lib.mjs",
2532
2532
  "sha256": "sha256:862a21a357f5219dc6d549fe83209267a5010461f1f7889bf63d0baa6423de2e"
2533
2533
  },
2534
2534
  {
2535
2535
  "package": "@xemahq/xema",
2536
- "version": "0.40.2",
2536
+ "version": "0.40.3",
2537
2537
  "path": "edition/no-follow-file.mjs",
2538
2538
  "sha256": "sha256:c17bdf5e441f3c079aa06d4b48bbed05cad087855b9ad2a5e6dc43d5ee2b17c3"
2539
2539
  },
2540
2540
  {
2541
2541
  "package": "@xemahq/xema",
2542
- "version": "0.40.2",
2542
+ "version": "0.40.3",
2543
2543
  "path": "edition/operation-requirements.mjs",
2544
2544
  "sha256": "sha256:e7fed25747ee92be84cdc864ca35a723356df5b3d08923cf71f4bc66a791eb5d"
2545
2545
  },
2546
2546
  {
2547
2547
  "package": "@xemahq/xema",
2548
- "version": "0.40.2",
2548
+ "version": "0.40.3",
2549
2549
  "path": "edition/pin-source.mjs",
2550
2550
  "sha256": "sha256:a23fef4698ef66ba9de4a1aabe3eebe6487d1f105f3fc161bafc5a33b99a2440"
2551
2551
  },
2552
2552
  {
2553
2553
  "package": "@xemahq/xema",
2554
- "version": "0.40.2",
2554
+ "version": "0.40.3",
2555
2555
  "path": "edition/source-authority.mjs",
2556
- "sha256": "sha256:858b607dde94e2abd00a341f569e18cfa567d4bff139c64a2d60abbb7de4c85e"
2556
+ "sha256": "sha256:9c351f60e8ea838f445f89c3709c60b7f13b021b78d6e40eaef91350a793f0ff"
2557
2557
  },
2558
2558
  {
2559
2559
  "package": "@xemahq/xema",
2560
- "version": "0.40.2",
2560
+ "version": "0.40.3",
2561
2561
  "path": "edition/strict-json.mjs",
2562
2562
  "sha256": "sha256:f95778396df7f38b086f55d2ec4f3b6644a8aeff2b32282a62829145fb544562"
2563
2563
  },
2564
2564
  {
2565
2565
  "package": "@xemahq/xema",
2566
- "version": "0.40.2",
2566
+ "version": "0.40.3",
2567
2567
  "path": "edition/string-order.mjs",
2568
2568
  "sha256": "sha256:b3210cd273e4b2e01fb8b1a91e28734cd211d4010c080d0ce95f5e5ff631cffb"
2569
2569
  },
2570
2570
  {
2571
2571
  "package": "@xemahq/xema",
2572
- "version": "0.40.2",
2572
+ "version": "0.40.3",
2573
2573
  "path": "edition/trusted-executable.mjs",
2574
2574
  "sha256": "sha256:37d46cdbc999ddd55d4ea8aeed4850ddd1efde89ed7b532f5a2ddec87105d796"
2575
2575
  },
2576
2576
  {
2577
2577
  "package": "@xemahq/xema",
2578
- "version": "0.40.2",
2578
+ "version": "0.40.3",
2579
2579
  "path": "edition/verifier-binding.mjs",
2580
2580
  "sha256": "sha256:80732658617f5255258c538eeb2383331ec8553b44b58d552b567845a7b916aa"
2581
2581
  },
2582
2582
  {
2583
2583
  "package": "@xemahq/xema",
2584
- "version": "0.40.2",
2584
+ "version": "0.40.3",
2585
2585
  "path": "edition/verifier-files.mjs",
2586
2586
  "sha256": "sha256:cdd14a20a63f0f194a8abb55680a5a7e3ced1f09f8b210a90d349e655f6de0c0"
2587
2587
  },
2588
2588
  {
2589
2589
  "package": "@xemahq/xema",
2590
- "version": "0.40.2",
2590
+ "version": "0.40.3",
2591
2591
  "path": "edition/verifier-provenance.mjs",
2592
2592
  "sha256": "sha256:3e2fb8e07cf1f66b8ad6c7ac2555fe3af64ec7abeb07cecbaf1939a18c2b3663"
2593
2593
  },
2594
2594
  {
2595
2595
  "package": "@xemahq/xema",
2596
- "version": "0.40.2",
2596
+ "version": "0.40.3",
2597
2597
  "path": "edition/verify-image-evidence.mjs",
2598
2598
  "sha256": "sha256:62f83f8bb835f360535402d12657156d2c9d6e651b426c2b30a04a2dc2f8d3e8"
2599
2599
  },
2600
2600
  {
2601
2601
  "package": "@xemahq/xema",
2602
- "version": "0.40.2",
2602
+ "version": "0.40.3",
2603
2603
  "path": "edition/workspace-sections.mjs",
2604
2604
  "sha256": "sha256:383f37ce066ea5573c30cedf124022095bf94d050f0a266a5881f587cff0ced0"
2605
2605
  },
2606
2606
  {
2607
2607
  "package": "@xemahq/xema",
2608
- "version": "0.40.2",
2608
+ "version": "0.40.3",
2609
2609
  "path": "scripts/build-evidence-verifier.mjs",
2610
2610
  "sha256": "sha256:5a688d20c47dc88aff37aa0c6cc02daf2c370a5bc610fef8446ce7be222d4c0b"
2611
2611
  },
package/edition/README.md CHANGED
@@ -99,6 +99,7 @@ The edition library builds, validates and verifies evidence statements; it never
99
99
  The CLI is coupled to the kernel's edition protocol, not to the kernel package. `@xemahq/contracts` exports the protocol identity as data (`dist/edition-protocol/edition-protocol.json`: `{contentDigest, provisional, version}`). This CLI supports exactly protocol v8 (a data root whose pinned kernel speaks v5 stays on CLI 0.33.x) and REFUSES an identity with `provisional: true`; every loader that executes the kernel, `xema edition check` first, and the evidence verifier compare only that identity and refuse an unsupported one, naming the supported and the declared versions. A protocol change needs a CLI release first; only then does the data root repin its kernel authority.
100
100
 
101
101
  - `xema edition evidence statement entitlement stage` writes entitlement predicate v2 (`lockDigest`, `catalogueDigest`, read from the edition's committed locks), so it takes `--editions-root` besides `--data-root` and `--kernel`.
102
+ - A source repository JOINS a data root that has committed pins with one command: `xema edition index runtime-image-source --trusted-write --repo <repository> [--at <40-hex>] [--kernel-root <checkout>]`. The repository is resolved once and the runtime image source, biome and platform-service indexes are all written from the committed pins plus that one pin; every committed pin is kept field for field (a differing one is refused by name), a second declared repository with no pin is refused, and the summary names each pin added, moved and dropped. Then `xema edition index biome --check`, `xema edition index runtime-image-source --check` and `pnpm check`. The biome builder takes no `--repo`: it would resolve the repository a second time.
102
103
  - The runtime image source index is v2 and carries operator-stated `producerWorkflowPins` (the workflow commit a producer certificate must be signed from, per repository). `xema edition index runtime-image-source [--producer-workflow-pin <owner/repo>=<40-hex>]...` preserves the committed pins exactly; each flag sets one repository's pin (repeatable; a repeated or non-GitHub `owner/repo` is refused; only `--trusted-write` writes an index, so the flag with any other mode, `--check-sources` included, is refused); a bootstrap index with none writes `[]`. A v1 index is refused, never migrated: delete it and bootstrap a new one with `--kernel-commit`.
103
104
 
104
105
  ## Build ledger completeness
@@ -67,10 +67,15 @@ if (isEntryModule(import.meta.url)) {
67
67
  * `--unpinned` is the escape, and it is a POLICY operation rather than a
68
68
  * correctness one. Two cases need it and no third has appeared:
69
69
  *
70
- * - BOOTSTRAP — a repository that has JOINED the source set has no pin yet,
71
- * and `createPinnedSourceAuthority` refuses it rather than resolving it
72
- * live. That refusal is right (a repository joining is a full rebuild, not
73
- * a scoped refresh) and this is how the full rebuild is asked for.
70
+ * - BOOTSTRAP — a data root with no committed pins at all. A repository
71
+ * JOINING a data root that has pins is not this case: it has no pin yet,
72
+ * and `createPinnedSourceAuthority` refuses it here rather than resolving
73
+ * it live. The join is `build-runtime-image-source-index.mjs
74
+ * --trusted-write --repo <repository> [--at <sha>]`, which resolves the
75
+ * joining repository ONCE and writes this index, the platform-service
76
+ * index and the runtime index from that one pin set, every committed pin
77
+ * kept (`buildIndexesWithJoiningRepository`). A `--repo` here would
78
+ * resolve it a second time: the race above.
74
79
  * - WORKSPACE — `--source workspace` snapshots a dirty local tree, which has
75
80
  * no pin by construction. It therefore IMPLIES `--unpinned`, rather than
76
81
  * making the operator state both.
@@ -4,9 +4,10 @@ import { execFileSync } from 'node:child_process';
4
4
  import { existsSync, readFileSync, realpathSync, writeFileSync } from 'node:fs';
5
5
  import { isAbsolute, join } from 'node:path';
6
6
 
7
+ import { buildValidatedPlatformServiceIndex } from './build-platform-service-index.mjs';
7
8
  import { connectorCrossRepoBuildInputs } from './connector-source-authority.mjs';
8
9
  import { editionWorkspaceConfig, workspaceSection } from './edition-config.mjs';
9
- import { biomeIndexPath, dataRoot, runtimeImageSourceIndexPath, WorkspaceRoot, configureEditionWorkspaceFromProcess, isEntryModule, sourcePath } from './edition-workspace.mjs';
10
+ import { biomeIndexPath, dataRoot, platformServiceIndexPath, runtimeImageSourceIndexPath, WorkspaceRoot, configureEditionWorkspaceFromProcess, isEntryModule, sourcePath } from './edition-workspace.mjs';
10
11
  import { COMMIT_PATTERN } from './evidence-grammars.mjs';
11
12
  import {
12
13
  buildTrustedKernelContractAuthority,
@@ -15,8 +16,10 @@ import {
15
16
  validateKernelContractAuthorityFromGit,
16
17
  } from './kernel-contract-authority.mjs';
17
18
  import {
19
+ buildBiomeIndex,
18
20
  canonicalPublicHttpsRepositoryUrl,
19
21
  compareStrings,
22
+ distributionBiomeSourceRepositories,
20
23
  loadKernelDistributionSchemas,
21
24
  readGitJsonAtCommit,
22
25
  serializeJson,
@@ -27,6 +30,7 @@ import {
27
30
  } from './platform-service-source-authority.mjs';
28
31
  import {
29
32
  createExactCommitSourceAuthority,
33
+ createPinnedSourceAuthority,
30
34
  createSourceAuthority,
31
35
  parseSourceModeArgs,
32
36
  } from './source-authority.mjs';
@@ -140,7 +144,7 @@ function declaredSourceRepositories() {
140
144
  * ONE authority that answers both questions about a component, never two
141
145
  * competing ones that can disagree about which commit a component is at.
142
146
  */
143
- function repositoryIdentity(repo, sourceAuthority) {
147
+ export function repositoryIdentity(repo, sourceAuthority) {
144
148
  const repoDirectory = sourcePath(repo);
145
149
  const sourceCommit = sourceAuthority.commitForRepository(repo);
146
150
  const vendoredSourceCommit =
@@ -834,6 +838,107 @@ function parseKernelCommit(argv) {
834
838
  };
835
839
  }
836
840
 
841
+ /**
842
+ * @param {{ schemas: object, repositoryPins: object[], kernelRoot?: string, producerWorkflowPins?: object[] }} request
843
+ * `kernelRoot` is the kernel checkout the committed contract authority is re-verified against
844
+ * (the default checkout when omitted); `producerWorkflowPins` are the operator-stated ones a
845
+ * trusted write merges over the committed ones.
846
+ * @returns {{ platformServiceIndex, biomeIndex, runtimeImageSourceIndex }}
847
+ */
848
+ export async function buildIndexesAtPins({ schemas, repositoryPins, kernelRoot, producerWorkflowPins = [] }) {
849
+ const sourceAuthority = createPinnedSourceAuthority({ pins: repositoryPins });
850
+ const platformServiceIndex = buildValidatedPlatformServiceIndex(schemas, sourceAuthority);
851
+ const biomeIndex = await buildBiomeIndex(platformServiceIndex, distributionBiomeSourceRepositories(), sourceAuthority);
852
+ const runtimeImageSourceIndex = schemas.parseRuntimeImageSourceIndex(
853
+ await buildTrustedIndex(schemas, sourceAuthority, kernelRoot, undefined, {
854
+ biomeIndex,
855
+ platformServiceIndex,
856
+ producerWorkflowPins,
857
+ }),
858
+ );
859
+ await validateRuntimeImageSourceIndex(runtimeImageSourceIndex, schemas, { biomeIndex, platformServiceIndex, kernelRoot });
860
+ return { platformServiceIndex, biomeIndex, runtimeImageSourceIndex };
861
+ }
862
+
863
+ /**
864
+ * A repository JOINING the index (`--repo <repository>` with no committed pin): all three indexes,
865
+ * from ONE resolution of the joining repository, with every committed pin kept.
866
+ *
867
+ * WHY HERE AND NOT A `--repo` ON THE BIOME BUILDER. The biome builder's `--repo` was removed
868
+ * because it resolved the scoped repository LIVE a second time, so a build landing between the two
869
+ * commands split the two artifacts on exactly that repository (see `build-biome-index.mjs`). A
870
+ * joining repository has the same shape: the runtime index cannot take it until the biome index
871
+ * names its biomes, and the biome index cannot take it until a pin exists. So the joining
872
+ * repository is resolved ONCE here (`sourceAuthority`, live or `--at`), appended to the committed
873
+ * pins, and every index is derived from that one pin set by the same builders a develop snapshot
874
+ * uses; nothing else is resolved live.
875
+ *
876
+ * Every committed pin that survives must come back field for field; one that differs is refused by
877
+ * name. A committed pin whose repository is no longer a source repository is DROPPED, and the caller
878
+ * names it (`describeRepositoryPinChanges`). A second declared repository with no pin is refused by
879
+ * the pinned authority: one joining repository per write.
880
+ */
881
+ export async function buildIndexesWithJoiningRepository({
882
+ schemas,
883
+ sourceAuthority,
884
+ repoScope,
885
+ committedPins,
886
+ kernelRoot,
887
+ producerWorkflowPins = [],
888
+ }) {
889
+ if (committedPins.some((pin) => pin.repo === repoScope)) {
890
+ throw new Error(`--repo "${repoScope}" already has a committed pin: it is a refresh, not a join`);
891
+ }
892
+ const kept = committedPins.map(({ repo, repositoryUrl, sourceCommit, vendoredSourceCommit }) => ({
893
+ repo,
894
+ repositoryUrl,
895
+ sourceCommit,
896
+ vendoredSourceCommit,
897
+ }));
898
+ const repositoryPins = [...kept, repositoryIdentity(repoScope, sourceAuthority)];
899
+ const built = await buildIndexesAtPins({ schemas, repositoryPins, kernelRoot, producerWorkflowPins });
900
+ const written = new Map(built.runtimeImageSourceIndex.repositoryPins.map((pin) => [pin.repo, pin]));
901
+ const moved = kept.filter((pin) => written.has(pin.repo) && serializeJson(written.get(pin.repo)) !== serializeJson(pin));
902
+ if (moved.length > 0) {
903
+ throw new Error(
904
+ `a join must keep every committed pin, and ${moved.map((pin) => pin.repo).join(', ')} came back different ` +
905
+ '(its repositoryUrl no longer matches its origin remote?)',
906
+ );
907
+ }
908
+ if (!written.has(repoScope)) {
909
+ throw new Error(
910
+ `--repo "${repoScope}" contributes no biome, platform service or supplemental subject at ` +
911
+ `${repositoryPins.at(-1).sourceCommit}: there is nothing for it to join`,
912
+ );
913
+ }
914
+ return built;
915
+ }
916
+
917
+ /**
918
+ * What a write did to the committed `repositoryPins`, by NAME: the repositories added, the ones
919
+ * dropped (a committed pin whose repository no longer sources any subject), the ones moved, and
920
+ * how many were kept unchanged. A count alone hid a dropped pin inside "N other pin(s)
921
+ * preserved", which is the one change a reviewer must see.
922
+ */
923
+ export function describeRepositoryPinChanges(committedPins, writtenPins) {
924
+ const committed = new Map((committedPins ?? []).map((pin) => [pin.repo, pin]));
925
+ const written = new Map(writtenPins.map((pin) => [pin.repo, pin]));
926
+ const added = [...written.keys()].filter((repo) => !committed.has(repo)).sort(compareStrings);
927
+ const dropped = [...committed.keys()].filter((repo) => !written.has(repo)).sort(compareStrings);
928
+ const moved = [...written.keys()]
929
+ .filter((repo) => committed.has(repo) && serializeJson(committed.get(repo)) !== serializeJson(written.get(repo)))
930
+ .sort(compareStrings);
931
+ const kept = written.size - added.length - moved.length;
932
+ const named = (label, repos) => `${label} ${repos.length}${repos.length > 0 ? ` (${repos.join(', ')})` : ''}`;
933
+ return {
934
+ added,
935
+ dropped,
936
+ moved,
937
+ kept,
938
+ summary: `${named('added', added)}; ${named('moved', moved)}; ${named('dropped', dropped)}; kept ${kept} unchanged`,
939
+ };
940
+ }
941
+
837
942
  const REPIN_FLAG = '--repin-contract-authority';
838
943
  const USAGE =
839
944
  'usage: build-runtime-image-source-index.mjs (--check | --check-sources | --trusted-write | ' +
@@ -898,55 +1003,78 @@ async function main() {
898
1003
  );
899
1004
  return;
900
1005
  }
901
- const candidate = schemas.parseRuntimeImageSourceIndex(
902
- await buildTrustedIndex(
1006
+ const committedPins = existsSync(runtimeImageSourceIndexPath())
1007
+ ? readJson(runtimeImageSourceIndexPath()).repositoryPins ?? []
1008
+ : [];
1009
+ const liveAuthority = () => createSourceAuthority({ mode, knownRepositories: declaredSourceRepositories() });
1010
+ const sourceAuthority =
1011
+ exactCommit === undefined
1012
+ ? liveAuthority()
1013
+ : createExactCommitSourceAuthority({ delegate: liveAuthority(), repo: repoScope, commit: exactCommit });
1014
+ // A `--repo` with no committed pin JOINS the index, and the biome and platform-service indexes
1015
+ // must take it in the same write: see `buildIndexesWithJoiningRepository`.
1016
+ const joining = repoScope !== undefined && !committedPins.some((pin) => pin.repo === repoScope);
1017
+ let candidate;
1018
+ let joined;
1019
+ if (joining) {
1020
+ if (committedPins.length === 0) {
1021
+ throw new Error(
1022
+ `--repo requires ${runtimeImageSourceIndexPath()} with committed repositoryPins: there are none to keep. ` +
1023
+ 'Bootstrap with --trusted-write and no --repo.',
1024
+ );
1025
+ }
1026
+ if (kernelCommit !== undefined) {
1027
+ throw new Error(`${KERNEL_COMMIT_FLAG} chooses a new contract authority; a join keeps the committed one`);
1028
+ }
1029
+ joined = await buildIndexesWithJoiningRepository({
903
1030
  schemas,
904
- exactCommit === undefined
905
- ? createSourceAuthority({
906
- mode,
907
- knownRepositories: declaredSourceRepositories(),
908
- })
909
- : createExactCommitSourceAuthority({
910
- delegate: createSourceAuthority({
911
- mode,
912
- knownRepositories: declaredSourceRepositories(),
913
- }),
914
- repo: repoScope,
915
- commit: exactCommit,
916
- }),
917
- kernelRoot,
1031
+ sourceAuthority,
918
1032
  repoScope,
919
- {
1033
+ committedPins,
1034
+ kernelRoot,
1035
+ producerWorkflowPins,
1036
+ });
1037
+ candidate = joined.runtimeImageSourceIndex;
1038
+ } else {
1039
+ candidate = schemas.parseRuntimeImageSourceIndex(
1040
+ await buildTrustedIndex(schemas, sourceAuthority, kernelRoot, repoScope, {
920
1041
  biomeIndex: readJson(biomeIndexPath()),
921
1042
  platformServiceIndex: readJson(join(dataRoot(), 'platform-service-index.json')),
922
1043
  kernelCommit,
923
1044
  producerWorkflowPins,
924
- },
925
- ),
926
- );
927
- await validateRuntimeImageSourceIndex(candidate, schemas, { kernelRoot });
1045
+ }),
1046
+ );
1047
+ await validateRuntimeImageSourceIndex(candidate, schemas, { kernelRoot });
1048
+ }
1049
+ const outputs = [
1050
+ [runtimeImageSourceIndexPath(), serializeJson(candidate)],
1051
+ ...(joined === undefined
1052
+ ? []
1053
+ : [
1054
+ [biomeIndexPath(), serializeJson(joined.biomeIndex)],
1055
+ [platformServiceIndexPath(), serializeJson(joined.platformServiceIndex)],
1056
+ ]),
1057
+ ];
928
1058
  if (flag === '--check-sources') {
929
- if (
930
- readFileSync(runtimeImageSourceIndexPath(), 'utf8') !==
931
- serializeJson(candidate)
932
- ) {
1059
+ const stale = outputs.filter(([path, text]) => !existsSync(path) || readFileSync(path, 'utf8') !== text);
1060
+ if (stale.length > 0) {
933
1061
  throw new Error(
934
- 'runtime-image-source-index.json does not match exact trusted sources',
1062
+ `${stale.map(([path]) => path).join(', ')} does not match exact trusted sources`,
935
1063
  );
936
1064
  }
937
1065
  console.log('[runtime-image-source-index] trusted source check passed');
938
1066
  return;
939
1067
  }
940
- writeFileSync(runtimeImageSourceIndexPath(), serializeJson(candidate));
1068
+ for (const [path, text] of outputs) {writeFileSync(path, text);}
1069
+ const changes = describeRepositoryPinChanges(committedPins, candidate.repositoryPins);
941
1070
  console.log(
942
- `[runtime-image-source-index] wrote ${runtimeImageSourceIndexPath()}` +
943
- (repoScope === undefined
944
- ? ' (all repository pins re-resolved)'
945
- : ` (${
946
- exactCommit === undefined
947
- ? `re-resolved ${repoScope}`
948
- : `pinned ${repoScope} at the exact commit ${exactCommit}`
949
- }; ${candidate.repositoryPins.length - 1} other pin(s) preserved)`),
1071
+ `[runtime-image-source-index] wrote ${outputs.map(([path]) => path).join(', ')} (${
1072
+ repoScope === undefined
1073
+ ? 'all repository pins re-resolved'
1074
+ : `${joining ? 'joined' : 're-resolved'} ${repoScope}${
1075
+ exactCommit === undefined ? '' : ` at the exact commit ${exactCommit}`
1076
+ }`
1077
+ }; ${changes.summary})`,
950
1078
  );
951
1079
  }
952
1080
 
@@ -5,25 +5,9 @@
5
5
  // authority; this runs the same builders, in memory, over any pin set. A develop
6
6
  // snapshot (develop-snapshot-inputs.mjs) and a vendor edition that holds
7
7
  // repositories (edition-pinned-inputs.mjs) both derive their indexes here, so
8
- // there is no second builder. No fallback: the authority has no delegate, so a
9
- // repository the pins do not name refuses by name instead of resolving to a tip.
8
+ // there is no second builder. The implementation lives beside the runtime index
9
+ // builder, whose repository join (`--repo` with no committed pin) uses it too.
10
+ // No fallback: the authority has no delegate, so a repository the pins do not
11
+ // name refuses by name instead of resolving to a tip.
10
12
 
11
- import { buildValidatedPlatformServiceIndex } from './build-platform-service-index.mjs';
12
- import { buildTrustedIndex, validateRuntimeImageSourceIndex } from './build-runtime-image-source-index.mjs';
13
- import { buildBiomeIndex, distributionBiomeSourceRepositories } from './lib.mjs';
14
- import { createPinnedSourceAuthority } from './source-authority.mjs';
15
-
16
- /**
17
- * @param {{ schemas: object, repositoryPins: object[] }} request
18
- * @returns {{ platformServiceIndex, biomeIndex, runtimeImageSourceIndex }}
19
- */
20
- export async function buildIndexesAtPins({ schemas, repositoryPins }) {
21
- const sourceAuthority = createPinnedSourceAuthority({ pins: repositoryPins });
22
- const platformServiceIndex = buildValidatedPlatformServiceIndex(schemas, sourceAuthority);
23
- const biomeIndex = await buildBiomeIndex(platformServiceIndex, distributionBiomeSourceRepositories(), sourceAuthority);
24
- const runtimeImageSourceIndex = schemas.parseRuntimeImageSourceIndex(
25
- await buildTrustedIndex(schemas, sourceAuthority, undefined, undefined, { biomeIndex, platformServiceIndex }),
26
- );
27
- await validateRuntimeImageSourceIndex(runtimeImageSourceIndex, schemas, { biomeIndex, platformServiceIndex });
28
- return { platformServiceIndex, biomeIndex, runtimeImageSourceIndex };
29
- }
13
+ export { buildIndexesAtPins, buildIndexesWithJoiningRepository } from './build-runtime-image-source-index.mjs';
@@ -519,7 +519,8 @@ export function createPinnedSourceAuthority({
519
519
  if (refreshRepository !== undefined && !byRepo.has(refreshRepository)) {
520
520
  throw new Error(
521
521
  `refresh scope "${refreshRepository}" has no pin to refresh. A repository ` +
522
- `joining the index is a full rebuild, not a scoped refresh.`,
522
+ `joining the index joins through build-runtime-image-source-index.mjs ` +
523
+ `--trusted-write --repo ${refreshRepository}, which writes every index.`,
523
524
  );
524
525
  }
525
526
  const pinned = (repo, field) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xemahq/xema",
3
- "version": "0.40.2",
3
+ "version": "0.40.3",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/xema-dev/xema-cli.git",
@@ -92,6 +92,6 @@
92
92
  "typecheck": "tsc --noEmit"
93
93
  },
94
94
  "xemaRelease": {
95
- "sourceDigest": "sha256:7354d3fd083104ce7289eb00634ebdd7dea0aefb38bd527bce4ab63dd52114aa"
95
+ "sourceDigest": "sha256:55a458c54ff157ee053b4f074068c355fb35d28f54a01b0a2662bf14b07b8323"
96
96
  }
97
97
  }