@01.software/sdk 0.65.1 → 0.68.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 (69) hide show
  1. package/CHANGELOG.md +81 -0
  2. package/README.md +201 -8
  3. package/dist/client.cjs +490 -338
  4. package/dist/client.cjs.map +1 -1
  5. package/dist/client.d.cts +11 -6
  6. package/dist/client.d.ts +11 -6
  7. package/dist/client.js +490 -338
  8. package/dist/client.js.map +1 -1
  9. package/dist/{collection-client-C2sMvfme.d.ts → collection-client-IOaWsTMI.d.ts} +4 -4
  10. package/dist/{collection-client-B7rsTtVj.d.cts → collection-client-MbIJ5KzW.d.cts} +4 -4
  11. package/dist/{const-CRxPTDGt.d.ts → const-Do27Udj6.d.ts} +2 -2
  12. package/dist/{const-D6ZRQLxI.d.cts → const-Dw5ElJs_.d.cts} +2 -2
  13. package/dist/embedded-admin/server.cjs +16 -1
  14. package/dist/embedded-admin/server.cjs.map +1 -1
  15. package/dist/embedded-admin/server.d.cts +4 -2
  16. package/dist/embedded-admin/server.d.ts +4 -2
  17. package/dist/embedded-admin/server.js +16 -1
  18. package/dist/embedded-admin/server.js.map +1 -1
  19. package/dist/embedded-admin.cjs +19 -0
  20. package/dist/embedded-admin.cjs.map +1 -1
  21. package/dist/embedded-admin.d.cts +7 -1
  22. package/dist/embedded-admin.d.ts +7 -1
  23. package/dist/embedded-admin.js +19 -0
  24. package/dist/embedded-admin.js.map +1 -1
  25. package/dist/errors.d.cts +2 -2
  26. package/dist/errors.d.ts +2 -2
  27. package/dist/{index-1auUEYrl.d.ts → index-DcYsLOja.d.ts} +3 -3
  28. package/dist/{index-Bbpf5t-l.d.cts → index-gnVxTKXu.d.cts} +3 -3
  29. package/dist/index.cjs +521 -367
  30. package/dist/index.cjs.map +1 -1
  31. package/dist/index.d.cts +7 -7
  32. package/dist/index.d.ts +7 -7
  33. package/dist/index.js +521 -367
  34. package/dist/index.js.map +1 -1
  35. package/dist/{payload-types-BnIET0xU.d.cts → payload-types-BY1uZFRf.d.cts} +162 -50
  36. package/dist/{payload-types-BnIET0xU.d.ts → payload-types-BY1uZFRf.d.ts} +162 -50
  37. package/dist/query.cjs +22 -1
  38. package/dist/query.cjs.map +1 -1
  39. package/dist/query.d.cts +5 -5
  40. package/dist/query.d.ts +5 -5
  41. package/dist/query.js +22 -1
  42. package/dist/query.js.map +1 -1
  43. package/dist/realtime.d.cts +2 -2
  44. package/dist/realtime.d.ts +2 -2
  45. package/dist/server.cjs +1251 -1046
  46. package/dist/server.cjs.map +1 -1
  47. package/dist/server.d.cts +10 -9
  48. package/dist/server.d.ts +10 -9
  49. package/dist/server.js +1251 -1046
  50. package/dist/server.js.map +1 -1
  51. package/dist/{types-D1y4TgQm.d.ts → types-AWSVpuPE.d.ts} +2 -2
  52. package/dist/{types-DnRC3nLG.d.ts → types-CLRw0aDa.d.ts} +214 -42
  53. package/dist/{types-Dp1oYpD3.d.cts → types-DnIuxgu2.d.cts} +2 -2
  54. package/dist/{types-Dzn0LD2N.d.cts → types-csjaig4G.d.cts} +214 -42
  55. package/dist/ui/canvas.cjs +5 -1
  56. package/dist/ui/canvas.cjs.map +1 -1
  57. package/dist/ui/canvas.js +5 -1
  58. package/dist/ui/canvas.js.map +1 -1
  59. package/dist/ui/form.d.cts +1 -1
  60. package/dist/ui/form.d.ts +1 -1
  61. package/dist/ui/video.cjs +9 -2
  62. package/dist/ui/video.cjs.map +1 -1
  63. package/dist/ui/video.d.cts +1 -1
  64. package/dist/ui/video.d.ts +1 -1
  65. package/dist/ui/video.js +9 -2
  66. package/dist/ui/video.js.map +1 -1
  67. package/dist/webhook.d.cts +5 -5
  68. package/dist/webhook.d.ts +5 -5
  69. package/package.json +4 -4
