Skip to content

objectql: 11 error classes still spell their code as an inline literal, so a consumer cannot follow the by code, not instanceof convention the docs already teach #16159

Description

@claude

Filed by the domain:engine execution seat from a census taken while building #16156 (the recognizer for SystemWriteOrganizationRequiredError). ⛔ Observation, not a runtime defect — no assignee, no pm:queue; triage grades it.

The census

packages/objectql/src, non-test source. 18 classes extend Error; 17 carry a readonly code field. Of those:

file:line code
driver-connect-errors.ts:99 ERR_DRIVER_CONNECT
driver-connect-errors.ts:174 ERR_DATASOURCE_UNAVAILABLE
readonly-strict-errors.ts:161 ERR_READONLY_FIELD_REJECTED
registry.ts:1292 NAMESPACE_CONFLICT
registry.ts:1404 DUPLICATE_ARTIFACT_OBJECT_NAME
registry.ts:1463 OBJECT_OWNERSHIP_CONFLICT
summary-errors.ts:24 ERR_SUMMARY_RECOMPUTE
transaction-errors.ts:26 ERR_TRANSACTION_UNSUPPORTED
transaction-errors.ts:64 ERR_CROSS_DATASOURCE_TRANSACTION_WRITE
validation/record-validator.ts:136 VALIDATION_FAILED
tenancy/system-write-organization.ts ERR_SYSTEM_WRITE_ORGANIZATION_REQUIREDdischarged by #16156

Why this is worth a card rather than a shrug

The reasoning is the one #14936 established, and it applies unchanged to each row: @objectstack/objectql declares both realms in its own exports (import to dist/index.mjs, require to dist/index.js), so a consumer holding the other realm's copy gets instanceof false — measured, and silent. A consumer's only sound option for these 11 is to re-spell the literal, which is what check:error-code-provenance counts as a stamp site in the consumer's own package, and which can drift from what the engine throws with no compile error.

The docs already teach the convention while offering nothing to import. content/docs/kernel/contracts/data-engine.mdx:361 says, of ReadonlyFieldRejectedError:

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

and ERR_READONLY_FIELD_REJECTED is row 3 above — an inline literal with no exported constant. So the published guidance and the published surface disagree today, for at least one error, in the documentation's own words.

Measured alongside: none of the five existing *_CODE constants appears anywhere in content/docs (0 files each; control @objectstack/objectql fires on 12 files). So no page enumerates these, and no page becomes stale by adding one.

Suggested shape, for triage rather than a ruling

Per-error convergence onto the existing five-precedent shape — export const X_CODE = '…' as const; with the class field reading from it, re-exported from index.ts. ⛔ Deliberately not one recognizer factory: #16156 measured that a factory's signature (makeRecognizer(code)) still requires every call site to supply the code, so it relocates the literal rather than removing it; and the 14 bespoke recognizers already in the repo across 8 packages compare three different things (a brand key, code-or-name-or-message, a cause walk with a depth budget), which no single factory fits.

⚠️ Each row is a published-surface widening and therefore its own Clause-② decision plus a minor changeset — which is why this is a sweep to be scheduled and graded, not a rider on any one PR. Splitting it per file (or per error family) is probably right.

⚠️ Two rows deserve checking before conversion rather than mechanical treatment: EMPTY_CREDENTIAL_REFUSAL_CODE is already 'VALIDATION_ERROR' and record-validator.ts uses 'VALIDATION_FAILED' — two different spellings of a validation refusal in one package, and whether they should converge is a question this card does not answer.

Refs: #14936 · #16156 · content/docs/kernel/contracts/data-engine.mdx · packages/spec/scripts/check-error-code-provenance.ts


Generated by Claude Code

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions