Skip to content

Commit cd67471

Browse files
committed
docs(types,metadata-protocol,changeset): state the split, and name the one surviving fallback
The census by rule found two claims carried in more than one place: a false universal about what `(e as Error).message` did to an undeclared throw, and a record formula that one of the fourteen call sites does not follow. Over the five values pinned at the operator-text fallback case, that expression did not answer one way. It read `undefined` for the string, the number and `{}`, and it threw a `TypeError` for `null` and `undefined`. The pin's title and the comment under it now state that split, which the shipped `operatorFacingErrorText` docblock and both site docblocks already stated. `seed-tenancy-backfill`'s organization probe keeps `operatorFacingErrorText(e) || 'unknown error'`, so for an EMPTY channel it records `'unknown error'`, not `''`. The metadata-protocol docblock, the pin title that claimed every site, and the changeset -- which ships as release notes -- now name that fallback and scope the formula to the other thirteen sites. The fallback is load-bearing rather than leftover: the site reads an empty value as "the probe did not fail", and with it removed an empty channel routes the run down the benign no-organization-yet path. Whether it should go is a behaviour question, tracked by #17167. Two more sentences of the same class: the recognizer docblock now reads "a NON-EMPTY sentence this fragment does not match" rather than "a DIFFERENT, NON-EMPTY sentence", and `messageChannelOf`'s docblock no longer attributes `undefined` to an expression that threw. No behaviour change. 58 changed .ts lines, 54 comment and 4 title, 0 other; comment-stripped and title-blanked hashes identical on all 13 .ts files; and packages/types/dist/index.mjs, dist/index.js and dist/index.d.ts are each byte-identical across this round. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XTBcV7zZHmokdyQgXjbyEU
1 parent 82ee689 commit cd67471

4 files changed

Lines changed: 52 additions & 21 deletions

File tree

.changeset/operator-facing-raw-exec-cause-text.md

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -79,9 +79,12 @@ and never a type. The change these sites were made for is the declared raw-path
7979
the record gains the dialect's words in place of the driver's composed placeholder. Every
8080
other throw now reaches these records through the rule above rather than through the
8181
expression each site spelled out, so its text can move too — a consequence of the rule, not a
82-
bounded list of exceptions, and some shapes still record `''` — a thrown empty string, a
83-
thrown empty array, and an `Error` whose `name` and `message` are both empty are the ones
84-
measured. The
85-
sentence being replaced is not a value any consumer can have been parsing: it is an opaque
86-
human diagnostic. A consumer reading these records gets the dialect's words back where it had
87-
been getting a placeholder.
82+
bounded list of exceptions. At thirteen of the fourteen sites the rule is the whole record,
83+
and some shapes still record `''` there: a thrown empty string, a thrown empty array, and an
84+
`Error` whose `name` and `message` are both empty are the ones measured. The fourteenth is
85+
`seed-tenancy-backfill`'s organization probe, which keeps a `|| 'unknown error'` fallback on
86+
top of the rule, so those same three shapes record `'unknown error'` there rather than `''`;
87+
that fallback is deliberate — the site reads an empty value as "the probe did not fail" — and
88+
whether it should go is tracked by #17167. The sentence being replaced is not a value any
89+
consumer can have been parsing: it is an opaque human diagnostic. A consumer reading these
90+
records gets the dialect's words back where it had been getting a placeholder.

packages/metadata-protocol/src/migrations/raw-exec-operator-detail-16657.test.ts

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,25 @@
2525
* The negative direction is pinned per site as well: a seam failure that is NOT
2626
* a declared raw-statement fault is NOT unwrapped — its `cause` is never walked
2727
* and the record reads the thrown value's own message channel,
28-
* `messageChannelOf(error) || String(error)` — because the alternative, a
29-
* helper that unwraps whatever it is handed, is the message sniffing #16019
30-
* exists to remove.
28+
* `messageChannelOf(error) || String(error)`, at every site here but the one
29+
* noted below — because the alternative, a helper that unwraps whatever it is
30+
* handed, is the message sniffing #16019 exists to remove.
31+
*
32+
* ⚠️ That formula is the WHOLE record at eight of this package's nine call
33+
* sites (thirteen of the fourteen across the change), not at all of them.
34+
* `seed-tenancy-backfill`'s ORGANIZATION probe still spells
35+
* `operatorFacingErrorText(e) || 'unknown error'` — the one surviving fallback
36+
* — so where the channel is EMPTY that record reads `'unknown error'` and
37+
* never `''`. Measured at that probe: a thrown `''`, a thrown `[]`, and an
38+
* `Error` whose `name` and `message` are both empty each record
39+
* `'unknown error'`; the control `new Error('boom')` records `'boom'`. The
40+
* fallback is load-bearing rather than leftover — this site reads
41+
* `organizationProbeError === ''` as "the probe did not fail", and with the
42+
* fallback deleted a thrown `''` routes the run down the benign
43+
* `no-organization-yet` path instead of the ambiguous one (measured by
44+
* ablation), which is the "unknown read as zero" confusion #9261 exists to
45+
* prevent. Whether this record SHOULD be `''` like the other eight is a
46+
* BEHAVIOUR question, deliberately not taken here; #17167 carries it.
3147
*
3248
* ⚠️ That channel is a RULE, not byte-identity with what each site used to
3349
* compute. Every negative pin below throws a NON-EMPTY `new Error(…)`, the
@@ -303,7 +319,13 @@ describe('[#16657] seed-tenancy-backfill — the stored operator record', () =>
303319
expect(line?.meta?.error).toBe(DIALECT_TEXT);
304320
});
305321