package/CHANGELOG.md CHANGED
@@ -1,5 +1,86 @@
1
1
  # @01.software/sdk
2
2
 
3
+ ## 0.68.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 0211dc7: Add server benefitVouchers issue/get/list/redeem/voidVoucher commands and Customer listMine. Vouchers are single-use non-monetary benefits; source identity and request UUIDs make explicit retries safe.
8
+ - 0211dc7: Support explicit embedded app execution-role opt-in on both the tenant binding
9
+ and receiver verifier, preserving administrator-only defaults. Add CLI
10
+ allowed-role configuration and binding-state readback.
11
+
12
+ ### Patch Changes
13
+
14
+ - 342cda2: Sync the generated benefit voucher definition documentation with its bilingual Admin description.
15
+
16
+ ## 0.67.0
17
+
18
+ ### Minor Changes
19
+
20
+ - 79bec7b: Add events.listMyRegistrations() for paginated customer-authenticated event registration history with a PII-free DTO.
21
+
22
+ BREAKING (0.x minor): `createServerClient().events` now returns an object facade instead of an `EventsClient` instance, excluding the customer-only `listMyRegistrations` method. The existing `getRange`, `getAdmissionAvailability`, `register`, `getGuestRegistration`, and `cancelGuestRegistration` calls remain supported. Consumers that accept server event helpers should import `RootServerClientWithEvents` from `@01.software/sdk/server` and use `RootServerClientWithEvents['events']` instead of `EventsClient`. Replace `instanceof EventsClient` checks with checks for the methods they need (for example, `typeof events.getRange === 'function'`). For customer registration history in a server BFF, create a request-scoped `createClient({ publishableKey, customer: { persist: false, token } })`.
23
+
24
+ ### Patch Changes
25
+
26
+ - 6d58a9c: Fix compiled Canvas template nodes with transparent backgrounds so their slug scope and transparent background classes remain separate tokens.
27
+ - 40d78bd: Sync generated types for the optional, operator-only virtual adminTitle on commerce config and tenant metadata. Storefront metadata reads and persisted fields are unchanged.
28
+
29
+ Private singleton titles are omitted from publishable-key, customer, and service-credential responses, including requests with a coincident Console session. They use the response document's tenant authority. GraphQL returns null when this denied optional field is explicitly selected.
30
+
31
+ - 5662849: Clarify the generated Link clickCount documentation: the field is read-only, and Console does not currently collect clicks automatically. Stored fields and access rules are unchanged.
32
+
33
+ ## 0.66.6
34
+
35
+ ### Patch Changes
36
+
37
+ - 0599ab4: 기존 CustomerAuth 요청의 path/method를 공통 HTTP manifest에 연결합니다. 등록·로그인·refresh·profile·password 요청의 body, 인증, 응답 및 세션 동작은 유지합니다.
38
+
39
+ ## 0.66.5
40
+
41
+ ### Patch Changes
42
+
43
+ - 604f443: Refresh generated community ban types to match the Admin Panel field order and temporary expiry guidance. The field types and API behavior are unchanged.
44
+
45
+ ## 0.66.4
46
+
47
+ ### Patch Changes
48
+
49
+ - ed09398: Clarify that a blank sender display name uses the workspace name for commerce and event notifications, and document the hosted sign-in logo URL fallback.
50
+ - c7b8803: Regenerate membership plan Payload declarations to match the Admin Panel's basic information field order. Field names, types, and fallback semantics are unchanged.
51
+ - e879ec4: Update generated type documentation comments to call the account boundary a workspace in English descriptions. Type names, field names, collection slugs, and runtime behavior are unchanged.
52
+
53
+ ## 0.66.3
54
+
55
+ ### Patch Changes
56
+
57
+ - 35568b4: Map stored video aspect-ratio enums to valid CSS ratios for VideoPlayer placeholders and ready players, preserving the 16/9 default and explicit style overrides. A stored ratio is now a valid inline `aspect-ratio`, so it takes precedence over an aspect ratio set through `className`; use `style.aspectRatio` or `placeholderStyle.aspectRatio` to override it.
58
+
59
+ ## 0.66.2
60
+
61
+ ### Patch Changes
62
+
63
+ - 055cb8d: Synchronize generated internal payment-account types with optional Commerce market assignments. Account management remains SDK-internal; this adds no public payment API.
64
+
65
+ ## 0.66.1
66
+
67
+ ### Patch Changes
68
+
69
+ - 8abfca5: Clarify that live-stream counters expose stored values and are not automatically aggregated by Console.
70
+ - 167f186: Clarify generated customer address field guidance comments.
71
+
72
+ ## 0.66.0
73
+
74
+ ### Minor Changes
75
+
76
+ - 29f068e: Add server-only `commerce.product.graphRevision` and `updateVariantPrices` for updating existing variant base prices without submitting the complete product graph. Keep one explicit content locale while reading the baseline, reviewing current values, writing, and independently reading back. Conflicts require a fresh review; the helper does not retry, refresh the revision, or fall back to upsert. Stock, deletes, options, and market prices are excluded. Existing PATCH receipts, projection warnings, and conflict metadata remain available.
77
+
78
+ ### Patch Changes
79
+
80
+ - fbb0715: Add optional closed-month observation quality metadata to the generated internal usage snapshot type.
81
+ - 62d1c63: Classify platform billing consent receipts as an internal Console collection.
82
+ - a9ca714: Synchronize generated usage invoice types with the private, transition-gated precharge notice protocol fields.
83
+
3
84
  ## 0.65.1
