Skip to content

feat(objectql): publish SYSTEM_WRITE_ORGANIZATION_REQUIRED_CODE and isSystemWriteOrganizationRequiredError so consumers stop re-spelling the literal - #16156

Queued
claude[bot] wants to merge 3 commits into
mainfrom
claude/issue-14936-system-write-org-code-export
Queued

feat(objectql): publish SYSTEM_WRITE_ORGANIZATION_REQUIRED_CODE and isSystemWriteOrganizationRequiredError so consumers stop re-spelling the literal#16156
claude[bot] wants to merge 3 commits into
mainfrom
claude/issue-14936-system-write-org-code-export

Conversation

@claude

@claude claude Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Fixes #14936

Publishes the affordance that makes this class's own documented convention followable. SystemWriteOrganizationRequiredError has always said it is identified by code rather than instanceof, "so the check survives crossing a package boundary where two copies of this module can exist". The convention was right; there was nothing to import for it, so a consumer's only sound option was to re-spell the literal.

Two new exports from the package root, and nothing else changes:

  • SYSTEM_WRITE_ORGANIZATION_REQUIRED_CODE — the code as a value. The class field now reads from it, so exactly one spelling of the string remains in the package.
  • isSystemWriteOrganizationRequiredError(err): boolean — the code compare itself, so a consumer never authors the string at all.

⛔ Behaviour is untouched: same code, same 500 status, same firing conditions, same #8844 derive-or-refuse ruling. Purely additive to what the package publishes.

The generality question, answered with its measurement

The card asked whether other engine error classes carry the same "identified by code, not instanceof" convention, such that one recognizer factory would beat a bespoke predicate per error. The convention generalises. The factory does not follow from it, and I did not build one. Three measurements, all re-run here rather than recalled:

1. Census of packages/objectql/src non-test source. 18 classes extend Error; 17 carry a readonly code field — 5 via a named exported constant (the precedents triage named), 11 via an inline string literal, 1 via a typeof annotation. So the convention is real and broad.

