@openlup/platform 0.25.0 → 0.26.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
@@ -10,6 +10,24 @@ in the source repository.
10
10
 
11
11
  ## [Unreleased]
12
12
 
13
+ ## [0.26.0]
14
+
15
+ - Publishable on the npm `latest` dist-tag as `0.26.0`, from tag
16
+ `openlup-platform-v0.26.0`.
17
+
18
+ Managed dunning attempt replay.
19
+
20
+ Carry the exact applied failure attempt through renewal propagation. Managed
21
+ same/stale observations replay the canonical result; newer attributed failures
22
+ advance and revoke prior recovery authority, including used tokens.
23
+
24
+ Migration: `CycleChargeFailurePropagationPort.openDunningCase` now requires nullable `failedPaymentAttemptId`; update host implementations and callers to pass the applied transition's identity.
25
+ Migration: `applyFailedResult` may return `paymentAttemptId?: string | null`; return the durable applied attempt so propagation can forward it, including on replay.
26
+ Migration: managed hosts must pass exact applied-transition attribution to the new overloads. Fresh missing attribution refuses with SQLSTATE `55000`; do not substitute the current or prepared attempt. Already-applied donor schema/backfill requires the explicit host mapping in DATA_AND_MIGRATIONS.
27
+
28
+ Native attempt-replay parity remains REGISTERED with M10; these managed forwards
29
+ do not implement native parity or authorize host activation.
30
+
13
31
  ## [0.25.0]
14
32
 
15
33
  - Publishable on the npm `latest` dist-tag as `0.25.0`, from tag
@@ -336,225 +354,3 @@ These are notes carried from the landed design, not fixes in this preparation.
336
354
  - PDF content-type normalization retains OpenLup behavior; signature checks are
337
355
  stricter than donor ASCII decoding. `invoicePdfFilename` falls back to English
