@objectstack/types 17.2.0 → 17.4.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/dist/index.d.mts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { TenancyPosture } from '@objectstack/spec/security';
2
2
  import { ErrorCode, ApiError, FieldErrorCode } from '@objectstack/spec/api';
3
+ import { Logger } from '@objectstack/spec/contracts';
3
4
 
4
5
  /**
5
6
  * Degraded-boot reporting, shared by every subsystem that can be told to boot
@@ -44,6 +45,47 @@ import { ErrorCode, ApiError, FieldErrorCode } from '@objectstack/spec/api';
44
45
  */
45
46
  declare function emitDegradedBootBanner(message: string): void;
46
47
 
48
+ /**
49
+ * [#11343 / #12751] Verified-email predicate over a stored `sys_user` row — a
50
+ * fail-closed ALLOW-LIST over the representations a driver may hand back for
51
+ * the `sys_user.email_verified` boolean column (JS `true`, SQLite `1`, and
52
+ * their stringified forms). Everything else — `false`/`0`, `null`, an ABSENT
53
+ * field on an imported/legacy row, or any representation not listed — reads
54
+ * as UNVERIFIED. Absent-means-unverified is deliberate: treating a missing
55
+ * column as verified would re-open the exact hole this predicate closes for
56
+ * every row that predates the column.
57
+ *
58
+ * ONE resolution, several consumers, by design (#12751) — and since the
59
+ * #11663 platform-admin re-anchor (leg L4) the walled platform-admin
60
+ * ELEVATION GATE this paragraph used to name first is RETIRED: under a
61
+ * walled posture `bootstrapPlatformAdmin` writes no grant row and elevates
62
+ * nobody, it reports. Standing is derived PER REQUEST instead — from a
63
+ * config-anchored verified email, or the legacy unscoped grant row — so the
64
+ * consumer set now includes the authorization derivation itself:
65
+ *
66
+ * - `matchesConfiguredPlatformAdmin` (`@objectstack/core`
67
+ * `security/platform-admin.ts`), read at the one derivation site
68
+ * (`resolve-authz-context.ts` §6b-config), where an UNVERIFIED account
69
+ * holding a declared address confers nothing — and, through it,
70
+ * `plugin-auth`'s last-admin guard, whose administrator enumeration must
71
+ * answer the same question the resolver does;
72
+ * - `resolvePlatformAdminStanding` (`plugin-security`
73
+ * `platform-admin-service.ts`), the read-only standing/audit answer the
74
+ * walled boot reports from, and `isVerifiedPlatformOwnerRow` beside it
75
+ * (`platform-owner-wall-bypass.ts`), the Layer 0 wall bypass;
76
+ * - the walled owner-verification boot diagnostic (`plugin-auth`
77
+ * `walled-owner-verification-path.ts`, where the check decides whether the
78
+ * declared owner's account is already past needing a verification path).
79
+ *
80
+ * They must all answer "is this row verified?" identically — a drift is no
81
+ * longer just a boot warning forecasting a refusal that will not be made, it
82
+ * is a diagnostic, an audit surface or a guard disagreeing with who actually
83
+ * resolves PLATFORM_ADMIN on the next request. `@objectstack/types` is the
84
+ * shared home every one of those packages already resolves
85
+ * `OS_PLATFORM_OWNER_EMAIL` from (`env.ts`).
86
+ */
87
+ declare function isEmailVerifiedUserRow(row: unknown): boolean;
88
+
47
89
  /**
48
90
  * Environment-variable helpers shared across `@objectstack/*` packages.
49
91
  *
@@ -142,6 +184,49 @@ declare function resolveMultiOrgEnabled(): boolean;
142
184
  * enforced is the `tenancy` service's answer (`isolationActive` / `degraded`).
143
185
  */
144
186
  declare function resolveTenancyPosture(): TenancyPosture;
187
+ /**
188
+ * The env variable naming the deployment's PLATFORM OWNER account
189
+ * (#11184, the framework leg of cloud#1509).
190
+ *
191
+ * Exported as a constant so every message that names it quotes exactly one
192
+ * spelling: the walled boot guard in plugin-auth, and plugin-security's
193
+ * `bootstrapPlatformAdmin` — its fail-closed backstop for an undeclared or
194
+ * refused config, and the config-derived standing it logs beside it.
195
+ *
196
+ * ⚠️ That second site is no longer an ELEVATION refusal. Since the #11663
197
+ * platform-admin re-anchor (leg L4) the walled `bootstrapPlatformAdmin` writes
198
+ * no grant row and elevates nobody — it reports. Standing is derived PER
199
+ * REQUEST at `resolve-authz-context.ts` §6b-config, from a declared address
200
+ * held on a VERIFIED `sys_user` row.
201
+ */
202
+ declare const PLATFORM_OWNER_EMAIL_ENV = "OS_PLATFORM_OWNER_EMAIL";
203
+ /**
204
+ * [#11184 / cloud#1509] Resolve the env-declared platform OWNER email —
205
+ * `OS_PLATFORM_OWNER_EMAIL`.
206
+ *
207
+ * Under a WALLED tenancy posture (`group` / `isolated`) the "first registrant
208
+ * becomes owner/platform admin" bootstrap path is REMOVED (maintainer ruling
209
+ * 2026-08-23, verbatim: 「1509 选择 env 指定 owner 邮箱」): on a walled
210
+ * deployment with self-registration reachable, whoever curls the sign-up
211
+ * endpoint first would otherwise receive the cross-tenant `admin_full_access`
212
+ * grant — measured on a real walled SaaS in cloud#1509. Platform admin is
213
+ * granted ONLY to the account whose email matches this variable, and a walled
214
+ * posture with no value declared REFUSES STARTUP (fail-closed, same reasoning
215
+ * as {@link resolveTenancyPosture}'s throw and ADR-0093 D5) rather than
216
+ * silently reverting to first-registrant elevation.
217
+ *
218
+ * The `single` posture never consults this: "first user is owner" is ruled
219
+ * reasonable there and unchanged.
220
+ *
221
+ * Returns the operator's value trimmed, or `undefined` when unset/blank.
222
+ * Comparison against `sys_user.email` is the CONSUMER's job and must be
223
+ * case-insensitive (this resolver echoes what the operator typed so refusal
224
+ * messages can quote it verbatim).
225
+ *
226
+ * Reads `process.env` live on each call, through `globalThis` like the other
227
+ * resolvers here (this package targets non-Node runtimes too).
228
+ */
229
+ declare function resolvePlatformOwnerEmail(): string | undefined;
145
230
  /**
146
231
  * Escape hatch for the degraded-tenancy boot guard (ADR-0093 D5).
147
232
  *
@@ -257,6 +342,27 @@ declare function resolveMcpStdioAutoStart(): {
257
342
  * Deployments that let users self-create orgs SHOULD set a generous cap.
258
343
  */
