@choiceapps/communityservices-sdk 0.6.0 → 0.8.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 (61) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/browser/enrollment-workflow.d.mts +29 -5
  3. package/browser/enrollment-workflow.mjs +88 -34
  4. package/browser/entry-correction.mjs +13 -11
  5. package/browser/entry-row-registration.mjs +63 -0
  6. package/browser/household-enrollment-discovery.mjs +49 -0
  7. package/browser/index.d.mts +7 -2
  8. package/browser/index.mjs +1 -1
  9. package/browser/playwright-session.mjs +24 -8
  10. package/browser/workflow-error-codes.mjs +198 -0
  11. package/dist/cjs/mobile-preflight.d.ts +19 -0
  12. package/dist/cjs/mobile-preflight.d.ts.map +1 -0
  13. package/dist/cjs/mobile-preflight.js +75 -0
  14. package/dist/cjs/mobile-preflight.js.map +1 -0
  15. package/dist/cjs/picklist-labels.d.ts +22 -0
  16. package/dist/cjs/picklist-labels.d.ts.map +1 -0
  17. package/dist/cjs/picklist-labels.js +35 -0
  18. package/dist/cjs/picklist-labels.js.map +1 -0
  19. package/dist/cjs/v2.d.ts +4 -0
  20. package/dist/cjs/v2.d.ts.map +1 -1
  21. package/dist/cjs/v2.js +5 -0
  22. package/dist/cjs/v2.js.map +1 -1
  23. package/dist/cjs/v3.d.ts +4 -0
  24. package/dist/cjs/v3.d.ts.map +1 -1
  25. package/dist/cjs/v3.js +5 -0
  26. package/dist/cjs/v3.js.map +1 -1
  27. package/dist/cjs/write-labels.d.ts +13 -0
  28. package/dist/cjs/write-labels.d.ts.map +1 -0
  29. package/dist/cjs/write-labels.js +68 -0
  30. package/dist/cjs/write-labels.js.map +1 -0
  31. package/dist/esm/mobile-preflight.d.ts +19 -0
  32. package/dist/esm/mobile-preflight.d.ts.map +1 -0
  33. package/dist/esm/mobile-preflight.js +70 -0
  34. package/dist/esm/mobile-preflight.js.map +1 -0
  35. package/dist/esm/picklist-labels.d.ts +22 -0
  36. package/dist/esm/picklist-labels.d.ts.map +1 -0
  37. package/dist/esm/picklist-labels.js +32 -0
  38. package/dist/esm/picklist-labels.js.map +1 -0
  39. package/dist/esm/v2.d.ts +4 -0
  40. package/dist/esm/v2.d.ts.map +1 -1
  41. package/dist/esm/v2.js +3 -0
  42. package/dist/esm/v2.js.map +1 -1
  43. package/dist/esm/v3.d.ts +4 -0
  44. package/dist/esm/v3.d.ts.map +1 -1
  45. package/dist/esm/v3.js +3 -0
  46. package/dist/esm/v3.js.map +1 -1
  47. package/dist/esm/write-labels.d.ts +13 -0
  48. package/dist/esm/write-labels.d.ts.map +1 -0
  49. package/dist/esm/write-labels.js +65 -0
  50. package/dist/esm/write-labels.js.map +1 -0
  51. package/docs/assessments.md +50 -1
  52. package/docs/capabilities.md +19 -0
  53. package/docs/enrollments.md +41 -5
  54. package/docs/reference.md +3 -2
  55. package/docs/troubleshooting.md +17 -0
  56. package/docs/vendor-issues/lookup-write-labels.md +13 -0
  57. package/examples/README.md +3 -0
  58. package/examples/catalog-write-labels.mts +15 -0
  59. package/examples/checked-assessment-writes.mts +14 -0
  60. package/examples/enrollment-discovery-and-correction.mts +19 -0
  61. package/package.json +1 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"write-labels.js","sourceRoot":"","sources":["../../src/write-labels.ts"],"names":[],"mappings":"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","sourcesContent":["import type {AssessmentDefinition,PicklistOption,QuestionDefinition} from './metadata.js';\nimport {flattenQuestions} from './metadata.js';\nimport type {EncodingOptions} from './encoding.js';\nimport {ValidationError} from './errors.js';\nimport {isRecord} from './validation.js';\n\nexport interface WriteLabelObservation {tenant:string;response:unknown;}\nexport type ReadBackWriteLabelResolver = NonNullable<EncodingOptions['writeLabel']> & {readonly tenant:string};\n\n/** Builds labels only from exact readback evidence. It never infers HUD suffixes.\n * Unobserved or changed options return undefined, so the standard encoder refuses them. */\nexport function createReadBackWriteLabelResolver(\n definition:AssessmentDefinition,\n observations:readonly WriteLabelObservation[],\n):ReadBackWriteLabelResolver{\n const questions=new Map<number,QuestionDefinition>();\n for(const {question} of flattenQuestions(definition.questions)){\n if(question.questionType!=='lookup')continue;\n const prior=questions.get(question.questionId);\n if(prior&&JSON.stringify(prior)!==JSON.stringify(question))throw new ValidationError('ambiguous write-label question');\n questions.set(question.questionId,structuredClone(question));\n }\n const labels=new Map<string,string>();\n const identity=(questionId:number,optionId:number)=>JSON.stringify([questionId,optionId]);\n const visit=(value:unknown,depth=0):void=>{\n if(depth>40)throw new ValidationError('write-label observation exceeds nesting limit');\n if(Array.isArray(value)){for(const child of value)visit(child,depth+1);return;}\n if(!isRecord(value))return;\n if(value.question_type==='lookup'&&typeof value.question_id==='number'){\n const question=questions.get(value.question_id);\n if(question){\n if(value.virt_field_name!==question.fieldName)throw new ValidationError('write-label question identity mismatch');\n const raw=value.answer_value;\n const id=typeof raw==='number'?raw:typeof raw==='string'&&/^\\d+$/.test(raw)?Number(raw):null;\n if(id!==null&&Number.isSafeInteger(id)&&typeof value.answer_value_text==='string'&&value.answer_value_text.trim()){\n const options=question.options.filter(option=>option.id===id);\n if(options.length!==1)throw new ValidationError('write-label option identity mismatch',[question.fieldName??String(question.questionId)]);\n const key=identity(question.questionId,id),prior=labels.get(key);\n if(prior!==undefined&&prior!==value.answer_value_text)throw new ValidationError('conflicting write-label observations',[question.fieldName??String(question.questionId)]);\n labels.set(key,value.answer_value_text);\n }\n }\n }\n // Traverse known response/assessment containers only. Do not interpret arbitrary strings as data.\n for(const key of ['value','values','sub_assessment_question_list','sub_assessment_answer_list','sub_assessment_list'])if(value[key]!==undefined)visit(value[key],depth+1);\n };\n for(const observation of observations){\n if(observation.tenant!==definition.tenant)throw new ValidationError('write-label observation tenant mismatch');\n visit(observation.response);\n }\n const sameOption=(a:PicklistOption,b:PicklistOption)=>a.id===b.id&&a.picklistId===b.picklistId&&a.code===b.code&&a.picklistCode===b.picklistCode&&a.name===b.name&&a.hudVisibility===b.hudVisibility;\n const resolver=(question:QuestionDefinition,option:PicklistOption)=>{\n const known=questions.get(question.questionId);\n if(!known||known.fieldName!==question.fieldName||question.questionType!=='lookup'||!known.options.some(o=>sameOption(o,option)))return undefined;\n return labels.get(identity(question.questionId,option.id));\n };\n return Object.freeze(Object.assign(resolver,{tenant:definition.tenant}));\n}\n"]}