4
85
 
5
86
  ### Patch Changes
package/README.md CHANGED
@@ -96,6 +96,12 @@ monthly allowance is exhausted or 503 when admission cannot be verified.
96
96
  legacy public-ID paths for unmigrated paid assets. A signed asset's retained
97
97
  `muxPlaybackId` is historical usage evidence and is not a playable URL.
98
98
 
99
+ `VideoPlayer` displays the stored `aspectRatio` values `16_9`, `9_16`,
100
+ `4_3`, and `1_1` as CSS ratios in both the placeholder and ready player.
101
+ An unset ratio defaults to `16/9`. Override the ready player with
102
+ `style.aspectRatio`, or the placeholder with `placeholderStyle.aspectRatio`;
103
+ each override applies only to its own rendering path.
104
+
99
105
  `CanvasRenderer` renders reserved `shape` nodes (`terminator`, `process`,
100
106
  `decision`, `io`, `subprocess`) with first-party SVG geometry, without a tenant
101
107
  catalog row or QuickJS template.
@@ -152,6 +158,20 @@ The assertion proves operator and tenant context only. It is not a Console API
152
158
  credential; the external backend must use its own server-held credential and
153
159
  authorization policy for tenant data.
154
160
 
161
+ The verifier defaults to `super-admin` and `tenant-admin` only. To allow an
162
+ editor or viewer, both the administrator-managed binding `allowedRoles` and the
163
+ receiver's trusted server `allowedRoles` option must include that exact role.
164
+ For example, a receiver can pass
165
+ `allowedRoles: ['super-admin', 'tenant-admin', 'tenant-viewer']`; never take this
166
+ allowlist from browser input. Only opted-in operators see the execution view;
167
+ binding configuration remains administrator-only.
168
+
169
+ The receiver server must enforce each app feature's permissions from verified
170
+ `tenant_role` and `operator_id`, independently of its server credential. Direct
171
+ platform viewer mutation denial remains unchanged. The 60-second assertion
172
+ expiry does not revoke an app-local session; the receiver owns its session
173
+ expiry, authorization rechecks, and revocation.
174
+
155
175
  Migration quick reference:
156
176
 
157
177
  - `createClient` remains available from `@01.software/sdk` and
@@ -751,6 +771,10 @@ const prepared = await server.memberships.subscribe({
751
771
  })
752
772
 
