@alvera-ai/platform-sdk 0.10.0-rc.2 → 0.10.0-rc.20

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 (55) hide show
  1. package/.agent/AGENTS.md +440 -0
  2. package/.agent/account_management.md +455 -0
  3. package/.agent/action_status_updaters.md +262 -0
  4. package/.agent/ai_agents.md +423 -0
  5. package/.agent/ai_sandbox.md +265 -0
  6. package/.agent/async.md +111 -0
  7. package/.agent/connected_apps.md +402 -0
  8. package/.agent/cookbook/_fixtures/README.md +99 -0
  9. package/.agent/cookbook/_fixtures/accounts_receivable/_customers_accounts_receivable_customer.liquid +32 -0
  10. package/.agent/cookbook/_fixtures/accounts_receivable/_customers_accounts_receivable_mdm.liquid +20 -0
  11. package/.agent/cookbook/_fixtures/foundation/_lead_submissions_foundation_generic_table.liquid +33 -0
  12. package/.agent/cookbook/_fixtures/foundation/_lead_submissions_foundation_legal_entity.liquid +88 -0
  13. package/.agent/cookbook/_fixtures/foundation/_lead_submissions_foundation_mdm.liquid +48 -0
  14. package/.agent/cookbook/_fixtures/healthcare/_cahps_appointments_healthcare_appointment.liquid +47 -0
  15. package/.agent/cookbook/_fixtures/healthcare/_cahps_appointments_healthcare_mdm.liquid +24 -0
  16. package/.agent/cookbook/_fixtures/healthcare/_cahps_appointments_healthcare_patient.liquid +38 -0
  17. package/.agent/cookbook/_fixtures/payment_risk/_compliance_screenings_payment_risk_compliance_screening.liquid +59 -0
  18. package/.agent/cookbook/_fixtures/payment_risk/_compliance_screenings_payment_risk_mdm.liquid +36 -0
  19. package/.agent/cookbook/_fixtures/payment_risk/_payment_accounts_payment_risk_mdm.liquid +30 -0
  20. package/.agent/cookbook/_fixtures/payment_risk/_payment_accounts_payment_risk_payment_account.liquid +55 -0
  21. package/.agent/cookbook/_setup/accounts_receivable.md +282 -0
  22. package/.agent/cookbook/_setup/foundation.md +277 -0
  23. package/.agent/cookbook/_setup/healthcare.md +279 -0
  24. package/.agent/cookbook/_setup/payment_risk.md +283 -0
  25. package/.agent/cookbook/appointment-review-sms-workflow.md +760 -0
  26. package/.agent/cookbook/birthday-greeting-sms-trigger.md +655 -0
  27. package/.agent/cookbook/contact-us-triage-with-llm.md +603 -0
  28. package/.agent/cookbook/dunning-sms-for-delinquent.md +619 -0
  29. package/.agent/cookbook/kyc-notification-on-account-activation.md +619 -0
  30. package/.agent/cookbook/sanctions-screening-with-agent-review.md +711 -0
  31. package/.agent/cookbook/score-leads-with-llm-categorization.md +602 -0
  32. package/.agent/cookbook/welcome-sms-for-customers.md +607 -0
  33. package/.agent/data_activation_clients.md +557 -0
  34. package/.agent/data_sources.md +234 -0
  35. package/.agent/datalakes.md +712 -0
  36. package/.agent/debugging.md +137 -0
  37. package/.agent/errors.md +196 -0
  38. package/.agent/generic_tables.md +351 -0
  39. package/.agent/interoperability_contracts.md +351 -0
  40. package/.agent/mdm.md +293 -0
  41. package/.agent/mutations.md +152 -0
  42. package/.agent/templates.md +98 -0
  43. package/.agent/tool-call-configs.md +90 -0
  44. package/.agent/tools.md +547 -0
  45. package/.agent/type_naming.md +131 -0
  46. package/.agent/workflows.md +601 -0
  47. package/README.md +46 -0
  48. package/dist/bin/platform-sdk.d.mts +1 -0
  49. package/dist/bin/platform-sdk.mjs +106 -0
  50. package/dist/bin/platform-sdk.mjs.map +1 -0
  51. package/dist/index.d.mts +633 -43145
  52. package/dist/index.d.mts.map +1 -1
  53. package/dist/index.mjs +1405 -7031
  54. package/dist/index.mjs.map +1 -1
  55. package/package.json +19 -10