@@ -29,7 +29,41 @@ The default metadata cache TTL is 60 seconds with 100 entries per client. Config
29
29
 
30
30
  WellSky requires a client context and known assessment code for these reads. There is no documented global assessment-list or standalone picklist-list endpoint. Mobile option metadata has no effective-date filter, so definitions explicitly identify its options as current even when the generic assessment read is dated. Metadata also does not provide a complete questionnaire branching engine.
31
31
 
32
- Use the raw `assessments.get`, `assessmentsMobile.getAnswers`, and `enrollments.getAssessment` methods for recorded answers. Use `encodeAnswers` and `encodeNewSubassessmentRow` for endpoint-specific encoding. Mobile lookups use option IDs; standalone/enrollment lookups require an explicit `writeLabel` resolver with tenant-verified labels. Raw option names are not assumed to be valid write labels. Some HUD-marked choices require `(HUD)` in the full write label even when metadata `name` omits it. Do not infer this from trailing whitespace or append the suffix to every option. Continue supplying tenant-verified labels until a label resolver has authoritative metadata; recorded `answer_value_text` is useful evidence for the corresponding option. Provider answers are refused for mobile writes because that endpoint can silently discard them. Answer-state code mappings and multi-value serialization are caller-supplied; a refusal is never converted to `No`.
32
+ Use the raw `assessments.get`, `assessmentsMobile.getAnswers`, and `enrollments.getAssessment` methods for recorded answers. Use `encodeAnswers` and `encodeNewSubassessmentRow` for endpoint-specific encoding. Mobile lookups use option IDs; standalone/enrollment lookups require an explicit `writeLabel` resolver with tenant-verified labels. Raw option names are not assumed to be valid write labels. Some HUD-marked choices require `(HUD)` in the full write label even when metadata `name` omits it. Do not infer this from trailing whitespace or append the suffix to every option. Use the catalog resolver below for exact labels without existing answers, or retain an explicitly verified callback; recorded `answer_value_text` also supplies evidence for observed options. Provider answers are refused for mobile writes because that endpoint can silently discard them. Answer-state code mappings and multi-value serialization are caller-supplied; a refusal is never converted to `No`.
33
+
34
+ ## Resolve lookup labels before an answer exists
35
+
36
+ The browser enrollment session exposes `readPicklistOptions({clientId, optionIds})`. It reads the current GWT picklist catalog and returns complete, counted rows with `optionId`, `picklistId`, `code`, and the exact full `name`. It needs an authenticated session and client context, but no existing enrollment or answer. Only explicit positive option IDs are queried, in batches of 200; missing, duplicated or malformed results stop discovery.
37
+
38
+ ```ts
39
+ import {flattenQuestions, createCatalogWriteLabelResolver} from '@choiceapps/communityservices-sdk';
40
+ const definition = await api.assessments.getDefinition({clientId, assessmentCode});
41
+ const optionIds = [...new Set(flattenQuestions(definition.questions)
42
+ .filter(({question}) => question.questionType === 'lookup')
43
+ .flatMap(({question}) => question.options.map(option => option.id)))];
44
+ const catalog = await session.enrollmentSession.readPicklistOptions({clientId, optionIds});
45
+ const writeLabel = createCatalogWriteLabelResolver(definition, catalog);
46
+ ```
47
+
48
+ The resolver matches option ID, picklist ID and code, preserves the full catalog name, and permits only options already allowed by the question definition. HUD and non-HUD choices have distinct IDs. It never appends a suffix, guesses from whitespace, or broadens the question's allowed list. Missing identity evidence or a tenant mismatch throws before encoding. The immutable resolver returns `undefined` if called with changed option metadata.
49
+
50
+ `createEnrollmentWorkflow` uses this catalog automatically when its `enrollmentSession` has `readPicklistOptions` and `writeLabel` is omitted. This covers standard entry/interim/exit batches, mandatory carry-forward and `correctEntry`. Explicit callbacks take precedence and skip automatic catalog reads. Legacy custom sessions without this reader still need a callback. Raw REST clients do not acquire UI access implicitly. See [the typed example](../examples/catalog-write-labels.mts) and [read-only evidence](vendor-issues/lookup-write-labels.md).
51
+
52
+ ## Resolve exact lookup write labels from readback
53
+
54
+ `createReadBackWriteLabelResolver` preserves the exact `answer_value_text` for an observed question/option pair, including a HUD suffix and whitespace. It does not infer labels from trailing spaces or `hudVisibility`: retained readbacks include suffixed and unsuffixed labels with the same visibility flag.
55
+
56
+ ```ts
57
+ import {createReadBackWriteLabelResolver} from '@choiceapps/communityservices-sdk';
58
+ const writeLabel = createReadBackWriteLabelResolver(definition, [
59
+ {tenant: api.tenant, response: recordedAnswers},
60
+ ]);
61
+ // Supply writeLabel to encodeAnswers or createEnrollmentWorkflow.
62
+ ```
63
+
64
+ Supply authorized assessment reads from the definition's tenant. The helper matches question ID, field name and option ID, rejects conflicting labels and mixed tenants, and returns an immutable resolver tagged with its tenant. The enrollment workflow checks that tenant. The resolver returns `undefined` for unobserved or changed options, so normal encoding refuses them. Refresh the definition and observations after metadata changes.
65
+
66
+ This is an evidence-backed resolver, not a complete label catalog. Unobserved options still need an authoritative readback or another independently verified resolver. Do not write answers merely to discover labels. See [the typed example](../examples/checked-assessment-writes.mts).
33
67
 