⚠️ That census is only trustworthy on the second attempt, and the first attempt is worth recording because it is a repo-wide trap: git grep … -- 'src/**/*.ts' returned zero hits for all five named precedents. git grep applies no :(glob) magic, so ** there behaves as a single * and silently excludes src/*.ts — which is where all five actually live. A control on a known-present symbol fires 2 files under that pathspec and 4 under a plain src directory pathspec. Every count above is from the directory form.

2. Repo-wide: 14 bespoke is…Error-shaped recognizers across 8 packages, and ZERO recognizer factories. They are also not uniform in what they compare — isAuthzStoreUnavailableError (core) compares a brand key, isPermissionDeniedError (plugin-security) compares code or name or a message prefix, isUniqueViolationError / isMissingTableError (types) walk a cause chain with a depth budget. A single factory over code fits none of those three. What these predicates encode is per-error knowledge about how that error arrives, not a shared compare.

3. A factory cannot remove the literal, which is this card's actual cost. Any factory's signature is makeRecognizer(code), so every call site still supplies the code — the literal moves from the catch to the factory call rather than disappearing. What removes it is the exported constant. The factory would address none of the three costs the card names.

⇒ Shape built: the constant (the five-precedent convention, and the thing that actually removes the literal) plus one bespoke predicate on top of it (the card's named ask, and the 14-predicate convention).

Why the predicate returns boolean and deliberately does NOT narrow to err is SystemWriteOrganizationRequiredError: a code compare is satisfied by any value carrying that code, including an envelope a transport rebuilt from the wire — #5437 withholds the prose and keeps the machine-readable code. A type guard would promise object, posture and reason members such a value need not have, which moves the unsoundness one layer down instead of removing it. Pinned as a test.

The remaining 11 classes still spelling their code inline are a follow-up sweep, filed separately rather than ridden here.

Clause-② — re-derived from this diff: yes

Instrument: build at head, swap both changed sources back to the merge base, rebuild, diff every declaration file files[] publishes (files: ["dist", …] ⇒ all six, not the two barrels exports names), then classify each hunk. Restore proved byte-exact.

published declaration file result
dist/core.d.ts / dist/core.d.mts identical
dist/util-mkEsVIjj.d.ts / .d.mts (shared chunk, named by no exports entry) identical
dist/index.d.ts / dist/index.d.mts differ — 57 changed lines

Classifying the 57: all but three are JSDoc (not surface). The three declaration-text hunks are:

+ declare const SYSTEM_WRITE_ORGANIZATION_REQUIRED_CODE: "ERR_SYSTEM_WRITE_ORGANIZATION_REQUIRED";
+ declare function isSystemWriteOrganizationRequiredError(err: unknown): boolean;
  export { …, SYSTEM_WRITE_ORGANIZATION_REQUIRED_CODE, …, isSystemWriteOrganizationRequiredError, … }

The root export list gains exactly those two names and loses none. No content-hashed chunk was renamed (file sets identical both snapshots), so none of that noise class applies. ⇒ Clause-②: yes — a widened published export surface, minor changeset attached, needs:contract-review on both carriers.

⭐ Two corroborating readings worth keeping:

  • declare class SystemWriteOrganizationRequiredError is byte-identical across the ablation, readonly code: "ERR_SYSTEM_WRITE_ORGANIZATION_REQUIRED" included. So moving the field to read from the constant did not widen it to string — which matters, because the existing cross-package consumer types its own constant as SystemWriteOrganizationRequiredError['code'] and would have kept compiling while silently losing its drift protection. Pinned as a compile-time test in a file carrying no test-typecheck-debt.json entry, so a future widening is red on arrival.
  • check:api-surface is green and did not move — and that is correct here, not a failed measurement. Its own output names its subject: "@objectstack/spec public API surface + factory signatures unchanged", and build-api-surface.ts resolves PKG_DIR to packages/spec and reads that package's own exports. Its population is packages/spec only, so an @objectstack/objectql export cannot move it by construction. (The general rule still holds: a green check:api-surface is never a Clause-② no.)

Reconciling the issue-13636 branch collision

origin/claude/issue-13636-orgless-write-declaration is still opengit branch -r --contains lists only itself, so it is not in origin/main. It does touch both of my files, as warned:

  • tenancy/system-write-organization.ts — +12 lines inside the header docblock, after the Prime-Directive-12 paragraph at :67. My additions are the constant at :347 and the predicate at the end of file.
  • index.ts — its hunk edits the platform-object-tenancy.js export block (:390-397 in my numbering) and inserts a new block before :398. My name goes into the different system-write-organization.js block at :375-384.

Avoidance was structural: append at the file end and use a different export block, never edit the docblock or the block it edits. Measured rather than eyeballedgit merge-tree --write-tree HEAD origin/claude/issue-13636-orgless-write-declaration exits 0: zero conflicts. Its only notes are regeneration reminders for three generated docs files that belong to that branch's diff and appear nowhere in mine.

Verification — all at 032f9d3b1

Exit codes captured by redirect-then-read, never through a pipe.

  • pnpm --filter @objectstack/objectql exec vitest run --maxWorkers=2 src/system-write-organization.test.ts34 passed. Reconciled so the green is not vacuous: 20 pre-existing (12 plain it + 4 it.each × 2) + 14 new (6 plain + one 8-row it.each) = 34 exactly.
  • pnpm --filter @objectstack/objectql typecheck0, including check:test-typecheck: "44 file(s) / 242 error(s)", exactly the ledger's recorded baseline ⇒ the new test file added zero type errors and stays unledgered.
  • check:error-code-provenance (run explicitly — it is not derived by dispatch-gates, [finding] dispatch-gates --commands does not derive check:optional-error-sink for a diff that adds a new logger-sink interface #14880; the derivation places it in the "artifact rosters" block whose silence is a fact about a list, not a clearance) → 0. Proof it is about this change rather than a population excluding it, from --report: [listed] @objectstack/objectql → ERR_SYSTEM_WRITE_ORGANIZATION_REQUIRED (constdef) packages/objectql/src/tenancy/system-write-organization.ts:347. The new *_CODE constant is a stamp site, it is seen, and it is [listed] under this package's own owner key — objectql already registers the code (System-context writes land untenanted at RUNTIME, so a single-tenant install keeps re-forking the autonumber scope and minting duplicate business identifiers — the producer #8686's backfill cannot reach (17.0.0 GA) #8844), so no ledger edit and no waiver is needed. The *_CODE name is load-bearing for exactly this reason and the docblock says so.
  • dispatch-gates.mjs --repo objectstack-ai/objectstack derived 56 families from the real changeset (4 paths, three-dot semantics). Green here: check:dispatcher-error-vocabulary, nul-bytes, engine-double-contract, where-matcher, query-options-erasure, published-files, test-source-alias, type-source-resolution, cross-package-test-inputs, type-check-coverage, driver-memory-census, comment-mask-adoption, undeclared-dep-imports, platform-object-tenancy-census, changeset-gate-self-tests, objectui-changeset, check-empty-changeset, check-changeset-no-major, check-adr-0087-registration.
  • check:system-context-census0, run deliberately: this diff changes line counts in packages/**, and a line-number-anchored census is exactly the family that a "my diff is small" narrowing misses.
  • check:dts-closure0, and its green covers this package: it swept 15 built packages, which is every dist/ on disk, packages/objectql/dist among them.
  • check:dual-build-cjs-loads — NOT MEASURED, not green. Exit 3 is the gate's own PREREQUISITE NOT MET — this gate reads built output, and some package has no dist/ … ⛔ This is NOT a pass: nothing was measured. It wants a whole-repo pnpm build; CI runs it on this PR. Recorded as absent rather than banked.

Declared narrowing: repo-wide sweeps (pnpm lint over the whole tree, the full pnpm build that dual-build-cjs-loads needs) were not run locally; CI runs the farm exactly once regardless. Everything above is the targeted half.

Relationship to the fast path — the fast path landed FIRST

Measured on origin/main, not inferred: PROVENANCE_WAIVERS in packages/spec/src/api/error-code-ledger.zod.ts already carries the @objectstack/plugin-sharing / ERR_SYSTEM_WRITE_ORGANIZATION_REQUIRED entry (adjudicated on #14937, maintainer ruling A, 2026-09-04), and plugin-sharing/src/sharing-rule-service.ts:137 already declares ENGINE_ORGANIZATION_REFUSAL_CODE. So #14935's bookkeeping answer is in the tree ahead of this root-cause one.

That waiver's own text says it is "removed together with the stamp site when #14936 lands and objectql publishes a recognizer". ⛔ This PR does not do that removal: it spans packages/spec and plugin-sharing, both outside this card's landing surface, and the waiver is held live three ways — removing the consumer's stamp site without removing the waiver reddens the gate in the other direction. Filed as follow-up work; #14935 is not addressed here and stays open for its lane to close.


Generated by Claude Code

…sSystemWriteOrganizationRequiredError (#14936)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ARYe3yQTQCUFm5qPYNgKaJ
…iscriminating controls (#14936)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ARYe3yQTQCUFm5qPYNgKaJ
@github-actions

github-actions Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/objectql, touching 4 documentable anchor(s). ⚠️ 1 changed file(s) yielded no anchor (packages/objectql/src/index.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

1 release-owned page(s) name something this change touched. These are read-only:

  • content/docs/releases/v17.mdx (via ERR_SYSTEM_WRITE_ORGANIZATION_REQUIRED (literal, a string literal in SYSTEM_WRITE_ORGANIZATION_REQUIRED_CODE; a string literal in SystemWriteOrganizationRequiredError))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/objectql/src/index.ts) — pages documenting those are invisible to this run
  • the SDK route bridge reached 61 of 219 client-bound route-ledger rows — the other 158 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 158: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • 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 — 16 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 94ecb7e5c4accdcab4c61e4c3525116f1a225f4fpackageMentionDocs.

Which tree this was computed on

This run read content/docs from 5569b1aa4d8a3589d0006db1ffbc150565deddf4 — the merge of head a4255ac68e7f418f7c71b098376bd38a5b603c4d into base 94ecb7e5c4accdcab4c61e4c3525116f1a225f4f, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 5569b1aa4d8a3589d0006db1ffbc150565deddf4 && git checkout 5569b1aa4d8a3589d0006db1ffbc150565deddf4
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 94ecb7e5c4accdcab4c61e4c3525116f1a225f4f a4255ac68e7f418f7c71b098376bd38a5b603c4d && git checkout -B drift-repro 94ecb7e5c4accdcab4c61e4c3525116f1a225f4f && git merge --no-ff a4255ac68e7f418f7c71b098376bd38a5b603c4d

node scripts/docs-audit/affected-docs.mjs --json 94ecb7e5c4accdcab4c61e4c3525116f1a225f4f

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 94ecb7e5c4accdcab4c61e4c3525116f1a225f4f → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@claude

claude Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor Author

Docs sweep — answering the Docs Drift Check's declared blind spot

The drift run reported 4 documentable anchors and one uncovered file: ⚠️ "1 changed file(s) yielded no anchor (packages/objectql/src/index.ts), so the pages documenting them are NOT COVERED by this run". That file is where this PR's whole deliverable lands, so the run has no opinion on the surface being widened. Hand-searched instead. Probes, controls and counts below; ⛔ nothing here was read as coverage.

All pathspecs are plain directory forms. (The ** trap is real — git grep applies no :(glob) magic — and it did fire on this card: git grep -- 'packages/objectql/src/**/*.ts' returned zero hits for all five precedent constants, which live in src/*.ts; the directory form found them. It does not bite inside content/docs, measured: content/docs/**/*.mdx and content/docs both return 12 files for @objectstack/objectql.)

