@openlup/platform 0.22.0 → 0.23.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.
Files changed (156) hide show
  1. package/CHANGELOG.md +222 -99
  2. package/README.md +59 -589
  3. package/dist/fulfillment/adapterConformance.js +1 -1
  4. package/dist/fulfillment/bindings.js +1 -1
  5. package/dist/fulfillment/contracts/conformance.d.ts +3 -1
  6. package/dist/fulfillment/contracts/dispatch.d.ts +41 -3
  7. package/dist/fulfillment/contracts/index.d.ts +3 -3
  8. package/dist/fulfillment/contracts/observation.d.ts +21 -1
  9. package/dist/fulfillment/contracts/readback.d.ts +21 -0
  10. package/dist/fulfillment/contracts/store.d.ts +3 -0
  11. package/dist/fulfillment/contracts/sync.d.ts +58 -2
  12. package/dist/fulfillment/dispatchProjection.d.ts +20 -1
  13. package/dist/fulfillment/dispatchProjection.js +95 -6
  14. package/dist/fulfillment/index.d.ts +3 -2
  15. package/dist/fulfillment/index.js +2 -2
  16. package/dist/fulfillment/migrations.d.ts +5 -0
  17. package/dist/fulfillment/migrations.js +1 -0
  18. package/dist/fulfillment/observationProjection.d.ts +24 -3
  19. package/dist/fulfillment/observationProjection.js +65 -12
  20. package/dist/fulfillment/omnipack/client.d.ts +3 -1
  21. package/dist/fulfillment/omnipack/client.js +17 -7
  22. package/dist/fulfillment/omnipack/index.d.ts +4 -1
  23. package/dist/fulfillment/omnipack/index.js +104 -45
  24. package/dist/fulfillment/omnipack/mappers.d.ts +7 -1
  25. package/dist/fulfillment/omnipack/mappers.js +24 -2
  26. package/dist/fulfillment/omnipack/observation.d.ts +11 -4
  27. package/dist/fulfillment/omnipack/observation.js +44 -29
  28. package/dist/fulfillment/omnipack/reconciliation.d.ts +15 -0
  29. package/dist/fulfillment/omnipack/reconciliation.js +55 -0
  30. package/dist/fulfillment/postgresDispatchStore.js +1 -1
  31. package/dist/fulfillment/postgresShipmentSpine.d.ts +8 -1
  32. package/dist/fulfillment/postgresShipmentSpine.js +17 -3
  33. package/dist/fulfillment/postgresStockSyncPort.js +6 -3
  34. package/dist/fulfillment/providerDispatchAcceptance.d.ts +1 -0
  35. package/dist/fulfillment/providerDispatchAcceptance.js +8 -2
  36. package/dist/fulfillment/providerDispatchWorker.d.ts +13 -0
  37. package/dist/fulfillment/providerDispatchWorker.js +47 -20
  38. package/dist/fulfillment/providerReconciliationWorker.d.ts +27 -19
  39. package/dist/fulfillment/providerReconciliationWorker.js +75 -19
  40. package/dist/fulfillment/providerStockSyncWorker.d.ts +5 -1
  41. package/dist/fulfillment/providerStockSyncWorker.js +229 -120
  42. package/dist/fulfillment/providerTrackingReferences.d.ts +3 -2
  43. package/dist/fulfillment/verifiedIngress.d.ts +38 -11
  44. package/dist/fulfillment/verifiedIngress.js +36 -8
  45. package/dist/invoicing/capabilities.d.ts +11 -0
  46. package/dist/invoicing/capabilities.js +10 -0
  47. package/dist/invoicing/contracts/capabilities.d.ts +40 -0
  48. package/dist/invoicing/contracts/capabilities.js +1 -0
  49. package/dist/invoicing/contracts/companyLookup.d.ts +49 -0
  50. package/dist/invoicing/contracts/companyLookup.js +1 -0
  51. package/dist/invoicing/contracts/delivery.d.ts +74 -0
  52. package/dist/invoicing/contracts/delivery.js +1 -0
  53. package/dist/invoicing/contracts/facts.d.ts +100 -0
  54. package/dist/invoicing/contracts/facts.js +1 -0
  55. package/dist/invoicing/contracts/host.d.ts +246 -0
  56. package/dist/invoicing/contracts/host.js +1 -0
  57. package/dist/invoicing/contracts/index.d.ts +16 -0
  58. package/dist/invoicing/contracts/index.js +1 -0
  59. package/dist/invoicing/contracts/provider.d.ts +126 -0
  60. package/dist/invoicing/contracts/provider.js +1 -0
  61. package/dist/invoicing/contracts/settlement.d.ts +66 -0
  62. package/dist/invoicing/contracts/settlement.js +1 -0
  63. package/dist/invoicing/contracts/store.d.ts +267 -0
  64. package/dist/invoicing/contracts/store.js +1 -0
  65. package/dist/invoicing/index.d.ts +1 -0
  66. package/dist/invoicing/index.js +1 -0
  67. package/dist/subscriptions/automaticRenewal.d.ts +44 -0
  68. package/dist/subscriptions/automaticRenewal.js +81 -0
  69. package/dist/subscriptions/automaticRenewalExecution.d.ts +21 -0
  70. package/dist/subscriptions/automaticRenewalExecution.js +44 -0
  71. package/dist/subscriptions/contracts/commands.d.ts +115 -0
  72. package/dist/subscriptions/contracts/commands.js +1 -0
  73. package/dist/subscriptions/contracts/dunning.d.ts +133 -0
  74. package/dist/subscriptions/contracts/dunning.js +1 -0
  75. package/dist/subscriptions/contracts/index.d.ts +6 -0
  76. package/dist/subscriptions/contracts/index.js +1 -0
  77. package/dist/subscriptions/contracts/messages.d.ts +43 -0
  78. package/dist/subscriptions/contracts/messages.js +1 -0
  79. package/dist/subscriptions/contracts/renewal.d.ts +533 -0
  80. package/dist/subscriptions/contracts/renewal.js +1 -0
  81. package/dist/subscriptions/contracts/stockWait.d.ts +73 -0
  82. package/dist/subscriptions/contracts/stockWait.js +1 -0
  83. package/dist/subscriptions/index.d.ts +39 -0
  84. package/dist/subscriptions/index.js +17 -0
  85. package/dist/subscriptions/paymentOutcomes.d.ts +8 -0
  86. package/dist/subscriptions/paymentOutcomes.js +27 -0
  87. package/dist/subscriptions/postgresRenewalStore.d.ts +20 -0
  88. package/dist/subscriptions/postgresRenewalStore.js +61 -0
  89. package/dist/subscriptions/postgresRetryStore.d.ts +78 -0
  90. package/dist/subscriptions/postgresRetryStore.js +142 -0
  91. package/dist/subscriptions/renewalCycleFailureMechanism.d.ts +34 -0
  92. package/dist/subscriptions/renewalCycleFailureMechanism.js +79 -0
  93. package/dist/subscriptions/renewalCycleMechanism.d.ts +46 -0
  94. package/dist/subscriptions/renewalCycleMechanism.js +157 -0
  95. package/dist/subscriptions/renewalCycleRefusalMechanism.d.ts +41 -0
  96. package/dist/subscriptions/renewalCycleRefusalMechanism.js +160 -0
  97. package/dist/subscriptions/renewalIdentity.d.ts +3 -0
  98. package/dist/subscriptions/renewalIdentity.js +4 -0
  99. package/package.json +22 -2
  100. package/release-gates.json +56 -3
  101. package/sql/20261022160000_subscription_retry_lifecycle.sql +328 -0
  102. package/sql/20261022165000_fulfillment_stock_evidence_replay_reopen.sql +30 -0
  103. package/src/fulfillment/adapterConformance.ts +1 -1
  104. package/src/fulfillment/bindings.ts +1 -1
  105. package/src/fulfillment/contracts/conformance.ts +3 -1
  106. package/src/fulfillment/contracts/dispatch.ts +20 -5
  107. package/src/fulfillment/contracts/index.ts +3 -3
  108. package/src/fulfillment/contracts/observation.ts +11 -3
  109. package/src/fulfillment/contracts/readback.ts +13 -0
  110. package/src/fulfillment/contracts/store.ts +1 -0
  111. package/src/fulfillment/contracts/sync.ts +13 -2
  112. package/src/fulfillment/dispatchProjection.ts +92 -7
  113. package/src/fulfillment/index.ts +3 -2
  114. package/src/fulfillment/migrations.ts +1 -0
  115. package/src/fulfillment/observationProjection.ts +64 -13
  116. package/src/fulfillment/omnipack/client.ts +19 -7
  117. package/src/fulfillment/omnipack/index.ts +96 -39
  118. package/src/fulfillment/omnipack/mappers.ts +23 -2
  119. package/src/fulfillment/omnipack/observation.ts +45 -27
  120. package/src/fulfillment/omnipack/reconciliation.ts +56 -0
  121. package/src/fulfillment/postgresDispatchStore.ts +1 -1
  122. package/src/fulfillment/postgresShipmentSpine.ts +21 -4
  123. package/src/fulfillment/postgresStockSyncPort.ts +6 -3
  124. package/src/fulfillment/providerDispatchAcceptance.ts +9 -3
  125. package/src/fulfillment/providerDispatchWorker.ts +48 -20
  126. package/src/fulfillment/providerReconciliationWorker.ts +81 -25
  127. package/src/fulfillment/providerStockSyncWorker.ts +222 -126
  128. package/src/fulfillment/providerTrackingReferences.ts +3 -2
  129. package/src/fulfillment/verifiedIngress.ts +44 -11
  130. package/src/invoicing/capabilities.ts +9 -0
  131. package/src/invoicing/contracts/capabilities.ts +15 -0
  132. package/src/invoicing/contracts/companyLookup.ts +13 -0
  133. package/src/invoicing/contracts/delivery.ts +24 -0
  134. package/src/invoicing/contracts/facts.ts +60 -0
  135. package/src/invoicing/contracts/host.ts +85 -0
  136. package/src/invoicing/contracts/index.ts +16 -0
  137. package/src/invoicing/contracts/provider.ts +78 -0
  138. package/src/invoicing/contracts/settlement.ts +39 -0
  139. package/src/invoicing/contracts/store.ts +98 -0
  140. package/src/invoicing/index.ts +1 -0
  141. package/src/subscriptions/automaticRenewal.ts +87 -0
  142. package/src/subscriptions/automaticRenewalExecution.ts +39 -0
  143. package/src/subscriptions/contracts/commands.ts +46 -0
  144. package/src/subscriptions/contracts/dunning.ts +48 -0
  145. package/src/subscriptions/contracts/index.ts +6 -0
  146. package/src/subscriptions/contracts/messages.ts +18 -0
  147. package/src/subscriptions/contracts/renewal.ts +325 -0
  148. package/src/subscriptions/contracts/stockWait.ts +34 -0
  149. package/src/subscriptions/index.ts +24 -0
  150. package/src/subscriptions/paymentOutcomes.ts +23 -0
  151. package/src/subscriptions/postgresRenewalStore.ts +76 -0
  152. package/src/subscriptions/postgresRetryStore.ts +174 -0
  153. package/src/subscriptions/renewalCycleFailureMechanism.ts +119 -0
  154. package/src/subscriptions/renewalCycleMechanism.ts +225 -0
  155. package/src/subscriptions/renewalCycleRefusalMechanism.ts +215 -0
  156. package/src/subscriptions/renewalIdentity.ts +5 -0