259
344
  declare function resolveOrgLimit(): number | undefined;
345
+ /**
346
+ * Maximum number of MEMBERS a single organization may hold, from
347
+ * `OS_ORG_MEMBERSHIP_LIMIT`. A different question from {@link resolveOrgLimit},
348
+ * which caps how many organizations one user may create.
349
+ *
350
+ * Unset → `undefined`, which the auth plugin forwards as "no cap". That default
351
+ * is a product decision, not an omission: seat entitlements are metered on AI
352
+ * seats, and plain membership is not a billed axis, so nothing about the
353
+ * platform wants a member ceiling.
354
+ *
355
+ * It has to be stated explicitly because better-auth's organization plugin
356
+ * substitutes a vendor default of **100** for an absent `membershipLimit`
357
+ * (`count >= (membershipLimit || 100)`), which reaches the operator as
358
+ * `Organization membership limit reached` — a refusal nobody in this codebase
359
+ * ever chose, on an axis the product does not limit.
360
+ *
361
+ * A deployment that DOES want a ceiling (a pilot, a trial tenant) sets a
362
+ * positive integer here. Non-positive or unparsable values read as unset rather
363
+ * than as zero: a typo must not be the thing that locks an organization.
364
+ */
365
+ declare function resolveOrgMembershipLimit(): number | undefined;
260
366
  /**
261
367
  * SINGLE decision point for "is pinyin search recall on?" (#2486).
262
368
  *
@@ -369,8 +475,10 @@ declare function _resetEnvDeprecationWarnings(): void;
369
475
  * "Do not ship driver internals to clients" is a property of the HTTP
370
476
  * boundary, not of one router, so the predicate lives here — the package both
371
477
  * `@objectstack/rest` and `@objectstack/runtime` already depend on — and each
372
- * boundary applies it in its own envelope. One heuristic, one place to widen
373
- * when a new dialect's phrasing shows up.
478
+ * boundary applies it in its own envelope. One heuristic, one place and
479
+ * since #16019 a FROZEN one: a phrasing it does not recognise is closed by the
480
+ * producer declaring its fault, never by a new row here (the ruling is
481
+ * recorded on {@link DIALECT_LEAK_PHRASINGS}).
374
482
  *
375
483
  * Deliberately a *heuristic over the message*, not a driver taxonomy: these
376
484
  * errors arrive as plain `Error`s from a half-dozen dialects with no shared
@@ -667,7 +775,8 @@ declare function sendOk(res: EnvelopeResponse, data: unknown, status?: number):
667
775
  * ## `extra` is `ApiError`'s own optional fields, not a `Record`
668
776
  *
669
777
  * Merged into `error`, and typed as exactly what `ApiErrorSchema` declares
670
- * beside `code` and `message` — `details`, `category`, `requestId`, `httpStatus`.
778
+ * beside `code` and `message` — `details`, `category`, `requestId`,
779
+ * `httpStatus`, `declaredCode`, `userMessage`.
671
780
  * `details` is the slot for structured context: `package-routes` puts a partial
672
781
  * delete's per-item failures there, `settings-routes` the whole
673
782
  * `SettingsActionResult`.
@@ -683,8 +792,80 @@ declare function sendOk(res: EnvelopeResponse, data: unknown, status?: number):
683
792
  * Closing it at the shared builder is the part that lasts: an undeclared sibling
684
793
  * is now a compile error in every module at once, rather than a key that quietly
685
794
  * evaporates at the schema boundary in whichever module reintroduces it.
795
+ *
796
+ * ## `declaredCode` — declared by the schema, barred by this writer
797
+ *
798
+ * ADR-0112's 2026-08-17 amendment (#9106, extended to the flat `/data` door by
799
+ * #9232) rules the demote at EVERY door: `code` stays the closed vocabulary,
800
+ * and a thrown code that is not a member is demoted to a declared sibling,
801
+ * `ApiError.declaredCode` — the open, author-authored channel that carries a
802
+ * metadata app's OWN `.code` across the QuickJS boundary (#7867) and onto the
803
+ * wire.
804
+ *
805
+ * `ApiErrorSchema` has declared that field since #9106 and the flat door emits
806
+ * it, but it was absent from the `Pick` above — so it was a COMPILE ERROR for
807
+ * any route answering the NESTED envelope to pass one, and every such route
808
+ * dropped the producer's spelling. Nothing invalid shipped (the closed `code`
809
+ * still carried the derived member), which is what made the loss silent and
810
+ * one-directional: the author's spelling gone, and a consumer told by the ADR
811
+ * to read `declaredCode` finding nothing there. Declared-but-unemittable is a
812
+ * `declared = enforced` gap, and admitting the field closes it at the ONE
813
+ * writer rather than in each module that later notices.
814
+ *
815
+ * ⛔ Presence MEANS demotion, and this writer does not re-derive that — the
816
+ * CALLER does, with `demotedDeclaredCode` (`thrown-http-error.ts`, one file
817
+ * over), exactly as the flat door's `thrownCodeFields` already does. That
818
+ * helper answers `undefined` when the producer's spelling IS the vocabulary
819
+ * member already sitting in `code`, which is what stops a registered refusal
820
+ * from carrying two spellings of one fact — `ApiErrorSchema.declaredCode`'s
821
+ * documented invariant. Passing a raw `thrown.declaredCode` re-opens exactly
822
+ * that, and no type here can catch it: vocabulary and position stay two
823
+ * decisions (#9232), so the demotion rule stays with the resolver that owns
824
+ * it rather than being restated in the envelope writer.
825
+ *
826
+ * ## `userMessage` — the second declared channel, and why this `Pick` stays explicit
827
+ *
828
+ * #9934's producer-side opt-in (maintainer ruling 2026-08-19 on objectui#5210,
829
+ * option 1) declares `ApiError.userMessage`: the text a producer marked, AT
830
+ * THROW TIME, as addressed to the END USER. Presence IS the marking — a
831
+ * consumer that sees the field renders it verbatim and keeps its generic
832
+ * substitution (#3821) for everything unmarked.
833
+ *
834
+ * The schema declared it and this writer barred it, with the same
835
+ * one-directional silence `declaredCode` had: the other two doors already emit
836
+ * it — the flat `/data` door through `withDeclaredUserMessage`
837
+ * (`rest/error-response.ts`) and the dispatcher door through
838
+ * `thrown.userMessage` (`runtime/http-dispatcher.ts`) — while a route
839
+ * answering the NESTED envelope could not, so an author's deliberate,
840
+ * localized refusal text was dropped on this door alone. Nothing invalid
841
+ * shipped; the text simply was not there.
842
+ *
843
+ * The channel is live on both ends, which is what makes admitting it a repair
844
+ * rather than a new declared-but-dead surface: a hook sets it at throw time —
845
+ * host-side, or a metadata app's sandboxed body whose `e.userMessage` crosses
846
+ * the QuickJS boundary through `SANDBOX_ERROR_PASSTHROUGH`
847
+ * (`runtime/sandbox/quickjs-runner.ts`) — and `resolveThrownHttpError` already
848
+ * carries it onto `ThrownHttpError` for every caller of the shared resolver.
849
+ *
850
+ * ⛔ Unlike `declaredCode`, this field carries NO invariant for the caller to
851
+ * re-derive. `declaredCode`'s presence MEANS demotion, so its caller passes
852
+ * `demotedDeclaredCode(thrown)` rather than the raw field; `userMessage`'s
853
+ * presence means only that the producer opted in, and `declaredUserMessage`
854
+ * has already decided that (a non-empty string, or nothing at all). The caller
855
+ * passes `thrown.userMessage` straight through, exactly as the dispatcher door
856
+ * does.
857
+ *
858
+ * That difference is why `extra` stays an explicit `Pick` rather than becoming
859
+ * "every optional field of `ApiError`". A derivation would admit each future
860
+ * optional on the day it lands, with nobody asked whether that channel should
861
+ * cross this door or what obligation it hands the caller — and the two fields
862
+ * above needed opposite answers to exactly that question. Recorded for the next
863
+ * reader, because it is the honest cost: with `userMessage` admitted the `Pick`
864
+ * now names ALL SIX of `ApiError`'s optional fields, so this gate has to date
865
+ * rejected none. What it has produced is a different caller obligation per
866
+ * field, which a derivation cannot produce at all.
686
867
  */