probe result
ERR_SYSTEM_WRITE_ORGANIZATION_REQUIRED or SystemWriteOrganizationRequiredError in content/docs 3 hits, all analysed below
pages naming any of the 5 existing *_CODE constants 0 files each (5 probes)
pages importing from @objectstack/objectql 3 files
pages using instanceof 5 files, 14 sites
control: @objectstack/objectql anywhere in content/docs fires — 12 files
control: objectql case-insensitive fires — 108 files

⚠️ One probe returned zero without a firing control and is reported as such rather than as a clean result: no *_CODE constant of this package appears in content/docs at all, so "no page enumerates objectql's exported code constants" is the honest reading — there is no positive instance of that pattern anywhere to control against.

The three hits, and why none is falsified

  1. content/docs/references/api/contract.mdx:162 and 2. content/docs/references/api/error-code-ledger.mdx:278 — both generated (the merge driver names them: "not text-merged — it is generated … pnpm --filter @objectstack/spec gen:docs"). They render the ledger in packages/spec, and this PR's change set is 4 paths, none in packages/spec. The code was already registered under @objectstack/objectql (System-context writes land untenanted at RUNTIME, so a single-tenant install keeps re-forking the autonumber scope and minting duplicate business identifiers — the producer #8686's backfill cannot reach (17.0.0 GA) #8844) and this PR neither adds nor moves a ledger row ⇒ their generated content cannot differ.
  2. content/docs/releases/v17.mdx:4507 — ⛔ read-only, never edited in a code PR. It describes the refusal's behaviour (ERR_SYSTEM_WRITE_ORGANIZATION_REQUIRED, status 500), and this PR changes no behaviour, no code and no status. Not falsified; nothing to file.