34
68
  ## Yes/no answer acceptance
35
69
 
@@ -54,6 +88,21 @@ An entry assessment and a standalone form can reference the same stored question
54
88
 
55
89
  Read the existing answer before planning a mobile scalar insert. If it already exists at that instant with another value, use a supported standard correction/update path after resolving the original operation; do not move its timestamp to evade the collision. Raw `assessmentsMobile.addAnswer` remains a raw write and performs no implicit read or cross-form collision check. Never switch endpoints to retry an uncertain batch.
56
90
 
91
+ Use `writeMobileAnswersChecked(api, request)` for an explicit scalar collision check before a raw mobile batch. It accepts the ordinary mobile request with a required timezone-qualified, whole-second `query.dateEffective`. It reads the supplied instrument one second after that instant so yes/no rows are visible, then matches question identity and effective date. A row at the target instant blocks the entire batch even when its value is identical or empty. Missing, invalid or newer readback evidence also blocks the write. Older answers allow the insert.
92
+
93
+ ```ts
94
+ import {writeMobileAnswersChecked, MobileAnswerPreflightError} from '@choiceapps/communityservices-sdk';
95
+ await writeMobileAnswersChecked(api, {
96
+ path: {clientId, assessmentCode},
97
+ query: {dateEffective: originalEffectiveInstant},
98
+ body: encodedMobileAnswers,
99
+ });
100
+ ```
101
+
102
+ `MobileAnswerPreflightError` has a stable `code`, affected `fields`, and `committed: false`. A collision uses `MOBILE_ANSWER_EXISTS`; unavailable evidence uses `MOBILE_PREFLIGHT_UNAVAILABLE`. The helper checks scalars, including questions shared across instruments, but does not detect duplicate subassessment rows. It makes one write after a successful preflight and does not retry or journal it. An error from that write retains its original type and possible uncertain outcome. Reads and writes are not atomic against other users. Pass the original instant; the SDK transport applies any configured mobile date correction.
103
+
104
+ New enrollment workflow plans also check scalar collisions before creating an enrollment or sending a mobile batch. Existing pending journal operations retain their reconciliation behavior.
105
+
57
106
  ## Race and ethnicity combinations
58
107
 
59
108
  `encodeAnswers` recognizes the standard `HUD_RACEETHNICITY` field. It resolves single-category and multiple-category answers to the tenant's `RACE_ETH_COMBO_*` option IDs. A single White answer uses `RACE_ETH_COMBO_WHI`, even when the caller supplies `RACE_ETH_WHI`. Atomic option IDs can read back with a label while appearing blank in the UI.