package/README.md CHANGED
@@ -4,14 +4,21 @@ Development preview. This package is part of the publishable set with core and o
4
4
 
5
5
  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`.
6
6
 
7
- For the prepared `0.22.0` set, read the [adjacent-release migration and known
8
- limitations](CHANGELOG.md#0220) before selecting the opt-in M05 parity seams or
9
- M06 composition bindings, the M07 payment selectors, or M08 fulfillment services. Native delivery alignment stays off or shadow until a host composes its automatic-renewal runner with the G23 admission decorator. M07 payment truth is complete for this wave; its selectors are default-off. M06 remains incomplete: inventory and shipping entries
10
- are development-preview surfaces, with managed-host proof classes pending in the
7
+ For the prepared `0.23.0` set, read the [adjacent-release migration and known
8
+ limitations](CHANGELOG.md#0230) before selecting the opt-in parity seams.
9
+ Install the complete native chain through position 76; follow the `0.22.0`
10
+ native-row migration notes. Native delivery alignment stays off or shadow until
11
+ a host composes its automatic-renewal runner with G23 admission.
12
+ M10 subscription renewal/retry (P1 exports, forward controls, P2 stores/executor,
13
+ native N1 SQL landed) and M09 invoicing selection/type-only contracts (no SQL)
14
+ are **EXPERIMENTAL, INCOMPLETE and DEFAULT-OFF**, not for adopter activation.
15
+ No runner is activated; G23/N1 host obligations remain pending.
16
+ M06 managed-host proof classes remain pending in the
11
17
  [M06 parity ledger](../../docs/platform/plans/oss-m06-stock-reservations-delivery.md#parity-pending-rows).
12
- The size gate compares the prepared `0.22.0` set with the published `0.21.0` tarball;
13
- preparation, offline adapter evidence and a passing size cap grant no publication
14
- or provider-account certification.
18
+ Size comparison uses the published `0.22.0` tarball. Preparation and offline
19
+ adapter evidence grant no publication or provider-account certification.
20
+
21
+ Longer module recipes live in the [platform package reference](../../docs/platform/PLATFORM_PACKAGE_REFERENCE.md).
15
22
 
16
23
  ## Package surface
17
24
 
@@ -37,6 +44,8 @@ or provider-account certification.
37
44
  | `./fulfillment/omnipack` | Opt-in Node fulfillment example with injected I/O; default-off. |
38
45
  | `./payments` | Experimental prepared-attempt ordering with injected persistence, execution and failure policy; contracts at `./payments/contracts`. |
39
46
  | `./fulfillment`, `./fulfillment/contracts` | [Experimental fulfillment services, contracts and status canon](../../docs/platform/ARCHITECTURE_AND_EXTENSIONS.md#fulfillment-contracts). |
47
+ | `./subscriptions`, `./subscriptions/contracts` | Default-off, incomplete; [experimental subscription contracts and renewal port](../../docs/platform/ARCHITECTURE_AND_EXTENSIONS.md#subscription-contracts). |
48
+ | `./invoicing`, `./invoicing/contracts` | Default-off, incomplete invoicing selection and type-only contracts; no SQL or activation. |
40
49
  | `./inventory` | Experimental schemas/ports, handlers, native stores, projection, injected forecast and SQL descriptor; `./inventory/contracts` DTOs. |
41
50
  | `./catalog` | Experimental neutral catalog schemas, drafts, reads, media source packs and publication ports |
42
51
  | `./catalog/contracts` | Dependency-free catalog DTOs, media resolver, authority and canonicalization helpers |
@@ -455,458 +464,23 @@ in both resolution modes with all three optional peers absent.
455
464
 
456
465
  ## Identity import candidate
457
466
 
458
- The OIDC API adds `createOidcIdentityVerifier`,
459
- `deriveOidcPrincipalId`, `createPostgresAccountLinkStore` and
460
- `identityOidcReadiness` from `@openlup/platform/identity`, which also exports
461
- `OIDC_FAILURE_CODES` and `OidcFailureCode` for hosts. The browser-shared
462
- `@openlup/platform/identity/contracts` entry excludes OIDC refusals.
463
- Inject the host's selected JWT library through `IdentityJwtLibraryLoader`.
464
- Discovery and rotating keys load only on verification or readiness. The opt-in
465
- direct composition uses normal reconcile with an explicit email-trust policy,
466
- authenticated actor reads and live operator membership. Managed storage requires
467
- native federation sessions and refuses raw upstream-token composition. Run
468
- `node tests/postgres/runOidcIdentityConformance.mjs` from the repository root with
469
- its cached pinned issuer and local prerequisites; the [OIDC recipe](../../docs/platform/IDENTITY.md#real-issuer-oidc-recipe)
470
- owns timing, refusal, session and certification boundaries. There is no schema or
471
- default login flow change; existing static session verification and default
472
- reference/browser flows retain their runtime behaviour.
473
-
474
- The existing `IdentityJwtLibrary.jwtVerify` contract widens with optional
475
- `currentDate?: Date` and `clockTolerance?: number` verify options and now returns
476
- `{ payload: IdentityJwtClaims }`, whose claims interface includes a string index
477
- signature. **Migration:** hosts implementing or typing their own
478
- `IdentityJwtLibrary` or `IdentityJwtLibraryLoader` must accept the optional verify
479
- options and return claims as `IdentityJwtClaims`.
480
-
481
- The direct operator API adds `createPostgresOperatorIdentityStore`,
482
- `createOperatorSessionService`, `createOperatorIdentityVerifier`,
483
- `createOperatorAdminAuth` and `createOperatorAuthHandlers` to `./identity`.
484
- `./identity/contracts` exposes strict operator challenge/session schemas and
485
- `OperatorBrowserAuthPort` without a server or provider import. Challenge delivery
486
- and session signing use the existing structural ports. Operator handlers send
487
- the padded generic acceptance before awaiting mailbox delivery, keeping delivery
488
- latency out of the response. Custom HTTP composition uses the session service's
489
- optional acceptance callback for the same ordering. Restoration reads preserve
490
- in-flight browser redemption; explicit sign-out still cancels it. Active operator
491
- login needs no pending invitation and preserves membership and invitation history. The new store's public
492
- signature exposes no driver types and loads its optional driver only on use.
493
-
494
- Apply the complete direct catalogue through position 68 before enabling human
495
- login; `sql/20261018180000_identity_operator_challenges.sql` is its exact projection.
496
- Configure a distinct operator audience and an adopter deployment route policy:
497
- the public policy refuses all mounted operator routes, including `me`, with
498
- `adopter_policy_required` before input or authentication-binding construction. The reference dispatcher accepts a
499
- session-binding factory through `createReferenceBffDispatch`; use it to compose
500
- separate operator delivery. Caller-supplied operator handlers retain the same
501
- policy boundary. The browser hook consumes guarded auth-port notifications rather
502
- than signing out from a stale post-redemption check. Use the
503
- explicit bootstrap/prune commands in the [identity guide](../../docs/platform/IDENTITY.md#direct-human-login).
504
- The bounded reference browser builds do not mount the admin shell; application
505
- composition must mount the provided pages and hook. First redemption activates the existing invitation and consumes the challenge in
506
- one transaction. Live membership, not a JWT role, authorizes each request. The
507
- cold consumer includes both identity entries and the new adapter's missing-peer
508
- refusal; real concurrency and upgrade proof needs the complete PostgreSQL chain.
509
-
510
- Creating a client/contact records commerce contact data, not an authentication
511
- identity. Creating an auth user provisions a principal. Neither a typed address
512
- nor a soft-account link proves mailbox ownership; reconcile may create a contact
513
- for an existing authenticated principal without creating that principal.
514
-
515
- Customer soft-account provisioning now creates unconfirmed auth users
516
- (`email_confirm:false`). The soft-account linker retains create-first lookup,
517
- normalized duplicate recovery and conditional linking. Checkout and paid-order
518
- retry are adopter composition that must call the linker. Failed links retain
519
- the provisioned identity for retry. Customer magic links keep
520
- `shouldCreateUser:false`: captured mailbox proof confirms that existing user;
521
- unknown addresses cannot sign themselves up through this path.
522
-
523
- The managed link store requires an injected `emailTrustPolicy`, assessed before
524
- creation or linking an existing identity by email. Unsafe or unavailable settings
525
- return a non-fatal soft-account refusal (`EMAIL_TRUST_ENVELOPE_REFUSED` or
526
- `EMAIL_TRUST_SETTINGS_UNAVAILABLE`) without creating an identity or link. Matching
527
- existing links retain their replay behaviour. The reference adapter binds managed
528
- settings assessment regardless of the selected bundle.
529
-
530
- **Migration:** soft-account provisioning requires a trusted envelope: automatic
531
- email confirmation disabled and every enabled non-email provider explicitly
532
- declared email-verifying. Automatic signup can otherwise confirm an unconfirmed
533
- user and issue a session to someone who merely knows the contact's address.
534
- Pass the policy to `createSupabaseClientAuthLinkStore(client, emailTrustPolicy)`;
535
- custom `ClientAuthLinkStore` implementations must expose `emailTrustPolicy`.
536
- An adopter may explicitly bind `createVerifierResultOnlyEmailTrustPolicy()` when
537
- its own trust boundary supplies the guarantee. Ensure the selected Auth installation can deliver and verify the
538
- first-login link for an existing unconfirmed user. Run the documented joint local conformance
539
- command after building core/platform. Existing identities, contact links,
540
- subscription references and migration history are not rewritten.
541
-
542
- Managed verification now requires the confirmation timestamp. Email-derived
543
- reconcile requires an injected `emailTrustPolicy`. Managed hosts share
544
- `createManagedEmailTrustPolicy` with `identityEmailTrustReadiness`; it reports
545
- module `identity` using core's existing codes. Hosts retaining verifier-only
546
- admission explicitly bind `createVerifierResultOnlyEmailTrustPolicy()`.
547
- See the [identity policy and migration](../../docs/platform/IDENTITY.md#responsibility-and-composition)
548
- for the empty provider allowlist, cache/deadline, 403/503 failures and the opt-in
549
- `node tests/postgres/runIdentityEnvelopeConformance.mjs --phase email-trust` and
550
- `node tests/postgres/runIdentityEnvelopeConformance.mjs --phase joint` prerequisites
551
- and conformance scope. Both phases replay the committed managed chain, including
552
- the identity capabilities forward, and prove association under the shipped managed
553
- reference grants.
554
- Construction and imports perform no settings read or SDK initialization.
555
-
556
-
557
- The `./identity` server entry imports the existing identity engine, authorization,
558
- profile readers, session adapters and BFF factories into one capability. Its
559
- `./identity/contracts` entry is declared browser-safe and currently exposes the
560
- customer auth port, value types and provider parser. Runtime-schema extraction
561
- is pending the contracts-boundary choice described in the
562
- [identity owner page](../../docs/platform/IDENTITY.md). Both entries remain an
563
- unpublished development-preview import candidate.
564
-
565
- Application composition supplies SDK clients/loaders, feature admission, redirect
566
- policy, rate limiting, messages, sender and deployment URLs. Factories use the
567
- foundation BFF envelope and accept the application's observed request-ID reader.
568
- The module depends only on the declared foundation entries, without changing the
569
- artifact's rail kind. Its schema contribution requires a host-selected inventory;
570
- its seven SQL files are verbatim projections at canonical positions 11, 12, 29,
571
- 60, 67, 68 and 69, not an independent migration chain. The owner page maps the characterization
572
- witnesses and records the remaining evidence.
573
-
574
- Identity authorizes stored operator roles only when they are `admin` or
575
- `distributor`; null and unknown values return `FORBIDDEN` (HTTP 403). The reference
576
- admin-profile reader reports those rows as non-admin, and admin magic links do
577
- not send for them while retaining the generic accepted response, padding and
578
- quota. Adopters needing legacy role mapping must do it in their own adapter
579
- before passing the role to the package; there is no package fallback option.
580
-
581
- Both managed contact stores match normalized addresses case-insensitively,
582
- including mixed-case stored contacts. Literal matching uses an anchored, escaped
583
- `imatch` database prefilter plus an exact `email.trim().toLowerCase()` comparison
584
- with the normalized input. Unicode regex folding alone cannot establish identity.
585
- Both lookups select email and fetch at most ten candidates. Soft linking selects
586
- the oldest accepted contact; reconciliation requires a single accepted contact
587
- and throws the existing multiple-row error if several remain. A match beyond the
588
- candidate bound is not selected. Injected structural query clients must implement
589
- `filter(column, "imatch", value)` and the bounded array query. The direct customer
590
- challenge already compares `lower(email)` to normalized input; no migration is
591
- needed.
592
-
593
- Magic-link factories default to the shared strict origin resolver. Pass
594
- `redirectOrigins: ["https://example.com", "https://secondary.example.com:8443"]`;
595
- the first configured origin builds the callback and every entry must be an exact
596
- HTTP(S) origin, without credentials, a path, query or fragment (a root trailing
597
- slash is accepted). Request headers never authorize an origin. Missing or invalid
598
- configuration returns BFF `UPSTREAM_UNAVAILABLE` (503), with details reason
599
- `magic_link_origin_not_configured`, before provider access. Customer `returnTo`
600
- is normalized to a same-origin relative path; unsafe targets are omitted for the
601
- callback's default landing page. An adopter needing its existing policy can keep
602
- passing `resolveOrigin`; that explicit injection overrides the default. See the
603
- [identity redirect contract](../../docs/platform/IDENTITY.md#magic-link-redirect-policy)
604
- for reference configuration and migration.
605
-
606
-
607
- ### Operator invitations
608
-
609
- `identity/contracts` exposes distinct direct pending-invitation and managed
610
- active-membership results. Matching runtime schemas remain in the server entry.
611
- The admin-action port returns `OperatorInviteResult`, a union of those results.
612
-
613
- `createDirectOperatorInvitationStore` provides the new pending, active, expired and
614
- revoked lifecycle in its trusted PostgreSQL transaction lane, including ordered
615
- membership locks and atomic membership/audit writes. Direct callers inject the
616
- store and a parsed TTL. `createDirectOperatorIdentityDirectory` commits principal
617
- creation/reuse separately. Apply the complete direct catalogue through position
618
- 67; the byte-identical package projection is for inspection and composition, not
619
- a standalone fresh chain. Direct human acceptance is a trusted command,
620
- unmounted from machine authentication.
621
-
622
- Managed hosts use `createInviteAdminUserGateway().activateMembership` in
623
- `runInviteAdminUserUseCase`. Insertion immediately creates active `admin_users`
624
- membership with invitation provenance; it omits `is_machine_actor` and relies on
625
- the database default false. The result carries principal ID, role, active state
626
- and `MEMBERSHIP_CREATED` or `ALREADY_ACTIVE`, without pending timestamps or an
627
- invitation ID. Identical repeats and concurrent insert losers return the existing
628
- membership without a second notification. Role differences conflict; revoked
629
- members refuse with `MANAGED_REINVITE_UNSUPPORTED`. Failures retain identities
630
- for retry, including after notification failure. No Auth metadata store,
631
- first-entry acceptance, expiry or compensation is used. Magic-link eligibility
632
- requires active membership and a known role. Authorization, removal and admin
633
- `me` retain their actor bindings without a service key for lifecycle state.
634
-
635
- **Migration:** direct hosts can adopt the new pending lifecycle by applying
636
- `20261018120000_identity_operator_invitations` after the existing direct catalogue
637
- and composing the store, trusted human acceptance and parsed TTL. Existing
638
- memberships are preserved. Managed immediate activation is retained; managed
639
- consumers must now parse the structured active result and its
640
- `MEMBERSHIP_CREATED`/`ALREADY_ACTIVE` codes instead of message-only success.
641
- Duplicates return existing membership rather than `ALREADY_ADDED`, and
642
- notification failure returns 503 `INVITATION_NOTIFICATION_FAILED` while keeping
643
- membership active. Managed pending/expiry remains a compatibility gap requiring
644
- a separate transactional store and reviewed forward. No managed SQL, RPC or SDK
645
- dependency is added. See the
646
- [identity lifecycle owner](../../docs/platform/IDENTITY.md#operator-invitation-lifecycle).
647
-
648
- The managed chain's identity capabilities forward
649
- `20261018160000_identity_service_role_capabilities.sql` grants `service_role`
650
- SELECT(email) and INSERT(id, email, role, membership_state, membership_provenance,
651
- membership_accepted_at) on `admin_users`.
652
- The opt-in managed conformance replays the complete committed chain with ambient
653
- default ACLs removed and adds no grant. It proves immediate activation,
654
- repeat/concurrent recovery and revoked-member refusal under the shipped grants.
655
- It does not establish managed/direct lifecycle parity.
656
-
657
- With a fresh local managed stack running, export its API URL, service key and
658
- PostgreSQL owner (`supabase_admin`) connection URL as `OPENLUP_MANAGED_CONFORMANCE_URL`,
659
- `OPENLUP_MANAGED_CONFORMANCE_SERVICE_KEY` and
660
- `OPENLUP_MANAGED_CONFORMANCE_DATABASE_URL`, then run from the repository root:
661
-
662
- ```sh
663
- npm --workspace @openlup/platform run build
664
- node --import tsx tests/postgres/operatorInvitationManaged.conformance.ts
665
- ```
666
-
667
- The module refuses non-loopback endpoints, an already-installed public schema,
668
- and existing synthetic fixture identities. It retains synthetic identities;
669
- discard the disposable stack after the run. Its database owner connection must
670
- be able to replay the committed prerequisites, baseline and ordered forwards.
467
+ `./identity` provides server identity services; `./identity/contracts` supplies browser-shareable contracts.
468
+ See the [identity recipe](../../docs/platform/PLATFORM_PACKAGE_REFERENCE.md#identity-import-candidate) for composition, migrations and operator invitations.
671
469
 
672
470
  ## Personal data
673
471
 
674
- The experimental identity entry exposes `createPersonalDataContributorRegistry`,
675
- `createIdentityPersonalDataContributor`, `createPrivacyOperations`,
676
- `exportPersonalData`, `erasePersonalData`, `createPrivacyExportHandler` and
677
- `createPrivacyEraseHandler`. Browser requests, versioned contributor documents and
678
- receipts come from `@openlup/platform/identity/contracts`. Register every required
679
- contributor explicitly, with its owned fields, assessment, erase/retain operation
680
- and remote/retention obligations. Missing required owners refuse erasure; export
681
- reports missing coverage. This capability erases identity-owned data only.
682
-
683
- Direct hosts compose `createPostgresPrivacyUnitOfWork` and
684
- `createPostgresPrivacyStore` with the identity contributor and their domain vetoes
685
- using the same executor. The reference subscriptions contributor holds a relation
686
- lock through commit, refusing active and paused subscriptions without changing
687
- them. `IdentityPrivacyPolicy.retainedContactFields` names explicit retention
688
- owners for populated opaque columns; unknown ownership refuses. The reference
689
- retains lifecycle/acquisition fields, and refuses nonempty metadata or external
690
- references until an owner declares their disposition.
691
-
692
- Migration: apply the complete direct catalogue including
693
- `20261018190000_identity_privacy_erasures` before using privacy or the updated
694
- customer challenge store. The forward appends one receipt table at position 69
695
- without grants or stored functions. Direct erasure pseudonymizes identity-owned
696
- fields, deletes challenges and outstanding invitations, uses guarded membership
697
- revocation, clears only the selected contact's direct principal link, and commits
698
- an idempotent audit receipt atomically. Contact IDs and managed auth links never
699
- change. Failed writes roll back. Retained audit references are pseudonymous, not
700
- an assertion of anonymization. Already-issued tokens may remain valid until expiry.
701
- Customer challenge issuance and redemption reject a receipt for either the
702
- contact or principal under the same transaction-held challenge relation lock as
703
- erasure, including late challenges inserted after cleanup. Hosts must use the
704
- package adapters; calling the retained raw challenge SQL functions directly
705
- bypasses this erasure receipt fence.
706
-
707
- Managed hosts compose `createManagedPrivacyStore` with their structural service
708
- client for export. Coverage explicitly excludes inaccessible country and
709
- identity history/receipt fields. Managed erasure refuses with
710
- `MANAGED_PRIVACY_ERASURE_UNAVAILABLE`: a managed adopter must provide its own
711
- erasure path. The committed grants and structural client cannot provide the
712
- required mutations and atomic receipt. This change adds no managed forward or
713
- erasure grants. Provider identity is never deleted; any future composed provider
714
- erasure must disable it after committed local success. The managed refusal here
715
- occurs before reaching any provider mutation.
716
-
717
- The reference mounts authenticated human-admin export/erase POSTs and an Admin
718
- Settings readback panel. Direct and managed mounts require the adopter deployment
719
- policy under admin mutation risk; the public default returns
720
- `adopter_policy_required` before body/query reads, and direct mounts also refuse
721
- before authentication or privacy binding construction. The shipped dispatcher mounts
722
- both routes for managed bundles even when its caller supplies no routes. After admission,
723
- exports and receipts stay in memory with no-store
724
- responses. The maintenance `prune-challenges` command prunes separate bounded
725
- operator/customer batches. See [identity privacy](../../docs/platform/IDENTITY.md#identity-personal-data)
726
- for refusals, token limits and retained obligations.
727
-
728
- The complete direct conformance is included in
729
- `npm test -- tests/postgres/operatorIdentityUpgrade.test.ts` from the repository
730
- root after normal setup. A maintainer host resource broker is optional.
731
- For managed conformance, supply the same three `OPENLUP_MANAGED_CONFORMANCE_*`
732
- local disposable-stack settings described for operator invitations, with
733
- `OPENLUP_MANAGED_CONFORMANCE_DATABASE_URL` set to the database owner
734
- (`supabase_admin`) connection, then run
735
- `node --experimental-strip-types tests/postgres/identityPrivacyManaged.conformance.ts`.
736
- Use a fresh public schema and reset the disposable stack between proofs; the
737
- managed privacy proof replays the committed chain without fixture erasure grants.
738
- Replay through node-postgres omits only the psql-only pg_dump `\restrict` and
739
- `\unrestrict` guard lines; all migration SQL remains unchanged.
740
-
741
-
742
- Reference startup can compose identity through `createReferenceIdentity` and
743
- `startPublicReferenceServer({ identity })`, with explicit route functions,
744
- selected adapters, trusted schema/migration observation and the bundle-specific
745
- inventory. It consumes email-trust readiness before listening, and rejects
746
- unknown or unsatisfied candidates. Direct reconcile reads the existing actor
747
- link; direct invite/remove select the pending-invitation stores. The default
748
- static reference and browser page sets remain unchanged. See the
749
- [composition contract](../../docs/platform/IDENTITY.md#responsibility-and-composition).
750
-
751
- Both identity entries already belong to the public export map and build output;
752
- artifact availability does not imply registry publication or release activation.
753
- The cold installed-artifact consumer imports both entries without optional peers,
754
- compiles contracts without driver types, and invokes every driver-owning direct
755
- identity adapter for its missing-peer refusal. Managed factories consume
756
- structural clients. Server declarations compile after peer selection. Full
757
- installed-artifact and database proof require the existing supervisor-run package
758
- consumer command and an owned disposable database.
759
-
760
- The managed identity service-role capabilities forward in the committed chain
761
- supplies client link writes and operator membership insert/email-read
762
- permissions; the local conformance runners add no fixture grant. No migration is
763
- added by reference composition.
764
-
472
+ The experimental identity entry provides contributor-based personal-data export and erasure.
473
+ See the [personal-data recipe](../../docs/platform/PLATFORM_PACKAGE_REFERENCE.md#personal-data) for composition, refusals and retained obligations.
765
474
 
766
475
  ## Disposable identity composition
767
476
 
768
- The reference application's opt-in identity shell consumes the existing
769
- `./identity` and `./identity/contracts` exports without changing their maturity
770
- or publishing a registry version. It reuses the customer/operator pages and
771
- captured-delivery port, with a private local mailbox and administrator privacy
772
- panel. Its explicit runtime must supply actual routes and readiness admission;
773
- the shell refuses a missing composition. See the
774
- [identity reference guide](../../docs/platform/IDENTITY.md#disposable-identity-reference)
775
- for both-bundle commands, the committed managed grants and installation limits.
477
+ The opt-in reference identity shell consumes the existing identity exports with explicit routes and readiness admission.
478
+ See [disposable identity composition](../../docs/platform/PLATFORM_PACKAGE_REFERENCE.md#disposable-identity-composition) for its boundary and reference guide.
776
479
 
777
480
  ## Communications import
778
481
 
779
- For adoption from `0.16.0`, follow the [0.17.0 Migration guidance](CHANGELOG-HISTORY.md#0170) (source repository)
780
- for the complete package set, canonical SQL installation and host-owned bindings.
781
-
782
- M04 P1 relocates the existing public implementation without changing its reference
783
- outputs. The reference shims bind the existing renderer, egress resolver, origin
784
- defaults, delivery-shaping policy and receipt transaction lane. The package does
785
- not select a deployment, mount a route or schedule another executor.
786
- `createCommunicationsContribution` requires the host's version and schema
787
- inventory; supplied handlers, schedules, routes and ports describe only that
788
- host's selection.
789
-
790
- `COMMUNICATIONS_MIGRATIONS` projects three existing canonical units byte for byte:
791
- comms outbox (position 2), transactional receipts (9) and communications control
792
- plane (20). This sparse list is not a standalone migration chain. Apply the full
793
- selected canonical prefix through its existing runner; importing the module does
794
- not apply SQL or advertise missing managed/direct capabilities.
795
-
796
- Browser previews retain their reference renderer, content defaults and template
797
- policy. The Auth hook receives that same renderer through its reference shim.
798
- The standard Auth webhook and the maintained HTTP mail provider verifiers keep their separate signature
799
- formats and clocks; the generic helper added to `./http` replaces neither.
800
- E2 is applied: the payment webhook module delegates these five generic helpers.
801
- `readRawBody` is the same package function object with its original declared
802
- `(req: VercelRequest) => Promise<string>` signature; the other four exports are
803
- plain re-exports. The payment provider verification and its replay tolerance stay local.
804
- The maintained mail transport, credentials, callback verification/mapping,
805
- poll read/mapping, timeline and send-ledger adapters remain in reference source.
806
- B1 changes only the four raw-injection guard sites in the reference delivery
807
- adapters and the approved invoice test fixture; the concrete client and all
808
- other vendor-directory files retain the comparison-base bytes. The neutral
809
- `./communications` entry exposes transport/result contracts and capture without
810
- concrete mail wiring. The platform ships opt-in vendor adapter subpaths;
811
- default wiring includes none, and adopters choose or replace them.
812
- The type-only `CommunicationProvider<Provider>` and `EmailMessageReadModel<Provider>`
813
- accept host vocabulary; reference types retain their original literal.
814
-
815
- `createGuardedEmailTransport({ providerKind, policy, send })` is the network
816
- adapter constructor. At each send it awaits a fresh host policy, validates the
817
- whole decision and enters the sender only for a coherent `production`,
818
- `sandbox_direct` or `sandbox_sink` decision. The sender receives the effective
819
- recipient and a frozen context with intended recipient, host-selected credential,
820
- mode, reason and environment tag. It must consume that context when selecting
821
- credentials and emitting provider metadata. The neutral entry reads no environment
822
- and contains no provider client. Opt-in adapters consume this same guard and
823
- host-selected context. Missing/throwing policy, unknown modes, mismatched
824
- recipients/tags, blank credentials or unsupported fields fail with status zero,
825
- no identifier and bounded error codes. Reasons are lower-case token codes up to
826
- 80 characters. One unadorned `local@domain.example` recipient mailbox is required;
827
- whitespace, lists, display names and local-only domains are refused on this new
828
- alternate-transport policy boundary.
829
-
830
- `blocked` returns failure; explicit `drop` returns terminal suppression with no
831
- send, no identifier and an ineligible sent timestamp. Arbitrary injected objects
832
- are refused by `guardEmailTransport`; a provider name or copied capture shape
833
- cannot establish admission. Package-created capture retains its ordered envelopes
834
- and synthetic identifiers with no network effect. Capture acceptance remains
835
- local acceptance, never evidence of external delivery.
836
-
837
- `admitPolicyEnforcedEmailTransport` is the trusted host bridge for a transport
838
- that already enforces egress internally, as the maintained reference client does.
839
- The package registry owns its identity; this bridge performs no second routing
840
- or credential decision. It is a composition trust boundary, not a sandbox for
841
- host code: callers must not admit an unguarded network sender. New adapters should
842
- use the validating constructor above. The reference default retains its original
843
- sink/drop policy, key selection, optional payload fields, headers, signal and
844
- result/error semantics. `createAuthEmailTransport` preserves the Auth envelope
845
- mapping and admission; raw Auth closures are refused before their send method.
846
- Admin/control and required-audit ordering stay at their existing callers.
847
- No egress result authorizes a subscription-state or delivery-cycle mutation.
848
-
849
- `processEmailObservation` accepts already authenticated, decoded and environment-admitted
850
- input. It looks up a local send, constructs evidence only when found, applies one
851
- observation and records best-effort telemetry. Lookup and apply failures propagate.
852
- `runDeliveryReconciliation` polls candidates sequentially, rotates every non-404
853
- read, and delegates consecutive failed-404 abandonment at threshold three to the
854
- store. Rotation failure is counted without dropping delivery truth; apply failure
855
- continues the batch. Candidate-query, read and not-found-store throws propagate.
856
- The host classifies protocol snapshots after rotation and passes recipient/evidence
857
- through unchanged. The existing reference domain entry points use both engines;
858
- routes and schedules remain the same.
859
-
860
- Neither engine writes suppression or subscription state. Both callback and poll
861
- composition retain `communication_update_email_delivery_from_provider` as the
862
- single durable convergence/suppression writer. These ports add no in-memory
863
- replay authority, database store or second executor. Legacy transport identifier
864
- fields remain compatibility residue; new ports use `messageId`.
865
-
866
- `createCommunicationPurposeRegistry` composes immutable exact identifiers with a
867
- mandatory `marketing` or `non-marketing` classification. Its built-ins cannot be
868
- redeclared. Existing policy, token and newsletter schema calls keep their finite
869
- defaults. Passing a registry to policy evaluation, token verification or
870
- `createNewsletterIntegrationSchemas` recognizes its identifiers without granting
871
- permission. Custom marketing purposes still require a grant and respect
872
- suppression; built-in tester pause and permission rules stay in force.
873
-
874
- `createRegisteredPublicPreferenceTokenPort` requires an explicit host persistence
875
- function and passes the exact purpose and frozen classification to it. It creates
876
- no sync event. Stock managed permission and newsletter-sync tables retain their
877
- finite CHECKs; registering a purpose does not expand those stores. Additional
878
- persistence and schema requirements belong to the adopter. No new purpose, table,
879
- migration, preference UI, URL or default fan-out is installed.
880
-
881
- `processNewsletterWebhookEvent` supplies the existing event → permission →
882
- settlement sequence behind neutral ports. Reference HTTP admission and signature
883
- mapping stay local. Explicit registered purposes survive permission writes;
884
- purpose-less feedback still targets exactly the two existing marketing purposes.
885
- Verified opt-in, suppression and replay rules are unchanged. Registered dispatch
886
- checks the batch's purposes before provider effects and retains claim tokens,
887
- echo and capability skips, retry behavior and counters. Sync-settled and accepted
888
- receipts are distinct from delivered evidence. Existing job admission, binding,
889
- leases, finish-run linkage and operator readback retain their host composition.
890
- No shared sender pipeline is added: required audit and best-effort send paths
891
- keep their distinct ordering and failure semantics.
892
-
893
- `MarketingAutomationProviderPort` and `MarketingAutomationStorePort` describe
894
- explicit scope, claim/generation fences, admission/deadlines, idempotency,
895
- observations/history and ambiguity/backoff. The synthetic substitute-provider
896
- conformance suite is an executable contract, not an installed worker or a remote
897
- provider certification. Evidence carries opaque references; remote feedback does
898
- not grant local consent. No trigger catalogue, schedule or provider activation
899
- is supplied.
900
-
901
- The required selection retains the moved-code witnesses and neutral package
902
- characterization tests. The package W1 hook reader witnesses the Auth composition;
903
- provider-specific reference test paths remain in the complete suite. The
904
- [M04 ledger](../../docs/platform/plans/oss-m04-communications-triggers.md#verification-ledger)
905
- records counted cases, separate boundary iterations and local timing provenance.
906
- The clean-commit artifact gate enforces all merged caps, including the owner-read, measured limit on the complete contracts entry. Artifact admission still needs a
907
- clean supervisor commit.
908
- See the [M04 plan](../../docs/platform/plans/oss-m04-communications-triggers.md)
909
- for scope, later waves and verification limits.
482
+ `./communications` provides host-composed communications services; `./communications/contracts` supplies pure content and browser contracts.
483
+ See the [communications recipe](../../docs/platform/PLATFORM_PACKAGE_REFERENCE.md#communications-import) for migration, email rendering and native operations.
910
484
 
911
485
  ## Package artifact limits
912
486
 
@@ -919,145 +493,6 @@ for measured sizes, current caps, the S1 baseline refresh and refusal remedies.
919
493
  Artifact checks require a clean package directory; `--manifest-only` supplies no
920
494
  artifact evidence.
921
495
 
922
- ### Pure email content and browser rendering
923
-
924
- `./communications/contracts` now exports `renderEmail`, the nine existing block
925
- constructors, `guideCardBlock` and per-template content factories.
926
- Each `create*ContentFactory` binds host-selected copy plus only the pure helpers
927
- it uses: order-reference formatting, payment-cause wording or marketing footer.
928
- Localized review routes remain in the reference helper.
929
- In P3 no copy pack, brand, locale choice, environment reader, provider client or
930
- sending policy moved into this entry. B2 adds the explicit neutral defaults below. The existing email DTOs remain its canonical types.
931
- Reference `petName` compatibility inputs are translated to neutral `contextName`
932
- by their existing facades. Complete subjects, preheaders, ordered blocks and
933
- HTML/text remain byte-identical, including omitted and explicit optional fields.
934
-
935
- Browser previews continue through those facades to contracts values only. Auth
936
- uses its existing server hook factory with the same host renderer injection;
937
- its inactive public content binding still refuses with 503. Standard Auth,
938
- mail callback, integration-event and generic HTTP verification remain separate
939
- policies with their existing raw-byte and replay behavior. No crypto refactor,
940
- new entry, dependency or graph edge is part of P3.
941
-
942
- The contracts conformance test compiles the complete entry with no Node types
943
- and checks its value inventory and import closure. The cold consumer repeats the
944
- exact inventory and renders with fetch forbidden and optional peers absent,
945
- retaining all other consumer checks. Browser size measures every runtime export
946
- of the built entry through `browserEntryBytes`; it never measures a tree-shaken
947
- consumer subset. See the M04 ledger for the measured size, owner-read cap and
948
- working-tree versus signed-candidate evidence limits.
949
-
950
- P3's complete built contracts entry measures **5,992 B minified+gzip**;
951
- the owner-read cap is **11,776 B**, rounded to the nearest 512 B at about
952
- twice that measurement. Only this browser limit changes. Existing pack classes,
953
- other browser limits and clean-candidate artifact checks remain binding.
954
-
955
-
956
- M04 B2 adds four default factories in this same pure contracts entry:
957
- `createInvoiceDocumentContentFactory`, `createRoleNotificationContentFactory`,
958
- `createInquiryContentFactory` and `createOptOutContentFactory`. Each accepts an
959
- optional complete bilingual copy replacement; each content call takes an explicit
960
- `pl`/`en` locale. Invoice takes resolved order/document references, kind and account
961
- URL; role takes role, operator URL and optional pending expiry. Inquiry builds
962
- receipt/notice blocks from caller details, signoff and URL without sending or
963
- promising a response time. Opt-out returns display strings for seven outcomes;
964
- it verifies no token and writes no consent. Hosts own brand, locale selection,
965
- URLs, policy and delivery.
966
-
967
- The reference invoice adapter defaults to its byte-identical Polish output; explicit
968
- construction locale EN maps to `/account?sekcja=orders`, while the production
969
- composition still omits locale. All existing role outputs remain identical.
970
- The reference opt-out page now selects Polish/EN throughout, escapes display values,
971
- and uses neutral selected-communications/service-message wording. Its methods,
972
- verification, purpose allowlist, durable RPC, headers and statuses are unchanged.
973
- Generic inquiry is only a pure binding; the existing private-label sender stays
974
- unchanged. Invoice/role gain Polish canon previews; no sender/registry activation.
975
- See the [complete B2 expectation ledger](../../docs/platform/plans/oss-m04-communications-triggers.md#b2-changed-expectations-and-preservation)
976
- and [default conformance tests](test/communications/contentDefaults.test.ts).
977
- The measured B2 contracts entry is 8,210 B minified+gzip under the unchanged
978
- 11,776 B cap; this is a local built-entry measurement, not release admission.
979
-
980
- ### Native communications operations (development preview)
981
-
982
- The server entry `@openlup/platform/communications` additionally exports
983
- `createPostgresDeliveryReconciliationPort`, `createPostgresCommunicationSyncStore`,
984
- `createPostgresNotificationRecipientsPort` and `createPostgresConsentEvaluator`.
985
- Install the complete canonical manifest through position 70 before selecting
986
- these factories. `COMMUNICATIONS_MIGRATIONS` now projects positions 2, 9, 20 and
987
- 70; it remains a sparse inventory, not an installer. Missing schema/configuration
988
- is an error, never an empty queue or an allow decision.
989
-
990
- Each factory owns a capability-local, role-free transaction lane and lazy `pg`
991
- pool, with `close()` for host cleanup. Only a trusted host may hold these owner
992
- credentials. Effective PUBLIC/browser table, column and inherited privileges
993
- cause refusal; the CREATE-only forward intentionally changes no old ACL or role.
994
- Do not expose a factory or its database lane to browser callers. The directory
995
- requires a verified active human operator, live communications authorization and
996
- absence of machine authority in the same transaction as each operation.
997
-
998
- A host registers `evidence: "network_accepted"` only after actual network
999
- acceptance, with a unique source key and binding. Capture is never registered as
1000
- a network target. Polls and admitted callbacks call the same `applyObservation`
1001
- writer; observation-key replay must preserve the immutable fact. Readback keeps
1002
- acceptance, observed state, attempt/404 counts and abandonment separate. Only
1003
- three consecutive 404s abandon a candidate; abandonment is not failed/delivered.
1004
- Bounce/complaint writes the authoritative fingerprint's suppression atomically.
1005
- No subscription or renewal state is changed.
1006
-
1007
- For newsletter sync, inject `rail: db => createPostgresOutboxStore(db)` from
1008
- `@openlup/outbox/postgres` in host composition. `db` is the current transaction,
1009
- so claim commits before I/O and rail ack and sidecar settlement commit together.
1010
- `registerWork` creates one existing-outbox identity for a source and target
1011
- binding. Successive preferences for one contact/target share the rail aggregate,
1012
- so newer work cannot overtake older unprocessed work. Configure a distinct
1013
- binding per target/provider, and exclude
1014
- `nativeSyncEventType(binding)` from generic dispatch registries. Invoke only
1015
- `runCommunicationSyncDispatch` with an explicit neutral membership provider.
1016
- The sidecar cannot claim work and has no lease or drainer. A membership settlement
1017
- records sync progress, never email delivery; remote exactly-once effects are not
1018
- promised across a process crash. There is no automatic backfill or activation.
1019
-
1020
- Native consent evaluates registered P4 policy against the authoritative native
1021
- permission and persists the decision before returning its id. Admin notices need
1022
- an active source-bound notification recipient. Optional `testerPaused` reads
1023
- host-owned authoritative pause state inside that transaction; omitted means no
1024
- pause policy. The capability explicitly declares `managedFrequencyCaps: false`,
1025
- `legacyContactTouching: false`, `sendTimeFence: false`: managed 16-hour / 2-day /
1026
- 4-week caps and legacy contact-touching are excluded. Evaluation does not fence a
1027
- later send against changed consent. Custom-purpose persistence remains host-owned.
1028
-
1029
- The [callable reference binding](../../server/runtime/communications/communicationsLifecycleBinding.ts)
1030
- selects only `PLATFORM_BUNDLE=node-postgres` with `DATABASE_URL`; it closes pools
1031
- on success/failure and supplies no network/provider default. Existing managed
1032
- HTTP cron/UI routes retain their existing composition. See the
1033
- [B3 expectation inventory](../../docs/platform/plans/oss-m04-communications-triggers.md#b3-native-stores)
1034
- and [real-PG witnesses](../../tests/postgres/communicationsNative.test.ts).
1035
-
1036
- C-compose extends the native delivery store with binding-scoped `findByMessageId`
1037
- and bounded `listEvidence({limit})` (1–100). Existing observation/replay and polling
1038
- methods retain their behavior. `registerPreference` on the native sync store
1039
- commits an authoritative denied/suppressed permission and default-purpose sync
1040
- work together; tester permission adds no marketing work. The trusted
1041
- `registerNativeSyncWorkInTransaction` helper contains the same registration SQL
1042
- used by `registerWork`; standalone input validation still precedes database entry.
1043
- This helper is a trusted host transaction seam: its caller-owned executor must
1044
- already hold the lane's safe effective privileges; the function does not check
1045
- them. Standard native sync factory transactions check
1046
- `public.communication_native_sync_state`, `public.outbox_events` and
1047
- `public.communication_recipient_consents`. The caller owns the transaction and
1048
- must commit or roll back permission, sidecar and existing-rail effects atomically.
1049
- Use a safely admitted lane; the helper grants no claim token or provider authority.
1050
-
1051
- Reference composition remains outside the package: actual installed factories,
1052
- Core readiness, explicit provider ports and host-supplied schedules. Native
1053
- readback and signed-token bridges do not promise managed frequency caps, legacy
1054
- tester-source parity, a send-time fence or a full native network sender. The
1055
- opt-in browser profile adds no npm entry or UI package export.
1056
-
1057
- Source contributors record notable changes in package changelog fragments;
1058
- release preparation assembles the shipped CHANGELOG. Follow the
1059
- [public contribution guide](https://github.com/openlup/openlup/blob/main/CONTRIBUTING.md#development-preview-checks).
1060
-
1061
496
  ## Payments prepared attempts
1062
497
 
1063
498
  `@openlup/platform/payments` exports `executePreparedAttemptMechanism`: validate before durable preparation, refuse prepared replay, dispatch once unless the host proves no charge, and retry one immutable local finalization. The host owns keys, fingerprints, nominal error trust and persistence. An unknown execution never authorizes redispatch. The reference facade retains its existing binding.
@@ -1241,3 +676,38 @@ readiness and simulator composition. Its prepared-attempt facade re-exports the
1241
676
  same error class so `instanceof` preserves the no-charge trust boundary.
1242
677
  The M07 factories' neutral envelopes and capability limits remain unchanged;
1243
678
  reference-support availability establishes no provider activation or stable API.
679
+
680
+ ## Fulfillment stock evidence replay
681
+
682
+ The fulfillment migration projection additionally includes the native
683
+ `20261022165000_fulfillment_stock_evidence_replay_reopen.sql` forward at canonical
684
+ position 76 in this catalogue. Install the complete chain before stock adoption.
685
+ Matching-fingerprint alert replay refreshes severity/quantities, reopens status and
686
+ clears `resolved_at`, preserving identity, first-seen time and historical resolver
687
+ metadata. Mismatches, including legacy 0.22.0 whole-input fingerprints, still
688
+ raise `23505`; no rewrite is supplied. See the [migration owner](../../docs/platform/DATA_AND_MIGRATIONS.md#module-catalogues-and-installed-histories)
689
+ for upgrade and operational-dependency proof.
690
+
691
+ ## Subscription contracts and renewal port
692
+
693
+ The experimental `./subscriptions` entry exposes hosted renewal mechanisms and
694
+ an explicit native capability descriptor; `./subscriptions/contracts` is
695
+ browser-safe and type-only. The [subscription architecture and recipe](../../docs/platform/ARCHITECTURE_AND_EXTENSIONS.md#subscription-contracts)
696
+ owns the concrete ports, continuity rules, capability refusals and witnesses.
697
+ These entries do not install a store or activate a runner.
698
+
699
+ Native renewal and N1 retry stores accept injected SQL executors and explicit
700
+ snapshot/identity policy. The generic native executor consumes the subscription
701
+ ports; host composition supplies G23, M07, transaction and correlation policy.
702
+ The initial binding preserves case-before-outcome ordering with atomic bookkeeping.
703
+ N1's same-obligation recovery repairs the native decline stall (F16); it does not
704
+ supply M07 prepared-store parity or activate a runner.
705
+
706
+ ## Invoicing contracts
707
+
708
+ Experimental `@openlup/platform/invoicing` exposes `readInvoiceCapabilities`;
709
+ `invoicing/contracts` contains types only. Host selection is explicit and default-off.
710
+ Frozen facts, correction recovery, exact absence, owed duties and host-only fiscal,
711
+ artifact, delivery, payout and owned-history seams are described in the
712
+ [invoicing reference](../../docs/platform/PLATFORM_PACKAGE_REFERENCE.md#invoicing-contracts).
713
+ No store, worker, adapter, route or activation is supplied by this contract step.