@endora-commerce/contracts 0.100.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 (327) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +34 -0
  3. package/dist/actor.d.ts +79 -0
  4. package/dist/actor.d.ts.map +1 -0
  5. package/dist/actor.js +41 -0
  6. package/dist/actor.js.map +1 -0
  7. package/dist/addresses.d.ts +134 -0
  8. package/dist/addresses.d.ts.map +1 -0
  9. package/dist/addresses.js +16 -0
  10. package/dist/addresses.js.map +1 -0
  11. package/dist/admin-actions.d.ts +367 -0
  12. package/dist/admin-actions.d.ts.map +1 -0
  13. package/dist/admin-actions.js +287 -0
  14. package/dist/admin-actions.js.map +1 -0
  15. package/dist/admin-contributions.d.ts +518 -0
  16. package/dist/admin-contributions.d.ts.map +1 -0
  17. package/dist/admin-contributions.js +495 -0
  18. package/dist/admin-contributions.js.map +1 -0
  19. package/dist/admin-i18n.d.ts +135 -0
  20. package/dist/admin-i18n.d.ts.map +1 -0
  21. package/dist/admin-i18n.js +72 -0
  22. package/dist/admin-i18n.js.map +1 -0
  23. package/dist/admin-notifications.d.ts +55 -0
  24. package/dist/admin-notifications.d.ts.map +1 -0
  25. package/dist/admin-notifications.js +16 -0
  26. package/dist/admin-notifications.js.map +1 -0
  27. package/dist/admin-roles.d.ts +125 -0
  28. package/dist/admin-roles.d.ts.map +1 -0
  29. package/dist/admin-roles.js +2 -0
  30. package/dist/admin-roles.js.map +1 -0
  31. package/dist/admin-users.d.ts +178 -0
  32. package/dist/admin-users.d.ts.map +1 -0
  33. package/dist/admin-users.js +14 -0
  34. package/dist/admin-users.js.map +1 -0
  35. package/dist/admin.d.ts +243 -0
  36. package/dist/admin.d.ts.map +1 -0
  37. package/dist/admin.js +246 -0
  38. package/dist/admin.js.map +1 -0
  39. package/dist/analytics.d.ts +123 -0
  40. package/dist/analytics.d.ts.map +1 -0
  41. package/dist/analytics.js +68 -0
  42. package/dist/analytics.js.map +1 -0
  43. package/dist/api-keys.d.ts +97 -0
  44. package/dist/api-keys.d.ts.map +1 -0
  45. package/dist/api-keys.js +64 -0
  46. package/dist/api-keys.js.map +1 -0
  47. package/dist/assets-library.d.ts +684 -0
  48. package/dist/assets-library.d.ts.map +1 -0
  49. package/dist/assets-library.js +181 -0
  50. package/dist/assets-library.js.map +1 -0
  51. package/dist/audit-logs.d.ts +141 -0
  52. package/dist/audit-logs.d.ts.map +1 -0
  53. package/dist/audit-logs.js +31 -0
  54. package/dist/audit-logs.js.map +1 -0
  55. package/dist/auth.d.ts +174 -0
  56. package/dist/auth.d.ts.map +1 -0
  57. package/dist/auth.js +27 -0
  58. package/dist/auth.js.map +1 -0
  59. package/dist/blog.d.ts +669 -0
  60. package/dist/blog.d.ts.map +1 -0
  61. package/dist/blog.js +360 -0
  62. package/dist/blog.js.map +1 -0
  63. package/dist/capabilities.d.ts +40 -0
  64. package/dist/capabilities.d.ts.map +1 -0
  65. package/dist/capabilities.js +38 -0
  66. package/dist/capabilities.js.map +1 -0
  67. package/dist/carts.d.ts +1367 -0
  68. package/dist/carts.d.ts.map +1 -0
  69. package/dist/carts.js +405 -0
  70. package/dist/carts.js.map +1 -0
  71. package/dist/catalog.d.ts +2855 -0
  72. package/dist/catalog.d.ts.map +1 -0
  73. package/dist/catalog.js +1543 -0
  74. package/dist/catalog.js.map +1 -0
  75. package/dist/cms.d.ts +872 -0
  76. package/dist/cms.d.ts.map +1 -0
  77. package/dist/cms.js +468 -0
  78. package/dist/cms.js.map +1 -0
  79. package/dist/common.d.ts +82 -0
  80. package/dist/common.d.ts.map +1 -0
  81. package/dist/common.js +72 -0
  82. package/dist/common.js.map +1 -0
  83. package/dist/comparisons.d.ts +487 -0
  84. package/dist/comparisons.d.ts.map +1 -0
  85. package/dist/comparisons.js +221 -0
  86. package/dist/comparisons.js.map +1 -0
  87. package/dist/credentials.d.ts +292 -0
  88. package/dist/credentials.d.ts.map +1 -0
  89. package/dist/credentials.js +142 -0
  90. package/dist/credentials.js.map +1 -0
  91. package/dist/credit-limits.d.ts +111 -0
  92. package/dist/credit-limits.d.ts.map +1 -0
  93. package/dist/credit-limits.js +35 -0
  94. package/dist/credit-limits.js.map +1 -0
  95. package/dist/currencies.d.ts +127 -0
  96. package/dist/currencies.d.ts.map +1 -0
  97. package/dist/currencies.js +20 -0
  98. package/dist/currencies.js.map +1 -0
  99. package/dist/custom-fields.d.ts +345 -0
  100. package/dist/custom-fields.d.ts.map +1 -0
  101. package/dist/custom-fields.js +185 -0
  102. package/dist/custom-fields.js.map +1 -0
  103. package/dist/customer-accounts.d.ts +690 -0
  104. package/dist/customer-accounts.d.ts.map +1 -0
  105. package/dist/customer-accounts.js +41 -0
  106. package/dist/customer-accounts.js.map +1 -0
  107. package/dist/customers.d.ts +305 -0
  108. package/dist/customers.d.ts.map +1 -0
  109. package/dist/customers.js +158 -0
  110. package/dist/customers.js.map +1 -0
  111. package/dist/dictionary.d.ts +580 -0
  112. package/dist/dictionary.d.ts.map +1 -0
  113. package/dist/dictionary.js +297 -0
  114. package/dist/dictionary.js.map +1 -0
  115. package/dist/email-address.d.ts +62 -0
  116. package/dist/email-address.d.ts.map +1 -0
  117. package/dist/email-address.js +64 -0
  118. package/dist/email-address.js.map +1 -0
  119. package/dist/email.d.ts +175 -0
  120. package/dist/email.d.ts.map +1 -0
  121. package/dist/email.js +45 -0
  122. package/dist/email.js.map +1 -0
  123. package/dist/envelopes.d.ts +15 -0
  124. package/dist/envelopes.d.ts.map +1 -0
  125. package/dist/envelopes.js +16 -0
  126. package/dist/envelopes.js.map +1 -0
  127. package/dist/environment-inputs.d.ts +306 -0
  128. package/dist/environment-inputs.d.ts.map +1 -0
  129. package/dist/environment-inputs.js +277 -0
  130. package/dist/environment-inputs.js.map +1 -0
  131. package/dist/erp-connector.d.ts +52 -0
  132. package/dist/erp-connector.d.ts.map +1 -0
  133. package/dist/erp-connector.js +34 -0
  134. package/dist/erp-connector.js.map +1 -0
  135. package/dist/errors.d.ts +455 -0
  136. package/dist/errors.d.ts.map +1 -0
  137. package/dist/errors.js +532 -0
  138. package/dist/errors.js.map +1 -0
  139. package/dist/google-analytics.d.ts +181 -0
  140. package/dist/google-analytics.d.ts.map +1 -0
  141. package/dist/google-analytics.js +176 -0
  142. package/dist/google-analytics.js.map +1 -0
  143. package/dist/google-tag-manager.d.ts +111 -0
  144. package/dist/google-tag-manager.d.ts.map +1 -0
  145. package/dist/google-tag-manager.js +129 -0
  146. package/dist/google-tag-manager.js.map +1 -0
  147. package/dist/i18n.d.ts +69 -0
  148. package/dist/i18n.d.ts.map +1 -0
  149. package/dist/i18n.js +59 -0
  150. package/dist/i18n.js.map +1 -0
  151. package/dist/import-export.d.ts +63 -0
  152. package/dist/import-export.d.ts.map +1 -0
  153. package/dist/import-export.js +37 -0
  154. package/dist/import-export.js.map +1 -0
  155. package/dist/index.d.ts +82 -0
  156. package/dist/index.d.ts.map +1 -0
  157. package/dist/index.js +126 -0
  158. package/dist/index.js.map +1 -0
  159. package/dist/inventory.d.ts +673 -0
  160. package/dist/inventory.d.ts.map +1 -0
  161. package/dist/inventory.js +412 -0
  162. package/dist/inventory.js.map +1 -0
  163. package/dist/invoice-ledger.d.ts +366 -0
  164. package/dist/invoice-ledger.d.ts.map +1 -0
  165. package/dist/invoice-ledger.js +114 -0
  166. package/dist/invoice-ledger.js.map +1 -0
  167. package/dist/invoices.d.ts +845 -0
  168. package/dist/invoices.d.ts.map +1 -0
  169. package/dist/invoices.js +314 -0
  170. package/dist/invoices.js.map +1 -0
  171. package/dist/kernel.d.ts +49 -0
  172. package/dist/kernel.d.ts.map +1 -0
  173. package/dist/kernel.js +19 -0
  174. package/dist/kernel.js.map +1 -0
  175. package/dist/languages.d.ts +122 -0
  176. package/dist/languages.d.ts.map +1 -0
  177. package/dist/languages.js +24 -0
  178. package/dist/languages.js.map +1 -0
  179. package/dist/linkedin-ads.d.ts +167 -0
  180. package/dist/linkedin-ads.d.ts.map +1 -0
  181. package/dist/linkedin-ads.js +156 -0
  182. package/dist/linkedin-ads.js.map +1 -0
  183. package/dist/megamenu.d.ts +556 -0
  184. package/dist/megamenu.d.ts.map +1 -0
  185. package/dist/megamenu.js +186 -0
  186. package/dist/megamenu.js.map +1 -0
  187. package/dist/meta-ads.d.ts +126 -0
  188. package/dist/meta-ads.d.ts.map +1 -0
  189. package/dist/meta-ads.js +112 -0
  190. package/dist/meta-ads.js.map +1 -0
  191. package/dist/mfa.d.ts +274 -0
  192. package/dist/mfa.d.ts.map +1 -0
  193. package/dist/mfa.js +187 -0
  194. package/dist/mfa.js.map +1 -0
  195. package/dist/modules.d.ts +1706 -0
  196. package/dist/modules.d.ts.map +1 -0
  197. package/dist/modules.js +1390 -0
  198. package/dist/modules.js.map +1 -0
  199. package/dist/newsletter.d.ts +611 -0
  200. package/dist/newsletter.d.ts.map +1 -0
  201. package/dist/newsletter.js +345 -0
  202. package/dist/newsletter.js.map +1 -0
  203. package/dist/orders.d.ts +1175 -0
  204. package/dist/orders.d.ts.map +1 -0
  205. package/dist/orders.js +630 -0
  206. package/dist/orders.js.map +1 -0
  207. package/dist/organizations.d.ts +938 -0
  208. package/dist/organizations.d.ts.map +1 -0
  209. package/dist/organizations.js +418 -0
  210. package/dist/organizations.js.map +1 -0
  211. package/dist/pagination.d.ts +21 -0
  212. package/dist/pagination.d.ts.map +1 -0
  213. package/dist/pagination.js +22 -0
  214. package/dist/pagination.js.map +1 -0
  215. package/dist/payment-methods.d.ts +472 -0
  216. package/dist/payment-methods.d.ts.map +1 -0
  217. package/dist/payment-methods.js +175 -0
  218. package/dist/payment-methods.js.map +1 -0
  219. package/dist/payment-return-url.d.ts +53 -0
  220. package/dist/payment-return-url.d.ts.map +1 -0
  221. package/dist/payment-return-url.js +35 -0
  222. package/dist/payment-return-url.js.map +1 -0
  223. package/dist/payments.d.ts +386 -0
  224. package/dist/payments.d.ts.map +1 -0
  225. package/dist/payments.js +84 -0
  226. package/dist/payments.js.map +1 -0
  227. package/dist/pim-connector.d.ts +60 -0
  228. package/dist/pim-connector.d.ts.map +1 -0
  229. package/dist/pim-connector.js +43 -0
  230. package/dist/pim-connector.js.map +1 -0
  231. package/dist/pim-field-path.d.ts +6 -0
  232. package/dist/pim-field-path.d.ts.map +1 -0
  233. package/dist/pim-field-path.js +101 -0
  234. package/dist/pim-field-path.js.map +1 -0
  235. package/dist/platform-language.d.ts +20 -0
  236. package/dist/platform-language.d.ts.map +1 -0
  237. package/dist/platform-language.js +22 -0
  238. package/dist/platform-language.js.map +1 -0
  239. package/dist/price-lists.d.ts +685 -0
  240. package/dist/price-lists.d.ts.map +1 -0
  241. package/dist/price-lists.js +330 -0
  242. package/dist/price-lists.js.map +1 -0
  243. package/dist/product-feeds.d.ts +2837 -0
  244. package/dist/product-feeds.d.ts.map +1 -0
  245. package/dist/product-feeds.js +1504 -0
  246. package/dist/product-feeds.js.map +1 -0
  247. package/dist/product-scope-overrides.d.ts +134 -0
  248. package/dist/product-scope-overrides.d.ts.map +1 -0
  249. package/dist/product-scope-overrides.js +82 -0
  250. package/dist/product-scope-overrides.js.map +1 -0
  251. package/dist/product-value-resolver.d.ts +88 -0
  252. package/dist/product-value-resolver.d.ts.map +1 -0
  253. package/dist/product-value-resolver.js +128 -0
  254. package/dist/product-value-resolver.js.map +1 -0
  255. package/dist/promotions.d.ts +678 -0
  256. package/dist/promotions.d.ts.map +1 -0
  257. package/dist/promotions.js +479 -0
  258. package/dist/promotions.js.map +1 -0
  259. package/dist/prompt-actions.d.ts +582 -0
  260. package/dist/prompt-actions.d.ts.map +1 -0
  261. package/dist/prompt-actions.js +221 -0
  262. package/dist/prompt-actions.js.map +1 -0
  263. package/dist/pwa.d.ts +293 -0
  264. package/dist/pwa.d.ts.map +1 -0
  265. package/dist/pwa.js +204 -0
  266. package/dist/pwa.js.map +1 -0
  267. package/dist/quick-order.d.ts +340 -0
  268. package/dist/quick-order.d.ts.map +1 -0
  269. package/dist/quick-order.js +177 -0
  270. package/dist/quick-order.js.map +1 -0
  271. package/dist/quote-requests.d.ts +538 -0
  272. package/dist/quote-requests.d.ts.map +1 -0
  273. package/dist/quote-requests.js +308 -0
  274. package/dist/quote-requests.js.map +1 -0
  275. package/dist/returns.d.ts +774 -0
  276. package/dist/returns.d.ts.map +1 -0
  277. package/dist/returns.js +389 -0
  278. package/dist/returns.js.map +1 -0
  279. package/dist/sales-channels.d.ts +392 -0
  280. package/dist/sales-channels.d.ts.map +1 -0
  281. package/dist/sales-channels.js +285 -0
  282. package/dist/sales-channels.js.map +1 -0
  283. package/dist/scope-notice.d.ts +60 -0
  284. package/dist/scope-notice.d.ts.map +1 -0
  285. package/dist/scope-notice.js +56 -0
  286. package/dist/scope-notice.js.map +1 -0
  287. package/dist/search.d.ts +321 -0
  288. package/dist/search.d.ts.map +1 -0
  289. package/dist/search.js +160 -0
  290. package/dist/search.js.map +1 -0
  291. package/dist/seo.d.ts +113 -0
  292. package/dist/seo.d.ts.map +1 -0
  293. package/dist/seo.js +63 -0
  294. package/dist/seo.js.map +1 -0
  295. package/dist/settings.d.ts +453 -0
  296. package/dist/settings.d.ts.map +1 -0
  297. package/dist/settings.js +337 -0
  298. package/dist/settings.js.map +1 -0
  299. package/dist/shipments.d.ts +140 -0
  300. package/dist/shipments.d.ts.map +1 -0
  301. package/dist/shipments.js +14 -0
  302. package/dist/shipments.js.map +1 -0
  303. package/dist/shipping-methods.d.ts +350 -0
  304. package/dist/shipping-methods.d.ts.map +1 -0
  305. package/dist/shipping-methods.js +99 -0
  306. package/dist/shipping-methods.js.map +1 -0
  307. package/dist/shopping-lists.d.ts +122 -0
  308. package/dist/shopping-lists.d.ts.map +1 -0
  309. package/dist/shopping-lists.js +92 -0
  310. package/dist/shopping-lists.js.map +1 -0
  311. package/dist/taxes.d.ts +106 -0
  312. package/dist/taxes.d.ts.map +1 -0
  313. package/dist/taxes.js +80 -0
  314. package/dist/taxes.js.map +1 -0
  315. package/dist/text-normalization.d.ts +199 -0
  316. package/dist/text-normalization.d.ts.map +1 -0
  317. package/dist/text-normalization.js +205 -0
  318. package/dist/text-normalization.js.map +1 -0
  319. package/dist/transactional-emails.d.ts +459 -0
  320. package/dist/transactional-emails.d.ts.map +1 -0
  321. package/dist/transactional-emails.js +212 -0
  322. package/dist/transactional-emails.js.map +1 -0
  323. package/dist/webhooks.d.ts +69 -0
  324. package/dist/webhooks.d.ts.map +1 -0
  325. package/dist/webhooks.js +53 -0
  326. package/dist/webhooks.js.map +1 -0
  327. package/package.json +46 -0