@@ -86,3 +86,22 @@ The integrator reported these results using SDK 0.4.1 in the training tenant. Th
86
86
  - After those household exits, both re-enrollment attempts failed on the pre-create REST list with HTTP 400 from the grouped-enrollment `42703` gap. This confirms that an exited household enrollment can still break that REST list.
87
87
 
88
88
  The GWT catalog correction has regression coverage for individual creation, household heads and member preflight, failed-read classification and re-enrollment. Same-date entry corrections and adoption of manual enrollments have synthetic coverage. These newer paths have not been independently verified live.
89
+
90
+ ## Integrator-reported SDK 0.6.0 acceptance
91
+
92
+ The integrator reports these training-tenant results on November 10 and 11, 2026; the SDK team has not independently rerun them:
93
+
94
+ - GWT `listEnrollments` succeeded for a household client whose REST list returned HTTP 400.
95
+ - Standalone `adoptEnrollment` followed by `createInterim`, and single-person same-day `correctEntry`, succeeded. Same-key repeats made no writes.
96
+ - Three pending 0.5.0 corrections were refused without confirmation and resolved as `rejected` with confirmation.
97
+ - Earlier 0.5.0 runs verified individual/household adoption followed by exit, household re-enrollment after exit, and path corrections in both directions for individuals and households, with no-write same-key repeats.
98
+
99
+ The integrator has not tested household adoption followed by `createHouseholdInterim`, scalar correction on an adopted enrollment, or a new correction key after resolution. Synthetic tests do not replace that acceptance.
100
+
101
+ The 0.7.0 label resolver, group flags, household discovery, row registration, error codes and mobile collision preflight have local regression coverage, not new live tenant acceptance. In 0.7.0, full write labels remained unresolved for unobserved options; the 0.8.0 catalog reader below removes that limitation when UI catalog access is available. Household discovery refuses ambiguous current-membership matches. Yes/no blanking remains unsupported; no alternative route has been verified.
102
+
103
+ ## Catalog label verification, November 11, 2026
104
+
105
+ Read-only training checks matched all 1,288 distinct option IDs in the Coordinated Entry instrument to current GWT catalog rows across three synthetic clients (4,089 question/option pairs). Catalog names differed from REST metadata names for 139 option occurrences in one definition. The five enhanced yes/no HUD questions shared a catalog; plain yes/no alternatives had separate option IDs. The visible profile DOB-quality dropdown matched the catalog's full labels. This establishes metadata identity and displayed labels without prior-answer evidence, not fresh acceptance of every standard write. Existing enrollment and answer records were left unchanged.
106
+
107
+ SDK 0.8.0 uses the catalog automatically for workflows with a built-in enrollment session. REST-only applications can supply a catalog resolver or retain their explicit callback. See [catalog evidence and limitations](vendor-issues/lookup-write-labels.md).
@@ -130,7 +130,7 @@ Every mandatory label is resolved before the first write. The `yesno` stage veri
130
130
 
131
131
  ## Correct an existing entry assessment
132
132
 
133
- `correctEntry` patches an entry at its original effective timestamp. Supply the verified `createEnrollment` key as `enrollmentKey` and a new stable `key` for each correction. Configure `enrollmentSession` and a `writeLabel` resolver for tenant-verified lookup labels. A correction requires an open enrollment with no interim reviews or exit workflows.
133
+ `correctEntry` patches an entry at its original effective timestamp. Supply the verified `createEnrollment` key as `enrollmentKey` and a new stable `key` for each correction. Configure `enrollmentSession`; built-in sessions discover exact lookup labels automatically. An explicit `writeLabel` callback overrides that discovery. A correction requires an open enrollment with no interim reviews or exit workflows.
134
134
 
135
135
  ```ts
136
136
  const corrected = await workflow.correctEntry({
@@ -148,7 +148,7 @@ const corrected = await workflow.correctEntry({
148
148
  });
149
149
  ```
150
150
 
151
- `answers` uses the ordinary answer and option selectors. `clear` explicitly removes a recorded value; an omitted field stays unchanged. Yes/no fields cannot appear in `clear`: the SDK throws `UNSUPPORTED_YES_NO_CLEAR`, with the affected `fields`, before any correction write or new journal record. Use `{value: false}` to answer `No`; it does not mean blank. No alternative yes/no blanking route has been verified. Do not use `notApplicable` to clear an old answer. Every row key must identify an existing row returned by the original entry operation. The correction never creates a second row, deletes a row, or changes the enrollment date.
151
+ `answers` uses the ordinary answer and option selectors. `clear` explicitly removes a recorded value; an omitted field stays unchanged. Yes/no fields cannot appear in `clear`: the SDK throws `UNSUPPORTED_YES_NO_CLEAR`, with the affected `fields`, before any correction write or new journal record. Use `{value: false}` to answer `No`; it does not mean blank. No alternative yes/no blanking route has been verified. Do not use `notApplicable` to clear an old answer. Every row key must identify an existing row returned by the original entry operation or explicitly bound with `registerEntryRows`. The correction never creates a second row, deletes a row, or changes the enrollment date.
152
152
 
153
153
  Scalars use the standard assessment endpoint, which supports same-date updates. The batch includes every mandatory scalar, preserving its current value unless the patch explicitly answers or clears it. Missing mandatory values require an explicit answer, or an explicit clear for a supported non-yes/no type; lookup values require verified write labels. Readback checks one second after the original instant so stored yes/no values are visible.
