@fabricorg/ports 0.3.0 → 0.4.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 CHANGED
@@ -1,5 +1,132 @@
1
1
  # @fabricorg/ports
2
2
 
3
+ ## 0.4.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 8ce5e06: Derive release grants from what an actor holds, and complete the port catalog.
8
+
9
+ `deriveAuthorizationGrants` turns usage contracts plus an actor's permissions, entitlements and
10
+ feature flags into an `SduiAuthorizationGrants`. A shell that answers "what may this actor see" from
11
+ its own configuration, while grants are hand-maintained elsewhere and enforcement happens in a third
12
+ place, produces three sources that must agree with nothing forcing them to. Deriving them makes
13
+ navigation a projection of the same declarations. It is navigation-shaped rather than enforcement:
14
+ the Host still re-checks authority at execution and `ProjectionHost` still authorizes every query.
15
+ Withheld capabilities are returned with the requirement that was missing, so an absent entry is
16
+ explainable.
17
+
18
+ `ContentPort`, `PaymentsPort`, `SearchPort` and `CustomerRecordPort` join flags, design tokens and
19
+ identity, each with the suite an adapter must pass. The suites encode what each port class actually
20
+ gets wrong: content inventing a default for an unauthored key or carrying a capability reference,
21
+ money expressed as a float or an idempotency key that moves it twice, search hits mistaken for
22
+ authorization rather than pointers a governed read still resolves, and redelivery duplicating a
23
+ customer record. `PaymentOutcome` includes `ambiguous`, which maps onto the platform's existing
24
+ adapter outcomes so an uncertain charge reconciles instead of being retried.
25
+
26
+ - 9a5b18a: Close what a fifth review found, including a regression the previous fix introduced.
27
+
28
+ **Ambiguity fired on legal documents.** Counting every declared view made publishing a view at two
29
+ versions a hard failure, and view versioning is a first-class pattern: the platform and the usage
30
+ contract both key a view on `name@version`. It also failed a release over a capability that release
31
+ never touched. Ambiguity is now reported for _granted_ references only, where a versionless reference
32
+ genuinely cannot pick between two versions, and a contract supplied twice gets its own finding
33
+ instead of making every one of its views look ambiguous.
34
+
35
+ **A failed replay is `unknown`, not `refused`.** `failed` is reachable after adapter steps succeeded
36
+ and the domain transaction committed, so telling an operator the action was definitely refused could
37
+ be as wrong as telling them it was applied. Only policy blocks and validation failures are decided
38
+ before any effect. A replay adapter that returns nothing is surfaced rather than counted as applied,
39
+ which was the one line the previous rewrite left untouched.
40
+
41
+ **`parked` and `expired` carry their records**, and reconciliation carries the invocation id. Bare
42
+ counts left nobody able to say which action needs approving or whose work was dropped, and external
43
+ reconciliation needs the id the type exists to enable.
44
+
45
+ Settled-failure classification gained the superseded-action-version case and dropped two it had
46
+ wrong: a future-dated capture comes good as clocks correct, and a gateway missing its replay window
47
+ is a deployment mistake somebody will fix. Discarding either would destroy work.
48
+
49
+ **`assertPortSuiteHasTeeth` takes a set of broken adapters.** Requiring one stub to trip every check
50
+ was unsatisfiable for any suite with a fixture-liveness guard, since the guard bites on the
51
+ complement of what it guards, and two reference ports could not pass their own suites. Port ids now
52
+ permit a hyphen in the first segment, so a port can be defined for every name a manifest can declare,
53
+ and `certifyAdapter` validates its definition rather than issuing evidence the gate reports as
54
+ missing.
55
+
56
+ - 9b2e55b: Close what a fourth review found: each earlier fix closed one instance of a class and left its
57
+ siblings.
58
+
59
+ **Replay disposition is now exhaustive over `ActionStatus`.** The previous fix enumerated the two
60
+ statuses that looked like refusals, so `failed`, `validation_failed` and `waiting_for_approval` fell
61
+ through and were deleted from the queue and reported to the operator as applied — the same bug the
62
+ fix set out to kill, one layer in. Any returned outcome now means the platform holds the invocation,
63
+ so the capture leaves the queue either way; what differs is whether a person needs to hear about it.
64
+ An undeterminable adapter outcome is reported as `unknown` rather than `refused`, because the effect
65
+ may well have happened. `isSettledReplayFailure` learned the refusals the sibling commit introduced,
66
+ including a capture rejected for age, which can never come good.
67
+
68
+ **A replay without `capturedAt` is refused.** It was reachable only through an untyped cast, so
69
+ omitting it skipped the age check entirely and stamped receipt time into durable capture evidence.
70
+
71
+ **A capability can name a namespaced port.** `definePort` required a dot and the manifest validator
72
+ forbade one, so no real capability could name a defined port and the certification gate was
73
+ unreachable. Certification is also checked structurally rather than by the presence of a property, so
74
+ a hand-written claim no longer satisfies the gate that exists to demand evidence, and a port
75
+ requirement narrows its candidates as each constraint is applied.
76
+
77
+ **`assertPortSuiteHasTeeth` names every vacuous check** rather than returning on the first one that
78
+ bites, and a reference port is actually run through it. The payments control asserts its result, so
79
+ an adapter that never charges no longer certifies. The capability-reference grammar matches the
80
+ platform's own view-name rule instead of being stricter than it, and ambiguity is counted by
81
+ occurrence across views and intents rather than by comparing names within one contract.
82
+
83
+ - 0d1f6f6: Close what a third independent review found, including two defects introduced by the previous fix.
84
+
85
+ **Offline replay counted refusals as successes.** `PlatformHost` reports a refused capture by
86
+ _returning_ `reconciliation_required` with the reason, not by throwing, so the queue took its success
87
+ branch, deleted the capture and reported it applied. The reconciliation path was unreachable. Replay
88
+ now reads the returned outcome. The already-applied branch is removed entirely: a repeated key
89
+ returns the original invocation without complaint, and an idempotency _conflict_ means the key was
90
+ reused for a materially different command, so that capture was never applied and is kept rather than
91
+ discarded.
92
+
93
+ **`deriveAuthorizationGrants` defaulted open and reached the signed artifact.** Requirements are
94
+ optional metadata, so a capability declaring none granted everything to an actor holding nothing.
95
+ Grants are also carried onto a release unexamined and promotion signs a route for each, so a surplus
96
+ grant became a signed route to a capability the screen never renders. Undeclared requirements now
97
+ withhold by default, and `document` scopes the result to references the screen actually binds.
98
+
99
+ Several conformance checks could not fail: a search suite passed an adapter returning nothing, a
100
+ payments suite passed a double-charger that omitted a provider reference, an identity suite certified
101
+ timestamps the scope check then rejected on every request, an MFE probe could be refused for an
102
+ unrelated reason, and a channel check accepted any exception as a refusal. Each now proves what it
103
+ claims. Promotion configuration rejects anything reading like a key rather than two spellings,
104
+ rejects unknown fields, and constrains the application name that decides the default output path.
105
+
106
+ ### Patch Changes
107
+
108
+ - a598fb9: Close the remaining review findings.
109
+
110
+ **One capability-reference grammar.** `sdui-release` accepted any name after the namespace while
111
+ `sdui-composer` rejected `/`, so a view named `orders/reports/daily` produced a release that validated
112
+ and then died at promotion with nothing wrong upstream to point at. Both now share
113
+ `parseCapabilityRef`, tightened to lowercase path segments matching what the platform permits for a
114
+ view name. Two views in one contract that reduce to the same reference are reported as
115
+ `ambiguous_capability_reference` at validation rather than surfacing as an ambiguous route later.
116
+
117
+ **A bounded replay window.** A client-supplied `capturedAt` had no lower bound. Deferring it to the
118
+ Host looked safe and was not: the Host checks staleness only when the capture-authority adapter
119
+ supplies an `expiresAt`, and skips the execution-time expiry check entirely for actions governed at
120
+ capture. A gateway that accepts replay must now declare `maxOfflineReplayAgeMs`, and the instant must
121
+ be RFC 3339 with an explicit offset.
122
+
123
+ **One token-set grammar.** `document.tokenSet` accepted names and versions that `supportedTokenSets`
124
+ rejected, while validation treats the two fields as interchangeable.
125
+
126
+ **Port requirements narrow as they are checked.** A requirement for a standard _and_ a version could
127
+ be satisfied by two different adapters covering one constraint each. `requirement.standard.version`
128
+ was declared and never read.
129
+
3
130
  ## 0.3.0
