@choiceapps/communityservices-sdk 0.1.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 (294) hide show
  1. package/AGENTS.md +43 -0
  2. package/API.md +86 -0
  3. package/CHANGELOG.md +12 -0
  4. package/LICENSE +201 -0
  5. package/NOTICE +11 -0
  6. package/README.md +100 -0
  7. package/SSVF.md +131 -0
  8. package/browser/attachment-conflict.mjs +6 -0
  9. package/browser/case-plans-ui.mjs +23 -0
  10. package/browser/case-plans.d.mts +20 -0
  11. package/browser/case-plans.mjs +89 -0
  12. package/browser/client-creation.d.mts +16 -0
  13. package/browser/client-creation.mjs +60 -0
  14. package/browser/client-profile-ui.mjs +59 -0
  15. package/browser/client-profile.d.mts +24 -0
  16. package/browser/client-profile.mjs +94 -0
  17. package/browser/client-workflows.mjs +135 -0
  18. package/browser/connection.d.mts +4 -0
  19. package/browser/connection.mjs +41 -0
  20. package/browser/current-living-situation.d.mts +44 -0
  21. package/browser/current-living-situation.mjs +161 -0
  22. package/browser/date-of-birth-ui.mjs +68 -0
  23. package/browser/date-of-birth.d.mts +28 -0
  24. package/browser/date-of-birth.mjs +112 -0
  25. package/browser/enrollment-workflow.d.mts +174 -0
  26. package/browser/enrollment-workflow.mjs +518 -0
  27. package/browser/exit-details.mjs +21 -0
  28. package/browser/file-store.mjs +32 -0
  29. package/browser/gwt-mapped.mjs +46 -0
  30. package/browser/household-exit.d.mts +34 -0
  31. package/browser/household-exit.mjs +102 -0
  32. package/browser/households-ui.mjs +20 -0
  33. package/browser/households.d.mts +22 -0
  34. package/browser/households.mjs +115 -0
  35. package/browser/index.d.mts +78 -0
  36. package/browser/index.mjs +46 -0
  37. package/browser/member-entry-date.d.mts +30 -0
  38. package/browser/member-entry-date.mjs +71 -0
  39. package/browser/need-recording.d.mts +37 -0
  40. package/browser/need-recording.mjs +68 -0
  41. package/browser/need-ui.mjs +138 -0
  42. package/browser/page-lock.mjs +11 -0
  43. package/browser/password-recovery.mjs +49 -0
  44. package/browser/playwright-session.mjs +196 -0
  45. package/browser/program-discovery.d.mts +22 -0
  46. package/browser/program-discovery.mjs +80 -0
  47. package/browser/recovery-diagnostics.mjs +9 -0
  48. package/browser/service-recording.d.mts +45 -0
  49. package/browser/service-recording.mjs +148 -0
  50. package/browser/service-ui.mjs +160 -0
  51. package/browser/session-authentication.mjs +40 -0
  52. package/browser/shelter-inventory.d.mts +103 -0
  53. package/browser/shelter-inventory.mjs +273 -0
  54. package/browser/shelter-ui.mjs +382 -0
  55. package/browser/shelters.d.mts +139 -0
  56. package/browser/shelters.mjs +425 -0
  57. package/browser/ssvf-rules.d.mts +21 -0
  58. package/browser/ssvf-rules.mjs +145 -0
  59. package/browser/ui-login.mjs +135 -0
  60. package/dist/cjs/bulk.d.ts +33 -0
  61. package/dist/cjs/bulk.d.ts.map +1 -0
  62. package/dist/cjs/bulk.js +42 -0
  63. package/dist/cjs/bulk.js.map +1 -0
  64. package/dist/cjs/control.d.ts +35 -0
  65. package/dist/cjs/control.d.ts.map +1 -0
  66. package/dist/cjs/control.js +141 -0
  67. package/dist/cjs/control.js.map +1 -0
  68. package/dist/cjs/effective-date.d.ts +3 -0
  69. package/dist/cjs/effective-date.d.ts.map +1 -0
  70. package/dist/cjs/effective-date.js +27 -0
  71. package/dist/cjs/effective-date.js.map +1 -0
  72. package/dist/cjs/encoding.d.ts +32 -0
  73. package/dist/cjs/encoding.d.ts.map +1 -0
  74. package/dist/cjs/encoding.js +121 -0
  75. package/dist/cjs/encoding.js.map +1 -0
  76. package/dist/cjs/enrollment-verifications.d.ts +39 -0
  77. package/dist/cjs/enrollment-verifications.d.ts.map +1 -0
  78. package/dist/cjs/enrollment-verifications.js +96 -0
  79. package/dist/cjs/enrollment-verifications.js.map +1 -0
  80. package/dist/cjs/errors.d.ts +33 -0
  81. package/dist/cjs/errors.d.ts.map +1 -0
  82. package/dist/cjs/errors.js +45 -0
  83. package/dist/cjs/errors.js.map +1 -0
  84. package/dist/cjs/generated/v2-models.d.ts +1482 -0
  85. package/dist/cjs/generated/v2-models.d.ts.map +1 -0
  86. package/dist/cjs/generated/v2-models.js +3 -0
  87. package/dist/cjs/generated/v2-models.js.map +1 -0
  88. package/dist/cjs/generated/v2.d.ts +1149 -0
  89. package/dist/cjs/generated/v2.d.ts.map +1 -0
  90. package/dist/cjs/generated/v2.js +252 -0
  91. package/dist/cjs/generated/v2.js.map +1 -0
  92. package/dist/cjs/generated/v3-models.d.ts +1420 -0
  93. package/dist/cjs/generated/v3-models.d.ts.map +1 -0
  94. package/dist/cjs/generated/v3-models.js +3 -0
  95. package/dist/cjs/generated/v3-models.js.map +1 -0
  96. package/dist/cjs/generated/v3.d.ts +1149 -0
  97. package/dist/cjs/generated/v3.d.ts.map +1 -0
  98. package/dist/cjs/generated/v3.js +252 -0
  99. package/dist/cjs/generated/v3.js.map +1 -0
  100. package/dist/cjs/gwt-enrollments.d.ts +131 -0
  101. package/dist/cjs/gwt-enrollments.d.ts.map +1 -0
  102. package/dist/cjs/gwt-enrollments.js +280 -0
  103. package/dist/cjs/gwt-enrollments.js.map +1 -0
  104. package/dist/cjs/index.d.ts +3 -0
  105. package/dist/cjs/index.d.ts.map +1 -0
  106. package/dist/cjs/index.js +19 -0
  107. package/dist/cjs/index.js.map +1 -0
  108. package/dist/cjs/metadata.d.ts +100 -0
  109. package/dist/cjs/metadata.d.ts.map +1 -0
  110. package/dist/cjs/metadata.js +165 -0
  111. package/dist/cjs/metadata.js.map +1 -0
  112. package/dist/cjs/package.json +1 -0
  113. package/dist/cjs/program-discovery.d.ts +28 -0
  114. package/dist/cjs/program-discovery.d.ts.map +1 -0
  115. package/dist/cjs/program-discovery.js +58 -0
  116. package/dist/cjs/program-discovery.js.map +1 -0
  117. package/dist/cjs/race.d.ts +4 -0
  118. package/dist/cjs/race.d.ts.map +1 -0
  119. package/dist/cjs/race.js +40 -0
  120. package/dist/cjs/race.js.map +1 -0
  121. package/dist/cjs/runtime.d.ts +108 -0
  122. package/dist/cjs/runtime.d.ts.map +1 -0
  123. package/dist/cjs/runtime.js +38 -0
  124. package/dist/cjs/runtime.js.map +1 -0
  125. package/dist/cjs/subassessments.d.ts +29 -0
  126. package/dist/cjs/subassessments.d.ts.map +1 -0
  127. package/dist/cjs/subassessments.js +107 -0
  128. package/dist/cjs/subassessments.js.map +1 -0
  129. package/dist/cjs/testing.d.ts +45 -0
  130. package/dist/cjs/testing.d.ts.map +1 -0
  131. package/dist/cjs/testing.js +66 -0
  132. package/dist/cjs/testing.js.map +1 -0
  133. package/dist/cjs/transport.d.ts +19 -0
  134. package/dist/cjs/transport.d.ts.map +1 -0
  135. package/dist/cjs/transport.js +373 -0
  136. package/dist/cjs/transport.js.map +1 -0
  137. package/dist/cjs/v2.d.ts +13 -0
  138. package/dist/cjs/v2.d.ts.map +1 -0
  139. package/dist/cjs/v2.js +29 -0
  140. package/dist/cjs/v2.js.map +1 -0
  141. package/dist/cjs/v3.d.ts +13 -0
  142. package/dist/cjs/v3.d.ts.map +1 -0
  143. package/dist/cjs/v3.js +29 -0
  144. package/dist/cjs/v3.js.map +1 -0
  145. package/dist/cjs/validation.d.ts +33 -0
  146. package/dist/cjs/validation.d.ts.map +1 -0
  147. package/dist/cjs/validation.js +77 -0
  148. package/dist/cjs/validation.js.map +1 -0
  149. package/dist/cjs/verification.d.ts +82 -0
  150. package/dist/cjs/verification.d.ts.map +1 -0
  151. package/dist/cjs/verification.js +122 -0
  152. package/dist/cjs/verification.js.map +1 -0
  153. package/dist/esm/bulk.d.ts +33 -0
  154. package/dist/esm/bulk.d.ts.map +1 -0
  155. package/dist/esm/bulk.js +39 -0
  156. package/dist/esm/bulk.js.map +1 -0
  157. package/dist/esm/control.d.ts +35 -0
  158. package/dist/esm/control.d.ts.map +1 -0
  159. package/dist/esm/control.js +132 -0
  160. package/dist/esm/control.js.map +1 -0
  161. package/dist/esm/effective-date.d.ts +3 -0
  162. package/dist/esm/effective-date.d.ts.map +1 -0
  163. package/dist/esm/effective-date.js +24 -0
  164. package/dist/esm/effective-date.js.map +1 -0
  165. package/dist/esm/encoding.d.ts +32 -0
  166. package/dist/esm/encoding.d.ts.map +1 -0
  167. package/dist/esm/encoding.js +117 -0
  168. package/dist/esm/encoding.js.map +1 -0
  169. package/dist/esm/enrollment-verifications.d.ts +39 -0
  170. package/dist/esm/enrollment-verifications.d.ts.map +1 -0
  171. package/dist/esm/enrollment-verifications.js +93 -0
  172. package/dist/esm/enrollment-verifications.js.map +1 -0
  173. package/dist/esm/errors.d.ts +33 -0
  174. package/dist/esm/errors.d.ts.map +1 -0
  175. package/dist/esm/errors.js +33 -0
  176. package/dist/esm/errors.js.map +1 -0
  177. package/dist/esm/generated/v2-models.d.ts +1482 -0
  178. package/dist/esm/generated/v2-models.d.ts.map +1 -0
  179. package/dist/esm/generated/v2-models.js +2 -0
  180. package/dist/esm/generated/v2-models.js.map +1 -0
  181. package/dist/esm/generated/v2.d.ts +1149 -0
  182. package/dist/esm/generated/v2.d.ts.map +1 -0
  183. package/dist/esm/generated/v2.js +247 -0
  184. package/dist/esm/generated/v2.js.map +1 -0
  185. package/dist/esm/generated/v3-models.d.ts +1420 -0
  186. package/dist/esm/generated/v3-models.d.ts.map +1 -0
  187. package/dist/esm/generated/v3-models.js +2 -0
  188. package/dist/esm/generated/v3-models.js.map +1 -0
  189. package/dist/esm/generated/v3.d.ts +1149 -0
  190. package/dist/esm/generated/v3.d.ts.map +1 -0
  191. package/dist/esm/generated/v3.js +247 -0
  192. package/dist/esm/generated/v3.js.map +1 -0
  193. package/dist/esm/gwt-enrollments.d.ts +131 -0
  194. package/dist/esm/gwt-enrollments.d.ts.map +1 -0
  195. package/dist/esm/gwt-enrollments.js +274 -0
  196. package/dist/esm/gwt-enrollments.js.map +1 -0
  197. package/dist/esm/index.d.ts +3 -0
  198. package/dist/esm/index.d.ts.map +1 -0
  199. package/dist/esm/index.js +3 -0
  200. package/dist/esm/index.js.map +1 -0
  201. package/dist/esm/metadata.d.ts +100 -0
  202. package/dist/esm/metadata.d.ts.map +1 -0
  203. package/dist/esm/metadata.js +157 -0
  204. package/dist/esm/metadata.js.map +1 -0
  205. package/dist/esm/program-discovery.d.ts +28 -0
  206. package/dist/esm/program-discovery.d.ts.map +1 -0
  207. package/dist/esm/program-discovery.js +55 -0
  208. package/dist/esm/program-discovery.js.map +1 -0
  209. package/dist/esm/race.d.ts +4 -0
  210. package/dist/esm/race.d.ts.map +1 -0
  211. package/dist/esm/race.js +37 -0
  212. package/dist/esm/race.js.map +1 -0
  213. package/dist/esm/runtime.d.ts +108 -0
  214. package/dist/esm/runtime.d.ts.map +1 -0
  215. package/dist/esm/runtime.js +33 -0
  216. package/dist/esm/runtime.js.map +1 -0
  217. package/dist/esm/subassessments.d.ts +29 -0
  218. package/dist/esm/subassessments.d.ts.map +1 -0
  219. package/dist/esm/subassessments.js +103 -0
  220. package/dist/esm/subassessments.js.map +1 -0
  221. package/dist/esm/testing.d.ts +45 -0
  222. package/dist/esm/testing.d.ts.map +1 -0
  223. package/dist/esm/testing.js +60 -0
  224. package/dist/esm/testing.js.map +1 -0
  225. package/dist/esm/transport.d.ts +19 -0
  226. package/dist/esm/transport.d.ts.map +1 -0
  227. package/dist/esm/transport.js +369 -0
  228. package/dist/esm/transport.js.map +1 -0
  229. package/dist/esm/v2.d.ts +13 -0
  230. package/dist/esm/v2.d.ts.map +1 -0
  231. package/dist/esm/v2.js +13 -0
  232. package/dist/esm/v2.js.map +1 -0
  233. package/dist/esm/v3.d.ts +13 -0
  234. package/dist/esm/v3.d.ts.map +1 -0
  235. package/dist/esm/v3.js +13 -0
  236. package/dist/esm/v3.js.map +1 -0
  237. package/dist/esm/validation.d.ts +33 -0
  238. package/dist/esm/validation.d.ts.map +1 -0
  239. package/dist/esm/validation.js +72 -0
  240. package/dist/esm/validation.js.map +1 -0
  241. package/dist/esm/verification.d.ts +82 -0
  242. package/dist/esm/verification.d.ts.map +1 -0
  243. package/dist/esm/verification.js +117 -0
  244. package/dist/esm/verification.js.map +1 -0
  245. package/docs/README.md +56 -0
  246. package/docs/assessments.md +103 -0
  247. package/docs/browser-session.md +129 -0
  248. package/docs/capabilities.md +75 -0
  249. package/docs/configuration.md +81 -0
  250. package/docs/current-living-situation.md +56 -0
  251. package/docs/dates-and-answers.md +40 -0
  252. package/docs/development.md +81 -0
  253. package/docs/durable-records.md +156 -0
  254. package/docs/enrollments.md +217 -0
  255. package/docs/files-and-notes.md +39 -0
  256. package/docs/getting-started.md +56 -0
  257. package/docs/independent-needs.md +77 -0
  258. package/docs/integration-methods.md +60 -0
  259. package/docs/profiles.md +68 -0
  260. package/docs/reference.md +64 -0
  261. package/docs/rest-client.md +97 -0
  262. package/docs/services.md +66 -0
  263. package/docs/shelter-inventory.md +64 -0
  264. package/docs/shelters.md +75 -0
  265. package/docs/troubleshooting.md +61 -0
  266. package/docs/using-the-sdk.md +203 -0
  267. package/docs/vendor-issues/README.md +46 -0
  268. package/docs/vendor-issues/api-coverage.md +27 -0
  269. package/docs/vendor-issues/attachment-authentication.md +40 -0
  270. package/docs/vendor-issues/date-semantics.md +51 -0
  271. package/docs/vendor-issues/empty-service-list-404.md +51 -0
  272. package/docs/vendor-issues/enrollment-complete-target-error.md +178 -0
  273. package/docs/vendor-issues/enrollment-effective-date-column.md +112 -0
  274. package/docs/vendor-issues/household-head-validation.md +26 -0
  275. package/docs/vendor-issues/write-contracts.md +51 -0
  276. package/examples/README.md +41 -0
  277. package/examples/current-living-situation.mts +27 -0
  278. package/examples/durable-records.mts +78 -0
  279. package/examples/enrollment-workflow.mts +46 -0
  280. package/examples/gwt-enrollment-session.ts +17 -0
  281. package/examples/gwt-file-store.mjs +2 -0
  282. package/examples/household-enrollment-workflow.mts +43 -0
  283. package/examples/household-exit-workflow.mts +35 -0
  284. package/examples/household-lifecycle-workflow.mts +34 -0
  285. package/examples/independent-needs.mts +30 -0
  286. package/examples/program-discovery.ts +19 -0
  287. package/examples/quickstart.mjs +13 -0
  288. package/examples/shelter-inventory.mts +30 -0
  289. package/examples/shelters.mts +33 -0
  290. package/examples/usage.ts +23 -0
  291. package/examples/verification-snapshot.ts +9 -0
  292. package/llms.txt +26 -0
  293. package/package.json +103 -0
  294. package/specs/provenance.json +17 -0
