Skip to content

docs(core): state the measured structuredClone behaviour on the authz brand - #14614

Merged
os-musk merged 1 commit into
mainfrom
claude/issue-14006-authz-brand-structured-clone-doc
Sep 2, 2026
Merged

docs(core): state the measured structuredClone behaviour on the authz brand#14614
os-musk merged 1 commit into
mainfrom
claude/issue-14006-authz-brand-structured-clone-doc

Conversation

@os-musk

@os-musk os-musk commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator

Fixes #14006

Prose-only. The brand docblock in packages/core/src/security/authz-store-unavailable.ts justified its string-keyed own property with two reasons joined by an and, and only the second was true. The false half is replaced by the measured behaviour; the property and the reason that actually earns it are kept.

The claim, before

A string-keyed own property (not a Symbol.for registry key) so it survives
structuredClone, and so a duplicated copy of this module still brands
identically.

Reproduction, run on this box before the edit

Node v22.22.2, the card's three lines plus the plain-object control:

$ node -e "const e = new Error('x'); e.__brand = true; e.code = 'C';
           const c = structuredClone(e); ..."
node v22.22.2
  c.__brand = undefined
  c.code    = undefined
  c.name    = Error
  c.message = x
  (plain-object control) co.__brand = true   co.code = C
  own props on cloned Error: message,stack

The control is the load-bearing half: the same two keys both survive on { __brand: true, code: 'C' } and both vanish on an Error, so the loss is specific to Error's dedicated serialization, not general to structuredClone.

Measured again on this module's actual class shape (own name, own code / status / object, own cause), because the class is not a bare Error:

  brand   : undefined     code   : undefined    status : undefined
  object  : undefined     name   : Error        cause  : driver boom
  own props on clone: cause,message,stack

Two things that measurement changed in the wording. cause does survive, and a subclass's own name does not — it returns as 'Error'. So the docblock says the clone carries message, stack and cause, rather than repeating the sibling module's "name, message, stack and cause" list verbatim: that list is true where it is written (service-not-registered.ts deliberately does not override name, and says so) but would have been false here. Same phrasing, corrected for the one place this class differs — not a second competing claim.

Wording alignment

packages/core/src/service-not-registered.ts already records the measured behaviour for its own brand ("The brand does NOT survive structuredClone, and no claim here depends on it doing so"). This docblock opens with that same sentence and then points at the reproducible script and the Node version rather than adding a fresh unsourced prose assertion. One phrasing across the two modules. #13905 is referenced here only as context; nothing in this PR touches that module.

Call sites: why no runtime change is owed

git grep over packages and apps for rethrowAuthzStoreUnavailable / isAuthzStoreUnavailableError finds every call site in-process — .catch(rethrowAuthzStoreUnavailable) on the rest rethrow paths (packages/rest/src/rest-server.ts, packages/rest/src/package-routes.ts) and if (isAuthzStoreUnavailableError(err)) throw err; inside service and plugin catch blocks (service-datasource, service-settings, service-storage, plugin-sharing, cloud-connection). git grep for postMessage / new Worker / worker_threads under packages/core/src returns nothing. No caller crosses a clone boundary, so nothing is broken today and no behaviour needs to change.

Per the triage fence, the clone gap is deliberately not "fixed" with a toJSON or custom serialization — that would widen the module's surface with nothing pulling on it. Instead the docblock names the trap the false claim invited: branching on the brand across a worker or postMessage boundary would answer false and fail OPEN on a security path.

Changeset decision: measured, PRESENT, so a changeset is owed

The brand constant is module-private, so whether its docblock ships was a real question rather than an assumption. Built the package and grepped the emitted declarations:

phrase dist/index.d.ts dist/index.d.cts
Node 22.22.2 (unique to the new text) 1 1
fail OPEN (unique to the new text) 1 1
so it survives (the removed claim) 0 0
HTTP status an unreachable authorization store answers with (positive control) 1 1
Raised when a permission-store read FAILED (positive control) 1 1

tsup's DTS rollup inlines the private brand constant into the exported class type, carrying the docblock with it — the emitted block sits at dist/index.d.ts:2047-2069, directly above declare const AUTHZ_STORE_UNAVAILABLE_BRAND. A comment reaching the published declarations is a published byte, so .changeset/authz-brand-structured-clone-doc.md declares @objectstack/core: patch. No skip-changeset label.

Clause-② self-reading: no

Read from this diff, not from the dispatch. All 28 changed lines in the .ts file begin with * — every one is a block-comment line, zero non-comment lines moved:

$ git diff origin/main...HEAD -- packages/core/src/security/authz-store-unavailable.ts \
    | grep -E '^[+-]' | grep -vE '^(\+\+\+|---)' | grep -vE '^[+-] \*' | wc -l
0

No accept/reject behaviour, no public type change, no gate moves.

Verification, at 72a4c5df0 (git rev-parse --short HEAD after the last commit)

