@mpgd/platform 0.19.0 → 0.21.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.
@@ -1,6 +1,8 @@
1
- import { type ActiveSubscription, type CommerceBillingAuthority, type CommerceCapabilities, type CommerceErrorCode, type CommerceErrorInfo, type CommerceEvent, type CommerceLogicalProductId, type CommerceProvider, type CommercePurchaseState, type CreditBalance, type CreditDebit, type CreditEntitlement, type CreditLot, type CreditLotSpendScope, type CreditMembershipTerms, type CreditOffer, type CreditSpendInput, type CreditSpendResult, type CreditWalletClient, type FetchProductsInput, type PurchaseKind, type RequestPurchaseInput, type RequestPurchaseResult, type StoreId, type StoreProduct, type StorePurchase } from './commerce.js';
2
- import type { CommerceConformanceCatalogEntry, CommerceConformanceErrorFixture, CommerceConformanceFinishResultFixture, CommerceConformanceLotFixture, CommerceConformanceProfile, CommerceConformancePurchaseFixture, CommerceConformanceRequestResultFixture, CommerceConformanceSubscriptionEventType, CommerceConformanceVector, CommerceConformanceVerifyDecision } from './commerce-vectors.js';
1
+ import { type ActiveSubscription, type CommerceBillingAuthority, type CommerceCapabilities, type CommerceErrorCode, type CommerceErrorInfo, type CommerceEvent, type CommerceEvidenceAuthority, type CommerceFinishOwner, type CommerceLogicalProductId, type CommerceProvider, type CommercePurchaseState, type CreditBalance, type CreditEntitlement, type CreditLot, type CreditLotSpendScope, type CreditOffer, type CreditSpendInput, type CreditSpendResult, type CreditWalletClient, type CreditWalletSubscribeInput, type FetchProductsInput, type PurchaseKind, type RequestPurchaseInput, type RequestPurchaseResult, type StoreId, type StoreProduct, type StorePurchase } from './commerce.js';
2
+ import type { CommerceConformanceCatalogEntry, CommerceConformanceErrorFixture, CommerceConformanceExpectation, CommerceConformanceFinishResultFixture, CommerceConformanceLotFixture, CommerceConformanceProfile, CommerceConformancePurchaseFixture, CommerceConformanceRequestResultFixture, CommerceConformanceVector, CommerceConformanceVerifyDecision } from './commerce-vectors.js';
3
+ import { type CreditMembershipStartInput, type CreditMembershipStartResult, type CreditScheduledDebit, type CreditSubscriptionEvent } from './commerce-wallet.js';
3
4
  import type { PlatformEvidenceEnvelope } from './index.js';
5
+ export { addIsoDuration, compareCreditLotSpendOrder, deriveCreditLotSpendScope, normalizeIsoTimestamp, } from './commerce-wallet.js';
4
6
  /**
5
7
  * Commerce v2 conformance runner (RFC "Conformance vectors") with the in-memory
6
8
  * reference pieces every vector needs: a fake clock, a fake store with its
@@ -12,7 +14,11 @@ import type { PlatformEvidenceEnvelope } from './index.js';
12
14
  *
13
15
  * Store (B-phase) providers run the same vectors by supplying
14
16
  * `createProvider` and translating the store actor steps into their own fake
15
- * SDK; the reconciler, journal, backend, and matcher stay shared.
17
+ * SDK; the reconciler, journal, backend, and matcher stay shared. A vector
18
+ * flagged `profile.adaptive` then matches expectations derived from the
19
+ * provider's declared capabilities rather than its fixture profile; see
20
+ * `deriveAdaptiveCommerceConformanceExpectation` and
21
+ * `commerceConformanceAdaptiveSkipReason`.
16
22
  */
17
23
  export declare const commerceConformanceDefaultPlayerId = "player-1";
18
24
  export declare const commerceConformanceDefaultTitleId = "title-a";
@@ -27,10 +33,6 @@ export interface CommerceConformanceClock {
27
33
  set(at: string): void;
28
34
  }
29
35
  export declare function createCommerceConformanceClock(initial?: string): CommerceConformanceClock;
30
- /** Canonical `YYYY-MM-DDTHH:mm:ssZ`; milliseconds are kept only when non-zero. */
31
- export declare function normalizeIsoTimestamp(input: string): string;
32
- /** Adds an ISO-8601 duration (`P30D`, `P1M`, `PT12H`, ...) to a timestamp, in UTC calendar units. */
33
- export declare function addIsoDuration(at: string, duration: string): string;
34
36
  /** Capabilities of a vector profile: omitted blocks default to all supported and `available`. */