@@ -0,0 +1,690 @@
1
+ /**
2
+ * `customer_accounts` module contracts — the in-process port surface
3
+ * (feature 075, Phase P).
4
+ *
5
+ * `customer_accounts` is the second-heaviest provider in the tree: 65 import
6
+ * sites across 19 modules, 51 of them on the `CustomerAccount` entity alone.
7
+ * That concentration is what this file answers — one record shape and one read
8
+ * port, so nineteen modules stop each writing their own `em.findOne`.
9
+ *
10
+ * Plain TypeScript rather than Zod: these describe in-process calls, not API
11
+ * boundaries. The module's HTTP shapes live in `customers.ts`, which is a
12
+ * different module's surface over the same rows and stays where it is — with
13
+ * one exception, the customer-group admin surface, whose Zod schemas arrived
14
+ * here with the entity in feature 076 (D-79) because this module serves those
15
+ * three routes itself.
16
+ *
17
+ * Nothing here imports from `backend/src/` (FR-034).
18
+ */
19
+ import { z } from 'zod';
20
+ export declare const customerGroupSchema: z.ZodObject<{
21
+ id: z.ZodString;
22
+ code: z.ZodString;
23
+ name: z.ZodString;
24
+ description: z.ZodNullable<z.ZodString>;
25
+ createdAt: z.ZodString;
26
+ updatedAt: z.ZodString;
27
+ }, z.core.$strip>;
28
+ export type CustomerGroup = z.infer<typeof customerGroupSchema>;
29
+ export declare const upsertCustomerGroupRequestSchema: z.ZodObject<{
30
+ code: z.ZodString;
31
+ name: z.ZodString;
32
+ description: z.ZodOptional<z.ZodNullable<z.ZodString>>;
33
+ }, z.core.$strip>;
34
+ /** A segmentation bucket as a module outside `customer_accounts` sees it. */
35
+ export interface CustomerGroupRecord {
36
+ id: string;
37
+ code: string;
38
+ name: string;
39
+ description: string | null;
40
+ createdAt: Date;
41
+ updatedAt: Date;
42
+ }
43
+ /**
44
+ * Container name: `customerGroupReadPort`. Owner: `customer_accounts`.
45
+ *
46
+ * The container name is unchanged by the relocation (D-81): it is what
47
+ * `check-port-dependencies.ts` compares, and keeping it means `pwa` did not
48
+ * have to be edited at all. `price_lists` reads it for the pricing rule-target
49
+ * picker and the rule-target validation; `pwa` for the push audience builder;
50
+ * `customers` for the group name on the admin customer list.
51
+ *
52
+ * This module is non-deactivatable, so the gate the port registration applies
53
+ * cannot be reached — as it could not under the previous owner, which is also
54
+ * non-deactivatable. The registration is a `providePort` anyway, for the reason
55
+ * its siblings give.
56
+ */
57
+ export interface CustomerGroupReadPort {
58
+ findById(id: string): Promise<CustomerGroupRecord | null>;
59
+ findByIds(ids: readonly string[]): Promise<CustomerGroupRecord[]>;
60
+ /** Every group, ordered by code. */
61
+ listAll(): Promise<CustomerGroupRecord[]>;
62
+ }
63
+ export type CustomerAccountRole = 'organization_admin' | 'regular_user';
64
+ export type CustomerAccountBlockSource = 'staff' | 'org_owner';
65
+ /**
66
+ * A customer account as it crosses a module boundary — a plain shape, never
67
+ * the ORM entity (FR-011).
68
+ *
69
+ * `passwordHash` is deliberately absent. It is read by exactly one module —
70
+ * the owner — and has no business travelling: a record that carries it turns
71
+ * every consumer into a place a credential can leak from.
72
+ */
73
+ export interface CustomerAccountRecord {
74
+ id: string;
75
+ /**
76
+ * The tenant that scopes this account. Never `null` since D-178:
77
+ * `customer_accounts.organization_id` is `NOT NULL`, an individual is backed
78
+ * by a single-member personal organisation, and there is no
79
+ * "no-organization" scoping path (Principle XI).
80
+ */
81
+ organizationId: string;
82
+ email: string;
83
+ firstName: string;
84
+ lastName: string;
85
+ role: CustomerAccountRole;
86
+ emailVerifiedAt: Date | null;
87
+ /** Whether a confirmed TOTP enrolment exists. The secret itself never travels. */
88
+ twoFactorEnabled: boolean;
89
+ lastLoginAt: Date | null;
90
+ createdAt: Date;
91
+ updatedAt: Date;
92
+ customFieldValues: Record<string, unknown>;
93
+ deletedAt: Date | null;
94
+ customerGroupId: string | null;
95
+ subtreeRollupEnabled: boolean;
96
+ blockedAt: Date | null;
97
+ blockReason: string | null;
98
+ blockSource: CustomerAccountBlockSource | null;
99
+ blockedByAdminUserId: string | null;
100
+ blockedByCustomerAccountId: string | null;
101
+ deletionRequestedByAdminUserId: string | null;
102
+ anonymizedAt: Date | null;
103
+ }
104
+ /**
105
+ * Which accounts a lookup should consider. Soft-deleted accounts still resolve
106
+ * from historical Orders, Invoices and RFQs, so "include them" is a real and
107
+ * frequently correct answer — sixteen of the measured call sites pass
108
+ * `deletedAt: null` and the rest deliberately do not.
109
+ */
110
+ export interface CustomerAccountLookupOptions {
111
+ /** Exclude soft-deleted accounts. Defaults to `false` — the wider read. */
112
+ activeOnly?: boolean;
113
+ }
114
+ /**
115
+ * Container name: `customerAccountReadPort`. Owner: `customer_accounts`.
116
+ *
117
+ * The union of what the nineteen consuming modules measurably ask for, and no
118
+ * more: identity lookups by id, ids, email and organisation, plus the two
119
+ * counting questions the org-admin invariant is enforced with.
120
+ *
121
+ * When `customer_accounts` is off every method throws `ModuleDisabledError`
122
+ * (503 `MODULE_DISABLED`) at the resolution seam. That is the right answer for
123
+ * a read whose absence would otherwise be indistinguishable from "no such
124
+ * account": a cart approval that cannot identify its buyer must refuse, not
125
+ * proceed anonymously.
126
+ *
127
+ * Whether `customer_accounts` has an off state at all is its manifest's `activation` to
128
+ * say, not this line's: a module declaring `nonDeactivatable` never enters one.
129
+ */
130
+ export interface CustomerAccountReadPort {
131
+ findById(id: string, options?: CustomerAccountLookupOptions): Promise<CustomerAccountRecord | null>;
132
+ findByIds(ids: readonly string[], options?: CustomerAccountLookupOptions): Promise<CustomerAccountRecord[]>;
133
+ findByEmail(email: string, options?: CustomerAccountLookupOptions): Promise<CustomerAccountRecord | null>;
134
+ /**
135
+ * The account, but only if it belongs to that organisation. The two-argument
136
+ * form exists because every admin path that touches a member checks
137
+ * membership first, and doing it in one query is what stops the check being
138
+ * forgotten.
139
+ */
140
+ findInOrganization(id: string, organizationId: string, options?: CustomerAccountLookupOptions): Promise<CustomerAccountRecord | null>;
141
+ /** Members of an organisation, ordered by role then creation date. */
142
+ listByOrganization(organizationId: string, options?: CustomerAccountLookupOptions): Promise<CustomerAccountRecord[]>;
143
+ /**
144
+ * How many accounts in the organisation hold the role, optionally ignoring
145
+ * one id and optionally counting only unblocked accounts. This is the shape
146
+ * the "an organisation may not lose its last admin" rule is spelled with, in
147
+ * three modules, three slightly different ways.
148
+ */
149
+ countByOrganizationRole(organizationId: string, role: CustomerAccountRole, options?: {
150
+ excludeCustomerAccountId?: string;
151
+ activeOnly?: boolean;
152
+ notBlocked?: boolean;
153
+ }): Promise<number>;
154
+ /**
155
+ * Substring search over the e-mail address, ordered by e-mail, capped at
156
+ * `limit`. An empty `query` returns the first `limit` accounts.
157
+ *
158
+ * Published after Phase P because `pwa`'s push Rule Builder is the one
159
+ * measured consumer that asks this question, and `listAll` is not the same
160
+ * answer: a picker that loads every account to keep 200 of them is a
161
+ * full-table read wearing a port.
162
+ */
163
+ searchByEmail(query: string, limit: number): Promise<CustomerAccountRecord[]>;
164
+ /**
165
+ * Ids of accounts whose e-mail, first name or last name contains `query`,
166
+ * case-insensitively. An empty `query` returns no ids.
167
+ *
168
+ * Published after Phase P, as the twin of
169
+ * `OrganizationDetailsPort.searchIdsByName` and for the same consumer: the
170
+ * admin orders list resolves the people a search term names, then constrains
171
+ * orders to them. It is deliberately **not** {@link searchByEmail}, which
172
+ * matches the address alone — an operator typing a surname into the orders
173
+ * search expects the surname to match, and it does today.
174
+ *
175
+ * Ids only, and uncapped, because the caller feeds them straight into an
176
+ * `$in` over its own table and a cap would silently drop orders rather than
177
+ * accounts.
178
+ */
179
+ searchIdsByName(query: string): Promise<string[]>;
180
+ /** Every account, for the bulk export adapter. Ordered by email. */
181
+ listAll(): Promise<CustomerAccountRecord[]>;
182
+ }
183
+ /**
184
+ * A new account, as the module that owns the membership asks for one.
185
+ *
186
+ * The **plain** password crosses, not a hash: `passwordHash` is deliberately
187
+ * absent from {@link CustomerAccountRecord} for the same reason, and a caller
188
+ * that hashes is a caller that has to be told which algorithm the owner uses
189
+ * and be trusted to keep using it. Hashing belongs on the owner's side of the
190
+ * port, and moving it there removed the last three `hashPassword` imports from
191
+ * `organizations`.
192
+ */
193
+ export interface CustomerAccountCreateInput {
194
+ organizationId: string;
195
+ email: string;
196
+ password: string;
197
+ firstName: string;
198
+ lastName: string;
199
+ role: CustomerAccountRole;
200
+ /**
201
+ * Whether the address is already confirmed. Accepting an invitation implies
202
+ * it — the invitation was delivered to that address; self-registration and
203
+ * the admin direct-create do not.
204
+ */
205
+ emailVerified?: boolean;
206
+ }
207
+ /** Absent fields are left unchanged. */
208
+ export interface CustomerAccountProfilePatch {
209
+ /**
210
+ * Lower-cased by the owner. Changing it clears `emailVerifiedAt`: the new
211
+ * address has not been confirmed, and leaving the old confirmation standing
212
+ * would let an admin verify an address by editing it.
213
+ */
214
+ email?: string;
215
+ firstName?: string;
216
+ lastName?: string;
217
+ }
218
+ /**
219
+ * Container name: `customerAccountMemberWritePort`. Owner: `customer_accounts`.
220
+ *
221
+ * The member lifecycle `organizations` runs over accounts in its
222
+ * organisations, published because that module ran it by creating and mutating
223
+ * this module's entity directly (feature 075, Phase C). It is the union of
224
+ * what that module measurably does and no more: three creation paths
225
+ * (self-registration, invitation accept, admin direct-create) collapse into
226
+ * one `create`, and the four field writes are one method each.
227
+ *
228
+ * **Every method is one unit of work on this module's table only** (D-78 rule
229
+ * 1). No `EntityManager` crosses, and none needs to: each caller's remaining
230
+ * writes are its own tables, flushed on its own side. Where that splits a
231
+ * flush the caller used to share — the invitation accept wrote the account and
232
+ * consumed the invitation together — the caller orders the two so the
233
+ * recoverable half fails first, and says so at the call site.
234
+ *
235
+ * **Each method audits its own write**, in the same unit of work, exactly as
236
+ * `roleService` and `addresses`' `addressService` do. The caller's own audit
237
+ * row is a different fact — "an operator edited this member on the
238
+ * organisation panel" rather than "this account's e-mail changed" — and both
239
+ * are kept, which is what the role endpoint has always recorded.
240
+ *
241
+ * `changeRole` is **not** here: {@link CustomerRolePort} already owns it, with
242
+ * the "an organisation keeps at least one admin" guard.
243
+ * {@link CustomerAccountMemberWritePort.promoteToOrganizationAdmin} is a
244
+ * different question — the break-glass path an operator reaches *because* an
245
+ * organisation has no admin left — and it is named rather than expressed as an
246
+ * unguarded `setRole`, which is a footgun beside a guarded one.
247
+ *
248
+ * When `customer_accounts` is off every method fails closed. The module is
249
+ * non-deactivatable, so that gate cannot be reached today; it is registered
250
+ * through `providePort` anyway, for the reason its seven siblings give.
251
+ */
252
+ export interface CustomerAccountMemberWritePort {
253
+ /**
254
+ * Creates the account. Throws HTTP 409 `EMAIL_ALREADY_REGISTERED` when the
255
+ * address is taken — the check is inside the write, so a caller that races
256
+ * its own pre-check still gets the right code rather than a constraint
257
+ * violation.
258
+ */
259
+ create(input: CustomerAccountCreateInput): Promise<CustomerAccountRecord>;
260
+ /**
261
+ * Throws HTTP 404 `NOT_FOUND` when no live account has that id, and HTTP 409
262
+ * `EMAIL_ALREADY_REGISTERED` when the new address belongs to another one.
263
+ */
264
+ updateProfile(customerAccountId: string, patch: CustomerAccountProfilePatch): Promise<CustomerAccountRecord>;
265
+ /** Feature 056 — the customer-side subtree roll-up capability. */
266
+ setSubtreeRollup(customerAccountId: string, enabled: boolean): Promise<CustomerAccountRecord>;
267
+ /** The break-glass promotion. Idempotent on an account that already holds it. */
268
+ promoteToOrganizationAdmin(customerAccountId: string): Promise<CustomerAccountRecord>;
269
+ /**
270
+ * Stamps `emailVerifiedAt`, idempotently — a second call keeps the first
271
+ * timestamp, so a retried verification does not move it.
272
+ */
273
+ markEmailVerified(customerAccountId: string, verifiedAt: Date): Promise<CustomerAccountRecord>;
274
+ /**
275
+ * Feature 051 — binds an org-less account to the organisation just
276
+ * provisioned for it. Throws HTTP 404 `NOT_FOUND` when no account has that
277
+ * id.
278
+ */
279
+ attachToOrganization(customerAccountId: string, organizationId: string): Promise<CustomerAccountRecord>;
280
+ }
281
+ /** What a caller sets the session cookie from after a successful first factor. */
282
+ export interface CustomerLoginResult {
283
+ customerAccount: CustomerAccountRecord;
284
+ /** Value to put into the Set-Cookie header. */
285
+ sessionCookieValue: string;
286
+ sessionExpiresAt: Date;
287
+ }
288
+ /** Discriminated outcome of the first login step (feature 042). */
289
+ export type CustomerLoginOutcome = ({
290
+ status: 'authenticated';
291
+ } & CustomerLoginResult) | {
292
+ status: 'mfaRequired';
293
+ challengeId: string;
294
+ } | {
295
+ status: 'mfaSetupRequired';
296
+ setupTicket: string;
297
+ };
298
+ export interface CustomerLoginInput {
299
+ email: string;
300
+ password: string;
301
+ ip?: string;
302
+ userAgent?: string;
303
+ salesChannelId?: string | null;
304
+ }
305
+ /**
306
+ * Container name: `customerAuthPort`. Owner: `customer_accounts`.
307
+ *
308
+ * (It said `customerAuthService` until issue #192. That name is registered too,
309
+ * for the `CustomerAuthService` **class**, which returns the `CustomerAccount`
310
+ * entity; it stays registered because this adapter is built over it. `customers/backend.ts` was the last consumer outside the module
311
+ * and resolves `customerAuthPort` since issue #195 — which was **not** the
312
+ * record-over-a-class trap {@link AddressServicePort} describes: it typed that
313
+ * resolution against a direct class-type import, an ordinary FR-011 edge that
314
+ * happened to sit on the same container name.)
315
+ *
316
+ * Consumed by `customers` and `organizations`, which own the storefront login,
317
+ * registration and self-service routes over these accounts.
318
+ *
319
+ * **Owner off:** the seam fails closed — resolving this port throws
320
+ * `ModuleDisabledError` and the call answers 503 `MODULE_DISABLED`, so nothing
321
+ * half-executes. Whether `customer_accounts` has an off state at all is its manifest's
322
+ * `activation` to say, not this line's: a module declaring
323
+ * `nonDeactivatable` never enters one.
324
+ */
325
+ export interface CustomerAuthPort {
326
+ login(input: CustomerLoginInput): Promise<CustomerLoginOutcome>;
327
+ changePassword(customerAccountId: string, currentPassword: string, newPassword: string): Promise<void>;
328
+ logout(sessionId: string): Promise<void>;
329
+ }
330
+ /**
331
+ * Container name: `passwordResetService`. Owner: `customer_accounts`.
332
+ *
333
+ * `requestReset` returns `{ rawToken: null }` for an unknown address on
334
+ * purpose — the caller must not be able to tell an unknown e-mail from a known
335
+ * one.
336
+ *
337
+ * **Owner off:** the seam fails closed — resolving this port throws
338
+ * `ModuleDisabledError` and the call answers 503 `MODULE_DISABLED`, so nothing
339
+ * half-executes. Whether `customer_accounts` has an off state at all is its manifest's
340
+ * `activation` to say, not this line's: a module declaring
341
+ * `nonDeactivatable` never enters one.
342
+ */
343
+ export interface CustomerPasswordResetPort {
344
+ requestReset(email: string): Promise<{
345
+ rawToken: string | null;
346
+ }>;
347
+ confirmReset(rawToken: string, newPassword: string): Promise<void>;
348
+ }
349
+ /**
350
+ * Container name: `customerPasswordStatePort`. Owner: `customer_accounts`.
351
+ *
352
+ * Whether the account has a password its holder can actually use — issue #222.
353
+ *
354
+ * `passwordHash` cannot answer that and never travels anyway: it is NOT NULL
355
+ * for every account, because federated auto-create mints a random one to keep
356
+ * the column satisfied. So a consumer asking "is there another way into this
357
+ * account" over `password_hash is not null` gets `true` for exactly the
358
+ * accounts where it is false.
359
+ *
360
+ * Deliberately not a field on {@link CustomerAccountRecord}. That record is
361
+ * read by nineteen modules; the state of a credential is a question one module
362
+ * asks — `mfa`, before severing an account's last federated identity — and a
363
+ * targeted port is what keeps it that way.
364
+ *
365
+ * The date rather than a boolean, because the one is derivable from the other
366
+ * and an account surface that wants to show *when* a password was set should
367
+ * not need a second method for it. `null` means no such password is on record:
368
+ * either none was ever set, or the row predates the column.
369
+ *
370
+ * **Owner off:** the seam fails closed — resolving this port throws
371
+ * `ModuleDisabledError` and the call answers 503 `MODULE_DISABLED`, so nothing
372
+ * half-executes. Whether `customer_accounts` has an off state at all is its
373
+ * manifest's `activation` to say, not this line's: a module declaring
374
+ * `nonDeactivatable` never enters one.
375
+ */
376
+ export interface CustomerPasswordStatePort {
377
+ /** `null` for an unknown id as well — an account nobody can find has no password on record. */
378
+ passwordSetAt(customerAccountId: string): Promise<Date | null>;
379
+ }
380
+ /**
381
+ * Container name: `customerPasswordVerificationPort`. Owner: `customer_accounts`.
382
+ *
383
+ * Does the stored credential of this account match the password presented?
384
+ * The customer-side twin of {@link AdminPasswordVerificationPort}, asked by
385
+ * the same consumer for the same reason: `mfa`'s step-up re-verification,
386
+ * before it disables a second factor.
387
+ *
388
+ * Deliberately **not** {@link CustomerAuthPort.login}, which is a different
389
+ * operation wearing similar arguments — it takes an e-mail, mints a session,
390
+ * stamps `lastLoginAt` and runs the login side effects. Step-up already knows
391
+ * who is asking and wants none of that.
392
+ *
393
+ * Deliberately **not** {@link CustomerPasswordStatePort} either, though both
394
+ * are credential questions one module asks: `passwordSetAt` answers "is there
395
+ * another way into this account" for an account-severing decision, and
396
+ * conflating the two would put a comparison against a caller-supplied secret
397
+ * on a port whose method takes no secret.
398
+ *
399
+ * `false` for an unknown id as well — an account nobody can find has no
400
+ * password to match. It is a lookup by id and nothing else: whether the caller
401
+ * may act as that account at all is the session layer's question, asked before
402
+ * this one.
403
+ *
404
+ * **Owner off:** the seam fails closed — resolving this port throws
405
+ * `ModuleDisabledError` and the call answers 503 `MODULE_DISABLED`, so nothing
406
+ * half-executes. Whether `customer_accounts` has an off state at all is its
407
+ * manifest's `activation` to say, not this line's: a module declaring
408
+ * `nonDeactivatable` never enters one.
409
+ */
410
+ export interface CustomerPasswordVerificationPort {
411
+ verifyPassword(customerAccountId: string, password: string): Promise<boolean>;
412
+ }
413
+ /**
414
+ * Container name: `customerRollupScopePort`. Owner: `customer_accounts`.
415
+ *
416
+ * Feature 056 (T032) — whether a customer login widens from single-org to its
417
+ * organization's subtree, and the widened id set when it does. Derived from the
418
+ * authenticated actor and never from request inputs (Principle XI).
419
+ *
420
+ * **Its consumer is a composition root, which is why the shape is unusual.**
421
+ * The per-request tenant-context builder is where the answer is needed, and the
422
+ * question has two halves owned by two modules: the capability flag on the
423
+ * account, which is this module's column, and the tree traversal, which is
424
+ * `organizations`'. The traversal therefore arrives as `subtreeIds` rather than
425
+ * being resolved here — the root already holds that module's tree service, and
426
+ * resolving it from this side would be a cross-module reach into a container
427
+ * name no contract publishes.
428
+ *
429
+ * `undefined` means "stay single-org", for both of the reasons it can: the
430
+ * account has no organisation, or it does not hold the capability. A caller
431
+ * that receives it must not widen.
432
+ *
433
+ * The capability read runs under a system scope on the owner's side, because
434
+ * the tenant context is being *established* by the caller and does not exist
435
+ * yet.
436
+ *
437
+ * **Owner off:** the seam fails closed — resolving this port throws
438
+ * `ModuleDisabledError` and the call answers 503 `MODULE_DISABLED`, so nothing
439
+ * half-executes. Whether `customer_accounts` has an off state at all is its
440
+ * manifest's `activation` to say, not this line's: a module declaring
441
+ * `nonDeactivatable` never enters one.
442
+ */
443
+ export interface CustomerRollupScopePort {
444
+ resolveSubtreeIds(customerAccountId: string, organizationId: string | null, subtreeIds: (organizationId: string) => Promise<string[]>): Promise<string[] | undefined>;
445
+ }
446
+ /**
447
+ * Container name: `customerRolePort`. Owner: `customer_accounts`.
448
+ *
449
+ * (It said `roleService` until issue #192. Nothing registers that name; the
450
+ * module's own class is `customerRoleService` and the gated port is this one.
451
+ * A name-keyed sweep had already miscounted this port as unreached because of
452
+ * it — see the Phase-P unreached-port audit, A5.)
453
+ *
454
+ * Consumed by `organizations`, which owns the member-management surface. The
455
+ * "an organisation keeps at least one admin" rule lives on this side of the
456
+ * port, not in the caller.
457
+ *
458
+ * **Owner off:** the seam fails closed — resolving this port throws
459
+ * `ModuleDisabledError` and the call answers 503 `MODULE_DISABLED`, so nothing
460
+ * half-executes. Whether `customer_accounts` has an off state at all is its manifest's
461
+ * `activation` to say, not this line's: a module declaring
462
+ * `nonDeactivatable` never enters one.
463
+ */
464
+ export interface CustomerRolePort {
465
+ listMembers(organizationId: string): Promise<CustomerAccountRecord[]>;
466
+ changeRole(organizationId: string, targetCustomerAccountId: string, newRole: CustomerAccountRole): Promise<CustomerAccountRecord>;
467
+ removeMember(organizationId: string, targetCustomerAccountId: string): Promise<void>;
468
+ }
469
+ /**
470
+ * A standalone (org-less) self-registration, as `customers` asks for one.
471
+ *
472
+ * The **plain** password crosses, for the reason
473
+ * {@link CustomerAccountCreateInput} gives: hashing belongs on the owner's
474
+ * side of the port, and a caller that hashes is a caller that has to be told
475
+ * which algorithm the owner uses and be trusted to keep using it.
476
+ *
477
+ * There is no `organizationId`, and since D-178 that is not because the account
478
+ * is created without one. The owner provisions the individual's personal
479
+ * organisation and writes the account **in one transaction**, so the caller has
480
+ * no organisation to supply and no window in which to supply it: the account
481
+ * row and its tenant either both exist or neither does. Before D-178 the two
482
+ * were separate units of work and a failure between them left a committed
483
+ * account with `organization_id = NULL` that nothing retried.
484
+ */
485
+ export interface CustomerAccountStandaloneCreateInput {
486
+ email: string;
487
+ password: string;
488
+ firstName: string;
489
+ lastName: string;
490
+ }
491
+ /**
492
+ * The request the write was made from, stamped onto its audit row.
493
+ *
494
+ * It travels because the admin moderation surface has always recorded it and
495
+ * the row is written on the owner's side of the port now: dropping it would
496
+ * quietly narrow what an operator can reconstruct from the audit log, which is
497
+ * the one thing a boundary cut must not do.
498
+ */
499
+ export interface CustomerAccountWriteRequestMeta {
500
+ ipAddress?: string | null;
501
+ userAgent?: string | null;
502
+ requestId?: string | null;
503
+ }
504
+ /** Which lifecycle bucket the admin customer list is asking for. */
505
+ export type CustomerAccountLifecycleStatus = 'active' | 'blocked' | 'deleted';
506
+ /**
507
+ * The admin customer list's filter, as plain data.
508
+ *
509
+ * `allowedOrganizationIds` is `null` for a platform administrator — the
510
+ * unscoped read — and otherwise the organisations the acting staff member may
511
+ * see, in which case org-less accounts are visible too (a standalone customer
512
+ * belongs to nobody's territory, so it belongs to everybody's). Expressing the
513
+ * scope as a nullable list rather than an `isPlatformAdmin` flag keeps the
514
+ * authority decision in `customers`, which is where the sales-rep scope is
515
+ * resolved.
516
+ */
517
+ export interface CustomerAccountAdminSearchCriteria {
518
+ /** Substring match over e-mail, first name and last name. */
519
+ q?: string | undefined;
520
+ status?: CustomerAccountLifecycleStatus | undefined;
521
+ organizationId?: string | undefined;
522
+ customerGroupId?: string | undefined;
523
+ allowedOrganizationIds: readonly string[] | null;
524
+ page: number;
525
+ pageSize: number;
526
+ }
527
+ export interface CustomerAccountAdminSearchResult {
528
+ rows: CustomerAccountRecord[];
529
+ total: number;
530
+ }
531
+ /**
532
+ * Container name: `customerAccountAdminSearchPort`. Owner: `customer_accounts`.
533
+ *
534
+ * The paginated, filtered read behind the admin customer list (feature 040,
535
+ * US5). Deliberately **not** a method on {@link CustomerAccountReadPort}: that
536
+ * port is what nineteen modules resolve and it is the union of what they all
537
+ * ask, whereas this is one screen's query — the same argument
538
+ * {@link CustomerPasswordStatePort} is separate for.
539
+ *
540
+ * It is one method rather than a set of primitives because the filter, the
541
+ * ordering and the page have to be one SQL statement: a caller that narrows a
542
+ * page after the fact returns fewer rows than it asked for, and one that pages
543
+ * after narrowing reads the whole table.
544
+ *
545
+ * The policy stays with the caller. This port takes the organisations the
546
+ * actor may see; it does not decide who that is.
547
+ *
548
+ * **Owner off:** the seam fails closed — resolving this port throws
549
+ * `ModuleDisabledError` and the call answers 503 `MODULE_DISABLED`, so nothing
550
+ * half-executes. Whether `customer_accounts` has an off state at all is its
551
+ * manifest's `activation` to say, not this line's: a module declaring
552
+ * `nonDeactivatable` never enters one.
553
+ */
554
+ export interface CustomerAccountAdminSearchPort {
555
+ search(criteria: CustomerAccountAdminSearchCriteria): Promise<CustomerAccountAdminSearchResult>;
556
+ }
557
+ /**
558
+ * Container name: `customerAccountLifecycleWritePort`. Owner: `customer_accounts`.
559
+ *
560
+ * The account lifecycle `customers` runs over this module's table — the
561
+ * counterpart to {@link CustomerAccountMemberWritePort}, which is the member
562
+ * lifecycle `organizations` runs. `customers` ran it by loading and mutating
563
+ * the `CustomerAccount` entity in five services and two route files; these are
564
+ * those writes, one method each, each a single unit of work on this table
565
+ * alone (D-78 rule 1). No `EntityManager` crosses.
566
+ *
567
+ * **The policy stays with the caller and the audit row comes here.** Whether a
568
+ * staff member may act on this customer, and whether blocking them would
569
+ * strand an organisation without an administrator, are `customers`' questions
570
+ * and stay there — the counting half of the second one is
571
+ * {@link CustomerAccountReadPort.countByOrganizationRole}, which already
572
+ * exists. What moves is the write and the one audit row that describes it, so
573
+ * that a row on this table is never written by a module that does not own it
574
+ * and never written without being recorded. Unlike
575
+ * {@link CustomerAccountMemberWritePort} the caller keeps **no** second row:
576
+ * there is no separate host fact here — "an operator blocked this customer" is
577
+ * the write.
578
+ *
579
+ * `anonymize` is the one method that is not an operator action. It is the
580
+ * retention sweep, it records itself with a null actor, and it is idempotent:
581
+ * an account already anonymised is returned unchanged, so a re-run after a
582
+ * partial sweep scrubs nothing twice.
583
+ *
584
+ * **Owner off:** the seam fails closed — resolving this port throws
585
+ * `ModuleDisabledError` and the call answers 503 `MODULE_DISABLED`, so nothing
586
+ * half-executes. Whether `customer_accounts` has an off state at all is its
587
+ * manifest's `activation` to say, not this line's: a module declaring
588
+ * `nonDeactivatable` never enters one.
589
+ */
590
+ export interface CustomerAccountLifecycleWritePort {
591
+ /**
592
+ * Creates an org-less account. Throws HTTP 409 `EMAIL_ALREADY_REGISTERED`
593
+ * when the address is taken — the check is inside the write, so a caller
594
+ * that races its own pre-check still gets the right code rather than a
595
+ * constraint violation.
596
+ */
597
+ createStandalone(input: CustomerAccountStandaloneCreateInput): Promise<CustomerAccountRecord>;
598
+ /**
599
+ * Idempotent: an account that is already blocked is returned unchanged and
600
+ * nothing is recorded. Throws HTTP 404 `CUSTOMER_NOT_FOUND` for an id no
601
+ * live account has.
602
+ */
603
+ block(customerAccountId: string, input: {
604
+ actorAdminUserId: string;
605
+ reason?: string | null;
606
+ audit?: CustomerAccountWriteRequestMeta;
607
+ }): Promise<CustomerAccountRecord>;
608
+ /** Idempotent, the same way round. */
609
+ unblock(customerAccountId: string, input: {
610
+ actorAdminUserId: string;
611
+ audit?: CustomerAccountWriteRequestMeta;
612
+ }): Promise<CustomerAccountRecord>;
613
+ /**
614
+ * Soft-delete. Throws HTTP 409 `CUSTOMER_ALREADY_DELETED` when the account
615
+ * already carries one — the caller's own refusal, kept here because the
616
+ * check and the write have to see the same row.
617
+ */
618
+ softDelete(customerAccountId: string, input: {
619
+ actorAdminUserId: string;
620
+ }): Promise<CustomerAccountRecord>;
621
+ /**
622
+ * Throws HTTP 409 `CUSTOMER_NOT_DELETED` when there is nothing to restore,
623
+ * and HTTP 409 `CUSTOMER_RESTORE_WINDOW_ELAPSED` once the account has been
624
+ * anonymised, which is irreversible.
625
+ */
626
+ restore(customerAccountId: string, input: {
627
+ actorAdminUserId: string;
628
+ }): Promise<CustomerAccountRecord>;
629
+ /**
630
+ * Moves the account into `organizationId`, recording
631
+ * `customer_account.organization_assigned`.
632
+ *
633
+ * The parameter was `string | null` until D-178, and the `null` meant "detach
634
+ * this account, leaving it standalone" — a durable row with no tenant, which
635
+ * Principle XI forbids and `customer_accounts.organization_id NOT NULL` now
636
+ * refuses at the column. What an operator detaching a member actually wants
637
+ * is {@link detachToPersonalOrganization} below.
638
+ */
639
+ setOrganization(customerAccountId: string, organizationId: string, input: {
640
+ actorAdminUserId: string;
641
+ }): Promise<CustomerAccountRecord>;
642
+ /**
643
+ * D-178 — the operator's "this person no longer belongs to that company",
644
+ * expressed as the move it has to be rather than as the detach it used to be.
645
+ *
646
+ * `personalOrganizationId` is the account's own personal organisation, which
647
+ * the caller obtains from
648
+ * `PersonalOrganizationPort.provisionPersonalOrganization`; passing anything
649
+ * else is a plain assignment and belongs in {@link setOrganization}. It is a
650
+ * separate method rather than a flag because each write on this port owns one
651
+ * audit verb, and this one keeps `customer_account.organization_unassigned` —
652
+ * the verb an operator's history already reads, now describing what really
653
+ * happened.
654
+ */
655
+ detachToPersonalOrganization(customerAccountId: string, personalOrganizationId: string, input: {
656
+ actorAdminUserId: string;
657
+ }): Promise<CustomerAccountRecord>;
658
+ /** `null` clears the direct group; the organisation's own group still applies. */
659
+ setCustomerGroup(customerAccountId: string, customerGroupId: string | null, input: {
660
+ actorAdminUserId: string;
661
+ }): Promise<CustomerAccountRecord>;
662
+ /**
663
+ * Feature 055 — the admin custom-field patch, as a read-modify-write the
664
+ * owner runs inside one Command.
665
+ *
666
+ * The **merge is the caller's**, and it is a callback rather than a
667
+ * pre-merged bag for one reason: the bag has to be read, validated and
668
+ * written inside a single transaction or a concurrent patch is silently
669
+ * lost, and the validator is {@link CustomFieldValuePort}, which the admin
670
+ * surface owning this screen resolves. So the caller supplies the function
671
+ * over the current bag and the owner runs it between its own load and its
672
+ * own write — one transaction, one audit row, and the entity never leaves.
673
+ *
674
+ * `merge` may throw; `custom_fields`' validation failure propagates to the
675
+ * caller unchanged, which is what the admin surface turns into a 422.
676
+ */
677
+ setCustomFieldValues(customerAccountId: string, merge: (current: Record<string, unknown>) => Promise<Record<string, unknown>>): Promise<CustomerAccountRecord>;
678
+ /**
679
+ * Soft-deleted accounts whose retention window elapsed before `cutoff` and
680
+ * that have not been anonymised yet — the sweep's work list.
681
+ */
682
+ listDueForAnonymization(cutoff: Date): Promise<CustomerAccountRecord[]>;
683
+ /**
684
+ * Irreversibly scrubs the account's personal data. Returns the account
685
+ * unchanged when it was already anonymised, and throws HTTP 404
686
+ * `CUSTOMER_NOT_FOUND` for an id nothing matches.
687
+ */
688
+ anonymize(customerAccountId: string): Promise<CustomerAccountRecord>;
689
+ }
690
+ //# sourceMappingURL=customer-accounts.d.ts.map