338
356
  `invoice`; supply host captions (PR #344).
339
-
340
- ## [0.23.0]
341
-
342
- - Publishable on the npm `latest` dist-tag as `0.23.0`, from tag
343
- `openlup-platform-v0.23.0`.
344
-
345
- - Add `approveTrackingUrl` inputs to `verifyAndNormalizeFulfillmentObservation`,
346
- `PostgresFulfillmentShipmentSpineOptions`, `FulfillmentAdapterConformanceSubject`
347
- and reconciliation `trackingCodecs`. Under the strict default, provider tracking
348
- URLs persist only with host approval and must be query-free and credential-free
349
- HTTPS; host-built tracking URLs pass unchanged, including query strings.
350
- - Existing `reconcileFulfillmentObservations` callers now receive row-local unknown
351
- readback failures (`ok: false` with `failures`) while recognized rows still
352
- process.
353
- - Observation ingest (`verifyAndNormalizeFulfillmentObservation`, the native
354
- `recordObservation`, `reconcileFulfillmentObservations` and
355
- `runFulfillmentReconciliationPage`) now applies the durable-sink sanitizer: provider-derived
356
- references outside the opaque reference rule (for example `#1001` or
357
- `ORD/2026/1`) are stored as null or redacted and counted instead of verbatim,
358
- and `firstReceivedAt` must be an RFC 3339 timestamp with an offset; other
359
- values fail ingest or the reconciliation row. A correlation refused to null can
360
- be quarantined as `missing_correlation` in reconciliation.
361
- - Fulfillment reconciliation inputs replace `readLocalFulfillmentState` with
362
- `readLocalFulfillmentStatus`, `latestStatusOccurredAt` and `readHandedOverAt`;
363
- migrate host persistence implementations to these three reads.
364
- - Add exported page observation contracts and `runFulfillmentReconciliationPage`
365
- for host-owned scheduling, checkpoint resume and HTTP/ledger result projection;
366
- exact lookup remains separate. `providerCalls` counts logical page-port reads,
367
- including failures and exhaustion, separately from transport retries.
368
- - Add redacted dispatch transcript/mapping fields, typed operation errors,
369
- acceptance recovery evidence and provider-status occurrence evidence. Existing
370
- unchanged stage/live dispatch identities retain their stored evidence for native replay.
371
- - Migration: shadow drafts/attempts recorded by `0.22.0`'s native dispatch store
372
- do not replay after this change: shadow now hashes the local candidate rather
373
- than the provider-prepared command. Claim may consume a remap or raise
374
- `fulfillment_dispatch_remap_exhausted`; an existing attempt may raise
375
- `fulfillment_attempt_key_conflict`. Complete or abandon such drafts before
376
- upgrading. The `0.22.0` native dispatch store has no adopter; no compatibility
377
- transition is supplied.
378
- - D2 KEEP-GAP: native `fulfillment_dispatch_finish` drops rejected/unknown error
379
- detail; durable retention is a scheduled SQL follow-up.
380
-
381
- - Compatibility: `getStock()` and `getStockMovements()` now return a row union;
382
- consumers must discriminate rejections with
383
- `"kind" in row && row.kind === "rejected"` before reading observation fields.
384
- Provider read failures now return failed worker results (`ok: false`) with
385
- persisted `providerError` diagnostics when the provider supplies a typed failure,
386
- instead of throwing. Hosts can map those
387
- failures to HTTP 502; terminal cursor-write failures still reject.
388
- Cursor reads, running-cursor writes, local stock/class/reservation reads and
389
- stock/evidence writes or SKU evidence resolution now also resolve `ok: false`
390
- instead of rejecting when a terminal failed cursor can be persisted.
391
- - `SyncCursor` adds optional `syncRunId`, `counts`, `items`, `alerts`, `staleSync`
392
- and `error`, worker results add `rejected`, `items`, `providerError` and `refusal`,
393
- and `fulfillment_stock_sync_cursors.error_code` now stores safe operation reasons
394
- (including `operation:SQLSTATE`) or typed provider/admission reasons instead of
395
- the generic `io_failure`.
396
- - Mixed valid/rejected batches with valid stock rows succeed, retain rejection
397
- counts and diagnostics, evaluate/resolve alerts for valid SKUs and advance
398
- `lastStockSyncedAt`; an empty stock response is also valid, but a nonempty stock
399
- response with no valid stock rows is malformed and non-retryable even with valid
400
- movements, preserves prior coverage gaps and cannot resolve stale alerts.
401
- Typed disabled, not-ready and bad-identity refusals retain their original reason
402
- and retryability in the worker result and cursor.
403
- Blank stock SKU references are skipped before persistence; stale-sync recovery
404
- resolves after the terminal cursor, with cleanup failure preserving success.
405
- Stock and movement rejection diagnostics carry a `kind` discriminator.
406
- Stock-sync redirects are non-retryable, report `redirect_refused` and make only
407
- one GET attempt; exhausted stock network reads remain retryable through the stock
408
- failure projection. Product-sync retains the base client diagnostics and retry
409
- behavior, including `retryable: false` after exhausted network reads.
410
- - Stock-sync alert fingerprints now contain only `requestKey`, `skuRef` and
411
- `thresholdKind`: changing quantities, severity or run evidence under the same
412
- key returns `replayed: true`. Different SKUs or thresholds use different keys;
413
- a changed identity under a reused key still conflicts.
414
- - Closed the scheduled KEEP-GAP for stock alert refresh/reopen with native forward
415
- `20261022165000_fulfillment_stock_evidence_replay_reopen.sql`. Matching-identity
416
- replay reopens the alert, refreshes severity/quantities and clears `resolved_at`;
417
- identity mismatches retain SQLSTATE `23505`.
418
-
419
- Migration: install the complete native catalogue through `20261022165000` before
420
- stock adoption. The package descriptor and SQL asset project that same forward;
421
- replay preserves the row identity, first-seen time and historical resolver run.
422
- There is no managed forward or legacy fingerprint rewrite.
423
-
424
- Migration: alert rows written by 0.22.0's native port carry the earlier whole-input fingerprint; subsequent writes under those keys conflict with SQLSTATE `23505`, and resolve only marks the row resolved while retaining its key and fingerprint, so it does not clear the conflict. Legacy fingerprints are documented only; this change does not rewrite them.
425
-
426
- - Fulfillment ingress exposes receipt IDs, a host-neutral HTTP result mapping,
427
- counted correlation/timestamp/tracking refusals and persisted safe superseded
428
- causes. The opt-in adapter preserves donor webhook key bytes and defaults to
429
- strict opaque references. Adopters can explicitly allow wider reference shapes
430
- through `observationCodecs.approveReference`; tracking URLs retain their strict
431
- default policy. `carrierRef` now stores the service-derived lowercase carrier
432
- prefix by default, or the safe kind returned by `normalizeCarrier`, and effects
433
- receive that same value as `carrierKind`. Both are null when no kind is available
434
- or service evidence is refused; raw provider carrier codes have no fallback,
435
- matching donor deduplication. Strict timestamp validation applies only at
436
- webhook ingress, accepting compact offsets and local timestamps as before;
437
- reconciliation and stock-movement timestamp parsing retain PR2 behavior.
438
- Hosts retain referenced raw evidence.
439
-
440
- Migration (observation ingress): donor-format colon identities remain the
441
- compatibility target. Observation receipts/effects recorded by 0.22.0's native
442
- stores under package digest keys are not recognized under the incumbent colon
443
- keys, so retrying such an event can apply tracking again. Replaying a superseded
444
- receipt processed by 0.22.0 fails with `fulfillment_observation_outcome_conflict`.
445
- Drain in-flight observation retries before upgrading. No bridges or aliases are
446
- provided for these native rows. Wider incumbent correlation/tracking references
447
- require an explicit adopter allowlist; the default refuses and reports them.
448
-
449
- Close the scheduled native stock-evidence refresh/reopen gap with forward
450
- `20261022165000_fulfillment_stock_evidence_replay_reopen.sql` and its byte-identical
451
- fulfillment package projection. Matching fingerprints refresh severity and nullable
452
- quantities, reopen status, and clear `resolved_at`; identity, first-seen time and
453
- historical resolver metadata survive. Existing-row replays and resolution serialize
454
- on the row lock. Fingerprint mismatches still raise `23505`.
455
-
456
- Migration: install the complete canonical native catalogue through `20261022165000`
457
- before stock adoption, preserving its immutable ledger prefix. Sparse package SQL
458
- is not a standalone install chain. The managed baseline already has this behavior.
459
- Legacy 0.22.0 whole-input fingerprints are not rewritten and still conflict with
460
- PR1 identity fingerprints, including after resolution.
461
-
462
- - Add experimental invoicing declarations and explicit default-off capability selection, preserving exact absence, uncertain effects, correction identity and owed-duty visibility.
463
- - Freeze host fiscal/facts/seller/money, registry, artifact, delivery, payout-item and safe account-history seams without installing a worker, store, provider or route.
464
-
465
- Approve only the exact frozen M10/F17 native and managed SQL packet through a separate forward-control class, with inert fixtures, refusal plants and a required real PostgreSQL/pgTAP catalog gate. This control adds no migration, runtime activation or new permission to an installation.
466
-
467
- - Add experimental subscription contracts and exact server/browser subpaths, preserving schedule identity, graph transaction boundaries, deferred admission and explicit native capability refusals.
468
- - Contribute the unchanged hosted cycle, refusal and durable failure mechanisms with injected policy and their characterization; preserve native captured-case protection through rollback until actual arrival.
469
-
470
- - Add injected native renewal and retry stores and a native executor using the subscription ports; bind hosted charge/failure facades to the contributed mechanism and existing M07 policies.
471
- - Type-level change visible to dev-preview consumers: `capabilities.retryRecovery` changes from `"unavailable"` to `"selected"` (asserted in packages/platform/test/subscriptions/portFit.test.ts).
472
-
473
- Migration: Install native N1 through the full ordered manifest before composing the retry driver. Native declines can retry or recover the original obligation instead of stalling indefinitely (F16); exhaustion leaves an active subscription and open case with no next retry. Preserve ledgers on rollback. The initial binding preserves case-before-outcome order and atomic terminal/bookkeeping writes. An existing immutable receipt short-circuits initial terminal replay before settlement, rebinding or case propagation, preserving all later retry effects; no runner is activated and M07 native prepared-store parity remains unavailable.
474
-
475
- Host obligations: establish explicit logical-cycle correlation before invoking the runner (including prepare/execute). Ordinary retries must use the frozen mandate/authorization; N1 does not bind an ordinary retry method, so the P8 retry driver or host must enforce this, with a composition witness added in P8. Prepared retries retain the existing non-terminal `retry_scheduled` disposition with no next dispatch date. Bare `failed` execution status without explicit failure evidence requires verified readback (or remains awaiting an ACK).
476
-
477
- A host or driver must never call `openCase` after a replayed `applyFailure` receipt, and the P8 retry driver must add a zero-delta replay witness.
478
-
479
- ### Warehouse product-sync parity
480
-
481
- A3 #311 restores product-sync parity and safe diagnostics in the opt-in warehouse
482
- adapter, including product/pack projection and injected transport behavior.
483
- It remains default-off with offline evidence only and no provider-account proof.
484
-
485
- Migration: upgrade the complete core/outbox/platform set from `0.22.0` to
486
- `0.23.0` for exact peers. Install the complete canonical native chain through
487
- position 76 (`20261022165000_fulfillment_stock_evidence_replay_reopen.sql`),
488
- including native N1, before selecting native stores. Sparse package SQL is not
489
- an installation chain. Follow the shadow-draft, observation-retry and legacy
490
- alert-fingerprint migration notes above; rollback preserves the forward ledger.
491
-
492
- ### Experimental stages: no adopter activation
493
-
494
- M10 subscription renewal/retry is **EXPERIMENTAL, INCOMPLETE and DEFAULT-OFF**,
495
- not for adopter activation. This set includes P1 exports, the forward controls,
496
- and P2 native renewal/retry stores and executor with native N1 SQL landed.
497
- No runner is activated. G23 composition and N1 correlation, frozen authorization
498
- and replay obligations remain pending: compose admission before any new cycle,
499
- bind a logical cycle before prepare/execute, enforce frozen authorization on
500
- ordinary retries, and never open a case after a replayed failure receipt.
501
- The later retry driver still owes its zero-delta replay witness.
502
-
503
- M09 invoicing capability selection and type-only contracts are
504
- **EXPERIMENTAL, INCOMPLETE and DEFAULT-OFF**, not for adopter activation.
505
- There is no invoicing SQL, store, worker, provider or route in this stage.
506
- Installing the set does not register subscription or invoicing routes, workers
507
- or a renewal runner; host composition remains explicit.
508
-
509
- ### Known limitations
510
-
511
- - Managed dunning exhaustion still pauses active subscriptions; retaining active status needs a managed forward. Native N1 lands the bounded retry/failure repair, with the host obligations below still pending.
512
- - D8-1: native advisory v2 promo-code candidates/rejection details unavailable.
513
- - D8-2: native mounted v2 issue/honor/fallback unavailable.
514
- - D8-3: native acquisition-only mounted issuance/honor unavailable.
515
- - D8-4: full native/managed eligibility equivalence unavailable.
516
- - Bundle-1: independent DB human-only actor-kind enforcement absent natively; first in the native-backend parity round.
517
- - Bundle-2: managed-equivalent actor-kind/email history absent natively.
518
- - Bundle-3 closes injected-reader M05 mounted admin/feed readback only. Native new-purchase stock read absent despite P5a installed-store proof; deferred second after bundle-1 (owner 2026-10-21); readiness refuses.
519
- - Bundle-4: incumbent real-PostgreSQL contention only; no exactly-once guarantee.
520
- - Managed schema/pgTAP retain `44.404`; removal needs owner decision/forward; reference composition supplies it to app code.
521
- - A1 offline only; account/hosted-time/owner TEST evidence unclaimed; `communications/klaviyo` verification-pending.
522
- - M05 omitted opt-ins preserve defaults/outputs/read order/schedules/refusals/acceptance-token bytes/SQL.
523
- - Native inventory adjustment unavailable; address writes refuse readiness. Flat-rate save needs selected port/Core readiness/applied-seed observation.
524
- - Native shipping audit unavailable; handler swallows best-effort audit failure.
525
- - Native flat-rate integer bounds (0 through host maximum; reference 100,000 minor units) use request schema, not DB CHECK.
526
- - Managed ATP/forecast/dated-supply/pagination readback unproved/pending M06.
527
- - Managed two-session locks/concurrency/uncovered compensation unproved/pending M06.
528
- - Managed exact-prefix upgrade/ledger unproved/pending M06.
529
- - Managed address idempotency/conflict/readback unproved/pending M06.
530
- - Managed successful flat-rate read/save unproved/pending host authority.
531
- - M06 stock-wait lifecycle/durable timing/staging controls retain C5/C6; busy/try-gate, continuity/flag/TTL transfers are dispositions, not PASS.
532
- - Shipping national-registry fields/locale-specific `canonicalAddressKey` fold await M16 host market pack/key migration; reference shipping copy stays locale-specific until M16.
533
- - Native durable prepared storage, method setup, absence reopen and cumulative refunds unavailable; no production installation parity claimed.
534
- - Typed refusal-envelope forwarding unavailable (`failureReason` only); reusable registration needs host ownership resolution.
535
- - Incomplete chargebacks stay refunds/unapplied: reclassification bypasses evidence validation into dispute apply; limited operator recovery.
536
- - Audit compatibility still detects `chargeId`; refund operation identity/original money supply no typed charge identity.
537
- - M07 follow-up: repair poll can clear host redirect; post-timeout verify never restarts polling; unlocalized poller; fixture builds/never calls handler.
538
- - M07 follow-up: reference status route wrong-rail witness.
539
- - M07 follow-up: explicit reference-route refusal for unsupported method setup.
540
- - M07 follow-up: deployment-neutral disabled-verify reason.
541
- - M07 follow-up: reference card normalizer missing-amount path; package settling evidence already requires positive money.
542
-
543
- - Native delivery alignment stays off or shadow until a host composes the native automatic-renewal runner with the G23 renewal admission decorator. The decorator alone neither composes nor activates the runner.
544
- - The core fulfillment status map carries localized adopter labels; replace them with host-injected labels or internationalization keys at the host presentation boundary.
545
- - The warehouse adapter example is opt-in and has offline conformance only; no provider-account proof or activation is established. Exact absence, label create/void and cancellation remain unsupported.
546
- - After an alignment extension, a deferred renewal operation at the old schedule remains claimed with no reader or reservation effect. Changed terms snapshots conflict under the existing snapshot-conflict rules.
547
-
548
- Native parity remains host-owned at these seams. Missing native bindings use the shared `FULFILLMENT_NATIVE_UNSUPPORTED` refusal; no partial write or parity claim is implied.
549
-
550
- | Amendment | Host-owned seam and native boundary |
551
- | --- | --- |
552
- | G18 | Replacement creation and atomic eligibility/replay receipts; native replacement callback refuses. |
553
- | G19 | Operator mutations, actor attribution and replay receipts; native mutation callbacks refuse. |
554
- | G20 | Customer alignment rows; native reader refuses, while the pure delay projection remains callable. |
555
- | G21 | Commerce list/detail and shipments overview; native read callbacks refuse. |
556
- | G22 | Authoritative preparation edits, authorization and replay receipts; missing preparation binding refuses before dispatch, after disabled/selection/readiness checks. |
557
-
558
- - M08 parity D2 KEEP-GAP: native dispatch finish still loses rejected/unknown error detail.
559
- - M08 parity KEEP-GAP: immutable stock snapshot/alert evidence storage is absent; cursor JSON is overwritten. Hosts retain referenced evidence.
560
- - Stock-evidence replay/reopen is CLOSED by native position 76; legacy `0.22.0` alert fingerprints still conflict with `23505`, including after resolution.
package/README.md CHANGED
@@ -10,14 +10,20 @@ Development preview. This package is part of the publishable set with core and o
10
10
 
11
11
  It holds the runnable server substrate an OpenLup application composes: the Node HTTP runtime, BFF dispatch, scheduling and job leases, blob storage, data gateways, the node-postgres migration runner and bundle composition. Ports and their contracts stay in `@openlup/core/platform-runtime`.
12
12
 
13
- For the prepared `0.25.0` set, read the [adjacent-release migration and known
14
- limitations](CHANGELOG.md#0250) before selecting optional services.
13
+ For the prepared `0.26.0` set, read the [adjacent-release migration and known
14
+ limitations](CHANGELOG.md#0260) before selecting optional services.
15
15
  Install the complete native chain through position 86, including N2 commands,
16
16
  N3 stock wait, the ATP seam/allocator and late-payment/retry-selection forwards;
17
17
  sparse SQL projections are not an install chain. Native runtime roles need the
18
18
  ATP/exposure EXECUTE and underlying SELECT grants described in the release notes.
19
- Managed hosts apply the complete prefix through `20261024185100` for stock-wait
20
- and refund-evidence functions. Preserve captured obligations and receipts on rollback.
19
+ Managed hosts apply the complete prefix through `20261025090100` for attributed
20
+ dunning attempt replay. `openDunningCase` requires nullable
21
+ `failedPaymentAttemptId`; pass the durable applied attempt through renewal failure
22
+ propagation, including replay. Fresh missing attribution refuses; never substitute
23
+ a current or prepared attempt. Read the migration notes for the new overloads
24
+ and existing donor schema/backfill mapping. Native attempt-replay parity remains
25
+ registered and these managed forwards authorize no host activation.
26
+ Preserve captured obligations and receipts on rollback.
21
27
  M10 subscription services and contributions remain **EXPERIMENTAL and DEFAULT-OFF**.
22
28
  Hosts explicitly supply readiness, execution, commands, mandatory G23 admission,
23
29
  a population lease and M04 delivery. Factories activate no serving work. The inert
@@ -32,7 +38,7 @@ Adopters must not activate unselected or unready invoicing; installation and
32
38
  inert contributions register no runner, worker, route or schedule.
33
39
  M06 managed-host proof classes remain pending in the
34
40
  [M06 parity ledger](../../docs/platform/plans/oss-m06-stock-reservations-delivery.md#parity-pending-rows).
35
- Size comparison uses the published `0.24.0` tarball. Preparation and offline
41
+ Size comparison uses the published `0.25.0` tarball. Preparation and offline
36
42
  adapter evidence grant no publication or provider-account certification.
37
43
 
38
44
  Longer module recipes live in the [platform package reference](../../docs/platform/PLATFORM_PACKAGE_REFERENCE.md).
@@ -129,9 +129,11 @@ export interface CycleChargeFailurePropagationPort {
129
129
  };
130
130
  }): Promise<{
131
131
  replayed: boolean;
132
+ paymentAttemptId?: string | null;
132
133
  }>;
133
134
  readCycleRetryState(cycleId: string): Promise<CycleRetryState>;
134
135
  openDunningCase(input: {
136
+ failedPaymentAttemptId: string | null;
135
137
  idempotencyKey: string;
136
138
  cycleId: string;
137
139
  subscriptionId: string;
@@ -25,7 +25,7 @@ export async function propagateRenewalFailure(port, ctx, policy) {
25
25
  });
26
26
  const cycleSnapshot = await port.readCycleRetryState(ctx.cycleId);
27
27
  return {
28
- ...await openDunningCase(port, ctx, cycleSnapshot, occurredAt, policy),
28
+ ...await openDunningCase(port, ctx, cycleSnapshot, occurredAt, policy, applied.paymentAttemptId ?? null),
29
29
  applyReplayed: applied.replayed,
30
30
  };
31
31
  }
@@ -37,7 +37,7 @@ async function resolveDurableOccurredAt(port, ctx, policy) {
37
37
  });
38
38
  return durable !== null && durable.length > 0 ? durable : ctx.occurredAt;
39
39
  }