@@ -0,0 +1,440 @@
1
+ # AGENTS.md — `@alvera-ai/platform-sdk` corpus
2
+
3
+ This corpus documents the platform's resource surface. It ships
4
+ inside the npm package and is read directly via `node_modules`
5
+ filesystem walk. Consumers include downstream agents writing
6
+ code in CLIs, React apps, integration test suites, and code
7
+ generators.
8
+
9
+ > **Wiring this into your project:** run `npx @alvera-ai/platform-sdk
10
+ > llm-export` from your project root to add a pointer to this corpus in
11
+ > your `AGENTS.md` (and a `@AGENTS.md` import in `CLAUDE.md`). Idempotent.
12
+
13
+ ## ⚠ This file is a LOOKUP ONLY. Read the per-kind MD before emitting code.
14
+
15
+ `AGENTS.md` (this file) is a **DIRECTORY**. It points you to
16
+ per-kind MDs (`tools.md`, `workflows.md`, `data_sources.md`,
17
+ etc.). It is **NOT** a schema reference, and you **CANNOT**
18
+ ground any wire-level claim from this file alone.
19
+
20
+ ### Mandatory protocol before emitting any code that touches a kind
21
+
22
+ 1. **Locate the kind** in the Resources section below.
23
+ 2. **Open the kind's MD** (`<kind>.md`).
24
+ 3. **Read §1 (Wire shape) and §2 (Rules the type cannot encode)**
25
+ at minimum.
26
+ 4. **Every claim you emit** about wire shape, polymorphism,
27
+ constraints, enum values, or required fields **MUST be
28
+ grounded in §1 or §2 of the kind's MD**. If you cannot
29
+ point at the section, re-read before emitting.
30
+
31
+ ### Failure mode this prevents
32
+
33
+ The one-line summaries in the Resources section are
34
+ **navigation labels**, not schemas. Example:
35
+
36
+ > The index line `workflows.md — Filter + decision + action;
37
+ > standard + agent-driven variants` **cannot** tell you whether
38
+ > `variant` is a body field. It is not. `workflows.md` §1 shows
39
+ > the variant is **structural** — via the presence of the
40
+ > `ai_agents[]` array on the request body — **not** a top-level
41
+ > discriminator.
42
+
43
+ An agent that paraphrases the index into a wire-level claim is
44
+ **hallucinating**. The index summary is too compressed to encode
45
+ polymorphism, constraints, enum values, or required fields.
46
+
47
+ ### Rule of thumb
48
+
49
+ Treat index summaries like file names in a directory listing:
50
+ useful for finding the right file, useless as a substitute for
51
+ opening it. If you find yourself emitting a claim about a kind's
52
+ wire shape without having opened that kind's MD in the current
53
+ session, **stop and open it now**.
54
+
55
+ ---
56
+
57
+ ## If you know MCP — vocabulary mapping
58
+
59
+ The platform's resources don't implement MCP wire format, but
60
+ the conceptual roles map cleanly onto MCP terms. If you've
61
+ landed here from an MCP background, this table tells you which
62
+ corpus page documents the surface you already know:
63
+
64
+ | MCP concept | Alvera resource | Corpus page |
65
+ |-----------------------|------------------------------|-----------------------------------------------|
66
+ | Host | The platform itself | This corpus + `account_management.md` |
67
+ | Client | Data Activation Client | `data_activation_clients.md` |
68
+ | Server | Agentic Workflow | `workflows.md` |
69
+ | Resources | Datasets (per data domain) | `generic_tables.md`, `mdm.md` |
70
+ | Prompts | Interoperability Contracts | `interoperability_contracts.md`, `templates.md` |
71
+ | Sampling | Dataset event triggers | `data_activation_clients.md` §6.4 logs |
72
+ | Tools | Tool Calls (polymorphic body)| `tools.md` |
73
+ | Roots | Datalake boundaries | `datalakes.md` |
74
+ | Human-in-the-loop | Connected Apps + page tokens | `connected_apps.md` |
75
+ | Sandboxed execution | Liquid + SQL fragment lanes | `ai_sandbox.md` |
76
+
77
+ A few caveats on the mapping:
78
+
79
+ - **Capability negotiation is configuration, not runtime
80
+ handshake.** Tools declare their `intent` and `tool_body_type`
81
+ at create time; data activation clients declare their bound
82
+ contracts and tool call shape; workflows declare their event
83
+ dataset and decision keys. The platform doesn't negotiate
84
+ these per session — they're fixed by the resource records.
85
+ - **Sampling is lossless, not subscribed.** Dataset events
86
+ surface through database triggers and per-tenant work queues
87
+ rather than an MCP-style sampling subscription. Consumers
88
+ observe outcomes through the log subresource on the data
89
+ activation client or the workflow, not through a streaming
90
+ channel.
91
+ - **Roots are the tenancy boundary, full stop.** Cross-datalake
92
+ reads and writes are physically blocked at the connection
93
+ layer; a session scoped to one datalake can never reach
94
+ another tenant's data even if the calling code addresses a
95
+ different slug.
96
+
97
+ The rest of this preamble covers the actual SDK auth and client
98
+ construction.
99
+
100
+ ## SDK auth + client construction
101
+
102
+ The SDK is a strict-TypeScript REST client for the Alvera
103
+ platform. Two-step auth + client construction:
104
+
105
+ import {
106
+ createSession,
107
+ createIsolatedPlatformApi,
108
+ type PlatformApi,
109
+ } from '@alvera-ai/platform-sdk'
110
+
111
+ // 1. mint a session
112
+ const session = await createSession({
113
+ baseUrl,
114
+ email,
115
+ password,
116
+ tenantSlug, // optional; omit for tenantless session
117
+ })
118
+
119
+ // 2. build a typed client
120
+ const api: PlatformApi = createIsolatedPlatformApi({
121
+ baseUrl,
122
+ sessionToken: session.sessionToken,
123
+ })
124
+
125
+ Sessions come in three scopes:
126
+ - **root** — Alvera root admin; user signup + confirmation
127
+ - **tenantless** — authenticated user with no tenant chosen yet;
128
+ used once to create a tenant via `api.tenants.create(...)`
129
+ - **tenant-scoped** — the canonical Bearer for tenant operations
130
+
131
+ Consumers holding multiple concurrent clients in the same file
132
+ (e.g. integration tests that need root + tenantless + tenant-
133
+ scoped APIs concurrently) MUST use `createIsolatedPlatformApi`
134
+ rather than the singleton `createPlatformApi`. The singleton
135
+ mutates a shared client; the most-recent construction call
136
+ clobbers earlier instances' auth.
137
+
138
+ ## Resources
139
+
140
+ ```
141
+ INFRASTRUCTURE (stood up before data flows)
142
+ ──────────────
143
+ datalakes.md Storage layer; regulated +
144
+ unregulated tiers.
145
+ data_sources.md External ingestion endpoints.
146
+ tools.md Authenticated connections for
147
+ action execution.
148
+ templates.md Platform-shipped Liquid templates
149
+ consumed by other resources.
150
+ ai_agents.md LLM-backed workers.
151
+ action_status_updaters.md Polling reconcilers.
152
+ connected_apps.md External app bridges.
153
+ mdm.md Entity resolution + identity
154
+ verification across domains.
155
+ ai_sandbox.md Three-layer safety contract
156
+ bounding every AI-authored
157
+ fragment: compliance gates,
158
+ Liquid sandbox, SQL boundary.
159
+
160
+ DATA ACTIVATION (how data flows in)
161
+ ───────────────
162
+ generic_tables.md Schema-on-write tables.
163
+ interoperability_contracts.md Liquid template mappings.
164
+ data_activation_clients.md Ingestion pipelines — both the
165
+ binding row (CRUD) and the
166
+ runtime verbs (.ingest, .ingestFile,
167
+ .runManually, logs, dataset search).
168
+
169
+ WORKFLOWS (composition on top)
170
+ ─────────
171
+ workflows.md Filter + decision + action;
172
+ standard + agent-driven variants.
173
+ ```
174
+
175
+ Axes are mostly disjoint but a few resources cross. AI Agents
176
+ anchor INFRASTRUCTURE and appear in DATA ACTIVATION (they
177
+ tokenize during ingestion) and WORKFLOWS (workflows invoke
178
+ them). Connected Apps anchor INFRASTRUCTURE and tag into
179
+ WORKFLOWS.
180
+
181
+ Each resource is exposed via a TypeScript namespace on the
182
+ typed client. The SDK namespace name follows TS conventions
183
+ (camelCase); the corpus MD filename matches the platform's
184
+ wire name (snake_case):
185
+
186
+ SDK namespace Corpus MD
187
+ ──────────────────── ───────────────────────────
188
+ api.datalakes datalakes.md
189
+ api.dataSources data_sources.md
190
+ api.tools tools.md
191
+ api.aiAgents ai_agents.md
192
+ api.actionStatusUpdaters action_status_updaters.md
193
+ api.connectedApps connected_apps.md
194
+ api.mdm mdm.md
195
+ api.genericTables generic_tables.md
196
+ api.interoperabilityContracts interoperability_contracts.md
197
+ api.dataActivationClients data_activation_clients.md
198
+ api.workflows workflows.md
199
+
200
+ ## Cookbooks (Golden Path scenarios)
201
+
202
+ `.agent/cookbook/<slug>.md` files are pure-markdown scenario
203
+ stories — one business outcome each, told as a numbered API-call
204
+ sequence drawn from a green end-to-end vitest scenario in the
205
+ platform's integration-tests suite. Agents read these directly
206
+ from the filesystem at
207
+ `node_modules/@alvera-ai/platform-sdk/.agent/cookbook/`; there
208
+ is no SDK function and no CLI verb that returns cookbook bytes
209
+ at runtime.
210
+
211
+ Each cookbook structure:
212
+
213
+ - **Front matter** — `title`, `summary`, `industry`, `slug`,
214
+ `vitest_source` (list of integration-test files the snippets
215
+ are lifted from, anchor file first), `status`. The `industry:`
216
+ field also drives automatic discovery of the per-industry
217
+ bootstrap setup file at `_setup/<industry>.md` — see
218
+ "Industry-derived setup files" below.
219
+ - **Problem** — the business-outcome statement in domain terms,
220
+ sourced from the anchor vitest's behaviour (not from
221
+ customer-narrative documentation).
222
+ - **Composition** — table of resources provisioned, mapped to
223
+ the audience skill that owns each (setup / build / compose).
224
+ - **Walkthrough** — numbered `## NNN — <step>` subsections, each
225
+ carrying one fenced TypeScript block lifted from the anchor or
226
+ ancillary vitest. State threads forward across subsections via
227
+ plain JavaScript variables in the validator's generated
228
+ `describe` closure.
229
+ - **Branches** — alternate paths the anchor vitest covers (a
230
+ filter rejection, a transport failure, an agent-classification
231
+ fallback).
232
+ - **Rollback** — teardown order for the resources the cookbook
233
+ provisioned.
234
+ - **Outcome** — author's prose summary of what the scenario
235
+ produces.
236
+ - **See also** — links to relevant per-resource reference MDs
237
+ and to the anchor + ancillary vitest files.
238
+
239
+ ### Industry-derived setup files
240
+
241
+ Cookbook authoring uses per-industry bootstrap setup files
242
+ under `.agent/cookbook/_setup/<industry>.md` to DRY out the
243
+ auth + tenant + datalake + dataset-seeding steps every
244
+ scenario in an industry shares. The convention matches the
245
+ markdown-doctest ecosystem pattern — setup belongs to a
246
+ scope (the industry), and any cookbook in that scope
247
+ inherits the setup by being there. No per-cookbook opt-in
248
+ field is required.
249
+
250
+ - Setup files live under `_setup/`. The leading underscore on
251
+ the directory marks them as fragments (not standalone
252
+ scenarios); the validator's discovery walk skips them and
253
+ the corpus index does not list them under "Available
254
+ cookbooks."
255
+ - The validator reads each scenario cookbook's existing
256
+ `industry:` front-matter field and auto-discovers the
257
+ setup file at `_setup/<industry>.md`. A cookbook in an
258
+ industry that has no setup file gets nothing inlined; the
259
+ validator does not fail if the setup file is absent.
260
+ Cookbook authors write no additional front-matter for
261
+ setup inclusion — the relationship is implicit, by
262
+ convention.
263
+ - The validator inlines the setup file's numbered `it()`
264
+ blocks BEFORE the scenario's own numbered `it()` blocks
265
+ inside the same `describe(...)`. Each `it()` label is
266
+ prefixed with its source slug so failure output
267
+ unambiguously points at the file to open (e.g.
268
+ `_setup/foundation §001 — auth` versus
269
+ `birthday-greeting-sms-trigger §001 — create workflow`).
270
+ Cookbook authors keep clean local §001-§00N numbering
271
+ inside their own markdown.
272
+ - An agent reading a scenario cookbook discovers the matching
273
+ setup file at the predictable conventional path
274
+ `_setup/<industry>.md` — one extra file open at a
275
+ fixed-by-convention location, not via cookbook-specific
276
+ metadata the agent has to learn.
277
+
278
+ ### Vendored fixtures (Liquid templates, CSV bodies)
279
+
280
+ End-to-end cookbooks walk the full data-activation chain
281
+ (data source → interoperability contract → Data Activation
282
+ Client → CSV ingest), which requires Liquid templates that
283
+ map inbound rows into the platform's upsert shapes. Inlining
284
+ those templates verbatim inside every cookbook markdown
285
+ bloats each recipe to several hundred lines of Liquid noise
286
+ that obscures the cookbook's lesson. The corpus ships them
287
+ as **vendored fixtures** instead — same pattern the platform
288
+ repo's own integration tests use.
289
+
290
+ - Fixtures live under
291
+ `.agent/cookbook/_fixtures/<industry>/`. The leading
292
+ underscore marks the directory as fragment infrastructure
293
+ (not a cookbook); the validator's discovery walk skips it.
294
+ Each `<industry>/` subdirectory carries a `README.md`
295
+ listing every template + which cookbook consumes it + the
296
+ production source-of-truth path it was vendored from
297
+ (`platform/priv/liquid_templates/…`).
298
+ - Cookbook code loads fixtures via the
299
+ `COOKBOOK_FIXTURES_DIR` environment variable:
300
+ ```typescript
301
+ import fs from 'node:fs'
302
+ import path from 'node:path'
303
+
304
+ const LE_TEMPLATE = fs.readFileSync(
305
+ path.join(
306
+ process.env.COOKBOOK_FIXTURES_DIR!,
307
+ 'foundation/_lead_submissions_foundation_legal_entity.liquid',
308
+ ),
309
+ 'utf8',
310
+ )
311
+ ```
312
+ Same env-var pattern as the `ALVERA_BASE_URL` /
313
+ `ALVERA_ROOT_EMAIL` / `ALVERA_ROOT_PASSWORD` credentials
314
+ the generated specs already consume.
315
+ `make validate-cookbook` exports
316
+ `COOKBOOK_FIXTURES_DIR=<absolute path to _fixtures>` to the
317
+ spawned `bun test` process; any operator running cookbook
318
+ code directly from a Bun script must set the same variable.
319
+ - The fixtures ship with the `@alvera-ai/platform-sdk` npm
320
+ package via the `files: [.agent]` field, so a consumer
321
+ who installed the package finds the templates at
322
+ `node_modules/@alvera-ai/platform-sdk/.agent/cookbook/_fixtures/`.
323
+ - An agent reading the corpus discovers vendored templates by
324
+ directory walk:
325
+ `_fixtures/` → `<industry>/` → `README.md` lists every
326
+ template plus the cookbooks that depend on it. No need to
327
+ read cookbook code first to know what fixtures exist.
328
+
329
+ ### Available cookbooks
330
+
331
+ The eight cookbook scenarios shipped at v0.10. Each is anchored
332
+ to a green end-to-end vitest scenario in the platform's
333
+ integration-tests suite and is verified at dev time by
334
+ `make validate-cookbook` at the platform-sdk repo root (not a
335
+ CLI verb). The entries between the managed markers below are
336
+ what Milestone 12's `alvera-sdk-init` lifts into consumer
337
+ `<cwd>/AGENTS.md` files as a managed block.
338
+
339
+ <!-- BEGIN:cookbook-index -->
340
+
341
+ **Healthcare**
342
+
343
+ - [appointment-review-sms-workflow](./cookbook/appointment-review-sms-workflow.md)
344
+ — Send a patient review-request SMS after a fulfilled
345
+ appointment, deep-linked to a connected-app feedback form.
346
+ - [contact-us-triage-with-llm](./cookbook/contact-us-triage-with-llm.md)
347
+ — Triage inbound contact-us messages into three priority
348
+ buckets (appointment / job-application / spam) via an LLM
349
+ agent, route each to a tailored SMS action.
350
+
351
+ **Accounts Receivable**
352
+
353
+ - [welcome-sms-for-customers](./cookbook/welcome-sms-for-customers.md)
354
+ — Send a welcome SMS to newly contracted customers with a
355
+ self-serve billing-portal link.
356
+ - [dunning-sms-for-delinquent](./cookbook/dunning-sms-for-delinquent.md)
357
+ — Send a payment-reminder SMS to delinquent customers
358
+ (filtered on phone-on-file and tax-id-verified) with a
359
+ pay-invoice link.
360
+
361
+ **Payment Risk**
362
+
363
+ - [kyc-notification-on-account-activation](./cookbook/kyc-notification-on-account-activation.md)
364
+ — Send a KYC-notification SMS when a payment account
365
+ transitions to active status.
366
+ - [sanctions-screening-with-agent-review](./cookbook/sanctions-screening-with-agent-review.md)
367
+ — Disambiguate gray-zone sanctions screenings via an LLM
368
+ agent, route confirmed-clean and confirmed-block outcomes to
369
+ distinct SMS actions.
370
+
371
+ **Foundation**
372
+
373
+ - [birthday-greeting-sms-trigger](./cookbook/birthday-greeting-sms-trigger.md)
374
+ — Send a happy-birthday SMS on each contact's next birthday
375
+ using a pure-Liquid trigger (year-roll math).
376
+ - [score-leads-with-llm-categorization](./cookbook/score-leads-with-llm-categorization.md)
377
+ — Score inbound leads into four bands
378
+ (hot / warm / cold / spam) via an LLM agent, route each band
379
+ to a tailored SMS action.
380
+
381
+ <!-- END:cookbook-index -->
382
+
383
+ ## Utility namespaces
384
+
385
+ Cross-resource utilities not documented in this corpus:
386
+
387
+ api.auth signUp + auth helpers
388
+ api.admin confirmUser + admin actions
389
+ api.tenants create + tenant lifecycle
390
+ api.datasets cross-resource search / metadata /
391
+ user-saved searches
392
+ api.templates system-template discovery + per-tenant
393
+ metadata
394
+
395
+ ## Resource page structure
396
+
397
+ Each per-resource MD (e.g. `tools.md`) follows a consistent
398
+ 6-section structure: Wire shape, Rules the type cannot encode,
399
+ Field ownership, Error envelopes, Lifecycle, Gotchas.
400
+
401
+ ## Common sections (apply to all resources)
402
+
403
+ These sibling files cover patterns that apply uniformly across
404
+ all resources. Per-resource MDs reference them by name instead
405
+ of restating:
406
+
407
+ | Topic | File |
408
+ |-----------------------------------------------|------------------|
409
+ | Error envelope shape | `errors.md` |
410
+ | Mutations (PUT and limited exceptions) | `mutations.md` |
411
+ | Async + readiness | `async.md` |
412
+ | Debugging (HTTP interceptor + safe redaction) | `debugging.md` |
413
+ | Type naming + server-derived fields | `type_naming.md` |
414
+
415
+ ## Just getting started?
416
+
417
+ In your consumer repo, run:
418
+
419
+ ```
420
+ npx @alvera-ai/platform-sdk llm-export
421
+ ```
422
+
423
+ This writes a managed block into your `AGENTS.md` pointing back
424
+ at this corpus, and an `@AGENTS.md` import into `CLAUDE.md` for
425
+ Claude Code compatibility. Re-running replaces only the managed
426
+ block — content outside the markers is preserved.
427
+
428
+ ## Base path
429
+
430
+ All resources are scoped under a tenant + datalake:
431
+
432
+ ```
433
+ /api/v1/tenants/{tenant_slug}/datalakes/{datalake_slug}/<resource>
434
+ ```
435
+
436
+ The `{tenant_slug}` comes from your authenticated session's
437
+ tenant; the `{datalake_slug}` comes from the response of
438
+ `api.datalakes.create(tenantSlug, body)` (server-derived; never
439
+ pre-compute client-side — see `type_naming.md` "Server-derived
440
+ fields auto-excluded from Writable").