306-
it('an UNDECLARED refusal reads its own message channel at every one of these sites', async () => {
322+
it('an UNDECLARED refusal reads its own message channel at the sites without a fallback', async () => {
323+
// "Without a fallback" is eight of this package's nine call sites. The
324+
// exception is the ORGANIZATION probe (`|| 'unknown error'`), whose
325+
// record for an EMPTY channel is `'unknown error'` rather than `''` —
326+
// see the module docblock above, and #17167. This pin drives the
327+
// duplicates warning, which carries no fallback, with a NON-EMPTY
328+
// message, so it exercises the channel and not the fallback.
307329
const log = createLogger();
308330
const bare = async (sql: string): Promise<unknown> => {
309331
if (sql.includes('rows_holding')) throw new Error('connection terminated unexpectedly');

packages/types/src/driver-error-classification.operator-text.test.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,9 +140,13 @@ describe('[#16657] operatorFacingErrorText — the fallback channel when no caus
140140
expect(operatorFacingErrorText(thrown)).not.toBe('');
141141
});
142142

143-
it('reads a thrown non-Error on its own channel, where `(e as Error).message` read `undefined`', () => {
143+
it('reads a thrown non-Error on its own channel, where `(e as Error).message` read `undefined` or threw', () => {
144144
// `(e as Error).message` — the expression this helper replaces at five
145-
// sites — evaluates to `undefined` for every one of these.
145+
// sites — answered these five two different ways, neither of them a
146+
// record worth storing. It evaluated to `undefined` for the string, the
147+
// number and `{}`; for `null` and `undefined` it threw a `TypeError`
148+
// out of the catch, so no record was written at all and the operation
149+
// aborted. The helper reads a channel instead, so all five store text.
146150
expect(operatorFacingErrorText('no such column: foo')).toBe('no such column: foo');
147151
expect(operatorFacingErrorText(42)).toBe('42');
148152
expect(operatorFacingErrorText(undefined)).toBe('undefined');

packages/types/src/driver-error-classification.ts

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -685,10 +685,11 @@ const DECLARED_DATABASE_FAULT_CODE = 'DATABASE_ERROR';
685685
* half) untouched: it declares the very same code and status, composes a
686686
* DIFFERENT sentence, and whether its prose should be unwrapped is a separate
687687
* decision this helper deliberately does not take. An envelope that declares
688-
* the code and composes a DIFFERENT, NON-EMPTY sentence is returned exactly as
689-
* it arrived — it speaks at depth 0, so the walk stops on it. ⚠️ Not so for a
690-
* declared envelope whose own message is EMPTY: an empty node says nothing, so
691-
* the walk steps past it and that envelope IS unwrapped (measured:
688+
* the code and composes a NON-EMPTY sentence this fragment does not match is
689+
* returned exactly as it arrived — it speaks at depth 0, so the walk stops on
690+
* it. ⚠️ Not so for a declared envelope whose own message is EMPTY: an empty
691+
* node says nothing, so the walk steps past it and that envelope IS unwrapped
692+
* (measured:
692693
* `{code:'DATABASE_ERROR', message:'', cause:{message:'walked'}}` answers
693694
* `'walked'`). ⛔ No claim is made about whether any producer composes an
694695
* empty-message `DATABASE_ERROR`; that was not measured.
@@ -707,11 +708,12 @@ const RAW_STATEMENT_FAULT_SENTENCE = /refused to run a raw statement/;
707708
* `message` for an object or function, the string itself for a string, and
708709
* `String()` for any other primitive; anything else reads `''`. So a non-Error
709710
* node reads whatever text it carries rather than the `undefined` that
710-
* `(e as Error).message` produced at the FIVE sites spelled that way — of the
711-
* fourteen this helper replaces; the other nine spell
712-
* `instanceof Error ? … : String()` (five) or `?.message ?? …` (four) and
713-
* already carried a fallback — and a node whose own text is empty, a thrown
714-
* empty string among them, reads `''`.
711+
* `(e as Error).message` produced at the FIVE sites spelled that way (for
712+
* `null` and `undefined` that expression produced nothing at all — it threw a
713+
* `TypeError` out of the catch) — of the fourteen this helper replaces; the
714+
* other nine spell `instanceof Error ? … : String()` (five) or
715+
* `?.message ?? …` (four) and already carried a fallback — and a node whose own
716+
* text is empty, a thrown empty string among them, reads `''`.
715717
*/
716718
function messageChannelOf(node: unknown): string {
717719
if (typeof node === 'string') return node;

0 commit comments

Comments
 (0)