154
154
 
@@ -176,7 +176,7 @@ await workflow.resolveEntryCorrection({
176
176
 
177
177
  The SDK verifies the complete saved pre-write state before and after confirmation and rechecks enrollment context. A partial application, changed state or unavailable read prevents resolution. Successful resolution marks the correction `rejected` while retaining its original intent, attempted steps, diagnostics and evidence reference. It makes no SDK record writes to WellSky. Repeating the same resolution returns `already-rejected`; submitting that correction key again throws `ENTRY_CORRECTION_REJECTED`. Use a new correction key for the corrected intent only after resolution. A verified successful correction cannot be rejected.
178
178
 
179
- This also supports pending 0.5.0 journals, including those without failure metadata, when the required independent evidence exists. A new validation failure that occurs before journaling needs no resolution.
179
+ This also supports pending 0.5.0 journals, including those without failure metadata, when the required independent evidence exists. For these journals the callback can receive only a step name and state, with no failure kind or status. The caller must retain its own original failure evidence; the SDK cannot reconstruct it and must not resend the write to obtain diagnostics. A new validation failure that occurs before journaling needs no resolution.
180
180
 
181
181
  ## Household enrollment and interim assessments
182
182
 
@@ -232,7 +232,9 @@ const detail = await session.enrollmentSession.readEnrollment({
232
232
  // detail.members contains the complete linked clientId/enrollmentId pairs.
233
233
  ```
234
234
 
235
- Use the session returned by `openEnrollmentSession`. Lists include open and exited entries, validate complete counts, and refuse more than 1,000 records. The detail read currently exposes `householdClientIds`, not `householdId`; these client IDs and the enrollment's group are not interchangeable with a household ID. Household adoption still requires an explicitly verified household ID.
235
+ Use the session returned by `openEnrollmentSession`. Lists include open and exited entries, validate complete counts, and refuse more than 1,000 records. List rows and details include `groupId` and `grouped`. The flag describes a stored group link, including a group with one participant; it is not a member count and requires no per-row detail reads. These fields are required on managed catalog rows and optional on custom detail-reader contracts for compatibility. `groupId` is never a household ID. For a verified household match from a head enrollment, use `discoverHouseholdEnrollment` below.
236
+
237
+ The same session also exposes `readPicklistOptions({clientId, optionIds})` for exact labels before answers exist. It uses fresh read context, validates counted results, and makes no record writes. Enrollment workflows use it automatically unless `writeLabel` is supplied. See [catalog lookup labels](assessments.md#resolve-lookup-labels-before-an-answer-exists).
236
238
 
237
239
  ## Individual enrollment exits
238
240
 
@@ -264,7 +266,41 @@ Use `adoptEnrollment({key, clientId, enrollmentId})` for a standalone enrollment
264
266
 
265
267
  Adoption writes only to the local durable journal. It does not create, attach, exit or modify an HMIS record. Later entrants retain their own original entry dates. An enrollment already tracked under another entry key is rejected; use its existing key. Retry an interrupted adoption with the same inputs and store.
266
268
 
267
- After standalone adoption, its key can be used as `enrollmentKey` for `createInterim`, scalar-only `correctEntry`, or `exitEnrollment`. After household adoption, the group key works with `createHouseholdInterim` and `exitHousehold`; use the returned member's `key` for that person's scalar-only `correctEntry`. Correction still requires an open entry without reviews or exits. Do not use an all-not-applicable `createEnrollment` call merely to link an existing entry: it can create an enrollment when no match exists. Exit assessment plans and normal household head rules still apply. Adoption records existing enrollment identities, not historical subassessment row-key mappings, so its returned `recordsetIds` is empty. It cannot identify rows for `correctEntry` by a new invented key. See [manual-entry exit examples](../examples/existing-enrollment-exits.mts).
269
+ After standalone adoption, its key can be used as `enrollmentKey` for `createInterim`, scalar-only `correctEntry`, or `exitEnrollment`. After household adoption, the group key works with `createHouseholdInterim` and `exitHousehold`; use the returned member's `key` for that person's scalar-only `correctEntry`. Correction still requires an open entry without reviews or exits. Do not use an all-not-applicable `createEnrollment` call merely to link an existing entry: it can create an enrollment when no match exists. Exit assessment plans and normal household head rules still apply. Adoption initially returns empty `recordsetIds`. Use `registerEntryRows` below to bind verified existing recordsets before row corrections; a new invented key alone cannot identify a historical row. See [manual-entry exit examples](../examples/existing-enrollment-exits.mts).
270
+
271
+ ### Discover a household from its head enrollment
272
+
273
+ ```ts
274
+ const discovered = await workflow.discoverHouseholdEnrollment({
275
+ headClientId, headEnrollmentId,
276
+ });
277
+ // discovered: householdId, householdSource, providerId, type, members
278
+ const adopted = await workflow.adoptHouseholdEnrollment({
279
+ key: sourceEnrollmentKey, headClientId, headEnrollmentId,
280
+ });
281
+ ```
282
+
283
+ Discovery reads the linked enrollment cohort through GWT and matches it against complete current household membership reads. It requires one household containing the cohort, the same head, and membership dates covering each entry. It verifies that household again before returning `householdSource: 'verified-membership'`. This is a verified membership match, not a native historical household pointer on the enrollment. Zero matches, multiple matches, incomplete reads or changed relationships fail without adopting. Historical households that no longer match require the existing explicit adoption inputs and their validation. No record is written to WellSky during discovery or adoption.
284
+
285
+ ### Register existing entry rows
286
+
287
+ ```ts
288
+ await workflow.registerEntryRows({
289
+ key: sourceRegistrationKey,
290
+ enrollmentKey: adoptedEntryKey,
291
+ clientId,
292
+ rows: [{key: 'entry-ce', parentField, recordsetId: existingRecordsetId}],
293
+ });
294
+ // correctEntry can now reference key: 'entry-ce' with the same parentField.
295
+ ```
296
+
297
+ Select explicit recordset IDs from authorized assessment reads. Registration verifies the enrollment, question definition, and each row's presence in both entry-time and current reads. It stores immutable key/parent/recordset bindings in the local journal and makes no WellSky record writes. Same-key repeats return `already-present`; duplicate aliases, absent rows, rebinding, and pending operations are refused. Native entry row keys already work without registration. For household adoption, use each returned member's entry `key` and register that member's rows separately. Registration does not establish who originally created a row or make later edits eligible: `correctEntry` still enforces open-entry and no-review rules. See [the typed example](../examples/enrollment-discovery-and-correction.mts).
298
+
299
+ ### Classify workflow failures
300
+
301
+ `EnrollmentWorkflowError` exposes a stable `code`, `operation`, and `committed`. Classify by code instead of message text. Pre-write validation, discovery and adoption failures report `committed: false`. Any attempted write, relevant prior attempt, or unreadable prior outcome remains `'unknown'`. The marker describes WellSky record writes for that operation, not local journal changes. `cause` preserves the original error where available.
302
+
303
+ A failed `resolveEntryCorrection` can have `committed: false` because resolution itself writes no WellSky records, while `originalCommitted: 'unknown'` describes the original correction. Never use a resolution error's marker to infer non-application of the original write. See [error codes and recovery](troubleshooting.md#workflow-error-codes).
268
304
 
269
305
  ## Household exits
270
306
 
package/docs/reference.md CHANGED
@@ -8,13 +8,14 @@ Only the package export paths below are supported import entry points. Declarati
8
8
 
9
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
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`, `parseRestInstant`, `readEnrollmentVerifications`, `readOutcome`, `readProgramCatalog`, `resolvePicklistOption`, `semanticHash`, `specSha256`, `toItems`, `verifyWrite`.
11
+ Runtime exports at the root: `MobileAnswerPreflightError`, `writeMobileAnswersChecked`, `createReadBackWriteLabelResolver`, `createCatalogWriteLabelResolver`, `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`, `parseRestInstant`, `readEnrollmentVerifications`, `readOutcome`, `readProgramCatalog`, `resolvePicklistOption`, `semanticHash`, `specSha256`, `toItems`, `verifyWrite`.
12
12
 
13
13
  | Contract | Declaration | Guide |
14
14
  | --- | --- | --- |
15
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
16
  | Authentication, transport, responses and errors | [Runtime](../dist/esm/runtime.d.ts) | [Configuration](configuration.md), [recovery](troubleshooting.md) |
17
17
  | Metadata and picklists | [Metadata](../dist/esm/metadata.d.ts) | [Assessments](assessments.md) |
18
+ | Checked mobile inserts and exact lookup labels | [Preflight](../dist/esm/mobile-preflight.d.ts), [readback labels](../dist/esm/write-labels.d.ts), [catalog labels](../dist/esm/picklist-labels.d.ts) | [Assessments](assessments.md) |
18
19
  | Answer encoding | [Encoding](../dist/esm/encoding.d.ts) | [Dates and answers](dates-and-answers.md) |
19
20
  | Write verification | [Verification](../dist/esm/verification.d.ts) | [REST](rest-client.md) |
20
21
  | Program catalog | [Program discovery](../dist/esm/program-discovery.d.ts) | [Enrollments](enrollments.md) |
@@ -36,7 +37,7 @@ Runtime exports: `AttachmentConflictError`, `CoordinatedEntryEventConflictError`
36
37
  | Profile fields and Sex | [Client profile](../browser/client-profile.d.mts) | `read`, `update` |
37
38
  | Calendar DOB | [Birth dates](../browser/date-of-birth.d.mts) | `read`, `validate`, `verify`, `update` |
38
39
  | Provider assessment defaults | [Discovery](../browser/program-discovery.d.mts) | `listAssessments`, `getProviderAssessments` |
39
- | Enrollment and household lifecycle | [Workflow](../browser/enrollment-workflow.d.mts) | Discovery, entry, `correctEntry`, `resolveEntryCorrection`, `adoptEnrollment`, `adoptHouseholdEnrollment`, member additions, interim, `exitEnrollment`, `exitHousehold`, journal status; `EntryExitType` and per-type `assessmentLocations` in [enrollments](enrollments.md) |
40
+ | Enrollment and household lifecycle | [Workflow](../browser/enrollment-workflow.d.mts) | Discovery, entry, `correctEntry`, `resolveEntryCorrection`, `adoptEnrollment`, `adoptHouseholdEnrollment`, `discoverHouseholdEnrollment`, `registerEntryRows`, member additions, interim, `exitEnrollment`, `exitHousehold`, journal status; `EntryExitType` and per-type `assessmentLocations` in [enrollments](enrollments.md) |
40
41
  | 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
42
  | Services and recovery diagnostics | [Service recording](../browser/service-recording.d.mts) | `record`, `recordBulk`, Funding Sources, `RecoveryDiagnostic` |
42
43
  | SSVF conditional rules | [SSVF types](../browser/ssvf-rules.d.mts) | [Field rules](../SSVF.md) and dated option snapshots |
@@ -46,6 +46,23 @@ Successful adoption of an existing record also needs its journal. Retain it even
46
46
 
47
47
  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.
48
48
 
49
+ ## Workflow error codes
50
+
51
+ Enrollment workflow errors expose stable `code` values. Common actionable codes include:
52
+
53
+ | Code | Meaning |
54
+ | --- | --- |
55
+ | `ADOPTION_HOUSEHOLD_HEAD_MISMATCH` | The supplied head does not match the verified enrollment cohort |
56
+ | `INDEPENDENT_CONFIRMATION_REQUIRED` | Resolution lacks independent confirmation of non-application |
57
+ | `UNSUPPORTED_YES_NO_CLEAR` | A correction requests blanking a yes/no answer; no supported route is verified |
58
+ | `ENTRY_CORRECTION_UNCERTAIN` | An attempted correction needs reconciliation; never resend it blindly |
59
+ | `MOBILE_ANSWER_EXISTS` | A scalar row already occupies the requested mobile effective instant |
60
+ | `MOBILE_PREFLIGHT_UNAVAILABLE` | The read cannot establish whether a scalar insert is safe |
61
+
62
+ The [workflow code table](../browser/workflow-error-codes.mjs) maps existing reasons to codes; new guards supply explicit codes. `committed: false` identifies a pre-write refusal for the named operation. `'unknown'` requires journal/readback reconciliation. Resolution errors separately retain `originalCommitted: 'unknown'` for the original correction. HTTP status and causes are exposed where captured; they are not proof of non-application.
63
+
64
+ Pending 0.5.0 corrections may have no saved `failure` kind or status. Keep your own original failure evidence for the confirmation callback. Do not repeat the write to recover missing diagnostics.
65
+
49
66
  ## Inspect an upstream error privately
50
67
 
51
68
  By default the transport discards HTTP error bodies because they can contain client data or credentials. For a controlled diagnostic session, construct `WellSkyClient` with `captureErrorBody: true`. An `UpstreamError` then exposes `responseBody` and `responseBodyTruncated`. The body is capped at 16,384 characters, retained in memory, and excluded from the error's enumerable properties, JSON serialization, and transport hooks/logger events. HTTP 401, 403, 429 and SetAuthKey authentication responses are excluded even with capture enabled. Raw bodies can still contain private information; inspect them through an access-controlled debugger or application diagnostic sink, not ordinary logs.
@@ -0,0 +1,13 @@
1
+ # Exact lookup write labels from the UI catalog
2
+
3
+ On November 11, 2026, read-only inspection of the training application's current GWT catalog established a way to discover full lookup labels before an answer exists.
4
+
5
+ REST assessment option metadata can omit `(HUD)` from `name`. The current UI's `SpPicklistValue.name` retains it. A mapped query returns `picklistValueId`, `picklistId`, `code` and `name` together. HUD and non-HUD enhanced yes/no choices are separate catalog records. `show_on_hud_nonhud_site` and trailing whitespace are not reliable substitutes for those identities.
6
+
7
+ The read used the application's observed `MetaQueryService` / `GenericService.getMappedResults` wire context and selected only explicit option IDs already enumerated by REST metadata. The [browser reader](../../browser/playwright-session.mjs) implements the same query with bounded batches and counted result validation. The [public resolver contract](../../dist/esm/picklist-labels.d.ts) joins IDs and codes while retaining the question's allowed choices.
8
+
9
+ Evidence: Coordinated Entry definitions for three synthetic clients matched all 1,288 distinct option IDs (4,089 question/option pairs). Full names differed from REST metadata for 139 occurrences in a definition. Disabling condition, domestic violence, non-cash benefits, health insurance and income-source questions shared the enhanced yes/no list. Living-situation and homelessness-history options were included. The visible profile DOB-quality dropdown matched its full catalog names, including all displayed options rather than only the selected answer.
10
+
11
+ No enrollment or assessment was saved during this inspection. No new REST writes were made to test acceptance of every returned label. Private captures, client identifiers and tenant-specific catalogs are excluded from the package. This internal GWT route is not part of the published REST Swagger; schema, permission, count or identity failures must stop discovery. Explicit application callbacks remain available for deployments without the catalog reader.
12
+
13
+ See [assessment integration](../assessments.md#resolve-lookup-labels-before-an-answer-exists) for usage.
@@ -15,6 +15,9 @@ Every file here ships with the package and imports the public `@choiceapps/commu
15
15
  | [program-discovery.ts](program-discovery.ts) | Program and VA-entry assessment discovery | Exported function; authenticated page required |
16
16
  | [enrollment-workflow.mts](enrollment-workflow.mts) | Single entry and interim assessment plans | Exported function; caller supplies plans and dependencies |
17
17
  | [individual-exit-workflow.mts](individual-exit-workflow.mts) | Standalone program enrollment exit and assessment | Verified entry key, enrollment reader, exit UI port and durable store required |
18
+ | [catalog-write-labels.mts](catalog-write-labels.mts) | Discover full lookup labels without existing answers; automatic workflow configuration | Authenticated enrollment session and current assessment definition required |
19
+ | [checked-assessment-writes.mts](checked-assessment-writes.mts) | Exact readback lookup labels and checked mobile scalar inserts | Unobserved labels remain unresolved; preflight does not make writes atomic |
20
+ | [enrollment-discovery-and-correction.mts](enrollment-discovery-and-correction.mts) | Discover/adopt from the head and bind existing correction rows | Explicit row IDs and unique verified household membership required |
18
21
  | [entry-correction-workflow.mts](entry-correction-workflow.mts) | Same-date scalar clears and existing row edits | Original entry/row keys, verified lookup labels and durable store required |
19
22
  | [existing-enrollment-exits.mts](existing-enrollment-exits.mts) | Adopt manual individual or household entries, then exit | Explicit enrollment identities and original household head required |
20
23
  | [household-enrollment-workflow.mts](household-enrollment-workflow.mts) | Per-member enrollment and review | Existing household and per-member answer plans required |
@@ -0,0 +1,15 @@
1
+ import {createCatalogWriteLabelResolver,flattenQuestions,type WellSkyClient} from '@choiceapps/communityservices-sdk';
2
+ import {createEnrollmentWorkflow,type EnrollmentCatalogReader,type EnrollmentWorkflowOptions} from '@choiceapps/communityservices-sdk/browser';
3
+
4
+ /** Standalone encoders can discover exact labels before any option has been answered. */
5
+ export async function discoverLabels(api:WellSkyClient,enrollmentSession:EnrollmentCatalogReader,input:{clientId:number;assessmentCode:string}){
6
+ const definition=await api.assessments.getDefinition(input);
7
+ const optionIds=[...new Set(flattenQuestions(definition.questions).filter(({question})=>question.questionType==='lookup').flatMap(({question})=>question.options.map(o=>o.id)))];
8
+ const catalog=await enrollmentSession.readPicklistOptions({clientId:input.clientId,optionIds});
9
+ return {definition,writeLabel:createCatalogWriteLabelResolver(definition,catalog)};
10
+ }
11
+
12
+ /** Built-in enrollment sessions discover labels automatically when writeLabel is omitted. */
13
+ export function automaticWorkflow(options:Omit<EnrollmentWorkflowOptions,'writeLabel'|'yesNoWrites'|'enrollmentSession'>&{enrollmentSession:EnrollmentCatalogReader}){
14
+ return createEnrollmentWorkflow({...options,yesNoWrites:'standard'});
15
+ }
@@ -0,0 +1,14 @@
1
+ import {
2
+ createReadBackWriteLabelResolver,writeMobileAnswersChecked,
3
+ type AssessmentDefinition,type WriteLabelObservation,type CheckedMobileWriteOptions,type WellSkyClient,
4
+ } from '@choiceapps/communityservices-sdk';
5
+
6
+ /** Observations must come from the definition's tenant. Unobserved options stay unresolved. */
7
+ export function exactWriteLabels(definition:AssessmentDefinition,observations:WriteLabelObservation[]){
8
+ return createReadBackWriteLabelResolver(definition,observations);
9
+ }
10
+
11
+ /** Pass the original requested instant; the SDK transport owns any configured mobile date correction. */
12
+ export async function insertMobileAnswers(api:WellSkyClient,input:CheckedMobileWriteOptions){
13
+ return writeMobileAnswersChecked(api,input);
14
+ }
@@ -0,0 +1,19 @@
1
+ import {createEnrollmentWorkflow,type EnrollmentWorkflowOptions,type EntryCorrectionInput} from '@choiceapps/communityservices-sdk/browser';
2
+
3
+ /** The head's explicit target identifies a cohort; ambiguous household matches fail. */
4
+ export async function adoptFromHead(dependencies:EnrollmentWorkflowOptions,input:{key:string;headClientId:number;headEnrollmentId:number}){
5
+ const workflow=createEnrollmentWorkflow(dependencies);
6
+ const discovery=await workflow.discoverHouseholdEnrollment({headClientId:input.headClientId,headEnrollmentId:input.headEnrollmentId});
7
+ const adopted=await workflow.adoptHouseholdEnrollment(input);
8
+ return {discovery,adopted};
9
+ }
10
+
11
+ /** Select IDs from an authorized assessment read. The SDK never guesses a row by date/value. */
12
+ export async function bindAndCorrectManualRows(
13
+ dependencies:EnrollmentWorkflowOptions,
14
+ input:{registrationKey:string;correction:EntryCorrectionInput;rows:Array<{key:string;parentField:string;recordsetId:number}>},
15
+ ){
16
+ const workflow=createEnrollmentWorkflow(dependencies),{clientId,enrollmentKey}=input.correction;
17
+ await workflow.registerEntryRows({key:input.registrationKey,clientId,enrollmentKey,rows:input.rows});
18
+ return workflow.correctEntry(input.correction);
19
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@choiceapps/communityservices-sdk",
3
- "version": "0.6.0",
3
+ "version": "0.8.0",
4
4
  "description": "SDK for WellSky Community Services (HMIS), formerly ServicePoint, Client API V2 and V3",
5
5
  "type": "module",
6
6
  "main": "./dist/cjs/index.js",