@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.
- package/AGENTS.md +43 -0
- package/API.md +86 -0
- package/CHANGELOG.md +12 -0
- package/LICENSE +201 -0
- package/NOTICE +11 -0
- package/README.md +100 -0
- package/SSVF.md +131 -0
- package/browser/attachment-conflict.mjs +6 -0
- package/browser/case-plans-ui.mjs +23 -0
- package/browser/case-plans.d.mts +20 -0
- package/browser/case-plans.mjs +89 -0
- package/browser/client-creation.d.mts +16 -0
- package/browser/client-creation.mjs +60 -0
- package/browser/client-profile-ui.mjs +59 -0
- package/browser/client-profile.d.mts +24 -0
- package/browser/client-profile.mjs +94 -0
- package/browser/client-workflows.mjs +135 -0
- package/browser/connection.d.mts +4 -0
- package/browser/connection.mjs +41 -0
- package/browser/current-living-situation.d.mts +44 -0
- package/browser/current-living-situation.mjs +161 -0
- package/browser/date-of-birth-ui.mjs +68 -0
- package/browser/date-of-birth.d.mts +28 -0
- package/browser/date-of-birth.mjs +112 -0
- package/browser/enrollment-workflow.d.mts +174 -0
- package/browser/enrollment-workflow.mjs +518 -0
- package/browser/exit-details.mjs +21 -0
- package/browser/file-store.mjs +32 -0
- package/browser/gwt-mapped.mjs +46 -0
- package/browser/household-exit.d.mts +34 -0
- package/browser/household-exit.mjs +102 -0
- package/browser/households-ui.mjs +20 -0
- package/browser/households.d.mts +22 -0
- package/browser/households.mjs +115 -0
- package/browser/index.d.mts +78 -0
- package/browser/index.mjs +46 -0
- package/browser/member-entry-date.d.mts +30 -0
- package/browser/member-entry-date.mjs +71 -0
- package/browser/need-recording.d.mts +37 -0
- package/browser/need-recording.mjs +68 -0
- package/browser/need-ui.mjs +138 -0
- package/browser/page-lock.mjs +11 -0
- package/browser/password-recovery.mjs +49 -0
- package/browser/playwright-session.mjs +196 -0
- package/browser/program-discovery.d.mts +22 -0
- package/browser/program-discovery.mjs +80 -0
- package/browser/recovery-diagnostics.mjs +9 -0
- package/browser/service-recording.d.mts +45 -0
- package/browser/service-recording.mjs +148 -0
- package/browser/service-ui.mjs +160 -0
- package/browser/session-authentication.mjs +40 -0
- package/browser/shelter-inventory.d.mts +103 -0
- package/browser/shelter-inventory.mjs +273 -0
- package/browser/shelter-ui.mjs +382 -0
- package/browser/shelters.d.mts +139 -0
- package/browser/shelters.mjs +425 -0
- package/browser/ssvf-rules.d.mts +21 -0
- package/browser/ssvf-rules.mjs +145 -0
- package/browser/ui-login.mjs +135 -0
- package/dist/cjs/bulk.d.ts +33 -0
- package/dist/cjs/bulk.d.ts.map +1 -0
- package/dist/cjs/bulk.js +42 -0
- package/dist/cjs/bulk.js.map +1 -0
- package/dist/cjs/control.d.ts +35 -0
- package/dist/cjs/control.d.ts.map +1 -0
- package/dist/cjs/control.js +141 -0
- package/dist/cjs/control.js.map +1 -0
- package/dist/cjs/effective-date.d.ts +3 -0
- package/dist/cjs/effective-date.d.ts.map +1 -0
- package/dist/cjs/effective-date.js +27 -0
- package/dist/cjs/effective-date.js.map +1 -0
- package/dist/cjs/encoding.d.ts +32 -0
- package/dist/cjs/encoding.d.ts.map +1 -0
- package/dist/cjs/encoding.js +121 -0
- package/dist/cjs/encoding.js.map +1 -0
- package/dist/cjs/enrollment-verifications.d.ts +39 -0
- package/dist/cjs/enrollment-verifications.d.ts.map +1 -0
- package/dist/cjs/enrollment-verifications.js +96 -0
- package/dist/cjs/enrollment-verifications.js.map +1 -0
- package/dist/cjs/errors.d.ts +33 -0
- package/dist/cjs/errors.d.ts.map +1 -0
- package/dist/cjs/errors.js +45 -0
- package/dist/cjs/errors.js.map +1 -0
- package/dist/cjs/generated/v2-models.d.ts +1482 -0
- package/dist/cjs/generated/v2-models.d.ts.map +1 -0
- package/dist/cjs/generated/v2-models.js +3 -0
- package/dist/cjs/generated/v2-models.js.map +1 -0
- package/dist/cjs/generated/v2.d.ts +1149 -0
- package/dist/cjs/generated/v2.d.ts.map +1 -0
- package/dist/cjs/generated/v2.js +252 -0
- package/dist/cjs/generated/v2.js.map +1 -0
- package/dist/cjs/generated/v3-models.d.ts +1420 -0
- package/dist/cjs/generated/v3-models.d.ts.map +1 -0
- package/dist/cjs/generated/v3-models.js +3 -0
- package/dist/cjs/generated/v3-models.js.map +1 -0
- package/dist/cjs/generated/v3.d.ts +1149 -0
- package/dist/cjs/generated/v3.d.ts.map +1 -0
- package/dist/cjs/generated/v3.js +252 -0
- package/dist/cjs/generated/v3.js.map +1 -0
- package/dist/cjs/gwt-enrollments.d.ts +131 -0
- package/dist/cjs/gwt-enrollments.d.ts.map +1 -0
- package/dist/cjs/gwt-enrollments.js +280 -0
- package/dist/cjs/gwt-enrollments.js.map +1 -0
- package/dist/cjs/index.d.ts +3 -0
- package/dist/cjs/index.d.ts.map +1 -0
- package/dist/cjs/index.js +19 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/metadata.d.ts +100 -0
- package/dist/cjs/metadata.d.ts.map +1 -0
- package/dist/cjs/metadata.js +165 -0
- package/dist/cjs/metadata.js.map +1 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/program-discovery.d.ts +28 -0
- package/dist/cjs/program-discovery.d.ts.map +1 -0
- package/dist/cjs/program-discovery.js +58 -0
- package/dist/cjs/program-discovery.js.map +1 -0
- package/dist/cjs/race.d.ts +4 -0
- package/dist/cjs/race.d.ts.map +1 -0
- package/dist/cjs/race.js +40 -0
- package/dist/cjs/race.js.map +1 -0
- package/dist/cjs/runtime.d.ts +108 -0
- package/dist/cjs/runtime.d.ts.map +1 -0
- package/dist/cjs/runtime.js +38 -0
- package/dist/cjs/runtime.js.map +1 -0
- package/dist/cjs/subassessments.d.ts +29 -0
- package/dist/cjs/subassessments.d.ts.map +1 -0
- package/dist/cjs/subassessments.js +107 -0
- package/dist/cjs/subassessments.js.map +1 -0
- package/dist/cjs/testing.d.ts +45 -0
- package/dist/cjs/testing.d.ts.map +1 -0
- package/dist/cjs/testing.js +66 -0
- package/dist/cjs/testing.js.map +1 -0
- package/dist/cjs/transport.d.ts +19 -0
- package/dist/cjs/transport.d.ts.map +1 -0
- package/dist/cjs/transport.js +373 -0
- package/dist/cjs/transport.js.map +1 -0
- package/dist/cjs/v2.d.ts +13 -0
- package/dist/cjs/v2.d.ts.map +1 -0
- package/dist/cjs/v2.js +29 -0
- package/dist/cjs/v2.js.map +1 -0
- package/dist/cjs/v3.d.ts +13 -0
- package/dist/cjs/v3.d.ts.map +1 -0
- package/dist/cjs/v3.js +29 -0
- package/dist/cjs/v3.js.map +1 -0
- package/dist/cjs/validation.d.ts +33 -0
- package/dist/cjs/validation.d.ts.map +1 -0
- package/dist/cjs/validation.js +77 -0
- package/dist/cjs/validation.js.map +1 -0
- package/dist/cjs/verification.d.ts +82 -0
- package/dist/cjs/verification.d.ts.map +1 -0
- package/dist/cjs/verification.js +122 -0
- package/dist/cjs/verification.js.map +1 -0
- package/dist/esm/bulk.d.ts +33 -0
- package/dist/esm/bulk.d.ts.map +1 -0
- package/dist/esm/bulk.js +39 -0
- package/dist/esm/bulk.js.map +1 -0
- package/dist/esm/control.d.ts +35 -0
- package/dist/esm/control.d.ts.map +1 -0
- package/dist/esm/control.js +132 -0
- package/dist/esm/control.js.map +1 -0
- package/dist/esm/effective-date.d.ts +3 -0
- package/dist/esm/effective-date.d.ts.map +1 -0
- package/dist/esm/effective-date.js +24 -0
- package/dist/esm/effective-date.js.map +1 -0
- package/dist/esm/encoding.d.ts +32 -0
- package/dist/esm/encoding.d.ts.map +1 -0
- package/dist/esm/encoding.js +117 -0
- package/dist/esm/encoding.js.map +1 -0
- package/dist/esm/enrollment-verifications.d.ts +39 -0
- package/dist/esm/enrollment-verifications.d.ts.map +1 -0
- package/dist/esm/enrollment-verifications.js +93 -0
- package/dist/esm/enrollment-verifications.js.map +1 -0
- package/dist/esm/errors.d.ts +33 -0
- package/dist/esm/errors.d.ts.map +1 -0
- package/dist/esm/errors.js +33 -0
- package/dist/esm/errors.js.map +1 -0
- package/dist/esm/generated/v2-models.d.ts +1482 -0
- package/dist/esm/generated/v2-models.d.ts.map +1 -0
- package/dist/esm/generated/v2-models.js +2 -0
- package/dist/esm/generated/v2-models.js.map +1 -0
- package/dist/esm/generated/v2.d.ts +1149 -0
- package/dist/esm/generated/v2.d.ts.map +1 -0
- package/dist/esm/generated/v2.js +247 -0
- package/dist/esm/generated/v2.js.map +1 -0
- package/dist/esm/generated/v3-models.d.ts +1420 -0
- package/dist/esm/generated/v3-models.d.ts.map +1 -0
- package/dist/esm/generated/v3-models.js +2 -0
- package/dist/esm/generated/v3-models.js.map +1 -0
- package/dist/esm/generated/v3.d.ts +1149 -0
- package/dist/esm/generated/v3.d.ts.map +1 -0
- package/dist/esm/generated/v3.js +247 -0
- package/dist/esm/generated/v3.js.map +1 -0
- package/dist/esm/gwt-enrollments.d.ts +131 -0
- package/dist/esm/gwt-enrollments.d.ts.map +1 -0
- package/dist/esm/gwt-enrollments.js +274 -0
- package/dist/esm/gwt-enrollments.js.map +1 -0
- package/dist/esm/index.d.ts +3 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +3 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/metadata.d.ts +100 -0
- package/dist/esm/metadata.d.ts.map +1 -0
- package/dist/esm/metadata.js +157 -0
- package/dist/esm/metadata.js.map +1 -0
- package/dist/esm/program-discovery.d.ts +28 -0
- package/dist/esm/program-discovery.d.ts.map +1 -0
- package/dist/esm/program-discovery.js +55 -0
- package/dist/esm/program-discovery.js.map +1 -0
- package/dist/esm/race.d.ts +4 -0
- package/dist/esm/race.d.ts.map +1 -0
- package/dist/esm/race.js +37 -0
- package/dist/esm/race.js.map +1 -0
- package/dist/esm/runtime.d.ts +108 -0
- package/dist/esm/runtime.d.ts.map +1 -0
- package/dist/esm/runtime.js +33 -0
- package/dist/esm/runtime.js.map +1 -0
- package/dist/esm/subassessments.d.ts +29 -0
- package/dist/esm/subassessments.d.ts.map +1 -0
- package/dist/esm/subassessments.js +103 -0
- package/dist/esm/subassessments.js.map +1 -0
- package/dist/esm/testing.d.ts +45 -0
- package/dist/esm/testing.d.ts.map +1 -0
- package/dist/esm/testing.js +60 -0
- package/dist/esm/testing.js.map +1 -0
- package/dist/esm/transport.d.ts +19 -0
- package/dist/esm/transport.d.ts.map +1 -0
- package/dist/esm/transport.js +369 -0
- package/dist/esm/transport.js.map +1 -0
- package/dist/esm/v2.d.ts +13 -0
- package/dist/esm/v2.d.ts.map +1 -0
- package/dist/esm/v2.js +13 -0
- package/dist/esm/v2.js.map +1 -0
- package/dist/esm/v3.d.ts +13 -0
- package/dist/esm/v3.d.ts.map +1 -0
- package/dist/esm/v3.js +13 -0
- package/dist/esm/v3.js.map +1 -0
- package/dist/esm/validation.d.ts +33 -0
- package/dist/esm/validation.d.ts.map +1 -0
- package/dist/esm/validation.js +72 -0
- package/dist/esm/validation.js.map +1 -0
- package/dist/esm/verification.d.ts +82 -0
- package/dist/esm/verification.d.ts.map +1 -0
- package/dist/esm/verification.js +117 -0
- package/dist/esm/verification.js.map +1 -0
- package/docs/README.md +56 -0
- package/docs/assessments.md +103 -0
- package/docs/browser-session.md +129 -0
- package/docs/capabilities.md +75 -0
- package/docs/configuration.md +81 -0
- package/docs/current-living-situation.md +56 -0
- package/docs/dates-and-answers.md +40 -0
- package/docs/development.md +81 -0
- package/docs/durable-records.md +156 -0
- package/docs/enrollments.md +217 -0
- package/docs/files-and-notes.md +39 -0
- package/docs/getting-started.md +56 -0
- package/docs/independent-needs.md +77 -0
- package/docs/integration-methods.md +60 -0
- package/docs/profiles.md +68 -0
- package/docs/reference.md +64 -0
- package/docs/rest-client.md +97 -0
- package/docs/services.md +66 -0
- package/docs/shelter-inventory.md +64 -0
- package/docs/shelters.md +75 -0
- package/docs/troubleshooting.md +61 -0
- package/docs/using-the-sdk.md +203 -0
- package/docs/vendor-issues/README.md +46 -0
- package/docs/vendor-issues/api-coverage.md +27 -0
- package/docs/vendor-issues/attachment-authentication.md +40 -0
- package/docs/vendor-issues/date-semantics.md +51 -0
- package/docs/vendor-issues/empty-service-list-404.md +51 -0
- package/docs/vendor-issues/enrollment-complete-target-error.md +178 -0
- package/docs/vendor-issues/enrollment-effective-date-column.md +112 -0
- package/docs/vendor-issues/household-head-validation.md +26 -0
- package/docs/vendor-issues/write-contracts.md +51 -0
- package/examples/README.md +41 -0
- package/examples/current-living-situation.mts +27 -0
- package/examples/durable-records.mts +78 -0
- package/examples/enrollment-workflow.mts +46 -0
- package/examples/gwt-enrollment-session.ts +17 -0
- package/examples/gwt-file-store.mjs +2 -0
- package/examples/household-enrollment-workflow.mts +43 -0
- package/examples/household-exit-workflow.mts +35 -0
- package/examples/household-lifecycle-workflow.mts +34 -0
- package/examples/independent-needs.mts +30 -0
- package/examples/program-discovery.ts +19 -0
- package/examples/quickstart.mjs +13 -0
- package/examples/shelter-inventory.mts +30 -0
- package/examples/shelters.mts +33 -0
- package/examples/usage.ts +23 -0
- package/examples/verification-snapshot.ts +9 -0
- package/llms.txt +26 -0
- package/package.json +103 -0
- package/specs/provenance.json +17 -0
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# Why the SDK uses REST, GWT-RPC, and browser forms
|
|
2
|
+
|
|
3
|
+
[Documentation index](README.md) · [Capabilities](capabilities.md) · [WellSky feedback](vendor-issues/README.md)
|
|
4
|
+
|
|
5
|
+
The SDK uses the public Client API where its contract covers the operation and stored results can be verified. Some workflows need fields, relationships, or reads that the public API does not expose reliably. Those workflows combine REST with the authenticated web application's forms and GWT-RPC calls.
|
|
6
|
+
|
|
7
|
+
These choices apply to every configured tenant. The evidence summarized here was reproduced in a sandbox tenant on October 22–24, 2026. A sandbox reproduction establishes an observation, not a separate SDK mode or a guarantee about every deployment.
|
|
8
|
+
|
|
9
|
+
## What each method means
|
|
10
|
+
|
|
11
|
+
| Method | What it does | Main tradeoff |
|
|
12
|
+
| --- | --- | --- |
|
|
13
|
+
| Public REST Client API | Uses the pinned V2 or V3 operations with REST authentication and typed request models | A documented operation can still have incomplete fields, surprising update semantics, or server errors |
|
|
14
|
+
| GWT-RPC | Calls the web application's internal services using its authenticated browser session and current serialization context | Depends on application models, module headers, and serialization policy that can change with the UI build |
|
|
15
|
+
| Browser forms | Opens current forms, reads controls, selects intended values, and submits through the application | Preserves the application's current serialization and form behavior, but depends on layout and asynchronous UI updates |
|
|
16
|
+
| Browser multipart endpoints | Uploads or downloads files in the web application's authenticated context | Uses a different authentication and entity contract from REST attachments |
|
|
17
|
+
| Local workflow orchestration | Coordinates discovery, durable journals, locks, retries, and verification across those methods | Cannot turn separate server requests into one transaction or lock out unrelated writers |
|
|
18
|
+
|
|
19
|
+
GWT-RPC is also a server API. Browser forms and GWT-RPC are not mutually exclusive: submitting a form often sends a GWT request. A GWT POST can also be a read. HTTP method alone does not identify a business mutation.
|
|
20
|
+
|
|
21
|
+
## Decisions by workflow
|
|
22
|
+
|
|
23
|
+
| Workflow | Chosen method | Reason and alternative rejected |
|
|
24
|
+
| --- | --- | --- |
|
|
25
|
+
| Client creation, ordinary reads, and supported profile fields | REST, with durable orchestration for writes | These operations have usable REST contracts. The profile helper preserves current fields because an omitted SSN was cleared by a raw update. Replacing all REST operations with browser actions would add UI dependencies without solving that contract issue. See [profiles](profiles.md). |
|
|
26
|
+
| Program and assessment discovery | REST provider catalog and client-scoped question definitions; admin UI and counted GWT reads for assignments/defaults | The provider list does not identify the entry, interim, and exit instrument assigned to each program. Guessing an assessment from its name can select the wrong form. See [assessments](assessments.md) and [enrollments](enrollments.md). |
|
|
27
|
+
| Household membership, linked enrollments, interims, and exits | REST for supported records and answers; current UI/GWT for the grouped business operation | Household membership is distinct from linked enrollment membership. Separate ungrouped enrollments do not reproduce a household operation. Grouped REST reads also fail in the reproduced cases, so verification uses fresh UI/GWT evidence. See [enrollments](enrollments.md). |
|
|
28
|
+
| Verification rows at entry | Generic assessment REST reads plus explicit interval filtering | Complete-enrollment projections omitted valid rows. The alternative assessment read includes future-start rows, so returning its raw list would also be wrong. `readEnrollmentVerifications` compares original row intervals with the enrollment instant. See [dates and answers](dates-and-answers.md). |
|
|
29
|
+
| DOB | REST for a new effective timestamp; matching entry-assessment UI for the supported same-timestamp correction | The tested REST update failed at an existing effective timestamp. Moving the timestamp would change history rather than correct the answer. DOB encoding is separate from mobile answer effective-date correction. See [profiles](profiles.md). |
|
|
30
|
+
| Sex in Client Record | Current UI form, which submits GWT | The legacy REST Gender field did not populate Sex. Treating the fields as aliases would silently update the wrong information. See [profiles](profiles.md). |
|
|
31
|
+
| Services and SSVF | REST creation followed by current UI completion and REST/GWT verification | REST covers ordinary service fields and several SSVF choices. It does not cover all assistance-period, need-outcome, or funding-allocation fields used by the workflow. A successful REST create alone is not a completed service workflow. See [services](services.md). |
|
|
32
|
+
| Independent needs and service-type discovery | Add Need form and counted GWT reads | The pinned public API has need reads but no independent-need create operation or full service-type catalog. Creating a service solely to obtain a need would create a different business record. See [independent needs](independent-needs.md). |
|
|
33
|
+
| Shelter stays and bed assignments | REST provider settings; UI check-in/checkout with fresh enrollment and stay reads | An ordinary shelter-coded service does not assign a bed. Aggregate provider inventory is not a list of currently assignable beds. See [shelters](shelters.md). |
|
|
34
|
+
| Shelter vacancies | Read-only browser inventory views | The visible unit lists, bed states, holds, and counted availability provide the required detail. The reader reports unknown availability when those views are incomplete. See [inventory](shelter-inventory.md). |
|
|
35
|
+
| Attachments | Browser multipart upload/download, GWT metadata reads, and downloaded-byte verification | REST attachment authentication failed independently of SDK serialization. Browser uploads succeeded for the supported locations. This does not establish anonymous access or sharing permissions. See [files and notes](files-and-notes.md). |
|
|
36
|
+
| Client Notes, goals, and action steps | REST writes, with counted UI/GWT reads where an initial empty collection needs confirmation | An initial REST 404 cannot establish absence. The helper verifies ownership and supported saved fields before accepting completion. Goal case notes are separate records and use the browser workflow because the REST goal response did not expose them. See [durable records](durable-records.md). |
|
|
37
|
+
|
|
38
|
+
## Why fresh GWT context matters
|
|
39
|
+
|
|
40
|
+
The early enrollment experiment intercepted a request generated by the current form, prevented its original transmission, and submitted it once in the same session. It established that fresh UI-generated requests could perform the member and interim operations. It did not establish a portable request template or safe repetition of a successful mutation.
|
|
41
|
+
|
|
42
|
+
GWT payloads contain serialized models, object references, type signatures, and string-table indices as well as record IDs. Replacing numbers in a captured body can change unrelated values or retain stale state. The SDK therefore uses fresh models and current application serialization context rather than storing payloads for later replay.
|
|
43
|
+
|
|
44
|
+
For enrollment helpers that prepare an intercepted request, the original UI submission is aborted before the journaled send. Other adapters submit the current form directly. Both paths need readback of identities, dates, participants, and saved values. An HTTP 200 can contain a GWT exception response; transport success alone is insufficient. See [the session adapter contract](browser-session.md).
|
|
45
|
+
|
|
46
|
+
The browser dependency has a cost: UI layouts and GWT serialization can change independently of the public API. Helpers reject unsupported or ambiguous states instead of guessing a new control or payload. Browser credentials also remain separate from REST credentials.
|
|
47
|
+
|
|
48
|
+
## Why a failed write does not trigger a different method
|
|
49
|
+
|
|
50
|
+
Method selection happens before a mutation. The SDK does not interpret a failed REST write as permission to repeat the same operation through GWT or a browser form. A service creation was observed to persist despite an HTTP 400, and controlled tests also reproduce lost acknowledgements and session loss after a commit.
|
|
51
|
+
|
|
52
|
+
Durable journals preserve the original intent and attempted stages. A retry first reconciles saved state with that original operation. Reauthentication restores access; it does not replay the callback. A changed key, discarded journal, or alternate transport would lose the evidence needed to avoid a duplicate.
|
|
53
|
+
|
|
54
|
+
Some workflows intentionally have several stages, such as REST service creation followed by UI completion. Those stages are not atomic. A failed later stage can leave the earlier record saved. See [recovery](troubleshooting.md) and [write-contract feedback](vendor-issues/write-contracts.md).
|
|
55
|
+
|
|
56
|
+
## What would allow a simpler implementation
|
|
57
|
+
|
|
58
|
+
A documented REST operation could replace a browser dependency when it exposes the required fields and relationships, preserves unrelated data, and supports authoritative readback. A migration must also preserve existing source keys and journal reconciliation. Merely adding a route with a similar name is insufficient.
|
|
59
|
+
|
|
60
|
+
[WellSky feedback](vendor-issues/README.md) separates reproduced failures from contract questions and missing-capability requests. Existing SDK guides remain the integration contract; historical experiments are evidence for these choices, not procedures to replay.
|
package/docs/profiles.md
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Update Client Profile fields and birth dates
|
|
2
|
+
|
|
3
|
+
[Documentation index](README.md) · [Agent guide](../AGENTS.md)
|
|
4
|
+
|
|
5
|
+
These helpers require a REST client and dedicated authenticated page for the same tenant, plus a durable mutation store. The snippets assume those dependencies and application-owned client IDs.
|
|
6
|
+
|
|
7
|
+
## Client Record fields and Sex
|
|
8
|
+
|
|
9
|
+
The tenant-configured `createClientProfile` helper updates Client Profile > Client Record. REST saves names, suffix, alias, SSN, name/SSN data quality and veteran status. The separate Sex field is saved through a fresh Client Record UI form, using the application's current GWT serializer. REST legacy `svpprofgender` does not update this Sex field.
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
import {createClientProfile, createClientProfileUi}
|
|
13
|
+
from '@choiceapps/communityservices-sdk/browser';
|
|
14
|
+
|
|
15
|
+
const profile = createClientProfile({
|
|
16
|
+
api: client, store, ui: createClientProfileUi({page: session.page}),
|
|
17
|
+
});
|
|
18
|
+
const current = await profile.read(clientId); // Values and live picklist choices.
|
|
19
|
+
await profile.update({
|
|
20
|
+
clientId, key: 'source-profile-change-42',
|
|
21
|
+
dateEffective: '2026-10-23T16:00:00Z',
|
|
22
|
+
changes: {middleName: 'Synthetic', sex: 'Female'},
|
|
23
|
+
});
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Use the same key, input and durable store for retries. The helper verifies fresh UI and REST values, preserves the entire Client Record in REST updates, and journals attempts before sending. This avoids an observed WellSky behavior where omitting SSN from a partial update clears it. A Sex-only change does not send a REST update. Picklist values and text lengths are validated before writes; blank strings explicitly clear optional text. Conflicting keys, unexpected field changes and unresolved saves block replay. Journal entries hold hashes rather than plaintext profile values, but returned values contain personal information and need application-level protection.
|
|
27
|
+
|
|
28
|
+
REST changes require a timezone-qualified `dateEffective`; Sex uses the current UI audit context. The two writes are not atomic. DOB, DOB quality, legacy Gender, race/ethnicity, phone and email are outside this helper's changes. Use the Birth dates helper below for the separately verified DOB conversion and existing-answer correction path. The Client Record helper does not apply any DOB correction. Existing assessment helpers remain separate from Client Record updates.
|
|
29
|
+
|
|
30
|
+
## Birth dates
|
|
31
|
+
|
|
32
|
+
Use `createDateOfBirth` for DOB changes in the configured tenant. Its input is a calendar date, not an instant. The tested REST path converts DOB values to fixed UTC−7, while the browser formats dates in its own timezone. This helper encodes a REST DOB at `07:00:00Z`, verifies browser compatibility before sending, then requires matching client API and fresh UI dates. It does not change shared date encoding or shift the effective timestamp.
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
import {createDateOfBirth, createDateOfBirthUi}
|
|
36
|
+
from '@choiceapps/communityservices-sdk/browser';
|
|
37
|
+
|
|
38
|
+
const birthDate = createDateOfBirth({
|
|
39
|
+
api: client, store, ui: createDateOfBirthUi({page: session.page}),
|
|
40
|
+
});
|
|
41
|
+
await birthDate.update({
|
|
42
|
+
clientId, key: 'source-dob-correction-42',
|
|
43
|
+
assessmentCode: 'TCHCECHOHUDCOCESGENTRYFORRRH2026',
|
|
44
|
+
dateOfBirth: '1984-02-29', effectiveDate: '2026-10-23T18:00:00Z',
|
|
45
|
+
entry: {enrollmentId, assessmentName: 'TCHC ECHO HUD CoC & ESG Entry for RRH 2026'},
|
|
46
|
+
});
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
A new effective timestamp uses REST and does not need `entry`. Correcting the latest existing timestamp uses the UI because REST returned an entity-tracking error on that path. The supplied enrollment must have that exact entry timestamp and expose the named assessment. Its existing mandatory answers must be complete; the helper does not populate other answers. Earlier historical corrections, interim/exit contexts and clearing DOB are outside this helper. Incompatible browser timezones fail before REST writes rather than silently changing the displayed calendar date.
|
|
50
|
+
|
|
51
|
+
Keep the same key, input and durable store for retries. Attempts are journaled before writes, uncertain results are reconciled without replay, and changed intent or later drift is rejected. Verification checks both current client API and fresh UI values because the mobile assessment projection can leave UI-created DOB values blank. Other Client Record fields, including SSN and DOB quality, and other exposed assessment answers are preserved. For UI corrections, the browser generates the current `addClientAnswersLinkedToEE` request; no captured payload is replayed.
|
|
52
|
+
|
|
53
|
+
## Unknown or declined birth date
|
|
54
|
+
|
|
55
|
+
When no DOB was supplied, record the actual quality response through assessment answers. Resolve `SVPPROFDOBTYPE` choices from current metadata: `DOBTYPE_DONTKNOW` means Client doesn't know; `DOBTYPE_REFUSED` means Client prefers not to answer. Do not call the calendar-date update helper or invent a date.
|
|
56
|
+
|
|
57
|
+
For the enrollment workflow, merge these field choices into the application's fully covered assessment plan:
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
const dobQualityFields = {
|
|
61
|
+
answers: {SVPPROFDOBTYPE: {option: {code: 'DOBTYPE_DONTKNOW'}}},
|
|
62
|
+
notApplicable: {SVPPROFDOB: "Client doesn't know their DOB; no date supplied"},
|
|
63
|
+
};
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Use the declined code and matching explanation when that is the client's response. `notApplicable` is the workflow's explanation for skipping a field; it neither writes a date nor clears an existing one. Do not combine it with a DOB answer or DOB carry-forward. At interim or exit, carry forward an existing quality answer if appropriate, while continuing to explain the absent date. Other assessment fields still require their own explicit coverage and head-of-household guards still apply.
|
|
67
|
+
|
|
68
|
+
The DOB reader returns `dateOfBirth: null` for a genuinely absent date. In sandbox, both quality-only choices let complete-enrollment reads succeed in V2 and V3 without a DOB. See the [controlled vendor report](vendor-issues/enrollment-complete-target-error.md). A failed complete read must never trigger a replacement enrollment or a fabricated date.
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# Public helper reference
|
|
2
|
+
|
|
3
|
+
[Documentation index](README.md) · [Generated HTTP methods](../API.md)
|
|
4
|
+
|
|
5
|
+
Only the package export paths below are supported import entry points. Declaration links are files for inspection, not extra runtime import paths. The documentation check compares this inventory with the built runtime exports.
|
|
6
|
+
|
|
7
|
+
## REST and shared helpers
|
|
8
|
+
|
|
9
|
+
Import from `@choiceapps/communityservices-sdk`, `@choiceapps/communityservices-sdk/v3`, or `@choiceapps/communityservices-sdk/v2`. The root selects V3 and also exports the testing helpers. Each version supplies its own generated HTTP models and fixed version header.
|
|
10
|
+
|
|
11
|
+
Runtime exports at the root: `AmbiguousCommitError`, `AssessmentMetadata`, `AuthError`, `CooldownError`, `DefinitionDriftError`, `FakeControl`, `FakeWellSkyClient`, `GwtEnrollmentConflictError`, `GwtEnrollmentHelpers`, `SubassessmentEditor`, `TenantMismatchError`, `ThrottledError`, `UnknownPicklistValueError`, `UpstreamError`, `ValidationError`, `WellSkyClient`, `WellSkyError`, `apiVersion`, `buildAssessmentDefinition`, `compareDefinition`, `createFakeWellSkyClient`, `createInMemoryGwtMutationStore`, `createWellSkyClient`, `encodeAnswers`, `encodeNewSubassessmentRow`, `flattenQuestions`, `mapBulk`, `operationRoutes`, `readEnrollmentVerifications`, `readOutcome`, `readProgramCatalog`, `resolvePicklistOption`, `semanticHash`, `specSha256`, `toItems`, `verifyWrite`.
|
|
12
|
+
|
|
13
|
+
| Contract | Declaration | Guide |
|
|
14
|
+
| --- | --- | --- |
|
|
15
|
+
| HTTP clients and generated models | [V3](../dist/esm/generated/v3.d.ts), [V2](../dist/esm/generated/v2.d.ts) | [REST](rest-client.md), [API table](../API.md) |
|
|
16
|
+
| Authentication, transport, responses and errors | [Runtime](../dist/esm/runtime.d.ts) | [Configuration](configuration.md), [recovery](troubleshooting.md) |
|
|
17
|
+
| Metadata and picklists | [Metadata](../dist/esm/metadata.d.ts) | [Assessments](assessments.md) |
|
|
18
|
+
| Answer encoding | [Encoding](../dist/esm/encoding.d.ts) | [Dates and answers](dates-and-answers.md) |
|
|
19
|
+
| Write verification | [Verification](../dist/esm/verification.d.ts) | [REST](rest-client.md) |
|
|
20
|
+
| Program catalog | [Program discovery](../dist/esm/program-discovery.d.ts) | [Enrollments](enrollments.md) |
|
|
21
|
+
| Enrollment verification intervals | [Snapshot](../dist/esm/enrollment-verifications.d.ts) | [Assessments](assessments.md) |
|
|
22
|
+
| Low-level GWT enrollment sessions | [GWT](../dist/esm/gwt-enrollments.d.ts) | [Browser sessions](browser-session.md) |
|
|
23
|
+
|
|
24
|
+
## Browser helpers
|
|
25
|
+
|
|
26
|
+
Import from `@choiceapps/communityservices-sdk/browser` in server-side ESM. These helpers use the tenant supplied by your application. Browser interfaces require `playwright-core`; some orchestration helpers use REST plus injected UI ports rather than directly launching a browser.
|
|
27
|
+
|
|
28
|
+
Runtime exports: `AttachmentConflictError`, `EnrollmentWorkflowError`, `LoginError`, `SessionInterruptedError`, `createFileGwtMutationStore`, `createPlaywrightEnrollmentHelpers`, `createPlaywrightGwtSession`, `createCasePlanUi`, `createCasePlans`, `createClientCreator`, `createClientProfile`, `createClientProfileUi`, `createClientWorkflows`, `createDateOfBirth`, `createDateOfBirthUi`, `createEnrollmentWorkflow`, `createHouseholdExitUi`, `createHouseholdUi`, `createHouseholds`, `createMemberEntryDatePort`, `createProgramDiscovery`, `createServiceRecorder`, `createServiceUi`, `getHouseholdOptions`, `getSsvfServiceRules`, `openEnrollmentSession`, `openUiSession`, `validateBirthDate`, `resolveUiConnection`.
|
|
29
|
+
|
|
30
|
+
| Task | Contract | Methods or behavior |
|
|
31
|
+
| --- | --- | --- |
|
|
32
|
+
| Login, session, file journal, uploads and goal case notes | [Browser index](../browser/index.d.mts) | Session lifecycle, `uploadAttachment`, `createCaseNote` with optional source key |
|
|
33
|
+
| Client creation | [Client creation](../browser/client-creation.d.mts) | `create`, `lookup`, `resolveCreated`; no automatic demographic matching |
|
|
34
|
+
| Household management | [Households](../browser/households.d.mts) | `create`, `read`, `addMembers`, `endMembers`, `updateRelationships`; head guards |
|
|
35
|
+
| Client Notes, goals and action steps | [Case plans](../browser/case-plans.d.mts) | Create/update methods, supported value fields and record IDs |
|
|
36
|
+
| Profile fields and Sex | [Client profile](../browser/client-profile.d.mts) | `read`, `update` |
|
|
37
|
+
| Calendar DOB | [Birth dates](../browser/date-of-birth.d.mts) | `read`, `validate`, `verify`, `update` |
|
|
38
|
+
| Provider assessment defaults | [Discovery](../browser/program-discovery.d.mts) | `listAssessments`, `getProviderAssessments` |
|
|
39
|
+
| Enrollment and household lifecycle | [Workflow](../browser/enrollment-workflow.d.mts) | Discovery, entry, member additions, interim, exit, journal status |
|
|
40
|
+
| Entry-date and exit UI ports | [Member entry](../browser/member-entry-date.d.mts), [exit](../browser/household-exit.d.mts) | Fresh reads and targeted completion |
|
|
41
|
+
| Services and recovery diagnostics | [Service recording](../browser/service-recording.d.mts) | `record`, `recordBulk`, Funding Sources, `RecoveryDiagnostic` |
|
|
42
|
+
| SSVF conditional rules | [SSVF types](../browser/ssvf-rules.d.mts) | [Field rules](../SSVF.md) and dated option snapshots |
|
|
43
|
+
|
|
44
|
+
Use the [durable-record guide](durable-records.md) and [shipped examples](../examples/README.md) for integration sequences. A type declaration describes accepted inputs; the [capabilities](capabilities.md) page states what has live evidence and what remains unsupported.
|
|
45
|
+
|
|
46
|
+
## Testing helpers
|
|
47
|
+
|
|
48
|
+
Import from `@choiceapps/communityservices-sdk/testing`. Runtime exports: `FakeControl`, `FakeWellSkyClient`, `createFakeWellSkyClient`. The [testing declarations](../dist/esm/testing.d.ts) define scripted replies, request capture, and commit-then-error behavior. The fake transport never falls through to the real network.
|
|
49
|
+
|
|
50
|
+
Independent need recording and service-type discovery: [guide](independent-needs.md), [declarations](../browser/need-recording.d.mts).
|
|
51
|
+
|
|
52
|
+
Exports: `createNeedRecorder`, `createNeedUi`, `NeedConflictError`.
|
|
53
|
+
|
|
54
|
+
### Shelter stays
|
|
55
|
+
|
|
56
|
+
`readShelterProgram` reads the REST provider tracking method. `createShelters` provides journaled `checkIn`, `checkout`, `getProgram`, and explicit `cancelUnsubmittedCheckIn` operations. Cancellation never deletes server records and refuses attempted shelter saves. `createShelterUi` supplies fresh UI forms and counted GWT enrollment reads. `ShelterConflictError` reports `SHELTER_CONFLICT` with a stable `reason`.
|
|
57
|
+
|
|
58
|
+
See [shelter contracts](../browser/shelters.d.mts), [shelter workflow](shelters.md), and [upstream issues](vendor-issues/README.md). Bed operations require named beds, an existing household, and a persistent shared store. The helper does not complete all assessment questions or schedule inactivity exits.
|
|
59
|
+
|
|
60
|
+
`createShelterInventory` supplies read-only `listUnitLists` and `getInventory` operations on a dedicated application page. Results include named beds, occupants, held and unknown states, overflow, vacancy counts, observation timestamps, and explicit completeness. `ShelterInventoryError` exposes `SHELTER_INVENTORY_UNAVAILABLE` and a sanitized `reason`. See the [inventory guide](shelter-inventory.md) and [inventory types](../browser/shelter-inventory.d.mts) for identity limits and future-checkout behavior.
|
|
61
|
+
|
|
62
|
+
### Current Living Situation
|
|
63
|
+
|
|
64
|
+
`createCurrentLivingSituationRecorder` and `CurrentLivingSituationConflictError`: [contact guide](current-living-situation.md), [declarations](../browser/current-living-situation.d.mts). `record` creates one journaled row per meaningful contact and verifies exact dates, answers, and recordset identity.
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# REST client configuration and requests
|
|
2
|
+
|
|
3
|
+
[Documentation index](README.md) · [Agent guide](../AGENTS.md)
|
|
4
|
+
|
|
5
|
+
## Configure a client
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import { createWellSkyClient, toItems } from '@choiceapps/communityservices-sdk';
|
|
9
|
+
|
|
10
|
+
const client = createWellSkyClient({
|
|
11
|
+
tenant: 'your-tenant',
|
|
12
|
+
baseUrl: 'https://csclientapi.wellsky.com/client-api-prod',
|
|
13
|
+
auth: { kind: 'credentials', username, password },
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
const people = toItems(await client.clients.get({ path: { clientId } }));
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Supply credentials from your server's secret store. The REST client reads no environment variables or credential files. The optional headless login reads its explicitly selected local credentials file. Construction rejects browser runtimes. `client.tenant` is read-only.
|
|
20
|
+
|
|
21
|
+
`tenant` selects the system. All configured tenants support the same methods. Callers select the tenant through connection configuration and credentials.
|
|
22
|
+
|
|
23
|
+
Authentication modes are `credentials`, a caller-managed static `token`, a `provider` with `getToken({ forceRefresh, signal })`, or `none` for explicitly unauthenticated operations. Automatic authentication uses `SetAuthKey` only. Matching clients share cached credentials and one in-flight mint; mints for different accounts are serialized. Opaque tokens without a usable JWT expiration are not reused between calls. Static-token lifecycle remains the caller's responsibility.
|
|
24
|
+
|
|
25
|
+
Detected mismatches in the `x-tenant` response header, supported tenant claims in JWTs, or top-level response tenant indicators fail closed. These checks detect mismatches; they do not cryptographically verify JWT signatures or establish a tenant when the server omits all indicators.
|
|
26
|
+
|
|
27
|
+
## Select an API version
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
import { WellSkyClient as V2 } from '@choiceapps/communityservices-sdk/v2';
|
|
31
|
+
import { WellSkyClient as V3 } from '@choiceapps/communityservices-sdk/v3';
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
The root export defaults to V3. Each entry point fixes its `x-version` header and exposes the matching models. Payloads are not translated between versions. All three entry points also support `require()`.
|
|
35
|
+
|
|
36
|
+
## Read and write resources
|
|
37
|
+
|
|
38
|
+
[API.md](../API.md) lists every named method. Requests use one object with typed `path`, `query`, and `body` properties. Parameter spelling matches Swagger, including `clientId`, `client_id`, and `$filter`.
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
await client.services.listForClient({ path: { clientId } });
|
|
42
|
+
await client.request('GET /clients/{clientId}', { path: { clientId } });
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Responses retain WellSky's object, array, OData `{ value: [...] }`, counted `{ count: number, values: [...] }`, or empty/null shape. Requests with `total_count: true` can return the counted format. `ApiResponse<T>` includes both collection types, and `toItems()` extracts their records. The count describes the whole result set, not the current page. Both envelopes validate each record against the endpoint's response model. Models with no documented response schema return `unknown`. HTTP errors, including 404, throw by default. A 404 is not globally interpreted as absence.
|
|
46
|
+
|
|
47
|
+
Attachment uploads accept a `Blob` or `File` in `body.fileData`. Files larger than 10 MB, defined as 10,000,000 bytes, throw `ValidationError` with `fields: ['fileData']` before authentication or upload. The limit applies to file bytes, excluding multipart overhead. Server permissions, MIME restrictions, and entity identifiers still require tenant-specific verification.
|
|
48
|
+
|
|
49
|
+
Dates remain strings. The SDK does not convert a calendar date through a timezone. Request validation checks declared types, required fields, nullability, integer safety, string lengths, and unexpected properties. Response validation checks structure and declared fields while allowing extra fields. Date formats, .NET regular expressions, tenant picklists, and program rules remain explicit caller/server concerns, except for the explicit corrections in the [date contracts](dates-and-answers.md).
|
|
50
|
+
|
|
51
|
+
In the SDK source checkout, `specs/overrides.json` records observed response-only corrections where Swagger differs from live behavior. In particular, optional service program-information objects can be null. It does not loosen request validation.
|
|
52
|
+
|
|
53
|
+
## Bulk execution and pagination
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
const report = await client.bulk(
|
|
57
|
+
'GET /clients/{clientId}',
|
|
58
|
+
clientIds.map(clientId => ({ path: { clientId } })),
|
|
59
|
+
{ concurrency: 4, signal, onResult: async result => persistProgress(result) },
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
for await (const person of client.paginate('GET /clients', {
|
|
63
|
+
query: { perPage: 30, startPage: 1 },
|
|
64
|
+
maxPages: 100,
|
|
65
|
+
signal,
|
|
66
|
+
})) {
|
|
67
|
+
await consume(person);
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Bulk execution schedules individual API calls. It preserves input order in the final report and returns each item's `fulfilled`, `rejected`, or `skipped` outcome. It does not retry items itself. `mapBulk(items, worker, options)` supports custom per-item operations such as verified writes. The callback runs in completion order and is awaited. If it fails, new scheduling stops, active items finish, and callback errors are reported separately without changing successful write outcomes. Cancellation skips unstarted items.
|
|
72
|
+
|
|
73
|
+
A rejected write promise does not prove rollback. Use verified-write results when the application needs commit certainty. Persist the returned report, including skipped items.
|
|
74
|
+
|
|
75
|
+
Pagination uses one-based `startPage` and `perPage`, extracts records from either collection envelope, continues through short pages, and stops on an empty page. It throws at `maxPages` instead of silently truncating. It never follows arbitrary next-link URLs or treats a 404 as an empty page. For providers, use `readProgramCatalog()`: that endpoint returns 404 beyond its last page, so the helper checks the counted total instead. Concurrent changes and delayed query visibility can affect paginated results; this is not a snapshot or proof of absence.
|
|
76
|
+
|
|
77
|
+
## Verify writes and classify reads
|
|
78
|
+
|
|
79
|
+
`verifyWrite()` sends at most once and then reads back, including after an HTTP error. Supply the semantic payload, expected fields, a submit function, and a read-back adapter. It returns:
|
|
80
|
+
|
|
81
|
+
- `verified`, `committed: true` when every expected field matches.
|
|
82
|
+
- `unverified`, `committed: true | 'unknown'` for mismatches or unavailable evidence.
|
|
83
|
+
- `failed`, `committed: false` for preflight failure or an explicit authoritative proof of non-commit.
|
|
84
|
+
|
|
85
|
+
A successful HTTP response alone is not verification. A failed write followed by an empty read is still unverified unless an endpoint-specific adapter supplies authoritative proof. Mismatch values are redacted, and evidence includes field match states and IDs. The adapter's `data` and expected values can contain private data and remain the application's responsibility. `semanticHash()` sorts object keys and hashes JSON values; include tenant, operation, target IDs, and effective date. Undefined values, sparse arrays, cycles, non-finite numbers, and non-plain objects are rejected. Use explicit `null` for an intentionally empty array element. Invalid expected fields fail before submission; malformed readback cannot verify a write. Hashing is not a server idempotency guarantee or a database transaction.
|
|
86
|
+
|
|
87
|
+
`readOutcome()` returns `found`, `absent`, or `unknown`. It maps no status to absence by default; the caller must explicitly supply endpoint-specific absence rules. Resume decisions and persisted step logs belong to the application.
|
|
88
|
+
|
|
89
|
+
## Request controls and observability
|
|
90
|
+
|
|
91
|
+
Default controls are 8 concurrent HTTP requests, a 30-second request deadline, and two additional GET attempts for network failures or HTTP 408, 429, 500, 502, 503, and 504. Configure `maxConcurrency`, `timeoutMs`, `maxReadRetries`, `retryBaseDelayMs`, and `maxRetryDelayMs`. The deadline includes authentication waits, queued requests, body reads, and backoff.
|
|
92
|
+
|
|
93
|
+
GET requests can refresh authentication once after 401. Writes are never replayed, including after 401. Authentication mint failures return to the caller. Retry-After seconds and HTTP dates are honored for retryable HTTP responses, including 429 and 503. If the delay exceeds `maxRetryDelayMs`, the original error returns immediately with `retryAfterMs`: `ThrottledError` for 429 or `UpstreamError` for other HTTP failures. A deadline or cancellation can interrupt an allowed wait. Retry-After on a write is exposed for diagnostics and does not authorize an automatic replay.
|
|
94
|
+
|
|
95
|
+
A shared cooldown opens after five qualifying HTTP failures in ten seconds and lasts thirty seconds by default. Configure `cooldown: { threshold, windowMs, durationMs }`. Matching tenant/base URL/transport clients share concurrency and cooldown state and must use consistent settings. `client.health()` exposes cooldown state, active/queued counts, and mint count. Coordination is in-process; multiple application processes need application-level scheduling controls.
|
|
96
|
+
|
|
97
|
+
Supply `correlationId` per request or let the SDK generate one. `hooks.onRequest`, `onResponse`, `onTokenMint`, `onThrottle`, `onCooldown`, and `logger` receive allowlisted events with endpoint templates, status, duration, retry count, and correlation ID. They receive no bodies, filters, credentials, or tokens. Use opaque correlation IDs, not client identity data. The SDK discards upstream error bodies and rejects redirects.
|
package/docs/services.md
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Record services and select Funding Sources
|
|
2
|
+
|
|
3
|
+
[Documentation index](README.md) · [Agent guide](../AGENTS.md)
|
|
4
|
+
|
|
5
|
+
The snippets assume an authenticated browser session and a REST `api`. Obtain IDs from the application and current lookups before writing.
|
|
6
|
+
|
|
7
|
+
## Service recording and Funding Sources
|
|
8
|
+
|
|
9
|
+
The service recorder combines REST creation with fresh UI completion for fields missing from Swagger. Every successful row has an associated need verified as **Closed / Fully Met**. SSVF service type, assistance type and amount use REST; assistance-period dates and Funding Source allocations use the current UI. Service dates remain separate from the assistance period.
|
|
10
|
+
|
|
11
|
+
```js
|
|
12
|
+
import {createServiceUi, createServiceRecorder,
|
|
13
|
+
createFileGwtMutationStore} from '@choiceapps/communityservices-sdk/browser';
|
|
14
|
+
|
|
15
|
+
const ui = createServiceUi({page: session.page});
|
|
16
|
+
const choices = await ui.listFundingSources({
|
|
17
|
+
// Existing service used only to obtain the current UI lookup and policy.
|
|
18
|
+
context: {clientId, serviceId: existingServiceId, providerId},
|
|
19
|
+
providerId, serviceCodeId: taxonomyCodeId, // Numeric taxonomy ID, not 'BH-3800.7000'.
|
|
20
|
+
});
|
|
21
|
+
const selected = choices.find(f => f.fundId === selectedFundId && f.fundCycleId === selectedCycleId);
|
|
22
|
+
if (!selected) throw new Error('Selected funding source is unavailable');
|
|
23
|
+
|
|
24
|
+
const recorder = createServiceRecorder({api, ui,
|
|
25
|
+
store: createFileGwtMutationStore('/private/path/service-journal')});
|
|
26
|
+
const report = await recorder.recordBulk([{
|
|
27
|
+
key: 'external-system-stable-row-id',
|
|
28
|
+
body: {
|
|
29
|
+
clientId, serviceProvider: providerId, serviceCode: 'BH-3800.7000',
|
|
30
|
+
serviceStartDate: '2026-10-22T12:15:00-04:00',
|
|
31
|
+
serviceEndDate: '2026-10-22T12:15:00-04:00',
|
|
32
|
+
serviceNote: 'Synthetic example',
|
|
33
|
+
ssvfInformation: {typeOfSsvfService: 'Shallow Subsidy',
|
|
34
|
+
ssvfFinancialAssistanceType: 'Rental assistance', ssvfFinancialAssistanceAmount: 925},
|
|
35
|
+
},
|
|
36
|
+
financialAssistancePeriod: {
|
|
37
|
+
start: '2026-10-01T12:00:00-04:00', end: '2026-10-31T12:00:00-04:00',
|
|
38
|
+
},
|
|
39
|
+
fundingSources: [{fundId:selected.fundId, fundCycleId:selected.fundCycleId,
|
|
40
|
+
name:selected.name, amount:925}],
|
|
41
|
+
}], {onResult: result => saveApplicationProgress(result)});
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Use IDs from the actual lookup, not the example IDs. Choices include names, descriptions, balances and submission deadlines; a null balance is not zero. Availability is provider/service/session dependent. The lookup needs a permitted existing service context but makes no record changes. The numeric service-code ID is available as `codeId` on the associated need; it is distinct from the taxonomy string used to create services.
|
|
45
|
+
|
|
46
|
+
`recordBulk` structurally validates all rows first, saves sequentially, stops at the first failure, and reports fulfilled/rejected/skipped rows. Cancellation applies between rows. Keep stable row keys and the same durable journal when retrying or reordering a batch. The recorder appends a deterministic marker to each service note. It never blindly repeats an uncertain REST create or UI save; partial saves may require manual reconciliation. Server business validation can fail after REST creation because UI completion is not atomic. Batches accept up to 10,000 rows; this is a bound, not a throughput guarantee. File journals coordinate one host; multiple hosts require a shared durable lock/store. Browser completion is experimental. Low-level REST `api.bulk` remains available but does not provide these service/need completion guarantees.
|
|
47
|
+
|
|
48
|
+
The recorder pins the service and associated need IDs in the journal. A retry rejects a different returned service ID, a changed need association, or a need readback with the wrong ID or an explicitly inactive state. Matching status text alone does not establish identity. Older journals without a need ID pin the first validated service readback before UI work; they cannot prove the historical need association before that observation.
|
|
49
|
+
|
|
50
|
+
If `onResult` throws, `callbackFailed` is true and remaining rows are skipped. The completed row keeps its actual outcome. Restore application progress from the returned report and reconcile with the original keys before resuming; a failed progress callback does not undo a saved service.
|
|
51
|
+
|
|
52
|
+
Live evidence, confirmed fields, UI request sequence and limitations are in [service findings](capabilities.md).
|
|
53
|
+
|
|
54
|
+
Use `getSsvfServiceRules({typeOfSsvfService, ssvfFinancialAssistanceType})` to obtain the choices reproduced in a sandbox tenant, visible dependent fields, required flags, limits and REST/UI mappings. The recorder supports the three dependent benefit picklists and the Other explanation. Nonfinancial services no longer require a financial type or amount. Selecting a financial type requires both assistance dates. Invalid choices and answers for an inactive service branch fail before writes, including anywhere in a bulk batch. See [SSVF field rules and examples](../SSVF.md) for every choice and the scope of the UI verification.
|
|
55
|
+
|
|
56
|
+
Rejected bulk rows retain a `message` and structured `error` fields, including the failed stage, code, kind, status, reauthentication result, and bounded causes when available. Use the [recovery example](../examples/durable-records.mts) to return those diagnostics to your application. A `SESSION_INTERRUPTED` result does not authorize automatic replay of the REST create or UI save.
|
|
57
|
+
|
|
58
|
+
## Independent needs and available service types
|
|
59
|
+
|
|
60
|
+
Use [the shared service-type catalog](independent-needs.md) to choose from the provider quicklist or broader AIRS/service taxonomy. Supply its `code` to `body.serviceCode`. For a PIT need without service delivery, use `createNeedRecorder`; its status and outcome are explicit and it creates no service or referral.
|
|
61
|
+
|
|
62
|
+
Applications should [cache the catalog](independent-needs.md#cache-the-catalog-in-your-application) once per tenant, with quicklists cached separately per provider/program rather than rereading thousands of terms for every form. Keep transaction verification reads fresh.
|
|
63
|
+
|
|
64
|
+
## Shelter services and beds
|
|
65
|
+
|
|
66
|
+
Use [the shelter guide](shelters.md) for bed assignments and enrollment coverage. The ordinary service recorder does not check clients into beds or maintain night-by-night shelter enrollments. Shelter inventory reporting, live bed availability, and service-type catalogs are separate data.
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# Shelter inventory and vacancies
|
|
2
|
+
|
|
3
|
+
[Documentation index](README.md) · [Shelter writes](shelters.md) · [Types](../browser/shelter-inventory.d.mts) · [Example](../examples/shelter-inventory.mts)
|
|
4
|
+
|
|
5
|
+
`createShelterInventory({page})` provides read-only reporting for an authenticated, dedicated Playwright page for the target tenant. It discovers the provider's visible unit lists, lists named beds and their occupants, and reports current vacancies when the visible evidence supports a complete count. It does not require a REST client or mutation journal.
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import {createShelterInventory} from '@choiceapps/communityservices-sdk/browser';
|
|
9
|
+
|
|
10
|
+
const inventory = createShelterInventory({page: session.page});
|
|
11
|
+
const catalog = await inventory.listUnitLists({providerId});
|
|
12
|
+
for (const {unitListId} of catalog.unitLists) {
|
|
13
|
+
const snapshot = await inventory.getInventory({providerId, unitListId});
|
|
14
|
+
if (snapshot.availability.status === 'known') {
|
|
15
|
+
// Use snapshot.availability.vacantCount and vacantBedKeys for this observation.
|
|
16
|
+
} else {
|
|
17
|
+
// Report unavailable information. Do not turn null into zero vacancies.
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Each operation navigates the page. Use the same page object as other SDK helpers so their shared page lock serializes operations. Close unfinished forms first. Open dialogs, shelter Save controls, login screens, and URLs outside the bound tenant block the read. No Save, Hold, Release, Check In, Check Out, or Transmit action is clicked by the inventory API. Discovery selects a provider, reads its unit-list dropdown, and opens the read-only Unit Availability view. The bed scan selects a unit list, chooses View All, and clears the display filter to All Beds.
|
|
23
|
+
|
|
24
|
+
## What the result means
|
|
25
|
+
|
|
26
|
+
| Field | Meaning |
|
|
27
|
+
| --- | --- |
|
|
28
|
+
| `provider`, `unitList.unitListId` | Provider identity and unit-list ID from the selected UI context and dropdown. |
|
|
29
|
+
| `beds` | Every named bed in the verified visible list, including vacant and held beds. This excludes overflow rows. |
|
|
30
|
+
| `beds[].status` | `occupied`, `vacant`, `unavailable`, or `unknown`. HELD is unavailable. An unrecognized status or EMPTY row without an available check-in control is unknown. |
|
|
31
|
+
| `beds[].occupant` | Client ID, displayed name, displayed entry date, group, and scheduled-checkout indication for a recognized occupied row. DOB and sex are not returned. Unknown rows do not establish an occupant identity. |
|
|
32
|
+
| `counts` | Named beds by observed status. `unavailable` is currently the verified HELD state. `unknown` must remain separate. |
|
|
33
|
+
| `overflow.occupants` | Occupants shown in overflow, outside named-bed counts. |
|
|
34
|
+
| `overflow.entryAvailable` | Whether the UI offers an EMPTY Overflow (New) action. This is not a number of additional beds or a capacity guarantee. |
|
|
35
|
+
| `unitList.reported` | Counts from the live Unit Availability view. Its `usedBeds` includes held beds, so it is not an occupant count. These are not the provider REST reporting-inventory fields. |
|
|
36
|
+
| `availability` | A known vacancy count and matching bed keys only when all named rows are classified and counts reconcile. Otherwise status is unknown, count is null, and the candidate-key list is empty. |
|
|
37
|
+
|
|
38
|
+
Future-dated checkouts stay occupied while the UI displays the occupant. The tooltip's checkout time is returned as `localDateTimeText`; it is not converted to UTC or used to predict future availability. `dateInText` is also display text, not an exact check-in instant. `not-indicated` means the UI supplies no scheduled-checkout indication, not that a backend exit date is definitely null. Refresh the inventory after the scheduled time to learn the actual current state.
|
|
39
|
+
|
|
40
|
+
Availability is a snapshot, not a reservation. A vacant bed may have eligibility, referral, operational, or permission restrictions this reader cannot determine. The write workflow must check availability again immediately before check-in.
|
|
41
|
+
|
|
42
|
+
## Identifiers and freshness
|
|
43
|
+
|
|
44
|
+
The inventory table's numbered control IDs are row positions. They are never returned as server bed IDs. `bedId` is explicitly null because the verified visible table does not expose that identifier.
|
|
45
|
+
|
|
46
|
+
`bedKey` is a JSON tuple of tenant, provider ID, unit-list ID, unit label, address label, and bed label. It stays stable across sorting and occupancy changes. A renamed or moved bed changes the key. Duplicate paths reject the read because the UI cannot distinguish them reliably. Use the key to match reports under that identity contract; do not pass it to a backend write expecting a numeric bed ID. Occupant client IDs and unit-list IDs remain numeric server identities.
|
|
47
|
+
|
|
48
|
+
`freshness.startedAt`, `observedAt`, and `completedAt` are host UTC timestamps spanning the reads. `observedAt` marks the bed-table observation, or the completed catalog observation. `sourceUpdatedAt` is null because no server update timestamp is exposed. `atomic` is false. There is no cache, historical as-of query, or future-vacancy projection. Separate unit-list calls have separate observation windows; combining them does not create an atomic provider-wide snapshot.
|
|
49
|
+
|
|
50
|
+
## Completeness and failure behavior
|
|
51
|
+
|
|
52
|
+
Catalog discovery matches the selected provider's dropdown IDs and unique names against every counted page of the Unit Availability popup. It checks page ranges, counts, duplicates, and provider names. The bed scan requires the verified All Beds table, separates occupied overflow from the new-overflow action, and reconciles named-bed and overflow totals. It reads availability again afterward and rejects changed totals or rows. An unchanged count cannot exclude every concurrent change between reads.
|
|
53
|
+
|
|
54
|
+
Successful results explicitly say `complete-visible-provider` or `complete-visible-unit-list`. These mean complete within this account's visible UI scope, not every configured or inactive bed in the tenant. They do not override row-level unknown statuses. A missing table, clipped bed list, incomplete page sequence, duplicate identity, changed counts, unexpected layout, or failed read produces `ShelterInventoryError` with code `SHELTER_INVENTORY_UNAVAILABLE` and a sanitized `reason`. It never becomes an empty successful result. A zero-list result requires a counted zero in the popup as well as an empty dropdown. A known zero vacancy requires complete evidence.
|
|
55
|
+
|
|
56
|
+
Default limits are 100 availability pages, 1,000 unit lists, and 10,000 named-bed plus occupied-overflow rows. Callers may lower `maxPages`, `maxUnitLists`, and `maxBeds`; these are also the maximum permitted values. The bed table has no verified pagination control. If its row count falls below the counted total, `incomplete-inventory` stops the read instead of guessing how to fetch more. `timeoutMs` defaults to 30,000 ms per wait, with a maximum of 60,000 ms; it is not an overall multi-page deadline.
|
|
57
|
+
|
|
58
|
+
Useful error reasons include `authentication-required`, `application-page-required`, `unfinished-form`, `unit-list-unavailable`, `availability-unavailable`, `incomplete-unit-lists`, `incomplete-inventory`, `inventory-changed`, `ambiguous-bed-identity`, `duplicate-occupant`, `page-limit`, and `bed-limit`. Unexpected browser errors become `read-failed` without embedding client names, page text, or raw browser errors. Managed sessions can also surface the existing session-interruption contract. Correct the cause and perform a fresh read; reads do not create mutation receipts.
|
|
59
|
+
|
|
60
|
+
## Evidence and limits
|
|
61
|
+
|
|
62
|
+
October 24, 2026 sandbox UI discovery verified the provider selector, numeric unit-list dropdown, full bed table, scheduled-checkout tooltip, counted Unit Availability popup, and HELD behavior. A temporary single-bed hold increased Used and decreased Available by one; releasing it restored the EMPTY row. The live table distinguished named beds, occupied overflow, and Overflow (New).
|
|
63
|
+
|
|
64
|
+
Source and installed-package tests run the public inventory reader in local Chrome against synthetic pages with all network requests intercepted. They cover counted pagination, row ordering, future checkout, holds, overflow, unknown states, mismatches, limits, and blocked forms. The live checks directly exercised UI controls and DOM extraction; they did not execute the installed package through the attached browser. That browser lacks the full Page interface. This new reader avoids GWT interception and authenticated fetch, but its automated live integration still needs a supported authenticated Playwright Page. Multi-page availability, alternative roles, and other inventory layouts have synthetic coverage only. No production behavior is claimed.
|
package/docs/shelters.md
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# Shelters: household check-in and checkout
|
|
2
|
+
|
|
3
|
+
[Documentation index](README.md) · [Services](services.md) · [Enrollments](enrollments.md) · [Example](../examples/shelters.mts)
|
|
4
|
+
|
|
5
|
+
Use `createShelters` with `createShelterUi`, a dedicated managed application page, and a durable mutation store. Set the session and REST client to the same tenant. Recording an ordinary service with an Emergency Shelter AIRS code does not assign a bed.
|
|
6
|
+
|
|
7
|
+
For read-only unit-list discovery, all named beds, occupants, and current vacancies, use [shelter inventory reporting](shelter-inventory.md). Inventory observations do not reserve beds or replace the write workflow's immediate availability check.
|
|
8
|
+
|
|
9
|
+
## Discover the tracking mode
|
|
10
|
+
|
|
11
|
+
`readShelterProgram(api, {providerId})` reads the actual provider's `projectType` and `emergencyShelterTrackingMethod` from `GET /providers/complete/{providerId}`. Emergency Shelter can return `Night-by-Night` or `Entry/Exit Date`. TH must be entry/exit. Unknown or inconsistent settings are rejected; the helper does not infer mode from names or AIRS codes.
|
|
12
|
+
|
|
13
|
+
REST service reads expose shelter stays, and complete-service reads add the bed name and associated need. A reproduction in a sandbox tenant returned an array for complete-service despite the documented object response. Provider `bedAndUnitInventoryInformation` is aggregate reporting inventory, not current vacancies or assignable beds. The pinned Swagger has no dedicated shelter check-in or bed-assignment write route.
|
|
14
|
+
|
|
15
|
+
## Check in a household
|
|
16
|
+
|
|
17
|
+
Supply a stable `key`, provider, unit list, household, head, explicit timezone-qualified `entryDate`, and exact named bed for every selected member. Each member must already belong to the household on that date. Named beds must be unique and available. Overflow, holds, reservations, and later additions to an existing shelter group are not supported by this adapter.
|
|
18
|
+
|
|
19
|
+
| Mode | Enrollment behavior |
|
|
20
|
+
| --- | --- |
|
|
21
|
+
| Entry/exit | One fresh UI check-in creates the selected members' stays and enrollments. A preexisting covering enrollment blocks check-in to prevent an implicit duplicate enrollment. |
|
|
22
|
+
| Night-by-night | Default `enrollmentStrategy: 'require-existing'` requires one covering same-program enrollment per member. Explicit `create-if-missing` can journal creation of the head's enrollment and attach members through the existing GWT enrollment helper before checking in. Partial or ambiguous enrollment groups stop the operation. |
|
|
23
|
+
|
|
24
|
+
An enrollment in a different provider never covers the stay, even when the providers have similar names or belong to the same organization. Selected members must share a covering enrollment group with the head. Shelter service group IDs and enrollment group IDs are different identities; do not join them by equality.
|
|
25
|
+
|
|
26
|
+
A single selected member can have a null shelter group or enrollment group. Multiple selected members must have a common non-null group within each record type. On forms that omit a checkbox for the client named in the verified form heading, the adapter treats that client as selected. Every additional requested member must have an explicit checked control.
|
|
27
|
+
|
|
28
|
+
Close unfinished check-in and checkout forms before starting a helper operation. The adapter refuses navigation when either a Save or Save & Exit control identifies an unfinished shelter form.
|
|
29
|
+
|
|
30
|
+
Prepare program-required assessment answers explicitly. This helper does not complete the enrollment assessment or create HUD verification rows. It reports validation failures without choosing answers. The TH check-in sets the head's relationship to Self; application workflows remain responsible for each member's complete assessment.
|
|
31
|
+
|
|
32
|
+
## Check out selected members
|
|
33
|
+
|
|
34
|
+
Supply a new stable key, the verified `checkInKey`, exact `clientIds`, `checkoutDate`, `lastNight`, and IANA `timeZone`. Checkout must fall on the calendar day following the supplied last occupied night. The helper does not infer the last night from check-in or silently add seven hours.
|
|
35
|
+
|
|
36
|
+
- Entry/exit requires destination and reason for leaving, verifies each selected enrollment's exit, and blocks exiting the head before remaining members. Conditional Other explanations and rental-subsidy details are not supported by this shelter adapter; use a separately supported exit workflow when those are required.
|
|
37
|
+
- Night-by-night checkout preserves program enrollments. Omit destination and reason. A checkout does not mean the client has left the program.
|
|
38
|
+
- Unselected records in the readback scope must remain unchanged, including a selected client's historical same-provider stays and other-provider enrollments. The scope includes shelter history for the check-in's selected clients and provider, and enrollment rows read for the household's clients. This is not a tenant-wide change audit. Selected members can check out separately, with the head last in entry/exit mode.
|
|
39
|
+
|
|
40
|
+
The form may include a required exit assessment. Populate its answers before checkout and confirm they are visible in the applicable date/context. A missing required answer fails the save. The helper waits for asynchronous form loading before saving; UI visibility alone is not proof that answers have loaded.
|
|
41
|
+
|
|
42
|
+
The application's supplied night-by-night rule is to exit after 90 days without a stay, or an explicit program exit, dated the day following the last shelter night. **No inactivity scheduler or automatic exit operation is implemented.** Resolve open stays, incomplete history, and head-of-household conflicts before any separate inactivity process exits enrollments.
|
|
43
|
+
|
|
44
|
+
## Retry and recovery contract
|
|
45
|
+
|
|
46
|
+
The durable store records immutable intent and attempted writes before mutation requests are sent. It pins each stay, need, client, bed ID, unit-list ID, group, and enrollment. The UI Codes/Notes field contains an opaque source-key marker; this is distinct from REST `serviceNote`. Removing the marker or changing pinned identities causes a conflict.
|
|
47
|
+
|
|
48
|
+
Retry the same key and unchanged input with the same store after reauthentication. The helper reads current REST and counted GWT state first. A saved result is returned without another Save. A missing, partial, or uncertain attempted write blocks further writes, including replacement keys. Inspect and reconcile it; do not delete the journal to force a retry. The workflow is not an atomic server transaction and cannot prevent unrelated external writers from racing.
|
|
49
|
+
|
|
50
|
+
REST stay history follows the counted total through short pages, including tenants that cap the requested page size. Duplicate records, changing counts, an empty page before the total is reached, and the 1,000-page safety limit stop the workflow. Checkout verifies the original enrollment IDs, entry dates, and group IDs again after saving. Matching exit fields on a replacement enrollment do not count as success.
|
|
51
|
+
|
|
52
|
+
Checkout excludes only the selected stay IDs and, in entry/exit mode, the selected covering enrollment IDs from its unchanged-record checks. An unrelated record change produces a conflict after the save. It does not roll back server changes or permit another save on retry.
|
|
53
|
+
|
|
54
|
+
A verified retry checks current state. Later external edits can produce a conflict instead of a cached success. Persist the journal outside ephemeral containers, and supply shared locking/storage for multi-host writers. The bundled file store is for cooperating processes on one host.
|
|
55
|
+
|
|
56
|
+
If form preparation fails before any shelter save, `cancelUnsubmittedCheckIn({key})` can explicitly close that unfinished intent. It refuses any attempted shelter save, uncertain enrollment write, or new shelter record. It preserves the audit journal and all server records, returns any retained verified enrollments, and permanently rejects reuse of the cancelled key. After reviewing that result, an application can begin a corrected operation using the retained enrollments. This is not recovery for an ambiguous submitted save.
|
|
57
|
+
|
|
58
|
+
## Server issues and evidence
|
|
59
|
+
|
|
60
|
+
See the shareable [WellSky issue log](vendor-issues/README.md), including:
|
|
61
|
+
|
|
62
|
+
- [HTTP 404 for an existing client's empty service list](vendor-issues/empty-service-list-404.md). The helper only accepts this first-page response as empty when an independent counted GWT read proves zero active services.
|
|
63
|
+
- [Enrollment reads fail with `v.entry_date_effective` missing](vendor-issues/enrollment-effective-date-column.md). Current counted GWT enrollment reads provide the fallback. A failed REST read never authorizes a replacement enrollment.
|
|
64
|
+
|
|
65
|
+
Earlier read-only sandbox checks confirmed wrong-provider enrollments for the user-created night-by-night example and matching-provider enrollments for the TH example. Those originals were left unchanged. Complete-service reads exposed bed assignments; null bed names occurred for overflow. Associated shelter needs existed, but their REST status was null. Do not apply the ordinary service recorder's Closed/Fully Met guarantee to shelter-generated needs.
|
|
66
|
+
|
|
67
|
+
The live behavior described here was reproduced in a sandbox tenant. Emergency-shelter entry/exit, overflow, confirmations, inventory administration, conditional exit details, and automated inactivity closure require separate acceptance work.
|
|
68
|
+
|
|
69
|
+
October 24, 2026 write acceptance used two new two-member synthetic households. TH check-in created two stays and two enrollments; a single household checkout closed both stays and both enrollments with the requested destination and reason. The head-only exit was blocked. Night-by-night check-in required separate same-program enrollments, and checkout closed its two stays while preserving both open enrollments. Repeated check-in and checkout keys returned the original records, changed intent was rejected, and a simulated lost acknowledgement after the nightly UI save reconciled without a second save. Required assessment answers were prepared explicitly; these tests do not establish full assessment or HUD-verification completion.
|
|
70
|
+
|
|
71
|
+
A later October 24 audit manually saved and reopened a single-person TH check-in and a next-day checkout in the sandbox UI. The inventory showed a blank shelter group, and the single-client checkout form had no member checkboxes. The packaged adapter's implicit-client handling, nullable-group contract, collateral-change detection, and retry behavior are covered by synthetic source and installed-package tests. That later manual UI run did not execute the packaged Playwright/GWT adapter against the live tenant or repeat the earlier multi-member and night-by-night acceptance.
|
|
72
|
+
|
|
73
|
+
## Meaningful contacts in night-by-night shelter
|
|
74
|
+
|
|
75
|
+
Use the [Current Living Situation recorder](current-living-situation.md) for each meaningful client contact. A bed stay is not a substitute for a contact event or the client's actual living-situation answer. Keep the subassessment start at program entry, put the contact date in Information Date, and allow an empty end.
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Recover without duplicate writes
|
|
2
|
+
|
|
3
|
+
[Documentation index](README.md) · [Session recovery](browser-session.md#reauthentication-and-interrupted-writes)
|
|
4
|
+
|
|
5
|
+
## Resume a failed workflow
|
|
6
|
+
|
|
7
|
+
1. Preserve the original input, source key, durable journal, and returned IDs. Stop scheduling new dependent writes.
|
|
8
|
+
2. Read `getStatus({clientId,key})` or `getHouseholdStatus({headClientId,key})` when using the enrollment workflow. This is local journal progress, not proof of current server state.
|
|
9
|
+
3. If authentication failed, correct the credentials or session first. Use `session.reauthenticate()` after a latched UI login failure. REST token refresh and browser reauthentication are separate operations.
|
|
10
|
+
4. Call the same workflow operation with the same inputs, key, and store. Reordering household members or a service batch is allowed only where documented; do not change intent.
|
|
11
|
+
5. Accept completion only when the helper verifies the saved state. If it still reports ambiguity, inspect the target in the UI and authoritative reads. Retain the pending journal for reconciliation.
|
|
12
|
+
|
|
13
|
+
There is no universal force-retry, journal reset, or safe timeout after which an uncertain write can be resent. A failed response, 404, or empty read does not establish that the server rolled back. Raw `api.bulk` and generated writes do not perform this workflow reconciliation.
|
|
14
|
+
|
|
15
|
+
Successful adoption of an existing record also needs its journal. Retain it even when the result was `already-present` and no server write occurred. Missing previously observed GWT membership/reviews, changed attachment IDs, and changed service/need IDs require review rather than automatic recreation or replacement. Matching text, dates or file bytes alone does not override a saved identity.
|
|
16
|
+
|
|
17
|
+
## Diagnose the failure
|
|
18
|
+
|
|
19
|
+
| Signal | Meaning | Next action |
|
|
20
|
+
| --- | --- | --- |
|
|
21
|
+
| `ValidationError`, `kind: 'validation'` | Request does not meet its contract | Inspect `fields`; validate dates, casing, required values, and current options before submitting |
|
|
22
|
+
| `TenantMismatchError` | Configuration or response tenant indicator conflicts | Stop; correct tenant/environment. Do not disable checks |
|
|
23
|
+
| `AuthError`, HTTP 401 | REST authentication unavailable/rejected | Resolve REST account/token; do not replay a write from a generic HTTP retry handler |
|
|
24
|
+
| `ThrottledError`, `CooldownError` | Request scheduling/backoff limit reached | Observe `retryAfterMs` or `until`. Resume reads; reconcile mutations before any new attempt |
|
|
25
|
+
| `UpstreamError`, `kind: 'http'` | Upstream rejected or failed a request | Keep the operation and status, not raw body logs; inspect authoritative saved state if it was a write |
|
|
26
|
+
| `kind: 'timeout'`, `'network'`, `'aborted'` | Request ended without reliable completion evidence | Treat an attempted mutation as uncertain |
|
|
27
|
+
| `kind: 'protocol'`, `'pagination'` | Invalid/incomplete read evidence | Do not interpret as zero records or permission to create |
|
|
28
|
+
| `DefinitionDriftError`, `UnknownPicklistValueError` | Metadata or lookup assumption changed | Refresh/review definitions and application mapping. Do not guess a numeric option |
|
|
29
|
+
| `AmbiguousCommitError` | Mutation outcome not established | Keep journal pending; re-read through the workflow's authoritative path |
|
|
30
|
+
| `GwtEnrollmentConflictError` | Duplicate/partial/conflicting enrollment or review | Inspect membership and review instant. Do not replay the captured request |
|
|
31
|
+
| `EnrollmentWorkflowError` | Invalid plan, missing head, schema drift, conflict, or unresolved stage | Inspect the specific reason and local status; fix application inputs only before reservation, otherwise reconcile original intent |
|
|
32
|
+
| `SessionInterruptedError`, `SESSION_INTERRUPTED` | Managed page operation ended around session expiry | Check `reauthenticated`; retry the original workflow call, never its prepared RPC |
|
|
33
|
+
| `LoginError`, `PASSWORD_EXPIRED` | Expiration detected with recovery disabled | Use the documented recovery option or resolve the password outside the helper |
|
|
34
|
+
| `LoginError`, `RECOVERY_UNCERTAIN` | Password write/check outcome unresolved | Retain original password, suffix, and recovery journal; determine which credential is active without repeating password writes |
|
|
35
|
+
| `LoginError`, `RECOVERY_CONFIGURATION` or `LOGIN_FAILED` | Invalid recovery setup or login failure | Correct the supplied file/options/account; avoid automatic login loops |
|
|
36
|
+
| `SOURCE_ID_CONFLICT`, `CASE_NOTE_SOURCE_CONFLICT` | Source key reused with different intent | Restore the original intent for retries; use a new revision key only for an intentional supported update |
|
|
37
|
+
| `POSSIBLE_DUPLICATE` | Client creation found identity candidates | Review candidates; never automatically merge or select by names or DOB |
|
|
38
|
+
| `INVALID_VETERAN_STATUS` | Client creation received an unsupported label | Use an exact HUD label from [client creation](durable-records.md); no API access or journal reservation occurred |
|
|
39
|
+
| `AttachmentConflictError`, `ATTACHMENT_CONFLICT` | Attachment intent or saved state conflicts with the journal | Inspect `reason`, the original file and journal; authentication cannot resolve this conflict |
|
|
40
|
+
| `CLIENT_CREATE_UNCERTAIN` | Client create lacks a durably saved receipt | Verify the ID independently, then use `resolveCreated` with the original request; never resend the create |
|
|
41
|
+
| `CLIENT_CREATION_DRIFT`, `CLIENT_ALREADY_MAPPED` | Saved fields or source identity do not agree | Review the existing mapping and authoritative record before proceeding |
|
|
42
|
+
| `EEXIST` from file store lock | Another writer or leftover crash lock | Check the owning process and reconcile its saved attempt before manual lock removal |
|
|
43
|
+
|
|
44
|
+
Not every browser helper error is a `WellSkyError`; some validation/UI failures are ordinary `Error` objects. `verifyWrite` returns a discriminated result rather than using a thrown error as its success contract. Its `unverified` result may already be committed.
|
|
45
|
+
|
|
46
|
+
## Check unexpected values
|
|
47
|
+
|
|
48
|
+
- Empty complete-enrollment verification arrays: use [assessment-based snapshots](assessments.md#read-verification-records-at-enrollment). Do not shift stored dates.
|
|
49
|
+
- Seven-hour effective-date discrepancy: measure saved `date_effective`, then use the [mobile-only correction](assessments.md#mobile-effective-date-correction) if warranted.
|
|
50
|
+
- Wrong or blank DOB: compare current client API, assessment effective date, and fresh UI. Use the [DOB helper](profiles.md#birth-dates); blank mobile values alone are inconclusive.
|
|
51
|
+
- Sex not updated by REST Gender: use the Client Record UI adapter, as described in [profiles](profiles.md).
|
|
52
|
+
- `Non-static method requires a target.` on a complete enrollment read: the enrollment may still exist and its assessment reads may succeed. V2 also fails for fixture 680. Do not recreate the enrollment or invent missing answers. See the [individual enrollment comparison](vendor-issues/enrollment-complete-target-error.md) for the controlled date-only and quality-only tests. Unknown/declined DOB quality also clears the tested failure while DOB remains absent. Record the actual response; never invent a birth date to make a read succeed.
|
|
53
|
+
- `v.entry_date_effective` error: use authoritative GWT enrollment reads or obtain an upstream fix. It occurs after ordinary UI saves too and reproduces in both V2 and V3 for grouped records. Do not switch versions or skip failed pages as a workaround. See the [comparison and reproduction report](vendor-issues/enrollment-effective-date-column.md).
|
|
54
|
+
- REST attachments rejected with valid REST token: use the verified [browser attachment workflow](files-and-notes.md). Extra body credentials do not resolve the observed server failure.
|
|
55
|
+
- Provider catalog missing or incomplete: do not substitute a partial list. `readProgramCatalog` verifies counts and applies provider filters locally.
|
|
56
|
+
|
|
57
|
+
## Keep recovery evidence private
|
|
58
|
+
|
|
59
|
+
Record the operation, stable source key, target IDs, journal stage, correlation ID, and whether reads verified a commit. SDK transport hooks expose allowlisted metadata. Do not log credentials, cookies, JWTs, full RPC bodies, SSNs, note text, or attachment bytes. Application journals and returned data can still contain client information even when SDK errors are sanitized.
|
|
60
|
+
|
|
61
|
+
The file store coordinates one host and leaves crash locks intentionally. Multi-host operation requires a shared durable locking implementation. No local journal prevents unrelated WellSky users or integrations from editing between a read and a save.
|