Gate set re-derived on the final tree with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands (no path list — the script took its own change set: 2 paths vs merge base d15ecd855). 30 commands derived; all exit codes captured after a redirect, never through a pipe.

  • 27 of 30 green (exit 0), including check:doc-authoring, check:published-files, check:test-source-alias, check:type-source-resolution, check:empty-changeset, check:changeset-no-major, check:objectui-changeset, check:dispatcher-error-vocabulary.
  • 3 NOT MEASURED (exit 3, in each gate's own words — not green, not red): check-test-completeness.mjs ("no local log to hand it... record this gate as NOT MEASURED"), pm/check-half-states.mjs (needs repo-scoped REST, which answers 403 in this container), and check:dual-build-cjs-loads ("PREREQUISITE NOT MET — this gate reads built output... 72 more" packages without dist/; a whole-repo build, which CI does and this seat did not).

27 + 3 = 30, counted from the captured logs (grep -c 'EXIT=0' / 'EXIT=3' across the three batch files) rather than by hand.

Always-runs:

  • pnpm lint (whole repo, eslint . --no-inline-config) — exit 0, full run, no narrowing.
  • pnpm check:nul-bytesexit 0 (7981 text files scanned, no raw control bytes). Also self-scanned both changed files with grep -naP before committing.
  • pnpm check:error-status-conformanceexit 0.
  • pnpm --filter @objectstack/core exec vitest run --maxWorkers=2 src/security/authz-store-unavailable.test.tsexit 0, 1 file, 37 tests passed.
  • pnpm --workspace-concurrency=2 --filter '@objectstack/core^...' --filter '@objectstack/core' buildexit 0 (the build the d.ts measurement above reads).

No ablation is owed: the diff is comment-only, so there is no guard whose removal could be shown to turn anything red.

Two things this seat could not measure, stated rather than glossed

  1. pnpm --filter @objectstack/core typecheck does not exist. The package declares only build, test and test:watch, and pnpm said so loudly (ERR_PNPM_RECURSIVE_RUN_NO_SCRIPT, exit 1) rather than silently matching zero scripts. Substituted tsc --noEmit -p tsconfig.json, which exits 2 with 98 pre-existing errors across 12 files, every one a .test.ts and none in this module — the sole log line naming authz-store-unavailable is the command echo. That red is baseline, not this diff. Filed as a separate finding: [finding] @objectstack/core declares no typecheck script, so no CI job ever type-checks it — and its own tsconfig program is currently red #14613.
  2. Stale-tree notice from the gate derivation. The branch is off d15ecd855, three commits behind origin/main at derivation time, and the tool flagged one derived-from file as changed in that range: scripts/check-skills-token-ratchet.mjs. Re-derived after git fetch origin main — byte-identical command list. That script governs skills/** only (SKILLS_DIR = join(REPO_ROOT, 'skills')), and this diff touches packages/core/** and .changeset/**, so it cannot enter this change set; it is absent from the derived 30. No rebase performed.

🤖 Generated with Claude Code

https://claude.ai/code/session_0112hMx9hjJ9BgB28X97DS68

…hz brand (#14006)

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.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0112hMx9hjJ9BgB28X97DS68
@github-actions github-actions Bot added the size/s label Sep 2, 2026
@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

⚠️ 1 changed file(s) yielded no anchor (packages/core/src/security/authz-store-unavailable.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files. Nothing else in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 1 changed package(s)).

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/core/src/security/authz-store-unavailable.ts) — pages documenting those are invisible to this run
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 24 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 79c71d29db6d015e45647ec44a48b960d53652d7packageMentionDocs.

os-musk commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator Author

Landing provenance (engine execution seat, session session_0112hMx9hjJ9BgB28X97DS68): flipped ready at 15:31Z and armed auto-merge (squash) at 15:31:11Z on head 72a4c5df0.

  • Review: ACCEPT on the card, comment 5511692589. Clause-② no, self-read from the diff and re-checked here — all 28 changed .ts lines are block-comment lines.
  • Governed-surface test on the two changed paths: 0 of 2 governed — ordinary queue landing.
  • Enqueue bar: every check run on 72a4c5df0 completed green or skipped (36 runs read at 15:31Z, perPage: 50). The last one standing was Test Core (1/6), 14:59:50Z to 15:25:41Z — 26 minutes, the long shard again; Lint & Repo Gates finished 15:15Z and Type Check · workspace 15:13Z. The window was re-armed once rather than flipped on a partial reading.
  • The auto-merge echo reads method: MERGE — the known shape whatever was passed; the queue build ref is the reading.
  • Landing owed by the seat at MERGED: verify by content on origin/main (the brand docblock no longer claims structuredClone survival, and the fail OPEN sentence is present), strip pm:dispatched from [finding] AuthzStoreUnavailableError brand doc claims structuredClone survival, which is false for an Error #14006, landing record on the card.

Generated by Claude Code

@os-musk
os-musk added this pull request to the merge queue Sep 2, 2026
Merged via the queue into main with commit e27583e Sep 2, 2026
38 checks passed
@os-musk
os-musk deleted the claude/issue-14006-authz-brand-structured-clone-doc branch September 2, 2026 16:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/s tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[finding] AuthzStoreUnavailableError brand doc claims structuredClone survival, which is false for an Error

2 participants