The advice question — the interesting one, and the answer is "no page, but a neighbouring one"

⭐ Exactly one page teaches this convention, and it does so correctlycontent/docs/kernel/contracts/data-engine.mdx:361:

Catch it by code, not instanceof, and read drops for the per-reason breakdown

That sentence is about ReadonlyFieldRejectedError / ERR_READONLY_FIELD_REJECTED, not this card's refusal — the page does not mention the system-write refusal anywhere (probe returned zero). So this PR falsifies nothing there, and no page anywhere advises instanceof for this error or tells a reader to re-spell this literal.

⚠️ But that page is a real finding in its own right, and it is filed rather than fixed here: ERR_READONLY_FIELD_REJECTED is one of the 11 objectql error codes still spelled as an inline literal with no constant to import, so the published guidance ("catch it by code") and the published surface (nothing to import for it) disagree today, in the docs' own words. That is #16159, together with the full census. ⛔ Out of scope for this PR: fixing it would mean widening a second package surface, a second Clause-② decision on a card that is not this one.

Nothing to change in content/docs for this PR, and one non-release page recorded as an independent finding rather than silently absorbed.


Generated by Claude Code

Copy link
Copy Markdown
Contributor

Contract review (clause ②) — PASS — PR #16156 at head 032f9d3b (Fixes #14936)

Director seat, summon #15, session_01TezFG8ZMrNH6n5VTNpPpdH (os-zhuang), 2026-09-06T03:40Z, batch review under the maintainer's 「按批次执行完所有的契约复审」. Tier fuse: get_session this session reads session_context.model = last_served_model = CONTRACT_REVIEW_TIER. Readings from the PR diff (4 files), card #14936 + evidence 5534764504 + triage 5545448600; the seat's dispatch 5556485939 and claim 5556509829 read afterwards as cross-check.

Implemented-by: session_01ARYe3yQTQCUFm5qPYNgKaJ os-dev round (branch claude/issue-14936-system-write-org-code-export)
Reviewed-by: session_01TezFG8ZMrNH6n5VTNpPpdH

Clause ② standing — yes (limb 1, additive), correctly declared

Two new root-entry exports of @objectstack/objectql: SYSTEM_WRITE_ORGANIZATION_REQUIRED_CODE (value) and isSystemWriteOrganizationRequiredError(err): boolean. Measured on all six published declaration files: root barrels +2 names, nothing removed, shared chunk and core entry byte-identical. Limb 2: no — same code, same 500, same firing conditions, #8844 ruling untouched.

