@fabricorg/assembly 0.4.0 → 0.4.1
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 +8 -0
- package/README.md +12 -0
- package/dist/index.cjs +159 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +65 -3
- package/dist/index.d.ts +65 -3
- package/dist/index.js +158 -5
- package/dist/index.js.map +1 -1
- package/package.json +7 -5
package/dist/index.d.cts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { RegisteredAdapter, AdapterCertification } from '@fabricorg/ports';
|
|
1
2
|
import { AdoptionBinding, BindingFinding, ComponentPackManifest } from '@fabricorg/adoption-bindings';
|
|
2
3
|
import { UsageContractDocument, UsageContract } from '@fabricorg/gen-capability';
|
|
3
4
|
|
|
@@ -76,6 +77,20 @@ interface AssemblyContractEntry {
|
|
|
76
77
|
namespace: string;
|
|
77
78
|
document: UsageContractDocument;
|
|
78
79
|
}
|
|
80
|
+
/**
|
|
81
|
+
* An adapter the vertical deploys behind a port, with the certification the
|
|
82
|
+
* port's suite issued. Structurally the output of `certifyAdapter`, so a
|
|
83
|
+
* vertical passes that straight in. Without a certification the adapter is a
|
|
84
|
+
* claim, and resolution refuses it for any port a contract requires.
|
|
85
|
+
*/
|
|
86
|
+
interface AssemblyAdapterEntry extends RegisteredAdapter {
|
|
87
|
+
certification?: AdapterCertification;
|
|
88
|
+
}
|
|
89
|
+
/** A port contract in force, so a certification against an older contract is stale. */
|
|
90
|
+
interface AssemblyPortDefinition {
|
|
91
|
+
id: string;
|
|
92
|
+
version: string;
|
|
93
|
+
}
|
|
79
94
|
/**
|
|
80
95
|
* What a vertical assembles: the capabilities it depends on, the component
|
|
81
96
|
* packs it uses, the adoption bindings it declares, and the usage contracts
|
|
@@ -90,6 +105,14 @@ interface AssemblyManifest {
|
|
|
90
105
|
componentPacks: AssemblyComponentPackReference[];
|
|
91
106
|
bindings: AssemblyBindingEntry[];
|
|
92
107
|
contracts: AssemblyContractEntry[];
|
|
108
|
+
/**
|
|
109
|
+
* Adapters deployed behind the ports the contracts require. Declaring the
|
|
110
|
+
* field, even empty, engages the adapter plane: every port a contract
|
|
111
|
+
* requires must then be satisfied by a certified adapter listed here, or
|
|
112
|
+
* the assembly does not resolve. An assembly that does not declare it
|
|
113
|
+
* resolves as before, with port satisfaction as a separate deployment gate.
|
|
114
|
+
*/
|
|
115
|
+
adapters?: AssemblyAdapterEntry[];
|
|
93
116
|
}
|
|
94
117
|
/** A published capability version available for resolution. */
|
|
95
118
|
interface AvailableCapability {
|
|
@@ -148,6 +171,27 @@ interface ResolvedBinding {
|
|
|
148
171
|
valid: boolean;
|
|
149
172
|
findings: BindingFinding[];
|
|
150
173
|
}
|
|
174
|
+
/**
|
|
175
|
+
* A certified adapter locked into the assembly. The certification itself is
|
|
176
|
+
* summarised by digest so the lockfile stays small and the evidence stays
|
|
177
|
+
* verifiable: the same certification produces the same digest.
|
|
178
|
+
*/
|
|
179
|
+
interface ResolvedAdapter {
|
|
180
|
+
port: string;
|
|
181
|
+
vendor: string;
|
|
182
|
+
version?: string;
|
|
183
|
+
standard?: {
|
|
184
|
+
name: string;
|
|
185
|
+
version?: string;
|
|
186
|
+
};
|
|
187
|
+
/** Whether a certification was declared. An uncertified adapter is recorded so the lockfile says what is deployed, and refused wherever a contract requires its port. */
|
|
188
|
+
certified: boolean;
|
|
189
|
+
/** Port contract version the adapter was certified against. */
|
|
190
|
+
portVersion?: string;
|
|
191
|
+
certifiedAt?: string;
|
|
192
|
+
/** SHA-256 of the whole canonical certification record. */
|
|
193
|
+
certificationDigest?: string;
|
|
194
|
+
}
|
|
151
195
|
/**
|
|
152
196
|
* The deterministic, resolved output of assembling a vertical. Every version
|
|
153
197
|
* is locked, every binding is validated, and the whole document carries a
|
|
@@ -161,8 +205,10 @@ interface AssemblyLockfile {
|
|
|
161
205
|
capabilities: ResolvedCapability[];
|
|
162
206
|
componentPacks: ResolvedComponentPack[];
|
|
163
207
|
bindings: ResolvedBinding[];
|
|
208
|
+
/** Present when the manifest declared adapters; covered by the digest. */
|
|
209
|
+
adapters?: ResolvedAdapter[];
|
|
164
210
|
}
|
|
165
|
-
type AssemblyFindingCode = "duplicate_capability" | "duplicate_component_pack" | "unresolved_capability" | "unresolved_component_pack" | "invalid_range" | "unknown_contract_for_binding" | "contract_namespace_mismatch" | "contract_manifest_digest_mismatch" | "binding_validation_failed" | "invalid_contract" | "invalid_component_pack" | "missing_component_pack_manifest" | "component_pack_artifact_digest_mismatch" | "duplicate_contract";
|
|
211
|
+
type AssemblyFindingCode = "duplicate_capability" | "duplicate_component_pack" | "unresolved_capability" | "unresolved_component_pack" | "invalid_range" | "unknown_contract_for_binding" | "contract_namespace_mismatch" | "contract_manifest_digest_mismatch" | "binding_validation_failed" | "invalid_contract" | "invalid_component_pack" | "missing_component_pack_manifest" | "component_pack_artifact_digest_mismatch" | "duplicate_contract" | "duplicate_adapter" | "unsatisfied_port" | "standard_not_spoken" | "version_mismatch" | "uncertified_adapter" | "stale_certification";
|
|
166
212
|
interface AssemblyFinding {
|
|
167
213
|
code: AssemblyFindingCode;
|
|
168
214
|
path: string;
|
|
@@ -184,6 +230,8 @@ declare function computeUsageContractDocumentDigest(document: UsageContractDocum
|
|
|
184
230
|
* digest it was validated against.
|
|
185
231
|
*/
|
|
186
232
|
declare function computeAssemblyLockfileDigest(lockfile: Omit<AssemblyLockfile, "assemblyDigest">): string;
|
|
233
|
+
/** The digest a certification record locks under: the whole record, keys sorted, checks sorted. */
|
|
234
|
+
declare function computeAdapterCertificationDigest(certification: AdapterCertification): string;
|
|
187
235
|
/**
|
|
188
236
|
* Structural validation of an assembly manifest. Checks the document's own
|
|
189
237
|
* shape; does not resolve ranges or validate contracts/bindings.
|
|
@@ -202,7 +250,15 @@ declare function assertAssemblyLockfile(value: unknown, path?: string): asserts
|
|
|
202
250
|
* Verify that the capability artifacts loaded by a runtime are exactly those
|
|
203
251
|
* approved by an integrity-checked assembly lockfile.
|
|
204
252
|
*/
|
|
205
|
-
declare function assertAssemblyRuntimeCompatible(lockfile: AssemblyLockfile, loadedCapabilities: readonly AvailableCapability[]
|
|
253
|
+
declare function assertAssemblyRuntimeCompatible(lockfile: AssemblyLockfile, loadedCapabilities: readonly AvailableCapability[],
|
|
254
|
+
/**
|
|
255
|
+
* The adapters this runtime registered, when the lockfile carries an
|
|
256
|
+
* adapter plane. Every locked certified adapter must be registered under
|
|
257
|
+
* the same port and vendor with the same certification, and a registered
|
|
258
|
+
* adapter the lockfile does not know is refused, so what runs behind a
|
|
259
|
+
* port is what was approved.
|
|
260
|
+
*/
|
|
261
|
+
registeredAdapters?: readonly AssemblyAdapterEntry[]): void;
|
|
206
262
|
interface ResolveAssemblyInput {
|
|
207
263
|
manifest: AssemblyManifest;
|
|
208
264
|
/** Published capability versions available for resolution. */
|
|
@@ -215,6 +271,12 @@ interface ResolveAssemblyInput {
|
|
|
215
271
|
coreComponents?: readonly string[];
|
|
216
272
|
/** Contracts available for cross-capability fragment resolution. */
|
|
217
273
|
availableContracts?: UsageContract[];
|
|
274
|
+
/**
|
|
275
|
+
* Port contracts in force. With them, a certification issued against an
|
|
276
|
+
* older port version is `stale_certification`; without them, only its
|
|
277
|
+
* presence is checked.
|
|
278
|
+
*/
|
|
279
|
+
portDefinitions?: readonly AssemblyPortDefinition[];
|
|
218
280
|
}
|
|
219
281
|
/**
|
|
220
282
|
* Resolve an assembly manifest into a deterministic lockfile. This is the
|
|
@@ -245,4 +307,4 @@ declare function validateAssemblyBindings(input: {
|
|
|
245
307
|
availableContracts?: UsageContract[];
|
|
246
308
|
}): AssemblyResult;
|
|
247
309
|
|
|
248
|
-
export { ASSEMBLY_LOCKFILE_FORMAT_VERSION, ASSEMBLY_MANIFEST_FORMAT_VERSION, type AssemblyBindingEntry, type AssemblyCapabilityReference, type AssemblyComponentPackReference, type AssemblyContractEntry, type AssemblyFinding, type AssemblyFindingCode, type AssemblyLockfile, type AssemblyManifest, type AssemblyResult, type AvailableCapability, type AvailableComponentPack, type ComponentPackRemote, type ResolveAssemblyInput, type ResolvedBinding, type ResolvedCapability, type ResolvedComponentPack, type SemverRange, assertAssemblyLockfile, assertAssemblyManifest, assertAssemblyResolved, assertAssemblyRuntimeCompatible, assertComponentPackManifest, computeAssemblyLockfileDigest, computeUsageContractDocumentDigest, parseSemverRange, resolveAssembly, resolveVersionRange, satisfiesRange, validateAssemblyBindings };
|
|
310
|
+
export { ASSEMBLY_LOCKFILE_FORMAT_VERSION, ASSEMBLY_MANIFEST_FORMAT_VERSION, type AssemblyAdapterEntry, type AssemblyBindingEntry, type AssemblyCapabilityReference, type AssemblyComponentPackReference, type AssemblyContractEntry, type AssemblyFinding, type AssemblyFindingCode, type AssemblyLockfile, type AssemblyManifest, type AssemblyPortDefinition, type AssemblyResult, type AvailableCapability, type AvailableComponentPack, type ComponentPackRemote, type ResolveAssemblyInput, type ResolvedAdapter, type ResolvedBinding, type ResolvedCapability, type ResolvedComponentPack, type SemverRange, assertAssemblyLockfile, assertAssemblyManifest, assertAssemblyResolved, assertAssemblyRuntimeCompatible, assertComponentPackManifest, computeAdapterCertificationDigest, computeAssemblyLockfileDigest, computeUsageContractDocumentDigest, parseSemverRange, resolveAssembly, resolveVersionRange, satisfiesRange, validateAssemblyBindings };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { RegisteredAdapter, AdapterCertification } from '@fabricorg/ports';
|
|
1
2
|
import { AdoptionBinding, BindingFinding, ComponentPackManifest } from '@fabricorg/adoption-bindings';
|
|
2
3
|
import { UsageContractDocument, UsageContract } from '@fabricorg/gen-capability';
|
|
3
4
|
|
|
@@ -76,6 +77,20 @@ interface AssemblyContractEntry {
|
|
|
76
77
|
namespace: string;
|
|
77
78
|
document: UsageContractDocument;
|
|
78
79
|
}
|
|
80
|
+
/**
|
|
81
|
+
* An adapter the vertical deploys behind a port, with the certification the
|
|
82
|
+
* port's suite issued. Structurally the output of `certifyAdapter`, so a
|
|
83
|
+
* vertical passes that straight in. Without a certification the adapter is a
|
|
84
|
+
* claim, and resolution refuses it for any port a contract requires.
|
|
85
|
+
*/
|
|
86
|
+
interface AssemblyAdapterEntry extends RegisteredAdapter {
|
|
87
|
+
certification?: AdapterCertification;
|
|
88
|
+
}
|
|
89
|
+
/** A port contract in force, so a certification against an older contract is stale. */
|
|
90
|
+
interface AssemblyPortDefinition {
|
|
91
|
+
id: string;
|
|
92
|
+
version: string;
|
|
93
|
+
}
|
|
79
94
|
/**
|
|
80
95
|
* What a vertical assembles: the capabilities it depends on, the component
|
|
81
96
|
* packs it uses, the adoption bindings it declares, and the usage contracts
|
|
@@ -90,6 +105,14 @@ interface AssemblyManifest {
|
|
|
90
105
|
componentPacks: AssemblyComponentPackReference[];
|
|
91
106
|
bindings: AssemblyBindingEntry[];
|
|
92
107
|
contracts: AssemblyContractEntry[];
|
|
108
|
+
/**
|
|
109
|
+
* Adapters deployed behind the ports the contracts require. Declaring the
|
|
110
|
+
* field, even empty, engages the adapter plane: every port a contract
|
|
111
|
+
* requires must then be satisfied by a certified adapter listed here, or
|
|
112
|
+
* the assembly does not resolve. An assembly that does not declare it
|
|
113
|
+
* resolves as before, with port satisfaction as a separate deployment gate.
|
|
114
|
+
*/
|
|
115
|
+
adapters?: AssemblyAdapterEntry[];
|
|
93
116
|
}
|
|
94
117
|
/** A published capability version available for resolution. */
|
|
95
118
|
interface AvailableCapability {
|
|
@@ -148,6 +171,27 @@ interface ResolvedBinding {
|
|
|
148
171
|
valid: boolean;
|
|
149
172
|
findings: BindingFinding[];
|
|
150
173
|
}
|
|
174
|
+
/**
|
|
175
|
+
* A certified adapter locked into the assembly. The certification itself is
|
|
176
|
+
* summarised by digest so the lockfile stays small and the evidence stays
|
|
177
|
+
* verifiable: the same certification produces the same digest.
|
|
178
|
+
*/
|
|
179
|
+
interface ResolvedAdapter {
|
|
180
|
+
port: string;
|
|
181
|
+
vendor: string;
|
|
182
|
+
version?: string;
|
|
183
|
+
standard?: {
|
|
184
|
+
name: string;
|
|
185
|
+
version?: string;
|
|
186
|
+
};
|
|
187
|
+
/** Whether a certification was declared. An uncertified adapter is recorded so the lockfile says what is deployed, and refused wherever a contract requires its port. */
|
|
188
|
+
certified: boolean;
|
|
189
|
+
/** Port contract version the adapter was certified against. */
|
|
190
|
+
portVersion?: string;
|
|
191
|
+
certifiedAt?: string;
|
|
192
|
+
/** SHA-256 of the whole canonical certification record. */
|
|
193
|
+
certificationDigest?: string;
|
|
194
|
+
}
|
|
151
195
|
/**
|
|
152
196
|
* The deterministic, resolved output of assembling a vertical. Every version
|
|
153
197
|
* is locked, every binding is validated, and the whole document carries a
|
|
@@ -161,8 +205,10 @@ interface AssemblyLockfile {
|
|
|
161
205
|
capabilities: ResolvedCapability[];
|
|
162
206
|
componentPacks: ResolvedComponentPack[];
|
|
163
207
|
bindings: ResolvedBinding[];
|
|
208
|
+
/** Present when the manifest declared adapters; covered by the digest. */
|
|
209
|
+
adapters?: ResolvedAdapter[];
|
|
164
210
|
}
|
|
165
|
-
type AssemblyFindingCode = "duplicate_capability" | "duplicate_component_pack" | "unresolved_capability" | "unresolved_component_pack" | "invalid_range" | "unknown_contract_for_binding" | "contract_namespace_mismatch" | "contract_manifest_digest_mismatch" | "binding_validation_failed" | "invalid_contract" | "invalid_component_pack" | "missing_component_pack_manifest" | "component_pack_artifact_digest_mismatch" | "duplicate_contract";
|
|
211
|
+
type AssemblyFindingCode = "duplicate_capability" | "duplicate_component_pack" | "unresolved_capability" | "unresolved_component_pack" | "invalid_range" | "unknown_contract_for_binding" | "contract_namespace_mismatch" | "contract_manifest_digest_mismatch" | "binding_validation_failed" | "invalid_contract" | "invalid_component_pack" | "missing_component_pack_manifest" | "component_pack_artifact_digest_mismatch" | "duplicate_contract" | "duplicate_adapter" | "unsatisfied_port" | "standard_not_spoken" | "version_mismatch" | "uncertified_adapter" | "stale_certification";
|
|
166
212
|
interface AssemblyFinding {
|
|
167
213
|
code: AssemblyFindingCode;
|
|
168
214
|
path: string;
|
|
@@ -184,6 +230,8 @@ declare function computeUsageContractDocumentDigest(document: UsageContractDocum
|
|
|
184
230
|
* digest it was validated against.
|
|
185
231
|
*/
|
|
186
232
|
declare function computeAssemblyLockfileDigest(lockfile: Omit<AssemblyLockfile, "assemblyDigest">): string;
|
|
233
|
+
/** The digest a certification record locks under: the whole record, keys sorted, checks sorted. */
|
|
234
|
+
declare function computeAdapterCertificationDigest(certification: AdapterCertification): string;
|
|
187
235
|
/**
|
|
188
236
|
* Structural validation of an assembly manifest. Checks the document's own
|
|
189
237
|
* shape; does not resolve ranges or validate contracts/bindings.
|
|
@@ -202,7 +250,15 @@ declare function assertAssemblyLockfile(value: unknown, path?: string): asserts
|
|
|
202
250
|
* Verify that the capability artifacts loaded by a runtime are exactly those
|
|
203
251
|
* approved by an integrity-checked assembly lockfile.
|
|
204
252
|
*/
|
|
205
|
-
declare function assertAssemblyRuntimeCompatible(lockfile: AssemblyLockfile, loadedCapabilities: readonly AvailableCapability[]
|
|
253
|
+
declare function assertAssemblyRuntimeCompatible(lockfile: AssemblyLockfile, loadedCapabilities: readonly AvailableCapability[],
|
|
254
|
+
/**
|
|
255
|
+
* The adapters this runtime registered, when the lockfile carries an
|
|
256
|
+
* adapter plane. Every locked certified adapter must be registered under
|
|
257
|
+
* the same port and vendor with the same certification, and a registered
|
|
258
|
+
* adapter the lockfile does not know is refused, so what runs behind a
|
|
259
|
+
* port is what was approved.
|
|
260
|
+
*/
|
|
261
|
+
registeredAdapters?: readonly AssemblyAdapterEntry[]): void;
|
|
206
262
|
interface ResolveAssemblyInput {
|
|
207
263
|
manifest: AssemblyManifest;
|
|
208
264
|
/** Published capability versions available for resolution. */
|
|
@@ -215,6 +271,12 @@ interface ResolveAssemblyInput {
|
|
|
215
271
|
coreComponents?: readonly string[];
|
|
216
272
|
/** Contracts available for cross-capability fragment resolution. */
|
|
217
273
|
availableContracts?: UsageContract[];
|
|
274
|
+
/**
|
|
275
|
+
* Port contracts in force. With them, a certification issued against an
|
|
276
|
+
* older port version is `stale_certification`; without them, only its
|
|
277
|
+
* presence is checked.
|
|
278
|
+
*/
|
|
279
|
+
portDefinitions?: readonly AssemblyPortDefinition[];
|
|
218
280
|
}
|
|
219
281
|
/**
|
|
220
282
|
* Resolve an assembly manifest into a deterministic lockfile. This is the
|
|
@@ -245,4 +307,4 @@ declare function validateAssemblyBindings(input: {
|
|
|
245
307
|
availableContracts?: UsageContract[];
|
|
246
308
|
}): AssemblyResult;
|
|
247
309
|
|
|
248
|
-
export { ASSEMBLY_LOCKFILE_FORMAT_VERSION, ASSEMBLY_MANIFEST_FORMAT_VERSION, type AssemblyBindingEntry, type AssemblyCapabilityReference, type AssemblyComponentPackReference, type AssemblyContractEntry, type AssemblyFinding, type AssemblyFindingCode, type AssemblyLockfile, type AssemblyManifest, type AssemblyResult, type AvailableCapability, type AvailableComponentPack, type ComponentPackRemote, type ResolveAssemblyInput, type ResolvedBinding, type ResolvedCapability, type ResolvedComponentPack, type SemverRange, assertAssemblyLockfile, assertAssemblyManifest, assertAssemblyResolved, assertAssemblyRuntimeCompatible, assertComponentPackManifest, computeAssemblyLockfileDigest, computeUsageContractDocumentDigest, parseSemverRange, resolveAssembly, resolveVersionRange, satisfiesRange, validateAssemblyBindings };
|
|
310
|
+
export { ASSEMBLY_LOCKFILE_FORMAT_VERSION, ASSEMBLY_MANIFEST_FORMAT_VERSION, type AssemblyAdapterEntry, type AssemblyBindingEntry, type AssemblyCapabilityReference, type AssemblyComponentPackReference, type AssemblyContractEntry, type AssemblyFinding, type AssemblyFindingCode, type AssemblyLockfile, type AssemblyManifest, type AssemblyPortDefinition, type AssemblyResult, type AvailableCapability, type AvailableComponentPack, type ComponentPackRemote, type ResolveAssemblyInput, type ResolvedAdapter, type ResolvedBinding, type ResolvedCapability, type ResolvedComponentPack, type SemverRange, assertAssemblyLockfile, assertAssemblyManifest, assertAssemblyResolved, assertAssemblyRuntimeCompatible, assertComponentPackManifest, computeAdapterCertificationDigest, computeAssemblyLockfileDigest, computeUsageContractDocumentDigest, parseSemverRange, resolveAssembly, resolveVersionRange, satisfiesRange, validateAssemblyBindings };
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// index.ts
|
|
2
2
|
import { createHash } from "crypto";
|
|
3
|
+
import { PORT_ID, validatePortRequirements } from "@fabricorg/ports";
|
|
3
4
|
import {
|
|
4
5
|
assertAdoptionBinding,
|
|
5
6
|
assertBindingValid,
|
|
@@ -203,9 +204,15 @@ function lockfileDigestBody(lockfile) {
|
|
|
203
204
|
application: lockfile.application,
|
|
204
205
|
capabilities: lockfile.capabilities,
|
|
205
206
|
componentPacks: lockfile.componentPacks,
|
|
206
|
-
bindings: lockfile.bindings
|
|
207
|
+
bindings: lockfile.bindings,
|
|
208
|
+
// Omitted when absent, so a lockfile from before the adapter plane
|
|
209
|
+
// keeps the digest it was approved with.
|
|
210
|
+
...lockfile.adapters ? { adapters: lockfile.adapters } : {}
|
|
207
211
|
};
|
|
208
212
|
}
|
|
213
|
+
function computeAdapterCertificationDigest(certification) {
|
|
214
|
+
return assemblyDigest({ ...certification, checks: [...certification.checks].sort() });
|
|
215
|
+
}
|
|
209
216
|
function fail(path, message) {
|
|
210
217
|
throw new Error(`Assembly manifest ${path} ${message}.`);
|
|
211
218
|
}
|
|
@@ -251,6 +258,42 @@ function assertAssemblyManifest(value, path = "assembly") {
|
|
|
251
258
|
fail(`${path}.contracts[${index}].document`, error instanceof Error ? error.message : String(error));
|
|
252
259
|
}
|
|
253
260
|
});
|
|
261
|
+
if (manifest.adapters !== void 0) {
|
|
262
|
+
requireArray(manifest.adapters, `${path}.adapters`).forEach((entry, index) => {
|
|
263
|
+
assertAdapterEntry(entry, `${path}.adapters[${index}]`);
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
function assertAdapterEntry(value, path) {
|
|
268
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) fail(path, "must be an object");
|
|
269
|
+
const adapter = value;
|
|
270
|
+
requireString(adapter.port, `${path}.port`);
|
|
271
|
+
requireString(adapter.vendor, `${path}.vendor`);
|
|
272
|
+
if (adapter.version !== void 0) requireString(adapter.version, `${path}.version`);
|
|
273
|
+
if (adapter.standard !== void 0) {
|
|
274
|
+
const standard = adapter.standard;
|
|
275
|
+
if (!standard || typeof standard !== "object" || Array.isArray(standard)) fail(`${path}.standard`, "must be an object");
|
|
276
|
+
requireString(standard.name, `${path}.standard.name`);
|
|
277
|
+
if (standard.version !== void 0) requireString(standard.version, `${path}.standard.version`);
|
|
278
|
+
}
|
|
279
|
+
if (adapter.certification !== void 0) {
|
|
280
|
+
const certification = adapter.certification;
|
|
281
|
+
if (!certification || typeof certification !== "object" || Array.isArray(certification)) fail(`${path}.certification`, "must be an object");
|
|
282
|
+
requireString(certification.portId, `${path}.certification.portId`);
|
|
283
|
+
if (!/^\d+\.\d+\.\d+$/.test(requireString(certification.portVersion, `${path}.certification.portVersion`))) {
|
|
284
|
+
fail(`${path}.certification.portVersion`, "must be a semver version");
|
|
285
|
+
}
|
|
286
|
+
const checks = requireArray(certification.checks, `${path}.certification.checks`);
|
|
287
|
+
if (checks.length === 0 || !checks.every((check) => typeof check === "string" && check.trim())) {
|
|
288
|
+
fail(`${path}.certification.checks`, "must name at least one check");
|
|
289
|
+
}
|
|
290
|
+
if (Number.isNaN(Date.parse(requireString(certification.certifiedAt, `${path}.certification.certifiedAt`)))) {
|
|
291
|
+
fail(`${path}.certification.certifiedAt`, "must be an instant");
|
|
292
|
+
}
|
|
293
|
+
if (certification.portId !== adapter.port) {
|
|
294
|
+
fail(`${path}.certification.portId`, `must match the adapter's port "${String(adapter.port)}"`);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
254
297
|
}
|
|
255
298
|
function assertComponentPackManifest(value, path = "pack") {
|
|
256
299
|
if (!value || typeof value !== "object" || Array.isArray(value)) fail(path, "must be an object");
|
|
@@ -342,19 +385,77 @@ function assertAssemblyLockfile(value, path = "lockfile") {
|
|
|
342
385
|
if (binding?.valid !== true) fail(`${path}.bindings[${index}].valid`, "must be true");
|
|
343
386
|
requireArray(binding?.findings, `${path}.bindings[${index}].findings`);
|
|
344
387
|
}
|
|
388
|
+
let adapters;
|
|
389
|
+
if (lockfile.adapters !== void 0) {
|
|
390
|
+
adapters = requireArray(lockfile.adapters, `${path}.adapters`);
|
|
391
|
+
const seen = /* @__PURE__ */ new Set();
|
|
392
|
+
for (const [index, entry] of adapters.entries()) {
|
|
393
|
+
const adapter = entry;
|
|
394
|
+
const port = requireString(adapter?.port, `${path}.adapters[${index}].port`);
|
|
395
|
+
const vendor = requireString(adapter?.vendor, `${path}.adapters[${index}].vendor`);
|
|
396
|
+
const key = `${port}/${vendor}`;
|
|
397
|
+
if (seen.has(key)) fail(`${path}.adapters[${index}]`, `declares adapter "${key}" a second time`);
|
|
398
|
+
seen.add(key);
|
|
399
|
+
if (adapter?.version !== void 0) requireString(adapter.version, `${path}.adapters[${index}].version`);
|
|
400
|
+
if (adapter?.standard !== void 0) {
|
|
401
|
+
const standard = adapter.standard;
|
|
402
|
+
if (!standard || typeof standard !== "object" || Array.isArray(standard)) fail(`${path}.adapters[${index}].standard`, "must be an object");
|
|
403
|
+
requireString(standard.name, `${path}.adapters[${index}].standard.name`);
|
|
404
|
+
if (standard.version !== void 0) requireString(standard.version, `${path}.adapters[${index}].standard.version`);
|
|
405
|
+
}
|
|
406
|
+
if (typeof adapter?.certified !== "boolean") fail(`${path}.adapters[${index}].certified`, "must be a boolean");
|
|
407
|
+
if (adapter.certified) {
|
|
408
|
+
if (!/^\d+\.\d+\.\d+$/.test(requireString(adapter?.portVersion, `${path}.adapters[${index}].portVersion`))) {
|
|
409
|
+
fail(`${path}.adapters[${index}].portVersion`, "must be a semver version");
|
|
410
|
+
}
|
|
411
|
+
if (Number.isNaN(Date.parse(requireString(adapter?.certifiedAt, `${path}.adapters[${index}].certifiedAt`)))) {
|
|
412
|
+
fail(`${path}.adapters[${index}].certifiedAt`, "must be an instant");
|
|
413
|
+
}
|
|
414
|
+
if (!/^[0-9a-f]{64}$/.test(requireString(adapter?.certificationDigest, `${path}.adapters[${index}].certificationDigest`))) {
|
|
415
|
+
fail(`${path}.adapters[${index}].certificationDigest`, "must be a lowercase SHA-256 digest");
|
|
416
|
+
}
|
|
417
|
+
} else if (adapter.portVersion !== void 0 || adapter.certifiedAt !== void 0 || adapter.certificationDigest !== void 0) {
|
|
418
|
+
fail(`${path}.adapters[${index}]`, "carries certification fields but is marked uncertified");
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
345
422
|
const expected = assemblyDigest(lockfileDigestBody({
|
|
346
423
|
formatVersion: ASSEMBLY_LOCKFILE_FORMAT_VERSION,
|
|
347
424
|
application: lockfile.application,
|
|
348
425
|
capabilities,
|
|
349
426
|
componentPacks,
|
|
350
|
-
bindings
|
|
427
|
+
bindings,
|
|
428
|
+
...adapters ? { adapters } : {}
|
|
351
429
|
}));
|
|
352
430
|
if (lockfile.assemblyDigest !== expected) {
|
|
353
431
|
fail(`${path}.assemblyDigest`, `does not match resolved lockfile content; expected "${expected}"`);
|
|
354
432
|
}
|
|
355
433
|
}
|
|
356
|
-
function assertAssemblyRuntimeCompatible(lockfile, loadedCapabilities) {
|
|
434
|
+
function assertAssemblyRuntimeCompatible(lockfile, loadedCapabilities, registeredAdapters) {
|
|
357
435
|
assertAssemblyLockfile(lockfile);
|
|
436
|
+
if (lockfile.adapters !== void 0 && registeredAdapters !== void 0) {
|
|
437
|
+
const registeredByKey = new Map(registeredAdapters.map((adapter) => [`${adapter.port}/${adapter.vendor}`, adapter]));
|
|
438
|
+
for (const approved of lockfile.adapters) {
|
|
439
|
+
const key = `${approved.port}/${approved.vendor}`;
|
|
440
|
+
const registered = registeredByKey.get(key);
|
|
441
|
+
if (!registered) throw new Error(`Approved adapter "${key}" is not registered by this runtime.`);
|
|
442
|
+
if (approved.version !== void 0 && registered.version !== approved.version) {
|
|
443
|
+
throw new Error(`Registered adapter "${key}" is version "${registered.version ?? "unversioned"}"; the assembly approved "${approved.version}".`);
|
|
444
|
+
}
|
|
445
|
+
if (approved.certified) {
|
|
446
|
+
const digest = registered.certification ? computeAdapterCertificationDigest(registered.certification) : void 0;
|
|
447
|
+
if (digest !== approved.certificationDigest) {
|
|
448
|
+
throw new Error(`Registered adapter "${key}" does not carry the certification the assembly approved (digest "${approved.certificationDigest}").`);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
for (const registered of registeredAdapters) {
|
|
453
|
+
const key = `${registered.port}/${registered.vendor}`;
|
|
454
|
+
if (!lockfile.adapters.some((approved) => approved.port === registered.port && approved.vendor === registered.vendor)) {
|
|
455
|
+
throw new Error(`Registered adapter "${key}" is not approved by assembly "${lockfile.assemblyDigest}".`);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
}
|
|
358
459
|
const loadedByNamespace = /* @__PURE__ */ new Map();
|
|
359
460
|
for (const loaded of loadedCapabilities) {
|
|
360
461
|
if (loadedByNamespace.has(loaded.namespace)) {
|
|
@@ -559,6 +660,51 @@ function resolveAssembly(input) {
|
|
|
559
660
|
}
|
|
560
661
|
contractsByNamespace.set(entry.namespace, entry.document.capability);
|
|
561
662
|
}
|
|
663
|
+
const resolvedAdapters = [];
|
|
664
|
+
const declaredAdapters = input.manifest.adapters ?? [];
|
|
665
|
+
const adapterKeys = /* @__PURE__ */ new Set();
|
|
666
|
+
for (const adapter of declaredAdapters) {
|
|
667
|
+
const key = `${adapter.port}/${adapter.vendor}`;
|
|
668
|
+
if (adapterKeys.has(key)) {
|
|
669
|
+
add("duplicate_adapter", `assembly.adapters.${key}`, `adapter "${key}" is declared a second time`);
|
|
670
|
+
continue;
|
|
671
|
+
}
|
|
672
|
+
adapterKeys.add(key);
|
|
673
|
+
resolvedAdapters.push({
|
|
674
|
+
port: adapter.port,
|
|
675
|
+
vendor: adapter.vendor,
|
|
676
|
+
...adapter.version === void 0 ? {} : { version: adapter.version },
|
|
677
|
+
...adapter.standard === void 0 ? {} : { standard: adapter.standard },
|
|
678
|
+
certified: adapter.certification !== void 0,
|
|
679
|
+
...adapter.certification === void 0 ? {} : {
|
|
680
|
+
portVersion: adapter.certification.portVersion,
|
|
681
|
+
certifiedAt: adapter.certification.certifiedAt,
|
|
682
|
+
certificationDigest: computeAdapterCertificationDigest(adapter.certification)
|
|
683
|
+
}
|
|
684
|
+
});
|
|
685
|
+
}
|
|
686
|
+
const portDefinitions = (input.portDefinitions ?? []).map((definition, index) => {
|
|
687
|
+
if (typeof definition.id !== "string" || !PORT_ID.test(definition.id)) {
|
|
688
|
+
fail(`portDefinitions[${index}].id`, `must be a namespaced lowercase port id, received ${JSON.stringify(definition.id)}`);
|
|
689
|
+
}
|
|
690
|
+
if (typeof definition.version !== "string" || !/^\d+\.\d+\.\d+$/.test(definition.version)) {
|
|
691
|
+
fail(`portDefinitions[${index}].version`, `must be a semver version, received ${JSON.stringify(definition.version)}`);
|
|
692
|
+
}
|
|
693
|
+
return { id: definition.id, version: definition.version, description: "", checks: () => [] };
|
|
694
|
+
});
|
|
695
|
+
for (const [namespace, capability] of contractsByNamespace) {
|
|
696
|
+
if (input.manifest.adapters === void 0) break;
|
|
697
|
+
if (!capability.requirements?.ports?.length) continue;
|
|
698
|
+
const result = validatePortRequirements({
|
|
699
|
+
capability: { namespace, requirements: { ports: capability.requirements.ports } },
|
|
700
|
+
adapters: declaredAdapters,
|
|
701
|
+
definitions: portDefinitions,
|
|
702
|
+
requireCertification: true
|
|
703
|
+
});
|
|
704
|
+
for (const finding of result.findings) {
|
|
705
|
+
add(finding.code, `assembly.contracts.${namespace}.requirements.ports.${finding.port}`, finding.message);
|
|
706
|
+
}
|
|
707
|
+
}
|
|
562
708
|
const resolvedBindings = [];
|
|
563
709
|
for (const entry of input.manifest.bindings) {
|
|
564
710
|
const contract = contractsByNamespace.get(entry.capability);
|
|
@@ -609,12 +755,17 @@ function resolveAssembly(input) {
|
|
|
609
755
|
const bindings = [...resolvedBindings].sort(
|
|
610
756
|
(a, b) => a.capability < b.capability ? -1 : a.capability > b.capability ? 1 : 0
|
|
611
757
|
);
|
|
758
|
+
const adapters = input.manifest.adapters === void 0 ? void 0 : [...resolvedAdapters].sort((a, b) => {
|
|
759
|
+
const key = (e) => `${e.port}/${e.vendor}`;
|
|
760
|
+
return key(a) < key(b) ? -1 : key(a) > key(b) ? 1 : 0;
|
|
761
|
+
});
|
|
612
762
|
const lockfileBody = {
|
|
613
763
|
formatVersion: ASSEMBLY_LOCKFILE_FORMAT_VERSION,
|
|
614
764
|
application: input.manifest.application,
|
|
615
765
|
capabilities,
|
|
616
766
|
componentPacks,
|
|
617
|
-
bindings
|
|
767
|
+
bindings,
|
|
768
|
+
...adapters ? { adapters } : {}
|
|
618
769
|
};
|
|
619
770
|
const lockfile = {
|
|
620
771
|
formatVersion: ASSEMBLY_LOCKFILE_FORMAT_VERSION,
|
|
@@ -622,7 +773,8 @@ function resolveAssembly(input) {
|
|
|
622
773
|
assemblyDigest: assemblyDigest(lockfileDigestBody(lockfileBody)),
|
|
623
774
|
capabilities,
|
|
624
775
|
componentPacks,
|
|
625
|
-
bindings
|
|
776
|
+
bindings,
|
|
777
|
+
...adapters ? { adapters } : {}
|
|
626
778
|
};
|
|
627
779
|
return {
|
|
628
780
|
lockfile: findings.length === 0 ? lockfile : void 0,
|
|
@@ -697,6 +849,7 @@ export {
|
|
|
697
849
|
assertAssemblyResolved,
|
|
698
850
|
assertAssemblyRuntimeCompatible,
|
|
699
851
|
assertComponentPackManifest,
|
|
852
|
+
computeAdapterCertificationDigest,
|
|
700
853
|
computeAssemblyLockfileDigest,
|
|
701
854
|
computeUsageContractDocumentDigest,
|
|
702
855
|
parseSemverRange,
|