@fabricorg/sdui-release 0.3.0 → 0.6.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/CHANGELOG.md +209 -0
- package/README.md +10 -3
- package/dist/chunk-BTWHVBZ3.js +255 -0
- package/dist/chunk-BTWHVBZ3.js.map +1 -0
- package/dist/contracts-4fL-Yh38.d.cts +399 -0
- package/dist/contracts-4fL-Yh38.d.ts +399 -0
- package/dist/contracts.cjs +286 -0
- package/dist/contracts.cjs.map +1 -0
- package/dist/contracts.d.cts +4 -0
- package/dist/contracts.d.ts +4 -0
- package/dist/contracts.js +21 -0
- package/dist/contracts.js.map +1 -0
- package/dist/index.cjs +603 -23
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +120 -261
- package/dist/index.d.ts +120 -261
- package/dist/index.js +355 -20
- package/dist/index.js.map +1 -1
- package/dist/node-crypto.cjs +51 -0
- package/dist/node-crypto.cjs.map +1 -0
- package/dist/node-crypto.d.cts +9 -0
- package/dist/node-crypto.d.ts +9 -0
- package/dist/node-crypto.js +26 -0
- package/dist/node-crypto.js.map +1 -0
- package/package.json +32 -8
package/dist/index.js
CHANGED
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
import {
|
|
2
|
+
RELEASE_CHANNEL_POINTER_FORMAT_VERSION,
|
|
3
|
+
SDUI_RELEASE_V3_FORMAT_VERSION,
|
|
4
|
+
activateReleaseChannelPointer,
|
|
5
|
+
assertReleaseChannelPointer,
|
|
6
|
+
assertSduiReleaseV3,
|
|
7
|
+
canonicalizeJcs,
|
|
8
|
+
verifySignedExperienceArtifact,
|
|
9
|
+
verifySignedExperienceRelease
|
|
10
|
+
} from "./chunk-BTWHVBZ3.js";
|
|
11
|
+
|
|
1
12
|
// types.ts
|
|
2
13
|
var SDUI_DOCUMENT_FORMAT_VERSION = 1;
|
|
3
14
|
var SDUI_TOKEN_SET_FORMAT_VERSION = 1;
|
|
@@ -24,8 +35,9 @@ function requireArray(value, path) {
|
|
|
24
35
|
if (!Array.isArray(value)) fail(path, "must be an array");
|
|
25
36
|
return value;
|
|
26
37
|
}
|
|
38
|
+
var CAPABILITY_REF = /^capability:\/\/([a-z][a-z0-9-]*)\/([a-z][a-z0-9._-]*(?:\/[a-z0-9._-]+)*)$/;
|
|
27
39
|
function parseCapabilityRef(ref) {
|
|
28
|
-
const match =
|
|
40
|
+
const match = CAPABILITY_REF.exec(ref);
|
|
29
41
|
if (!match) return void 0;
|
|
30
42
|
return { namespace: match[1], name: match[2] };
|
|
31
43
|
}
|
|
@@ -160,8 +172,20 @@ function assertSduiDocument(value, path = "document") {
|
|
|
160
172
|
});
|
|
161
173
|
}
|
|
162
174
|
const tokenSet = requireRecord(document.tokenSet, `${path}.tokenSet`);
|
|
163
|
-
|
|
164
|
-
|
|
175
|
+
assertTokenSetReference(tokenSet, `${path}.tokenSet`);
|
|
176
|
+
if (document.supportedTokenSets !== void 0) {
|
|
177
|
+
const seenTokenSets = /* @__PURE__ */ new Set();
|
|
178
|
+
requireArray(document.supportedTokenSets, `${path}.supportedTokenSets`).forEach((entry, index) => {
|
|
179
|
+
const supportedPath = `${path}.supportedTokenSets[${index}]`;
|
|
180
|
+
const supported = requireRecord(entry, supportedPath);
|
|
181
|
+
assertTokenSetReference(supported, supportedPath);
|
|
182
|
+
const identity = `${String(supported.name)}\0${String(supported.version)}`;
|
|
183
|
+
if (seenTokenSets.has(identity)) {
|
|
184
|
+
fail(supportedPath, `declares token set "${String(supported.name)}@${String(supported.version)}" a second time`);
|
|
185
|
+
}
|
|
186
|
+
seenTokenSets.add(identity);
|
|
187
|
+
});
|
|
188
|
+
}
|
|
165
189
|
requireArray(document.componentPacks, `${path}.componentPacks`).forEach((entry, index) => {
|
|
166
190
|
const pack = requireRecord(entry, `${path}.componentPacks[${index}]`);
|
|
167
191
|
requireString(pack.pack, `${path}.componentPacks[${index}].pack`);
|
|
@@ -241,6 +265,14 @@ function assertSduiRelease(value, path = "release") {
|
|
|
241
265
|
}
|
|
242
266
|
});
|
|
243
267
|
}
|
|
268
|
+
function assertTokenSetReference(reference, path) {
|
|
269
|
+
if (!/^[a-z][a-z0-9-]*$/.test(requireString(reference.name, `${path}.name`))) {
|
|
270
|
+
fail(`${path}.name`, "must be lowercase letters, numbers and hyphens");
|
|
271
|
+
}
|
|
272
|
+
if (!/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(requireString(reference.version, `${path}.version`))) {
|
|
273
|
+
fail(`${path}.version`, "must be a semver version");
|
|
274
|
+
}
|
|
275
|
+
}
|
|
244
276
|
|
|
245
277
|
// validate.ts
|
|
246
278
|
import {
|
|
@@ -293,6 +325,16 @@ function releaseDigest(document, tokenSet, componentPacks, grants, assemblyDiges
|
|
|
293
325
|
variants: [...document.variants].sort((left, right) => left.id < right.id ? -1 : left.id > right.id ? 1 : 0)
|
|
294
326
|
} : {},
|
|
295
327
|
tokenSet: document.tokenSet,
|
|
328
|
+
// Which brands a document may be promoted against is part of what was
|
|
329
|
+
// authored. Sorted, and omitted when absent so a document declaring
|
|
330
|
+
// none digests exactly as it did before the field existed.
|
|
331
|
+
...document.supportedTokenSets?.length ? {
|
|
332
|
+
supportedTokenSets: [...document.supportedTokenSets].sort((left, right) => {
|
|
333
|
+
if (left.name !== right.name) return left.name < right.name ? -1 : 1;
|
|
334
|
+
if (left.version !== right.version) return left.version < right.version ? -1 : 1;
|
|
335
|
+
return 0;
|
|
336
|
+
})
|
|
337
|
+
} : {},
|
|
296
338
|
componentPacks: [...document.componentPacks].sort((a, b) => {
|
|
297
339
|
const key = (e) => `${e.namespace}/${e.pack}`;
|
|
298
340
|
return key(a) < key(b) ? -1 : key(a) > key(b) ? 1 : 0;
|
|
@@ -342,19 +384,56 @@ function buildKnownViews(contracts) {
|
|
|
342
384
|
const views = /* @__PURE__ */ new Set();
|
|
343
385
|
for (const contract of contracts) {
|
|
344
386
|
for (const view of contract.views) {
|
|
345
|
-
|
|
346
|
-
views.add(`${contract.namespace}/${name}`);
|
|
387
|
+
views.add(`${contract.namespace}/${shortViewName(view.name, contract.namespace)}`);
|
|
347
388
|
}
|
|
348
389
|
}
|
|
349
390
|
return views;
|
|
350
391
|
}
|
|
351
|
-
function
|
|
392
|
+
function shortViewName(name, namespace) {
|
|
393
|
+
return name.startsWith(`${namespace}/`) ? name.slice(namespace.length + 1) : name;
|
|
394
|
+
}
|
|
395
|
+
function reportAmbiguousGrantedViews(contracts, grantedViews, add) {
|
|
396
|
+
const occurrences = /* @__PURE__ */ new Map();
|
|
397
|
+
for (const contract of contracts) {
|
|
398
|
+
for (const view of contract.views) {
|
|
399
|
+
const reference = `capability://${contract.namespace}/${shortViewName(view.name, contract.namespace)}`;
|
|
400
|
+
if (!grantedViews.has(reference)) continue;
|
|
401
|
+
occurrences.set(reference, [...occurrences.get(reference) ?? [], `${view.name}@${view.version}`]);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
for (const [reference, declarations] of occurrences) {
|
|
405
|
+
if (declarations.length > 1) {
|
|
406
|
+
add(
|
|
407
|
+
"ambiguous_capability_reference",
|
|
408
|
+
`grants.views.${reference}`,
|
|
409
|
+
`"${reference}" is granted but addresses ${declarations.length} views (${declarations.join(", ")}); a versionless reference cannot pick one`
|
|
410
|
+
);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
function buildKnownActionIntents(contracts, add, grantedIntents) {
|
|
352
415
|
const intents = /* @__PURE__ */ new Set();
|
|
416
|
+
const occurrences = /* @__PURE__ */ new Map();
|
|
353
417
|
for (const contract of contracts) {
|
|
354
418
|
const experience = contract.extensions?.["fabric.experience/v1"];
|
|
419
|
+
const declaredActions = new Set(contract.actions.map((action) => action.actionId));
|
|
355
420
|
for (const intent of experience?.actionIntents ?? []) {
|
|
356
|
-
|
|
357
|
-
|
|
421
|
+
if (!declaredActions.has(intent.actionId)) continue;
|
|
422
|
+
const shortName2 = intent.name.startsWith(`${contract.namespace}.`) ? intent.name.slice(contract.namespace.length + 1) : intent.name;
|
|
423
|
+
const reference = `${contract.namespace}/${shortName2}`;
|
|
424
|
+
if (grantedIntents === void 0 || grantedIntents.has(`capability://${reference}`)) {
|
|
425
|
+
occurrences.set(reference, [...occurrences.get(reference) ?? [], intent.name]);
|
|
426
|
+
}
|
|
427
|
+
intents.add(reference);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
for (const [reference, declarations] of occurrences) {
|
|
431
|
+
if (declarations.length > 1) {
|
|
432
|
+
add?.(
|
|
433
|
+
"ambiguous_capability_reference",
|
|
434
|
+
`contracts.actionIntents.${reference}`,
|
|
435
|
+
`"capability://${reference}" is declared ${declarations.length} times (${declarations.join(", ")}), so a route for it cannot be resolved`
|
|
436
|
+
);
|
|
358
437
|
}
|
|
359
438
|
}
|
|
360
439
|
return intents;
|
|
@@ -548,18 +627,13 @@ function validateSduiRelease(input) {
|
|
|
548
627
|
const add = (code, path, message) => {
|
|
549
628
|
findings.push({ code, path, message });
|
|
550
629
|
};
|
|
551
|
-
|
|
630
|
+
const promotableTokenSets = [input.document.tokenSet, ...input.document.supportedTokenSets ?? []];
|
|
631
|
+
const promotedAgainst = promotableTokenSets.some((candidate) => candidate.name === input.tokenSet.name && candidate.version === input.tokenSet.version);
|
|
632
|
+
if (!promotedAgainst) {
|
|
552
633
|
add(
|
|
553
634
|
"missing_token_set",
|
|
554
|
-
"document.tokenSet
|
|
555
|
-
`document
|
|
556
|
-
);
|
|
557
|
-
}
|
|
558
|
-
if (input.document.tokenSet.version !== input.tokenSet.version) {
|
|
559
|
-
add(
|
|
560
|
-
"missing_token_set",
|
|
561
|
-
"document.tokenSet.version",
|
|
562
|
-
`document references token set version "${input.document.tokenSet.version}" but the provided set is version "${input.tokenSet.version}"`
|
|
635
|
+
"document.tokenSet",
|
|
636
|
+
`document supports token sets [${promotableTokenSets.map((set) => `${set.name}@${set.version}`).join(", ")}] but the provided set is "${input.tokenSet.name}@${input.tokenSet.version}"`
|
|
563
637
|
);
|
|
564
638
|
}
|
|
565
639
|
const packsByKey = /* @__PURE__ */ new Map();
|
|
@@ -675,9 +749,43 @@ function validateSduiRelease(input) {
|
|
|
675
749
|
}
|
|
676
750
|
const coreComponents = new Set(input.coreComponents ?? []);
|
|
677
751
|
const knownViews = buildKnownViews(contracts);
|
|
678
|
-
const knownIntents = buildKnownActionIntents(contracts);
|
|
679
752
|
const grantedViews = buildGrantedReferences(input.grants.views, "grants.views");
|
|
680
753
|
const grantedIntents = buildGrantedReferences(input.grants.intents, "grants.intents");
|
|
754
|
+
const contractNamespaces = /* @__PURE__ */ new Set();
|
|
755
|
+
const distinctContracts = contracts.filter((contract) => {
|
|
756
|
+
if (contractNamespaces.has(contract.namespace)) {
|
|
757
|
+
add(
|
|
758
|
+
"ambiguous_capability_reference",
|
|
759
|
+
`contracts.${contract.namespace}`,
|
|
760
|
+
`contract for "${contract.namespace}" is supplied more than once`
|
|
761
|
+
);
|
|
762
|
+
return false;
|
|
763
|
+
}
|
|
764
|
+
contractNamespaces.add(contract.namespace);
|
|
765
|
+
return true;
|
|
766
|
+
});
|
|
767
|
+
const knownIntents = buildKnownActionIntents(distinctContracts, add, grantedIntents);
|
|
768
|
+
reportAmbiguousGrantedViews(distinctContracts, grantedViews, add);
|
|
769
|
+
for (const reference of grantedViews) {
|
|
770
|
+
const parsed = parseCapabilityRef(reference);
|
|
771
|
+
if (parsed && !knownViews.has(`${parsed.namespace}/${parsed.name}`)) {
|
|
772
|
+
add(
|
|
773
|
+
"unauthorized_data_ref",
|
|
774
|
+
`grants.views.${reference}`,
|
|
775
|
+
`release grants "${reference}", which no supplied capability publishes; promotion would fail to route it`
|
|
776
|
+
);
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
for (const reference of grantedIntents) {
|
|
780
|
+
const parsed = parseCapabilityRef(reference);
|
|
781
|
+
if (parsed && !knownIntents.has(`${parsed.namespace}/${parsed.name}`)) {
|
|
782
|
+
add(
|
|
783
|
+
"mutation_bypass",
|
|
784
|
+
`grants.intents.${reference}`,
|
|
785
|
+
`release grants "${reference}", which no supplied capability declares as an intent; promotion would fail to route it`
|
|
786
|
+
);
|
|
787
|
+
}
|
|
788
|
+
}
|
|
681
789
|
validateIntentActionDeclarations(contracts, add);
|
|
682
790
|
const fragmentGrants = /* @__PURE__ */ new Map();
|
|
683
791
|
for (const [fragmentId, declared] of Object.entries(input.grants.fragments ?? {})) {
|
|
@@ -802,6 +910,221 @@ function collectCoreComponents(fragment) {
|
|
|
802
910
|
}
|
|
803
911
|
return used;
|
|
804
912
|
}
|
|
913
|
+
async function refusalReason(run, expect) {
|
|
914
|
+
try {
|
|
915
|
+
await run();
|
|
916
|
+
return null;
|
|
917
|
+
} catch (error) {
|
|
918
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
919
|
+
return expect.test(message) ? message : null;
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
function sduiChannelChecks() {
|
|
923
|
+
return [
|
|
924
|
+
{
|
|
925
|
+
id: "fabric.sdui-channel.verified-release.v1",
|
|
926
|
+
async run(subject) {
|
|
927
|
+
assertSduiReleaseIntegrity(subject.validRelease);
|
|
928
|
+
const result = subject.channel.consume(subject.validRelease);
|
|
929
|
+
if (result.release.releaseDigest !== subject.validRelease.releaseDigest) {
|
|
930
|
+
throw new Error("channel returned a release other than the one it was given");
|
|
931
|
+
}
|
|
932
|
+
return [subject.validRelease.releaseDigest];
|
|
933
|
+
}
|
|
934
|
+
},
|
|
935
|
+
{
|
|
936
|
+
id: "fabric.sdui-channel.tampered-release-denial.v1",
|
|
937
|
+
async run(subject) {
|
|
938
|
+
const tampered = {
|
|
939
|
+
...subject.validRelease,
|
|
940
|
+
document: { ...subject.validRelease.document, name: `${subject.validRelease.document.name}-tampered` }
|
|
941
|
+
};
|
|
942
|
+
const reason = await refusalReason(() => subject.channel.consume(tampered), /digest|integrity/i);
|
|
943
|
+
if (reason === null) {
|
|
944
|
+
throw new Error("channel did not refuse a release whose content no longer matches its digest, citing the digest");
|
|
945
|
+
}
|
|
946
|
+
for (const [label, mutate] of [
|
|
947
|
+
["version", (release) => ({ ...release, document: { ...release.document, version: "9.9.9" } })],
|
|
948
|
+
["grants", (release) => ({ ...release, grants: { ...release.grants, views: [...release.grants.views, "capability://smuggled/view"] } })],
|
|
949
|
+
["assembly", (release) => ({ ...release, assemblyDigest: "f".repeat(64) })]
|
|
950
|
+
]) {
|
|
951
|
+
const edited = mutate(subject.validRelease);
|
|
952
|
+
if (await refusalReason(() => subject.channel.consume(edited), /digest|integrity/i) === null) {
|
|
953
|
+
throw new Error(`channel accepted a release whose ${label} was edited after promotion`);
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
return ["tampered document refused", "edited version, grants and assembly refused"];
|
|
957
|
+
}
|
|
958
|
+
},
|
|
959
|
+
{
|
|
960
|
+
id: "fabric.sdui-channel.unpromoted-document-denial.v1",
|
|
961
|
+
async run(subject) {
|
|
962
|
+
const expected = /digest|integrity|release|formatVersion|must be/i;
|
|
963
|
+
const unpromoted = { document: subject.validRelease.document };
|
|
964
|
+
if (await refusalReason(() => subject.channel.consume(unpromoted), expected) === null) {
|
|
965
|
+
throw new Error("channel did not refuse a bare document that was never promoted, citing what was wrong with it");
|
|
966
|
+
}
|
|
967
|
+
const digestless = { ...subject.validRelease, releaseDigest: void 0 };
|
|
968
|
+
if (await refusalReason(() => subject.channel.consume(digestless), expected) === null) {
|
|
969
|
+
throw new Error("channel did not refuse a release carrying no digest, citing what was wrong with it");
|
|
970
|
+
}
|
|
971
|
+
return ["bare document refused", "digestless release refused"];
|
|
972
|
+
}
|
|
973
|
+
},
|
|
974
|
+
{
|
|
975
|
+
id: "fabric.sdui-channel.reports-missing-vocabulary.v1",
|
|
976
|
+
async run(subject) {
|
|
977
|
+
const used = collectCoreComponents(subject.validRelease.document.root);
|
|
978
|
+
for (const variant of subject.validRelease.document.variants ?? []) {
|
|
979
|
+
for (const component of collectCoreComponents(variant.root)) used.add(component);
|
|
980
|
+
}
|
|
981
|
+
if (!used.has(subject.unimplementedCoreComponent)) {
|
|
982
|
+
throw new Error(
|
|
983
|
+
`conformance subject is vacuous: validRelease never uses "${subject.unimplementedCoreComponent}", so the channel is never asked to report it`
|
|
984
|
+
);
|
|
985
|
+
}
|
|
986
|
+
if (subject.channel.coreComponents.includes(subject.unimplementedCoreComponent)) {
|
|
987
|
+
throw new Error(
|
|
988
|
+
`conformance subject is contradictory: the channel implements "${subject.unimplementedCoreComponent}"`
|
|
989
|
+
);
|
|
990
|
+
}
|
|
991
|
+
const result = subject.channel.consume(subject.validRelease);
|
|
992
|
+
if (result.compatible) {
|
|
993
|
+
throw new Error("channel reported compatible for a release using a component it does not implement");
|
|
994
|
+
}
|
|
995
|
+
if (!result.missingCoreComponents.includes(subject.unimplementedCoreComponent)) {
|
|
996
|
+
throw new Error("channel reported incompatible without naming the component it cannot render");
|
|
997
|
+
}
|
|
998
|
+
return [`missing: ${result.missingCoreComponents.join(", ")}`];
|
|
999
|
+
}
|
|
1000
|
+
},
|
|
1001
|
+
{
|
|
1002
|
+
id: "fabric.sdui-channel.consumption-is-pure.v1",
|
|
1003
|
+
async run(subject) {
|
|
1004
|
+
const before = JSON.stringify(subject.validRelease);
|
|
1005
|
+
subject.channel.consume(subject.validRelease);
|
|
1006
|
+
subject.channel.consume(subject.validRelease);
|
|
1007
|
+
if (JSON.stringify(subject.validRelease) !== before) {
|
|
1008
|
+
throw new Error("channel mutated the release it consumed");
|
|
1009
|
+
}
|
|
1010
|
+
return ["release unchanged after two consumptions"];
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
];
|
|
1014
|
+
}
|
|
1015
|
+
async function runSduiChannelChecks(subject) {
|
|
1016
|
+
const checks = [];
|
|
1017
|
+
for (const check of sduiChannelChecks()) {
|
|
1018
|
+
try {
|
|
1019
|
+
checks.push({ id: check.id, status: "passed", evidence: await check.run(subject) });
|
|
1020
|
+
} catch (error) {
|
|
1021
|
+
checks.push({
|
|
1022
|
+
id: check.id,
|
|
1023
|
+
status: "failed",
|
|
1024
|
+
evidence: [],
|
|
1025
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1026
|
+
});
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
return { passed: checks.every((check) => check.status === "passed"), checks };
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
// references.ts
|
|
1033
|
+
function collectDocumentReferences(document) {
|
|
1034
|
+
const views = /* @__PURE__ */ new Set();
|
|
1035
|
+
const intents = /* @__PURE__ */ new Set();
|
|
1036
|
+
const trees = [document.root, ...(document.variants ?? []).map((variant) => variant.root)];
|
|
1037
|
+
for (const tree of trees) walk(tree, views, intents);
|
|
1038
|
+
return { views, intents };
|
|
1039
|
+
}
|
|
1040
|
+
function walk(fragment, views, intents) {
|
|
1041
|
+
if (fragment.dataRef) views.add(fragment.dataRef);
|
|
1042
|
+
for (const action of Object.values(fragment.actions ?? {})) {
|
|
1043
|
+
intents.add(action.intent);
|
|
1044
|
+
if (action.params) collectValues(action.params, views);
|
|
1045
|
+
}
|
|
1046
|
+
if (fragment.props) collectValues(fragment.props, views);
|
|
1047
|
+
for (const child of fragment.children ?? []) walk(child, views, intents);
|
|
1048
|
+
}
|
|
1049
|
+
function collectValues(props, views) {
|
|
1050
|
+
for (const value of Object.values(props)) collectValue(value, views);
|
|
1051
|
+
}
|
|
1052
|
+
function collectValue(value, views) {
|
|
1053
|
+
if (value !== null && typeof value === "object" && !Array.isArray(value) && "$ref" in value) {
|
|
1054
|
+
views.add(value.$ref);
|
|
1055
|
+
for (const [key, sibling] of Object.entries(value)) {
|
|
1056
|
+
if (key !== "$ref") collectValue(sibling, views);
|
|
1057
|
+
}
|
|
1058
|
+
return;
|
|
1059
|
+
}
|
|
1060
|
+
if (Array.isArray(value)) {
|
|
1061
|
+
for (const item of value) collectValue(item, views);
|
|
1062
|
+
return;
|
|
1063
|
+
}
|
|
1064
|
+
if (value !== null && typeof value === "object") collectValues(value, views);
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
// entitlements.ts
|
|
1068
|
+
function deriveAuthorizationGrants(contracts, held, options = {}) {
|
|
1069
|
+
const undeclared = options.undeclaredRequirements ?? "withhold";
|
|
1070
|
+
const bound = options.document ? collectDocumentReferences(options.document) : void 0;
|
|
1071
|
+
const permissions = new Set(held.permissions ?? []);
|
|
1072
|
+
const entitlements = new Set(held.entitlements ?? []);
|
|
1073
|
+
const featureFlags = new Set(held.featureFlags ?? []);
|
|
1074
|
+
const views = [];
|
|
1075
|
+
const intents = [];
|
|
1076
|
+
const withheld = [];
|
|
1077
|
+
for (const contract of contracts) {
|
|
1078
|
+
if (undeclared === "withhold" && !declaresAnyRequirement(contract)) {
|
|
1079
|
+
withheld.push({ namespace: contract.namespace, missing: { kind: "permission", name: "(none declared)" } });
|
|
1080
|
+
continue;
|
|
1081
|
+
}
|
|
1082
|
+
const missing = firstMissingRequirement(contract, { permissions, entitlements, featureFlags });
|
|
1083
|
+
if (missing) {
|
|
1084
|
+
withheld.push({ namespace: contract.namespace, missing });
|
|
1085
|
+
continue;
|
|
1086
|
+
}
|
|
1087
|
+
for (const view of contract.views) {
|
|
1088
|
+
const reference = `capability://${contract.namespace}/${shortName(view.name, contract.namespace, "/")}`;
|
|
1089
|
+
if (!bound || bound.views.has(reference)) views.push(reference);
|
|
1090
|
+
}
|
|
1091
|
+
for (const intent of actionIntentsOf(contract)) {
|
|
1092
|
+
const reference = `capability://${contract.namespace}/${shortName(intent.name, contract.namespace, ".")}`;
|
|
1093
|
+
if (!bound || bound.intents.has(reference)) intents.push(reference);
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
return {
|
|
1097
|
+
grants: { views: [...new Set(views)].sort(), intents: [...new Set(intents)].sort() },
|
|
1098
|
+
withheld: withheld.sort((left, right) => left.namespace < right.namespace ? -1 : left.namespace > right.namespace ? 1 : 0)
|
|
1099
|
+
};
|
|
1100
|
+
}
|
|
1101
|
+
function declaresAnyRequirement(contract) {
|
|
1102
|
+
const requirements = contract.requirements;
|
|
1103
|
+
return Boolean(
|
|
1104
|
+
requirements && ((requirements.permissions?.length ?? 0) > 0 || (requirements.entitlements?.length ?? 0) > 0 || (requirements.featureFlags?.length ?? 0) > 0)
|
|
1105
|
+
);
|
|
1106
|
+
}
|
|
1107
|
+
function firstMissingRequirement(contract, held) {
|
|
1108
|
+
const requirements = contract.requirements;
|
|
1109
|
+
for (const name of requirements?.permissions ?? []) {
|
|
1110
|
+
if (!held.permissions.has(name)) return { kind: "permission", name };
|
|
1111
|
+
}
|
|
1112
|
+
for (const name of requirements?.entitlements ?? []) {
|
|
1113
|
+
if (!held.entitlements.has(name)) return { kind: "entitlement", name };
|
|
1114
|
+
}
|
|
1115
|
+
for (const name of requirements?.featureFlags ?? []) {
|
|
1116
|
+
if (!held.featureFlags.has(name)) return { kind: "featureFlag", name };
|
|
1117
|
+
}
|
|
1118
|
+
return void 0;
|
|
1119
|
+
}
|
|
1120
|
+
function actionIntentsOf(contract) {
|
|
1121
|
+
const experience = contract.extensions?.["fabric.experience/v1"];
|
|
1122
|
+
return experience?.actionIntents ?? [];
|
|
1123
|
+
}
|
|
1124
|
+
function shortName(name, namespace, separator) {
|
|
1125
|
+
const prefix = `${namespace}${separator}`;
|
|
1126
|
+
return name.startsWith(prefix) ? name.slice(prefix.length) : name;
|
|
1127
|
+
}
|
|
805
1128
|
|
|
806
1129
|
// schemas.ts
|
|
807
1130
|
var SDUI_DOCUMENT_JSON_SCHEMA = {
|
|
@@ -834,22 +1157,34 @@ var SDUI_RELEASE_JSON_SCHEMA = {
|
|
|
834
1157
|
}
|
|
835
1158
|
};
|
|
836
1159
|
export {
|
|
1160
|
+
RELEASE_CHANNEL_POINTER_FORMAT_VERSION,
|
|
837
1161
|
SDUI_COMPONENT_PACK_FORMAT_VERSION,
|
|
838
1162
|
SDUI_DOCUMENT_FORMAT_VERSION,
|
|
839
1163
|
SDUI_DOCUMENT_JSON_SCHEMA,
|
|
840
1164
|
SDUI_RELEASE_FORMAT_VERSION,
|
|
841
1165
|
SDUI_RELEASE_JSON_SCHEMA,
|
|
1166
|
+
SDUI_RELEASE_V3_FORMAT_VERSION,
|
|
842
1167
|
SDUI_TOKEN_SET_FORMAT_VERSION,
|
|
1168
|
+
activateReleaseChannelPointer,
|
|
1169
|
+
assertReleaseChannelPointer,
|
|
843
1170
|
assertSduiAuthorizationGrants,
|
|
844
1171
|
assertSduiComponentPack,
|
|
845
1172
|
assertSduiDocument,
|
|
846
1173
|
assertSduiFragment,
|
|
847
1174
|
assertSduiRelease,
|
|
848
1175
|
assertSduiReleaseIntegrity,
|
|
1176
|
+
assertSduiReleaseV3,
|
|
849
1177
|
assertSduiReleaseValid,
|
|
850
1178
|
assertSduiTokenSet,
|
|
1179
|
+
canonicalizeJcs,
|
|
851
1180
|
computeReleaseDigest,
|
|
852
1181
|
createSduiChannel,
|
|
853
|
-
|
|
1182
|
+
deriveAuthorizationGrants,
|
|
1183
|
+
parseCapabilityRef,
|
|
1184
|
+
runSduiChannelChecks,
|
|
1185
|
+
sduiChannelChecks,
|
|
1186
|
+
validateSduiRelease,
|
|
1187
|
+
verifySignedExperienceArtifact,
|
|
1188
|
+
verifySignedExperienceRelease
|
|
854
1189
|
};
|
|
855
1190
|
//# sourceMappingURL=index.js.map
|