35
37
  export declare function createCommerceConformanceCapabilities(profile: CommerceConformanceProfile): CommerceCapabilities;
36
38
  export interface CommerceConformanceFixtureContext {
@@ -72,6 +74,14 @@ export interface CommerceConformanceStoreActor {
72
74
  readonly transactionId: string;
73
75
  readonly ledgerEntryId: string;
74
76
  }): void | Promise<void>;
77
+ /**
78
+ * The `token` this store reports for a fixture purchase when it is not the
79
+ * fixture's own: a host-order store reports the transaction id (RFC "Client
80
+ * contract"). An adaptive run replays the fixture purchases with this token
81
+ * into the reference store, so the derived verify requests carry the token
82
+ * the provider under test reports. Absent, the fixture token is used.
83
+ */
84
+ tokenOf?(purchase: CommerceConformancePurchaseFixture): string;
75
85
  }
76
86
  export interface CommerceConformanceStore extends CommerceConformanceStoreActor {
77
87
  /** The `CommerceProvider` over this fake store. */
@@ -163,12 +173,24 @@ export interface CreateCommerceConformanceBackendInput {
163
173
  readonly ledgerEntryId: string;
164
174
  readonly logicalId: CommerceLogicalProductId;
165
175
  }) => void | Promise<void>;
176
+ /**
177
+ * When it returns true, a verify request that replays a key this backend
178
+ * already granted is a finalization re-read (the reference reconciler under a
179
+ * server-owned finish): it is answered `{ granted, ledgerEntryId,
180
+ * finalization: 'completed', alreadyProcessed: true }` from the backend's own
181
+ * grant state and runs the finalizer, consuming no queued decision. Read on
182
+ * every call, so the runner can decide it once the provider's capabilities
183
+ * are known.
184
+ */
185
+ readonly finalizeOnReread?: () => boolean;
166
186
  }
167
187
  /**
168
188
  * Fake backend: every verify call consumes the next `queueVerify` decision in
169
189
  * order and is logged; an unqueued verify is an unscripted operation. A
170
190
  * `granted` decision makes sure the ledger holds the entry, so a replayed
171
- * grant and a host-pushed grant never create a second entry.
191
+ * grant and a host-pushed grant never create a second entry. With
192
+ * `finalizeOnReread`, a replayed granted key is answered as finalized from
193
+ * the backend's own state.
172
194
  */
173
195
  export declare function createCommerceConformanceBackend(input?: CreateCommerceConformanceBackendInput): CommerceConformanceBackend;