① Derived judgments

# claim reading verdict
1 Cross-realm instanceof is unsound (dual exports, two module copies); the class's own docblock mandates code; the missing piece is the affordance Card's measurement (A===B false, cross-realm instanceof false, code compare true) is the whole card; the fix ships the affordance and nothing else. correct
2 Generality question answered with measurement, not built as a factory: 17/18 objectql error classes carry code (5 via named constant, 11 inline, 1 typeof); 14 bespoke recognizers repo-wide compare different things; a factory cannot remove the literal Reasoning holds — the constant is what removes the second spelling; the 11 inline classes are a follow-up sweep, filed not folded. correct
3 Predicate returns boolean, deliberately not a type guard Right: a transport-rebuilt envelope carries the code without object/posture/reason; a guard would relocate the unsoundness. Pinned. correct
4 readonly code = SYSTEM_WRITE_ORGANIZATION_REQUIRED_CODE keeps the literal type (declaration byte-identical); the cross-package consumer typing SystemWriteOrganizationRequiredError['code'] keeps its drift protection; compile-time pin added Measured via the .d.ts ablation. correct
5 check:error-code-provenance sees the new *_CODE constdef as a [listed] stamp site under objectql's own owner key — no ledger edit, no waiver Run explicitly (the gate is not derived by dispatch-gates); the name shape is load-bearing and the docblock says so. correct
6 Collision with origin/claude/issue-13636-… avoided structurally; git merge-tree 0 conflicts Measured. accepted
7 The #14935 fast-path waiver in packages/spec is left in place (removing it spans two other lanes) Correct fence; the waiver's own text names #14936 as its retirement trigger — the services/spec lanes close it. accepted

② semver

@objectstack/objectql minor — additive published exports, the five-precedent shape. Correct.

Evidence and landing

Checks on 032f9d3b: 12 success / 6 skipped / 16 in progress at review time; check-governed-merges --test on the 4 paths: 0 hits. Clearing, same stroke: needs:contract-review off #14936 and PR #16156 with provenance. Landing (ready + auto-merge SQUASH) from this seat once every check is green — at its next check-in if the dispatching seat has not.


Generated by Claude Code

