@fabricorg/sdui-composer 0.2.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 +74 -0
- package/LICENSE +21 -0
- package/README.md +15 -0
- package/dist/index.cjs +261 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +62 -0
- package/dist/index.d.ts +62 -0
- package/dist/index.js +242 -0
- package/dist/index.js.map +1 -0
- package/package.json +56 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.2.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- a598fb9: Close the remaining review findings.
|
|
8
|
+
|
|
9
|
+
**One capability-reference grammar.** `sdui-release` accepted any name after the namespace while
|
|
10
|
+
`sdui-composer` rejected `/`, so a view named `orders/reports/daily` produced a release that validated
|
|
11
|
+
and then died at promotion with nothing wrong upstream to point at. Both now share
|
|
12
|
+
`parseCapabilityRef`, tightened to lowercase path segments matching what the platform permits for a
|
|
13
|
+
view name. Two views in one contract that reduce to the same reference are reported as
|
|
14
|
+
`ambiguous_capability_reference` at validation rather than surfacing as an ambiguous route later.
|
|
15
|
+
|
|
16
|
+
**A bounded replay window.** A client-supplied `capturedAt` had no lower bound. Deferring it to the
|
|
17
|
+
Host looked safe and was not: the Host checks staleness only when the capture-authority adapter
|
|
18
|
+
supplies an `expiresAt`, and skips the execution-time expiry check entirely for actions governed at
|
|
19
|
+
capture. A gateway that accepts replay must now declare `maxOfflineReplayAgeMs`, and the instant must
|
|
20
|
+
be RFC 3339 with an explicit offset.
|
|
21
|
+
|
|
22
|
+
**One token-set grammar.** `document.tokenSet` accepted names and versions that `supportedTokenSets`
|
|
23
|
+
rejected, while validation treats the two fields as interchangeable.
|
|
24
|
+
|
|
25
|
+
**Port requirements narrow as they are checked.** A requirement for a standard _and_ a version could
|
|
26
|
+
be satisfied by two different adapters covering one constraint each. `requirement.standard.version`
|
|
27
|
+
was declared and never read.
|
|
28
|
+
|
|
29
|
+
### Patch Changes
|
|
30
|
+
|
|
31
|
+
- 274d948: Close the gaps a second independent review found.
|
|
32
|
+
|
|
33
|
+
Experience promotion now binds the usage contracts it compiles routes from to the assembly the
|
|
34
|
+
release was validated against. Promotion previously accepted arbitrary contracts while copying the
|
|
35
|
+
release's assembly digest through unchanged, so a substituted contract could remap an already-granted
|
|
36
|
+
`capability://` reference onto a different installed action. PlatformHost still authorized that
|
|
37
|
+
action, which made this an integrity failure of what a release exposes rather than an authorization
|
|
38
|
+
bypass, and invisible downstream. `@fabricorg/assembly` exports `computeAssemblyLockfileDigest` so
|
|
39
|
+
tooling can build a self-consistent lockfile; the validator already recomputed it.
|
|
40
|
+
|
|
41
|
+
The MFE loader gains `fabric.mfe-loader.mismatched-digest-denial.v1`, which supplies a well-formed
|
|
42
|
+
integrity value that cannot match the approved bundle. The previous integrity check supplied only a
|
|
43
|
+
malformed value, so it proved a regex ran; a transport that ignored integrity entirely certified
|
|
44
|
+
clean. Since same-realm MFE code carries the application's own authority, a false integrity
|
|
45
|
+
certification is worth catching in the suite rather than in production.
|
|
46
|
+
|
|
47
|
+
The SDUI channel suite no longer certifies a channel it never exercised: the missing-vocabulary check
|
|
48
|
+
now runs against the subject rather than a fresh built-in one, rejects a vacuous or contradictory
|
|
49
|
+
subject instead of passing it, requires a refusal to name the digest so an unrelated crash cannot
|
|
50
|
+
read as correct behaviour, and edits several digest-covered fields so a channel cannot pass by
|
|
51
|
+
special-casing one fixture. `supportedTokenSets` uses a total comparator over its fields rather than
|
|
52
|
+
an interpolated key, and duplicate or malformed token-set references are rejected.
|
|
53
|
+
|
|
54
|
+
- Updated dependencies [8ce5e06]
|
|
55
|
+
- Updated dependencies [a598fb9]
|
|
56
|
+
- Updated dependencies [8f75ac9]
|
|
57
|
+
- Updated dependencies [9a5b18a]
|
|
58
|
+
- Updated dependencies [9b2e55b]
|
|
59
|
+
- Updated dependencies [0d1f6f6]
|
|
60
|
+
- Updated dependencies [274d948]
|
|
61
|
+
- @fabricorg/sdui-release@0.5.0
|
|
62
|
+
- @fabricorg/assembly@0.4.0
|
|
63
|
+
|
|
64
|
+
## 0.1.0
|
|
65
|
+
|
|
66
|
+
### Minor Changes
|
|
67
|
+
|
|
68
|
+
- Add exact route compilation, effective-grant materialization, v2-to-v3 migration,
|
|
69
|
+
signed promotion, channel-pointer signing, and public conformance checks.
|
|
70
|
+
|
|
71
|
+
### Patch Changes
|
|
72
|
+
|
|
73
|
+
- Updated dependencies
|
|
74
|
+
- @fabricorg/sdui-release@0.4.0
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Fabric
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# `@fabricorg/sdui-composer`
|
|
2
|
+
|
|
3
|
+
Headless promotion tooling for SDUI releases. Promotion verifies Release v2, resolves
|
|
4
|
+
every granted view and intent to an exact versioned route, computes tree-scoped
|
|
5
|
+
effective grants, creates Release v3, and signs its canonical bytes with ES256.
|
|
6
|
+
|
|
7
|
+
Ambiguous or missing routes fail promotion. Runtime modules never interpret raw
|
|
8
|
+
adoption bindings.
|
|
9
|
+
|
|
10
|
+
`migrateSduiReleaseV2ToV3` is the explicit migration seam and intentionally has the
|
|
11
|
+
same fail-closed inputs as promotion. `signReleaseChannelPointer` signs a monotonic
|
|
12
|
+
release-channel pointer; activate it with a durable `ReleaseGenerationStore` so a
|
|
13
|
+
restart cannot reopen an older generation.
|
|
14
|
+
|
|
15
|
+
Replacement composers can run `sduiComposerChecks`.
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
createEs256ReleaseSigner: () => createEs256ReleaseSigner,
|
|
24
|
+
migrateSduiReleaseV2ToV3: () => migrateSduiReleaseV2ToV3,
|
|
25
|
+
promoteExperienceRelease: () => promoteExperienceRelease,
|
|
26
|
+
runSduiComposerChecks: () => runSduiComposerChecks,
|
|
27
|
+
sduiComposerChecks: () => sduiComposerChecks,
|
|
28
|
+
signReleaseChannelPointer: () => signReleaseChannelPointer
|
|
29
|
+
});
|
|
30
|
+
module.exports = __toCommonJS(index_exports);
|
|
31
|
+
var import_node_crypto = require("crypto");
|
|
32
|
+
var import_assembly = require("@fabricorg/assembly");
|
|
33
|
+
var import_sdui_release = require("@fabricorg/sdui-release");
|
|
34
|
+
var import_contracts = require("@fabricorg/sdui-release/contracts");
|
|
35
|
+
function createEs256ReleaseSigner(input) {
|
|
36
|
+
if (!input.keyId.trim()) throw new Error("Experience release signing key id is required.");
|
|
37
|
+
return {
|
|
38
|
+
keyId: input.keyId,
|
|
39
|
+
async sign(signingInput) {
|
|
40
|
+
return new Uint8Array((0, import_node_crypto.sign)(
|
|
41
|
+
"sha256",
|
|
42
|
+
signingInput,
|
|
43
|
+
{ key: input.privateKey, dsaEncoding: "ieee-p1363" }
|
|
44
|
+
));
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
var encoder = new TextEncoder();
|
|
49
|
+
async function promoteExperienceRelease(input) {
|
|
50
|
+
(0, import_sdui_release.assertSduiReleaseIntegrity)(input.release);
|
|
51
|
+
(0, import_sdui_release.assertSduiRelease)(input.release);
|
|
52
|
+
if (!input.issuer.trim()) throw new Error("Experience release issuer is required.");
|
|
53
|
+
assertContractsApprovedByAssembly(input.release, input.contracts, input.assembly);
|
|
54
|
+
const contracts = input.contracts.map((document) => document.capability);
|
|
55
|
+
const routes = compileRoutes(input.release, contracts);
|
|
56
|
+
const effectiveGrants = compileEffectiveGrants(input.release);
|
|
57
|
+
const body = {
|
|
58
|
+
formatVersion: import_contracts.SDUI_RELEASE_V3_FORMAT_VERSION,
|
|
59
|
+
kind: "experience-release",
|
|
60
|
+
issuer: input.issuer,
|
|
61
|
+
application: input.application,
|
|
62
|
+
assemblyDigest: input.release.assemblyDigest,
|
|
63
|
+
document: input.release.document,
|
|
64
|
+
tokenSet: input.release.tokenSet,
|
|
65
|
+
componentPacks: input.release.componentPacks,
|
|
66
|
+
routes,
|
|
67
|
+
effectiveGrants
|
|
68
|
+
};
|
|
69
|
+
const release = {
|
|
70
|
+
...body,
|
|
71
|
+
releaseDigest: sha256((0, import_contracts.canonicalizeJcs)(body))
|
|
72
|
+
};
|
|
73
|
+
return {
|
|
74
|
+
release,
|
|
75
|
+
compactJws: await signCompactJws(release, "fabric-experience-release+jws", input.signer)
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
var migrateSduiReleaseV2ToV3 = promoteExperienceRelease;
|
|
79
|
+
async function signReleaseChannelPointer(pointer, signer) {
|
|
80
|
+
(0, import_contracts.assertReleaseChannelPointer)(pointer);
|
|
81
|
+
return signCompactJws(pointer, "fabric-release-channel-pointer+jws", signer);
|
|
82
|
+
}
|
|
83
|
+
function sduiComposerChecks() {
|
|
84
|
+
return [
|
|
85
|
+
{
|
|
86
|
+
id: "fabric.sdui-composer.exact-promotion.v1",
|
|
87
|
+
async run(subject) {
|
|
88
|
+
const promoted = await subject.promote(subject.validInput);
|
|
89
|
+
if (promoted.release.formatVersion !== 3 || promoted.compactJws.split(".").length !== 3) {
|
|
90
|
+
throw new Error("Composer did not produce a signed Release v3.");
|
|
91
|
+
}
|
|
92
|
+
return [promoted.release.releaseDigest];
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
id: "fabric.sdui-composer.unresolved-denial.v1",
|
|
97
|
+
async run(subject) {
|
|
98
|
+
let rejected = false;
|
|
99
|
+
try {
|
|
100
|
+
await subject.promote(subject.invalidInput);
|
|
101
|
+
} catch {
|
|
102
|
+
rejected = true;
|
|
103
|
+
}
|
|
104
|
+
if (!rejected) throw new Error("Composer promoted unresolved or ambiguous capability routes.");
|
|
105
|
+
return ["invalid route rejected"];
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
];
|
|
109
|
+
}
|
|
110
|
+
async function runSduiComposerChecks(subject, checks = sduiComposerChecks()) {
|
|
111
|
+
const results = [];
|
|
112
|
+
for (const check of checks) {
|
|
113
|
+
try {
|
|
114
|
+
results.push({ id: check.id, status: "passed", evidence: await check.run(subject) });
|
|
115
|
+
} catch (error) {
|
|
116
|
+
results.push({ id: check.id, status: "failed", evidence: [], error: error instanceof Error ? error.message : String(error) });
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return { passed: results.every((result) => result.status === "passed"), checks: results };
|
|
120
|
+
}
|
|
121
|
+
function compileRoutes(release, contracts) {
|
|
122
|
+
const views = [...new Set(release.grants.views)].sort().map((reference) => resolveView(reference, contracts));
|
|
123
|
+
const intents = [...new Set(release.grants.intents)].sort().map((reference) => resolveIntent(reference, contracts));
|
|
124
|
+
return { views, intents };
|
|
125
|
+
}
|
|
126
|
+
function resolveView(reference, contracts) {
|
|
127
|
+
const { namespace, name } = parseReference(reference);
|
|
128
|
+
const matches = contracts.flatMap((contract) => {
|
|
129
|
+
if (contract.namespace !== namespace) return [];
|
|
130
|
+
return contract.views.filter((view) => view.name === name || view.name === `${namespace}/${name}`).map((view) => ({ contract, view }));
|
|
131
|
+
});
|
|
132
|
+
if (matches.length !== 1) throw new Error(`Capability view route "${reference}" is ${matches.length === 0 ? "missing" : "ambiguous"}.`);
|
|
133
|
+
const match = matches[0];
|
|
134
|
+
const parameterSchemaDigest = match.view.parameterSchema === void 0 ? void 0 : sha256((0, import_contracts.canonicalizeJcs)(match.view.parameterSchema));
|
|
135
|
+
return {
|
|
136
|
+
reference,
|
|
137
|
+
name: `${namespace}/${name}`,
|
|
138
|
+
version: match.view.version,
|
|
139
|
+
...match.view.parameterSchema === void 0 ? {} : { parameterSchema: match.view.parameterSchema },
|
|
140
|
+
...parameterSchemaDigest === void 0 ? {} : { parameterSchemaDigest }
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
function resolveIntent(reference, contracts) {
|
|
144
|
+
const { namespace, name } = parseReference(reference);
|
|
145
|
+
const matches = contracts.flatMap((contract) => {
|
|
146
|
+
if (contract.namespace !== namespace) return [];
|
|
147
|
+
const metadata = contract.extensions?.["fabric.experience/v1"];
|
|
148
|
+
return (metadata?.actionIntents ?? []).flatMap((intent) => {
|
|
149
|
+
const shortName = intent.name.startsWith(`${namespace}.`) ? intent.name.slice(namespace.length + 1) : intent.name;
|
|
150
|
+
if (shortName !== name) return [];
|
|
151
|
+
const action = contract.actions.find((candidate) => candidate.actionId === intent.actionId);
|
|
152
|
+
return action ? [{ intent, action }] : [];
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
if (matches.length !== 1) throw new Error(`Capability intent route "${reference}" is ${matches.length === 0 ? "missing" : "ambiguous"}.`);
|
|
156
|
+
const match = matches[0];
|
|
157
|
+
const parameterSchemaDigest = match.action.parameterSchema === void 0 ? void 0 : sha256((0, import_contracts.canonicalizeJcs)(match.action.parameterSchema));
|
|
158
|
+
return {
|
|
159
|
+
reference,
|
|
160
|
+
actionId: match.action.actionId,
|
|
161
|
+
version: match.action.version,
|
|
162
|
+
...match.action.parameterSchema === void 0 ? {} : { parameterSchema: match.action.parameterSchema },
|
|
163
|
+
...parameterSchemaDigest === void 0 ? {} : { parameterSchemaDigest },
|
|
164
|
+
...match.action.execution === void 0 ? {} : { execution: match.action.execution }
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
function compileEffectiveGrants(release) {
|
|
168
|
+
const result = [];
|
|
169
|
+
const releaseViews = new Set(release.grants.views);
|
|
170
|
+
const releaseIntents = new Set(release.grants.intents);
|
|
171
|
+
const seen = /* @__PURE__ */ new Set();
|
|
172
|
+
walkTree("root", release.document.root, releaseViews, releaseIntents, release, result, seen);
|
|
173
|
+
for (const variant of release.document.variants ?? []) {
|
|
174
|
+
walkTree(variant.id, variant.root, releaseViews, releaseIntents, release, result, seen);
|
|
175
|
+
}
|
|
176
|
+
return result;
|
|
177
|
+
}
|
|
178
|
+
function walkTree(treeId, fragment, inheritedViews, inheritedIntents, release, result, seen) {
|
|
179
|
+
const identity = `${treeId}\0${fragment.id}`;
|
|
180
|
+
if (seen.has(identity)) throw new Error(`SDUI release repeats fragment "${fragment.id}" in tree "${treeId}".`);
|
|
181
|
+
seen.add(identity);
|
|
182
|
+
const narrowing = release.grants.fragments?.[fragment.id];
|
|
183
|
+
const views = narrowing?.views === void 0 ? new Set(inheritedViews) : intersect(inheritedViews, narrowing.views);
|
|
184
|
+
const intents = narrowing?.intents === void 0 ? new Set(inheritedIntents) : intersect(inheritedIntents, narrowing.intents);
|
|
185
|
+
if (fragment.dataRef && !views.has(fragment.dataRef)) {
|
|
186
|
+
throw new Error(`Fragment "${fragment.id}" references ungranted view "${fragment.dataRef}".`);
|
|
187
|
+
}
|
|
188
|
+
for (const action of Object.values(fragment.actions ?? {})) {
|
|
189
|
+
if (!intents.has(action.intent)) throw new Error(`Fragment "${fragment.id}" references ungranted intent "${action.intent}".`);
|
|
190
|
+
}
|
|
191
|
+
result.push({
|
|
192
|
+
treeId,
|
|
193
|
+
fragmentId: fragment.id,
|
|
194
|
+
views: [...views].sort(),
|
|
195
|
+
intents: [...intents].sort()
|
|
196
|
+
});
|
|
197
|
+
for (const child of fragment.children ?? []) walkTree(treeId, child, views, intents, release, result, seen);
|
|
198
|
+
}
|
|
199
|
+
function intersect(values, narrowing) {
|
|
200
|
+
return new Set(narrowing.filter((value) => values.has(value)));
|
|
201
|
+
}
|
|
202
|
+
function parseReference(reference) {
|
|
203
|
+
const parsed = (0, import_sdui_release.parseCapabilityRef)(reference);
|
|
204
|
+
if (!parsed) throw new Error(`Capability route "${reference}" is invalid.`);
|
|
205
|
+
return parsed;
|
|
206
|
+
}
|
|
207
|
+
function sha256(value) {
|
|
208
|
+
return (0, import_node_crypto.createHash)("sha256").update(value).digest("hex");
|
|
209
|
+
}
|
|
210
|
+
async function signCompactJws(payloadValue, type, signer) {
|
|
211
|
+
const protectedHeader = encodeBase64Url((0, import_contracts.canonicalizeJcs)({
|
|
212
|
+
alg: "ES256",
|
|
213
|
+
kid: signer.keyId,
|
|
214
|
+
typ: type
|
|
215
|
+
}));
|
|
216
|
+
const payload = encodeBase64Url((0, import_contracts.canonicalizeJcs)(payloadValue));
|
|
217
|
+
const signingInput = `${protectedHeader}.${payload}`;
|
|
218
|
+
const signature = await signer.sign(encoder.encode(signingInput));
|
|
219
|
+
if (signature.byteLength !== 64) throw new Error("Experience release signer must return a 64-byte ES256 JOSE signature.");
|
|
220
|
+
return `${signingInput}.${encodeBytesBase64Url(signature)}`;
|
|
221
|
+
}
|
|
222
|
+
function encodeBase64Url(value) {
|
|
223
|
+
return Buffer.from(value, "utf8").toString("base64url");
|
|
224
|
+
}
|
|
225
|
+
function encodeBytesBase64Url(value) {
|
|
226
|
+
return Buffer.from(value).toString("base64url");
|
|
227
|
+
}
|
|
228
|
+
function assertContractsApprovedByAssembly(release, contracts, assembly) {
|
|
229
|
+
(0, import_assembly.assertAssemblyLockfile)(assembly);
|
|
230
|
+
if (assembly.assemblyDigest !== release.assemblyDigest) {
|
|
231
|
+
throw new Error(
|
|
232
|
+
`Experience promotion was given assembly "${assembly.assemblyDigest}" but the release was validated against "${release.assemblyDigest}".`
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
const approved = new Map(
|
|
236
|
+
assembly.capabilities.filter((capability) => capability.usageContractDigest !== void 0).map((capability) => [capability.namespace, capability.usageContractDigest])
|
|
237
|
+
);
|
|
238
|
+
for (const document of contracts) {
|
|
239
|
+
const namespace = document.capability.namespace;
|
|
240
|
+
const expected = approved.get(namespace);
|
|
241
|
+
if (expected === void 0) {
|
|
242
|
+
throw new Error(`Assembly "${assembly.assemblyDigest}" approves no usage contract for capability "${namespace}".`);
|
|
243
|
+
}
|
|
244
|
+
const actual = (0, import_assembly.computeUsageContractDocumentDigest)(document);
|
|
245
|
+
if (actual !== expected) {
|
|
246
|
+
throw new Error(
|
|
247
|
+
`Usage contract for "${namespace}" has digest "${actual}", which is not the "${expected}" that assembly "${assembly.assemblyDigest}" approved.`
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
253
|
+
0 && (module.exports = {
|
|
254
|
+
createEs256ReleaseSigner,
|
|
255
|
+
migrateSduiReleaseV2ToV3,
|
|
256
|
+
promoteExperienceRelease,
|
|
257
|
+
runSduiComposerChecks,
|
|
258
|
+
sduiComposerChecks,
|
|
259
|
+
signReleaseChannelPointer
|
|
260
|
+
});
|
|
261
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../index.ts"],"sourcesContent":["import { createHash, sign, type KeyObject } from \"node:crypto\";\nimport type { UsageContract, UsageContractDocument } from \"@fabricorg/gen-capability\";\nimport {\n\tassertAssemblyLockfile,\n\tcomputeUsageContractDocumentDigest,\n\ttype AssemblyLockfile,\n} from \"@fabricorg/assembly\";\nimport {\n\tparseCapabilityRef,\n\tassertSduiReleaseIntegrity,\n\tassertSduiRelease,\n\ttype SduiFragment,\n\ttype SduiRelease,\n} from \"@fabricorg/sdui-release\";\nimport {\n\tSDUI_RELEASE_V3_FORMAT_VERSION,\n\tassertReleaseChannelPointer,\n\tcanonicalizeJcs,\n\ttype EffectiveFragmentGrant,\n\ttype ExperienceIntentRoute,\n\ttype ExperienceViewRoute,\n\ttype ReleaseChannelPointer,\n\ttype SduiReleaseV3,\n} from \"@fabricorg/sdui-release/contracts\";\n\nexport interface ExperienceReleaseSigner {\n\tkeyId: string;\n\tsign(signingInput: Uint8Array): Promise<Uint8Array>;\n}\n\nexport function createEs256ReleaseSigner(input: {\n\tkeyId: string;\n\tprivateKey: KeyObject;\n}): ExperienceReleaseSigner {\n\tif (!input.keyId.trim()) throw new Error(\"Experience release signing key id is required.\");\n\treturn {\n\t\tkeyId: input.keyId,\n\t\tasync sign(signingInput) {\n\t\t\treturn new Uint8Array(sign(\n\t\t\t\t\"sha256\",\n\t\t\t\tsigningInput,\n\t\t\t\t{ key: input.privateKey, dsaEncoding: \"ieee-p1363\" },\n\t\t\t));\n\t\t},\n\t};\n}\n\nexport interface PromoteExperienceReleaseInput {\n\tapplication: string;\n\trelease: SduiRelease;\n\t/**\n\t * The generated usage-contract documents routes are compiled from.\n\t *\n\t * These decide which action a granted `capability://` reference resolves to,\n\t * so they cannot be taken on trust: every one is checked against the\n\t * approved digests in `assembly` before a route is signed. Without that, a\n\t * substituted contract could remap an already-granted reference onto a\n\t * different installed action while the release kept its original assembly\n\t * digest.\n\t */\n\tcontracts: readonly UsageContractDocument[];\n\t/** The lockfile the release was validated against. */\n\tassembly: AssemblyLockfile;\n\tissuer: string;\n\tsigner: ExperienceReleaseSigner;\n}\n\nexport interface PromotedExperienceRelease {\n\trelease: SduiReleaseV3;\n\tcompactJws: string;\n}\n\nexport interface SduiComposerConformanceSubject {\n\tpromote(input: PromoteExperienceReleaseInput): Promise<PromotedExperienceRelease>;\n\tvalidInput: PromoteExperienceReleaseInput;\n\tinvalidInput: PromoteExperienceReleaseInput;\n}\n\nexport interface SduiComposerCheck {\n\tid: \"fabric.sdui-composer.exact-promotion.v1\" | \"fabric.sdui-composer.unresolved-denial.v1\";\n\trun(subject: SduiComposerConformanceSubject): Promise<string[]>;\n}\n\nconst encoder = new TextEncoder();\n\nexport async function promoteExperienceRelease(\n\tinput: PromoteExperienceReleaseInput,\n): Promise<PromotedExperienceRelease> {\n\tassertSduiReleaseIntegrity(input.release);\n\tassertSduiRelease(input.release);\n\tif (!input.issuer.trim()) throw new Error(\"Experience release issuer is required.\");\n\tassertContractsApprovedByAssembly(input.release, input.contracts, input.assembly);\n\tconst contracts = input.contracts.map((document) => document.capability);\n\tconst routes = compileRoutes(input.release, contracts);\n\tconst effectiveGrants = compileEffectiveGrants(input.release);\n\tconst body = {\n\t\tformatVersion: SDUI_RELEASE_V3_FORMAT_VERSION,\n\t\tkind: \"experience-release\" as const,\n\t\tissuer: input.issuer,\n\t\tapplication: input.application,\n\t\tassemblyDigest: input.release.assemblyDigest,\n\t\tdocument: input.release.document,\n\t\ttokenSet: input.release.tokenSet,\n\t\tcomponentPacks: input.release.componentPacks,\n\t\troutes,\n\t\teffectiveGrants,\n\t};\n\tconst release: SduiReleaseV3 = {\n\t\t...body,\n\t\treleaseDigest: sha256(canonicalizeJcs(body)),\n\t};\n\treturn {\n\t\trelease,\n\t\tcompactJws: await signCompactJws(release, \"fabric-experience-release+jws\", input.signer),\n\t};\n}\n\n/** Explicit v2-to-v3 migration seam. Migration fails unless every capability route resolves exactly. */\nexport const migrateSduiReleaseV2ToV3 = promoteExperienceRelease;\n\nexport async function signReleaseChannelPointer(\n\tpointer: ReleaseChannelPointer,\n\tsigner: ExperienceReleaseSigner,\n): Promise<string> {\n\tassertReleaseChannelPointer(pointer);\n\treturn signCompactJws(pointer, \"fabric-release-channel-pointer+jws\", signer);\n}\n\nexport function sduiComposerChecks(): readonly SduiComposerCheck[] {\n\treturn [\n\t\t{\n\t\t\tid: \"fabric.sdui-composer.exact-promotion.v1\",\n\t\t\tasync run(subject) {\n\t\t\t\tconst promoted = await subject.promote(subject.validInput);\n\t\t\t\tif (promoted.release.formatVersion !== 3 || promoted.compactJws.split(\".\").length !== 3) {\n\t\t\t\t\tthrow new Error(\"Composer did not produce a signed Release v3.\");\n\t\t\t\t}\n\t\t\t\treturn [promoted.release.releaseDigest];\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tid: \"fabric.sdui-composer.unresolved-denial.v1\",\n\t\t\tasync run(subject) {\n\t\t\t\tlet rejected = false;\n\t\t\t\ttry { await subject.promote(subject.invalidInput); } catch { rejected = true; }\n\t\t\t\tif (!rejected) throw new Error(\"Composer promoted unresolved or ambiguous capability routes.\");\n\t\t\t\treturn [\"invalid route rejected\"];\n\t\t\t},\n\t\t},\n\t];\n}\n\nexport async function runSduiComposerChecks(\n\tsubject: SduiComposerConformanceSubject,\n\tchecks: readonly SduiComposerCheck[] = sduiComposerChecks(),\n): Promise<{ passed: boolean; checks: Array<{ id: SduiComposerCheck[\"id\"]; status: \"passed\" | \"failed\"; evidence: string[]; error?: string }> }> {\n\tconst results: Array<{ id: SduiComposerCheck[\"id\"]; status: \"passed\" | \"failed\"; evidence: string[]; error?: string }> = [];\n\tfor (const check of checks) {\n\t\ttry { results.push({ id: check.id, status: \"passed\", evidence: await check.run(subject) }); }\n\t\tcatch (error) { results.push({ id: check.id, status: \"failed\", evidence: [], error: error instanceof Error ? error.message : String(error) }); }\n\t}\n\treturn { passed: results.every((result) => result.status === \"passed\"), checks: results };\n}\n\nfunction compileRoutes(\n\trelease: SduiRelease,\n\tcontracts: readonly UsageContract[],\n): { views: ExperienceViewRoute[]; intents: ExperienceIntentRoute[] } {\n\tconst views = [...new Set(release.grants.views)].sort().map((reference) => resolveView(reference, contracts));\n\tconst intents = [...new Set(release.grants.intents)].sort().map((reference) => resolveIntent(reference, contracts));\n\treturn { views, intents };\n}\n\nfunction resolveView(reference: string, contracts: readonly UsageContract[]): ExperienceViewRoute {\n\tconst { namespace, name } = parseReference(reference);\n\tconst matches = contracts.flatMap((contract) => {\n\t\tif (contract.namespace !== namespace) return [];\n\t\treturn contract.views\n\t\t\t.filter((view) => view.name === name || view.name === `${namespace}/${name}`)\n\t\t\t.map((view) => ({ contract, view }));\n\t});\n\tif (matches.length !== 1) throw new Error(`Capability view route \"${reference}\" is ${matches.length === 0 ? \"missing\" : \"ambiguous\"}.`);\n\tconst match = matches[0]!;\n\tconst parameterSchemaDigest = match.view.parameterSchema === undefined\n\t\t? undefined\n\t\t: sha256(canonicalizeJcs(match.view.parameterSchema));\n\treturn {\n\t\treference: reference as `capability://${string}`,\n\t\tname: `${namespace}/${name}`,\n\t\tversion: match.view.version,\n\t\t...(match.view.parameterSchema === undefined ? {} : { parameterSchema: match.view.parameterSchema }),\n\t\t...(parameterSchemaDigest === undefined ? {} : { parameterSchemaDigest }),\n\t};\n}\n\nfunction resolveIntent(reference: string, contracts: readonly UsageContract[]): ExperienceIntentRoute {\n\tconst { namespace, name } = parseReference(reference);\n\tconst matches = contracts.flatMap((contract) => {\n\t\tif (contract.namespace !== namespace) return [];\n\t\tconst metadata = contract.extensions?.[\"fabric.experience/v1\"] as\n\t\t\t| { actionIntents?: Array<{ name: string; actionId: string }> }\n\t\t\t| undefined;\n\t\treturn (metadata?.actionIntents ?? []).flatMap((intent) => {\n\t\t\tconst shortName = intent.name.startsWith(`${namespace}.`) ? intent.name.slice(namespace.length + 1) : intent.name;\n\t\t\tif (shortName !== name) return [];\n\t\t\tconst action = contract.actions.find((candidate) => candidate.actionId === intent.actionId);\n\t\t\treturn action ? [{ intent, action }] : [];\n\t\t});\n\t});\n\tif (matches.length !== 1) throw new Error(`Capability intent route \"${reference}\" is ${matches.length === 0 ? \"missing\" : \"ambiguous\"}.`);\n\tconst match = matches[0]!;\n\tconst parameterSchemaDigest = match.action.parameterSchema === undefined\n\t\t? undefined\n\t\t: sha256(canonicalizeJcs(match.action.parameterSchema));\n\treturn {\n\t\treference: reference as `capability://${string}`,\n\t\tactionId: match.action.actionId as `${string}.${string}`,\n\t\tversion: match.action.version,\n\t\t...(match.action.parameterSchema === undefined ? {} : { parameterSchema: match.action.parameterSchema as ExperienceIntentRoute[\"parameterSchema\"] }),\n\t\t...(parameterSchemaDigest === undefined ? {} : { parameterSchemaDigest }),\n\t\t...(match.action.execution === undefined ? {} : { execution: match.action.execution }),\n\t};\n}\n\nfunction compileEffectiveGrants(release: SduiRelease): EffectiveFragmentGrant[] {\n\tconst result: EffectiveFragmentGrant[] = [];\n\tconst releaseViews = new Set(release.grants.views);\n\tconst releaseIntents = new Set(release.grants.intents);\n\tconst seen = new Set<string>();\n\twalkTree(\"root\", release.document.root, releaseViews, releaseIntents, release, result, seen);\n\tfor (const variant of release.document.variants ?? []) {\n\t\twalkTree(variant.id, variant.root, releaseViews, releaseIntents, release, result, seen);\n\t}\n\treturn result;\n}\n\nfunction walkTree(\n\ttreeId: string,\n\tfragment: SduiFragment,\n\tinheritedViews: ReadonlySet<string>,\n\tinheritedIntents: ReadonlySet<string>,\n\trelease: SduiRelease,\n\tresult: EffectiveFragmentGrant[],\n\tseen: Set<string>,\n): void {\n\tconst identity = `${treeId}\\0${fragment.id}`;\n\tif (seen.has(identity)) throw new Error(`SDUI release repeats fragment \"${fragment.id}\" in tree \"${treeId}\".`);\n\tseen.add(identity);\n\tconst narrowing = release.grants.fragments?.[fragment.id];\n\tconst views = narrowing?.views === undefined ? new Set(inheritedViews) : intersect(inheritedViews, narrowing.views);\n\tconst intents = narrowing?.intents === undefined ? new Set(inheritedIntents) : intersect(inheritedIntents, narrowing.intents);\n\tif (fragment.dataRef && !views.has(fragment.dataRef)) {\n\t\tthrow new Error(`Fragment \"${fragment.id}\" references ungranted view \"${fragment.dataRef}\".`);\n\t}\n\tfor (const action of Object.values(fragment.actions ?? {})) {\n\t\tif (!intents.has(action.intent)) throw new Error(`Fragment \"${fragment.id}\" references ungranted intent \"${action.intent}\".`);\n\t}\n\tresult.push({\n\t\ttreeId,\n\t\tfragmentId: fragment.id,\n\t\tviews: [...views].sort(),\n\t\tintents: [...intents].sort(),\n\t});\n\tfor (const child of fragment.children ?? []) walkTree(treeId, child, views, intents, release, result, seen);\n}\n\nfunction intersect(values: ReadonlySet<string>, narrowing: readonly string[]): Set<string> {\n\treturn new Set(narrowing.filter((value) => values.has(value)));\n}\n\nfunction parseReference(reference: string): { namespace: string; name: string } {\n\t// The same grammar the release was validated against. A stricter one here\n\t// would reject references a valid release legitimately carries, and the\n\t// failure would land at promotion with nothing wrong upstream to point at.\n\tconst parsed = parseCapabilityRef(reference);\n\tif (!parsed) throw new Error(`Capability route \"${reference}\" is invalid.`);\n\treturn parsed;\n}\n\nfunction sha256(value: string): string {\n\treturn createHash(\"sha256\").update(value).digest(\"hex\");\n}\n\nasync function signCompactJws(\n\tpayloadValue: unknown,\n\ttype: string,\n\tsigner: ExperienceReleaseSigner,\n): Promise<string> {\n\tconst protectedHeader = encodeBase64Url(canonicalizeJcs({\n\t\talg: \"ES256\",\n\t\tkid: signer.keyId,\n\t\ttyp: type,\n\t}));\n\tconst payload = encodeBase64Url(canonicalizeJcs(payloadValue));\n\tconst signingInput = `${protectedHeader}.${payload}`;\n\tconst signature = await signer.sign(encoder.encode(signingInput));\n\tif (signature.byteLength !== 64) throw new Error(\"Experience release signer must return a 64-byte ES256 JOSE signature.\");\n\treturn `${signingInput}.${encodeBytesBase64Url(signature)}`;\n}\n\nfunction encodeBase64Url(value: string): string {\n\treturn Buffer.from(value, \"utf8\").toString(\"base64url\");\n}\n\nfunction encodeBytesBase64Url(value: Uint8Array): string {\n\treturn Buffer.from(value).toString(\"base64url\");\n}\n\n/**\n * Bind the contracts routes are compiled from to the assembly the release was\n * validated against.\n *\n * Promotion signs a mapping from a granted reference to a concrete action id\n * and version. The reference was authorized at release validation; the mapping\n * is decided here. If the contracts deciding it are not the approved ones, a\n * signed release can point a granted reference at an action nobody reviewed for\n * it. PlatformHost still authorizes that action, so this is an integrity\n * failure of what the release exposes rather than an authorization bypass, and\n * it is exactly the kind that is invisible downstream.\n */\nfunction assertContractsApprovedByAssembly(\n\trelease: SduiRelease,\n\tcontracts: readonly UsageContractDocument[],\n\tassembly: AssemblyLockfile,\n): void {\n\tassertAssemblyLockfile(assembly);\n\tif (assembly.assemblyDigest !== release.assemblyDigest) {\n\t\tthrow new Error(\n\t\t\t`Experience promotion was given assembly \"${assembly.assemblyDigest}\" but the release was validated against \"${release.assemblyDigest}\".`,\n\t\t);\n\t}\n\tconst approved = new Map(\n\t\tassembly.capabilities\n\t\t\t.filter((capability) => capability.usageContractDigest !== undefined)\n\t\t\t.map((capability) => [capability.namespace, capability.usageContractDigest as string]),\n\t);\n\tfor (const document of contracts) {\n\t\tconst namespace = document.capability.namespace;\n\t\tconst expected = approved.get(namespace);\n\t\tif (expected === undefined) {\n\t\t\tthrow new Error(`Assembly \"${assembly.assemblyDigest}\" approves no usage contract for capability \"${namespace}\".`);\n\t\t}\n\t\tconst actual = computeUsageContractDocumentDigest(document);\n\t\tif (actual !== expected) {\n\t\t\tthrow new Error(\n\t\t\t\t`Usage contract for \"${namespace}\" has digest \"${actual}\", which is not the \"${expected}\" that assembly \"${assembly.assemblyDigest}\" approved.`,\n\t\t\t);\n\t\t}\n\t}\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { KeyObject } from 'node:crypto';
|
|
2
|
+
import { UsageContractDocument } from '@fabricorg/gen-capability';
|
|
3
|
+
import { AssemblyLockfile } from '@fabricorg/assembly';
|
|
4
|
+
import { SduiRelease } from '@fabricorg/sdui-release';
|
|
5
|
+
import { SduiReleaseV3, ReleaseChannelPointer } from '@fabricorg/sdui-release/contracts';
|
|
6
|
+
|
|
7
|
+
interface ExperienceReleaseSigner {
|
|
8
|
+
keyId: string;
|
|
9
|
+
sign(signingInput: Uint8Array): Promise<Uint8Array>;
|
|
10
|
+
}
|
|
11
|
+
declare function createEs256ReleaseSigner(input: {
|
|
12
|
+
keyId: string;
|
|
13
|
+
privateKey: KeyObject;
|
|
14
|
+
}): ExperienceReleaseSigner;
|
|
15
|
+
interface PromoteExperienceReleaseInput {
|
|
16
|
+
application: string;
|
|
17
|
+
release: SduiRelease;
|
|
18
|
+
/**
|
|
19
|
+
* The generated usage-contract documents routes are compiled from.
|
|
20
|
+
*
|
|
21
|
+
* These decide which action a granted `capability://` reference resolves to,
|
|
22
|
+
* so they cannot be taken on trust: every one is checked against the
|
|
23
|
+
* approved digests in `assembly` before a route is signed. Without that, a
|
|
24
|
+
* substituted contract could remap an already-granted reference onto a
|
|
25
|
+
* different installed action while the release kept its original assembly
|
|
26
|
+
* digest.
|
|
27
|
+
*/
|
|
28
|
+
contracts: readonly UsageContractDocument[];
|
|
29
|
+
/** The lockfile the release was validated against. */
|
|
30
|
+
assembly: AssemblyLockfile;
|
|
31
|
+
issuer: string;
|
|
32
|
+
signer: ExperienceReleaseSigner;
|
|
33
|
+
}
|
|
34
|
+
interface PromotedExperienceRelease {
|
|
35
|
+
release: SduiReleaseV3;
|
|
36
|
+
compactJws: string;
|
|
37
|
+
}
|
|
38
|
+
interface SduiComposerConformanceSubject {
|
|
39
|
+
promote(input: PromoteExperienceReleaseInput): Promise<PromotedExperienceRelease>;
|
|
40
|
+
validInput: PromoteExperienceReleaseInput;
|
|
41
|
+
invalidInput: PromoteExperienceReleaseInput;
|
|
42
|
+
}
|
|
43
|
+
interface SduiComposerCheck {
|
|
44
|
+
id: "fabric.sdui-composer.exact-promotion.v1" | "fabric.sdui-composer.unresolved-denial.v1";
|
|
45
|
+
run(subject: SduiComposerConformanceSubject): Promise<string[]>;
|
|
46
|
+
}
|
|
47
|
+
declare function promoteExperienceRelease(input: PromoteExperienceReleaseInput): Promise<PromotedExperienceRelease>;
|
|
48
|
+
/** Explicit v2-to-v3 migration seam. Migration fails unless every capability route resolves exactly. */
|
|
49
|
+
declare const migrateSduiReleaseV2ToV3: typeof promoteExperienceRelease;
|
|
50
|
+
declare function signReleaseChannelPointer(pointer: ReleaseChannelPointer, signer: ExperienceReleaseSigner): Promise<string>;
|
|
51
|
+
declare function sduiComposerChecks(): readonly SduiComposerCheck[];
|
|
52
|
+
declare function runSduiComposerChecks(subject: SduiComposerConformanceSubject, checks?: readonly SduiComposerCheck[]): Promise<{
|
|
53
|
+
passed: boolean;
|
|
54
|
+
checks: Array<{
|
|
55
|
+
id: SduiComposerCheck["id"];
|
|
56
|
+
status: "passed" | "failed";
|
|
57
|
+
evidence: string[];
|
|
58
|
+
error?: string;
|
|
59
|
+
}>;
|
|
60
|
+
}>;
|
|
61
|
+
|
|
62
|
+
export { type ExperienceReleaseSigner, type PromoteExperienceReleaseInput, type PromotedExperienceRelease, type SduiComposerCheck, type SduiComposerConformanceSubject, createEs256ReleaseSigner, migrateSduiReleaseV2ToV3, promoteExperienceRelease, runSduiComposerChecks, sduiComposerChecks, signReleaseChannelPointer };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { KeyObject } from 'node:crypto';
|
|
2
|
+
import { UsageContractDocument } from '@fabricorg/gen-capability';
|
|
3
|
+
import { AssemblyLockfile } from '@fabricorg/assembly';
|
|
4
|
+
import { SduiRelease } from '@fabricorg/sdui-release';
|
|
5
|
+
import { SduiReleaseV3, ReleaseChannelPointer } from '@fabricorg/sdui-release/contracts';
|
|
6
|
+
|
|
7
|
+
interface ExperienceReleaseSigner {
|
|
8
|
+
keyId: string;
|
|
9
|
+
sign(signingInput: Uint8Array): Promise<Uint8Array>;
|
|
10
|
+
}
|
|
11
|
+
declare function createEs256ReleaseSigner(input: {
|
|
12
|
+
keyId: string;
|
|
13
|
+
privateKey: KeyObject;
|
|
14
|
+
}): ExperienceReleaseSigner;
|
|
15
|
+
interface PromoteExperienceReleaseInput {
|
|
16
|
+
application: string;
|
|
17
|
+
release: SduiRelease;
|
|
18
|
+
/**
|
|
19
|
+
* The generated usage-contract documents routes are compiled from.
|
|
20
|
+
*
|
|
21
|
+
* These decide which action a granted `capability://` reference resolves to,
|
|
22
|
+
* so they cannot be taken on trust: every one is checked against the
|
|
23
|
+
* approved digests in `assembly` before a route is signed. Without that, a
|
|
24
|
+
* substituted contract could remap an already-granted reference onto a
|
|
25
|
+
* different installed action while the release kept its original assembly
|
|
26
|
+
* digest.
|
|
27
|
+
*/
|
|
28
|
+
contracts: readonly UsageContractDocument[];
|
|
29
|
+
/** The lockfile the release was validated against. */
|
|
30
|
+
assembly: AssemblyLockfile;
|
|
31
|
+
issuer: string;
|
|
32
|
+
signer: ExperienceReleaseSigner;
|
|
33
|
+
}
|
|
34
|
+
interface PromotedExperienceRelease {
|
|
35
|
+
release: SduiReleaseV3;
|
|
36
|
+
compactJws: string;
|
|
37
|
+
}
|
|
38
|
+
interface SduiComposerConformanceSubject {
|
|
39
|
+
promote(input: PromoteExperienceReleaseInput): Promise<PromotedExperienceRelease>;
|
|
40
|
+
validInput: PromoteExperienceReleaseInput;
|
|
41
|
+
invalidInput: PromoteExperienceReleaseInput;
|
|
42
|
+
}
|
|
43
|
+
interface SduiComposerCheck {
|
|
44
|
+
id: "fabric.sdui-composer.exact-promotion.v1" | "fabric.sdui-composer.unresolved-denial.v1";
|
|
45
|
+
run(subject: SduiComposerConformanceSubject): Promise<string[]>;
|
|
46
|
+
}
|
|
47
|
+
declare function promoteExperienceRelease(input: PromoteExperienceReleaseInput): Promise<PromotedExperienceRelease>;
|
|
48
|
+
/** Explicit v2-to-v3 migration seam. Migration fails unless every capability route resolves exactly. */
|
|
49
|
+
declare const migrateSduiReleaseV2ToV3: typeof promoteExperienceRelease;
|
|
50
|
+
declare function signReleaseChannelPointer(pointer: ReleaseChannelPointer, signer: ExperienceReleaseSigner): Promise<string>;
|
|
51
|
+
declare function sduiComposerChecks(): readonly SduiComposerCheck[];
|
|
52
|
+
declare function runSduiComposerChecks(subject: SduiComposerConformanceSubject, checks?: readonly SduiComposerCheck[]): Promise<{
|
|
53
|
+
passed: boolean;
|
|
54
|
+
checks: Array<{
|
|
55
|
+
id: SduiComposerCheck["id"];
|
|
56
|
+
status: "passed" | "failed";
|
|
57
|
+
evidence: string[];
|
|
58
|
+
error?: string;
|
|
59
|
+
}>;
|
|
60
|
+
}>;
|
|
61
|
+
|
|
62
|
+
export { type ExperienceReleaseSigner, type PromoteExperienceReleaseInput, type PromotedExperienceRelease, type SduiComposerCheck, type SduiComposerConformanceSubject, createEs256ReleaseSigner, migrateSduiReleaseV2ToV3, promoteExperienceRelease, runSduiComposerChecks, sduiComposerChecks, signReleaseChannelPointer };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
// index.ts
|
|
2
|
+
import { createHash, sign } from "crypto";
|
|
3
|
+
import {
|
|
4
|
+
assertAssemblyLockfile,
|
|
5
|
+
computeUsageContractDocumentDigest
|
|
6
|
+
} from "@fabricorg/assembly";
|
|
7
|
+
import {
|
|
8
|
+
parseCapabilityRef,
|
|
9
|
+
assertSduiReleaseIntegrity,
|
|
10
|
+
assertSduiRelease
|
|
11
|
+
} from "@fabricorg/sdui-release";
|
|
12
|
+
import {
|
|
13
|
+
SDUI_RELEASE_V3_FORMAT_VERSION,
|
|
14
|
+
assertReleaseChannelPointer,
|
|
15
|
+
canonicalizeJcs
|
|
16
|
+
} from "@fabricorg/sdui-release/contracts";
|
|
17
|
+
function createEs256ReleaseSigner(input) {
|
|
18
|
+
if (!input.keyId.trim()) throw new Error("Experience release signing key id is required.");
|
|
19
|
+
return {
|
|
20
|
+
keyId: input.keyId,
|
|
21
|
+
async sign(signingInput) {
|
|
22
|
+
return new Uint8Array(sign(
|
|
23
|
+
"sha256",
|
|
24
|
+
signingInput,
|
|
25
|
+
{ key: input.privateKey, dsaEncoding: "ieee-p1363" }
|
|
26
|
+
));
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
var encoder = new TextEncoder();
|
|
31
|
+
async function promoteExperienceRelease(input) {
|
|
32
|
+
assertSduiReleaseIntegrity(input.release);
|
|
33
|
+
assertSduiRelease(input.release);
|
|
34
|
+
if (!input.issuer.trim()) throw new Error("Experience release issuer is required.");
|
|
35
|
+
assertContractsApprovedByAssembly(input.release, input.contracts, input.assembly);
|
|
36
|
+
const contracts = input.contracts.map((document) => document.capability);
|
|
37
|
+
const routes = compileRoutes(input.release, contracts);
|
|
38
|
+
const effectiveGrants = compileEffectiveGrants(input.release);
|
|
39
|
+
const body = {
|
|
40
|
+
formatVersion: SDUI_RELEASE_V3_FORMAT_VERSION,
|
|
41
|
+
kind: "experience-release",
|
|
42
|
+
issuer: input.issuer,
|
|
43
|
+
application: input.application,
|
|
44
|
+
assemblyDigest: input.release.assemblyDigest,
|
|
45
|
+
document: input.release.document,
|
|
46
|
+
tokenSet: input.release.tokenSet,
|
|
47
|
+
componentPacks: input.release.componentPacks,
|
|
48
|
+
routes,
|
|
49
|
+
effectiveGrants
|
|
50
|
+
};
|
|
51
|
+
const release = {
|
|
52
|
+
...body,
|
|
53
|
+
releaseDigest: sha256(canonicalizeJcs(body))
|
|
54
|
+
};
|
|
55
|
+
return {
|
|
56
|
+
release,
|
|
57
|
+
compactJws: await signCompactJws(release, "fabric-experience-release+jws", input.signer)
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
var migrateSduiReleaseV2ToV3 = promoteExperienceRelease;
|
|
61
|
+
async function signReleaseChannelPointer(pointer, signer) {
|
|
62
|
+
assertReleaseChannelPointer(pointer);
|
|
63
|
+
return signCompactJws(pointer, "fabric-release-channel-pointer+jws", signer);
|
|
64
|
+
}
|
|
65
|
+
function sduiComposerChecks() {
|
|
66
|
+
return [
|
|
67
|
+
{
|
|
68
|
+
id: "fabric.sdui-composer.exact-promotion.v1",
|
|
69
|
+
async run(subject) {
|
|
70
|
+
const promoted = await subject.promote(subject.validInput);
|
|
71
|
+
if (promoted.release.formatVersion !== 3 || promoted.compactJws.split(".").length !== 3) {
|
|
72
|
+
throw new Error("Composer did not produce a signed Release v3.");
|
|
73
|
+
}
|
|
74
|
+
return [promoted.release.releaseDigest];
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
id: "fabric.sdui-composer.unresolved-denial.v1",
|
|
79
|
+
async run(subject) {
|
|
80
|
+
let rejected = false;
|
|
81
|
+
try {
|
|
82
|
+
await subject.promote(subject.invalidInput);
|
|
83
|
+
} catch {
|
|
84
|
+
rejected = true;
|
|
85
|
+
}
|
|
86
|
+
if (!rejected) throw new Error("Composer promoted unresolved or ambiguous capability routes.");
|
|
87
|
+
return ["invalid route rejected"];
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
];
|
|
91
|
+
}
|
|
92
|
+
async function runSduiComposerChecks(subject, checks = sduiComposerChecks()) {
|
|
93
|
+
const results = [];
|
|
94
|
+
for (const check of checks) {
|
|
95
|
+
try {
|
|
96
|
+
results.push({ id: check.id, status: "passed", evidence: await check.run(subject) });
|
|
97
|
+
} catch (error) {
|
|
98
|
+
results.push({ id: check.id, status: "failed", evidence: [], error: error instanceof Error ? error.message : String(error) });
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return { passed: results.every((result) => result.status === "passed"), checks: results };
|
|
102
|
+
}
|
|
103
|
+
function compileRoutes(release, contracts) {
|
|
104
|
+
const views = [...new Set(release.grants.views)].sort().map((reference) => resolveView(reference, contracts));
|
|
105
|
+
const intents = [...new Set(release.grants.intents)].sort().map((reference) => resolveIntent(reference, contracts));
|
|
106
|
+
return { views, intents };
|
|
107
|
+
}
|
|
108
|
+
function resolveView(reference, contracts) {
|
|
109
|
+
const { namespace, name } = parseReference(reference);
|
|
110
|
+
const matches = contracts.flatMap((contract) => {
|
|
111
|
+
if (contract.namespace !== namespace) return [];
|
|
112
|
+
return contract.views.filter((view) => view.name === name || view.name === `${namespace}/${name}`).map((view) => ({ contract, view }));
|
|
113
|
+
});
|
|
114
|
+
if (matches.length !== 1) throw new Error(`Capability view route "${reference}" is ${matches.length === 0 ? "missing" : "ambiguous"}.`);
|
|
115
|
+
const match = matches[0];
|
|
116
|
+
const parameterSchemaDigest = match.view.parameterSchema === void 0 ? void 0 : sha256(canonicalizeJcs(match.view.parameterSchema));
|
|
117
|
+
return {
|
|
118
|
+
reference,
|
|
119
|
+
name: `${namespace}/${name}`,
|
|
120
|
+
version: match.view.version,
|
|
121
|
+
...match.view.parameterSchema === void 0 ? {} : { parameterSchema: match.view.parameterSchema },
|
|
122
|
+
...parameterSchemaDigest === void 0 ? {} : { parameterSchemaDigest }
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
function resolveIntent(reference, contracts) {
|
|
126
|
+
const { namespace, name } = parseReference(reference);
|
|
127
|
+
const matches = contracts.flatMap((contract) => {
|
|
128
|
+
if (contract.namespace !== namespace) return [];
|
|
129
|
+
const metadata = contract.extensions?.["fabric.experience/v1"];
|
|
130
|
+
return (metadata?.actionIntents ?? []).flatMap((intent) => {
|
|
131
|
+
const shortName = intent.name.startsWith(`${namespace}.`) ? intent.name.slice(namespace.length + 1) : intent.name;
|
|
132
|
+
if (shortName !== name) return [];
|
|
133
|
+
const action = contract.actions.find((candidate) => candidate.actionId === intent.actionId);
|
|
134
|
+
return action ? [{ intent, action }] : [];
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
if (matches.length !== 1) throw new Error(`Capability intent route "${reference}" is ${matches.length === 0 ? "missing" : "ambiguous"}.`);
|
|
138
|
+
const match = matches[0];
|
|
139
|
+
const parameterSchemaDigest = match.action.parameterSchema === void 0 ? void 0 : sha256(canonicalizeJcs(match.action.parameterSchema));
|
|
140
|
+
return {
|
|
141
|
+
reference,
|
|
142
|
+
actionId: match.action.actionId,
|
|
143
|
+
version: match.action.version,
|
|
144
|
+
...match.action.parameterSchema === void 0 ? {} : { parameterSchema: match.action.parameterSchema },
|
|
145
|
+
...parameterSchemaDigest === void 0 ? {} : { parameterSchemaDigest },
|
|
146
|
+
...match.action.execution === void 0 ? {} : { execution: match.action.execution }
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
function compileEffectiveGrants(release) {
|
|
150
|
+
const result = [];
|
|
151
|
+
const releaseViews = new Set(release.grants.views);
|
|
152
|
+
const releaseIntents = new Set(release.grants.intents);
|
|
153
|
+
const seen = /* @__PURE__ */ new Set();
|
|
154
|
+
walkTree("root", release.document.root, releaseViews, releaseIntents, release, result, seen);
|
|
155
|
+
for (const variant of release.document.variants ?? []) {
|
|
156
|
+
walkTree(variant.id, variant.root, releaseViews, releaseIntents, release, result, seen);
|
|
157
|
+
}
|
|
158
|
+
return result;
|
|
159
|
+
}
|
|
160
|
+
function walkTree(treeId, fragment, inheritedViews, inheritedIntents, release, result, seen) {
|
|
161
|
+
const identity = `${treeId}\0${fragment.id}`;
|
|
162
|
+
if (seen.has(identity)) throw new Error(`SDUI release repeats fragment "${fragment.id}" in tree "${treeId}".`);
|
|
163
|
+
seen.add(identity);
|
|
164
|
+
const narrowing = release.grants.fragments?.[fragment.id];
|
|
165
|
+
const views = narrowing?.views === void 0 ? new Set(inheritedViews) : intersect(inheritedViews, narrowing.views);
|
|
166
|
+
const intents = narrowing?.intents === void 0 ? new Set(inheritedIntents) : intersect(inheritedIntents, narrowing.intents);
|
|
167
|
+
if (fragment.dataRef && !views.has(fragment.dataRef)) {
|
|
168
|
+
throw new Error(`Fragment "${fragment.id}" references ungranted view "${fragment.dataRef}".`);
|
|
169
|
+
}
|
|
170
|
+
for (const action of Object.values(fragment.actions ?? {})) {
|
|
171
|
+
if (!intents.has(action.intent)) throw new Error(`Fragment "${fragment.id}" references ungranted intent "${action.intent}".`);
|
|
172
|
+
}
|
|
173
|
+
result.push({
|
|
174
|
+
treeId,
|
|
175
|
+
fragmentId: fragment.id,
|
|
176
|
+
views: [...views].sort(),
|
|
177
|
+
intents: [...intents].sort()
|
|
178
|
+
});
|
|
179
|
+
for (const child of fragment.children ?? []) walkTree(treeId, child, views, intents, release, result, seen);
|
|
180
|
+
}
|
|
181
|
+
function intersect(values, narrowing) {
|
|
182
|
+
return new Set(narrowing.filter((value) => values.has(value)));
|
|
183
|
+
}
|
|
184
|
+
function parseReference(reference) {
|
|
185
|
+
const parsed = parseCapabilityRef(reference);
|
|
186
|
+
if (!parsed) throw new Error(`Capability route "${reference}" is invalid.`);
|
|
187
|
+
return parsed;
|
|
188
|
+
}
|
|
189
|
+
function sha256(value) {
|
|
190
|
+
return createHash("sha256").update(value).digest("hex");
|
|
191
|
+
}
|
|
192
|
+
async function signCompactJws(payloadValue, type, signer) {
|
|
193
|
+
const protectedHeader = encodeBase64Url(canonicalizeJcs({
|
|
194
|
+
alg: "ES256",
|
|
195
|
+
kid: signer.keyId,
|
|
196
|
+
typ: type
|
|
197
|
+
}));
|
|
198
|
+
const payload = encodeBase64Url(canonicalizeJcs(payloadValue));
|
|
199
|
+
const signingInput = `${protectedHeader}.${payload}`;
|
|
200
|
+
const signature = await signer.sign(encoder.encode(signingInput));
|
|
201
|
+
if (signature.byteLength !== 64) throw new Error("Experience release signer must return a 64-byte ES256 JOSE signature.");
|
|
202
|
+
return `${signingInput}.${encodeBytesBase64Url(signature)}`;
|
|
203
|
+
}
|
|
204
|
+
function encodeBase64Url(value) {
|
|
205
|
+
return Buffer.from(value, "utf8").toString("base64url");
|
|
206
|
+
}
|
|
207
|
+
function encodeBytesBase64Url(value) {
|
|
208
|
+
return Buffer.from(value).toString("base64url");
|
|
209
|
+
}
|
|
210
|
+
function assertContractsApprovedByAssembly(release, contracts, assembly) {
|
|
211
|
+
assertAssemblyLockfile(assembly);
|
|
212
|
+
if (assembly.assemblyDigest !== release.assemblyDigest) {
|
|
213
|
+
throw new Error(
|
|
214
|
+
`Experience promotion was given assembly "${assembly.assemblyDigest}" but the release was validated against "${release.assemblyDigest}".`
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
const approved = new Map(
|
|
218
|
+
assembly.capabilities.filter((capability) => capability.usageContractDigest !== void 0).map((capability) => [capability.namespace, capability.usageContractDigest])
|
|
219
|
+
);
|
|
220
|
+
for (const document of contracts) {
|
|
221
|
+
const namespace = document.capability.namespace;
|
|
222
|
+
const expected = approved.get(namespace);
|
|
223
|
+
if (expected === void 0) {
|
|
224
|
+
throw new Error(`Assembly "${assembly.assemblyDigest}" approves no usage contract for capability "${namespace}".`);
|
|
225
|
+
}
|
|
226
|
+
const actual = computeUsageContractDocumentDigest(document);
|
|
227
|
+
if (actual !== expected) {
|
|
228
|
+
throw new Error(
|
|
229
|
+
`Usage contract for "${namespace}" has digest "${actual}", which is not the "${expected}" that assembly "${assembly.assemblyDigest}" approved.`
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
export {
|
|
235
|
+
createEs256ReleaseSigner,
|
|
236
|
+
migrateSduiReleaseV2ToV3,
|
|
237
|
+
promoteExperienceRelease,
|
|
238
|
+
runSduiComposerChecks,
|
|
239
|
+
sduiComposerChecks,
|
|
240
|
+
signReleaseChannelPointer
|
|
241
|
+
};
|
|
242
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../index.ts"],"sourcesContent":["import { createHash, sign, type KeyObject } from \"node:crypto\";\nimport type { UsageContract, UsageContractDocument } from \"@fabricorg/gen-capability\";\nimport {\n\tassertAssemblyLockfile,\n\tcomputeUsageContractDocumentDigest,\n\ttype AssemblyLockfile,\n} from \"@fabricorg/assembly\";\nimport {\n\tparseCapabilityRef,\n\tassertSduiReleaseIntegrity,\n\tassertSduiRelease,\n\ttype SduiFragment,\n\ttype SduiRelease,\n} from \"@fabricorg/sdui-release\";\nimport {\n\tSDUI_RELEASE_V3_FORMAT_VERSION,\n\tassertReleaseChannelPointer,\n\tcanonicalizeJcs,\n\ttype EffectiveFragmentGrant,\n\ttype ExperienceIntentRoute,\n\ttype ExperienceViewRoute,\n\ttype ReleaseChannelPointer,\n\ttype SduiReleaseV3,\n} from \"@fabricorg/sdui-release/contracts\";\n\nexport interface ExperienceReleaseSigner {\n\tkeyId: string;\n\tsign(signingInput: Uint8Array): Promise<Uint8Array>;\n}\n\nexport function createEs256ReleaseSigner(input: {\n\tkeyId: string;\n\tprivateKey: KeyObject;\n}): ExperienceReleaseSigner {\n\tif (!input.keyId.trim()) throw new Error(\"Experience release signing key id is required.\");\n\treturn {\n\t\tkeyId: input.keyId,\n\t\tasync sign(signingInput) {\n\t\t\treturn new Uint8Array(sign(\n\t\t\t\t\"sha256\",\n\t\t\t\tsigningInput,\n\t\t\t\t{ key: input.privateKey, dsaEncoding: \"ieee-p1363\" },\n\t\t\t));\n\t\t},\n\t};\n}\n\nexport interface PromoteExperienceReleaseInput {\n\tapplication: string;\n\trelease: SduiRelease;\n\t/**\n\t * The generated usage-contract documents routes are compiled from.\n\t *\n\t * These decide which action a granted `capability://` reference resolves to,\n\t * so they cannot be taken on trust: every one is checked against the\n\t * approved digests in `assembly` before a route is signed. Without that, a\n\t * substituted contract could remap an already-granted reference onto a\n\t * different installed action while the release kept its original assembly\n\t * digest.\n\t */\n\tcontracts: readonly UsageContractDocument[];\n\t/** The lockfile the release was validated against. */\n\tassembly: AssemblyLockfile;\n\tissuer: string;\n\tsigner: ExperienceReleaseSigner;\n}\n\nexport interface PromotedExperienceRelease {\n\trelease: SduiReleaseV3;\n\tcompactJws: string;\n}\n\nexport interface SduiComposerConformanceSubject {\n\tpromote(input: PromoteExperienceReleaseInput): Promise<PromotedExperienceRelease>;\n\tvalidInput: PromoteExperienceReleaseInput;\n\tinvalidInput: PromoteExperienceReleaseInput;\n}\n\nexport interface SduiComposerCheck {\n\tid: \"fabric.sdui-composer.exact-promotion.v1\" | \"fabric.sdui-composer.unresolved-denial.v1\";\n\trun(subject: SduiComposerConformanceSubject): Promise<string[]>;\n}\n\nconst encoder = new TextEncoder();\n\nexport async function promoteExperienceRelease(\n\tinput: PromoteExperienceReleaseInput,\n): Promise<PromotedExperienceRelease> {\n\tassertSduiReleaseIntegrity(input.release);\n\tassertSduiRelease(input.release);\n\tif (!input.issuer.trim()) throw new Error(\"Experience release issuer is required.\");\n\tassertContractsApprovedByAssembly(input.release, input.contracts, input.assembly);\n\tconst contracts = input.contracts.map((document) => document.capability);\n\tconst routes = compileRoutes(input.release, contracts);\n\tconst effectiveGrants = compileEffectiveGrants(input.release);\n\tconst body = {\n\t\tformatVersion: SDUI_RELEASE_V3_FORMAT_VERSION,\n\t\tkind: \"experience-release\" as const,\n\t\tissuer: input.issuer,\n\t\tapplication: input.application,\n\t\tassemblyDigest: input.release.assemblyDigest,\n\t\tdocument: input.release.document,\n\t\ttokenSet: input.release.tokenSet,\n\t\tcomponentPacks: input.release.componentPacks,\n\t\troutes,\n\t\teffectiveGrants,\n\t};\n\tconst release: SduiReleaseV3 = {\n\t\t...body,\n\t\treleaseDigest: sha256(canonicalizeJcs(body)),\n\t};\n\treturn {\n\t\trelease,\n\t\tcompactJws: await signCompactJws(release, \"fabric-experience-release+jws\", input.signer),\n\t};\n}\n\n/** Explicit v2-to-v3 migration seam. Migration fails unless every capability route resolves exactly. */\nexport const migrateSduiReleaseV2ToV3 = promoteExperienceRelease;\n\nexport async function signReleaseChannelPointer(\n\tpointer: ReleaseChannelPointer,\n\tsigner: ExperienceReleaseSigner,\n): Promise<string> {\n\tassertReleaseChannelPointer(pointer);\n\treturn signCompactJws(pointer, \"fabric-release-channel-pointer+jws\", signer);\n}\n\nexport function sduiComposerChecks(): readonly SduiComposerCheck[] {\n\treturn [\n\t\t{\n\t\t\tid: \"fabric.sdui-composer.exact-promotion.v1\",\n\t\t\tasync run(subject) {\n\t\t\t\tconst promoted = await subject.promote(subject.validInput);\n\t\t\t\tif (promoted.release.formatVersion !== 3 || promoted.compactJws.split(\".\").length !== 3) {\n\t\t\t\t\tthrow new Error(\"Composer did not produce a signed Release v3.\");\n\t\t\t\t}\n\t\t\t\treturn [promoted.release.releaseDigest];\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tid: \"fabric.sdui-composer.unresolved-denial.v1\",\n\t\t\tasync run(subject) {\n\t\t\t\tlet rejected = false;\n\t\t\t\ttry { await subject.promote(subject.invalidInput); } catch { rejected = true; }\n\t\t\t\tif (!rejected) throw new Error(\"Composer promoted unresolved or ambiguous capability routes.\");\n\t\t\t\treturn [\"invalid route rejected\"];\n\t\t\t},\n\t\t},\n\t];\n}\n\nexport async function runSduiComposerChecks(\n\tsubject: SduiComposerConformanceSubject,\n\tchecks: readonly SduiComposerCheck[] = sduiComposerChecks(),\n): Promise<{ passed: boolean; checks: Array<{ id: SduiComposerCheck[\"id\"]; status: \"passed\" | \"failed\"; evidence: string[]; error?: string }> }> {\n\tconst results: Array<{ id: SduiComposerCheck[\"id\"]; status: \"passed\" | \"failed\"; evidence: string[]; error?: string }> = [];\n\tfor (const check of checks) {\n\t\ttry { results.push({ id: check.id, status: \"passed\", evidence: await check.run(subject) }); }\n\t\tcatch (error) { results.push({ id: check.id, status: \"failed\", evidence: [], error: error instanceof Error ? error.message : String(error) }); }\n\t}\n\treturn { passed: results.every((result) => result.status === \"passed\"), checks: results };\n}\n\nfunction compileRoutes(\n\trelease: SduiRelease,\n\tcontracts: readonly UsageContract[],\n): { views: ExperienceViewRoute[]; intents: ExperienceIntentRoute[] } {\n\tconst views = [...new Set(release.grants.views)].sort().map((reference) => resolveView(reference, contracts));\n\tconst intents = [...new Set(release.grants.intents)].sort().map((reference) => resolveIntent(reference, contracts));\n\treturn { views, intents };\n}\n\nfunction resolveView(reference: string, contracts: readonly UsageContract[]): ExperienceViewRoute {\n\tconst { namespace, name } = parseReference(reference);\n\tconst matches = contracts.flatMap((contract) => {\n\t\tif (contract.namespace !== namespace) return [];\n\t\treturn contract.views\n\t\t\t.filter((view) => view.name === name || view.name === `${namespace}/${name}`)\n\t\t\t.map((view) => ({ contract, view }));\n\t});\n\tif (matches.length !== 1) throw new Error(`Capability view route \"${reference}\" is ${matches.length === 0 ? \"missing\" : \"ambiguous\"}.`);\n\tconst match = matches[0]!;\n\tconst parameterSchemaDigest = match.view.parameterSchema === undefined\n\t\t? undefined\n\t\t: sha256(canonicalizeJcs(match.view.parameterSchema));\n\treturn {\n\t\treference: reference as `capability://${string}`,\n\t\tname: `${namespace}/${name}`,\n\t\tversion: match.view.version,\n\t\t...(match.view.parameterSchema === undefined ? {} : { parameterSchema: match.view.parameterSchema }),\n\t\t...(parameterSchemaDigest === undefined ? {} : { parameterSchemaDigest }),\n\t};\n}\n\nfunction resolveIntent(reference: string, contracts: readonly UsageContract[]): ExperienceIntentRoute {\n\tconst { namespace, name } = parseReference(reference);\n\tconst matches = contracts.flatMap((contract) => {\n\t\tif (contract.namespace !== namespace) return [];\n\t\tconst metadata = contract.extensions?.[\"fabric.experience/v1\"] as\n\t\t\t| { actionIntents?: Array<{ name: string; actionId: string }> }\n\t\t\t| undefined;\n\t\treturn (metadata?.actionIntents ?? []).flatMap((intent) => {\n\t\t\tconst shortName = intent.name.startsWith(`${namespace}.`) ? intent.name.slice(namespace.length + 1) : intent.name;\n\t\t\tif (shortName !== name) return [];\n\t\t\tconst action = contract.actions.find((candidate) => candidate.actionId === intent.actionId);\n\t\t\treturn action ? [{ intent, action }] : [];\n\t\t});\n\t});\n\tif (matches.length !== 1) throw new Error(`Capability intent route \"${reference}\" is ${matches.length === 0 ? \"missing\" : \"ambiguous\"}.`);\n\tconst match = matches[0]!;\n\tconst parameterSchemaDigest = match.action.parameterSchema === undefined\n\t\t? undefined\n\t\t: sha256(canonicalizeJcs(match.action.parameterSchema));\n\treturn {\n\t\treference: reference as `capability://${string}`,\n\t\tactionId: match.action.actionId as `${string}.${string}`,\n\t\tversion: match.action.version,\n\t\t...(match.action.parameterSchema === undefined ? {} : { parameterSchema: match.action.parameterSchema as ExperienceIntentRoute[\"parameterSchema\"] }),\n\t\t...(parameterSchemaDigest === undefined ? {} : { parameterSchemaDigest }),\n\t\t...(match.action.execution === undefined ? {} : { execution: match.action.execution }),\n\t};\n}\n\nfunction compileEffectiveGrants(release: SduiRelease): EffectiveFragmentGrant[] {\n\tconst result: EffectiveFragmentGrant[] = [];\n\tconst releaseViews = new Set(release.grants.views);\n\tconst releaseIntents = new Set(release.grants.intents);\n\tconst seen = new Set<string>();\n\twalkTree(\"root\", release.document.root, releaseViews, releaseIntents, release, result, seen);\n\tfor (const variant of release.document.variants ?? []) {\n\t\twalkTree(variant.id, variant.root, releaseViews, releaseIntents, release, result, seen);\n\t}\n\treturn result;\n}\n\nfunction walkTree(\n\ttreeId: string,\n\tfragment: SduiFragment,\n\tinheritedViews: ReadonlySet<string>,\n\tinheritedIntents: ReadonlySet<string>,\n\trelease: SduiRelease,\n\tresult: EffectiveFragmentGrant[],\n\tseen: Set<string>,\n): void {\n\tconst identity = `${treeId}\\0${fragment.id}`;\n\tif (seen.has(identity)) throw new Error(`SDUI release repeats fragment \"${fragment.id}\" in tree \"${treeId}\".`);\n\tseen.add(identity);\n\tconst narrowing = release.grants.fragments?.[fragment.id];\n\tconst views = narrowing?.views === undefined ? new Set(inheritedViews) : intersect(inheritedViews, narrowing.views);\n\tconst intents = narrowing?.intents === undefined ? new Set(inheritedIntents) : intersect(inheritedIntents, narrowing.intents);\n\tif (fragment.dataRef && !views.has(fragment.dataRef)) {\n\t\tthrow new Error(`Fragment \"${fragment.id}\" references ungranted view \"${fragment.dataRef}\".`);\n\t}\n\tfor (const action of Object.values(fragment.actions ?? {})) {\n\t\tif (!intents.has(action.intent)) throw new Error(`Fragment \"${fragment.id}\" references ungranted intent \"${action.intent}\".`);\n\t}\n\tresult.push({\n\t\ttreeId,\n\t\tfragmentId: fragment.id,\n\t\tviews: [...views].sort(),\n\t\tintents: [...intents].sort(),\n\t});\n\tfor (const child of fragment.children ?? []) walkTree(treeId, child, views, intents, release, result, seen);\n}\n\nfunction intersect(values: ReadonlySet<string>, narrowing: readonly string[]): Set<string> {\n\treturn new Set(narrowing.filter((value) => values.has(value)));\n}\n\nfunction parseReference(reference: string): { namespace: string; name: string } {\n\t// The same grammar the release was validated against. A stricter one here\n\t// would reject references a valid release legitimately carries, and the\n\t// failure would land at promotion with nothing wrong upstream to point at.\n\tconst parsed = parseCapabilityRef(reference);\n\tif (!parsed) throw new Error(`Capability route \"${reference}\" is invalid.`);\n\treturn parsed;\n}\n\nfunction sha256(value: string): string {\n\treturn createHash(\"sha256\").update(value).digest(\"hex\");\n}\n\nasync function signCompactJws(\n\tpayloadValue: unknown,\n\ttype: string,\n\tsigner: ExperienceReleaseSigner,\n): Promise<string> {\n\tconst protectedHeader = encodeBase64Url(canonicalizeJcs({\n\t\talg: \"ES256\",\n\t\tkid: signer.keyId,\n\t\ttyp: type,\n\t}));\n\tconst payload = encodeBase64Url(canonicalizeJcs(payloadValue));\n\tconst signingInput = `${protectedHeader}.${payload}`;\n\tconst signature = await signer.sign(encoder.encode(signingInput));\n\tif (signature.byteLength !== 64) throw new Error(\"Experience release signer must return a 64-byte ES256 JOSE signature.\");\n\treturn `${signingInput}.${encodeBytesBase64Url(signature)}`;\n}\n\nfunction encodeBase64Url(value: string): string {\n\treturn Buffer.from(value, \"utf8\").toString(\"base64url\");\n}\n\nfunction encodeBytesBase64Url(value: Uint8Array): string {\n\treturn Buffer.from(value).toString(\"base64url\");\n}\n\n/**\n * Bind the contracts routes are compiled from to the assembly the release was\n * validated against.\n *\n * Promotion signs a mapping from a granted reference to a concrete action id\n * and version. The reference was authorized at release validation; the mapping\n * is decided here. If the contracts deciding it are not the approved ones, a\n * signed release can point a granted reference at an action nobody reviewed for\n * it. PlatformHost still authorizes that action, so this is an integrity\n * failure of what the release exposes rather than an authorization bypass, and\n * it is exactly the kind that is invisible downstream.\n */\nfunction assertContractsApprovedByAssembly(\n\trelease: SduiRelease,\n\tcontracts: readonly UsageContractDocument[],\n\tassembly: AssemblyLockfile,\n): void {\n\tassertAssemblyLockfile(assembly);\n\tif (assembly.assemblyDigest !== release.assemblyDigest) {\n\t\tthrow new Error(\n\t\t\t`Experience promotion was given assembly \"${assembly.assemblyDigest}\" but the release was validated against \"${release.assemblyDigest}\".`,\n\t\t);\n\t}\n\tconst approved = new Map(\n\t\tassembly.capabilities\n\t\t\t.filter((capability) => capability.usageContractDigest !== undefined)\n\t\t\t.map((capability) => [capability.namespace, capability.usageContractDigest as string]),\n\t);\n\tfor (const document of contracts) {\n\t\tconst namespace = document.capability.namespace;\n\t\tconst expected = approved.get(namespace);\n\t\tif (expected === undefined) {\n\t\t\tthrow new Error(`Assembly \"${assembly.assemblyDigest}\" approves no usage contract for capability \"${namespace}\".`);\n\t\t}\n\t\tconst actual = computeUsageContractDocumentDigest(document);\n\t\tif (actual !== expected) {\n\t\t\tthrow new Error(\n\t\t\t\t`Usage contract for \"${namespace}\" has digest \"${actual}\", which is not the \"${expected}\" that assembly \"${assembly.assemblyDigest}\" approved.`,\n\t\t\t);\n\t\t}\n\t}\n}\n"],"mappings":"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","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@fabricorg/sdui-composer",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Headless SDUI route compilation, promotion, and signing tooling.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"sideEffects": false,
|
|
8
|
+
"main": "./dist/index.cjs",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"import": {
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"default": "./dist/index.js"
|
|
15
|
+
},
|
|
16
|
+
"require": {
|
|
17
|
+
"types": "./dist/index.d.cts",
|
|
18
|
+
"default": "./dist/index.cjs"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"dist",
|
|
24
|
+
"README.md",
|
|
25
|
+
"CHANGELOG.md",
|
|
26
|
+
"LICENSE"
|
|
27
|
+
],
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"@fabricorg/sdui-release": "^0.5.0",
|
|
30
|
+
"@fabricorg/assembly": "0.4.0"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@types/node": "^22.10.0",
|
|
34
|
+
"tsup": "^8.5.0",
|
|
35
|
+
"typescript": "^5.7.3",
|
|
36
|
+
"vitest": "^4.1.5",
|
|
37
|
+
"@fabricorg/gen-capability": "^0.5.1",
|
|
38
|
+
"@fabricorg/platform": "^1.3.0"
|
|
39
|
+
},
|
|
40
|
+
"engines": {
|
|
41
|
+
"node": ">=20"
|
|
42
|
+
},
|
|
43
|
+
"publishConfig": {
|
|
44
|
+
"access": "public"
|
|
45
|
+
},
|
|
46
|
+
"peerDependencies": {
|
|
47
|
+
"@fabricorg/gen-capability": "^0.5.1",
|
|
48
|
+
"@fabricorg/platform": "^1.3.0"
|
|
49
|
+
},
|
|
50
|
+
"scripts": {
|
|
51
|
+
"clean": "rm -rf dist tsconfig.tsbuildinfo",
|
|
52
|
+
"build": "tsup",
|
|
53
|
+
"type-check": "tsc --noEmit",
|
|
54
|
+
"test": "vitest run"
|
|
55
|
+
}
|
|
56
|
+
}
|