4
131
 
5
132
  ### Minor Changes
@@ -0,0 +1,457 @@
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
+ // catalog.ts
21
+ var catalog_exports = {};
22
+ __export(catalog_exports, {
23
+ CONTENT_PORT: () => CONTENT_PORT,
24
+ CUSTOMER_RECORD_PORT: () => CUSTOMER_RECORD_PORT,
25
+ DESIGN_TOKENS_PORT: () => DESIGN_TOKENS_PORT,
26
+ FLAGS_PORT: () => FLAGS_PORT,
27
+ IDENTITY_PORT: () => IDENTITY_PORT,
28
+ PAYMENTS_PORT: () => PAYMENTS_PORT,
29
+ REFERENCE_PORTS: () => REFERENCE_PORTS,
30
+ SEARCH_PORT: () => SEARCH_PORT
31
+ });
32
+ module.exports = __toCommonJS(catalog_exports);
33
+
34
+ // index.ts
35
+ var ALIAS = /^\{([^}]+)\}$/;
36
+ var IDENTITY_ACTOR_TYPES = [
37
+ "natural_person",
38
+ "agent",
39
+ "system",
40
+ "service_account",
41
+ "external_system",
42
+ "integration"
43
+ ];
44
+ var RFC3339 = /^\d{4}-\d{2}-\d{2}[Tt]\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:[Zz]|[+-]\d{2}:\d{2})$/;
45
+ var PORT_ID = /^[a-z][a-z0-9-]*(?:\.[a-z][a-z0-9-]*)+$/;
46
+ function definePort(definition) {
47
+ if (!PORT_ID.test(definition.id)) {
48
+ throw new Error(`Port id "${definition.id}" must be namespaced lowercase, for example "acme.docusign".`);
49
+ }
50
+ if (!/^\d+\.\d+\.\d+$/.test(definition.version)) {
51
+ throw new Error(`Port "${definition.id}" version must be a semver version.`);
52
+ }
53
+ if (!definition.description.trim()) {
54
+ throw new Error(`Port "${definition.id}" must describe what an adapter behind it is responsible for.`);
55
+ }
56
+ if (typeof definition.checks !== "function") {
57
+ throw new Error(`Port "${definition.id}" must supply a conformance suite.`);
58
+ }
59
+ return definition;
60
+ }
61
+ var PortContractFailure = class extends Error {
62
+ name = "PortContractFailure";
63
+ };
64
+ function expect(condition, message) {
65
+ if (!condition) throw new PortContractFailure(message);
66
+ }
67
+ function flagsPortChecks() {
68
+ const unknownKey = "fabric.contract.definitely-not-configured";
69
+ return [
70
+ {
71
+ id: "flags.default-on-unknown-key",
72
+ title: "an unknown flag resolves to the supplied default rather than throwing",
73
+ async run(port) {
74
+ const result = await port.resolveBoolean(unknownKey, true);
75
+ expect(result.value === true, `an unknown flag returned ${String(result.value)} instead of the supplied default`);
76
+ expect(
77
+ result.reason === "DEFAULT" || result.reason === "ERROR",
78
+ `an unknown flag resolved with reason "${result.reason}"; a default or error was expected`
79
+ );
80
+ }
81
+ },
82
+ {
83
+ id: "flags.default-is-typed",
84
+ title: "each typed resolver returns its own type",
85
+ async run(port) {
86
+ expect(typeof (await port.resolveBoolean(unknownKey, false)).value === "boolean", "resolveBoolean did not return a boolean");
87
+ expect(typeof (await port.resolveString(unknownKey, "fallback")).value === "string", "resolveString did not return a string");
88
+ expect(typeof (await port.resolveNumber(unknownKey, 42)).value === "number", "resolveNumber did not return a number");
89
+ }
90
+ },
91
+ {
92
+ id: "flags.evaluation-is-pure",
93
+ title: "the same key and context resolve the same way twice",
94
+ async run(port) {
95
+ const context = { targetingKey: "fabric-contract-subject" };
96
+ const first = await port.resolveString(unknownKey, "fallback", context);
97
+ const second = await port.resolveString(unknownKey, "fallback", context);
98
+ expect(first.value === second.value, `the same evaluation returned "${first.value}" then "${second.value}"`);
99
+ }
100
+ },
101
+ {
102
+ id: "flags.tolerates-absent-context",
103
+ title: "evaluation without a context does not throw",
104
+ async run(port) {
105
+ await port.resolveBoolean(unknownKey, false);
106
+ }
107
+ }
108
+ ];
109
+ }
110
+ function identityPortChecks(fixtures) {
111
+ const checks = [
112
+ {
113
+ id: "identity.rejects-garbage",
114
+ title: "an unverifiable credential resolves to null rather than partial claims",
115
+ async run(port) {
116
+ const result = await port.verify("not-a-credential");
117
+ expect(result === null, "an unverifiable credential resolved to claims instead of null");
118
+ }
119
+ },
120
+ {
121
+ id: "identity.rejects-empty",
122
+ title: "an empty credential resolves to null",
123
+ async run(port) {
124
+ expect(await port.verify("") === null, "an empty credential resolved to claims");
125
+ }
126
+ },
127
+ {
128
+ id: "identity.resolves-valid",
129
+ title: "a valid credential resolves to the expected subject and tenant",
130
+ async run(port) {
131
+ const claims = await port.verify(fixtures.validCredential);
132
+ expect(claims !== null, "a valid credential failed to verify");
133
+ expect(claims?.subject === fixtures.expected.subject, `subject was "${claims?.subject}"`);
134
+ expect(claims?.tenantId === fixtures.expected.tenantId, `tenantId was "${claims?.tenantId}"`);
135
+ }
136
+ },
137
+ {
138
+ id: "identity.claims-are-complete",
139
+ title: "resolved claims carry every field the platform derives actor context from",
140
+ async run(port) {
141
+ const claims = await port.verify(fixtures.validCredential);
142
+ expect(claims !== null, "a valid credential failed to verify");
143
+ if (!claims) return;
144
+ expect(IDENTITY_ACTOR_TYPES.includes(claims.actorType), `actorType "${claims.actorType}" is not a known actor kind`);
145
+ expect(typeof claims.issuer === "string" && claims.issuer.length > 0, "claims carry no issuer");
146
+ expect(RFC3339.test(claims.issuedAt), `issuedAt "${claims.issuedAt}" is not RFC 3339 with an explicit offset`);
147
+ expect(RFC3339.test(claims.expiresAt), `expiresAt "${claims.expiresAt}" is not RFC 3339 with an explicit offset`);
148
+ expect(typeof claims.tenantId === "string" && claims.tenantId.length > 0, "claims carry no tenant");
149
+ expect(
150
+ claims.spaceIds === "tenant-wide" || Array.isArray(claims.spaceIds),
151
+ 'spaceIds must be an explicit list or the literal "tenant-wide"'
152
+ );
153
+ }
154
+ },
155
+ {
156
+ id: "identity.carries-no-credential-material",
157
+ title: "resolved claims never echo the credential back",
158
+ async run(port) {
159
+ const claims = await port.verify(fixtures.validCredential);
160
+ expect(claims !== null, "a valid credential failed to verify, so this check proved nothing");
161
+ if (!claims) return;
162
+ const serialized = JSON.stringify(claims);
163
+ expect(
164
+ !serialized.includes(fixtures.validCredential),
165
+ "resolved claims contain the presented credential; claims must carry an opaque reference instead"
166
+ );
167
+ }
168
+ },
169
+ {
170
+ id: "identity.verification-is-stable",
171
+ title: "the same credential resolves the same subject twice",
172
+ async run(port) {
173
+ const first = await port.verify(fixtures.validCredential);
174
+ const second = await port.verify(fixtures.validCredential);
175
+ expect(first?.subject === second?.subject, "the same credential resolved to two different subjects");
176
+ }
177
+ }
178
+ ];
179
+ if (fixtures.expiredCredential !== void 0) {
180
+ checks.push({
181
+ id: "identity.rejects-expired",
182
+ title: "an expired credential resolves to null rather than stale claims",
183
+ async run(port) {
184
+ const expiredCredential = fixtures.expiredCredential;
185
+ expect(await port.verify(expiredCredential) === null, "an expired credential still resolved to claims");
186
+ }
187
+ });
188
+ }
189
+ return checks;
190
+ }
191
+ function contentPortChecks(fixtures) {
192
+ return [
193
+ {
194
+ id: "content.missing-resolves-null",
195
+ title: "an unauthored key resolves to null rather than an invented default",
196
+ async run(port) {
197
+ const result = await port.resolve({ ...fixtures.present, key: fixtures.absentKey });
198
+ expect(result === null, "an unauthored key resolved to content instead of null");
199
+ }
200
+ },
201
+ {
202
+ id: "content.revision-is-stable",
203
+ title: "identical content resolves the same revision twice",
204
+ async run(port) {
205
+ const first = await port.resolve(fixtures.present);
206
+ const second = await port.resolve(fixtures.present);
207
+ expect(first !== null && second !== null, "the fixture key is not authored");
208
+ expect(first?.revision === second?.revision, "the same content reported two revisions, so it cannot be cached");
209
+ }
210
+ },
211
+ {
212
+ id: "content.carries-no-capability-reference",
213
+ title: "authored content never carries a capability reference",
214
+ async run(port) {
215
+ const result = await port.resolve(fixtures.present);
216
+ expect(
217
+ !JSON.stringify(result ?? {}).includes("capability://"),
218
+ "content carried a capability reference; what a screen may reach is decided by a release, not by an author"
219
+ );
220
+ }
221
+ }
222
+ ];
223
+ }
224
+ function paymentsPortChecks(fixtures) {
225
+ return [
226
+ {
227
+ id: "payments.rejects-non-integer-amount",
228
+ title: "a fractional minor-unit amount is refused rather than rounded",
229
+ async run(port) {
230
+ let refused = false;
231
+ try {
232
+ await port.charge({ ...fixtures.request, amount: { ...fixtures.request.amount, amount: 10.5 } });
233
+ } catch {
234
+ refused = true;
235
+ }
236
+ expect(refused, "a fractional minor-unit amount was accepted; money is an integer or it drifts");
237
+ const control = await port.charge({
238
+ ...fixtures.request,
239
+ idempotencyKey: `${fixtures.request.idempotencyKey}:integer-probe`
240
+ });
241
+ expect(
242
+ control.outcome === "succeeded",
243
+ `the fixture charge resolved "${control.outcome}"; a suite run against an adapter that never succeeds certifies nothing`
244
+ );
245
+ }
246
+ },
247
+ {
248
+ id: "payments.requires-idempotency-key",
249
+ title: "a charge without a stable key is refused",
250
+ async run(port) {
251
+ let refused = false;
252
+ try {
253
+ await port.charge({ ...fixtures.request, idempotencyKey: "" });
254
+ } catch {
255
+ refused = true;
256
+ }
257
+ expect(refused, "a charge with no idempotency key was accepted; a retry would move money twice");
258
+ }
259
+ },
260
+ {
261
+ id: "payments.same-key-moves-money-once",
262
+ title: "the same idempotency key resolves to the same provider reference",
263
+ async run(port) {
264
+ const first = await port.charge(fixtures.request);
265
+ const second = await port.charge(fixtures.request);
266
+ expect(
267
+ first.outcome === second.outcome,
268
+ `the same key produced "${first.outcome}" then "${second.outcome}"`
269
+ );
270
+ expect(
271
+ first.outcome !== "succeeded" || first.providerReference !== void 0,
272
+ "a succeeded charge carried no provider reference, so a retry cannot be told from a second charge"
273
+ );
274
+ expect(
275
+ first.providerReference === second.providerReference,
276
+ "the same key produced two provider references, so a retry charged twice"
277
+ );
278
+ }
279
+ },
280
+ {
281
+ id: "payments.outcome-is-declared",
282
+ title: "an outcome is one of the three the platform can act on",
283
+ async run(port) {
284
+ const result = await port.charge(fixtures.request);
285
+ expect(
286
+ result.outcome === "succeeded" || result.outcome === "failed" || result.outcome === "ambiguous",
287
+ `outcome "${result.outcome}" is not one the platform can reconcile`
288
+ );
289
+ }
290
+ }
291
+ ];
292
+ }
293
+ function searchPortChecks(fixtures) {
294
+ return [
295
+ {
296
+ id: "search.fixture-returns-hits",
297
+ title: "the fixture query matches something, so the checks below are not vacuous",
298
+ async run(port) {
299
+ const result = await port.query(fixtures.query);
300
+ expect(
301
+ result.hits.length > 0,
302
+ "the fixture query returned nothing, so every check below passes without exercising the adapter"
303
+ );
304
+ }
305
+ },
306
+ {
307
+ id: "search.respects-limit",
308
+ title: "a result set never exceeds the requested limit",
309
+ async run(port) {
310
+ const result = await port.query({ ...fixtures.query, limit: 1 });
311
+ expect(result.hits.length <= 1, `returned ${result.hits.length} hits for a limit of 1`);
312
+ }
313
+ },
314
+ {
315
+ id: "search.returns-identifiers-not-records",
316
+ title: "a hit carries an identifier a governed read can resolve, not the record",
317
+ async run(port) {
318
+ const result = await port.query(fixtures.query);
319
+ for (const hit of result.hits) {
320
+ expect(typeof hit.id === "string" && hit.id.length > 0, "a hit carried no identifier");
321
+ expect(
322
+ Object.keys(hit).every((key) => key === "id" || key === "score"),
323
+ "a hit carried record fields; search results are pointers, and reading one still goes through ProjectionHost"
324
+ );
325
+ }
326
+ }
327
+ }
328
+ ];
329
+ }
330
+ function customerRecordPortChecks(fixtures) {
331
+ return [
332
+ {
333
+ // This proves redelivery does not *fail*. Whether it duplicates is not
334
+ // observable through this port, which has no read side by design, so
335
+ // the suite says what it can and does not imply more.
336
+ id: "crm.redelivery-does-not-fail",
337
+ title: "recording the same event twice is not an error",
338
+ async run(port) {
339
+ await port.record(fixtures.event);
340
+ await port.record(fixtures.event);
341
+ }
342
+ },
343
+ {
344
+ id: "crm.rejects-unidentified-event",
345
+ title: "an event with no stable id is refused",
346
+ async run(port) {
347
+ let refused = false;
348
+ try {
349
+ await port.record({ ...fixtures.event, eventId: "" });
350
+ } catch {
351
+ refused = true;
352
+ }
353
+ expect(refused, "an event with no id was accepted; redelivery would duplicate the record");
354
+ }
355
+ }
356
+ ];
357
+ }
358
+ function designTokensPortChecks(theme) {
359
+ return [
360
+ {
361
+ id: "tokens.theme-resolves",
362
+ title: "a known theme resolves to at least one token",
363
+ async run(port) {
364
+ const tokens = await port.resolve(theme);
365
+ expect(Array.isArray(tokens) && tokens.length > 0, `theme "${theme}" resolved to no tokens`);
366
+ }
367
+ },
368
+ {
369
+ id: "tokens.no-unresolved-aliases",
370
+ title: "no resolved token still carries an alias",
371
+ async run(port) {
372
+ for (const token of await port.resolve(theme)) {
373
+ expect(
374
+ typeof token.value !== "string" || !ALIAS.test(token.value.trim()),
375
+ `token "${token.name}" resolved to the unfollowed alias ${String(token.value)}`
376
+ );
377
+ }
378
+ }
379
+ },
380
+ {
381
+ id: "tokens.names-are-unique",
382
+ title: "token names are unique within a theme",
383
+ async run(port) {
384
+ const names = (await port.resolve(theme)).map((token) => token.name);
385
+ expect(new Set(names).size === names.length, "the theme resolved duplicate token names");
386
+ }
387
+ }
388
+ ];
389
+ }
390
+
391
+ // catalog.ts
392
+ var FLAGS_PORT = definePort({
393
+ id: "fabric.flags",
394
+ version: "1.0.0",
395
+ standard: { name: "OpenFeature" },
396
+ description: "Resolves a typed flag for an evaluation context, defaulting rather than throwing.",
397
+ checks: () => flagsPortChecks()
398
+ });
399
+ var DESIGN_TOKENS_PORT = definePort({
400
+ id: "fabric.design-tokens",
401
+ version: "1.0.0",
402
+ standard: { name: "W3C DTCG" },
403
+ description: "Resolves a DTCG token document into flat, renderer-neutral values.",
404
+ checks: (fixtures) => designTokensPortChecks(fixtures)
405
+ });
406
+ var IDENTITY_PORT = definePort({
407
+ id: "fabric.identity",
408
+ version: "1.0.0",
409
+ standard: { name: "OAuth 2.0 / OIDC" },
410
+ description: "Verifies a presented credential into actor claims, or resolves it to null.",
411
+ checks: (fixtures) => identityPortChecks(fixtures)
412
+ });
413
+ var CONTENT_PORT = definePort({
414
+ id: "fabric.content",
415
+ version: "1.0.0",
416
+ description: "Resolves authored copy and layout, never authority.",
417
+ checks: (fixtures) => contentPortChecks(fixtures)
418
+ });
419
+ var PAYMENTS_PORT = definePort({
420
+ id: "fabric.payments",
421
+ version: "1.0.0",
422
+ description: "Moves money once per idempotency key, and admits when the outcome is unknown.",
423
+ checks: (fixtures) => paymentsPortChecks(fixtures)
424
+ });
425
+ var SEARCH_PORT = definePort({
426
+ id: "fabric.search",
427
+ version: "1.0.0",
428
+ description: "Returns identifiers a governed read can resolve, never records.",
429
+ checks: (fixtures) => searchPortChecks(fixtures)
430
+ });
431
+ var CUSTOMER_RECORD_PORT = definePort({
432
+ id: "fabric.customer-record",
433
+ version: "1.0.0",
434
+ description: "Records outbound events idempotently. Told, never asked.",
435
+ checks: (fixtures) => customerRecordPortChecks(fixtures)
436
+ });
437
+ var REFERENCE_PORTS = [
438
+ FLAGS_PORT,
439
+ DESIGN_TOKENS_PORT,
440
+ IDENTITY_PORT,
441
+ CONTENT_PORT,
442
+ PAYMENTS_PORT,
443
+ SEARCH_PORT,
444
+ CUSTOMER_RECORD_PORT
445
+ ];
446
+ // Annotate the CommonJS export names for ESM import in node:
447
+ 0 && (module.exports = {
448
+ CONTENT_PORT,
449
+ CUSTOMER_RECORD_PORT,
450
+ DESIGN_TOKENS_PORT,
451
+ FLAGS_PORT,
452
+ IDENTITY_PORT,
453
+ PAYMENTS_PORT,
454
+ REFERENCE_PORTS,
455
+ SEARCH_PORT
456
+ });
457
+ //# sourceMappingURL=catalog.cjs.map