…tant instead of spelling it (#14936)

A lowercase error-code literal in a `code` position is an ADR-0112 D1 finding,
and `check:error-code-casing` cannot tell a negative fixture from a real
emission - it classified this site as `(emission)`. Deriving the value from
SYSTEM_WRITE_ORGANIZATION_REQUIRED_CODE is not an opt-out: the gate's own output
records that a code value with no literal at the position is out of reach for
its patterns by construction. No `adr0112-ok:` suppression was added (the count
stays 17) and KNOWN_LOWERCASE_CODES is untouched. It also makes the fixture
track the constant rather than restate it - this card's own argument about
consumers re-spelling literals, applied to its own test.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ARYe3yQTQCUFm5qPYNgKaJ
@claude

claude Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor Author

Red Lint & Repo Gates fixed — check:error-code-casing, head now a4255ac68

The finding was mine and real, and the gate's self-test passed (46 recognizer + 5 registry cases), so this was never a broken gate:

packages/objectql/src/system-write-organization.test.ts:475  'err_system_write_organization_required'  (emission)

That line was a deliberate negative fixture proving the recognizer does not match a wrong-cased code. The gate cannot tell a negative fixture from a real emission — it classified the site as (emission) — and ADR-0112 D1 makes a lowercase literal in a code position a finding regardless.

Reproduced first, then fixed. pnpm check:error-code-casing at 032f9d3b1REAL_EXIT=1, same line. After the fix → REAL_EXIT=0, "no unlisted lowercase error codes in 5582 scanned file(s)". Exit codes captured by redirect-then-read.

The fix, and why this option

Chose (b) — restructure so the value is not a literal, over (a) an adr0112-ok: suppression:

['the code in the wrong case', Object.assign(new Error('x'), {
  code: SYSTEM_WRITE_ORGANIZATION_REQUIRED_CODE.toLowerCase(),
})],

Three reasons, the first being the one that makes this not a dodge:

  1. The gate's own output declares this shape out of reach BY CONSTRUCTION, not opted out of: "a code value with NO literal at the position — a constant, a template, a ternary, a helper parameter — is out of reach for every pattern here." So the gate keeps its full coverage of literals; there is simply no literal here any more.
  2. It makes the fixture track the constant instead of restating it — which is precisely this PR's own argument about consumers re-spelling literals, applied to its own test. The fixture can no longer drift from what the engine throws.
  3. It costs no clarity: SYSTEM_WRITE_ORGANIZATION_REQUIRED_CODE.toLowerCase() says "the code in the wrong case" more directly than a hand-typed lowercase string did.

No escape hatch was taken, and both are verifiable: the adr0112-ok: suppression count is 17 before and 17 after (I added none), and scripts/check-error-code-casing.mjs is untouched — KNOWN_LOWERCASE_CODES was never a candidate, since it only shrinks and defers real lowercase codes to their owning card.

Re-verification at the new head

  • src/system-write-organization.test.ts34 passed, unchanged: the edit swaps one fixture value, it adds and removes no case. The 20 + 14 reconciliation still holds.
  • pnpm --filter @objectstack/objectql typecheck0, check:test-typecheck still "44 file(s) / 242 error(s)" — the exact ledger baseline, so the derived expression introduced no type error in a file that carries no debt entry.
  • 19 gates re-run green, including the anchor/census family deliberately (a test file's line count moved, and a line-number-anchored census is exactly the family a "my diff is only a fixture" narrowing misses): error-code-casing, system-context-census, error-code-provenance, dispatcher-error-vocabulary, engine-double-contract, where-matcher, query-options-erasure, cross-package-test-inputs, type-check-coverage, nul-bytes, test-source-alias, type-source-resolution, published-files, dts-closure, objectql-double-limit, platform-object-tenancy-census, comment-mask-adoption, check-empty-changeset, check-changeset-no-major.

Clause-② is unmoved, and that is measured rather than argued

All six published declaration files built at a4255ac68 are byte-identical to the snapshot taken at 032f9d3b1 (core.d.ts/.d.mts, index.d.ts/.d.mts, and the shared chunk util-mkEsVIjj.d.ts/.d.mts). dist/ contains 0 test artifacts. ⇒ the verdict stays Clause-②: yes on exactly the surface already measured; this commit widened nothing further.

⚠️ The re-hang was owed and has been done. A contract review cleared needs:contract-review from both carriers at 2026-09-06T03:42:55Z, and this push moved the head to 04:04:22Z — so the clear no longer covered what would land, and check-clause2-carriers reported exit 4 for exactly that reason ("the gate WAS bound and cleared … but the PR's head has MOVED since"). Re-hung on both carriers, each with a comparative read-back proving union(read, target) exactly and nothing stripped. ⭐ Worth noting the additive endpoint earned its keep here: the PR had meanwhile gained documentation, tests and tooling from the auto-labeler, and all three survived — a whole-set PUT would have clobbered them. check-clause2-carriers --pair 16156 from a fresh origin/main worktree (checker blob 751b4a6e4fbd…, proven identical to origin/main) now reports REAL_EXIT=0.


Generated by Claude Code

Copy link
Copy Markdown
Contributor

Contract review (clause ②) — PASS extended to head a4255ac6 (Fixes #14936)

Director seat, summon #15, session_01TezFG8ZMrNH6n5VTNpPpdH (os-zhuang), 2026-09-06T04:29Z. Extends PASS 5556680957 (head 032f9d3b).

Delta read (032f9d3b…a4255ac6, one commit, test-only)

packages/objectql/src/system-write-organization.test.ts +11/−1: the wrong-case negative fixture is now SYSTEM_WRITE_ORGANIZATION_REQUIRED_CODE.toLowerCase() instead of a spelled lowercase literal — which check:error-code-casing (ADR-0112 D1) had classified as an emission and reddened Lint & Repo Gates. Deriving the fixture from the constant is not an opt-out of the gate (no literal sits at the code position by construction) and it is the card's own argument applied to its own test. No source, export or changeset moved. Verdict unchanged.

Evidence and landing

Checks on a4255ac6: 31 success / 0 red / 0 pending. The push hook re-hung needs:contract-review on both carriers; stripped again this stroke with this extension as provenance; check-clause2-carriers --pair 16156 then ready + auto-merge SQUASH from this seat (0 governed paths).


Generated by Claude Code

@os-zhuang
os-zhuang marked this pull request as ready for review September 6, 2026 04:30
@os-zhuang
os-zhuang added this pull request to the merge queue Sep 6, 2026
Any commits made after this event will not be merged.
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/m tests tooling

Projects

None yet

2 participants