Skip to content

Commit e27583e

Browse files
os-muskclaude
andauthored
docs(core): state the measured structuredClone behaviour on the authz brand (#14006) (#14614)
The brand docblock justified its string-keyed own property with two reasons joined by an `and`, and only the second was true. Measured on Node 22.22.2, the structured-clone algorithm gives `Error` a dedicated serialization carrying `message`, `stack` and `cause` only and drops every other own property — brand, ADR-0112 `code`, `status` and `object` alike; the plain-object control keeps both keys through the same call, so the loss is specific to `Error`, not general to `structuredClone`. The property and the reason that earns it stay (a duplicated copy of the module still brands identically, which `instanceof` cannot do). The false half is replaced by the measured behaviour plus the reproducible script and Node version, phrased to match what `service-not-registered.ts` already records. No runtime change, and deliberately no `toJSON` — every call site is in-process, so nothing pulls on clone support. Claude-Session: https://claude.ai/code/session_0112hMx9hjJ9BgB28X97DS68 Co-authored-by: Claude <noreply@anthropic.com>
1 parent 8227a19 commit e27583e

2 files changed

Lines changed: 67 additions & 3 deletions

File tree

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
---
2+
'@objectstack/core': patch
3+
---
4+
5+
docs(core): the `AuthzStoreUnavailableError` brand doc states the measured `structuredClone` behaviour instead of claiming survival (#14006)
6+
7+
Documentation only — no runtime change, no type change, no accept/reject
8+
behaviour moves. It ships as a patch because the docblock is a **published
9+
byte**: `tsup`'s declaration rollup carries it into `dist/index.d.ts` and
10+
`dist/index.d.cts`, so it is what a consumer reads on hover.
11+
12+
The brand's docblock justified the string-keyed own property with two reasons
13+
joined by an `and`, of which only the second was true:
14+
15+
> A string-keyed own property (not a `Symbol.for` registry key) so it survives
16+
> `structuredClone`, and so a duplicated copy of this module still brands
17+
> identically.
18+
19+
Measured on Node 22.22.2: the structured-clone algorithm gives `Error` a
20+
dedicated serialization carrying `message`, `stack` and `cause` only, and drops
21+
every other own property — the brand, the ADR-0112 `code`, `status` and
22+
`object` alike (a subclass's own `name` returns as `'Error'`). The
23+
plain-object control is the half that proves it: `{ __brand: true, code: 'C' }`
24+
keeps **both** keys through the same call, so the loss is specific to `Error`,
25+
not general to `structuredClone`.
26+
27+
The property and the reason that actually earns it are kept — a duplicated copy
28+
of the module still brands identically, which is exactly what `instanceof`
29+
cannot do across two installed copies of `@objectstack/core`. The false half is
30+
replaced by the measured behaviour, carrying the reproducible script and the
31+
Node version rather than a second unsourced assertion, and phrased to match
32+
what `service-not-registered.ts` already records for its own brand (one
33+
phrasing across the two modules, not two).
34+
35+
⛔ The clone gap is deliberately NOT "fixed" with a `toJSON` or a custom
36+
serialization: no call site crosses a clone boundary today
37+
(`rethrowAuthzStoreUnavailable` on the rest rethrow paths,
38+
`isAuthzStoreUnavailableError` inside service `catch` blocks — all in-process),
39+
and adding one would widen the module's surface with nothing pulling on it. The
40+
docblock instead names the trap the false claim invited: branching on the brand
41+
across a worker or `postMessage` boundary would answer `false` and fail OPEN on
42+
a security path.

packages/core/src/security/authz-store-unavailable.ts

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -106,9 +106,31 @@ export const AUTHZ_STORE_UNAVAILABLE_MESSAGE =
106106

107107
/**
108108
* The own-property brand {@link isAuthzStoreUnavailableError} tests for.
109-
* A string-keyed own property (not a `Symbol.for` registry key) so it survives
110-
* `structuredClone`, and so a duplicated copy of this module still brands
111-
* identically.
109+
* A string-keyed own property (not a `Symbol.for` registry key), so a
110+
* duplicated copy of this module still brands identically — which is exactly
111+
* what `instanceof` cannot do (module doc above).
112+
*
113+
* ⚠️ The brand does NOT survive `structuredClone`, and no claim here depends
114+
* on it doing so — the same measured behaviour `service-not-registered.ts`
115+
* records for its own brand. Reproduce on Node 22.22.2:
116+
*
117+
* ```js
118+
* const e = new Error('x'); e.__brand = true; e.code = 'C';
119+
* const c = structuredClone(e);
120+
* // c.__brand === undefined c.code === undefined c.message === 'x'
121+
* // control: structuredClone({ __brand: true, code: 'C' }) keeps BOTH keys
122+
* ```
123+
*
124+
* `Error` has a dedicated serialization carrying `message`, `stack` and
125+
* `cause` only, so it DROPS every other own property — this brand, the
126+
* ADR-0112 `code`, `status` and `object` alike (and a subclass's own `name`
127+
* returns as `'Error'`). The plain-object control is the half that proves the
128+
* loss is specific to `Error`, not general to `structuredClone`.
129+
*
130+
* ⛔ So never branch on this brand across a worker or `postMessage` boundary:
131+
* it would answer `false` and fail OPEN. Every call site today is in-process —
132+
* `rethrowAuthzStoreUnavailable` on the rest rethrow paths and
133+
* `isAuthzStoreUnavailableError` inside service `catch` blocks.
112134
*/
113135
const AUTHZ_STORE_UNAVAILABLE_BRAND = '__objectstackAuthzStoreUnavailable' as const;
114136

0 commit comments

Comments
 (0)