40
- async function openDunningCase(port, ctx, cycle, occurredAt, policy) {
40
+ async function openDunningCase(port, ctx, cycle, occurredAt, policy, failedPaymentAttemptId) {
41
41
  const retryAttempt = cycle.retryAttempt ?? 1;
42
42
  // Resolve again after the retry read: original context evidence remains dynamic.
43
43
  const failureClass = policy.resolveClassification(ctx.failureClassification, ctx.failureReason).failureClass;
@@ -52,6 +52,7 @@ async function openDunningCase(port, ctx, cycle, occurredAt, policy) {
52
52
  ? cycle.nextRetryAt
53
53
  : nextRetryAttemptAt(occurredAt, retryAttempt, undefined, failureClass);
54
54
  const outcome = await port.openDunningCase({
55
+ failedPaymentAttemptId,
55
56
  idempotencyKey: policy.dunningKey(ctx.executionIdempotencyKey, retryAttempt),
56
57
  cycleId: ctx.cycleId,
57
58
  subscriptionId: ctx.subscriptionId,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openlup/platform",
3
- "version": "0.25.0",
3
+ "version": "0.26.0",
4
4
  "private": false,
5
5
  "description": "Runnable server substrate: Node HTTP runtime, BFF dispatch, scheduler, storage, data gateways and bundle composition; development preview.",
6
6
  "license": "Apache-2.0",
@@ -31,7 +31,7 @@
31
31
  "api:update": "npm run build && node --experimental-strip-types ./scripts/api-contract.ts --update"
32
32
  },
33
33
  "peerDependencies": {
34
- "@openlup/core": "0.25.0",
34
+ "@openlup/core": "0.26.0",
35
35
  "pg": "^8.22.0",
36
36
  "@aws-sdk/client-s3": "^3.1144.0",
37
37
  "@types/pg": "^8.20.0"
@@ -535,8 +535,8 @@
535
535
  "maxUnpackedBytes": 6553600,
536
536
  "maxFiles": 2100,
537
537
  "baseline": {
538
- "version": "0.24.0",
539
- "packedBytes": 1355949
538
+ "version": "0.25.0",
539
+ "packedBytes": 1392132
540
540
  },
541
541
  "browserEntries": {
542
542
  "./catalog/contracts": 4096,
@@ -87,9 +87,10 @@ export interface CycleChargeFailurePropagationPort {
87
87
  occurredAt: string;
88
88
  failureReason: string;
89
89
  failureClassification: { failureClass: string; decidedBy: string };
90
- }): Promise<{ replayed: boolean }>;
90
+ }): Promise<{ replayed: boolean; paymentAttemptId?: string | null }>;
91
91
  readCycleRetryState(cycleId: string): Promise<CycleRetryState>;
92
92
  openDunningCase(input: {
93
+ failedPaymentAttemptId: string | null;
93
94
  idempotencyKey: string;
94
95
  cycleId: string;
95
96
  subscriptionId: string;
@@ -47,7 +47,7 @@ export async function propagateRenewalFailure(
47
47
  });
48
48
  const cycleSnapshot = await port.readCycleRetryState(ctx.cycleId);
49
49
  return {
50
- ...await openDunningCase(port, ctx, cycleSnapshot, occurredAt, policy),
50
+ ...await openDunningCase(port, ctx, cycleSnapshot, occurredAt, policy, applied.paymentAttemptId ?? null),
51
51
  applyReplayed: applied.replayed,
52
52
  };
53
53
  }
@@ -69,6 +69,7 @@ async function openDunningCase(
69
69
  cycle: CycleRetryState,
70
70
  occurredAt: string,
71
71
  policy: RenewalFailurePolicy,
72
+ failedPaymentAttemptId: string | null,
72
73
  ): Promise<Omit<FailurePropagationResult, "applyReplayed">> {
73
74
  const retryAttempt = cycle.retryAttempt ?? 1;
74
75
  // Resolve again after the retry read: original context evidence remains dynamic.
@@ -92,6 +93,7 @@ async function openDunningCase(
92
93
  failureClass,
93
94
  );
94
95
  const outcome = await port.openDunningCase({
96
+ failedPaymentAttemptId,
95
97
  idempotencyKey: policy.dunningKey(ctx.executionIdempotencyKey, retryAttempt),
96
98
  cycleId: ctx.cycleId,
97
99
  subscriptionId: ctx.subscriptionId,