753
773
  if (prepared.status === 'incomplete') {
774
+ const expiresAt = Date.parse(prepared.firstCharge.expiresAt)
775
+ if (!Number.isFinite(expiresAt) || Date.now() >= expiresAt) {
776
+ throw new Error('First charge instruction expired or invalid')
777
+ }
754
778
  await tenantPsp.charge({
755
779
  idempotencyKey: prepared.firstCharge.idempotencyKey,
756
780
  amount: prepared.firstCharge.amount,
@@ -767,8 +791,8 @@ posts the signed outcome through the configured charge-result webhook. A trial
767
791
  response is immediately `trialing` and has no `firstCharge` field. Before a paid
768
792
  subscribe, configure an active delegated billing connection for
769
793
  the tenant; otherwise Console returns `409 conflict` without lifecycle writes.
770
- Do not start
771
- the PSP payment at or after `expiresAt`; deduplicate PSP work with
794
+ Do not start the PSP payment if `expiresAt` is invalid or the current time is
795
+ at or after it; deduplicate PSP work with
772
796
  `firstCharge.idempotencyKey`. A late paid result remains evidence but cannot
773
797
  activate the Membership, so the tenant handles any refund outside this API. Replays use
774
798
  the same plan, price, customer reference, and idempotency key while the first
@@ -1869,6 +1893,55 @@ options, option values, and variants. It is the tenant-admin safe path because
1869
1893
  it applies the product/option/variant transaction that raw collection writes do
1870
1894
  not provide.
1871
1895
 
1896
+ For an existing variant's **base price only**, use the server-only narrow helper:
1897
+
1898
+ ```typescript
1899
+ const locale = 'ko'
1900
+ const baseline = await server.commerce.product.graphRevision({
1901
+ productId,
1902
+ locale,
1903
+ })
1904
+ // Review current base prices after taking the baseline, in the same locale.
1905
+ const current = await server.collections.from('product-variants').find({
1906
+ where: { product: { equals: productId } },
1907
+ locale,
1908
+ })
1909
+ const variantId = current.docs[0].id
1910
+ const saved = await server.commerce.product.updateVariantPrices({
1911
+ productId,
1912
+ locale,
1913
+ graphRevision: baseline.graphRevision,
1914
+ updates: [{ id: variantId, price: 1250 }],
1915
+ })
1916
+ // Independent readback; this does not replace the revision of an older draft.
1917
+ const readback = await server.collections
1918
+ .from('product-variants')
1919
+ .findById(variantId, { locale })
1920
+ if (saved.productProjectionStale) {
1921
+ console.warn('Prices saved; product listing projection may still be stale.')
1922
+ }
1923
+ ```
1924
+
1925
+ `graphRevision()` returns only `{ productId, graphRevision }` from a private,
1926
+ no-store server read. `updateVariantPrices()` sends only the supplied existing
1927
+ rows to the variants PATCH and returns its `ok`, `productId`, `updated`,
1928
+ `deleted`, and `productProjectionStale` fields unchanged. It accepts 1–250
1929
+ `{ id, price }` rows; price uses the existing numeric shop-currency amount,
1930
+ with currency precision enforced by Console. Stock, deletions, option changes,
1931
+ compare-at prices, and market prices are rejected as SDK inputs. They are not
1932
+ silently stripped or sent. Full graph upsert remains a separate operation.
1933
+
1934
+ Use the same explicit enabled content `locale` (`ko` or `en`) for baseline,
1935
+ value review, save, and readback. A `409 graph_conflict` requires reviewing fresh
1936
+ values before a deliberate new edit; the SDK never fetches a replacement
1937
+ revision, retries the PATCH, or falls back to upsert. The existing conflict
1938
+ fields remain at `SDKError.details.body.currentGraphRevision` and
1939
+ `SDKError.details.body.currentGraphUpdatedAt`, together with
1940
+ `apiReason` and `requestId`. Read-only service keys can read the revision but
1941
+ cannot change prices; operator/PAT tenant roles and tenant pinning still apply.
1942
+ A saved projection warning reports committed graph data, not a failed save or
1943
+ permission to repeat the write.
1944
+
1872
1945
  `commerce.product.upsert()` is a graph-write API. For Console product
1873
1946
  documents, Payload saves product fields first; upsert receives `productId`,
1874
1947
  `graphRevision` (required when updating an existing product via `productId` -
@@ -1939,20 +2012,92 @@ if (!result.ok) {
1939
2012
  }
1940
2013
  ```
1941
2014
 
1942
- Existing product graph edits send the saved product id and the composer draft
1943
- baseline, without product document fields:
2015
+ Existing product graph edits send the saved product id and the revision from
2016
+ the composer draft baseline, without product document fields. The draft is an
2017
+ editor read model, not an SDK mutation payload: construct `options` and
2018
+ `variants` explicitly instead of forwarding `draft.options` / `draft.variants`.
2019
+
2020
+ This example assumes the complete persisted graph has one Color option, two
2021
+ values, and two variants. Replace the sample IDs and reviewed values with
2022
+ your own baseline, retaining every option, value, and variant you intend to
2023
+ keep. Media IDs must already belong to the saved product's image pool.
1944
2024
 
1945
2025
  ```ts
1946
2026
  await client.commerce.product.upsert({
1947
2027
  productId: 'prod_123',
1948
2028
  graphRevision: draft.graphRevision,
1949
- options: draft.options,
1950
- variants: draft.variants,
2029
+ options: [
2030
+ {
2031
+ id: 'opt_color',
2032
+ title: 'Color',
2033
+ slug: 'color',
2034
+ values: [
2035
+ {
2036
+ id: 'val_black',
2037
+ value: 'Black',
2038
+ slug: 'black',
2039
+ swatch: { type: 'color', color: '#111111' },
2040
+ },
2041
+ {
2042
+ id: 'val_white',
2043
+ value: 'White',
2044
+ slug: 'white',
2045
+ swatch: { type: 'media', mediaItemId: 'img_white' },
2046
+ },
2047
+ ],
2048
+ },
2049
+ ],
2050
+ variants: [
2051
+ {
2052
+ id: 'var_black',
2053
+ optionValues: { color: { valueId: 'val_black' } },
2054
+ price: 29000,
2055
+ images: ['img_black'],
2056
+ },
2057
+ {
2058
+ id: 'var_white',
2059
+ optionValues: { color: { valueId: 'val_white' } },
2060
+ price: 29000,
2061
+ images: ['img_white'],
2062
+ },
2063
+ ],
1951
2064
  })
1952
2065
  ```
1953
2066
 
1954
- For updates to existing options or option-values, prefer `id` / `valueId` when
1955
- available so rename-safe updates do not depend on slugs.
2067
+ Keep persisted option/value/variant `id`s and select existing values by
2068
+ `valueId`; do not regenerate identity from labels or change stored slugs.
2069
+ Resolve editor `optionValueKeys` (client keys) to SDK `optionValues`, as above,
2070
+ whose record keys are option slugs or titles, not option IDs. Do not send
2071
+ unsaved matrix proposal rows (`included: false` with no `id`). Preserve IDs
2072
+ for persisted rows you retain, and send `isActive: false` when deliberately
2073
+ deactivating one. Copy only mutation fields, excluding `clientKey`, `included`,
2074
+ and other editor state. SDK `images` contains string IDs, not hydrated media
2075
+ objects.
2076
+ An SDK swatch object requires `type: 'color'` with a color string or
2077
+ `type: 'media'` with a `mediaItemId` string. For an existing value, omit
2078
+ `swatch` to preserve its stored swatch. The SDK type also accepts `swatch: null`;
2079
+ runtime clearing with it is not verified here. Do not copy a draft swatch
2080
+ with a missing/null tag or null active value into that object shape.
2081
+
2082
+ Upsert synchronizes the full graph, not just changed rows: omitted options or
2083
+ values can be deleted, and omitted variants can be deleted or deactivated.
2084
+ If reservation history prevents variant deletion, omission can instead reject
2085
+ the write with `409` and roll it back.
2086
+ Keep the revision of the reviewed baseline and review fresh values after a
2087
+ `graph_conflict`; do not substitute a newer revision onto stale values.
2088
+ `graphRevision` excludes inventory-only changes, so this graph edit omits
2089
+ loaded `stock` on persisted variants. New finite variants may carry initial
2090
+ stock. An intentional stock write is different from replaying loaded stock;
2091
+ this example does not perform one.
2092
+
2093
+ This example is for a persisted option graph. For editor-only placeholders,
2094
+ use the simple-variant branch only when `editorPlaceholderGraph` is true and
2095
+ the structure still matches: one unsaved `Default` option, one unsaved value
2096
+ with a nonempty label (not necessarily `Standard`), and one included variant
2097
+ selecting only that value. Send `options: []` and that simple variant with
2098
+ `optionValues: {}`. Preserve its variant ID if persisted; a new variant has
2099
+ no saved ID. Omit loaded stock on persisted variants. Base-price-only changes
2100
+ use `updateVariantPrices()` above, rather than a full graph upsert.
1956
2101
 
1957
2102
  ```typescript
1958
2103
  // Batch stock check (point-in-time read, NOT a reservation)
@@ -2046,6 +2191,24 @@ replacement API. Pass an empty list to clear codes. Singular `admission`,
2046
2191
  Browser `mine()`/`merge({ guestCartToken })` derive customer identity from the
2047
2192
  JWT; server calls require an explicit `customerToken`.
2048
2193
 
2194
+ ### Customer event registration history
2195
+
2196
+ After customer sign-in, `client.events.listMyRegistrations({ page: 1, limit: 10 })`
2197
+ returns only that session's registrations with status, quantity, created time,
2198
+ and event/occurrence references plus current public title/time metadata.
2199
+ `page` defaults to 1; `limit` defaults to 10 and is capped at 100. The result has
2200
+ `docs`, `totalDocs`, `limit`, `page`, `totalPages`, `hasNextPage`, `hasPrevPage`.
2201
+ A customer JWT and matching publishable key are required; server keys cannot
2202
+ impersonate customers. Reads and responses use `no-store`. Non-public/missing
2203
+ metadata becomes null while registration references/status remain. PII expiry
2204
+ and cleanup preserve the same attendee-free DTO. Guest credentials, answers,
2205
+ audit fields and locations are absent; same-email guest records are not linked.
2206
+
2207
+ `createServerClient().events` excludes this customer-only method, following the
2208
+ customer-only membership/profile/order helpers. A server BFF creates a client
2209
+ per request with `createClient({ publishableKey, customer: { persist: false, token } })`
2210
+ and calls `client.events.listMyRegistrations()` with the customer's JWT.
2211
+
2049
2212
  ### Community public reads
2050
2213
 
2051
2214
  Use the shaped `community` helpers for browser/customer community screens. A
@@ -2466,3 +2629,33 @@ P5 also adds JWT-`jti` revocation: `revokeCustomerJti(jti, ttl)` on the server i
2466
2629
  **Boolean field renames** (6 collections):
2467
2630
 
2468
2631
  - `status: 'active' | 'inactive'` → `isActive: boolean` on Forms, ArticleAuthors, CustomerGroups, ShippingPolicies, ProductVariants
2632
+
2633
+ ### Customer benefit vouchers
2634
+
2635
+ Tenant-admins manage benefit voucher definitions in Console. These are single-use, non-monetary benefits, with no Checkout discount or credit effect. Your server decides recipients and timing (for example from a membership webhook or your own cron); source uniqueness prevents duplicate effects only.
2636
+
2637
+ ```ts
2638
+ import { createServerClient } from '@01.software/sdk/server'
2639
+
2640
+ const server = createServerClient({ publishableKey, secretKey })
2641
+ const { voucher, created } = await server.benefitVouchers.issue({
2642
+ definitionId,
2643
+ customerId,
2644
+ sourceNamespace: 'member-benefits',
2645
+ sourceRef: 'period:2030-01:member-opaque-id',
2646
+ })
2647
+ const redeemed = await server.benefitVouchers.redeem({
2648
+ customerId,
2649
+ voucherId: voucher.id,
2650
+ requestId,
2651
+ actorRef: 'staff:opaque-id',
2652
+ })
2653
+ ```
2654
+
2655
+ `server.benefitVouchers.get({ customerId, voucherId })` and `list({ customerId, state?, definitionId?, limit?, cursor? })` require a read-scoped server key. `issue`, `redeem`, and `voidVoucher({ customerId, voucherId, requestId, actorRef, reason })` require write scope, sk01 credentials and no browser Origin. Void reasons are `issued_in_error`, `benefit_revoked`, `customer_request`, and `other`. Issuing and redeeming require effective Ecommerce and Customers; reads and void remain available after feature loss.
2656
+
2657
+ Reuse the same sourceNamespace/sourceRef and identical issue input on an unknown outcome. For redeem/void, create a request UUID once and reuse it on retries; these POSTs are not automatically retried. Reusing an identity with different input returns 409. A successful transition is irreversible. Expiry uses database time and is inclusive at expiresAt. Correct an issued benefit by voiding it and issuing a new source identity.
2658
+
2659
+ `client.benefitVouchers.listMine({ status: 'available', limit: 20 })` uses the Customer JWT and returns only identity, customer-visible copy, state and timestamps. Use `status: 'all'` for history and the returned `nextCursor` for another page (maximum 100). The response is private, no-store. Server lists return the server DTO and require customerId; cross-customer/tenant targets are masked as 404. Console customer detail shows a read-only list; Console action buttons follow separately.
2660
+
2661
+ The staff app server must verify the staff member and Customer before redeeming: voucherId is not a bearer capability. `actorRef` is app-attested evidence, never a platform-verified staff identity or an authorization input. Use opaque references without personal data in sourceRef and actorRef. Definitions snapshot name, description and expiry at issue; later edits do not change issued vouchers. Voucher history survives Customer erasure and blocks tenant hard deletion. Referenced definitions must be archived.