687
- declare function sendError(res: EnvelopeResponse, status: number, code: ErrorCode, message: string, extra?: Pick<ApiError, 'category' | 'httpStatus' | 'details' | 'requestId'>): void;
868
+ declare function sendError(res: EnvelopeResponse, status: number, code: ErrorCode, message: string, extra?: Pick<ApiError, 'category' | 'httpStatus' | 'details' | 'requestId' | 'declaredCode' | 'userMessage'>): void;
688
869
 
689
870
  /**
690
871
  * The ONE rule for "what HTTP answer does a THROWN error declare?" (#8016).
@@ -756,6 +937,14 @@ declare function sendError(res: EnvelopeResponse, status: number, code: ErrorCod
756
937
  * answers come from ONE function, which is what keeps agreement a construction
757
938
  * rather than two suites agreeing about literals.
758
939
  *
940
+ * [#12509] And the channel has a SCOPE, ruled 2026-08-27 (option D): on a 5xx
941
+ * the producer did not declare, the demoted spelling came off an undeclared
942
+ * producer and is withheld with the prose, while an author-declared code
943
+ * survives. The discriminator is {@link serverFaultProvenance} — one function,
944
+ * read by {@link demotedDeclaredCode}, which every door already calls, so no
945
+ * registrar carries a variant. Read that function's note for why the status
946
+ * channel is the only honest signal here.
947
+ *
759
948
  * ## What this deliberately does NOT decide
760
949
  *
761
950
  * - **Message disclosure.** A 5xx message may name physical tables or carry a
@@ -814,6 +1003,11 @@ interface ThrownHttpError {
814
1003
  * every door (#9106) — but for the wire's `declaredCode` channel when the
815
1004
  * spelling is not a vocabulary member ({@link demotedDeclaredCode}). See the
816
1005
  * module note on why there are two.
1006
+ *
1007
+ * ⚠️ This field records what the producer WROTE, not what a boundary may
1008
+ * emit: since #12509 a demoted spelling is withheld on an undeclared 5xx.
1009
+ * ⛔ Read {@link demotedDeclaredCode}, never this field, when deciding what
1010
+ * goes on a wire.
817
1011
  */
818
1012
  declaredCode?: string;
819
1013
  /** The thrown message, UNSANITISED — see the module note on disclosure. */
