@openlup/core 0.21.0 → 0.22.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 +20 -343
- package/README.md +9 -1
- package/dist/fulfillment/currentFulfillmentParcel.d.ts +16 -0
- package/dist/fulfillment/currentFulfillmentParcel.js +70 -0
- package/dist/fulfillment/index.d.ts +16 -0
- package/dist/fulfillment/index.js +8 -0
- package/dist/fulfillment/normalizedFulfillmentFacts.d.ts +14 -0
- package/dist/fulfillment/normalizedFulfillmentFacts.js +18 -0
- package/dist/fulfillment/statusMap.d.ts +220 -0
- package/dist/fulfillment/statusMap.js +214 -0
- package/dist/fulfillment/trackingProgress.d.ts +4 -0
- package/dist/fulfillment/trackingProgress.js +42 -0
- package/dist/fulfillment/types.d.ts +15 -0
- package/dist/fulfillment/types.js +1 -0
- package/package.json +1 -1
- package/release-gates.json +7 -3
- package/src/fulfillment/currentFulfillmentParcel.ts +94 -0
- package/src/fulfillment/index.ts +20 -0
- package/src/fulfillment/normalizedFulfillmentFacts.ts +52 -0
- package/src/fulfillment/statusMap.ts +253 -0
- package/src/fulfillment/trackingProgress.ts +49 -0
- package/src/fulfillment/types.ts +12 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,10 +4,29 @@ Notable changes to this public-source workspace package are recorded here.
|
|
|
4
4
|
“Unreleased” means no npm version carries the change yet; immutable OpenLup
|
|
5
5
|
source previews may already ship some changes, and their release notes say
|
|
6
6
|
which. Declaration snapshots track candidate drift, not a stable API or
|
|
7
|
-
SemVer promise.
|
|
7
|
+
SemVer promise. This file keeps the current release and the two previous ones;
|
|
8
|
+
earlier releases are recorded verbatim in `CHANGELOG-HISTORY.md`
|
|
9
|
+
in the source repository.
|
|
8
10
|
|
|
9
11
|
## [Unreleased]
|
|
10
12
|
|
|
13
|
+
## [0.22.0]
|
|
14
|
+
|
|
15
|
+
- Publishable on the npm `latest` dist-tag as `0.22.0`, from tag
|
|
16
|
+
`openlup-core-v0.22.0`.
|
|
17
|
+
|
|
18
|
+
- Add pure fulfillment status, tracking-progress, current-parcel and canonical
|
|
19
|
+
fact-parser helpers to the existing `./fulfillment` subpath. Generic fact types
|
|
20
|
+
retain their names; narrowed fact aliases use the `Canonical` prefix. New
|
|
21
|
+
fulfillment re-exports carry the same `@beta` annotation as existing facts.
|
|
22
|
+
|
|
23
|
+
Migration: existing platform fulfillment imports remain compatible re-exports;
|
|
24
|
+
no application import or schema migration is required.
|
|
25
|
+
|
|
26
|
+
### Known limitations
|
|
27
|
+
|
|
28
|
+
- `FULFILLMENT_STATUS_MAP` carries localized adopter labels. Replace them with host-injected labels or internationalization keys before any stable-framework claim.
|
|
29
|
+
|
|
11
30
|
## [0.21.0]
|
|
12
31
|
|
|
13
32
|
- Publishable on the npm `latest` dist-tag as `0.21.0`, from tag
|
|
@@ -25,345 +44,3 @@ Migration: upgrade the complete set together; core introduces no API or schema m
|
|
|
25
44
|
- Republish unchanged APIs, runtime behavior and shipped SQL from `0.19.0` as part of the complete `0.20.0` package set.
|
|
26
45
|
|
|
27
46
|
Migration: upgrade the complete set together; core introduces no API or schema migration in this release.
|
|
28
|
-
|
|
29
|
-
## [0.19.0]
|
|
30
|
-
|
|
31
|
-
- Publishable on the npm `latest` dist-tag as `0.19.0`, from tag
|
|
32
|
-
`openlup-core-v0.19.0`.
|
|
33
|
-
|
|
34
|
-
- Republish unchanged APIs, runtime behavior and shipped SQL from `0.18.0` as part of the complete `0.19.0` package set.
|
|
35
|
-
|
|
36
|
-
Migration: upgrade the complete set together; core introduces no API or schema migration in this release.
|
|
37
|
-
|
|
38
|
-
## [0.18.0]
|
|
39
|
-
|
|
40
|
-
- Publishable on the npm `latest` dist-tag as `0.18.0`, from tag
|
|
41
|
-
`openlup-core-v0.18.0`.
|
|
42
|
-
|
|
43
|
-
- Republish unchanged APIs, runtime behavior and shipped SQL from `0.17.0` as
|
|
44
|
-
part of the complete `0.18.0` package set.
|
|
45
|
-
|
|
46
|
-
Migration: upgrade the complete set together; core introduces no API or schema
|
|
47
|
-
migration in this release.
|
|
48
|
-
|
|
49
|
-
## [0.17.0]
|
|
50
|
-
|
|
51
|
-
- Publishable on the npm `latest` dist-tag as `0.17.0`, from tag
|
|
52
|
-
`openlup-core-v0.17.0`.
|
|
53
|
-
|
|
54
|
-
- Republish unchanged APIs, runtime behavior and shipped SQL as part of the
|
|
55
|
-
complete `0.17.0` package set.
|
|
56
|
-
|
|
57
|
-
Baseline evidence (2026-10-20): published `0.16.0` tarball 161,232 packed bytes,
|
|
58
|
-
SHA256 `48abf4ea8ddc446b8d6e2c610801e2cfcbe5575743bd5a61fbb8b8b50a2b36a3`.
|
|
59
|
-
The initial `0.16.0` release-gates baseline is retained under S1 option B.
|
|
60
|
-
|
|
61
|
-
## [0.16.0]
|
|
62
|
-
|
|
63
|
-
- Publishable on the npm `latest` dist-tag as `0.16.0`, from tag
|
|
64
|
-
`openlup-core-v0.16.0`.
|
|
65
|
-
|
|
66
|
-
- Add optional logical module selection, dependency/entry contracts, module-qualified bindings and module diagnostics to experimental `./readiness`. Each selected module retains an independent schema requirement within one artifact.
|
|
67
|
-
|
|
68
|
-
Migration: additive API; existing manifests and loaded packages that omit module fields keep byte-identical readiness reports and binding keys. To opt an artifact in, observe `LoadedPackage.modules` and supply exactly one manifest `module` per selected id, with its logical kind, provided entries and in-artifact dependencies. Bind ports, triggers and routes as `package/module:id`; keep artifact kind/version and environment keys unchanged. Mixed modes, missing dependencies/entries and duplicate module/event/schedule/route ownership refuse using existing codes. Empty selections need no contributions; disabled modules need no bindings or schema probe. Inspect optional issue `module` when diagnosing module/schema failures. No release, publication or activation is performed by this change.
|
|
69
|
-
|
|
70
|
-
## [0.15.0]
|
|
71
|
-
|
|
72
|
-
- Publishable on the npm `latest` dist-tag as `0.15.0`, from tag
|
|
73
|
-
`openlup-core-v0.15.0`.
|
|
74
|
-
|
|
75
|
-
- Republished unchanged.
|
|
76
|
-
|
|
77
|
-
## [0.14.0]
|
|
78
|
-
|
|
79
|
-
- Publishable on the npm `latest` dist-tag as `0.14.0`, from tag
|
|
80
|
-
`openlup-core-v0.14.0`.
|
|
81
|
-
|
|
82
|
-
- Add experimental `buildGroupedPostgresSchemaProbe`, `decodeGroupedPostgresSchemaRows` and `PostgresSchemaCatalogEntry` on `./readiness` for one ordered, parameterized PostgreSQL catalog read with table, column and exact function-overload shapes. The strict decoder refuses incomplete or malformed rows without exposing catalog bodies.
|
|
83
|
-
|
|
84
|
-
Migration: additive API; existing scalar probes, observations, readiness codes and deadlines remain unchanged. Catalog rows use jsonb text, definition-string/null aggregates and raw pg_policy values; the decoder accepts text only. Hosts retain connection, snapshot, identity, digest and admission ownership. This preparation targets the 0.14.0 MINOR set; merging it does not publish packages.
|
|
85
|
-
|
|
86
|
-
## [0.13.1]
|
|
87
|
-
|
|
88
|
-
- Publishable on the npm `latest` dist-tag as `0.13.1`, from tag
|
|
89
|
-
`openlup-core-v0.13.1`.
|
|
90
|
-
|
|
91
|
-
- Republish the unchanged core API and implementation with the repaired outbox publication as the complete `0.13.1` package set.
|
|
92
|
-
|
|
93
|
-
## [0.13.0]
|
|
94
|
-
|
|
95
|
-
- Publishable on the npm `latest` dist-tag as `0.13.0`, from tag
|
|
96
|
-
`openlup-core-v0.13.0`.
|
|
97
|
-
|
|
98
|
-
- Add experimental outbox envelope/handler descriptors, structural validation, job lease and candidate readiness contracts.
|
|
99
|
-
- Check package inventory, event ownership/coverage, actual bindings and qualified schema observations before admitting a new candidate.
|
|
100
|
-
|
|
101
|
-
Migration: existing exports remain; compose the selected rail and bind its schema probe before activation. Unknown observations refuse the new candidate and do not stop a serving version.
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
### Changed
|
|
105
|
-
|
|
106
|
-
- `AGENTS.md` gains a required "Using this package in an application" section: do not edit or
|
|
107
|
-
patch the installed package, change behaviour through its seams, and upgrade every
|
|
108
|
-
`@openlup/*` package together. `docs:check` refuses a guide without it.
|
|
109
|
-
|
|
110
|
-
## [0.12.0]
|
|
111
|
-
|
|
112
|
-
### Removed
|
|
113
|
-
|
|
114
|
-
- **API REMOVAL: the `./checkout` subpath.** It held only an
|
|
115
|
-
`Idempotency-Key` header-shape helper that no checkout path used.
|
|
116
|
-
|
|
117
|
-
Migration: replace
|
|
118
|
-
|
|
119
|
-
```ts
|
|
120
|
-
import { validateCheckoutIdempotencyHeader } from "@openlup/core/checkout";
|
|
121
|
-
```
|
|
122
|
-
|
|
123
|
-
with an application-owned validator, or delete the call if, like the
|
|
124
|
-
reference checkout, your checkout carries its idempotency key in the
|
|
125
|
-
request body.
|
|
126
|
-
|
|
127
|
-
### Changed
|
|
128
|
-
|
|
129
|
-
- **BEHAVIOUR CHANGE for the subscription status matrix.** `cancelled` is no
|
|
130
|
-
longer terminal: `cancelled -> active` is now an allowed edge. This is the
|
|
131
|
-
owner win-back that the managed SQL guard
|
|
132
|
-
`public.subscription_guard_status_transition` already admits. Only the new
|
|
133
|
-
`reactivateSubscription` takes that edge. `resumeSubscription` still leaves
|
|
134
|
-
`paused` only and still refuses a cancelled subscription. `completed` stays
|
|
135
|
-
terminal. A caller that treated `cancelled` as final must now expect a
|
|
136
|
-
reactivated subscription to renew again.
|
|
137
|
-
- **BEHAVIOUR CHANGE: an exhausted retry ladder now has a modelled outcome.** A
|
|
138
|
-
renewal whose refusals ran past the last rung may pause an active
|
|
139
|
-
subscription through `pauseSubscriptionForExpiredDunning`, which is the
|
|
140
|
-
sanctioned non-payment rule. It is the only engine path that suspends a live
|
|
141
|
-
subscription for a reason the owner did not choose. A ladder cut short by a
|
|
142
|
-
terminating refusal class, or a failure that still has a retry after it, is
|
|
143
|
-
refused as `dunning_not_exhausted` and leaves the subscription active.
|
|
144
|
-
`recordPaymentFailure` is unchanged and still never touches the subscription.
|
|
145
|
-
- `SubscriptionEngineErrorCode` gains `missing_payment_method` and
|
|
146
|
-
`dunning_not_exhausted`. An exhaustive `switch` over the union must handle
|
|
147
|
-
both.
|
|
148
|
-
|
|
149
|
-
- `release-gates.json` sets `packageRelease.versionRule` to `0.N.P`. Below
|
|
150
|
-
1.0 every `@openlup/*` package carries one set version: a minor set for any
|
|
151
|
-
API, behaviour or schema change, a patch set for a fix only.
|
|
152
|
-
|
|
153
|
-
Migration: a tool that reads `versionRule` expects the set rule. Before:
|
|
154
|
-
`"versionRule": "0.<n>.0"`. After: `"versionRule": "0.N.P"`.
|
|
155
|
-
|
|
156
|
-
- Publishable on the npm `latest` dist-tag as `0.12.0`, from tag
|
|
157
|
-
`openlup-core-v0.12.0`.
|
|
158
|
-
- Publishable on the npm `preview` dist-tag as `0.11.0`, for source preview
|
|
159
|
-
`openlup-source-preview/11`.
|
|
160
|
-
- Publishable on the npm `preview` dist-tag as `0.10.0`, for source preview
|
|
161
|
-
`openlup-source-preview/10`.
|
|
162
|
-
- Publishable on the npm `preview` dist-tag as `0.9.0`, for source preview
|
|
163
|
-
`openlup-source-preview/9`.
|
|
164
|
-
- Publishable on the npm `preview` dist-tag as `0.7.0`, for source preview
|
|
165
|
-
`openlup-source-preview/7`.
|
|
166
|
-
- Publishable on the npm `preview` dist-tag as `0.6.0`, for source preview
|
|
167
|
-
`openlup-source-preview/6`. Directory `npm publish` stays refused.
|
|
168
|
-
|
|
169
|
-
- **BREAKING for `slugSchema`'s accepted language.** The `./catalog` schema no
|
|
170
|
-
longer trims its input, so a slug with leading or trailing whitespace is now
|
|
171
|
-
rejected rather than normalised and accepted, which is what the published
|
|
172
|
-
`catalogSlugFormat` it tests against always described. A caller that relied on
|
|
173
|
-
that normalisation must trim before validating.
|
|
174
|
-
- **BREAKING for the retry ladder's semantics.** `nextRetryAttemptAt` now
|
|
175
|
-
TERMINATES past the end of the ladder instead of capping at its last slot: an
|
|
176
|
-
attempt beyond the configured backoff length answers `null`, which is the pause
|
|
177
|
-
signal, rather than repeating the final interval forever. The stored schedule
|
|
178
|
-
this kernel mirrors has always terminated, so the two rails now agree; the
|
|
179
|
-
previous capping behaviour meant a caller asking for attempt 4 (or 9) was told
|
|
180
|
-
a fourth charge was scheduled when none was. Callers that guarded with
|
|
181
|
-
`attempt <= maxRetryAttempts()` before calling should drop the guard — asking
|
|
182
|
-
for the next slot IS the decision, and a second copy of the budget check is how
|
|
183
|
-
the two rails drifted apart in the first place.
|
|
184
|
-
|
|
185
|
-
### Added
|
|
186
|
-
|
|
187
|
-
- `AGENTS.md` ships in the tarball. It states the package's kind and each
|
|
188
|
-
subpath's maturity, gives one wiring example that compiles in CI, points to
|
|
189
|
-
the shipped sources and declarations, and lists the readiness codes the
|
|
190
|
-
package raises (none yet).
|
|
191
|
-
- `release-gates.json` schema 5 declares the package kind (`"kind": "kernel"`)
|
|
192
|
-
and splits the pack budget. Code files (dist JavaScript, declarations and
|
|
193
|
-
shipped sources) and root documentation and metadata files each have their
|
|
194
|
-
own caps on packed bytes and files; the tarball total stays as a backstop.
|
|
195
|
-
|
|
196
|
-
Migration: a tool that reads `release-gates.json` checks `schemaVersion: 5`.
|
|
197
|
-
Before: `"pack": { "maxPackedBytes": …, "maxFiles": … }`. After: `"pack": {
|
|
198
|
-
"code": { "maxPackedBytes": …, "maxFiles": … }, "docs": { … },
|
|
199
|
-
"maxPackedBytes": …, "maxFiles": … }`.
|
|
200
|
-
- Add neutral checkout recovery evidence, method, operation and action contracts
|
|
201
|
-
to the existing payment surface. They describe guidance only and do not change
|
|
202
|
-
payment admission, renewal classification or enabled payment methods.
|
|
203
|
-
- Export neutral lookup normalization from `./company-identity` for extensions.
|
|
204
|
-
|
|
205
|
-
- `SUBSCRIPTION_STATUS_TRANSITIONS`, the frozen status matrix, and
|
|
206
|
-
`canTransitionSubscriptionStatus` on `./subscription`. The matrix mirrors the
|
|
207
|
-
managed SQL guard over the engine's four statuses, and a contract test
|
|
208
|
-
enumerates every ordered pair.
|
|
209
|
-
- `reactivateSubscription`, the win-back from `cancelled`. It needs a stored
|
|
210
|
-
payment method and restarts the schedule `RESTART_LEAD_DAYS` (two) days out,
|
|
211
|
-
never earlier than the stored `nextCycleAt`.
|
|
212
|
-
- `isDunningLadderExhausted(cycle, cadence?)` on the retry ladder. It holds when
|
|
213
|
-
a cycle is `payment_failed`, has nothing scheduled, and its attempt is past
|
|
214
|
-
`maxRetryAttempts(cadence)`.
|
|
215
|
-
- `pauseSubscriptionForExpiredDunning` and
|
|
216
|
-
`resumeSubscriptionFromExpiredDunning`. The resume is the customer's recovery
|
|
217
|
-
from the non-payment pause: it takes an optional replacement method, skips the
|
|
218
|
-
uncollected cycle, and restarts the schedule like a win-back.
|
|
219
|
-
- `recordPaymentFailure` takes an optional `cadence`, so a deployment ladder
|
|
220
|
-
reaches the schedule and the exhaustion check alike. Callers that pass none
|
|
221
|
-
keep the shipped ladder.
|
|
222
|
-
- `recordPaymentFailure` accepts an optional neutral `failureClass` and passes it
|
|
223
|
-
to the canonical retry-ladder decision. Existing callers remain
|
|
224
|
-
source-compatible. The shipped terminating-class set is exactly
|
|
225
|
-
`["hard_do_not_retry"]`: that refusal gets no later rung, while every other
|
|
226
|
-
class keeps the existing 24h/72h/168h schedule.
|
|
227
|
-
- Class-aware retry termination on `./subscription`.
|
|
228
|
-
`CycleRetryCadence` gains an optional `terminatingFailureClasses`, the new
|
|
229
|
-
`ladderTerminatedByClass(failureClass, cadence?)` answers whether the REASON
|
|
230
|
-
for a refusal ends the ladder, and `nextRetryAttemptAt` / `shouldScheduleRetry`
|
|
231
|
-
take an optional trailing `failureClass`. `DEFAULT_CYCLE_RETRY_CADENCE` ships
|
|
232
|
-
only `hard_do_not_retry`. The predicate fails open for an absent or unlisted
|
|
233
|
-
class, for a listed class the taxonomy does not recognise, and for a listed
|
|
234
|
-
class that `failureClassDecision` still permits. Only explicit membership plus
|
|
235
|
-
the taxonomy's existing refusal can end the ladder early.
|
|
236
|
-
- Injectable retry cadence on `./subscription`: `CycleRetryCadence` and the
|
|
237
|
-
frozen `DEFAULT_CYCLE_RETRY_CADENCE` (24h/72h/168h). `nextRetryAttemptAt`,
|
|
238
|
-
`shouldScheduleRetry` and `maxRetryAttempts` each take an optional cadence and
|
|
239
|
-
default to the shipped one, so a deployment can publish its own ladder without
|
|
240
|
-
forking the kernel and nothing changes for a caller that passes none.
|
|
241
|
-
|
|
242
|
-
- Pack budgets raised (packed 98 KiB -> 106 KiB, unpacked 448 KiB -> 480 KiB;
|
|
243
|
-
file count unchanged at 186): the new allocation kernel ships as one source file
|
|
244
|
-
plus its two build artifacts, and its adopter-facing JSDoc — the floor-clamp
|
|
245
|
-
rule, the two-line split rationale and the failure-versus-throw split — is most
|
|
246
|
-
of the added bytes. The docs are the deliverable, so the budget moves rather
|
|
247
|
-
than the docs. Measured, not estimated: clean base packs 98532/446519/183 and
|
|
248
|
-
this change packs 106488/475826/186.
|
|
249
|
-
- Experimental bundle target-price allocation on `./pricing`:
|
|
250
|
-
`allocateBundleTargetPrice`, the pure kernel that turns ONE operator-set price
|
|
251
|
-
for a composed bundle into exact per-component money. Largest-remainder
|
|
252
|
-
(Hamilton) split weighted by each component's own reference subtotal, all in
|
|
253
|
-
BigInt, ties broken by component code so output never depends on input order.
|
|
254
|
-
Two rules bound it — a component never allocates below its minimum payable per
|
|
255
|
-
unit (and a component priced under that minimum is held to its own reference
|
|
256
|
-
price, because no floor may lift a component above what it is worth), and never
|
|
257
|
-
above its reference unit price. A quantity whose allocated subtotal does not
|
|
258
|
-
divide evenly emits TWO lines rather than a rounded unit price, so the
|
|
259
|
-
`unitPrice x quantity === lineSubtotal` identity every quote line refines still
|
|
260
|
-
holds. The single asserted invariant is that the emitted lines sum to the target
|
|
261
|
-
exactly. Three refusals stay data rather than exceptions: nothing to weigh
|
|
262
|
-
against, a target above the sum of the parts, and a target that cannot cover
|
|
263
|
-
every minimum payable. No clock, no randomness, no I/O.
|
|
264
|
-
- Line-scoped `bundle` component in the pricing breakdown vocabulary, with an
|
|
265
|
-
optional `bundle_allocated_discount_minor` on the line input: positive on the
|
|
266
|
-
way in, emitted as one negative component, absent or zero emits nothing. It is
|
|
267
|
-
line-scoped rather than order-scoped because a bundle prices a subset of the
|
|
268
|
-
cart, and refunding one line must return exactly what that line was charged.
|
|
269
|
-
- Pack budgets raised (packed 90 KiB -> 94 KiB, unpacked 416 KiB -> 432 KiB,
|
|
270
|
-
files 180 -> 186): one new kernel module ships as a source file plus its build
|
|
271
|
-
artifacts and their maps, and its adopter-facing JSDoc is most of the added
|
|
272
|
-
bytes. The docs are the deliverable, so the budget moves rather than the docs.
|
|
273
|
-
- Experimental payment-method lifecycle vocabulary on `./payment`: the five
|
|
274
|
-
transitions a rail may report about a method it holds on file
|
|
275
|
-
(`method_registered`, `method_updated`, `method_revoked`, `method_expired`,
|
|
276
|
-
`method_suspended`), the neutral event an adapter translates its own event
|
|
277
|
-
names into, and the three published facts (`schemeLabel`, `lastDigits`,
|
|
278
|
-
`expiresAt`) a transition may carry — never the instrument number. Plus two
|
|
279
|
-
pure helpers: `endsStoredMethodUsability`, the single place that answers
|
|
280
|
-
whether a transition means the method can no longer back a charge (a rotation
|
|
281
|
-
is not a death), and `expiryInstantFromMonthYear`, which pins the convention
|
|
282
|
-
that a month-and-year validity runs to the LAST instant of that month in UTC.
|
|
283
|
-
No rail is named and no clock, network or storage is read.
|
|
284
|
-
- Pack budgets raised (packed 88 KiB -> 90 KiB, unpacked 400 KiB -> 416 KiB): the provider-capability contracts
|
|
285
|
-
add ~0.9 KiB of shipped declarations and their adopter-facing JSDoc; the
|
|
286
|
-
docs are the deliverable, so the budget moves rather than the docs.
|
|
287
|
-
- (formatting) the block-reason alias is declared single-line so the
|
|
288
|
-
generated declaration snapshot carries no trailing whitespace.
|
|
289
|
-
- Experimental payment-provider capability contracts on `./payment`: a
|
|
290
|
-
descriptor an adapter publishes so renewal and dunning can decide from
|
|
291
|
-
CAPABILITIES — whether stored consent evidence must be read and whether it
|
|
292
|
-
is chargeable unattended, which provider flow an unattended charge uses,
|
|
293
|
-
what payer context the request needs, and what a stored method must satisfy
|
|
294
|
-
before a charge — plus a registry contract keyed by provider kind. Types
|
|
295
|
-
only, with no provider named and no runtime consumer yet; branching on
|
|
296
|
-
provider identity is unchanged.
|
|
297
|
-
- Experimental payment-failure taxonomy on `./payment`: a pure classifier that
|
|
298
|
-
resolves a refusal into one of seven neutral classes from optional evidence
|
|
299
|
-
(scheme retry advice, adopter-asserted neutral hints, an opaque decline code
|
|
300
|
-
read through an adopter-supplied hint table, and the internal reason key),
|
|
301
|
-
with a documented precedence order, a frozen per-class retry decision record,
|
|
302
|
-
and a scheme retry ceiling helper pinned at 15 attempts per 30 days. The
|
|
303
|
-
kernel embeds no acquirer code tables and has no runtime consumer yet; retry
|
|
304
|
-
scheduling is unchanged.
|
|
305
|
-
- Experimental provider-neutral payment-decline evidence on `./payment`, with
|
|
306
|
-
a provider code (never free-form prose) and retryability hint that lets
|
|
307
|
-
adopters classify synchronous PSP refusals without persisting customer data.
|
|
308
|
-
Extended with optional `declineCode` and `adviceCode`, so a refusal keeps the
|
|
309
|
-
finer bank reason and the provider's retry recommendation — both codes, never
|
|
310
|
-
messages — for a later classification policy to read. Extended again with
|
|
311
|
-
optional `neutralReasonHints`, so an adapter can carry its OWN reading of a
|
|
312
|
-
refusal — already translated out of its code vocabulary — and a classifier
|
|
313
|
-
never has to learn which adapter refused.
|
|
314
|
-
- `PAYMENT_FAILURE_CLASSIFICATION_SOURCES` on `./payment`: the classification
|
|
315
|
-
sources published as a value as well as a type, so a boundary schema can
|
|
316
|
-
validate `decidedBy` against the kernel's own vocabulary instead of restating
|
|
317
|
-
it. The type is unchanged and still derives from the same four members.
|
|
318
|
-
- Additive `evaluatePromotionAdjustmentsV2` API in the existing candidate
|
|
319
|
-
`./promo` subpath. It chooses one best product and shipping adjustment,
|
|
320
|
-
interprets product percentages as target-effective basis points against a
|
|
321
|
-
trusted reference subtotal, and requires an adopter-defined product floor.
|
|
322
|
-
The legacy evaluator and DB-shaped types remain unchanged for compatibility;
|
|
323
|
-
downstream applications can adopt the package boundary before any separately
|
|
324
|
-
flagged runtime cutover.
|
|
325
|
-
- Experimental `./fulfillment` normalized-fact contract with explicit schema,
|
|
326
|
-
strict N/N-1 parsing, adopter-supplied canonical-status validation,
|
|
327
|
-
discriminated provenance, application-minted evidence references, comparable
|
|
328
|
-
provider ordinals, deterministic fingerprint/dedupe, and self-contained
|
|
329
|
-
transition-conflict diagnostics with strict stream/decision validation.
|
|
330
|
-
- The then-current package identity and extracted-root governance evidence;
|
|
331
|
-
this is historical evidence, not authorization for a separate repository or
|
|
332
|
-
release channel.
|
|
333
|
-
- Package-local documentation and governance material for the portability
|
|
334
|
-
boundary.
|
|
335
|
-
- Candidate and experimental maturity metadata for the package surface.
|
|
336
|
-
|
|
337
|
-
### Changed
|
|
338
|
-
|
|
339
|
-
- Marked candidate/experimental declarations `@beta` and testing-only
|
|
340
|
-
declarations `@internal`, without changing package exports or runtime shapes.
|
|
341
|
-
Removed the private `./testing` neutrality matcher after moving all packaged
|
|
342
|
-
leak classes into the packed-artifact audit.
|
|
343
|
-
- Reclassified all declaration snapshots as private package-surface drift proof;
|
|
344
|
-
this changes no exports, runtime behavior, dependencies, or publish refusal.
|
|
345
|
-
- Raised the reviewed packed/unpacked artifact ceilings by 4/16 KiB to admit
|
|
346
|
-
the additive promo v2 declarations and implementation.
|
|
347
|
-
- Replaced public-surface and freeze language with the private package-surface
|
|
348
|
-
inventory: 16 current exports have package smoke evidence but no public
|
|
349
|
-
stability claim.
|
|
350
|
-
|
|
351
|
-
### Removed
|
|
352
|
-
|
|
353
|
-
- Removed the unused `./external-effects` API, implementation, snapshot, and
|
|
354
|
-
self-conformance fixture before any public release. The application
|
|
355
|
-
one-writer cleanup left no runtime consumer, so external-effect retry
|
|
356
|
-
semantics stay adopter-owned instead of becoming a fourth core concern.
|
|
357
|
-
|
|
358
|
-
### Security
|
|
359
|
-
|
|
360
|
-
- Recorded the private vulnerability-reporting contact for this proof package.
|
|
361
|
-
|
|
362
|
-
## Release Status
|
|
363
|
-
|
|
364
|
-
Versions up to `0.11.0` were published on the npm `preview` dist-tag without
|
|
365
|
-
sections of their own, so the `0.12.0` section also lists the changes they
|
|
366
|
-
carried. A release-preparation pull request turns the Unreleased section into
|
|
367
|
-
the next version's section; its release tag and npm record when it is
|
|
368
|
-
published. No version establishes a stable package API or supported upgrade
|
|
369
|
-
channel.
|
package/README.md
CHANGED
|
@@ -8,6 +8,12 @@ application" section, which `docs:check` requires, tells an application's agent
|
|
|
8
8
|
not to edit or patch the installed package and to upgrade every `@openlup/*`
|
|
9
9
|
package together.
|
|
10
10
|
|
|
11
|
+
The prepared `0.22.0` set adds the pure fulfillment presentation helpers and
|
|
12
|
+
retains compatible platform re-exports. Upgrade all packages together and read
|
|
13
|
+
[the migration and label limitation](CHANGELOG.md#0220). The size baseline is
|
|
14
|
+
the published `0.21.0` artifact; older releases remain in the source-only
|
|
15
|
+
`CHANGELOG-HISTORY.md`. Preparation establishes no npm publication.
|
|
16
|
+
|
|
11
17
|
## Channel and stability
|
|
12
18
|
|
|
13
19
|
- **Channel:** npm, on the `latest` dist-tag. Each version is published with
|
|
@@ -80,6 +86,8 @@ ceilings. Its aggregate budgets in `release-gates.json` follow the measured
|
|
|
80
86
|
decision its `$budgetReason` records; later growth still needs its own
|
|
81
87
|
measured budget decision. Passing a local pack does not authorize publication.
|
|
82
88
|
|
|
89
|
+
`./fulfillment` owns pure status, tracking-progress, current-parcel and canonical fact-parser helpers.
|
|
90
|
+
|
|
83
91
|
## Package Surface Maturity
|
|
84
92
|
|
|
85
93
|
Every current export is an internal candidate, experimental kernel, or testing
|
|
@@ -91,7 +99,7 @@ promise.
|
|
|
91
99
|
| `./bundle` | kernel | candidate | `smoke/subscriptionBundleStandalone.test.ts` |
|
|
92
100
|
| `./catalog` | kernel | candidate | `smoke/catalogStandalone.test.ts` |
|
|
93
101
|
| `./company-identity` | kernel | experimental | `smoke/companyIdentityStandalone.test.ts` |
|
|
94
|
-
| `./fulfillment` | kernel | experimental | `smoke/fulfillmentStandalone.test.ts` |
|
|
102
|
+
| `./fulfillment` | kernel | experimental | `smoke/fulfillmentStandalone.test.ts`<br>`test/fulfillmentStatusMap.test.ts`<br>`test/fulfillmentTrackingProgress.test.ts`<br>`test/fulfillmentCurrentParcel.test.ts`<br>`test/fulfillmentNormalizedFacts.test.ts` |
|
|
95
103
|
| `./inventory` | kernel | candidate | `smoke/inventoryStandalone.test.ts` |
|
|
96
104
|
| `./marketing/research` | kernel | experimental | `smoke/marketingResearchStandalone.test.ts` |
|
|
97
105
|
| `./partners` | kernel | experimental | `smoke/partnersStandalone.test.ts` |
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export type FulfillmentParcelRow = {
|
|
2
|
+
id?: unknown;
|
|
3
|
+
order_id?: unknown;
|
|
4
|
+
sequence_no?: unknown;
|
|
5
|
+
};
|
|
6
|
+
export declare function parcelSequenceNo(row: FulfillmentParcelRow): number;
|
|
7
|
+
export declare function compareParcelPrecedence(left: FulfillmentParcelRow, right: FulfillmentParcelRow): number;
|
|
8
|
+
export declare function currentParcel<Row extends FulfillmentParcelRow>(rows: readonly Row[]): Row | null;
|
|
9
|
+
export declare function currentParcelFor<Row extends FulfillmentParcelRow>(rows: readonly Row[], orderId: unknown): Row | null;
|
|
10
|
+
export declare function currentParcelPerOrder<Row extends FulfillmentParcelRow>(rows: readonly Row[]): Map<string, Row>;
|
|
11
|
+
export declare function currentParcelFirst<Row extends FulfillmentParcelRow>(rows: readonly Row[]): Row[];
|
|
12
|
+
export type ParcelAttributedRow = {
|
|
13
|
+
fulfillment_order_id?: unknown;
|
|
14
|
+
};
|
|
15
|
+
export declare function belongsToParcel(row: ParcelAttributedRow, parcelId: unknown): boolean;
|
|
16
|
+
export declare function rowsForParcel<Row extends ParcelAttributedRow>(rows: readonly Row[], parcelId: unknown): Row[];
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// An order may hold more than one fulfilment row: the original at `sequence_no` 0 and a
|
|
2
|
+
// replacement parcel at 1, 2, ... Several reads still have to answer with exactly one, and
|
|
3
|
+
// they must all answer the same way, or the operator, the customer and the support agent
|
|
4
|
+
// each see a different parcel for the same order.
|
|
5
|
+
//
|
|
6
|
+
// The rule, fixed once here: the parcel that *currently represents the order* is the highest
|
|
7
|
+
// `sequence_no`, tie-broken by `id` descending. While every order has exactly one row - which
|
|
8
|
+
// is every order until an operator can request a replacement - each function below returns
|
|
9
|
+
// that row, so the reads calling them are unchanged.
|
|
10
|
+
//
|
|
11
|
+
// This module is the single owner of that rule. It was four near-copies across two adapters
|
|
12
|
+
// and two read models before they were merged; keep it one.
|
|
13
|
+
// A row read without the column, or by a query that does not select it, is the original
|
|
14
|
+
// parcel - `sequence_no` carries a database default of 0 for exactly the same reason.
|
|
15
|
+
export function parcelSequenceNo(row) {
|
|
16
|
+
return typeof row.sequence_no === "number" ? row.sequence_no : 0;
|
|
17
|
+
}
|
|
18
|
+
// Negative when `left` is the more current parcel, so this sorts the current parcel first.
|
|
19
|
+
// Plain codepoint order on `id`, never `localeCompare`: this has to agree with SQL's
|
|
20
|
+
// `ORDER BY id DESC` over a uuid, and a collation that folds hyphens or case would not.
|
|
21
|
+
export function compareParcelPrecedence(left, right) {
|
|
22
|
+
const bySequence = parcelSequenceNo(right) - parcelSequenceNo(left);
|
|
23
|
+
if (bySequence !== 0)
|
|
24
|
+
return bySequence;
|
|
25
|
+
const leftId = String(left.id ?? "");
|
|
26
|
+
const rightId = String(right.id ?? "");
|
|
27
|
+
if (leftId === rightId)
|
|
28
|
+
return 0;
|
|
29
|
+
return leftId < rightId ? 1 : -1;
|
|
30
|
+
}
|
|
31
|
+
export function currentParcel(rows) {
|
|
32
|
+
return rows.reduce((best, row) => (best === null || compareParcelPrecedence(row, best) < 0 ? row : best), null);
|
|
33
|
+
}
|
|
34
|
+
export function currentParcelFor(rows, orderId) {
|
|
35
|
+
return currentParcel(rows.filter((row) => row.order_id === orderId));
|
|
36
|
+
}
|
|
37
|
+
export function currentParcelPerOrder(rows) {
|
|
38
|
+
const byOrder = new Map();
|
|
39
|
+
for (const row of rows) {
|
|
40
|
+
const orderId = String(row.order_id ?? "");
|
|
41
|
+
const incumbent = byOrder.get(orderId);
|
|
42
|
+
if (!incumbent || compareParcelPrecedence(row, incumbent) < 0)
|
|
43
|
+
byOrder.set(orderId, row);
|
|
44
|
+
}
|
|
45
|
+
return byOrder;
|
|
46
|
+
}
|
|
47
|
+
// For consumers that take `[0]` of an order's rows after filtering by `order_id`. Grouping
|
|
48
|
+
// preserves each order's first-appearance position, so a result that already holds one row
|
|
49
|
+
// per order comes back in exactly the order it arrived.
|
|
50
|
+
export function currentParcelFirst(rows) {
|
|
51
|
+
const byOrder = new Map();
|
|
52
|
+
for (const row of rows) {
|
|
53
|
+
const key = String(row.order_id ?? "");
|
|
54
|
+
const group = byOrder.get(key);
|
|
55
|
+
if (group)
|
|
56
|
+
group.push(row);
|
|
57
|
+
else
|
|
58
|
+
byOrder.set(key, [row]);
|
|
59
|
+
}
|
|
60
|
+
return [...byOrder.values()].flatMap((group) => (group.length > 1 ? [...group].sort(compareParcelPrecedence) : group));
|
|
61
|
+
}
|
|
62
|
+
export function belongsToParcel(row, parcelId) {
|
|
63
|
+
const attributed = row.fulfillment_order_id;
|
|
64
|
+
if (typeof attributed !== "string" || !attributed)
|
|
65
|
+
return true;
|
|
66
|
+
return typeof parcelId === "string" && parcelId ? attributed === parcelId : true;
|
|
67
|
+
}
|
|
68
|
+
export function rowsForParcel(rows, parcelId) {
|
|
69
|
+
return rows.filter((row) => belongsToParcel(row, parcelId));
|
|
70
|
+
}
|
|
@@ -2,3 +2,19 @@
|
|
|
2
2
|
export { FULFILLMENT_FACT_SOURCE_AUTHORITIES, FULFILLMENT_FACT_SOURCES, PROVIDER_FULFILLMENT_FACT_SOURCES, NORMALIZED_FULFILLMENT_FACT_PREVIOUS_SCHEMA_VERSION, NORMALIZED_FULFILLMENT_FACT_SCHEMA_VERSION, TRANSITION_CONFLICT_REASONS, InvalidFulfillmentFactError, FulfillmentFactCollisionError, UnsupportedFulfillmentFactSchemaError, canonicalFulfillmentFactFingerprint, canonicalizeFulfillmentFacts, parseNormalizedFulfillmentFact, parseTransitionConflict, } from "./facts.js";
|
|
3
3
|
/** @beta */
|
|
4
4
|
export type { CompatibleNormalizedFulfillmentFact, FulfillmentFactSourceAuthority, FulfillmentFactSource, FulfillmentFactProvenance, FulfillmentRawEventReference, FulfillmentProviderSequence, FulfillmentFactParserOptions, ProviderFulfillmentFactSource, NormalizedFulfillmentFact, NormalizedFulfillmentFactV0, NormalizedFulfillmentFactV1, TransitionConflict, TransitionConflictDecision, FulfillmentTransitionSnapshot, TransitionConflictReason, } from "./facts.js";
|
|
5
|
+
/** @beta */
|
|
6
|
+
export { FULFILLMENT_STATUS_MAP, CANONICAL_LOCAL_FULFILLMENT_STATUSES, CUSTOMER_STEP_SEQUENCE, canonicalStatusToken, stepForToken, customerStepFromSignals, phaseIndexForStep, stepLabelKey, timelineLabelForStep, trackingPhaseKeyForStep, stepUsesTruckIcon } from "./statusMap.js";
|
|
7
|
+
/** @beta */
|
|
8
|
+
export type { FulfillmentStatusStage, FulfillmentStatusMap } from "./statusMap.js";
|
|
9
|
+
/** @beta */
|
|
10
|
+
export { fulfillmentProgressPresentation } from "./trackingProgress.js";
|
|
11
|
+
/** @beta */
|
|
12
|
+
export { parcelSequenceNo, compareParcelPrecedence, currentParcel, currentParcelFor, currentParcelPerOrder, currentParcelFirst, belongsToParcel, rowsForParcel } from "./currentFulfillmentParcel.js";
|
|
13
|
+
/** @beta */
|
|
14
|
+
export type { FulfillmentParcelRow, ParcelAttributedRow } from "./currentFulfillmentParcel.js";
|
|
15
|
+
/** @beta */
|
|
16
|
+
export { isCanonicalLocalFulfillmentStatus, parseFulfillmentFact, parseFulfillmentTransitionConflict } from "./normalizedFulfillmentFacts.js";
|
|
17
|
+
/** @beta */
|
|
18
|
+
export type { CanonicalNormalizedFulfillmentFact, CanonicalNormalizedFulfillmentFactV1, CanonicalNormalizedFulfillmentFactV0, CanonicalCompatibleNormalizedFulfillmentFact, CanonicalFulfillmentTransitionSnapshot, CanonicalTransitionConflict } from "./normalizedFulfillmentFacts.js";
|
|
19
|
+
/** @beta */
|
|
20
|
+
export type { CanonicalLocalFulfillmentStatus, CustomerFulfillmentStep, FulfillmentProgressInput, FulfillmentProgressPresentation } from "./types.js";
|
|
@@ -1,2 +1,10 @@
|
|
|
1
1
|
/** @beta */
|
|
2
2
|
export { FULFILLMENT_FACT_SOURCE_AUTHORITIES, FULFILLMENT_FACT_SOURCES, PROVIDER_FULFILLMENT_FACT_SOURCES, NORMALIZED_FULFILLMENT_FACT_PREVIOUS_SCHEMA_VERSION, NORMALIZED_FULFILLMENT_FACT_SCHEMA_VERSION, TRANSITION_CONFLICT_REASONS, InvalidFulfillmentFactError, FulfillmentFactCollisionError, UnsupportedFulfillmentFactSchemaError, canonicalFulfillmentFactFingerprint, canonicalizeFulfillmentFacts, parseNormalizedFulfillmentFact, parseTransitionConflict, } from "./facts.js";
|
|
3
|
+
/** @beta */
|
|
4
|
+
export { FULFILLMENT_STATUS_MAP, CANONICAL_LOCAL_FULFILLMENT_STATUSES, CUSTOMER_STEP_SEQUENCE, canonicalStatusToken, stepForToken, customerStepFromSignals, phaseIndexForStep, stepLabelKey, timelineLabelForStep, trackingPhaseKeyForStep, stepUsesTruckIcon } from "./statusMap.js";
|
|
5
|
+
/** @beta */
|
|
6
|
+
export { fulfillmentProgressPresentation } from "./trackingProgress.js";
|
|
7
|
+
/** @beta */
|
|
8
|
+
export { parcelSequenceNo, compareParcelPrecedence, currentParcel, currentParcelFor, currentParcelPerOrder, currentParcelFirst, belongsToParcel, rowsForParcel } from "./currentFulfillmentParcel.js";
|
|
9
|
+
/** @beta */
|
|
10
|
+
export { isCanonicalLocalFulfillmentStatus, parseFulfillmentFact, parseFulfillmentTransitionConflict } from "./normalizedFulfillmentFacts.js";
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type CompatibleNormalizedFulfillmentFact as CoreCompatibleNormalizedFulfillmentFact, type FulfillmentTransitionSnapshot as CoreFulfillmentTransitionSnapshot, type NormalizedFulfillmentFact as CoreNormalizedFulfillmentFact, type NormalizedFulfillmentFactV0 as CoreNormalizedFulfillmentFactV0, type NormalizedFulfillmentFactV1 as CoreNormalizedFulfillmentFactV1, type TransitionConflict as CoreTransitionConflict } from "./facts.js";
|
|
2
|
+
import type { CanonicalLocalFulfillmentStatus } from "./types.js";
|
|
3
|
+
/** Application binding: core evidence narrowed to the single Layer B status canon. */
|
|
4
|
+
export type CanonicalNormalizedFulfillmentFact = CoreNormalizedFulfillmentFact<CanonicalLocalFulfillmentStatus>;
|
|
5
|
+
export type CanonicalNormalizedFulfillmentFactV1 = CoreNormalizedFulfillmentFactV1<CanonicalLocalFulfillmentStatus>;
|
|
6
|
+
export type CanonicalNormalizedFulfillmentFactV0 = CoreNormalizedFulfillmentFactV0<CanonicalLocalFulfillmentStatus>;
|
|
7
|
+
export type CanonicalCompatibleNormalizedFulfillmentFact = CoreCompatibleNormalizedFulfillmentFact<CanonicalLocalFulfillmentStatus>;
|
|
8
|
+
export type CanonicalFulfillmentTransitionSnapshot = CoreFulfillmentTransitionSnapshot<CanonicalLocalFulfillmentStatus>;
|
|
9
|
+
export type CanonicalTransitionConflict = CoreTransitionConflict<CanonicalLocalFulfillmentStatus>;
|
|
10
|
+
export declare function isCanonicalLocalFulfillmentStatus(value: string): value is CanonicalLocalFulfillmentStatus;
|
|
11
|
+
/** Strict unknown-input parser bound to the existing status canon. */
|
|
12
|
+
export declare function parseFulfillmentFact(value: unknown): CanonicalNormalizedFulfillmentFact;
|
|
13
|
+
/** Strict conflict parser bound to the same status canon. */
|
|
14
|
+
export declare function parseFulfillmentTransitionConflict(value: unknown): CanonicalTransitionConflict;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { parseNormalizedFulfillmentFact, parseTransitionConflict, } from "./facts.js";
|
|
2
|
+
import { CANONICAL_LOCAL_FULFILLMENT_STATUSES, } from "./statusMap.js";
|
|
3
|
+
const CANONICAL_STATUSES = new Set(CANONICAL_LOCAL_FULFILLMENT_STATUSES);
|
|
4
|
+
export function isCanonicalLocalFulfillmentStatus(value) {
|
|
5
|
+
return CANONICAL_STATUSES.has(value);
|
|
6
|
+
}
|
|
7
|
+
/** Strict unknown-input parser bound to the existing status canon. */
|
|
8
|
+
export function parseFulfillmentFact(value) {
|
|
9
|
+
return parseNormalizedFulfillmentFact(value, {
|
|
10
|
+
isCanonicalStatus: isCanonicalLocalFulfillmentStatus,
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
/** Strict conflict parser bound to the same status canon. */
|
|
14
|
+
export function parseFulfillmentTransitionConflict(value) {
|
|
15
|
+
return parseTransitionConflict(value, {
|
|
16
|
+
isCanonicalStatus: isCanonicalLocalFulfillmentStatus,
|
|
17
|
+
});
|
|
18
|
+
}
|