From 23cc629a4724765e24ba598314e3b87732a2cbf4 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 30 Aug 2026 13:34:35 +0000 Subject: [PATCH 1/2] fix(driver-mongodb): refuse the rejected alias `reference_to` at the schema door `syncCollectionSchema` gated its field-level join index on `field.reference_to`. `reference` is the only relationship spelling `@objectstack/spec` declares; `reference_to` is a rejected alias (`FieldSchema` answers `unrecognized_keys` for it on any field type, carrying any value). So one key had two doors with opposite answers, and the silent one was the one that touched the database. The door is stated ahead of `createCollection` and ahead of every per-field branch, matching the placement `sql-driver.ts` uses for its copy: the spec's verdict is gated on neither the field's type nor the key's value, so neither may gate the driver's. Predicate is `!== undefined`, identical to the SQL door. The join-index arm is left byte-identical and its `field.reference_to` conjunct is now unreachable, deliberately: deleting it would start building `idx_FIELD_lookup` for canonical `reference` lookups, which is a boot-time behaviour change for existing deployments and a separate, still-open ruling. Graded `minor`, not `patch`: this package's published README taught the key in a sample calling `driver.syncSchema` directly, and `syncSchema` forwards verbatim with no Zod, so the affected population is non-zero by construction. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01F3jdziLbAPGeceVNmSox5L --- ...godb-refuse-rejected-reference-to-alias.md | 57 +++++ content/docs/protocol/objectql/types.mdx | 25 +- ...mongodb-13222-reference-to-refusal.test.ts | 226 ++++++++++++++++++ .../driver-mongodb/src/mongodb-schema.ts | 125 +++++++++- 4 files changed, 427 insertions(+), 6 deletions(-) create mode 100644 .changeset/mongodb-refuse-rejected-reference-to-alias.md create mode 100644 packages/drivers/driver-mongodb/src/mongodb-13222-reference-to-refusal.test.ts diff --git a/.changeset/mongodb-refuse-rejected-reference-to-alias.md b/.changeset/mongodb-refuse-rejected-reference-to-alias.md new file mode 100644 index 0000000000..6a3ec93e33 --- /dev/null +++ b/.changeset/mongodb-refuse-rejected-reference-to-alias.md @@ -0,0 +1,57 @@ +--- +"@objectstack/driver-mongodb": minor +--- + +fix(driver-mongodb): refuse the rejected alias `reference_to` at the schema door instead of honouring it (#13222) + +`syncCollectionSchema` gated its field-level join index on `field.reference_to`. +`reference` is the only relationship spelling `@objectstack/spec` declares — +`reference_to` is a **rejected alias**, answered by `FieldSchema` with +`unrecognized_keys` and *"Did you mean `reference_to` → `reference`?"* — so one +key had two doors with opposite answers, and the silent one was the one that +touched the database. + +A field still carrying `reference_to` when it reaches schema sync now throws +`VALIDATION_ERROR`/400 naming it as a rejected alias of `reference`, in the same +words `FieldSchema` uses. The refusal is stated ahead of `createCollection` and +ahead of every per-field branch, because the spec's verdict is gated on neither +the field's type nor the key's value: `{ type: 'text', reference_to: 'x' }` is +refused exactly as the `lookup` fixture is, and `'company'`, `null` and `''` +alike. One key, one answer, on both doors — this is the same door +`@objectstack/driver-sql` grew in #11567. + +**⚠️ Upgrade note — this IS a behaviour change for a real, non-zero population, +which is why it is graded `minor` and not `patch`.** #11567 could grade the SQL +half `patch` on "no authored deployment could reach the branch". That reasoning +does **not** transfer here: this package's own published `README.md` taught +`reference_to`, in a sample calling `driver.syncSchema(...)` **directly** — + +```typescript +company_id: { type: 'lookup', reference_to: 'company' } // what the README taught +``` + +— and `syncSchema(object, schema: unknown)` casts and forwards that metadata +**verbatim**, with no Zod, no normalisation and no key filtering. `README.md` is +in the package's `files` array, so it shipped to npm at +`@objectstack/driver-mongodb` **17.2.0 and every earlier version**. A deployment +that copied that sample boots today and, after this release, is refused at the +schema-sync door. The affected population is therefore non-zero **by +construction**, not by speculation — and it is not measurable from inside this +repo. There is deliberately **no deprecation window**: a warn-and-continue +release would be a third answer to a key the schema has always refused. + +Fix, if you have such metadata — the same rename the schema has always asked for: + +| Wrote | Write instead | +|---|---| +| `{ type: 'lookup', reference_to: 'company' }` | `{ type: 'lookup', reference: 'company' }` | + +The README no longer teaches the key; its remaining mention is prose recording +that the spelling is refused. + +**What this does NOT change.** No index is added, removed or renamed. A `user` +field still gets `idx_FIELD_lookup`; a canonically-spelled `reference` lookup +still gets none. Renaming the key therefore does not, by itself, produce a join +index — whether it should is a separate open question, because starting to build +that index changes boot behaviour for deployments already holding large +collections. It is tracked apart from this release on purpose. diff --git a/content/docs/protocol/objectql/types.mdx b/content/docs/protocol/objectql/types.mdx index 9a58524b20..a79d8c84dd 100644 --- a/content/docs/protocol/objectql/types.mdx +++ b/content/docs/protocol/objectql/types.mdx @@ -731,11 +731,26 @@ contacts: A relationship field authored with `reference:` gets **no database-level -`FOREIGN KEY` constraint**. The SQL driver's FK DDL is gated on a `reference_to` -property that the spec's `reference` never populates, and `master_detail` / -`tree` do not reach that branch at all. Referential integrity is enforced by the -**engine** instead: `deleteBehavior` is applied on delete, which is what produces -the `409 DELETE_RESTRICTED` above. +`FOREIGN KEY` constraint** and **no MongoDB join index**. Both gaps have one +root cause: the driver branches that would have built them were gated on +`reference_to` — a key the spec REFUSES (`FieldSchema` answers +`unrecognized_keys` for it, on any field type) and one that `reference` never +populates. `master_detail` / `tree` did not reach either branch at all. + +- **SQL:** the `FOREIGN KEY` DDL is retired (#11567). A field that still carries + `reference_to` when it reaches DDL is refused at the driver's door, in the + schema's own words (`400 VALIDATION_ERROR`), instead of silently changing the + physical schema. +- **MongoDB:** the field-level join index `idx_FIELD_lookup` is gated on that + same refused key, so a canonically-spelled `reference` lookup is **not + indexed**. A `user` field still is — that arm needs no relationship key, which + is why the feature looked healthy. `reference_to` is refused at this driver's + door too, with the same verdict (#13222); whether a canonical `reference` + lookup should start building the index is tracked separately, because it + changes boot behaviour for deployments that already hold large collections. + +Referential integrity is enforced by the **engine** instead: `deleteBehavior` is +applied on delete, which is what produces the `409 DELETE_RESTRICTED` above. --- diff --git a/packages/drivers/driver-mongodb/src/mongodb-13222-reference-to-refusal.test.ts b/packages/drivers/driver-mongodb/src/mongodb-13222-reference-to-refusal.test.ts new file mode 100644 index 0000000000..da1a9cd486 --- /dev/null +++ b/packages/drivers/driver-mongodb/src/mongodb-13222-reference-to-refusal.test.ts @@ -0,0 +1,226 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. +// +// #13222 part (1) — `syncCollectionSchema` REFUSES `reference_to` at the door. +// +// `reference` is the only relationship spelling `@objectstack/spec` declares. +// `reference_to` is a REJECTED ALIAS: `FieldSchema` answers `unrecognized_keys` +// for it on any field type, carrying any value. Until this door, this driver +// read `reference_to` and only `reference_to` as the gate on its field-level +// join index — so one key had two doors with opposite answers, and the silent +// one was the one that touched the database. +// +// Driven against a fake `Db`, deliberately: this package's real-server suite is +// OPT-IN (`describe.skipIf(!sharedMongod)`, `OS_TEST_MONGODB_MEMORY_SERVER_ENABLED=1`, +// #5517's ~123 MB download), so an assertion parked there runs on no ordinary CI +// lane — which is exactly the lane that has to notice if this regresses. The +// recorder below is the same narrow slice of `Db` that +// `mongodb-schema-declared-indexes.test.ts` records against; it is duplicated +// rather than imported because neither file exports it, and a shared fixture +// module between two suites that pin OPPOSITE halves of one arm would couple +// them for no gain. +// +// ⛔ NOT pinned here: whether a canonically-spelled `reference` lookup should +// GET `idx_FIELD_lookup`. That is part (2) of #13222 — a separate, still-open +// ruling (it is a boot-time behaviour change for existing deployments: index +// builds on large collections). The last case below is this PR's own NO-CHANGE +// control for it, and is expected to flip in the PR that takes part (2), +// alongside `mongodb-schema-declared-indexes.test.ts`'s #12252 pin, which owns +// the fact. + +import { describe, it, expect } from 'vitest'; +import type { Db } from 'mongodb'; +import { syncCollectionSchema } from './mongodb-schema.js'; + +interface CreatedIndex { + spec: Record; + options: Record; +} + +/** + * The narrow slice of `Db` `syncCollectionSchema` touches, recording every + * `createCollection` and `createIndex` call in order. Nothing is stubbed beyond + * that slice — the function under test runs verbatim. + */ +function fakeDb(existingCollections: string[] = []) { + const created: CreatedIndex[] = []; + const collectionsCreated: string[] = []; + const db = { + listCollections: ({ name }: { name: string }) => ({ + toArray: async () => (existingCollections.includes(name) ? [{ name }] : []), + }), + createCollection: async (name: string) => { + collectionsCreated.push(name); + }, + collection: () => ({ + createIndex: async (spec: Record, options: Record) => { + created.push({ spec, options }); + }, + }), + } as unknown as Db; + return { db, created, collectionsCreated }; +} + +/** Every index name the sync asked MongoDB to create, core indexes included. */ +const names = (created: CreatedIndex[]) => created.map((c) => c.options.name); + +/** The ADR-0112 envelope this refusal is required to speak. */ +interface CodedError { + code?: string; + status?: number; + message: string; +} + +/** Run the sync and hand back the rejection, or fail loudly if there wasn't one. */ +async function refusalFrom(fields: Record) { + const { db, created, collectionsCreated } = fakeDb(); + let caught: CodedError | undefined; + try { + await syncCollectionSchema(db, 'lead', { + name: 'lead', + fields: fields as Parameters[2]['fields'], + }); + } catch (error) { + caught = error as CodedError; + } + expect(caught, 'syncCollectionSchema was expected to refuse and did not').toBeDefined(); + return { err: caught as CodedError, created, collectionsCreated }; +} + +describe('#13222 part (1) — driver-mongodb refuses `reference_to` at the schema door', () => { + it('refuses with the ADR-0112 envelope, not a bare throw', async () => { + // ⚠️ `code` + `status` are the assertion, not `.toThrow()`. A bare + // `toThrow()` would stay green against an unrelated `Error` from anywhere + // else in the sync — including the very silence this door replaces, had it + // failed for some other reason. + const { err } = await refusalFrom({ + company_id: { type: 'lookup', reference_to: 'company' }, + }); + + expect(err.code).toBe('VALIDATION_ERROR'); + expect(err.status).toBe(400); + }); + + it("states the refusal in `FieldSchema`'s own words, and names the field", async () => { + // The wording IS the contract here: the ruling is "one key, one answer, on + // both doors", so this door has to hand back the same verdict and the same + // one-word remedy the authoring door does — not a driver-flavoured paraphrase. + const { err } = await refusalFrom({ + company_id: { type: 'lookup', reference_to: 'company' }, + }); + + expect(err.message).toContain('[driver-mongodb]'); + expect(err.message).toContain("field 'company_id' on 'lead'"); + expect(err.message).toContain('rejected alias'); + expect(err.message).toContain('reference_to` -> `reference'); + // The spec's own verdict word, so a reader can match this against the + // `FieldSchema` failure they may already be holding. + expect(err.message).toContain('unrecognized_keys'); + }); + + it('refuses on ANY field type — the door is gated on the key, not the type', async () => { + // Measured on `@objectstack/spec`: `{ type:'text', reference_to:'company' }` + // draws the SAME `unrecognized_keys` verdict as the `lookup` fixture, so a + // door gated on `type === 'lookup'` would answer differently from the schema + // for every other type. `sql-driver.ts` states its copy before the type + // switch for exactly this reason; this file has no type switch, so the + // equivalent placement is ahead of the whole field loop. + for (const type of ['text', 'string', 'user', 'number', undefined]) { + const { err } = await refusalFrom({ company_id: { type, reference_to: 'company' } }); + expect(err.code, String(type)).toBe('VALIDATION_ERROR'); + expect(err.status, String(type)).toBe(400); + } + }); + + it('refuses a `multiple` field too — no short-circuit gets past the door', async () => { + // The SQL door's stated hazard, transplanted: a multi-value lookup returned + // from `createColumn` immediately and used to carry the key straight past + // that seam. Nothing here may acquire the same shape. + const { err } = await refusalFrom({ + company_ids: { type: 'lookup', multiple: true, reference_to: 'company' }, + }); + + expect(err.code).toBe('VALIDATION_ERROR'); + expect(err.status).toBe(400); + }); + + it('refuses every value the key can carry, including `null` and the empty string', async () => { + // The predicate is `!== undefined`, not truthiness. Measured on + // `FieldSchema`: `'company'`, `null` and `''` all draw one identical + // `unrecognized_keys` verdict — so a truthy gate would have let two of the + // three shapes the schema refuses walk through this door. + for (const value of ['company', null, '', 0, false]) { + const { err } = await refusalFrom({ company_id: { type: 'lookup', reference_to: value } }); + expect(err.code, JSON.stringify(value)).toBe('VALIDATION_ERROR'); + expect(err.status, JSON.stringify(value)).toBe(400); + } + }); + + it('touches NOTHING on the database when it refuses', async () => { + // The refusal is stated ahead of `createCollection`, so a refused sync does + // not leave a collection (or a partial index set) behind for the next boot + // to find. "Before the collection exists, not after documents are in it." + const { created, collectionsCreated } = await refusalFrom({ + name: { type: 'string', unique: true }, + company_id: { type: 'lookup', reference_to: 'company' }, + owner_id: { type: 'user' }, + }); + + expect(collectionsCreated).toEqual([]); + expect(names(created)).toEqual([]); + }); + + it('lets an explicit `{ reference_to: undefined }` through, exactly as the SQL door does', async () => { + // `!== undefined` rather than `'reference_to' in field` — the narrower of + // two correct predicates, and BOTH doors take the same one. Measured: + // `FieldSchema`'s own canonical output does not carry `reference_to` as an + // own key, so a producer spreading canonical output can never trip this; + // a producer spreading an explicit `undefined` is not writing a + // relationship, and refusing it would be the two doors disagreeing again, + // in the other direction. + const { db, created, collectionsCreated } = fakeDb(); + await syncCollectionSchema(db, 'lead', { + name: 'lead', + fields: { company_id: { type: 'lookup', reference_to: undefined } }, + }); + + expect(collectionsCreated).toEqual(['lead']); + expect(names(created)).toEqual(['idx_id_unique', 'idx_created_at', 'idx_updated_at']); + }); + + it('leaves the join-index arm exactly as it was — part (2) is NOT taken here', async () => { + // ⚠️ THE NO-CHANGE CONTROL for this PR, and load-bearing in both directions. + // + // Positive half: a `user` field still gets `idx_owner_id_lookup`, which + // proves the arm still executes and that the harness is wired to something — + // without it the negative half below would pass just as happily against a + // function that created no indexes at all. + // + // Negative half: a canonically-spelled `reference` lookup still gets NO join + // index. That is the divergence #12252 pinned and part (2) of #13222 owns. + // ⛔ This case records what the driver DOES, not what it should do: the door + // added in this PR makes the arm's `field.reference_to` conjunct unreachable + // but deliberately does not delete it, because deleting it would start + // building indexes on existing deployments' large collections — an unruled + // behaviour change. When part (2) lands, this case is expected to flip to + // `toContain`, in the same stroke as the #12252 pin in + // `mongodb-schema-declared-indexes.test.ts`. + // + // Bound through a variable rather than written inline: the driver's own + // `FieldDef` declares no `reference` key, so a fresh object literal carrying + // it trips TypeScript's excess-property check. + const canonicalLookup = { type: 'lookup', reference: 'company' }; + + const { db, created } = fakeDb(); + await syncCollectionSchema(db, 'lead', { + name: 'lead', + fields: { company_id: canonicalLookup, owner_id: { type: 'user' } }, + }); + + expect(names(created)).toEqual([ + 'idx_id_unique', + 'idx_created_at', + 'idx_updated_at', + 'idx_owner_id_lookup', + ]); + }); +}); diff --git a/packages/drivers/driver-mongodb/src/mongodb-schema.ts b/packages/drivers/driver-mongodb/src/mongodb-schema.ts index 7373499ec6..1a55193a27 100644 --- a/packages/drivers/driver-mongodb/src/mongodb-schema.ts +++ b/packages/drivers/driver-mongodb/src/mongodb-schema.ts @@ -9,6 +9,8 @@ import type { Db, CreateIndexesOptions, IndexSpecification } from 'mongodb'; +import { StandardErrorCode } from '@objectstack/spec/api'; + /** * ObjectStack field definition (subset needed for schema sync). */ @@ -34,7 +36,22 @@ interface FieldDef { */ unique?: boolean | 'global'; required?: boolean; - reference_to?: string; + /** + * [#13222] ⛔ A REJECTED ALIAS of `reference`, declared here so the door in + * {@link syncCollectionSchema} can READ it — never so this driver can honour + * it. `reference` is the only relationship spelling `@objectstack/spec` + * declares, and `FieldSchema` answers `unrecognized_keys` for this key. + * See {@link refuseRejectedReferenceAlias}. + * + * Typed `unknown` rather than `string`: only the key's PRESENCE is ever read + * here, and the metadata reaching this seam went around Zod — `MongoDBDriver. + * syncSchema(object, schema: unknown)` casts and forwards verbatim — so + * `string` would be a claim about untrusted input nothing on this path + * checked. Measured on `FieldSchema`: `'company'`, `null` and `''` all draw + * the one identical `unrecognized_keys` verdict, so the value's shape carries + * no information the door needs. + */ + reference_to?: unknown; multiple?: boolean; } @@ -66,6 +83,105 @@ interface ObjectDef { indexes?: IndexDef[]; } +/** + * [#13222] A field reached schema sync carrying `reference_to` — a key + * `FieldSchema` REFUSES — so this face refuses it too, in the spec's own words. + * + * ## Why the DDL seam needs a door the schema already has + * + * `reference` is the only relationship spelling the spec declares; + * `reference_to` is a REJECTED ALIAS, not a normalised one. Measured against + * `@objectstack/spec` built from this tree: + * + * ``` + * FieldSchema.safeParse({ name:'company_id', type:'lookup', reference_to:'company' }) + * => success:false, issue.code = `unrecognized_keys` + * "Unrecognized key(s) on this field: `reference_to`. + * Did you mean `reference_to` -> `reference`? Until this shape was closed + * these were dropped silently ..." + * ``` + * + * Until this door, the driver read `reference_to` and ONLY `reference_to`, as + * the gate on the field-level join index below. So one key had TWO doors with + * opposite answers: the authoring door refused it, while this one silently + * honoured it and built an index off it. The silent one was the one that + * touched the database. + * + * ⚠️ Unlike the SQL counterpart (`sql-driver.ts`, #11567), whose `patch` grade + * rested on "no authored deployment could reach the branch", the affected + * population HERE is non-zero by construction: this package's own published + * README taught `reference_to` in a sample calling `driver.syncSchema` directly + * — shipped at `@objectstack/driver-mongodb` 17.2.0 and earlier — and + * `syncSchema(object, schema: unknown)` casts and forwards verbatim with no Zod + * (`mongodb-driver.ts`). A deployment that copied that sample boots today and + * is refused here after this. That is why the change is graded `minor`. The + * README no longer teaches the key: its remaining mention is prose recording + * that the spelling is refused. + * + * ## Where the door sits, and why + * + * Stated BEFORE the collection is created and before every per-field branch, + * because the spec's refusal is gated on neither type nor value. Measured: + * `{ type:'text', reference_to:'company' }` draws the SAME `unrecognized_keys` + * verdict as the `lookup` fixture, and `'company'`, `null` and `''` are refused + * alike. `sql-driver.ts` states its copy before the `multiple` short-circuit and + * before the type switch for that same reason; this file has no type switch, so + * the equivalent placement is ahead of the whole field loop — which also puts it + * ahead of `db.createCollection`, so a refused sync leaves nothing behind. + * One key, one answer, wherever it appears. + * + * `!== undefined` rather than `'reference_to' in field`, matching the SQL door + * exactly: it refuses every value the key can actually carry — including the + * `null` and `''` the old truthy gate ignored — while staying immune to a + * producer that spreads an explicit `{ reference_to: undefined }`. Measured: + * `FieldSchema`'s own canonical output does NOT carry `reference_to` as an own + * key, so key presence would have been correct too; this is the narrower of two + * correct predicates, and both doors take the same one. + * + * ## Why refuse rather than ignore + * + * Ignoring the key would be a THIRD answer to one question. An author who wrote + * `reference_to` meant to point the field somewhere; a driver that drops it on + * the floor creates a collection pointing nowhere and says nothing. The refusal + * is stated at this seam because it is the last place the mistake is still + * cheap: before the collection exists, not after documents are in it. + * + * `VALIDATION_ERROR`/400 rather than a 500 (ADR-0112): the metadata is the + * caller's, the condition is decided entirely by what the caller wrote, and the + * fix is a one-word rename — the same envelope every other refusal in this + * package speaks. + * + * ## ⛔ What this deliberately does NOT change + * + * The field-level join-index arm below is left BYTE-IDENTICAL, and its + * `field.reference_to` conjunct is now unreachable — not oversight. Deleting + * that conjunct would make a canonically-spelled `reference` lookup start + * building `idx_FIELD_lookup`, which is a behaviour change for existing + * deployments (index builds on large collections) and is a SEPARATE, still-open + * ruling — part (2) of #13222, which the maintainer carries in a later batch. + * Whoever takes that ruling owns the arm, its comment, and the `#12252` pin in + * `mongodb-schema-declared-indexes.test.ts` in one stroke. Until then the arm's + * observable behaviour is exactly what it was: a `user` field is indexed, a + * canonical `reference` lookup is not. + */ +function refuseRejectedReferenceAlias(collectionName: string, fieldName: string): never { + const err = new Error( + `[driver-mongodb] field '${fieldName}' on '${collectionName}' declares \`reference_to\`, a ` + + `rejected alias of \`reference\`. Did you mean \`reference_to\` -> \`reference\`? ` + + `\`reference\` is the only relationship spelling @objectstack/spec declares, and ` + + `\`FieldSchema\` refuses this key with that same verdict (\`unrecognized_keys\`) on ANY field ` + + `type — so a field still carrying it at schema-sync time went around the schema ` + + `(\`syncSchema(object, schema: unknown)\` casts and forwards it verbatim, with no Zod). ` + + `Rename the key. Note that renaming does not, by itself, get the field a join index: this ` + + `driver's join-index arm still reads the refused spelling, so a canonical \`reference\` lookup ` + + `is unindexed here — that is part (2) of #13222, a separate open ruling, deliberately ` + + `unchanged by the door you just hit.`, + ) as Error & { code?: string; status?: number }; + err.code = StandardErrorCode.enum.VALIDATION_ERROR; + err.status = 400; + throw err; +} + /** * Synchronize a MongoDB collection to match an ObjectStack object definition. * @@ -81,6 +197,13 @@ export async function syncCollectionSchema( collectionName: string, schema: ObjectDef, ): Promise { + // [#13222] The door — see {@link refuseRejectedReferenceAlias}. Ahead of + // `createCollection` and of every per-field branch, because the spec's + // refusal is gated on neither the field's type nor the key's value. + for (const [fieldName, field] of Object.entries(schema.fields ?? {})) { + if (field.reference_to !== undefined) refuseRejectedReferenceAlias(collectionName, fieldName); + } + // Ensure collection exists const collections = await db.listCollections({ name: collectionName }).toArray(); if (collections.length === 0) { From 00fd198c10499df823ebec788bf19e87143c1e1a Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 30 Aug 2026 14:08:43 +0000 Subject: [PATCH 2/2] fix(driver-mongodb): strip the tracker id from the refusal's runtime message MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `check:doc-authoring`'s sibling-package prose-id leg went red on the new message: a runtime string reaches authors, operators and generated surfaces, none of whom can resolve an internal issue id. The pointer stays in the adjacent TSDoc, where the reader who can resolve it already is. Maintainer ruling 2026-08-12, verbatim: 「处理 issue 时犯的错应该总结成经验,保留 issue id没有意义」 Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01F3jdziLbAPGeceVNmSox5L --- packages/drivers/driver-mongodb/src/mongodb-schema.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/drivers/driver-mongodb/src/mongodb-schema.ts b/packages/drivers/driver-mongodb/src/mongodb-schema.ts index 1a55193a27..fd7d19a33d 100644 --- a/packages/drivers/driver-mongodb/src/mongodb-schema.ts +++ b/packages/drivers/driver-mongodb/src/mongodb-schema.ts @@ -174,8 +174,8 @@ function refuseRejectedReferenceAlias(collectionName: string, fieldName: string) `(\`syncSchema(object, schema: unknown)\` casts and forwards it verbatim, with no Zod). ` + `Rename the key. Note that renaming does not, by itself, get the field a join index: this ` + `driver's join-index arm still reads the refused spelling, so a canonical \`reference\` lookup ` + - `is unindexed here — that is part (2) of #13222, a separate open ruling, deliberately ` + - `unchanged by the door you just hit.`, + `is unindexed here. That is a separate, still-open question — deliberately unchanged by the ` + + `door you just hit — and not something the rename above regresses.`, ) as Error & { code?: string; status?: number }; err.code = StandardErrorCode.enum.VALIDATION_ERROR; err.status = 400;