@@ -888,21 +1082,206 @@ declare function resolveThrownHttpError(error: unknown, fallbackStatus?: number)
888
1082
  * consumer keeps its generic substitution (#3821 preserved by construction).
889
1083
  */
890
1084
  declare function declaredUserMessage(error: unknown): string | undefined;
1085
+ /**
1086
+ * [#12509] WHO named this 5xx — the producer, or this resolver's fallback.
1087
+ * `undefined` for anything below 500, where nothing is sanitised at all.
1088
+ *
1089
+ * This is the ONE definition of the distinction ADR-0112's 5xx-sanitisation
1090
+ * scope turns on (maintainer ruling 2026-08-27, option D), and it exists as a
1091
+ * named function rather than as an inline conjunction because TWO rules read
1092
+ * it and they read opposite limbs:
1093
+ *
1094
+ * - `'undeclared'` — the throw declared no HTTP answer, so
1095
+ * {@link ThrownHttpError.status} is the caller's `fallbackStatus` and
1096
+ * EVERYTHING this resolver picked up off that throw is the producer's
1097
+ * internals rather than an answer it composed. A driver errno
1098
+ * (`SQLITE_ERROR`, `42P01`) is the measured case, and it is why
1099
+ * {@link demotedDeclaredCode} withholds the code here: the spelling names
1100
+ * the backend, which is one of the two disclosures the 5xx message
1101
+ * withhold exists to prevent (`looksLikeInternalErrorLeak`; the other,
1102
+ * identifiers, is already covered).
1103
+ * - `'declared'` — the producer named a 5xx ITSELF, so its code is authored
1104
+ * and survives. #11718's `{ status: 503, code: 'SERVICE_UNAVAILABLE' }`
1105
+ * relay is this limb, and so is a metadata app's own 5xx refusal spelling
1106
+ * (#7867), which the ADR-0112 amendment wrote `declaredCode` for.
1107
+ *
1108
+ * ⚠️ The DISCRIMINATOR is the status channel, not the code's shape. There is
1109
+ * no other structural signal: a driver errno and an app's own spelling both
1110
+ * arrive on `.code` as a plain string, so anything that told them apart by
1111
+ * LOOKING at the string would be a heuristic over an open channel — the
1112
+ * consumer-side tolerance ADR-0112 exists to forbid, and unfalsifiable besides
1113
+ * (nothing stops an app from spelling `SQLITE_ERROR`). The cost of the
1114
+ * structural answer is stated rather than hidden: a producer that spells a
1115
+ * code but declares NO status loses that code on a 5xx. It keeps it by
1116
+ * declaring the status it means, which is the shape the ADR already asks for.
1117
+ *
1118
+ * ⛔ NOT gated on whether `looksLikeInternalErrorLeak` actually fired on the
1119
+ * message. That predicate is a heuristic over a DIFFERENT channel, and gating
1120
+ * here on it would leak the errno for exactly the dialects whose prose the
1121
+ * heuristic misses — the ceiling `sendThrownError`'s note records. The 5xx
1122
+ * sanitisation REGIME is the condition, not one of its two outcomes.
1123
+ *
1124
+ * ⭐ #12281 — the prose axis of the same 2026-08-27 ruling — is the
1125
+ * `'declared'` limb of this same function: the dispatcher door withholds the
1126
+ * message of EVERY declared 5xx, aligning to `/data`. It is a separate card
1127
+ * with its own measurement-first step, so nothing here applies it; this
1128
+ * function is the shape it will read rather than a second copy it would have
1129
+ * to grow.
1130
+ */
1131
+ type ServerFaultProvenance = 'declared' | 'undeclared';
1132
+ /** See {@link ServerFaultProvenance}. */
1133
+ declare function serverFaultProvenance(thrown: ThrownHttpError): ServerFaultProvenance | undefined;
891
1134
  /**
892
1135
  * The producer's spelling a boundary should surface as the wire's
893
1136
  * `declaredCode` beside the closed `code` — or `undefined` when there is
894
1137
  * nothing to surface (#9106).
895
1138
  *
896
1139
  * Present exactly when the throw spelled a code that did NOT survive into
897
- * {@link ThrownHttpError.code} — i.e. the demote happened. A registered code
898
- * is already in `code`, so emitting it again would put two spellings of one
899
- * fact on every refusal; a throw with no code has nothing to declare. Spelled
900
- * once here rather than as three `!==` comparisons at three exits, so
901
- * "presence means demotion" (`ApiErrorSchema.declaredCode`'s documented
902
- * semantics) has one definition.
1140
+ * {@link ThrownHttpError.code} — i.e. the demote happened AND the answer is
1141
+ * not an undeclared server fault. A registered code is already in `code`, so
1142
+ * emitting it again would put two spellings of one fact on every refusal; a
1143
+ * throw with no code has nothing to declare. Spelled once here rather than as
1144
+ * three `!==` comparisons at three exits, so "presence means demotion"
1145
+ * (`ApiErrorSchema.declaredCode`'s documented semantics) has one definition.
1146
+ *
1147
+ * [#12509] The withhold limb, ruled 2026-08-27 (option D): on a 5xx the
1148
+ * producer did NOT declare, the spelling this resolver demoted came off an
1149
+ * undeclared producer — a driver errno, measured on the wire at three of this
1150
+ * repo's doors — and it is withheld along with the prose. An AUTHOR-declared
1151
+ * code survives at every status. The judgement lives in
1152
+ * {@link serverFaultProvenance}; it is applied HERE, in the one read every
1153
+ * boundary already makes, so all of them inherit it without a door growing a
1154
+ * rule of its own. ⛔ Do not re-derive the condition at a door: a per-door
1155
+ * variant is the divergence this channel has now been repaired for twice.
903
1156
  */
904
1157
  declare function demotedDeclaredCode(thrown: ThrownHttpError): string | undefined;
905
1158
 
1159
+ /**
1160
+ * [#14310] The one rule for "a 5xx must never be silent", shared by every
1161
+ * transport that turns a fault into an HTTP envelope.
1162
+ *
1163
+ * ## The hole this closes
1164
+ *
1165
+ * A 500 that leaves no server-side line is diagnosed from the browser or not
1166
+ * at all. Measured on `main`: a plain `Error` thrown out of a dispatcher route
1167
+ * answered `500 INTERNAL_ERROR` with **zero** log records at any level — the
1168
+ * only evidence was the client's console and the response body. The failure
1169
+ * that motivated this had been reachable for a week and nobody saw it, which
1170
+ * is AGENTS.md "Route & surface ownership §3 — absence must be loud" inverted.
1171
+ *
1172
+ * The reporting that DID exist was not a substitute, in two independent ways:
1173
+ *
1174
+ * 1. `ErrorReporter.captureException` is an APM channel and defaults to
1175
+ * `NoopErrorReporter`. A dev server — the surface an operator actually
1176
+ * watches — wires no reporter, so the capture was a no-op every time.
1177
+ * 2. It is fed by `res.__obsRecordedError`, which only the THROWN exit sets.
1178
+ * A dispatcher route that catches its own fault and RETURNS a 5xx envelope
1179
+ * (`deps.errorFromThrown`, which is how every `/packages` handler answers)
1180
+ * records nothing, so even a wired reporter never saw those.
1181
+ *
1182
+ * This module is the log half, and it is deliberately not the reporter half:
1183
+ * an APM capture is opt-in telemetry, a log line is the operator's floor.
1184
+ *
1185
+ * ## Why it lives here
1186
+ *
1187
+ * Same argument, and the same package, as `resolveThrownHttpError` one file
1188
+ * over: a rule two doors must agree on cannot live inside one of them.
1189
+ * `@objectstack/runtime` depends on `@objectstack/rest`, so an import between
1190
+ * the two doors could only ever point one way — which is exactly why the
1191
+ * "what status does this throw mean" rule was moved here in #8016. "Is this
1192
+ * answer worth an operator's attention" is the same kind of rule, read by the
1193
+ * same two doors, so it gets the same home rather than a second one.
1194
+ *
1195
+ * Living beside {@link sendError} is what makes the REST side automatic: that
1196
+ * writer is the single exit for every nested-envelope 5xx, so the direct-mount
1197
+ * registrars need no per-door call and cannot forget one. Each transport logs
1198
+ * at its own single exit, so a fault costs one line and never two.
1199
+ *
1200
+ * ## `error` level, and why that clears the default
1201
+ *
1202
+ * The requirement is that the line survives `--log-level`'s DEFAULT. The CLI
1203
+ * default is `warn` (`packages/cli/src/utils/log-level.ts`) and `error` (40)
1204
+ * outranks `warn` (30) in `LEVEL_PRIORITY`, so an `error` record passes the
1205
+ * default threshold without any bypass of the level system. An operator who
1206
+ * asks for `--log-level silent` still gets silence: that is a deliberate
1207
+ * instruction, not the default this issue is about.
1208
+ *
1209
+ * ## 5xx only
1210
+ *
1211
+ * 4xx stays quiet, deliberately and at this one gate rather than at each call
1212
+ * site. A client error is the caller's mistake and the response already
1213
+ * explains it; logging them is how the `/meta` `?state=draft` probe once
1214
+ * printed 45 stack traces in one browsing session. `isServerFault` is the
1215
+ * whole rule: at or above 500.
1216
+ */
1217
+
1218
+ /** The request coordinates an operator needs to find the failing call. */
1219
+ interface ServerFaultRequest {
1220
+ /** HTTP method, e.g. `GET`. */
1221
+ method?: string;
1222
+ /** Request path as served, e.g. `/api/v1/packages`. */
1223
+ path?: string;
1224
+ /** Correlation id — the `X-Request-Id` echoed on the response. */
1225
+ requestId?: string;
1226
+ }
1227
+ /** One fault, as the emitting door knows it. */
1228
+ interface ServerFaultLogInput {
1229
+ /** The HTTP status about to be written. Below 500 nothing is logged. */
1230
+ status: number;
1231
+ /**
1232
+ * The original thrown value, when the door still holds it. Carries the
1233
+ * stack; the wire body never does, because a 5xx message is withheld.
1234
+ */
1235
+ error?: unknown;
1236
+ /** The envelope's `code`, when the door resolved one. */
1237
+ code?: string;
1238
+ /**
1239
+ * The message to print when {@link ServerFaultLogInput.error} carries
1240
+ * none — a declared fault built from a string rather than a throw.
1241
+ */
1242
+ message?: string;
1243
+ /** Where the call came in. */
1244
+ request?: ServerFaultRequest;
1245
+ }
1246
+ /** The prefix every fault line carries, so an operator can grep one token. */
1247
+ declare const SERVER_FAULT_LOG_PREFIX = "[5xx]";
1248
+ /**
1249
+ * THE predicate. A response is a server fault worth a line exactly when its
1250
+ * status is 5xx. Exported so a door can decide without restating `>= 500`.
1251
+ */
1252
+ declare function isServerFault(status: number): boolean;
1253
+ /**
1254
+ * The human half of the line: `[5xx] 500 GET /api/v1/packages — <message>`.
1255
+ * Split out so both the emitted record and a test can name the same string.
1256
+ */
1257
+ declare function serverFaultLogMessage(input: ServerFaultLogInput): string;
1258
+ /**
1259
+ * The structured half. `status`/`code`/`requestId` are what a log search keys
1260
+ * on; `method`/`path` repeat the message's coordinates because a JSON sink
1261
+ * indexes fields, not prose.
1262
+ */
1263
+ declare function serverFaultLogMeta(input: ServerFaultLogInput): Record<string, unknown>;
1264
+ /**
1265
+ * Emit EXACTLY ONE `error`-level record for a 5xx, or nothing at all.
1266
+ *
1267
+ * Returns whether a record was emitted, so a caller that must not double-log
1268
+ * can branch on the answer rather than re-deriving the 5xx test.
1269
+ *
1270
+ * `logger` is optional: a door with no injected logger falls back to
1271
+ * `console.error`, because the point of this function is that the line exists
1272
+ * even on a surface nobody configured. Emission never throws — a logging
1273
+ * failure must not become a second fault on top of the one being reported.
1274
+ */
1275
+ declare function logServerFault(input: ServerFaultLogInput, logger?: Logger): boolean;
1276
+ /**
1277
+ * Read request coordinates off whatever request object the transport hands
1278
+ * the door. Adapters disagree on the spelling (`path` / `url` /
1279
+ * `originalUrl`), and the request id may be on the object (set by
1280
+ * `instrumentRouteHandler`) or only on the incoming header — so both are
1281
+ * read here, once, instead of at each call site.
1282
+ */
1283
+ declare function describeFaultRequest(req: unknown): ServerFaultRequest;
1284
+
906
1285
  /** The HTTP status a validation failure maps to when the error names none. */
907
1286
  declare const VALIDATION_FAILED_STATUS = 400;
908
1287
  interface ValidationFailureDetails {
@@ -963,6 +1342,118 @@ declare function fieldsFromZodIssues(issues: Array<{
963
1342
  message: string;
964
1343
  }>;
965
1344
 
1345
+ /**
1346
+ * The machine-readable half of a `RESUME_FAILED` — a decision that is durably
1347
+ * recorded whose flow run could not be resumed (#13807).
1348
+ *
1349
+ * ## The condition
1350
+ *
1351
+ * An approval decision finalises: the `sys_approval_request` row flips to its
1352
+ * terminal status, the audit action is written, the record's mirrored status
1353
+ * field advances — and only THEN is the owning flow run resumed. When that
1354
+ * resume fails the writes are already durable, so the outcome stands and the
1355
+ * run is stranded. `@objectstack/plugin-approvals` throws rather than
1356
+ * answering `resumed: false`, deliberately: a recorded decision whose flow
1357
+ * never advances is #4420's zombie half-state, and the contract
1358
+ * (`ApprovalDecisionResult`) declares the throw intentional.
1359
+ *
1360
+ * ⛔ This module does NOT change that posture. The maintainer ruled on
1361
+ * 2026-09-04 (decision batch #37, option B) that the door **keeps its status
1362
+ * code** — the 500-class `RESUME_FAILED` — because the effect landing while
1363
+ * the run strands is still a failure. What the ruling changed is that the
1364
+ * throw must be *truthful*: the facts a caller needs were being discarded.
1365
+ *
1366
+ * ## What was being discarded, measured
1367
+ *
1368
+ * Three states coexist after such a call: the caller reads 500, the request
1369
+ * IS in its terminal status, and the run is stranded. A caller — human,
1370
+ * script, or agent — reads 500 as "the rejection did not happen" and retries
1371
+ * or escalates. It did happen. Before this module the only carrier of that
1372
+ * fact was English prose in `error`, so an operator had to regex the run id
1373
+ * out of a sentence, and nothing said whether the run was repairable at all.
1374
+ *
1375
+ * Meanwhile the engine already knew. `AutomationResult.status: 'stranded'`
1376
+ * (`@objectstack/spec`, `automation-service.ts`) is stamped on exactly the
1377
+ * exit that journals a repair snapshot — the shape-4 name from #13937 — and
1378
+ * it is distinct from `'failed'` on purpose: `'failed'` says the run ran and
1379
+ * was rejected, `'stranded'` says a recorded continuation stopped mid-flight
1380
+ * and an operator has something to repair. It had a producer and, until this
1381
+ * module, **zero consumers**: the approvals door read only
1382
+ * `success` / `code` / `error` off the resume result and dropped it one line
1383
+ * before the envelope was built.
1384
+ *
1385
+ * ## Why it lives in `@objectstack/types`
1386
+ *
1387
+ * Same Home rule as {@link ValidationFailureDetails} one file over: the
1388
+ * PRODUCER is `@objectstack/plugin-approvals` and the CONSUMER is the REST
1389
+ * door in `@objectstack/rest`, and rest cannot import a plugin. Both already
1390
+ * depend on this package, so the shared declaration adds no dependency edge —
1391
+ * and keeping the constructor and the reader in ONE module is what stops the
1392
+ * two sides from drifting into a stringly-typed agreement about a property
1393
+ * name.
1394
+ *
1395
+ * ⛔ Deliberately NOT a tolerant reader. There is no alias chain and no prose
1396
+ * parsing: a body either carries the four facts the producer attached, or the
1397
+ * response is exactly what it was before. A `RESUME_FAILED` raised by
1398
+ * something that never had a decision to report (a test double, a future
1399
+ * caller) must not be dressed up as one.
1400
+ */
1401
+ /**
1402
+ * The four facts a stranded decision publishes alongside its `code` and
1403
+ * `error`. Every field is present or the whole envelope is absent — a partial
1404
+ * one would let a consumer branch on `finalized === undefined` and read it as
1405
+ * "the decision did not stand", which is the exact misreading this exists to
1406
+ * end.
1407
+ */
1408
+ interface StrandedDecisionDetails {
1409
+ /**
1410
+ * Always `true`. The decision reached a terminal state and is durable; the
1411
+ * 5xx is about the run, never about the decision. Spelled as a literal
1412
+ * rather than omitted so a consumer reads a fact instead of an absence.
1413
+ */
1414
+ finalized: true;
1415
+ /**
1416
+ * Which outcome was recorded — `'approve'` / `'reject'` for a decision, and
1417
+ * the sibling doors on the same path for the rest (`'revise'` on a
1418
+ * send-back, `'resubmit'`). Free-form by design: the vocabulary belongs to
1419
+ * the producing service, not to this recogniser.
1420
+ */
1421
+ decision: string;
1422
+ /** The stranded run. The one identifier an operator needs to act. */
1423
+ runId: string;
1424
+ /**
1425
+ * Whether the engine says this run can still be repaired — derived from the
1426
+ * engine's own discriminator (`AutomationResult.status === 'stranded'`),
1427
+ * never from the message text and never assumed.
1428
+ *
1429
+ * `false` is the honest answer for every other exit, including the ones
1430
+ * that report no status at all (a lost run, an engine that predates the
1431
+ * discriminator). ⛔ Absence of the signal is not repairability: promising a
1432
+ * repair verb that will refuse is worse than promising nothing.
1433
+ */
1434
+ repairable: boolean;
1435
+ }
1436
+ /**
1437
+ * Structured details for a thrown stranded-decision failure, or `undefined`
1438
+ * when `err` is not one.
1439
+ *
1440
+ * Callers use the `undefined` result as the predicate and the returned object
1441
+ * as the payload, so the two can never disagree — the same contract
1442
+ * `validationFailureDetails` keeps one module over. Every field is validated:
1443
+ * a malformed carrier answers `undefined` rather than putting a half-envelope
1444
+ * on the wire.
1445
+ */
1446
+ declare function strandedDecisionDetails(err: unknown): StrandedDecisionDetails | undefined;
1447
+ /**
1448
+ * The CONSTRUCTOR for the shape {@link strandedDecisionDetails} recognises —
1449
+ * kept in the same module so the two can never drift.
1450
+ *
1451
+ * The message stays the producer's own, unchanged: the prose is what a human
1452
+ * reads in a log, the details are what a machine reads on the wire, and this
1453
+ * ruling added the second without touching the first.
1454
+ */
1455
+ declare function strandedDecisionFailure(message: string, details: StrandedDecisionDetails): Error;
1456
+
966
1457
  /**
967
1458
  * The one home for Postgres' `«sub-object» "x" of relation "y" …` phrasing
968
1459
  * (#6615).
@@ -1154,6 +1645,153 @@ declare function isUniqueViolationError(error: unknown): boolean;
1154
1645
  */
1155
1646
  declare function uniqueViolationColumn(error: unknown): string | undefined;
1156
1647
 
1648
+ /**
1649
+ * [#13438] The physical table a driver's statement TARGETED, declared on the
1650
+ * error envelope by the producer that knows it.
1651
+ *
1652
+ * `readObject` closed the #13324 hole for callers that can name what they read
1653
+ * — and left a residual one layer down. A caller names its OBJECT (the API
1654
+ * name); a driver compiles the statement against the PHYSICAL table, and for a
1655
+ * federated object (ADR-0015, `external.remoteName`) those are two different
1656
+ * names. `driver-sql` reads `crm_order` from `legacy_orders`, so when that
1657
+ * remote is genuinely absent the dialect phrase names `legacy_orders`, the
1658
+ * caller names `crm_order`, and the comparison called a real missing table
1659
+ * "about something else" — loud, for the one case the licence was built for.
1660
+ *
1661
+ * Nothing at a call site can fold that away: the mapping lives on the driver
1662
+ * instance, and asking every caller to consult it is the guessing this channel
1663
+ * exists to remove (maintainer ruling 2026-09-01, option 2 on the card). So the
1664
+ * fact is declared where it is known — the driver that composed the envelope
1665
+ * stamps the table its statement targeted onto it — and the predicate PREFERS
1666
+ * a declared table over the caller-supplied `readObject`. The caller never
1667
+ * needs to know a federated object's remote name, and a driver that declares
1668
+ * nothing gets exactly the #13324 behaviour.
1669
+ *
1670
+ * A symbol key from the global registry, held non-enumerable: the carrier
1671
+ * discipline `driver-sql` already applies to its withheld-diagnostic symbols
1672
+ * and to the envelope's own `cause`. Readable by code; invisible to
1673
+ * `JSON.stringify`, `{ ...err }`, `Object.keys`, `for…in` and the
1674
+ * structured-clone boundary — so the physical table name, the very thing the
1675
+ * envelope's composed message withholds, can never ride back onto a wire that
1676
+ * serialises the error. `Symbol.for` so a duplicated copy of this package
1677
+ * resolves the same key.
1678
+ *
1679
+ * ⚠️ A declaration is EVIDENCE, so it also narrows the one-argument form: an
1680
+ * envelope declaring `legacy_orders` whose dialect phrase names some other
1681
+ * relation reads not-benign even with no `readObject` — the driver supplied
1682
+ * the fact the caller could not. That is the #13324 verdict reached without
1683
+ * the caller's help, in the direction the module docblock calls cheap.
1684
+ */
1685
+ declare const DRIVER_TARGETED_TABLE: symbol;
1686
+ /**
1687
+ * Declare, on `error`, the physical table the statement that raised it targeted.
1688
+ *
1689
+ * The producer's half of {@link DRIVER_TARGETED_TABLE} — for a driver composing
1690
+ * an error envelope over a dialect failure. `table` is the name the statement
1691
+ * was compiled against (a federated object's `external.remoteName`, otherwise
1692
+ * the object's own table), bare: the comparison folds away schema and database
1693
+ * qualifiers on both sides, so none is needed here.
1694
+ *
1695
+ * Non-enumerable and non-writable, and the FIRST declaration wins: the actor
1696
+ * that compiled the statement is the one that knows its target, and a later,
1697
+ * more distant wrapper re-declaring it would be re-introducing the guess. (The
1698
+ * predicate applies the same rule across a `cause` chain: the declaration
1699
+ * NEAREST the dialect phrase is the one compared.) An empty or non-string
1700
+ * `table` declares nothing — silently, because this runs on an error path
1701
+ * where a thrown `TypeError` would replace the envelope it was meant to
1702
+ * annotate; the predicate then falls back to `readObject` exactly as if no
1703
+ * driver had spoken.
1704
+ *
1705
+ * @returns `error`, for chaining.
1706
+ */
1707
+ declare function declareTargetedTable<E extends object>(error: E, table: string): E;
1708
+ /**
1709
+ * The table `error` declares its statement targeted, or `null` when it declares
1710
+ * none — the reading half of {@link declareTargetedTable}. Tolerant of bare
1711
+ * input: any non-object, and any object without a non-empty string under the
1712
+ * key, is "no declaration".
1713
+ */
1714
+ declare function targetedTableOf(error: unknown): string | null;
1715
+ /**
1716
+ * Is this DDL error the benign "already provisioned" case?
1717
+ *
1718
+ * @param error - The value thrown by `syncSchema()` (or any DDL call).
1719
+ * @param depth - Internal `cause`-chain recursion counter; callers pass nothing.
1720
+ * @returns `true` only when the error positively identifies as
1721
+ * table/column/index-already-exists. Anything else — including an
1722
+ * unrecognised error, `undefined`, or a permission/connection failure —
1723
+ * returns `false` and MUST be reported loudly by the caller.
1724
+ */
1725
+ declare function isSchemaAlreadyExistsError(error: unknown, depth?: number): boolean;
1726
+ /**
1727
+ * Is this READ error the benign "table has not been provisioned yet" case?
1728
+ *
1729
+ * The only failure that licenses a caller to treat an empty table as the truth
1730
+ * — there are no rows, so there is nothing to be inconsistent with. A
1731
+ * connection drop, a timeout, a permission denial or a query error all mean the
1732
+ * rows may well exist and simply were not seen; those return `false` and the
1733
+ * caller must report the consequence and give up rather than compute an answer
1734
+ * from data it never read (#4825).
1735
+ *
1736
+ * A failure about a **column** of a relation is never this case, in either of
1737
+ * Postgres' two phrasings — the relation is right there in the message because
1738
+ * it exists (#6347). See {@link MISSING_TABLE}'s `excludes`.
1739
+ *
1740
+ * [#13324] Neither is a failure that names a **different relation**, and that
1741
+ * one cannot be seen without `readObject`. The message test asks what the
1742
+ * phrase LOOKS like and never which table it names, so a read of a view whose
1743
+ * base table has been dropped — `no such table: main.<base>`, measured on
1744
+ * libsql for a view that itself exists — answered benign for a relation that is
1745
+ * present and may be backed by rows. Naming the read closes it: the phrase must
1746
+ * be about the table the caller asked for, or it is not evidence about it.
1747
+ *
1748
+ * Pass `readObject` from every in-repo call site. It is **optional** so that
1749
+ * omitting it is exactly the pre-#13324 behaviour rather than a new loud
1750
+ * failure — this is a published export (`@objectstack/types`, and still
1751
+ * `@objectstack/metadata/errors` by re-export), and a required parameter would
1752
+ * be a breaking change to it. The cost of the choice
1753
+ * is that the narrowing is opt-in per call site: a new caller that forgets it
1754
+ * silently gets the old, wider verdict.
1755
+ *
1756
+ * [#13440] That last sentence is no longer only a warning. In-repo callers are
1757
+ * held to it by `driver-error-classification.callers.test.ts`, which walks every
1758
+ * TypeScript source under `packages/` and fails any call of this function that
1759
+ * omits `readObject` or passes it as `undefined`/`null`. The exemption is this
1760
+ * module's own contract tests, which exercise the one-argument PUBLISHED form on
1761
+ * purpose; read that file's header before adding to the exemption, because
1762
+ * widening it is how the enforcement becomes prose again. External consumers are
1763
+ * untouched: the signature below is unchanged, and the gate binds only callers
1764
+ * inside this repository.
1765
+ *
1766
+ * [#13438] `readObject` is the caller's name for what it read, and for a
1767
+ * federated object (ADR-0015) that is not the name the driver put in the
1768
+ * statement — `crm_order` reads `external.remoteName: 'legacy_orders'`, so a
1769
+ * genuinely absent remote raised a phrase naming `legacy_orders` against a
1770
+ * caller naming `crm_order`, and the #13324 comparison read it loud. A driver
1771
+ * that knows the table it targeted now DECLARES it on the envelope
1772
+ * ({@link declareTargetedTable}), and a declared table is preferred over
1773
+ * `readObject` outright: the phrase is compared against the declared name, and
1774
+ * the caller-supplied one is not consulted at that node or below it. Absent a
1775
+ * declaration the comparison is the #13324 one, unchanged. Two consequences,
1776
+ * both pinned: a genuinely absent federated remote reads benign again without
1777
+ * the caller learning the mapping; and — because a declaration is evidence the
1778
+ * caller did not have — an envelope whose phrase names a relation other than
1779
+ * its declared table reads NOT benign even through the one-argument form.
1780
+ *
1781
+ * @param error - The value thrown by a driver/engine read (`find`, `findOne`, …).
1782
+ * @param readObject - The object/table whose emptiness the caller is about to
1783
+ * treat as the truth — its own API name is fine, the comparison folds
1784
+ * away schema qualifiers, the legacy `ns__short` prefix and case.
1785
+ * Omitted (or not a string) means "cannot say", never "be loud".
1786
+ * Superseded, at any node of the `cause` chain that declares the
1787
+ * table its statement targeted, by that declaration (#13438).
1788
+ * @param depth - Internal `cause`-chain recursion counter; callers pass nothing.
1789
+ * @returns `true` only when the error positively identifies as
1790
+ * table/relation-does-not-exist **for the table that was read** —
1791
+ * the declared target where a driver supplied one, else `readObject`.
1792
+ */
1793
+ declare function isMissingTableError(error: unknown, readObject?: string, depth?: number): boolean;
1794
+
1157
1795
  /**
1158
1796
  * Whether a thrown driver error says the `ON CONFLICT` target it was given is
1159
1797
  * backed by no PRIMARY KEY or UNIQUE index.
@@ -1407,4 +2045,4 @@ interface RuntimePlugin {
1407
2045
  onStart?: (ctx: RuntimeContext) => void | Promise<void>;
1408
2046
  }
1409
2047
 
1410
- export { type EnvelopeResponse, GLOBAL_UNIQUE_CONFIRMATION_REQUIRED, GLOBAL_UNIQUE_ISOLATED_PRESCRIPTION, type GlobalUniqueAttestation, type GlobalUniqueFinding, type IKernel, INTERNAL_ERROR_MESSAGE, type KeysetPageQuery, type KeysetWalk, type KeysetWalkOptions, type RuntimeContext, type RuntimePlugin, type ThrownHttpError, VALIDATION_FAILED_STATUS, type ValidationFailureDetails, _resetEnvDeprecationWarnings, buildGlobalUniqueStopMessage, collectConfiguredLocales, collectGlobalUniques, declaredIndexUniqueIsGlobal, declaredUserMessage, declaresServerFault, demotedDeclaredCode, describeGlobalUniqueFinding, emitDegradedBootBanner, fieldUniqueIsGlobal, fieldsFromZodIssues, globalUniqueFindingId, isMcpServerEnabled, isModuleNotFoundError, isPlatformOwnedObject, isRelationSubObjectPhrase, isUnbackedConflictTargetError, isUniqueViolationError, keysetWalk, looksLikeInternalErrorLeak, matchMissingColumnOfRelation, postureGatesGlobalUniques, readEnvWithDeprecation, recordGlobalUniqueAttestation, resolveAllowDegradedTenancy, resolveAllowDevPlugin, resolveAllowDriverConnectFailure, resolveMcpStdioAutoStart, resolveMultiOrgEnabled, resolveOrgLimit, resolveSandboxTimeoutMs, resolveSearchPinyinEnabled, resolveTenancyPosture, resolveThrownHttpError, sendError, sendOk, stampSearchPinyinEnabled, unconfirmedGlobalUniques, uniqueViolationColumn, validationFailure, validationFailureDetails };
2048
+ export { DRIVER_TARGETED_TABLE, type EnvelopeResponse, GLOBAL_UNIQUE_CONFIRMATION_REQUIRED, GLOBAL_UNIQUE_ISOLATED_PRESCRIPTION, type GlobalUniqueAttestation, type GlobalUniqueFinding, type IKernel, INTERNAL_ERROR_MESSAGE, type KeysetPageQuery, type KeysetWalk, type KeysetWalkOptions, PLATFORM_OWNER_EMAIL_ENV, type RuntimeContext, type RuntimePlugin, SERVER_FAULT_LOG_PREFIX, type ServerFaultLogInput, type ServerFaultProvenance, type ServerFaultRequest, type StrandedDecisionDetails, type ThrownHttpError, VALIDATION_FAILED_STATUS, type ValidationFailureDetails, _resetEnvDeprecationWarnings, buildGlobalUniqueStopMessage, collectConfiguredLocales, collectGlobalUniques, declareTargetedTable, declaredIndexUniqueIsGlobal, declaredUserMessage, declaresServerFault, demotedDeclaredCode, describeFaultRequest, describeGlobalUniqueFinding, emitDegradedBootBanner, fieldUniqueIsGlobal, fieldsFromZodIssues, globalUniqueFindingId, isEmailVerifiedUserRow, isMcpServerEnabled, isMissingTableError, isModuleNotFoundError, isPlatformOwnedObject, isRelationSubObjectPhrase, isSchemaAlreadyExistsError, isServerFault, isUnbackedConflictTargetError, isUniqueViolationError, keysetWalk, logServerFault, looksLikeInternalErrorLeak, matchMissingColumnOfRelation, postureGatesGlobalUniques, readEnvWithDeprecation, recordGlobalUniqueAttestation, resolveAllowDegradedTenancy, resolveAllowDevPlugin, resolveAllowDriverConnectFailure, resolveMcpStdioAutoStart, resolveMultiOrgEnabled, resolveOrgLimit, resolveOrgMembershipLimit, resolvePlatformOwnerEmail, resolveSandboxTimeoutMs, resolveSearchPinyinEnabled, resolveTenancyPosture, resolveThrownHttpError, sendError, sendOk, serverFaultLogMessage, serverFaultLogMeta, serverFaultProvenance, stampSearchPinyinEnabled, strandedDecisionDetails, strandedDecisionFailure, targetedTableOf, unconfirmedGlobalUniques, uniqueViolationColumn, validationFailure, validationFailureDetails };