@@ -0,0 +1,129 @@
1
+ # Use a browser session
2
+
3
+ [Documentation index](README.md) · [Agent guide](../AGENTS.md)
4
+
5
+ The examples below are integration fragments. Supply application-owned IDs and an authenticated session. The browser export runs in Node and controls a dedicated Playwright page; it is not frontend JavaScript.
6
+
7
+ ## Experimental GWT enrollment helpers
8
+
9
+ `GwtEnrollmentHelpers` adds `addHouseholdMembers` and `createInterimUpdate` for the web application's GWT-RPC calls. This is an explicit browser-session integration, separate from `WellSkyClient` and REST authentication. Supply a trusted `GwtEnrollmentSession` adapter and a `GwtMutationStore`:
10
+
11
+ ```ts
12
+ const helpers = new GwtEnrollmentHelpers({
13
+ tenant: "your-tenant",
14
+ moduleBaseUrl: "https://wscs.wellsky.com/your-tenant/com.bowmansystems.sp5.core.ServicePoint/",
15
+ session, // Fresh GWT/UI reads, fresh request preparation, authenticated same-origin POST.
16
+ store, // Shared durable enrollment lock and attempt journal.
17
+ });
18
+
19
+ await helpers.addHouseholdMembers({ clientId, enrollmentId, memberClientIds: [memberClientId] });
20
+ await helpers.createInterimUpdate({
21
+ clientId, enrollmentId, clientIds: [clientId, memberClientId],
22
+ reviewDate: "2026-10-22T12:00:00-04:00",
23
+ });
24
+ ```
25
+
26
+ Dates require an explicit offset and are compared as instants. No seven-hour correction is applied. The clients must already belong to the household before enrollment attachment. Interim clients must already be enrolled, and the target client must be included in `clientIds`.
27
+
28
+ For a client without a household, the browser adapter supports the UI's `createEntryExitReview` call with its implicit single client. The core accepts this signature only when the requested clients, enrolled members and household snapshot all identify that sole head. Household reviews continue to use `createEntryExitReviewWithHouseholdMembers`. Creating a review does not populate its assessment answers; discover the program's interim instrument and write its applicable answers at the review date separately.
29
+
30
+ The adapter must implement three operations:
31
+
32
+ - `readEnrollment`: return a fresh, complete enrollment model, household membership, enrolled members, and all reviews with their included clients. Supply a revision/digest covering those reads and a non-secret session identifier. Failed, truncated, stale, or REST-derived empty results are not valid snapshots.
33
+ - `prepare`: use current models to serialize the intended operation, or intercept a newly generated UI request and abort it **before** transmission. Return it with the matching intent, session, revision and capture timestamp. The SDK invokes preparation only when the requested state is missing; previously saved templates are not accepted. The adapter must verify the nested DTO identities and other stored values. The SDK validates the RPC envelope, known type signatures, endpoint, headers and freshness; it is not a general GWT object serializer.
34
+ - `send`: issue exactly one authenticated same-origin POST. Do not retry, redirect, log payloads, or replay after authentication refresh. The SDK does not acquire browser credentials or export browser cookies.
35
+
36
+ Before sending, the helper reads state again and checks session/revision stability, then persists an attempt. It verifies the result through another authoritative read even if the response is lost or contains an error. If the result cannot be established, it raises `AmbiguousCommitError` and leaves the attempt pending. A pending attempt blocks all subsequent writes to that enrollment until a fresh read proves it succeeded. An empty read alone never clears an uncertain attempt. A partially matching or duplicated Update at the same instant raises `GwtEnrollmentConflictError` instead of creating another review.
37
+
38
+ All processes must share the store and enrollment lock. `createInMemoryGwtMutationStore()` is for development and protects only callers sharing that instance; it does not survive restarts. The packaged [file-store example](../examples/gwt-file-store.mjs) re-exports a single-host durable file journal with exclusive locks and atomic writes. A crash intentionally leaves its lock for operator reconciliation. Multi-host deployments need a shared durable implementation. The server has no proven idempotency key or conditional-write contract, so this cannot prevent an unrelated user or integration writing outside that lock between read and send.
39
+
40
+ Sandbox acceptance through October 23, 2026 verified member attachment, interim creation, and same-key reconciliation across fresh helpers and browser logins without another mutation. See the [capability and evidence summary](capabilities.md).
41
+
42
+ ## Headless browser adapter
43
+
44
+ The optional ESM export `@choiceapps/communityservices-sdk/browser` supplies the adapter, a configurable browser login, and a durable single-host file journal. Install `playwright-core` separately and provide an installed Chrome executable (or install Playwright's Chromium). Tested with Playwright Core 1.56.1. The REST entry points do not load Playwright.
45
+
46
+ ```js
47
+ import { chromium } from 'playwright-core';
48
+ import {
49
+ openEnrollmentSession,
50
+ createFileGwtMutationStore,
51
+ } from '@choiceapps/communityservices-sdk/browser';
52
+
53
+ const session = await openEnrollmentSession({
54
+ tenant: 'your-tenant',
55
+ chromium,
56
+ envFile: '.env', // HMIS_UI_USER and HMIS_UI_PASSWORD, distinct from REST credentials.
57
+ executablePath: '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
58
+ store: createFileGwtMutationStore('/private/path/hmis-journal'),
59
+ });
60
+ try {
61
+ await session.helpers.addHouseholdMembers({
62
+ clientId, enrollmentId, memberClientIds: [memberClientId],
63
+ });
64
+ await session.helpers.createInterimUpdate({
65
+ clientId, enrollmentId, clientIds: [clientId, memberClientId],
66
+ reviewDate: '2026-10-21T12:00:00-04:00',
67
+ });
68
+ } finally {
69
+ await session.close();
70
+ }
71
+ ```
72
+
73
+ The login uses the required `tenant` option to build its URL, opens a fresh browser context, and never exports credentials or cookies. Use your application's configured tenant identifier. Supply `moduleBaseUrl` for another host; its path must match the tenant. Keep the page alive. Existing authenticated pages can use `createPlaywrightEnrollmentHelpers({page, store, tenant, moduleBaseUrl})`; all tenants use the same options and operations. The documented workflow was reproduced in a sandbox tenant. Each operation reads current GWT state, prepares through the current UI, intercepts and aborts Save, persists the attempt, sends the captured request once, and verifies it. Whole-second interim timestamps are required; ambiguous local daylight-saving times fail closed. Interim creation creates the review and its participant links, not additional assessment answers.
74
+
75
+ Use the page exclusively for these helpers. They serialize their operations on a shared page; do not drive it simultaneously through unrelated scripts. Selectors and GWT signatures are experimental. Unexpected UI/schema changes, incomplete lists (over 1,000 records), authentication loss, or conflicting records stop the operation. A new login does not clear an uncertain attempt. The file journal supports one host; use a shared durable store across multiple hosts. Keep journals private and retain them across restarts. A crash can leave a lock that requires manual reconciliation.
76
+
77
+ ## Expired UI passwords
78
+
79
+ Password recovery is enabled by default for `openUiSession` and `openEnrollmentSession`. It uses `!` and a private durable journal under `~/.wellsky-sdk/password-recovery`, shared by UI account across callers on that host. Set `HMIS_UI_PASSWORD_RECOVERY_DIR` in the selected credentials file to use a persistent directory of your choice. The directory must remain available across restarts. You can override the store or suffix:
80
+
81
+ ```js
82
+ const session = await openEnrollmentSession({
83
+ tenant: 'your-tenant',
84
+ chromium,
85
+ envFile: '.env',
86
+ executablePath: '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
87
+ store: createFileGwtMutationStore('/private/path/enrollment-journal'),
88
+ passwordRecovery: {
89
+ store: createFileGwtMutationStore('/private/path/password-recovery-journal'),
90
+ appendCharacter: '!', // Exactly one printable, non-space ASCII character.
91
+ },
92
+ });
93
+ console.log(session.passwordRecovery); // 'not-needed' or 'restored-original'
94
+ ```
95
+
96
+ On the expired-login form, the helper appends the character to the original `HMIS_UI_PASSWORD`, submits both reset fields, opens User Profile / Change Password, and restores the original. It then signs in with the original in a fresh browser context before returning success. The `.env` file stays unchanged. A normal login makes no password changes. Set `passwordRecovery: false` to disable password changes. With recovery disabled, expiration raises `LoginError` with code `PASSWORD_EXPIRED`.
97
+
98
+ Use one shared, durable recovery store for every caller of the same UI account. Multi-host deployments must override the default with shared durable storage and locking. Keep using an existing custom recovery store if it has a pending recovery; changing stores does not reconcile it. The per-account lock covers the entire recovery. Its journal contains stages and the append character, never either password. The original must be 8–49 characters when a reset is needed so the temporary password fits the observed 50-character limit. A 50-character password can still log in normally. Keep the original credential and append character unchanged while a recovery is pending. The SDK sanitizes login errors; do not enable Playwright tracing, request-body capture, or debug logging containing credentials.
99
+
100
+ A saved temporary-password attempt can resume with one authentication check of that temporary password. A saved restoration attempt can resume with one authentication check of the original. Neither uncertain password write is repeated. An interrupted or failed authentication check leaves a blocking journal stage and raises `RECOVERY_UNCERTAIN`; reconcile the account and journal before proceeding. A process crash may also leave the file store's exclusive lock. Do not delete journals or automatically expire locks to force retries. If server policy rejects restoring the original, the helper stops and the temporary password may remain active.
101
+
102
+ Both UI actions use GWT-RPC: the shipped expired-login handler calls `AuthenticationService.changePasswordHeartbeat`, while the user-settings restoration was observed calling `UserService.updateUserPassword` with HTTP 200 / GWT OK. The former uses the limited session established by the expired login. The helper currently drives the live forms to generate fresh requests. A standalone RPC-only password adapter has not been implemented or tested; captured password payloads must not be stored or replayed. See [sandbox findings](capabilities.md).
103
+
104
+ ## Adopting existing enrollment records
105
+
106
+ If requested membership or a review already exists, the GWT helper records a verified journal entry before returning `already-present`. Later calls return `reconciled`; if the observed state disappears, they raise `previously-verified-missing` without preparing or sending another write. Journal persistence must succeed even when the operation adopts existing records. Older versions did not journal these observations, so an existing journal cannot prove an observation that was never saved.
107
+
108
+ ## Reauthentication and interrupted writes
109
+
110
+ Sessions returned by the headless helpers retain the same `page` and `context` across reauthentication. Enrollment helpers, service UI, program discovery, attachments and exit helpers built from that managed page check authentication under the shared page lock before each browser operation. A visible login form or an HTTP 401 from the configured tenant triggers reauthentication using the same credentials and recovery journal. WellSky can also return HTTP 500 while leaving an expired dashboard visible. After an unclassified operation failure, the helper opens a separate read-only page to check authentication; it reconnects only if that page shows the login form. An inconclusive probe leaves the original error intact. Authentication failures block further automatic attempts until explicitly resolved.
111
+
112
+ ```js
113
+ await session.ensureAuthenticated(); // Reconnect only when expiry has been detected.
114
+ await session.reauthenticate(); // Explicit reconnect, serialized with browser operations.
115
+ ```
116
+
117
+ Confirmed attachment conflicts retain `AttachmentConflictError` and their `reason` without an authentication probe or reconnect. Authentication cannot resolve changed attachment identity, content, or intent. See [attachment recovery](files-and-notes.md). The next operation still performs normal authentication preflight.
118
+
119
+ For other failures that coincide with confirmed session expiration, the helper reconnects after the operation unwinds and throws `SessionInterruptedError`, code `SESSION_INTERRUPTED`. Its `reauthenticated` property says whether reconnecting succeeded. It never reruns the interrupted callback, resends a captured payload, clears a mutation journal, or restarts a bulk batch. If a failed authentication attempt needs to be retried after correction, call `session.reauthenticate()` explicitly.
120
+
121
+ Retry the enrollment or service operation with its original key, inputs and durable store. The existing journals reconcile confirmed commits and block unresolved writes. A changed key is not a recovery strategy. GWT snapshots and prepared requests from before reauthentication are invalidated, and the next operation reads fresh state. Bulk service recording still stops at the failed row; retry with the same row keys after resolving it.
122
+
123
+ This behavior applies to SDK helpers using the managed page lock. A raw Playwright action or low-level GWT session does not gain automatic retries or authentication recovery. Unknown GWT errors and HTTP 403 are not, by themselves, treated as proof of expiration. Use explicit reauthentication when session loss is established but the UI does not display its login form. Reauthentication can reset UI navigation and provider/Enter Data As context; operations must select and validate their requested provider again.
124
+
125
+ ## Grouped-enrollment REST error
126
+
127
+ Sandbox REST enrollment-list and complete-enrollment reads returned HTTP 400 with `42703: column v.entry_date_effective does not exist` after household attachment. This occurred after both an ordinary UI save and a direct RPC replay. The replay fixture reproduced it before its interim was created. Fresh UI/GWT reads confirmed the stored enrollment and reviews.
128
+
129
+ This indicates a server query/schema problem in the observed grouped-enrollment read path. A schema/version mismatch is a possible explanation, not a confirmed root cause. It is distinct from the verification-date cutoff issue. The client cannot repair the server column/view, and changing record dates or repeating creation is not a workaround. Consumers relying on those REST reads need a verified GWT read path or an upstream WellSky fix; a failed REST read must never mean "record absent."
@@ -0,0 +1,75 @@
1
+ # Capabilities and verified limits
2
+
3
+ [Documentation index](README.md)
4
+
5
+ See [integration methods](integration-methods.md) for the reasoning behind each REST, GWT-RPC, or browser path, and [WellSky feedback](vendor-issues/README.md) for actionable reports and contract questions.
6
+
7
+ The generated client covers the pinned V2/V3 Swagger operations. The following findings were reproduced in a sandbox tenant through October 24, 2026. They are not production certification or proof of behavior at every provider. The evidence summaries below retain the tested scenarios and their limitations.
8
+
9
+ | Task | Available path | Verified behavior and limit |
10
+ | --- | --- | --- |
11
+ | Typed REST requests | Root, `/v2`, `/v3` | Generated operation/model coverage with validation; server behavior still varies by endpoint |
12
+ | Questions and picklists | `api.assessments.getDefinition`, `api.questions`, `api.picklists` | Client-scoped reads, nested definitions, current options; no complete conditional engine or historical option catalog |
13
+ | Programs | `readProgramCatalog` | Counted active/operational providers; includes organizations, not just enrollable programs; no permission/eligibility guarantee |
14
+ | Program assessment defaults | `createProgramDiscovery` | Read-only admin UI/GWT discovery of entry/interim/exit defaults; requires administrative access |
15
+ | Enrollment and assessment answers | `createEnrollmentWorkflow` | Discovers defaults, saves per-client answers/rows, verifies and reconciles; existing clients required |
16
+ | Client creation | `createClientCreator` | Durable source mapping; candidate detection without merging; lost receipt requires explicit verified ID |
17
+ | Household creation and membership | `createHouseholds` | Stable keys, preserved history, later members, non-head departures and relationship changes; no head replacement or household closure |
18
+ | Household enrollment | `createHouseholdEnrollment` | Existing household and at least two initial clients including head; per-member plans and IDs |
19
+ | Later child or member | `addHouseholdMembers` | Must already belong to household; own later entry date, assessment, and DOB; existing dates preserved |
20
+ | Interims | Workflow `createInterim`, `createHouseholdInterim` or low-level GWT `createInterimUpdate` | Workflow populates answers; low-level helper creates only review/participant links. Selected household members supported |
21
+ | Household exit | `exitHousehold` with exit UI port | Selected members, staggered dates, subsidy/Other details, per-member exit assessments; all-member metadata uses one GWT save, assessments follow separately |
22
+ | Head of household | Enrollment workflow guards | Missing or mid-enrollment replacement head rejected. Head cannot exit before remaining members. Retroactive replacement is not implemented |
23
+ | Verification records at entry | `readEnrollmentVerifications` | Reads generic assessment and filters intervals; complete-enrollment entry arrays can omit valid UI rows |
24
+ | Existing verification end dates | `api.subassessments.setEndDate` | Preserves row ID and other values, verifies result; no blind resend or delete/recreate |
25
+ | Client Record fields | `createClientProfile` | Names, SSN, quality, veteran fields via preserved REST update; Sex via UI. Legacy Gender is not Sex |
26
+ | DOB | `createDateOfBirth`, workflow `birthDates` | Calendar-date encoding, UI/API agreement, same-entry timestamp correction via UI; no arbitrary historical correction |
27
+ | Independent needs | `createNeedRecorder`, `createNeedUi` | Add Need form, explicit status/outcome, durable key, no service/referral creation |
28
+ | Service types and AIRS taxonomy | `createNeedUi().listServiceTypes` / `listServiceQuicklist` | Tenant-wide active assignable codes and separate program quicklists; not service eligibility |
29
+ | Shelters and bed stays | `createShelters`, `createShelterUi`; [shelter guide](shelters.md) | Named-bed household check-in/checkout, same-program guards, durable retries; no overflow or inactivity scheduler |
30
+ | Shelter inventory reads | `createShelterInventory`; [inventory guide](shelter-inventory.md) | Complete visible unit lists and named beds, occupants, holds, overflow reporting, and known/unknown vacancies; UI-derived bed keys, no server bed IDs, reservations, or future projections |
31
+ | Services and bulk services | `createServiceRecorder` | Closed/Fully Met need, REST service plus UI completion; sequential stable-key bulk with stop-on-failure |
32
+ | SSVF conditional fields | Recorder plus `getSsvfServiceRules` | Benefit choices and Other text via REST; assistance dates through UI. Rules are a dated sandbox snapshot |
33
+ | Funding Sources | `createServiceUi().listFundingSources`, recorder allocations | Current provider/service/session lookup; existing permitted service context required. No universal funding catalog |
34
+ | Attachment REST API | `fileAttachments.createForClient`, `getForClient` | Encoded multipart/body credentials supported, but supplied sandbox credentials still fail server authentication checks; not a verified upload solution |
35
+ | Profile, Case Plans, goal uploads | `session.workflows.uploadAttachment` | Browser multipart, 10,000,000-byte cap, downloaded checksum verification, duplicate guard; visibility unchanged and public access unverified |
36
+ | Client Notes, goals, action steps | `createCasePlans` plus raw REST methods | Journaled create/update and supported-field verification; helper supplies clientId on goal updates; unreadable follow-up fields excluded |
37
+ | Goal case notes | `session.workflows.createCaseNote` | Optional source key rejects changed intent and verifies saved note ID; legacy unkeyed matching remains; single client only. REST goal response does not expose these notes |
38
+ | Multi-client/multi-case notes | No packaged bulk helper | Do not infer note participants from unrelated `households` nested in a goal response |
39
+ | Expired UI password | Headless login, enabled by default | Temporary suffix then restore original, durable recovery stages, fresh-login verification; policy rejection may leave temporary password active |
40
+ | Session expiration | Managed session plus durable workflow journals | Reauthenticate and report interruption; caller retries original intent for reconciliation, not automatic callback replay |
41
+
42
+ ## Known server gaps
43
+
44
+ Grouped REST enrollment reads can return `42703: column v.entry_date_effective does not exist` after both UI and RPC saves. This is not specific to replay. A broken read is not an absent enrollment. The browser enrollment reader uses current UI/GWT evidence.
45
+
46
+ The October 24 [V2/V3 comparison](vendor-issues/enrollment-effective-date-column.md) reproduced the missing-column error for all 16 tested grouped complete enrollments in each version. Individual-only client lists succeeded, and individual pages within a mixed client also succeeded. Switching to V2 does not fix the issue. One individual complete read had a separate `Non-static method requires a target.` error.
47
+
48
+ REST DOB updates at an existing effective timestamp can fail with an entity-tracking error. Changing the timestamp to evade that error changes history. The dedicated helper uses the matching entry UI instead.
49
+
50
+ Attachment REST authentication failure reproduces outside the SDK. The token user-ID claim format is a possible contributor, not a confirmed root cause. Do not modify token claims or passwords to work around it.
51
+
52
+ ## Evidence and support boundary
53
+
54
+ Local tests cover the generated routes, validation, journal recovery, and browser fixtures. Live acceptance tested new synthetic clients, UI readback, and repeated keys. The latest DOB workflow run verified one history row and one enrollment per new client after household interim changes, with zero additional mutations on retries.
55
+
56
+ The SDK guides and vendor reports retain sanitized observations, dates, controls, and limitations. Raw authenticated captures, client data, and private journals do not belong in the repository or npm package. Recheck server-dependent behavior within an authorized scope before depending on a different deployment's UI or picklists.
57
+
58
+ ### Retained sandbox acceptance evidence
59
+
60
+ These observations explain the current contracts. They are dated acceptance results, not a runnable live test suite or fresh verification of the server.
61
+
62
+ | Scenario | Reproduced evidence | Limit and owning guide |
63
+ | --- | --- | --- |
64
+ | Fresh-helper retries and existing-record adoption, October 24, 2026 | Household membership/interims, attachments, and a service reconciled to the same saved identities. REST, GWT mutation, and upload counters had zero increase during retries and checks. | Missing-record and changed-identity faults were injected into readback or journal copies, not created by deleting live records. See [durable records](durable-records.md) and [recovery](troubleshooting.md). |
65
+ | Session loss after committed writes, October 23, 2026 | Controlled cookie loss after household enrollment and service saves was followed by reauthentication and reconciliation under the original keys, without duplicate records. | This tests the observed stages, not every possible interruption. Authentication requests and read-only GWT POSTs are distinct from record writes. See [browser sessions](browser-session.md). |
66
+ | Household exits and head coverage, October 23, 2026 | An invalid early head exit was blocked before writes. Valid staggered exits and an all-member exit preserved dates, original heads, and selected subgroups; retries made no extra record writes. | Earlier server-accepted orphaned groups were invalid under the current SDK contract. See [head validation feedback](vendor-issues/household-head-validation.md) and [enrollments](enrollments.md). |
67
+ | Attachment identity and content, October 24, 2026 | Profile, Case Plans, and goal files retained their IDs across fresh helpers, and downloaded bytes matched the originals. | Does not establish anonymous access or sharing permissions. REST authentication remained a separate failure. See [files and notes](files-and-notes.md). |
68
+ | SSVF conditional forms, October 23, 2026 | Form inspection checked conditional branches and required-date validation without saving; four REST-supported dependencies were then saved and read back, with zero duplicate writes on same-key retries. | This is a dated rules snapshot, not exhaustive financial assistance or funding-allocation acceptance. See [SSVF rules](../SSVF.md) and [services](services.md). |
69
+ | Expired-password recovery, October 23, 2026 | An expired account completed temporary-password recovery, restored its original password, and authenticated in a fresh context. | Account policy can reject a recovery stage; retain the durable recovery journal. See [browser sessions](browser-session.md). |
70
+
71
+ Independent needs and AIRS/service-type discovery use UI/GWT helpers. See [independent needs](independent-needs.md) for the creation, counted catalog, and retry contracts. The pinned Swagger exposes need reads but no independent-need creation.
72
+
73
+ The [separate complete-enrollment target error](vendor-issues/enrollment-complete-target-error.md) was isolated to missing DOB information in controlled sandbox fixtures. Adding only `SVPPROFDOB`, or only `SVPPROFDOBTYPE` with `DOBTYPE_DONTKNOW` or `DOBTYPE_REFUSED`, changed HTTP 400 to success in V2 and V3. The quality-only fixtures retained empty DOB values, confirmed through REST and the UI. Never invent a DOB or recreate an enrollment to recover a read.
74
+
75
+ Current Living Situation contact recording is available through `createCurrentLivingSituationRecorder`. It preserves distinct contacts with program-entry starts and optional recording-program verification. See the [contact guide](current-living-situation.md) for metadata mapping, date contracts, blocked server reads, and acceptance limits.
@@ -0,0 +1,81 @@
1
+ # Configuration reference
2
+
3
+ [Documentation index](README.md) · [REST usage](rest-client.md)
4
+
5
+ The root export selects V3. `/v2` and `/v3` fix their own `x-version` headers. Options are defined in [ClientOptions](../dist/esm/runtime.d.ts); response models and route parameters are version-specific.
6
+
7
+ ## REST client options
8
+
9
+ | Option | Default | Contract |
10
+ | --- | --- | --- |
11
+ | `tenant` | Required | Explicit tenant identifier; read-only on the client |
12
+ | `auth` | Required | One of the modes below |
13
+ | `baseUrl` | `https://csclientapi.wellsky.com/client-api-prod` | The explicit tenant selects the system on this shared host |
14
+ | `fetch` | Global `fetch` | Injectable transport for tests or routing |
15
+ | `timeoutMs` | `30000` | Total request deadline, including authentication, queueing, backoff, and body reads |
16
+ | `maxConcurrency` | `8` | Shared in-process scheduler for matching clients |
17
+ | `maxReadRetries` | `2` | Additional GET attempts; writes are not replayed |
18
+ | `retryBaseDelayMs` | `250` | Initial backoff setting |
19
+ | `maxRetryDelayMs` | `30000` | Backoff/Retry-After bound |
20
+ | `cooldown` | `{threshold:5, windowMs:10000, durationMs:30000}` | Shared failure-window cooldown |
21
+ | `metadataCacheTtlMs` | `60000` | Definition cache lifetime |
22
+ | `metadataCacheMaxEntries` | `100` | Per-client metadata cache bound |
23
+ | `mobileAssessmentDateCorrectionMinutes` | Unset | Measured integer offset from -840 to 840, applied only to AddAssessmentAnswer's effective-date query |
24
+ | `hooks`, `logger` | Unset | Allowlisted events without bodies, credentials, filters, or tokens |
25
+
26
+ Matching tenant/base URL/transport clients share scheduling state and must use consistent scheduler settings. Coordination is within one process. Tenant consistency checks are described in [REST configuration](rest-client.md#configure-a-client).
27
+
28
+ ## Authentication modes
29
+
30
+ | `auth.kind` | Required fields | Lifecycle |
31
+ | --- | --- | --- |
32
+ | `credentials` | `username`, `password` | Automatic `SetAuthKey`; matching credentials share cache and in-flight mint |
33
+ | `token` | `token` | Caller owns expiry and replacement |
34
+ | `provider` | `getToken({forceRefresh, signal})` | Caller returns a token; honors cancellation and refresh requests |
35
+ | `none` | None | Only for deliberately unauthenticated operations or synthetic tests |
36
+
37
+ REST authentication does not read `.env`. A GET can refresh authentication once after 401. A write is not replayed after 401. Mint failures return to the caller. Token minting can affect other processes using that account; keep token lifecycle coordinated in your application.
38
+
39
+ ## Per-request controls
40
+
41
+ Every request accepts `signal`, `timeoutMs`, and `correlationId`. Use an opaque correlation ID. `WellSkyError` reports `kind`, operation template, and optional status/request ID/retry delay. Upstream bodies are discarded. See [error recovery](troubleshooting.md).
42
+
43
+ ## Browser session options
44
+
45
+ `openUiSession` and `openEnrollmentSession` use [LoginOptions](../browser/index.d.mts):
46
+
47
+ | Option | Meaning |
48
+ | --- | --- |
49
+ | `tenant` | Required tenant identifier, for example `your-tenant` |
50
+ | `moduleBaseUrl` | Optional complete application module URL; must match `tenant` |
51
+ | `chromium` | Required Playwright browser type supplied by the application |
52
+ | `envFile` | Selected local credentials file; defaults to `.env` |
53
+ | `executablePath` | Path to an installed compatible browser; needed when no Playwright browser is installed |
54
+ | `timeoutMs` | Per-action login/bootstrap timeout; defaults to `90000` milliseconds. This is not a total workflow deadline |
55
+ | `passwordRecovery` | Enabled by default; `false` disables password mutations, or supply `{store, appendCharacter}` |
56
+ | `store` | Required by `openEnrollmentSession` for enrollment/client mutation journals |
57
+
58
+ Only the selected file supplies UI credentials:
59
+
60
+ ```dotenv
61
+ HMIS_UI_USER=your-browser-login
62
+ HMIS_UI_PASSWORD=your-browser-password
63
+ # Optional persistent recovery directory:
64
+ HMIS_UI_PASSWORD_RECOVERY_DIR=/private/application/password-recovery
65
+ ```
66
+
67
+ The recovery directory defaults to `~/.wellsky-sdk/password-recovery`. Its journal is separate from workflow mutation journals. The default temporary suffix is `!`. Read [password recovery](browser-session.md#expired-ui-passwords) before enabling an account: the helper changes an expired password and then attempts to restore the original. REST credentials can differ from these UI credentials.
68
+
69
+ No browser binary is bundled. The browser helper uses a fresh context, configured tenant URL, and a dedicated page. Sessions expose `ensureAuthenticated`, `reauthenticate`, and `close`. Close the session in `finally`.
70
+
71
+ ## Tenant connections
72
+
73
+ Use the neutral exports, such as `openUiSession`, `openEnrollmentSession`, `createServiceRecorder`, and `createCurrentLivingSituationRecorder`. Browser login requires an explicit tenant.
74
+
75
+ Supply `tenant` when opening a browser session. For the default host, `tenant: 'your-tenant'` resolves to `https://wscs.wellsky.com/your-tenant/com.bowmansystems.sp5.core.ServicePoint/`. Changing the tenant selects another path; the SDK methods stay the same. `moduleBaseUrl` can select another HTTPS host, but its tenant path must match. REST and browser credentials remain separate.
76
+
77
+ Pass `session.page` to UI adapters. Each adapter binds to that page's application URL and rejects later navigation into another tenant. REST/UI compositions reject mismatched tenant identifiers. Keep program, provider, client, assessment, and picklist IDs specific to the selected tenant.
78
+
79
+ Browser sessions expose `session.connection`. A need recorder gets its tenant from `createNeedUi({page: session.page})`. When supplying a custom need adapter, pass `tenant` explicitly because there is no REST client from which to derive it.
80
+
81
+ Keep durable journals separate for each tenant and deployment. Retain the same journal when resuming an operation so uncertain writes are reconciled. Password-recovery records include the connection scope and account identity.
@@ -0,0 +1,56 @@
1
+ # Record Current Living Situation contacts
2
+
3
+ [Documentation index](README.md) · [Assessment guide](assessments.md) · [Public contract](../browser/current-living-situation.d.mts) · [Example](../examples/current-living-situation.mts)
4
+
5
+ Street outreach teams and night-by-night emergency shelter staff generally record Current Living Situation at each meaningful client contact. A contact is an event. An unchanged living situation does not make the next contact a duplicate.
6
+
7
+ | Field | Meaning |
8
+ | --- | --- |
9
+ | Subassessment Start Date | The selected client's program entry date, for every contact in that enrollment |
10
+ | Subassessment End Date | May be null or empty. A later contact does not close the previous contact row |
11
+ | Information Date | The date that contact happened |
12
+ | Current Living Situation | The client's actual living situation on that contact date, selected from this question's current picklist |
13
+ | Living Situation Verified By | Optional unless the actual platform requires it. May identify the program recording the data |
14
+
15
+ Do not use contact date as the row start, or replace the previous contact's information date or living-situation answer. Do not copy the income-verification close-and-replace pattern to these event records. Program entry, the contact's information date, and the assessment answer's effective timestamp have separate roles.
16
+
17
+ ## Discover the fields and choices
18
+
19
+ Read `api.assessments.getDefinition({clientId, assessmentCode, refresh: true})` for the program's instrument. Supply its exact virtual fields to `createCurrentLivingSituationRecorder`. The SDK does not choose a program, assessment, project type, or living-situation answer from a shelter bed assignment.
20
+
21
+ An October 24, 2026 sandbox metadata read exposed this mapping:
22
+
23
+ | Role | Virtual field | Type |
24
+ | --- | --- | --- |
25
+ | Parent | `SVP_HUD_CURRENTLIVINGSITUATION` | sub_assessment |
26
+ | Start | `SVP_HUD_CURRENTLIVINGSITUATIONSTART` | date |
27
+ | End | `SVP_HUD_CURRENTLIVINGSITUATIONEND` | date |
28
+ | Information date | `SVP_HUD_CLS_INFODATE` | date |
29
+ | Living situation | `SVP_HUD_CLS_CURRENTLIVINGSIT` | lookup |
30
+ | Verified By | `SVP_HUD_CLS_LIVINGSITVERIFIEDBY` | lookup |
31
+
32
+ The form metadata explicitly labels Information Date as Date of Contact. Verified By had program options with separate **picklist option IDs**. Resolve the actual option from that question and use `{option: {code: discoveredCode}}`; do not send a numeric program ID or construct a code from it. A recording program is allowed, but it is not a required default. If another verified instrument exposes text instead, supply `{value: programDisplayName}`. Numeric values are rejected for text questions. Provider-valued questions are refused on the mobile path because the SDK cannot guarantee their persistence there.
33
+
34
+ The helper requires information date and living situation, derives start from the stored enrollment, and sends an empty end. It leaves other answers absent unless supplied. `additionalAnswers` accepts actual extra platform questions and cannot override the mapped fields. Metadata-required additional answers must be provided explicitly. Conditional rules not represented in metadata still require application/form validation; do not invent defaults. A platform requirement for a nonempty end raises `platform-requires-end-date` instead of changing this workflow's rule.
35
+
36
+ ## Record and retry
37
+
38
+ Use the [typed example](../examples/current-living-situation.mts) with an API client configured for the target tenant and durable `createFileGwtMutationStore` or equivalent shared store. No browser is launched and no credentials are loaded by this recorder.
39
+
40
+ Each input specifies an existing client and enrollment, the instrument, a stable contact event key, information date, and the selected living-situation answer. It does not accept a caller-supplied start date. The helper reads the counted enrollment list and derives start from the selected enrollment's `projectStartDate`. This matters for a later-entering household member: use that person's enrollment, not another member's entry. Contacts before entry or after a recorded exit are refused. Failed, incomplete, or ambiguous enrollment/assessment reads block writes.
41
+
42
+ As with the existing enrollment workflow, date inputs use timezone-qualified ISO instants with whole-second precision. Supply the contact's intended date and time in the program's timezone, for example `2026-10-24T09:00:00-04:00`. The information-date answer and assessment effective-date query use that instant; the row start uses program entry. The helper does not use the host timezone or shift body dates. Only a separately configured, measured mobile query correction affects the query; see [date contracts](dates-and-answers.md). Date-only input is rejected rather than interpreted in an unknown timezone.
43
+
44
+ For outreach, record each meaningful outreach contact with its own upstream event key and the situation reported that day. For night-by-night shelter, record each meaningful contact with a separate key; do not assume a bed-night automatically supplies a contact or the client's answer. Two distinct contact keys create two rows even if the date and answers are identical. Same-key retries verify the original recordset ID and never submit again.
45
+
46
+ The recorder snapshots all existing CLS rows, journals the attempt before submitting one new `recordset_id: 0`, then requires exactly one additional matching row and preservation of earlier rows. It does not adopt a preexisting row merely because its answers match. Lost responses reconcile through the journal and fresh reads. Missing, changed, multiple, or ambiguous new rows leave the attempt unresolved, blocking resubmission and later contact keys for that client. Restore the same journal and reconcile the original operation; never clear it or generate a replacement key to force a retry.
47
+
48
+ All callers must share the durable store. The journal does not lock external writers or provide server-side idempotency. Coordinate other applications and manual edits during a save; one concurrent identical external row cannot be distinguished from the requested save by answer content alone. Journals contain contact snapshots and must remain private. Do not rebuild lost journals by guessing from date and living situation.
49
+
50
+ ## Verification limits
51
+
52
+ The helper uses generic assessment reads for saved rows, not the complete-enrollment projection's scalar current-living-situation property. A scalar cannot represent contact history. It does not create an interim review, end an enrollment, overwrite a prior contact, or schedule contacts automatically.
53
+
54
+ The regression suite covers multiple identical contacts, later contacts, optional program verification, metadata-required fields, date and identity drift, preservation failures, lost responses, and no replay. On October 24, 2026, a test in a sandbox tenant created three rows for an existing synthetic client: two contacts with identical dates and answers, plus a later contact with recording-program verification. Fresh assessment reads confirmed all three recordset IDs, program-entry starts, contact information dates, null ends, and selected lookup values. Same-key retries retained those IDs without additional writes. No extra answers were required by that instrument.
55
+
56
+ That live test used a coordinated-entry instrument with an existing transitional-housing enrollment. It verifies the shared CLS storage and retry path, not every street outreach or night-by-night shelter form, conditional rule, role, or tenant deployment. If the counted enrollment read hits the documented grouped-enrollment server error, the recorder stops; it does not replace the entry date with the contact date or infer an empty enrollment list.
@@ -0,0 +1,40 @@
1
+ # Date and answer contracts
2
+
3
+ [Documentation index](README.md) · [Assessment guide](assessments.md)
4
+
5
+ The SDK deals with several kinds of dates. Applying one correction to all of them corrupts data.
6
+
7
+ | Value | Input | Behavior |
8
+ | --- | --- | --- |
9
+ | Raw REST date field | String required by that operation | No general timezone conversion or business calendar validation |
10
+ | Workflow entry, interim, exit, row start/end | Timezone-qualified ISO instant, whole-second precision | Validates dates and compares instants; does not shift stored dates |
11
+ | Workflow `SVPPROFDOB` | `{value:'1984-02-29'}` | Strict calendar date through `birthDates`; timestamp input is rejected |
12
+ | Standalone DOB helper | `dateOfBirth:'1984-02-29'`, separate `effectiveDate` instant | Encodes DOB at `07:00:00Z` for the tested fixed UTC-7 REST conversion; checks browser compatibility |
13
+ | Mobile answer effective date | ISO instant when correction is configured | Optional measured minutes adjust only the outgoing AddAssessmentAnswer query |
14
+ | Verification snapshot enrollment date | ISO instant with offset | Inclusive comparison against the original row start/end instants |
15
+ | Goal case-note date | `YYYY-MM-DD` | Browser-local calendar date |
16
+ | Service dates and financial-assistance period | Explicit timestamps | Separate fields; the period is not inferred from the service dates |
17
+
18
+ The observed seven-hour mobile effective-date shift and DOB conversion are separate server behaviors. `mobileAssessmentDateCorrectionMinutes: 420` compensates for a measured subtraction in one mobile query. It does not fix DOB values or verification array filtering. Never pre-adjust a timestamp and also enable the correction. Revalidate measured offsets against saved effective dates after deployment/server changes.
19
+
20
+ The DOB helper verifies client API, assessment effective date, and fresh UI values. At a new effective timestamp it uses REST. Correcting the latest existing timestamp requires the matching entry assessment context because the tested REST path returns an entity-tracking error. The helper does not move effective dates to avoid that error. Older history correction and same-timestamp interim/exit correction are not supported.
21
+
22
+ The current mobile assessment projection may show a blank value for a UI-created DOB. That is not proof of missing data. The DOB helper requires agreement between client API and fresh UI before accepting the mobile row's effective date.
23
+
24
+ ## Verification intervals
25
+
26
+ `readEnrollmentVerifications` selects rows with `start <= enrollment <= end`. Null or empty end means open. It reads the current generic assessment once and retains original IDs and date strings. It does not round an end date to the end of a day or reconstruct historical edits. Invalid or ambiguous dates cause an error rather than an empty result.
27
+
28
+ The user workflow normally starts each applicable verification on or before entry. Unchanged records remain open. When a type changes, the application supplies the old row's end and a new row's start, normally on the next calendar day. The SDK does not select the program's day-boundary policy. An exit does not automatically end all verification records.
29
+
30
+ ## Answer states and identity
31
+
32
+ `No`, client refusal, client uncertainty, data not collected, and an omitted value are different states. Use the discovered option or an explicit encoder state mapping. Never replace an unknown value with `No` to satisfy coverage.
33
+
34
+ Use option codes from the current question's picklist, not cached labels from another provider. `HUD_RACEETHNICITY` needs the combination option even for a single category. The encoder handles this standard field; raw numeric IDs can display a label in REST while remaining blank in the UI.
35
+
36
+ Question metadata supplies types and mandatory flags, but does not describe every conditional rule. The application decides applicability and child-specific questions. [SSVF rules](../SSVF.md) are an explicitly tested sandbox form snapshot, not a general branching engine.
37
+
38
+ ## Current Living Situation dates
39
+
40
+ Every contact row starts at the selected client's program entry. Its information date is the contact date, and its end may remain null or empty. Later contacts retain earlier rows without closing or replacing them. This differs from changing income/benefit verification intervals. See [contact recording](current-living-situation.md) for date input, optional Verified By, and retry contracts.
@@ -0,0 +1,81 @@
1
+ # Develop and release the SDK
2
+
3
+ [Documentation index](README.md) · [Agent guide](../AGENTS.md)
4
+
5
+ Run commands in `packages/wellsky-sdk`. Node 22 or newer is required. Local tests use synthetic data and do not authenticate to HMIS. The repository contains the SDK and its supporting documentation and tests.
6
+
7
+ ## Fake client and validation
8
+
9
+ ```ts
10
+ import { createFakeWellSkyClient } from '@choiceapps/communityservices-sdk/testing';
11
+ const fake = createFakeWellSkyClient();
12
+ fake.fake.set('GET /clients/{clientId}', {
13
+ kind: 'reply', body: [{ clientId }],
14
+ });
15
+ ```
16
+
17
+ The fake implements the same V3 client and runs through the real transport and validation. Script replies, network errors, custom handlers, and commit-then-error behavior. No request falls through to a real network. Use synthetic data only; the fake intentionally exposes its scripted calls/state to tests.
18
+
19
+ ```sh
20
+ npm ci
21
+ npm run generate
22
+ npm run generate:check
23
+ npm run docs:check
24
+ npm test
25
+ npm run package:check
26
+ ```
27
+
28
+ `npm run generate` rewrites generated files from the pinned contracts. Use `generate:check` for a read-only consistency check. Run `npm run specs:check` separately when checking upstream drift: it contacts the public WellSky Swagger endpoints, compares them to the pinned snapshots, and does not update files or authenticate to HMIS. A mismatch requires review before regeneration.
29
+
30
+ ## Include browser fixtures
31
+
32
+ Without `WELLSKY_TEST_CHROME`, the suite skips tests that require a real browser. Install a compatible Chrome or Chromium executable, then set its absolute path when running the package check. For example, on macOS:
33
+
34
+ ```sh
35
+ WELLSKY_TEST_CHROME='/Applications/Google Chrome.app/Contents/MacOS/Google Chrome' npm run package:check
36
+ ```
37
+
38
+ These browser tests use local synthetic forms and intercepted responses, not a live HMIS login. Check the test summary for skipped tests before describing a run as complete browser coverage. GitLab's `verify:browser` job installs Chromium and its system dependencies, sets `WELLSKY_TEST_CHROME`, and runs the package check. The separate Node 22 and 24 jobs still check the package without a browser.
39
+
40
+ The local suite covers every versioned HTTP operation, metadata, tenant/auth controls, concurrency, bulk execution, cancellation, verification, and end-dating. Package checks install the tarball and its optional Playwright Core peer in a clean consumer, check shipped documentation links and runtime-export coverage, compare installed examples to source, compile every example against installed declarations, and execute offline example checks with network access blocked. Installed client-creation, attachment-retry, and session-authentication regressions run separately. ESM and CommonJS REST declarations and imports are also checked, along with ESM browser consumer types. Any live verification requires an explicitly authorized tenant and operation. Preserve sanitized conclusions, reproduction dates, and limitations in the relevant SDK guide or vendor report. Keep raw captures, credentials, and client data outside the repository and package; do not replay historical fixture setup.
41
+
42
+ ## GitLab CI releases
43
+
44
+ The root `.gitlab-ci.yml` runs verification on Node 22 and 24 and a separate Chromium fixture job on Node 22. Publishing requires all three jobs and uses the tested Node 22 tarball, GitLab's `CI_JOB_TOKEN`, and the current project's npm registry. The publish job checks version agreement, verifies registry integrity, and installs the published package and optional browser peer in a clean directory. It runs the shared installed-consumer checks there, including every shipped example, public entry points, type checks, and retry regressions.
45
+
46
+ Release either by pushing a matching version tag (for example, `v0.1.0`), or by running a pipeline on the default branch with the matching `RELEASE_VERSION` (for example, `0.1.0`). Change `package.json` and its lockfile first for future versions. Never reuse a published version. No long-lived publishing token is required.
47
+
48
+ These are release procedures, not evidence that the current working tree has been published. A local `package:check` creates a tarball but does not upload it. Verify the actual release pipeline, registry version, and installed artifact before claiming registry availability.
49
+
50
+ See [CHANGELOG.md](../CHANGELOG.md) for compatibility changes. This package is an independent integration and has no WellSky endorsement.
51
+
52
+ ## GitHub mirror and public npm releases
53
+
54
+ GitLab is the source of truth. Its push mirror copies branches and tags to `nmbgeek/communityservices-sdk` on GitHub. Commit changes, including `.github/workflows/publish.yml`, in GitLab. Do not edit the mirror or push independent commits there.
55
+
56
+ The GitHub workflow checks Node 22, Node 24, and Chromium using synthetic fixtures. A matching `v<version>` tag publishes the tested Chromium-job tarball as public `@choiceapps/communityservices-sdk`. Main pushes only verify. To recover an interrupted release, run `publish.yml` manually against the existing version tag. An identical existing artifact is verified without republishing; a different artifact under the same version fails. npm versions cannot be replaced.
57
+
58
+ Configure the npm package's trusted publisher for GitHub owner `nmbgeek`, repository `communityservices-sdk`, workflow filename `publish.yml`, with publishing allowed and no environment name. The workflow runs on GitHub-hosted runners, grants `id-token: write` only to the publish job, and uses npm 11.19.1. It needs no `NPM_TOKEN` or `NODE_AUTH_TOKEN` secret. The package's repository URL points to this public mirror so npm can attach provenance. GitLab publication remains a separate job using `CI_JOB_TOKEN`.
59
+
60
+ An npm owner with 2FA must complete the initial package setup before OIDC publishing can work. For an existing package, configure it in npm settings or with an authenticated npm CLI:
61
+
62
+ ```sh
63
+ npm trust github @choiceapps/communityservices-sdk --repo nmbgeek/communityservices-sdk --file publish.yml --allow-publish --yes
64
+ ```
65
+
66
+ See [npm trusted publishing](https://docs.npmjs.com/trusted-publishers/) for initial setup requirements. This workflow does not bootstrap credentials or create a placeholder release. Verify the workflow result, registry integrity, provenance, and installed-consumer checks before calling a release available.
67
+
68
+ ## License
69
+
70
+ The SDK is Apache-2.0, copyright 2026 Choice Technology Solutions, LLC. Keep the repository and package copies of `LICENSE` and `NOTICE` identical. Package verification checks that both files ship unchanged.
71
+
72
+ ## Tenant parity regression checks
73
+
74
+ The synthetic workflow and browser fixtures accept `WELLSKY_TEST_TENANT` to select the fixture tenant. Run the same package checks with two distinct tenant values:
75
+
76
+ ```sh
77
+ WELLSKY_TEST_TENANT=tenant-a npm run package:check
78
+ WELLSKY_TEST_TENANT=tenant-b npm run package:check
79
+ ```
80
+
81
+ Set `WELLSKY_TEST_CHROME` to the local Chrome executable to include the browser fixtures. These checks intercept browser traffic and use synthetic records; selecting another fixture tenant here does not access the live tenant. The suite also checks shared-journal isolation, neutral public exports, cross-tenant rejection, and explicit tenant types for login and custom need adapters.