@fabricorg/assembly 0.4.0 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/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[]): void;
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[]): void;
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,