174
196
  export interface CommerceJournalRecord {
@@ -246,33 +268,11 @@ export interface CreateCommerceConformanceRuntimeInput {
246
268
  * `granted`, and only a ledger revocation moves it to `revoked`.
247
269
  */
248
270
  export declare function createCommerceConformanceRuntime(input: CreateCommerceConformanceRuntimeInput): CommerceConformanceRuntime;
249
- export interface CommerceScheduledDebit extends CreditDebit {
250
- readonly idempotencyKey: string;
251
- }
252
- export interface CommerceConformanceSubscriptionEvent {
253
- /** Stable id `<subscriptionId>:<periodIndex>:<type>`, so redelivery is idempotent. */
254
- readonly eventId: string;
255
- readonly type: CommerceConformanceSubscriptionEventType;
256
- readonly subscriptionId: string;
257
- readonly logicalId: CommerceLogicalProductId;
258
- readonly periodIndex: number;
259
- readonly expiresAt: string;
260
- readonly reason?: CommerceErrorCode;
261
- }
262
- export interface CommerceConformanceMembershipStart {
263
- readonly subscriptionId: string;
264
- readonly titleId: string;
265
- readonly logicalId: CommerceLogicalProductId;
266
- readonly terms: CreditMembershipTerms;
267
- }
268
- export type CommerceConformanceMembershipResult = {
269
- readonly status: 'started';
270
- readonly subscription: ActiveSubscription;
271
- readonly entitlementId: string;
272
- } | {
273
- readonly status: 'rejected';
274
- readonly error: CommerceErrorInfo;
275
- };
271
+ export type CommerceScheduledDebit = CreditScheduledDebit;
272
+ /** Stable id `<subscriptionId>:<periodIndex>:<type>`, so redelivery is idempotent. */
273
+ export type CommerceConformanceSubscriptionEvent = CreditSubscriptionEvent;
274
+ export type CommerceConformanceMembershipStart = CreditMembershipStartInput;
275
+ export type CommerceConformanceMembershipResult = CreditMembershipStartResult;
276
276
  /** The wallet actor of a vector: wallet steps plus wallet observations. */
277
277
  export interface CommerceConformanceWalletActor {
278
278
  readonly lots: readonly CreditLot[];
@@ -297,8 +297,14 @@ export interface CommerceConformanceWalletActor {
297
297
  }
298
298
  export interface CommerceConformanceWallet extends CreditWalletClient, CommerceConformanceWalletActor {
299
299
  readonly entitlements: readonly CreditEntitlement[];
300
+ /** Offers the wallet prices spends against; the provider registers the vector catalog when empty. */
301
+ readonly offers: readonly CreditOffer[];
302
+ addOffers(offers: readonly CreditOffer[]): void;
300
303
  startMembership(input: CommerceConformanceMembershipStart): Promise<CommerceConformanceMembershipResult>;
301
- subscribe(listener: (event: CommerceEvent) => void): () => void;
304
+ getActiveSubscriptions(input?: {
305
+ readonly titleId?: string;
306
+ }): Promise<readonly ActiveSubscription[]>;
307
+ subscribe(listener: (event: CommerceEvent) => void, input?: CreditWalletSubscribeInput): () => void;
302
308
  }
303
309
  export interface CreateCommerceConformanceWalletInput {
304
310
  readonly store: StoreId;
@@ -308,18 +314,12 @@ export interface CreateCommerceConformanceWalletInput {
308
314
  readonly offers?: readonly CreditOffer[];
309
315
  }
310
316
  /**
311
- * Reference in-memory wallet implementing the RFC "Credit lots" and "Billing
312
- * authority and memberships" rules: lot ids equal ledger entry ids, spend
313
- * scope derives from the lot origin, spends consume eligible lots in the
314
- * fixed order, are idempotent by key, and debit nothing on
315
- * `INSUFFICIENT_BALANCE`; memberships debit `membership:<id>:<period>` per
316
- * paid period and lapse at period end when the eligible balance runs out.
317
+ * The reference wallet (`createInMemoryCreditWallet`) on the vector clock,
318
+ * with the actor surface: fixture lots credit through `creditFromGrant`, the
319
+ * backend finalizer redeems through `finalize`, and owner-side mistakes in a
320
+ * vector surface as script errors.
317
321
  */
318
322
  export declare function createCommerceConformanceWallet(input: CreateCommerceConformanceWalletInput): CommerceConformanceWallet;
319
- /** Store lots are title-scoped, off-store lots cross-title, promotion lots as issued (title by default). */
320
- export declare function deriveCreditLotSpendScope(origin: CreditLot['origin'], explicit?: CreditLotSpendScope): CreditLotSpendScope;
321
- /** RFC spend order: earliest `expiresAt` (none last), `title` before `cross-title`, oldest `createdAt`, then `lotId`. */
322
- export declare function compareCreditLotSpendOrder(left: CreditLot, right: CreditLot): number;
323
323
  export interface CreateCommerceConformanceWalletProviderInput {
324
324
  readonly wallet: CommerceConformanceWallet;
325
325
  readonly profile: CommerceConformanceProfile;
@@ -330,13 +330,12 @@ export interface CreateCommerceConformanceWalletProviderInput {
330
330
  readonly clock: CommerceConformanceClock;
331
331
  }
332
332
  /**
333
- * `CommerceProvider` over the reference wallet: `fetchProducts` lists the
334
- * wallet's offers (catalog-derived offers only when the wallet lists none),
335
- * `requestPurchase` spends the offer price times the requested quantity
336
- * (outcome `purchased`, `settlement` set, evidence adapter-settled),
337
- * `getAvailablePurchases` lists unredeemed entitlements, and finishing is the
338
- * backend's `redeem`. Memberships exist only where the billing authority is
339
- * `host`.
333
+ * `createCreditCommerceProvider` over the reference wallet with the vector
334
+ * profile as its capability block. A wallet that lists no offers has the
335
+ * vector catalog registered as its offers first (a fixture price of one unit
336
+ * of the first wallet currency for entries without membership terms), so the
337
+ * wallet prices every spend and starts memberships itself. Memberships exist
338
+ * only where the billing authority is `host`.
340
339
  */
341
340
  export declare function createCommerceConformanceWalletProvider(input: CreateCommerceConformanceWalletProviderInput): CommerceProvider;
342
341
  export interface CommerceConformanceHarness {
@@ -364,10 +363,33 @@ export interface CommerceConformanceFailure {
364
363
  readonly expected: unknown;
365
364
  readonly actual: unknown;
366
365
  }
366
+ export type CommerceConformanceVectorStatus = 'passed' | 'failed' | 'skipped';
367
+ /**
368
+ * `CommerceConformanceExpectation` as the matcher consumes it: a derived
369
+ * `finishCalls` list carries the store actor's own records, whose `result` may
370
+ * also be `unfinished`.
371
+ */
372
+ export interface CommerceConformanceResolvedExpectation extends Omit<CommerceConformanceExpectation, 'finishCalls'> {
373
+ readonly finishCalls?: readonly CommerceConformanceFinishCall[];
374
+ }
375
+ /** How an adaptive vector's expectations were derived for the provider under test. */
376
+ export interface CommerceConformanceAdaptation {
377
+ /** The provider's store id; it replaces the fixture store id in derived keys and verify requests. */
378
+ readonly store: StoreId;
379
+ readonly finish: CommerceFinishOwner;
380
+ readonly evidence: CommerceEvidenceAuthority;
381
+ /** The expectation the vector was matched against. */
382
+ readonly expect: CommerceConformanceResolvedExpectation;
383
+ }
367
384
  export interface CommerceConformanceVectorResult {
368
385
  readonly vector: string;
386
+ /** `skipped` vectors did not run; `passed` is false and `reason` says why. */
387
+ readonly status: CommerceConformanceVectorStatus;
369
388
  readonly passed: boolean;
370
389
  readonly failures: readonly CommerceConformanceFailure[];
390
+ readonly reason?: string;
391
+ /** Present when the vector ran with adaptive expectations. */
392
+ readonly adaptive?: CommerceConformanceAdaptation;
371
393
  }
372
394
  export declare function createCommerceConformanceHarness(context: CommerceConformanceContext): CommerceConformanceHarness;
373
395
  /**
@@ -375,9 +397,94 @@ export declare function createCommerceConformanceHarness(context: CommerceConfor
375
397
  * each, then matches `expect`. Matching compares only the listed fields; arrays
376
398
  * compare in order and by length; a listed `journal` is the whole journal.
377
399
  * Any unscripted operation fails the vector at the step that needed it.
400
+ *
401
+ * With `options.createProvider`, a vector flagged `profile.adaptive` is first
402
+ * checked against the provider's declared capabilities
403
+ * (`commerceConformanceAdaptiveSkipReason`) and then matched against
404
+ * expectations derived from a reference run under those capabilities
405
+ * (`deriveAdaptiveCommerceConformanceExpectation`). Duplicate detection needs
406
+ * the whole run set, so it applies through `runCommerceConformanceVectors`.
378
407
  */
379
408
  export declare function runCommerceConformanceVector(vector: CommerceConformanceVector, options?: CommerceConformanceRunOptions): Promise<CommerceConformanceVectorResult>;
380
409
  export declare function runCommerceConformanceVectors(vectors: readonly CommerceConformanceVector[], options?: CommerceConformanceRunOptions): Promise<readonly CommerceConformanceVectorResult[]>;
410
+ export declare function isAdaptiveCommerceConformanceVector(vector: CommerceConformanceVector): boolean;
411
+ /**
412
+ * Why an adaptive vector does not apply to a provider with `capabilities`, or
413
+ * `undefined` when it runs. `set` is the whole run (for duplicate detection).
414
+ * The rules:
415
+ *
416
+ * - a `game.requestPurchase` of a product kind the provider does not sell
417
+ * (`purchase.<kind>` false, by the vector catalog), or with a quantity above
418
+ * one without `purchase.quantity`, cannot open the store UI;
419
+ * - a store-side `pending` purchase needs `lifecycle.pending`;
420
+ * - a store `error` event needs `lifecycle.events`;
421
+ * - without `lifecycle.events`, a store `complete` or `update` reaches the
422
+ * reconciler only by polling, so the next `runtime` step after it must be
423
+ * `reconcile` or `start`;
424
+ * - a `queueFinish` script needs a `client` or `host` finish owner, since the
425
+ * provider never calls `finishTransaction` under `server` or `none`;
426
+ * - within the `purchase-<owner>-finish` family only one member runs: the
427
+ * one named after the provider's owner, else the first by name.
428
+ */
429
+ export declare function commerceConformanceAdaptiveSkipReason(vector: CommerceConformanceVector, capabilities: CommerceCapabilities, set?: readonly CommerceConformanceVector[]): string | undefined;
430
+ /**
431
+ * The vector with every scripted store purchase (`complete`, `update`, and a
432
+ * `queueRequest` answering `purchased`) carrying the token the store actor
433
+ * reports for it (`CommerceConformanceStoreActor.tokenOf`).
434
+ */
435
+ export declare function withStoreTokens(vector: CommerceConformanceVector, tokenOf: (purchase: CommerceConformancePurchaseFixture) => string): CommerceConformanceVector;
436
+ /**
437
+ * Derives the adaptive expectation for `vector` from the observable state of a
438
+ * finished reference run under the provider's capabilities, applying the
439
+ * rules the RFC table states:
440
+ *
441
+ * - `journal`: the reference journal, so the tail after `granted` is
442
+ * `finishing, finished` for `client`, `host`, and `server` owners and
443
+ * `finished` for `none`; derived keys carry the provider's store id;
444
+ * - `finishCalls`: the reference store's calls, `by` the owner for `client`
445
+ * and `host`, none for `server` and `none`;
446
+ * - `verifyRequests`: the reference backend's log, with the finalization
447
+ * re-read a `server` owner adds unless the grant already reported
448
+ * `finalization: 'completed'`;
449
+ * - `available`: what the reference store still lists once idle, so empty
450
+ * after a completed finish for `client`, `host`, and `server` owners.
451
+ *
452
+ * Fields the fixture does not assert stay unasserted. The derivation is
453
+ * refused (a skip reason) when the reference run does not end every journal
454
+ * key in the fixture's terminal state, since the scenario then depends on a
455
+ * capability the provider lacks.
456
+ */
457
+ export declare function deriveAdaptiveCommerceConformanceExpectation(vector: CommerceConformanceVector, reference: {
458
+ readonly store: StoreId;
459
+ readonly journal: Readonly<Record<string, readonly CommercePurchaseState[]>>;
460
+ readonly verifyRequests: readonly CommerceVerifyRequest[];
461
+ readonly finishCalls: readonly CommerceConformanceFinishCall[];
462
+ /** What the reference store still lists (pending included) once the run is idle. */
463
+ readonly available: readonly StorePurchase[];
464
+ }): Promise<{
465
+ readonly expect: CommerceConformanceResolvedExpectation;
466
+ readonly reason?: undefined;
467
+ } | {
468
+ readonly expect?: undefined;
469
+ readonly reason: string;
470
+ }>;
471
+ /**
472
+ * A fixture journal key as the provider's store id derives it: only the
473
+ * encoded store segment of a `store:<store>:<transactionId>` key
474
+ * (`createDerivedPurchaseIdempotencyKey`) is replaced, so a transaction id
475
+ * that happens to contain the fixture store id is left alone. Game-chosen
476
+ * keys and store-specific key forms carry no such prefix and pass through.
477
+ */
478
+ export declare function adaptDerivedPurchaseKeyStore(key: string, fixtureStore: string, providerStore: string): string;
479
+ /**
480
+ * A fixture journal key as the provider's store id derives it. When the key is
481
+ * the derived key of a purchase the vector scripts under the fixture store, it
482
+ * is re-derived with `createDerivedPurchaseIdempotencyKey` for the provider
483
+ * store, which also covers the store-specific key forms (`google-play` token
484
+ * keys, `apps-in-toss:purchase:<id>`); otherwise only the generic
485
+ * `store:<store>:` prefix is swapped (`adaptDerivedPurchaseKeyStore`).
486
+ */
487
+ export declare function adaptDerivedPurchaseKey(vector: CommerceConformanceVector, key: string, providerStore: string): Promise<string>;
381
488
  /**
382
489
  * Partial match: an expected object asserts only its listed keys, an expected
383
490
  * array asserts its length and each element in order, and primitives compare