@mpgd/platform 0.12.1 → 0.12.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.
@@ -26,5 +26,11 @@ export interface PlatformGatewayCapabilityConformanceReport {
26
26
  * Runs provider-neutral capability checks against real adapter gateways or
27
27
  * target-configured wrappers. Every read must return a complete boolean
28
28
  * snapshot, a fresh object, and the provider's latest state.
29
+ * @evidence docs/specs/platform-capability-snapshots.md#snapshot-shape Checks each gateway read against the required boolean keys and expected provider state.
30
+ * @evidenceReview docs/specs/platform-capability-snapshots.md#snapshot-shape #f83948b Reviewed the required keys, optional banner default, and fresh reread in runFixture.
31
+ * @evidence docs/specs/platform-capability-snapshots.md#provider-transitions Checks a fresh provider read after the optional transition update.
32
+ * @evidenceReview docs/specs/platform-capability-snapshots.md#provider-transitions #839c0c8 Reviewed the changed-state guard and post-update snapshot assertion.
33
+ * @evidence docs/specs/platform-capability-snapshots.md#fixture-validation Rejects invalid fixture sets and returns the names of passing fixtures.
34
+ * @evidenceReview docs/specs/platform-capability-snapshots.md#fixture-validation #d566052 Checked the nonempty and unique name guards and target assertion.
29
35
  */
30
36
  export declare function runPlatformGatewayCapabilityConformance(input: RunPlatformGatewayCapabilityConformanceInput): Promise<PlatformGatewayCapabilityConformanceReport>;
@@ -24,6 +24,12 @@ const optionalPlatformCapabilityKeySet = new Set(optionalPlatformCapabilityKeys)
24
24
  * Runs provider-neutral capability checks against real adapter gateways or
25
25
  * target-configured wrappers. Every read must return a complete boolean
26
26
  * snapshot, a fresh object, and the provider's latest state.
27
+ * @evidence docs/specs/platform-capability-snapshots.md#snapshot-shape Checks each gateway read against the required boolean keys and expected provider state.
28
+ * @evidenceReview docs/specs/platform-capability-snapshots.md#snapshot-shape #f83948b Reviewed the required keys, optional banner default, and fresh reread in runFixture.
29
+ * @evidence docs/specs/platform-capability-snapshots.md#provider-transitions Checks a fresh provider read after the optional transition update.
30
+ * @evidenceReview docs/specs/platform-capability-snapshots.md#provider-transitions #839c0c8 Reviewed the changed-state guard and post-update snapshot assertion.
31
+ * @evidence docs/specs/platform-capability-snapshots.md#fixture-validation Rejects invalid fixture sets and returns the names of passing fixtures.
32
+ * @evidenceReview docs/specs/platform-capability-snapshots.md#fixture-validation #d566052 Checked the nonempty and unique name guards and target assertion.
27
33
  */
28
34
  export async function runPlatformGatewayCapabilityConformance(input) {
29
35
  if (input.fixtures.length === 0) {
@@ -52,21 +58,28 @@ export async function runPlatformGatewayCapabilityConformance(input) {
52
58
  return { passedFixtures };
53
59
  }
54
60
  async function runFixture(fixture) {
61
+ // A fixture may accidentally share its expected object with the provider.
62
+ // Keep the oracle independent before tryMutateSnapshot probes isolation.
63
+ const initialExpected = { ...fixture.expectedCapabilities };
64
+ const transitionExpected = fixture.transition === undefined
65
+ ? undefined
66
+ : { ...fixture.transition.expectedCapabilities };
55
67
  assertEqual(fixture.gateway.target, fixture.expectedTarget, 'gateway target must match the fixture target');
56
68
  const first = await fixture.gateway.getCapabilities();
57
- assertCapabilitySnapshot(first, fixture.expectedCapabilities);
69
+ assertCapabilitySnapshot(first, initialExpected);
58
70
  tryMutateSnapshot(first);
59
71
  const second = await fixture.gateway.getCapabilities();
60
72
  assert(second !== first, 'getCapabilities must return a fresh snapshot object for every read');
61
- assertCapabilitySnapshot(second, fixture.expectedCapabilities);
62
- if (fixture.transition === undefined) {
73
+ assertCapabilitySnapshot(second, initialExpected);
74
+ if (fixture.transition === undefined || transitionExpected === undefined) {
63
75
  return;
64
76
  }
65
- assert(hasCapabilityDifference(fixture.expectedCapabilities, fixture.transition.expectedCapabilities), 'a transition fixture must change at least one capability');
77
+ assert(hasCapabilityDifference(initialExpected, transitionExpected), 'a transition fixture must change at least one capability');
66
78
  await fixture.transition.update();
67
79
  const updated = await fixture.gateway.getCapabilities();
68
80
  assert(updated !== second, 'getCapabilities must return a fresh snapshot after a provider transition');
69
- assertCapabilitySnapshot(updated, fixture.transition.expectedCapabilities);
81
+ assert(updated !== first, 'getCapabilities must not reuse an earlier snapshot after a provider transition');
82
+ assertCapabilitySnapshot(updated, transitionExpected);
70
83
  }
71
84
  function assertCapabilitySnapshot(actual, expected) {
72
85
  const actualKeys = Object.keys(actual).sort();
package/dist/index.d.ts CHANGED
@@ -59,7 +59,29 @@ export interface PurchaseResult {
59
59
  }
60
60
  export interface PurchaseRestoreResult {
61
61
  readonly restoredEntitlements: readonly Entitlement[];
62
+ /** Server-confirmed consumable outcomes; native order visibility alone is not a grant. */
63
+ readonly settledPurchases?: readonly PurchaseSettlement[];
62
64
  }
65
+ export interface PurchaseSettlement {
66
+ readonly transactionId: string;
67
+ readonly productId: LogicalProductId;
68
+ readonly status: 'granted' | 'refunded';
69
+ readonly ledgerEntryId?: string;
70
+ readonly alreadyProcessed?: boolean;
71
+ }
72
+ /**
73
+ * Resolve a specific purchase from authoritative checkout or restore evidence.
74
+ * A completed native checkout without authoritativeGrant is still unconfirmed.
75
+ * This is UI status evidence, never permission to credit a wallet locally.
76
+ * The caller must pair `purchase` with its requested `productId` because
77
+ * PurchaseResult itself does not carry a product identifier.
78
+ */
79
+ export declare function findAuthoritativePurchaseSettlement(input: {
80
+ readonly productId: LogicalProductId;
81
+ readonly transactionId?: string;
82
+ readonly purchase?: PurchaseResult;
83
+ readonly restore?: PurchaseRestoreResult;
84
+ }): PurchaseSettlement | null;
63
85
  export interface RewardedAdResult {
64
86
  readonly status: 'completed' | 'skipped' | 'unavailable' | 'failed';
65
87
  readonly rewardGranted: boolean;
package/dist/index.js CHANGED
@@ -41,6 +41,41 @@ function isPlatformOperationCode(code) {
41
41
  && code.length <= 128
42
42
  && /^[A-Z][A-Z0-9_:-]*$/u.test(code);
43
43
  }
44
+ /**
45
+ * Resolve a specific purchase from authoritative checkout or restore evidence.
46
+ * A completed native checkout without authoritativeGrant is still unconfirmed.
47
+ * This is UI status evidence, never permission to credit a wallet locally.
48
+ * The caller must pair `purchase` with its requested `productId` because
49
+ * PurchaseResult itself does not carry a product identifier.
50
+ */
51
+ export function findAuthoritativePurchaseSettlement(input) {
52
+ const effectiveTransactionId = input.transactionId ?? input.purchase?.transactionId;
53
+ const matchingRestored = input.restore?.settledPurchases?.filter((settlement) => settlement.productId === input.productId
54
+ && (effectiveTransactionId === undefined || settlement.transactionId === effectiveTransactionId));
55
+ // Without an order ID, multiple historical outcomes are ambiguous.
56
+ if (matchingRestored !== undefined && matchingRestored.length > 1) {
57
+ return null;
58
+ }
59
+ if (matchingRestored?.[0] !== undefined) {
60
+ return matchingRestored[0];
61
+ }
62
+ const purchase = input.purchase;
63
+ if (purchase?.status !== 'completed'
64
+ || purchase.transactionId === undefined
65
+ || purchase.authoritativeGrant === undefined
66
+ || (effectiveTransactionId !== undefined && effectiveTransactionId !== purchase.transactionId)) {
67
+ return null;
68
+ }
69
+ return {
70
+ transactionId: purchase.transactionId,
71
+ productId: input.productId,
72
+ status: 'granted',
73
+ ledgerEntryId: purchase.authoritativeGrant.ledgerEntryId,
74
+ ...(purchase.authoritativeGrant.alreadyProcessed === undefined
75
+ ? {}
76
+ : { alreadyProcessed: purchase.authoritativeGrant.alreadyProcessed }),
77
+ };
78
+ }
44
79
  export function resolveShareCompletion(result) {
45
80
  if (result.status !== 'shared') {
46
81
  return undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mpgd/platform",
3
- "version": "0.12.1",
3
+ "version": "0.12.2",
4
4
  "description": "Shared platform gateway, monetization, ads, leaderboard, identity, lifecycle, and storage contracts for mpgd games.",
5
5
  "license": "MIT",
6
6
  "repository": {