@xemahq/xema 0.43.0 → 0.44.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/edition/verify-image-evidence.bundle.mjs +75 -6
- package/dist/edition/verify-image-evidence.bundle.provenance.json +71 -47
- package/edition/README.md +14 -0
- package/edition/build-platform-service-index.mjs +1 -1
- package/edition/build-runtime-image-source-index.mjs +25 -1
- package/edition/canonical-repository-url.mjs +35 -0
- package/edition/checkout-indexed-source-repositories.mjs +2 -1
- package/edition/develop-edition.mjs +3 -4
- package/edition/develop-freeze.mjs +8 -7
- package/edition/develop-graph.mjs +26 -0
- package/edition/develop-snapshot-inputs.mjs +0 -1
- package/edition/edition-config.mjs +27 -1
- package/edition/first-party-source-ids.mjs +15 -12
- package/edition/git-source-auth.mjs +1 -1
- package/edition/lib.mjs +0 -32
- package/edition/pin-source.mjs +21 -14
- package/edition/workspace-sections.mjs +35 -5
- package/package.json +2 -2
|
@@ -62888,6 +62888,39 @@ var AuthorizationLockCheck = Object.freeze({
|
|
|
62888
62888
|
// edition/edition-config.mjs
|
|
62889
62889
|
import { join as join3 } from "node:path";
|
|
62890
62890
|
|
|
62891
|
+
// edition/canonical-repository-url.mjs
|
|
62892
|
+
function canonicalPublicHttpsRepositoryUrl(raw, context) {
|
|
62893
|
+
if (typeof raw !== "string" || raw.length === 0 || raw.trim() !== raw) {
|
|
62894
|
+
throw new Error(`${context} must be a non-empty canonical HTTPS URL`);
|
|
62895
|
+
}
|
|
62896
|
+
let parsed;
|
|
62897
|
+
try {
|
|
62898
|
+
parsed = new URL(raw);
|
|
62899
|
+
} catch {
|
|
62900
|
+
throw new Error(`${context} is not a valid URL`);
|
|
62901
|
+
}
|
|
62902
|
+
if (parsed.protocol !== "https:") {
|
|
62903
|
+
throw new Error(`${context} must use HTTPS`);
|
|
62904
|
+
}
|
|
62905
|
+
if (parsed.username.length > 0 || parsed.password.length > 0) {
|
|
62906
|
+
throw new Error(
|
|
62907
|
+
`${context} must not contain username/password credentials`
|
|
62908
|
+
);
|
|
62909
|
+
}
|
|
62910
|
+
if (parsed.search.length > 0 || parsed.hash.length > 0 || raw.includes("?") || raw.includes("#")) {
|
|
62911
|
+
throw new Error(`${context} must not contain a query or fragment`);
|
|
62912
|
+
}
|
|
62913
|
+
if (parsed.hostname.length === 0) {
|
|
62914
|
+
throw new Error(`${context} must identify a repository host`);
|
|
62915
|
+
}
|
|
62916
|
+
return parsed.toString();
|
|
62917
|
+
}
|
|
62918
|
+
|
|
62919
|
+
// edition/git-source-auth.mjs
|
|
62920
|
+
function isUnderSourcePrefix(canonicalUrl, prefix) {
|
|
62921
|
+
return canonicalUrl.startsWith(prefix) && canonicalUrl.length > prefix.length;
|
|
62922
|
+
}
|
|
62923
|
+
|
|
62891
62924
|
// edition/strict-json.mjs
|
|
62892
62925
|
import { readFileSync } from "node:fs";
|
|
62893
62926
|
var DuplicateJsonKeyError = class extends Error {
|
|
@@ -63056,18 +63089,33 @@ function supplementalLauncher(value, where) {
|
|
|
63056
63089
|
}
|
|
63057
63090
|
return Object.freeze({ kind: value.kind, biomeId: text(value.biomeId, `${where}.biomeId`) });
|
|
63058
63091
|
}
|
|
63092
|
+
var RetiredFirstPartySourceShapeError = class extends Error {
|
|
63093
|
+
constructor(repo, id) {
|
|
63094
|
+
super(
|
|
63095
|
+
`${FILE}: "firstPartySources"["${repo}"] is the retired source-id string ${JSON.stringify(id)}. Migrate it to { "sourceId": ${JSON.stringify(id)}, "repositoryUrl": "<the repository's canonical https URL>" }.`
|
|
63096
|
+
);
|
|
63097
|
+
this.name = "RetiredFirstPartySourceShapeError";
|
|
63098
|
+
}
|
|
63099
|
+
};
|
|
63059
63100
|
function parseFirstPartySources(value) {
|
|
63060
63101
|
const where = '"firstPartySources"';
|
|
63061
63102
|
if (!isObject(value) || Object.keys(value).length === 0) {
|
|
63062
|
-
fail(where, "must be a non-empty {repo handle:
|
|
63103
|
+
fail(where, "must be a non-empty {repo handle: { sourceId, repositoryUrl }} object");
|
|
63063
63104
|
}
|
|
63064
|
-
const entries = Object.entries(value).map(([repo,
|
|
63105
|
+
const entries = Object.entries(value).map(([repo, entry]) => {
|
|
63065
63106
|
repoHandle(repo, `${where} key`);
|
|
63066
|
-
if (typeof
|
|
63067
|
-
|
|
63107
|
+
if (typeof entry === "string") {
|
|
63108
|
+
throw new RetiredFirstPartySourceShapeError(repo, entry);
|
|
63109
|
+
}
|
|
63110
|
+
const at = `${where}["${repo}"]`;
|
|
63111
|
+
exactObject(entry, at, ["sourceId", "repositoryUrl"]);
|
|
63112
|
+
if (typeof entry.sourceId !== "string" || !SOURCE_ID.test(entry.sourceId)) {
|
|
63113
|
+
fail(`${at}.sourceId`, `must be a lowercase kebab-case source id; got ${JSON.stringify(entry.sourceId)}`);
|
|
63068
63114
|
}
|
|
63069
|
-
return [repo,
|
|
63115
|
+
return [repo, Object.freeze({ sourceId: entry.sourceId, repositoryUrl: text(entry.repositoryUrl, `${at}.repositoryUrl`) })];
|
|
63070
63116
|
});
|
|
63117
|
+
uniqueBy(entries, `${where} sourceId`, ([, entry]) => entry.sourceId);
|
|
63118
|
+
uniqueBy(entries, `${where} repositoryUrl`, ([, entry]) => entry.repositoryUrl);
|
|
63071
63119
|
return Object.freeze(Object.fromEntries(entries));
|
|
63072
63120
|
}
|
|
63073
63121
|
function parseRuntimeBinding(value, where) {
|
|
@@ -63299,6 +63347,23 @@ function requireEvidenceTrustPolicy(config) {
|
|
|
63299
63347
|
}
|
|
63300
63348
|
return Object.freeze({ policy: value.policy, pin: value.pin });
|
|
63301
63349
|
}
|
|
63350
|
+
function assertFirstPartyUrls(firstPartySources2, prefix) {
|
|
63351
|
+
for (const [repo, { repositoryUrl }] of Object.entries(firstPartySources2)) {
|
|
63352
|
+
const where = `${EDITION_WORKSPACE_CONFIG_FILE}: "firstPartySources"["${repo}"].repositoryUrl`;
|
|
63353
|
+
let canonical;
|
|
63354
|
+
try {
|
|
63355
|
+
canonical = canonicalPublicHttpsRepositoryUrl(repositoryUrl, where);
|
|
63356
|
+
} catch (err) {
|
|
63357
|
+
throw new EditionWorkspaceConfigError(err.message);
|
|
63358
|
+
}
|
|
63359
|
+
if (canonical !== repositoryUrl) {
|
|
63360
|
+
throw new EditionWorkspaceConfigError(`${where} ${JSON.stringify(repositoryUrl)} is not in its canonical form (${canonical})`);
|
|
63361
|
+
}
|
|
63362
|
+
if (!isUnderSourcePrefix(canonical, prefix)) {
|
|
63363
|
+
throw new EditionWorkspaceConfigError(`${where} ${JSON.stringify(repositoryUrl)} is not under sourceRepositoryUrlPrefix ${prefix}`);
|
|
63364
|
+
}
|
|
63365
|
+
}
|
|
63366
|
+
}
|
|
63302
63367
|
function parseEditionWorkspaceConfig(document) {
|
|
63303
63368
|
if (document === null || typeof document !== "object" || Array.isArray(document)) {
|
|
63304
63369
|
throw new Error(`${EDITION_WORKSPACE_CONFIG_FILE} must be a JSON object`);
|
|
@@ -63314,11 +63379,15 @@ function parseEditionWorkspaceConfig(document) {
|
|
|
63314
63379
|
const sections = Object.fromEntries(
|
|
63315
63380
|
Object.entries(SECTIONS).filter(([key]) => document[key] !== void 0).map(([key, parse]) => [key, parse(document[key])])
|
|
63316
63381
|
);
|
|
63382
|
+
const sourceRepositoryUrlPrefix = requireString(document, "sourceRepositoryUrlPrefix", SOURCE_URL_PREFIX, "https://<host>/<organisation>/ with a trailing slash");
|
|
63383
|
+
if (sections.firstPartySources !== void 0) {
|
|
63384
|
+
assertFirstPartyUrls(sections.firstPartySources, sourceRepositoryUrlPrefix);
|
|
63385
|
+
}
|
|
63317
63386
|
return Object.freeze({
|
|
63318
63387
|
...sections,
|
|
63319
63388
|
imageRegistry: requireString(document, "imageRegistry", IMAGE_REGISTRY, "<host>/<namespace> with no scheme or trailing slash"),
|
|
63320
63389
|
...optionalChartSet(document),
|
|
63321
|
-
sourceRepositoryUrlPrefix
|
|
63390
|
+
sourceRepositoryUrlPrefix,
|
|
63322
63391
|
sourceAuthUsername: requireSourceAuthUsername(document),
|
|
63323
63392
|
evidenceTrustPolicy: requireEvidenceTrustPolicy(document)
|
|
63324
63393
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"artifactSha256": "sha256:
|
|
3
|
+
"artifactSha256": "sha256:945753c501638be9393cd1ebc697d58c2ddb11adbdebcdac7ae045e2cd34bf9b",
|
|
4
4
|
"esbuild": {
|
|
5
5
|
"version": "0.28.2"
|
|
6
6
|
},
|
|
@@ -2419,253 +2419,277 @@
|
|
|
2419
2419
|
},
|
|
2420
2420
|
{
|
|
2421
2421
|
"package": "@xemahq/xema",
|
|
2422
|
-
"version": "0.
|
|
2422
|
+
"version": "0.44.0",
|
|
2423
2423
|
"path": "edition/adapter-host-versions.mjs",
|
|
2424
2424
|
"sha256": "sha256:1b7ebd3592f4da1225fba74aaba912e7d85fbceeb1626f7dcdac83491cbb4d69"
|
|
2425
2425
|
},
|
|
2426
2426
|
{
|
|
2427
2427
|
"package": "@xemahq/xema",
|
|
2428
|
-
"version": "0.
|
|
2428
|
+
"version": "0.44.0",
|
|
2429
2429
|
"path": "edition/authorization-base.mjs",
|
|
2430
2430
|
"sha256": "sha256:bbeac7ff01376cb89ed2140526d4795d8e559b3f01612b404a701f19aaa24c6a"
|
|
2431
2431
|
},
|
|
2432
2432
|
{
|
|
2433
2433
|
"package": "@xemahq/xema",
|
|
2434
|
-
"version": "0.
|
|
2434
|
+
"version": "0.44.0",
|
|
2435
2435
|
"path": "edition/build-ledger.mjs",
|
|
2436
2436
|
"sha256": "sha256:05202cbdd606aee086c8e9c62d28be68c429653a8b1d73a289ed0fe4ff14da85"
|
|
2437
2437
|
},
|
|
2438
2438
|
{
|
|
2439
2439
|
"package": "@xemahq/xema",
|
|
2440
|
-
"version": "0.
|
|
2440
|
+
"version": "0.44.0",
|
|
2441
|
+
"path": "edition/canonical-repository-url.mjs",
|
|
2442
|
+
"sha256": "sha256:91e7590afba782a3ee2fa64084e7c715e6a5b52c11e8a9cc68c64397f181b888"
|
|
2443
|
+
},
|
|
2444
|
+
{
|
|
2445
|
+
"package": "@xemahq/xema",
|
|
2446
|
+
"version": "0.44.0",
|
|
2441
2447
|
"path": "edition/connector-source-authority.mjs",
|
|
2442
2448
|
"sha256": "sha256:ea94475f4324737673accd7de1303c44db1cbc4dac61a865a06cd83abe310164"
|
|
2443
2449
|
},
|
|
2444
2450
|
{
|
|
2445
2451
|
"package": "@xemahq/xema",
|
|
2446
|
-
"version": "0.
|
|
2452
|
+
"version": "0.44.0",
|
|
2453
|
+
"path": "edition/develop-graph.mjs",
|
|
2454
|
+
"sha256": "sha256:319db77e708dd73a9dd4a81a963d86e52b030cad3346b104c0862af4e38a2237"
|
|
2455
|
+
},
|
|
2456
|
+
{
|
|
2457
|
+
"package": "@xemahq/xema",
|
|
2458
|
+
"version": "0.44.0",
|
|
2447
2459
|
"path": "edition/dispatched-runtime-components.mjs",
|
|
2448
2460
|
"sha256": "sha256:9712823d183a60108cd002b2d36a9e5c0867b9d576dd02825264a18f7b8ece69"
|
|
2449
2461
|
},
|
|
2450
2462
|
{
|
|
2451
2463
|
"package": "@xemahq/xema",
|
|
2452
|
-
"version": "0.
|
|
2464
|
+
"version": "0.44.0",
|
|
2453
2465
|
"path": "edition/distribution-paths.mjs",
|
|
2454
2466
|
"sha256": "sha256:d78a5c8405bc6c25d36fcec61b0030e1fdacac7d528e698adb96f906f4b70279"
|
|
2455
2467
|
},
|
|
2456
2468
|
{
|
|
2457
2469
|
"package": "@xemahq/xema",
|
|
2458
|
-
"version": "0.
|
|
2470
|
+
"version": "0.44.0",
|
|
2459
2471
|
"path": "edition/edition-artifact.mjs",
|
|
2460
2472
|
"sha256": "sha256:282b6d5e70736f7e41cff41167d2cd99b7d0127dcc2b712132888dcfc82e36cd"
|
|
2461
2473
|
},
|
|
2462
2474
|
{
|
|
2463
2475
|
"package": "@xemahq/xema",
|
|
2464
|
-
"version": "0.
|
|
2476
|
+
"version": "0.44.0",
|
|
2465
2477
|
"path": "edition/edition-channel.mjs",
|
|
2466
2478
|
"sha256": "sha256:3c0c1564dc50be133cad500452d953353aa5beb703142c05a17bbce8175ba9f3"
|
|
2467
2479
|
},
|
|
2468
2480
|
{
|
|
2469
2481
|
"package": "@xemahq/xema",
|
|
2470
|
-
"version": "0.
|
|
2482
|
+
"version": "0.44.0",
|
|
2471
2483
|
"path": "edition/edition-chart-set.mjs",
|
|
2472
2484
|
"sha256": "sha256:f9f16b58279c8c2619a39c5cc92bdbc334543d1a324304cd5d3b120d66dc8f3a"
|
|
2473
2485
|
},
|
|
2474
2486
|
{
|
|
2475
2487
|
"package": "@xemahq/xema",
|
|
2476
|
-
"version": "0.
|
|
2488
|
+
"version": "0.44.0",
|
|
2477
2489
|
"path": "edition/edition-config.mjs",
|
|
2478
|
-
"sha256": "sha256:
|
|
2490
|
+
"sha256": "sha256:7604e79a480c9b9db1d1c256155f05298adf6caa49da95ad232f6be581f3c72e"
|
|
2479
2491
|
},
|
|
2480
2492
|
{
|
|
2481
2493
|
"package": "@xemahq/xema",
|
|
2482
|
-
"version": "0.
|
|
2494
|
+
"version": "0.44.0",
|
|
2483
2495
|
"path": "edition/edition-constants.mjs",
|
|
2484
2496
|
"sha256": "sha256:d3747ca300f38832796521b47d12076bdba7f214199a1dec52c9384ab3dde394"
|
|
2485
2497
|
},
|
|
2486
2498
|
{
|
|
2487
2499
|
"package": "@xemahq/xema",
|
|
2488
|
-
"version": "0.
|
|
2500
|
+
"version": "0.44.0",
|
|
2489
2501
|
"path": "edition/edition-contract-parse.mjs",
|
|
2490
2502
|
"sha256": "sha256:c575e96b70bda82899931eb0b18567988e2e13e664fa498bcbef96b571dcabb4"
|
|
2491
2503
|
},
|
|
2492
2504
|
{
|
|
2493
2505
|
"package": "@xemahq/xema",
|
|
2494
|
-
"version": "0.
|
|
2506
|
+
"version": "0.44.0",
|
|
2495
2507
|
"path": "edition/edition-contract.json",
|
|
2496
2508
|
"sha256": "sha256:af7c88854007e63e4e77863cf72610f59ff838959dbd381087ec50ba782ae0e1"
|
|
2497
2509
|
},
|
|
2498
2510
|
{
|
|
2499
2511
|
"package": "@xemahq/xema",
|
|
2500
|
-
"version": "0.
|
|
2512
|
+
"version": "0.44.0",
|
|
2501
2513
|
"path": "edition/edition-protocol.mjs",
|
|
2502
2514
|
"sha256": "sha256:1f10ece5f34b6a6b0dbe169b6cbabe4b1b0778a1bc136c9c5c29b54181b1f546"
|
|
2503
2515
|
},
|
|
2504
2516
|
{
|
|
2505
2517
|
"package": "@xemahq/xema",
|
|
2506
|
-
"version": "0.
|
|
2518
|
+
"version": "0.44.0",
|
|
2507
2519
|
"path": "edition/edition-source-pins.mjs",
|
|
2508
2520
|
"sha256": "sha256:b141474fff33b06734fb62cd2552752b991c8267bb1c9bccb22a81dd80e91c6c"
|
|
2509
2521
|
},
|
|
2510
2522
|
{
|
|
2511
2523
|
"package": "@xemahq/xema",
|
|
2512
|
-
"version": "0.
|
|
2524
|
+
"version": "0.44.0",
|
|
2513
2525
|
"path": "edition/edition-workspace.mjs",
|
|
2514
2526
|
"sha256": "sha256:892b360f9f80d2308b0b12f9b99658ae06173878d9b71cf5d6fc8bd717b3c86e"
|
|
2515
2527
|
},
|
|
2516
2528
|
{
|
|
2517
2529
|
"package": "@xemahq/xema",
|
|
2518
|
-
"version": "0.
|
|
2530
|
+
"version": "0.44.0",
|
|
2519
2531
|
"path": "edition/evidence-checkout.mjs",
|
|
2520
2532
|
"sha256": "sha256:31fb669769b31de4c6f2b2501bb96959548172ef166e7568edbbfed7f5079a11"
|
|
2521
2533
|
},
|
|
2522
2534
|
{
|
|
2523
2535
|
"package": "@xemahq/xema",
|
|
2524
|
-
"version": "0.
|
|
2536
|
+
"version": "0.44.0",
|
|
2525
2537
|
"path": "edition/evidence-grammars.mjs",
|
|
2526
2538
|
"sha256": "sha256:4dae22b61ade99219a482b971c9780d68a3d37d021e39cdfe860b245364b8b9f"
|
|
2527
2539
|
},
|
|
2528
2540
|
{
|
|
2529
2541
|
"package": "@xemahq/xema",
|
|
2530
|
-
"version": "0.
|
|
2542
|
+
"version": "0.44.0",
|
|
2531
2543
|
"path": "edition/evidence-trust-pin.mjs",
|
|
2532
2544
|
"sha256": "sha256:65077589d1d9bd129c175c03a6dca6bb4eaf277fa29d6391e1065654fe8cc246"
|
|
2533
2545
|
},
|
|
2534
2546
|
{
|
|
2535
2547
|
"package": "@xemahq/xema",
|
|
2536
|
-
"version": "0.
|
|
2548
|
+
"version": "0.44.0",
|
|
2549
|
+
"path": "edition/first-party-source-ids.mjs",
|
|
2550
|
+
"sha256": "sha256:20cb1ad7cfb8c6e6686a6be5a51289da4bba76c20b58d9c1b5b6b3813a38dc1c"
|
|
2551
|
+
},
|
|
2552
|
+
{
|
|
2553
|
+
"package": "@xemahq/xema",
|
|
2554
|
+
"version": "0.44.0",
|
|
2537
2555
|
"path": "edition/git-json.mjs",
|
|
2538
2556
|
"sha256": "sha256:d0d25cda0c19007becabc9a2827c3dcf759b295cd0b41596eb4480c9549a1f21"
|
|
2539
2557
|
},
|
|
2540
2558
|
{
|
|
2541
2559
|
"package": "@xemahq/xema",
|
|
2542
|
-
"version": "0.
|
|
2560
|
+
"version": "0.44.0",
|
|
2561
|
+
"path": "edition/git-source-auth.mjs",
|
|
2562
|
+
"sha256": "sha256:c1b32aa6f7d59798db58238d632cafeadbd4f07376396a31acdeb5f0ac56dcac"
|
|
2563
|
+
},
|
|
2564
|
+
{
|
|
2565
|
+
"package": "@xemahq/xema",
|
|
2566
|
+
"version": "0.44.0",
|
|
2543
2567
|
"path": "edition/image-delivery-entitlement-descriptor.mjs",
|
|
2544
2568
|
"sha256": "sha256:4726b66116f01bf2523fafe2b7a356591eca8fb5ca6bd8b154dd4821873e921b"
|
|
2545
2569
|
},
|
|
2546
2570
|
{
|
|
2547
2571
|
"package": "@xemahq/xema",
|
|
2548
|
-
"version": "0.
|
|
2572
|
+
"version": "0.44.0",
|
|
2549
2573
|
"path": "edition/image-evidence-replay-state.mjs",
|
|
2550
2574
|
"sha256": "sha256:eba0cf1d38af0ab2f00c5099fa8bc2edc543136ce92f895544d4d284b3d6af0f"
|
|
2551
2575
|
},
|
|
2552
2576
|
{
|
|
2553
2577
|
"package": "@xemahq/xema",
|
|
2554
|
-
"version": "0.
|
|
2578
|
+
"version": "0.44.0",
|
|
2555
2579
|
"path": "edition/image-evidence.mjs",
|
|
2556
2580
|
"sha256": "sha256:8ccbd4e7ce482cbb25521e893ac047d31ca1da782e87ee2179629e8911aa6aa3"
|
|
2557
2581
|
},
|
|
2558
2582
|
{
|
|
2559
2583
|
"package": "@xemahq/xema",
|
|
2560
|
-
"version": "0.
|
|
2584
|
+
"version": "0.44.0",
|
|
2561
2585
|
"path": "edition/kernel-capability-domain.mjs",
|
|
2562
2586
|
"sha256": "sha256:3ecf887dd8d399e91a8285f6f115f6e418fb59bbf3f004539fe65ec667219356"
|
|
2563
2587
|
},
|
|
2564
2588
|
{
|
|
2565
2589
|
"package": "@xemahq/xema",
|
|
2566
|
-
"version": "0.
|
|
2590
|
+
"version": "0.44.0",
|
|
2567
2591
|
"path": "edition/kernel-contract-authority.mjs",
|
|
2568
2592
|
"sha256": "sha256:20abdde62a7b037c01ea9bbe1ff1e6094e6b0efd0add2f656f1938a04409bf82"
|
|
2569
2593
|
},
|
|
2570
2594
|
{
|
|
2571
2595
|
"package": "@xemahq/xema",
|
|
2572
|
-
"version": "0.
|
|
2596
|
+
"version": "0.44.0",
|
|
2573
2597
|
"path": "edition/lib.mjs",
|
|
2574
|
-
"sha256": "sha256:
|
|
2598
|
+
"sha256": "sha256:2cf8f4b52f9532f536a27c8bf6736cc760d7026754945209b1cf5be23391ef49"
|
|
2575
2599
|
},
|
|
2576
2600
|
{
|
|
2577
2601
|
"package": "@xemahq/xema",
|
|
2578
|
-
"version": "0.
|
|
2602
|
+
"version": "0.44.0",
|
|
2579
2603
|
"path": "edition/no-follow-file.mjs",
|
|
2580
2604
|
"sha256": "sha256:c17bdf5e441f3c079aa06d4b48bbed05cad087855b9ad2a5e6dc43d5ee2b17c3"
|
|
2581
2605
|
},
|
|
2582
2606
|
{
|
|
2583
2607
|
"package": "@xemahq/xema",
|
|
2584
|
-
"version": "0.
|
|
2608
|
+
"version": "0.44.0",
|
|
2585
2609
|
"path": "edition/operation-requirements.mjs",
|
|
2586
2610
|
"sha256": "sha256:92656c5e920fca2a5a75c0b8db133d551ba044c84a149f33435479248b3dff8b"
|
|
2587
2611
|
},
|
|
2588
2612
|
{
|
|
2589
2613
|
"package": "@xemahq/xema",
|
|
2590
|
-
"version": "0.
|
|
2614
|
+
"version": "0.44.0",
|
|
2591
2615
|
"path": "edition/pin-source.mjs",
|
|
2592
|
-
"sha256": "sha256:
|
|
2616
|
+
"sha256": "sha256:32fa918729a83587b4d3bca6e5fe381f5a525397ebc49d52729ec1f8cf216c37"
|
|
2593
2617
|
},
|
|
2594
2618
|
{
|
|
2595
2619
|
"package": "@xemahq/xema",
|
|
2596
|
-
"version": "0.
|
|
2620
|
+
"version": "0.44.0",
|
|
2597
2621
|
"path": "edition/sigstore-bundle-media-type.mjs",
|
|
2598
2622
|
"sha256": "sha256:e0aa5a3992f0385ddbbe69eb0307d642ac428ef516c8394816ac4fd52e0ac39c"
|
|
2599
2623
|
},
|
|
2600
2624
|
{
|
|
2601
2625
|
"package": "@xemahq/xema",
|
|
2602
|
-
"version": "0.
|
|
2626
|
+
"version": "0.44.0",
|
|
2603
2627
|
"path": "edition/source-authority.mjs",
|
|
2604
2628
|
"sha256": "sha256:9c351f60e8ea838f445f89c3709c60b7f13b021b78d6e40eaef91350a793f0ff"
|
|
2605
2629
|
},
|
|
2606
2630
|
{
|
|
2607
2631
|
"package": "@xemahq/xema",
|
|
2608
|
-
"version": "0.
|
|
2632
|
+
"version": "0.44.0",
|
|
2609
2633
|
"path": "edition/strict-json.mjs",
|
|
2610
2634
|
"sha256": "sha256:f95778396df7f38b086f55d2ec4f3b6644a8aeff2b32282a62829145fb544562"
|
|
2611
2635
|
},
|
|
2612
2636
|
{
|
|
2613
2637
|
"package": "@xemahq/xema",
|
|
2614
|
-
"version": "0.
|
|
2638
|
+
"version": "0.44.0",
|
|
2615
2639
|
"path": "edition/string-order.mjs",
|
|
2616
2640
|
"sha256": "sha256:b3210cd273e4b2e01fb8b1a91e28734cd211d4010c080d0ce95f5e5ff631cffb"
|
|
2617
2641
|
},
|
|
2618
2642
|
{
|
|
2619
2643
|
"package": "@xemahq/xema",
|
|
2620
|
-
"version": "0.
|
|
2644
|
+
"version": "0.44.0",
|
|
2621
2645
|
"path": "edition/trust-policy-arming.mjs",
|
|
2622
2646
|
"sha256": "sha256:6f6f192118407e71a744087caea988bcf3d33aca9803217bceb8134d7e2b9120"
|
|
2623
2647
|
},
|
|
2624
2648
|
{
|
|
2625
2649
|
"package": "@xemahq/xema",
|
|
2626
|
-
"version": "0.
|
|
2650
|
+
"version": "0.44.0",
|
|
2627
2651
|
"path": "edition/trusted-executable.mjs",
|
|
2628
2652
|
"sha256": "sha256:37d46cdbc999ddd55d4ea8aeed4850ddd1efde89ed7b532f5a2ddec87105d796"
|
|
2629
2653
|
},
|
|
2630
2654
|
{
|
|
2631
2655
|
"package": "@xemahq/xema",
|
|
2632
|
-
"version": "0.
|
|
2656
|
+
"version": "0.44.0",
|
|
2633
2657
|
"path": "edition/verifier-binding.mjs",
|
|
2634
2658
|
"sha256": "sha256:80732658617f5255258c538eeb2383331ec8553b44b58d552b567845a7b916aa"
|
|
2635
2659
|
},
|
|
2636
2660
|
{
|
|
2637
2661
|
"package": "@xemahq/xema",
|
|
2638
|
-
"version": "0.
|
|
2662
|
+
"version": "0.44.0",
|
|
2639
2663
|
"path": "edition/verifier-files.mjs",
|
|
2640
2664
|
"sha256": "sha256:cdd14a20a63f0f194a8abb55680a5a7e3ced1f09f8b210a90d349e655f6de0c0"
|
|
2641
2665
|
},
|
|
2642
2666
|
{
|
|
2643
2667
|
"package": "@xemahq/xema",
|
|
2644
|
-
"version": "0.
|
|
2668
|
+
"version": "0.44.0",
|
|
2645
2669
|
"path": "edition/verifier-provenance.mjs",
|
|
2646
2670
|
"sha256": "sha256:3e2fb8e07cf1f66b8ad6c7ac2555fe3af64ec7abeb07cecbaf1939a18c2b3663"
|
|
2647
2671
|
},
|
|
2648
2672
|
{
|
|
2649
2673
|
"package": "@xemahq/xema",
|
|
2650
|
-
"version": "0.
|
|
2674
|
+
"version": "0.44.0",
|
|
2651
2675
|
"path": "edition/verify-catalogue-evidence.mjs",
|
|
2652
2676
|
"sha256": "sha256:d7e42efc9edced97e4330cddc46183445a870ba22f57896a7ea6bc600d369137"
|
|
2653
2677
|
},
|
|
2654
2678
|
{
|
|
2655
2679
|
"package": "@xemahq/xema",
|
|
2656
|
-
"version": "0.
|
|
2680
|
+
"version": "0.44.0",
|
|
2657
2681
|
"path": "edition/verify-image-evidence.mjs",
|
|
2658
2682
|
"sha256": "sha256:396936f3d9e0ed68902b8b03c6fa02479e439357e6194ac8ce0e0991be834706"
|
|
2659
2683
|
},
|
|
2660
2684
|
{
|
|
2661
2685
|
"package": "@xemahq/xema",
|
|
2662
|
-
"version": "0.
|
|
2686
|
+
"version": "0.44.0",
|
|
2663
2687
|
"path": "edition/workspace-sections.mjs",
|
|
2664
|
-
"sha256": "sha256:
|
|
2688
|
+
"sha256": "sha256:18c28e9fa2a6b7d8589c8434fc6ed9f1e93954d41339813e711ed0fbbcf5c61d"
|
|
2665
2689
|
},
|
|
2666
2690
|
{
|
|
2667
2691
|
"package": "@xemahq/xema",
|
|
2668
|
-
"version": "0.
|
|
2692
|
+
"version": "0.44.0",
|
|
2669
2693
|
"path": "scripts/build-evidence-verifier.mjs",
|
|
2670
2694
|
"sha256": "sha256:5a688d20c47dc88aff37aa0c6cc02daf2c370a5bc610fef8446ce7be222d4c0b"
|
|
2671
2695
|
},
|
package/edition/README.md
CHANGED
|
@@ -106,6 +106,20 @@ It refuses on:
|
|
|
106
106
|
|
|
107
107
|
It reads no lock content (the committed `distribution`, `images`, `authorization` and per-edition `chart-set` locks; the trust-arming rule counts lock files by presence) and no source checkout at a pinned commit. The sources root is read only for the kernel contract checkout the runtime-image-source index check verifies its authority against. `--check-sources` is refused: it is the release check's source audit.
|
|
108
108
|
|
|
109
|
+
## Develop graph
|
|
110
|
+
|
|
111
|
+
The develop graph is the data root's first-party repositories, declared in `edition-workspace.json`:
|
|
112
|
+
|
|
113
|
+
```json
|
|
114
|
+
"firstPartySources": {
|
|
115
|
+
"repos/xema-base": { "sourceId": "xema-base", "repositoryUrl": "https://git.example.com/acme/xema-base.git" }
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
- Each key is a repository handle (`repos/<x>` or `submodules/<x>`); `sourceId` is its kebab-case source id; `repositoryUrl` is canonical HTTPS under `sourceRepositoryUrlPrefix`. Both are unique. The retired string form (`"repos/x": "x"`) is refused with `RetiredFirstPartySourceShapeError`.
|
|
120
|
+
- `xema edition develop freeze-graph` pins every declared repository at the `develop` head of its declared URL; overrides may name any of them. The snapshot's `repositoryPins` (`develop resolve`, `checkout-indexed-source-repositories.mjs --snapshot-pins`) are exactly these repositories. The runtime image source index's `repositoryPins` are release pins and are not read on develop.
|
|
121
|
+
- A first-party repository is in the develop graph before any release pins it. The converse is checked offline: every `repositoryPins` entry must be declared first-party at the same `repositoryUrl`.
|
|
122
|
+
|
|
109
123
|
## Develop plane invariants
|
|
110
124
|
|
|
111
125
|
`xema edition develop resolve` asserts the capability-domain reservation table and biome confinement on the candidate it resolves, against the develop graph it froze, before any later phase. On develop they are asserted only there; `xema edition check` asserts them over the committed locks.
|
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
import { execFileSync } from 'node:child_process';
|
|
4
4
|
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
5
5
|
|
|
6
|
+
import { canonicalPublicHttpsRepositoryUrl } from './canonical-repository-url.mjs';
|
|
6
7
|
import { platformServiceIndexPath , WorkspaceRoot, configureEditionWorkspaceFromProcess, isEntryModule, sourcePath } from './edition-workspace.mjs';
|
|
7
8
|
import {
|
|
8
|
-
canonicalPublicHttpsRepositoryUrl,
|
|
9
9
|
loadKernelDistributionSchemas,
|
|
10
10
|
readGitJsonAtCommit,
|
|
11
11
|
serializeJson,
|
|
@@ -5,10 +5,12 @@ import { existsSync, readFileSync, realpathSync, writeFileSync } from 'node:fs';
|
|
|
5
5
|
import { isAbsolute, join } from 'node:path';
|
|
6
6
|
|
|
7
7
|
import { buildValidatedPlatformServiceIndex } from './build-platform-service-index.mjs';
|
|
8
|
+
import { canonicalPublicHttpsRepositoryUrl } from './canonical-repository-url.mjs';
|
|
8
9
|
import { connectorCrossRepoBuildInputs } from './connector-source-authority.mjs';
|
|
9
10
|
import { editionWorkspaceConfig, workspaceSection } from './edition-config.mjs';
|
|
10
11
|
import { biomeIndexPath, dataRoot, platformServiceIndexPath, runtimeImageSourceIndexPath, WorkspaceRoot, configureEditionWorkspaceFromProcess, isEntryModule, sourcePath } from './edition-workspace.mjs';
|
|
11
12
|
import { COMMIT_PATTERN } from './evidence-grammars.mjs';
|
|
13
|
+
import { firstPartySources } from './first-party-source-ids.mjs';
|
|
12
14
|
import {
|
|
13
15
|
buildTrustedKernelContractAuthority,
|
|
14
16
|
KERNEL_COMMIT_FLAG,
|
|
@@ -17,7 +19,6 @@ import {
|
|
|
17
19
|
} from './kernel-contract-authority.mjs';
|
|
18
20
|
import {
|
|
19
21
|
buildBiomeIndex,
|
|
20
|
-
canonicalPublicHttpsRepositoryUrl,
|
|
21
22
|
compareStrings,
|
|
22
23
|
distributionBiomeSourceRepositories,
|
|
23
24
|
loadKernelDistributionSchemas,
|
|
@@ -572,6 +573,28 @@ function compareSubject(left, right) {
|
|
|
572
573
|
);
|
|
573
574
|
}
|
|
574
575
|
|
|
576
|
+
/**
|
|
577
|
+
* Every release pin names a first-party repository (`firstPartySources`) at
|
|
578
|
+
* the URL declared there. The converse is not required: a first-party
|
|
579
|
+
* repository is in the develop graph before any release pins it.
|
|
580
|
+
*/
|
|
581
|
+
function assertPinsAreFirstParty(repositoryPins) {
|
|
582
|
+
const declared = firstPartySources('checking that every release pin is a first-party repository');
|
|
583
|
+
for (const pin of repositoryPins) {
|
|
584
|
+
const source = declared[pin.repo];
|
|
585
|
+
if (source === undefined) {
|
|
586
|
+
throw new Error(
|
|
587
|
+
`runtime-image-source-index pins ${JSON.stringify(pin.repo)}, which edition-workspace.json does not declare under "firstPartySources"`,
|
|
588
|
+
);
|
|
589
|
+
}
|
|
590
|
+
if (source.repositoryUrl !== pin.repositoryUrl) {
|
|
591
|
+
throw new Error(
|
|
592
|
+
`runtime-image-source-index pins ${pin.repo} at ${pin.repositoryUrl}, but "firstPartySources" declares it at ${source.repositoryUrl}`,
|
|
593
|
+
);
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
|
|
575
598
|
export async function validateRuntimeImageSourceIndex(
|
|
576
599
|
index,
|
|
577
600
|
schemas,
|
|
@@ -658,6 +681,7 @@ export async function validateRuntimeImageSourceIndex(
|
|
|
658
681
|
throw new Error(`${repository.repo} repositoryUrl is not canonical`);
|
|
659
682
|
}
|
|
660
683
|
}
|
|
684
|
+
assertPinsAreFirstParty(index.repositoryPins);
|
|
661
685
|
// Every subject a biome contributes to the image plane — BOTH the standing
|
|
662
686
|
// services and the runtime components. A runtime component is a full image
|
|
663
687
|
// subject (built, signed, mirrored) that simply is never scheduled, so a
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// The canonical form of a public HTTPS repository URL, and the refusals that
|
|
2
|
+
// define it. Leaf module: the workspace config and the git source hold read it
|
|
3
|
+
// without importing the edition library.
|
|
4
|
+
|
|
5
|
+
export function canonicalPublicHttpsRepositoryUrl(raw, context) {
|
|
6
|
+
if (typeof raw !== 'string' || raw.length === 0 || raw.trim() !== raw) {
|
|
7
|
+
throw new Error(`${context} must be a non-empty canonical HTTPS URL`);
|
|
8
|
+
}
|
|
9
|
+
let parsed;
|
|
10
|
+
try {
|
|
11
|
+
parsed = new URL(raw);
|
|
12
|
+
} catch {
|
|
13
|
+
throw new Error(`${context} is not a valid URL`);
|
|
14
|
+
}
|
|
15
|
+
if (parsed.protocol !== 'https:') {
|
|
16
|
+
throw new Error(`${context} must use HTTPS`);
|
|
17
|
+
}
|
|
18
|
+
if (parsed.username.length > 0 || parsed.password.length > 0) {
|
|
19
|
+
throw new Error(
|
|
20
|
+
`${context} must not contain username/password credentials`,
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
if (
|
|
24
|
+
parsed.search.length > 0 ||
|
|
25
|
+
parsed.hash.length > 0 ||
|
|
26
|
+
raw.includes('?') ||
|
|
27
|
+
raw.includes('#')
|
|
28
|
+
) {
|
|
29
|
+
throw new Error(`${context} must not contain a query or fragment`);
|
|
30
|
+
}
|
|
31
|
+
if (parsed.hostname.length === 0) {
|
|
32
|
+
throw new Error(`${context} must identify a repository host`);
|
|
33
|
+
}
|
|
34
|
+
return parsed.toString();
|
|
35
|
+
}
|
|
@@ -250,7 +250,8 @@ const SNAPSHOT_PINS_FLAG = '--snapshot-pins';
|
|
|
250
250
|
|
|
251
251
|
/**
|
|
252
252
|
* `--snapshot-pins <file>` is the only argument: with it, the plan is the
|
|
253
|
-
*
|
|
253
|
+
* develop graph (the data root's firstPartySources) at that snapshot (the
|
|
254
|
+
* develop edition's frozen pins), never the committed index's repositoryPins;
|
|
254
255
|
* without it, the committed index untouched.
|
|
255
256
|
*/
|
|
256
257
|
export function indexForCheckout(committedIndex, argv) {
|
|
@@ -28,8 +28,8 @@
|
|
|
28
28
|
// freeze-graph --data-sha <sha> --kernel-sha <sha>
|
|
29
29
|
// --deploy-sha <sha> [--editions-root-url <https url>]
|
|
30
30
|
// [--overrides <json>] --out <file>
|
|
31
|
-
// resolve refs/heads/develop once per graph repository (
|
|
32
|
-
//
|
|
31
|
+
// resolve refs/heads/develop once per develop-graph repository (the data root's
|
|
32
|
+
// firstPartySources, at each declared URL) and the editions root; the three checked-out
|
|
33
33
|
// repositories are pinned at what the workflow checked out; write the pins. --kernel-sha is refused
|
|
34
34
|
// unless its declared edition protocol is one this CLI supports (the check choose-kernel applies to a pin).
|
|
35
35
|
// check-existing <id> --pins <file> --artifact <dir> --engine-image <repository>@sha256:<digest>
|
|
@@ -76,7 +76,7 @@ import { resolveDevelopEdition } from './develop-edition-resolve.mjs';
|
|
|
76
76
|
import { assertNoToolingOverride, freezeGraph, freezeSources } from './develop-freeze.mjs';
|
|
77
77
|
import { LOCAL_WORKFLOW, assertExistingArtifactMatches, developEditionTarget, workflowFromEnv, writeEditionArtifact } from './edition-artifact.mjs';
|
|
78
78
|
import { EditionChannel } from './edition-constants.mjs';
|
|
79
|
-
import { WorkspaceRoot, configureEditionWorkspaceFromProcess, isEntryModule, kernelRepoDir
|
|
79
|
+
import { WorkspaceRoot, configureEditionWorkspaceFromProcess, isEntryModule, kernelRepoDir } from './edition-workspace.mjs';
|
|
80
80
|
import { descriptorEvidenceTrust } from './evidence-trust-policy.mjs';
|
|
81
81
|
import { assertKernelCommitSupported, chooseKernelCommit } from './kernel-protocol-choice.mjs';
|
|
82
82
|
import { loadKernelDistributionSchemas } from './lib.mjs';
|
|
@@ -187,7 +187,6 @@ async function run(argv, env) {
|
|
|
187
187
|
case 'freeze-graph': {
|
|
188
188
|
assertKernelCommitSupported({ repoDir: kernelRepoDir(), commit: options['--kernel-sha'], label: `--kernel-sha ${options['--kernel-sha']}` });
|
|
189
189
|
const pins = freezeGraph({
|
|
190
|
-
runtimeSourceIndex: readStrictJsonFile(runtimeImageSourceIndexPath()),
|
|
191
190
|
dataSha: options['--data-sha'],
|
|
192
191
|
kernelSha: options['--kernel-sha'],
|
|
193
192
|
deploySha: options['--deploy-sha'],
|
|
@@ -15,9 +15,10 @@
|
|
|
15
15
|
|
|
16
16
|
import { execFileSync } from 'node:child_process';
|
|
17
17
|
|
|
18
|
+
import { canonicalPublicHttpsRepositoryUrl } from './canonical-repository-url.mjs';
|
|
19
|
+
import { developGraphMembers } from './develop-graph.mjs';
|
|
18
20
|
import { EditionChannel } from './edition-channel.mjs';
|
|
19
21
|
import { assertCanonicalSourceUrl, assertSourceDeclaresNoCredential, isUnderSourcePrefix, withSourceGitEnv } from './git-source-auth.mjs';
|
|
20
|
-
import { canonicalPublicHttpsRepositoryUrl } from './lib.mjs';
|
|
21
22
|
import {
|
|
22
23
|
dataPinKey,
|
|
23
24
|
EDITIONS_ROOT_PIN_KEY,
|
|
@@ -144,7 +145,6 @@ function assertCheckedOutPin({ key, sha, flag, what, overrides }) {
|
|
|
144
145
|
|
|
145
146
|
/**
|
|
146
147
|
* @param {object} request
|
|
147
|
-
* @param {object} request.runtimeSourceIndex the checkout's runtime-image-source-index.json (raw)
|
|
148
148
|
* @param {string} request.dataSha the commit the data root checkout is at
|
|
149
149
|
* @param {string} request.kernelSha the commit the kernel checkout is at
|
|
150
150
|
* @param {string} request.deploySha the commit the deploy checkout is at
|
|
@@ -152,7 +152,6 @@ function assertCheckedOutPin({ key, sha, flag, what, overrides }) {
|
|
|
152
152
|
* @param {(source: {name: string, url: string}) => void} request.holdSource the hold of one source; runs for ALL graph sources before the first head
|
|
153
153
|
*/
|
|
154
154
|
export function freezeGraph({
|
|
155
|
-
runtimeSourceIndex,
|
|
156
155
|
dataSha,
|
|
157
156
|
kernelSha,
|
|
158
157
|
deploySha,
|
|
@@ -164,7 +163,9 @@ export function freezeGraph({
|
|
|
164
163
|
assertNoToolingOverride(overrides);
|
|
165
164
|
assertOverrides(overrides, GRAPH_PHASE);
|
|
166
165
|
const head = memoizedHeads(resolveHead);
|
|
167
|
-
|
|
166
|
+
// The develop graph is the data root's first-party repositories (develop-graph.mjs), never the release pins.
|
|
167
|
+
const members = developGraphMembers();
|
|
168
|
+
const graph = new Set(members.map((member) => member.repo));
|
|
168
169
|
for (const [key] of Object.entries(overrides)) {
|
|
169
170
|
if (snapshotPinNamespace(key) === SnapshotPinNamespace.Graph && !graph.has(key)) {
|
|
170
171
|
throw new Error(`override names "${key}", which is not a repository in the graph`);
|
|
@@ -176,7 +177,7 @@ export function freezeGraph({
|
|
|
176
177
|
{ key: deployPinKey(), sha: deploySha, flag: '--deploy-sha', what: 'the deploy checkout' },
|
|
177
178
|
];
|
|
178
179
|
for (const { key, sha, flag, what } of checkedOut) {assertCheckedOutPin({ key, sha, flag, what, overrides });}
|
|
179
|
-
const toResolve =
|
|
180
|
+
const toResolve = members.map((member) => ({ name: member.repo, url: member.repositoryUrl }));
|
|
180
181
|
if (editionsRootUrl !== undefined) {
|
|
181
182
|
toResolve.push({ name: EDITIONS_ROOT_PIN_KEY, url: canonicalPublicHttpsRepositoryUrl(editionsRootUrl, '--editions-root-url') });
|
|
182
183
|
}
|
|
@@ -186,8 +187,8 @@ export function freezeGraph({
|
|
|
186
187
|
throw new Error(`override names "${EDITIONS_ROOT_PIN_KEY}", but the editions root is this repository`);
|
|
187
188
|
}
|
|
188
189
|
const pins = Object.fromEntries(checkedOut.map(({ key, sha }) => [key, sha]));
|
|
189
|
-
for (const
|
|
190
|
-
pins[
|
|
190
|
+
for (const member of members) {
|
|
191
|
+
pins[member.repo] = overrides[member.repo] ?? head({ name: member.repo, url: member.repositoryUrl });
|
|
191
192
|
}
|
|
192
193
|
if (editionsRootUrl !== undefined) {
|
|
193
194
|
const url = canonicalPublicHttpsRepositoryUrl(editionsRootUrl, '--editions-root-url');
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// develop-graph — which repositories a DEVELOP candidate is built from.
|
|
2
|
+
//
|
|
3
|
+
// The develop graph is every first-party repository the data root declares
|
|
4
|
+
// (`firstPartySources` in edition-workspace.json), each at its declared
|
|
5
|
+
// `repositoryUrl`. It is never read from the release pins
|
|
6
|
+
// (`runtime-image-source-index.json#repositoryPins`): develop and release are
|
|
7
|
+
// separate lifecycles, and a repository is first-party on develop before any
|
|
8
|
+
// release pins it. freeze-graph, the snapshot checkout, the snapshot pins'
|
|
9
|
+
// validation and the snapshot's index all take their members from here.
|
|
10
|
+
|
|
11
|
+
import { firstPartySources } from './first-party-source-ids.mjs';
|
|
12
|
+
import { compareStrings } from './lib.mjs';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* The develop graph: `{ repo, repositoryUrl }` per declared first-party
|
|
16
|
+
* repository, sorted by repo handle.
|
|
17
|
+
*
|
|
18
|
+
* @returns {readonly { repo: string, repositoryUrl: string }[]}
|
|
19
|
+
*/
|
|
20
|
+
export function developGraphMembers() {
|
|
21
|
+
return Object.freeze(
|
|
22
|
+
Object.entries(firstPartySources('the develop graph'))
|
|
23
|
+
.map(([repo, { repositoryUrl }]) => Object.freeze({ repo, repositoryUrl }))
|
|
24
|
+
.sort((a, b) => compareStrings(a.repo, b.repo)),
|
|
25
|
+
);
|
|
26
|
+
}
|
|
@@ -81,7 +81,6 @@ export async function buildSnapshotInputs({ id, pins, schemas }) {
|
|
|
81
81
|
readStrictJsonFile(runtimeImageSourceIndexPath()),
|
|
82
82
|
);
|
|
83
83
|
validateSnapshotPins(pins, {
|
|
84
|
-
runtimeSourceIndex: committedIndex,
|
|
85
84
|
declaredSourceNames: composed.declaredSourceNames,
|
|
86
85
|
editionsRootExternal: editionsRoot() !== dataRoot(),
|
|
87
86
|
});
|
|
@@ -7,7 +7,9 @@
|
|
|
7
7
|
|
|
8
8
|
import { join } from 'node:path';
|
|
9
9
|
|
|
10
|
+
import { canonicalPublicHttpsRepositoryUrl } from './canonical-repository-url.mjs';
|
|
10
11
|
import { dataRoot } from './edition-workspace.mjs';
|
|
12
|
+
import { isUnderSourcePrefix } from './git-source-auth.mjs';
|
|
11
13
|
import { readStrictJsonFile } from './strict-json.mjs';
|
|
12
14
|
import {
|
|
13
15
|
parseConfinedBiomes,
|
|
@@ -163,6 +165,28 @@ function requireEvidenceTrustPolicy(config) {
|
|
|
163
165
|
return Object.freeze({ policy: value.policy, pin: value.pin });
|
|
164
166
|
}
|
|
165
167
|
|
|
168
|
+
/**
|
|
169
|
+
* Every first-party repository URL is canonical and under the workspace's
|
|
170
|
+
* `sourceRepositoryUrlPrefix`: the develop graph sends the source token there.
|
|
171
|
+
*/
|
|
172
|
+
function assertFirstPartyUrls(firstPartySources, prefix) {
|
|
173
|
+
for (const [repo, { repositoryUrl }] of Object.entries(firstPartySources)) {
|
|
174
|
+
const where = `${EDITION_WORKSPACE_CONFIG_FILE}: "firstPartySources"["${repo}"].repositoryUrl`;
|
|
175
|
+
let canonical;
|
|
176
|
+
try {
|
|
177
|
+
canonical = canonicalPublicHttpsRepositoryUrl(repositoryUrl, where);
|
|
178
|
+
} catch (err) {
|
|
179
|
+
throw new EditionWorkspaceConfigError(err.message);
|
|
180
|
+
}
|
|
181
|
+
if (canonical !== repositoryUrl) {
|
|
182
|
+
throw new EditionWorkspaceConfigError(`${where} ${JSON.stringify(repositoryUrl)} is not in its canonical form (${canonical})`);
|
|
183
|
+
}
|
|
184
|
+
if (!isUnderSourcePrefix(canonical, prefix)) {
|
|
185
|
+
throw new EditionWorkspaceConfigError(`${where} ${JSON.stringify(repositoryUrl)} is not under sourceRepositoryUrlPrefix ${prefix}`);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
166
190
|
/** Validate a parsed config document; returns the frozen, typed config. */
|
|
167
191
|
export function parseEditionWorkspaceConfig(document) {
|
|
168
192
|
if (document === null || typeof document !== 'object' || Array.isArray(document)) {
|
|
@@ -179,11 +203,13 @@ export function parseEditionWorkspaceConfig(document) {
|
|
|
179
203
|
.filter(([key]) => document[key] !== undefined)
|
|
180
204
|
.map(([key, parse]) => [key, parse(document[key])]),
|
|
181
205
|
);
|
|
206
|
+
const sourceRepositoryUrlPrefix = requireString(document, 'sourceRepositoryUrlPrefix', SOURCE_URL_PREFIX, 'https://<host>/<organisation>/ with a trailing slash');
|
|
207
|
+
if (sections.firstPartySources !== undefined) {assertFirstPartyUrls(sections.firstPartySources, sourceRepositoryUrlPrefix);}
|
|
182
208
|
return Object.freeze({
|
|
183
209
|
...sections,
|
|
184
210
|
imageRegistry: requireString(document, 'imageRegistry', IMAGE_REGISTRY, '<host>/<namespace> with no scheme or trailing slash'),
|
|
185
211
|
...optionalChartSet(document),
|
|
186
|
-
sourceRepositoryUrlPrefix
|
|
212
|
+
sourceRepositoryUrlPrefix,
|
|
187
213
|
sourceAuthUsername: requireSourceAuthUsername(document),
|
|
188
214
|
evidenceTrustPolicy: requireEvidenceTrustPolicy(document),
|
|
189
215
|
});
|
|
@@ -15,20 +15,23 @@ import { workspaceSection } from './edition-config.mjs';
|
|
|
15
15
|
// `sources[]` entry, so its id is declared in the data root's
|
|
16
16
|
// `edition-workspace.json` (`firstPartySources`), keyed by the `repo` handle the
|
|
17
17
|
// runtime image source index pins it under. The key is a lookup handle and may
|
|
18
|
-
// move with the aggregator layout; the
|
|
18
|
+
// move with the aggregator layout; the entry's `sourceId` is the identity and
|
|
19
|
+
// must not. The entry's `repositoryUrl` is where the repository lives
|
|
20
|
+
// (provenance, never identity): the develop graph resolves heads there.
|
|
19
21
|
//
|
|
20
|
-
// EVERY repository `runtime-image-source-index.json` pins is declared there
|
|
21
|
-
//
|
|
22
|
-
// refused at once rather than the day it first declares a
|
|
23
|
-
//
|
|
24
|
-
//
|
|
22
|
+
// EVERY repository `runtime-image-source-index.json` pins is declared there, at
|
|
23
|
+
// the same URL (the offline index check refuses otherwise), so a newly pinned
|
|
24
|
+
// repository is refused at once rather than the day it first declares a
|
|
25
|
+
// composed image. A declared repository need not be pinned yet: the develop
|
|
26
|
+
// graph carries it before any release does. A lookup of an undeclared
|
|
27
|
+
// repository is refused (`firstPartySourceId`), never named by a fallback.
|
|
25
28
|
|
|
26
29
|
/** The kernel's source-handle grammar (`BiomeSourceNameSchema`): lowercase kebab-case. */
|
|
27
30
|
export const DECLARED_SOURCE_ID_PATTERN = /^[a-z][a-z0-9-]*$/;
|
|
28
31
|
|
|
29
|
-
/** The data root's
|
|
30
|
-
export const
|
|
31
|
-
workspaceSection('firstPartySources',
|
|
32
|
+
/** The data root's first-party repositories (`firstPartySources`): repo handle -> { sourceId, repositoryUrl }. */
|
|
33
|
+
export const firstPartySources = (needed = 'naming a runtime subject after its first-party source') =>
|
|
34
|
+
workspaceSection('firstPartySources', needed);
|
|
32
35
|
|
|
33
36
|
export class UndeclaredSourceIdError extends Error {
|
|
34
37
|
constructor(message) {
|
|
@@ -48,12 +51,12 @@ export function assertDeclaredSourceId(sourceId, where) {
|
|
|
48
51
|
}
|
|
49
52
|
|
|
50
53
|
export function firstPartySourceId(repo) {
|
|
51
|
-
const
|
|
52
|
-
if (!Object.hasOwn(
|
|
54
|
+
const sources = firstPartySources();
|
|
55
|
+
if (!Object.hasOwn(sources, repo)) {
|
|
53
56
|
throw new UndeclaredSourceIdError(
|
|
54
57
|
`first-party repository "${repo}" has no declared source id — declare it in ` +
|
|
55
58
|
'edition-workspace.json under "firstPartySources"; a runtime identity is never derived from a repository name or URL',
|
|
56
59
|
);
|
|
57
60
|
}
|
|
58
|
-
return assertDeclaredSourceId(
|
|
61
|
+
return assertDeclaredSourceId(sources[repo].sourceId, `first-party repository "${repo}"`);
|
|
59
62
|
}
|
|
@@ -25,7 +25,7 @@ import { mkdtempSync, rmSync } from 'node:fs';
|
|
|
25
25
|
import { tmpdir } from 'node:os';
|
|
26
26
|
import { join } from 'node:path';
|
|
27
27
|
|
|
28
|
-
import { canonicalPublicHttpsRepositoryUrl } from './
|
|
28
|
+
import { canonicalPublicHttpsRepositoryUrl } from './canonical-repository-url.mjs';
|
|
29
29
|
|
|
30
30
|
/** The fixed environment variable holding the source read token. */
|
|
31
31
|
export const SOURCE_READ_TOKEN_ENV = 'EVIDENCE_REPOSITORIES_TOKEN';
|
package/edition/lib.mjs
CHANGED
|
@@ -3075,38 +3075,6 @@ export function validateImmutableImageLock(lock, schemas) {
|
|
|
3075
3075
|
return lock;
|
|
3076
3076
|
}
|
|
3077
3077
|
|
|
3078
|
-
export function canonicalPublicHttpsRepositoryUrl(raw, context) {
|
|
3079
|
-
if (typeof raw !== 'string' || raw.length === 0 || raw.trim() !== raw) {
|
|
3080
|
-
throw new Error(`${context} must be a non-empty canonical HTTPS URL`);
|
|
3081
|
-
}
|
|
3082
|
-
let parsed;
|
|
3083
|
-
try {
|
|
3084
|
-
parsed = new URL(raw);
|
|
3085
|
-
} catch {
|
|
3086
|
-
throw new Error(`${context} is not a valid URL`);
|
|
3087
|
-
}
|
|
3088
|
-
if (parsed.protocol !== 'https:') {
|
|
3089
|
-
throw new Error(`${context} must use HTTPS`);
|
|
3090
|
-
}
|
|
3091
|
-
if (parsed.username.length > 0 || parsed.password.length > 0) {
|
|
3092
|
-
throw new Error(
|
|
3093
|
-
`${context} must not contain username/password credentials`,
|
|
3094
|
-
);
|
|
3095
|
-
}
|
|
3096
|
-
if (
|
|
3097
|
-
parsed.search.length > 0 ||
|
|
3098
|
-
parsed.hash.length > 0 ||
|
|
3099
|
-
raw.includes('?') ||
|
|
3100
|
-
raw.includes('#')
|
|
3101
|
-
) {
|
|
3102
|
-
throw new Error(`${context} must not contain a query or fragment`);
|
|
3103
|
-
}
|
|
3104
|
-
if (parsed.hostname.length === 0) {
|
|
3105
|
-
throw new Error(`${context} must identify a repository host`);
|
|
3106
|
-
}
|
|
3107
|
-
return parsed.toString();
|
|
3108
|
-
}
|
|
3109
|
-
|
|
3110
3078
|
/**
|
|
3111
3079
|
* Assert that an exact 40-hex commit exists in a source repository. Delegates
|
|
3112
3080
|
* to `@xemahq/distribution-source-hash` — the tooling holds no second copy of
|
package/edition/pin-source.mjs
CHANGED
|
@@ -10,8 +10,9 @@
|
|
|
10
10
|
// published pin. This module owns the SHAPE of that map: a flat object whose
|
|
11
11
|
// keys live in six closed namespaces and whose values are exact 40-hex SHAs.
|
|
12
12
|
//
|
|
13
|
-
// repos/<x> | submodules/<x> every
|
|
14
|
-
//
|
|
13
|
+
// repos/<x> | submodules/<x> every repository of the develop graph (the
|
|
14
|
+
// data root's firstPartySources, develop-graph.mjs),
|
|
15
|
+
// no more, no fewer
|
|
15
16
|
// <pinRoles.data> the commit of the data root checkout (editions and indices)
|
|
16
17
|
// <pinRoles.kernel> the commit of the kernel the authorization lock is sealed
|
|
17
18
|
// with (may match repos/<x>, but is NOT in the graph)
|
|
@@ -20,6 +21,7 @@
|
|
|
20
21
|
// only when that root is NOT this repository
|
|
21
22
|
// sources/<name> one per `sources[]` entry declared in the chain
|
|
22
23
|
|
|
24
|
+
import { developGraphMembers } from './develop-graph.mjs';
|
|
23
25
|
import { workspaceSection } from './edition-config.mjs';
|
|
24
26
|
import { compareStrings } from './lib.mjs';
|
|
25
27
|
import { PinRole } from './workspace-sections.mjs';
|
|
@@ -109,19 +111,20 @@ function graphPinsOf(pins) {
|
|
|
109
111
|
}
|
|
110
112
|
|
|
111
113
|
/**
|
|
112
|
-
* Validate a COMPLETE snapshot against the graph and the composed
|
|
113
|
-
* `declaredSourceNames` are the `sources[].name` entries the chain
|
|
114
|
-
* `editionsRootExternal` is whether the editions root is another
|
|
115
|
-
* Every refusal names the offending key; nothing falls back to a
|
|
114
|
+
* Validate a COMPLETE snapshot against the develop graph and the composed
|
|
115
|
+
* chain. `declaredSourceNames` are the `sources[].name` entries the chain
|
|
116
|
+
* declares; `editionsRootExternal` is whether the editions root is another
|
|
117
|
+
* repository. Every refusal names the offending key; nothing falls back to a
|
|
118
|
+
* published pin.
|
|
116
119
|
*/
|
|
117
120
|
export function validateSnapshotPins(
|
|
118
121
|
pins,
|
|
119
|
-
{
|
|
122
|
+
{ declaredSourceNames, editionsRootExternal },
|
|
120
123
|
) {
|
|
121
124
|
assertPinsObject(pins);
|
|
122
125
|
assertExactShas(pins);
|
|
123
126
|
for (const key of Object.keys(pins)) {snapshotPinNamespace(key);}
|
|
124
|
-
checkGraphPins(
|
|
127
|
+
checkGraphPins(developGraphMembers(), pins);
|
|
125
128
|
for (const [key, what] of [
|
|
126
129
|
[dataPinKey(), 'the commit of the data root checkout'],
|
|
127
130
|
[kernelPinKey(), 'the commit of the kernel the authorization lock is sealed with'],
|
|
@@ -175,7 +178,10 @@ export function snapshotPinSource(pins) {
|
|
|
175
178
|
/**
|
|
176
179
|
* Apply a pin source to a parsed runtime image source index. Published returns
|
|
177
180
|
* the SAME object (inert, byte for byte). Snapshot returns a copy whose
|
|
178
|
-
* `repositoryPins`
|
|
181
|
+
* `repositoryPins` are the DEVELOP GRAPH (develop-graph.mjs) at the snapshot
|
|
182
|
+
* SHAs: the committed index's own pins are not read, so a first-party
|
|
183
|
+
* repository no release pins yet is in it, and a pinned one that is not
|
|
184
|
+
* first-party is not.
|
|
179
185
|
*/
|
|
180
186
|
export function applyPinSource(runtimeSourceIndex, pinSource = PUBLISHED_PIN_SOURCE) {
|
|
181
187
|
switch (pinSource?.kind) {
|
|
@@ -211,12 +217,13 @@ function applySnapshot(runtimeSourceIndex, pins) {
|
|
|
211
217
|
assertPinsObject(pins);
|
|
212
218
|
assertExactShas(pins);
|
|
213
219
|
for (const key of Object.keys(pins)) {snapshotPinNamespace(key);}
|
|
214
|
-
const graph =
|
|
220
|
+
const graph = developGraphMembers();
|
|
215
221
|
checkGraphPins(graph, pins);
|
|
216
222
|
return {
|
|
217
223
|
...runtimeSourceIndex,
|
|
218
|
-
repositoryPins: graph.map((
|
|
219
|
-
|
|
224
|
+
repositoryPins: graph.map(({ repo, repositoryUrl }) => ({
|
|
225
|
+
repo,
|
|
226
|
+
repositoryUrl,
|
|
220
227
|
// A develop snapshot pins every repository at its develop head, so that one
|
|
221
228
|
// SHA is both the built commit (image axis, `sourceCommit`) and the source
|
|
222
229
|
// tip (`vendoredSourceCommit`): the image pin lagging the source tip cannot
|
|
@@ -226,8 +233,8 @@ function applySnapshot(runtimeSourceIndex, pins) {
|
|
|
226
233
|
// The COMMITTED index keeps the axes apart (release channel): see
|
|
227
234
|
// `source-authority.mjs#publishedSourceTip` and
|
|
228
235
|
// `build-runtime-image-source-index.mjs#repositoryIdentity`.
|
|
229
|
-
|
|
230
|
-
|
|
236
|
+
sourceCommit: pins[repo],
|
|
237
|
+
vendoredSourceCommit: pins[repo],
|
|
231
238
|
})),
|
|
232
239
|
};
|
|
233
240
|
}
|
|
@@ -91,16 +91,46 @@ function supplementalLauncher(value, where) {
|
|
|
91
91
|
return Object.freeze({ kind: value.kind, biomeId: text(value.biomeId, `${where}.biomeId`) });
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
+
/**
|
|
95
|
+
* The retired `{repo handle: source id}` string form of a `firstPartySources`
|
|
96
|
+
* entry. Refused, never read: the entry also declares where the repository
|
|
97
|
+
* lives, which the develop graph resolves its head from.
|
|
98
|
+
*/
|
|
99
|
+
export class RetiredFirstPartySourceShapeError extends Error {
|
|
100
|
+
constructor(repo, id) {
|
|
101
|
+
super(
|
|
102
|
+
`${FILE}: "firstPartySources"["${repo}"] is the retired source-id string ${JSON.stringify(id)}. ` +
|
|
103
|
+
`Migrate it to { "sourceId": ${JSON.stringify(id)}, "repositoryUrl": "<the repository's canonical https URL>" }.`,
|
|
104
|
+
);
|
|
105
|
+
this.name = 'RetiredFirstPartySourceShapeError';
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* `firstPartySources`: every first-party repository, keyed by its source
|
|
111
|
+
* checkout handle, with its declared source id (the identity a runtime subject
|
|
112
|
+
* is named after) and its repository URL (where it lives: the develop graph
|
|
113
|
+
* resolves its head there, and a release pin must name the same URL). The URL
|
|
114
|
+
* is checked canonical and under `sourceRepositoryUrlPrefix` with the whole
|
|
115
|
+
* config (edition-config.mjs).
|
|
116
|
+
*/
|
|
94
117
|
export function parseFirstPartySources(value) {
|
|
95
118
|
const where = '"firstPartySources"';
|
|
96
|
-
if (!isObject(value) || Object.keys(value).length === 0) {
|
|
97
|
-
|
|
119
|
+
if (!isObject(value) || Object.keys(value).length === 0) {
|
|
120
|
+
fail(where, 'must be a non-empty {repo handle: { sourceId, repositoryUrl }} object');
|
|
121
|
+
}
|
|
122
|
+
const entries = Object.entries(value).map(([repo, entry]) => {
|
|
98
123
|
repoHandle(repo, `${where} key`);
|
|
99
|
-
if (typeof
|
|
100
|
-
|
|
124
|
+
if (typeof entry === 'string') {throw new RetiredFirstPartySourceShapeError(repo, entry);}
|
|
125
|
+
const at = `${where}["${repo}"]`;
|
|
126
|
+
exactObject(entry, at, ['sourceId', 'repositoryUrl']);
|
|
127
|
+
if (typeof entry.sourceId !== 'string' || !SOURCE_ID.test(entry.sourceId)) {
|
|
128
|
+
fail(`${at}.sourceId`, `must be a lowercase kebab-case source id; got ${JSON.stringify(entry.sourceId)}`);
|
|
101
129
|
}
|
|
102
|
-
return [repo,
|
|
130
|
+
return [repo, Object.freeze({ sourceId: entry.sourceId, repositoryUrl: text(entry.repositoryUrl, `${at}.repositoryUrl`) })];
|
|
103
131
|
});
|
|
132
|
+
uniqueBy(entries, `${where} sourceId`, ([, entry]) => entry.sourceId);
|
|
133
|
+
uniqueBy(entries, `${where} repositoryUrl`, ([, entry]) => entry.repositoryUrl);
|
|
104
134
|
return Object.freeze(Object.fromEntries(entries));
|
|
105
135
|
}
|
|
106
136
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xemahq/xema",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.44.0",
|
|
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:
|
|
95
|
+
"sourceDigest": "sha256:72008b3d37f2cbcef3c5ec25e2d85989198ebc1f9bc1f481b8b767965813d5aa"
|
|
96
96
|
}
|
|
97
97
|
}
|