Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .changeset/20595-driver-sql-provenance-anchors.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
'@objectstack/driver-sql': patch
---

Provenance comments in `@objectstack/driver-sql` cite the commits and ADR that decided them, not tracker numbers that no longer resolve

Clause-②: no

Docblocks and comments across the package cited issue-tracker numbers that now answer 404 on GitHub.
Each one now cites the commit in this repository's history that made the decision it describes, or the
ADR that records it (ADR-0104's 2026-09-05 addendum). Some of these docblocks sit on exported members,
so the reworded text appears in the published `index.d.ts` / `index.d.mts`, and comments that esbuild
keeps appear in the JavaScript output.

Comment only: no export, type, error code, status, message text or runtime behaviour changes.
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ export type FieldKeyClass = 'storage' | 'presentation';
export const FIELD_KEY_STORAGE_CLASS: Readonly<Record<string, FieldKeyClass>> = Object.freeze({
// ---- storage: the column's own shape -------------------------------------
type: 'storage', // `createColumn`: the column type itself
maxLength: 'storage', // `createColumn`: varchar(n) vs TEXT, and the #11374 keyable decision
maxLength: 'storage', // `createColumn`: varchar(n) vs TEXT, and commit d0e3a885b's keyable decision
multiple: 'storage', // `createColumn`: a multi-value field is a JSON column
precision: 'storage', // numeric column shape (this driver does not read it yet)
scale: 'storage', // numeric column shape (this driver does not read it yet)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@
* `engine.syncObjectSchema` → `SqlDriver.syncSchema` → the DDL gate, on a
* server that is already serving HTTP. That was the exact test #8035 applied
* when it UNregistered `MONGODB_MULTI_TENANT_UNSUPPORTED` for failing it — a
* removal #16649 reversed under the #16404 door-or-no-door rule, which takes
* removal that commit 613bfbd3d reversed under the #16404 door-or-no-door rule, which takes
* registration out of that test's reach entirely: every `code` that ships in
* `dist` carries a ledger row, and wire-reachability now decides only what a
* door ANSWERS with. This one can be carried, so the door serves it under its
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -448,7 +448,7 @@ export function declareUnprovisionedCell(cell: DialectCell, matrix: string): voi
* Nothing in the corridor (15_000, 600_000) is distinguishable by measurement,
* so the value is fixed by this package's OWN existing answer for live-touching
* sites: 60 explicit `60_000` budgets across 22 files — #13688 and its sweep
* #13902 put them on live test BODIES, #14213 and #14628 on the hooks that pay
* #13902 put them on live test BODIES, #14213 and commit 6392b9c2b on the hooks that pay
* a live connect. Adopting it leaves the live matrix with ONE live budget
* instead of two, so a red at 60_000 ms is unambiguous about which bound it hit.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
* refusing.
*
* ⛔ The single most load-bearing assertion here is that the PostgreSQL retype
* arm's pre-check exists at all. The #15041 addendum prescribed the retype with
* arm's pre-check exists at all. The ADR-0104 2026-09-05 addendum prescribed the retype with
* NO pre-check, and that form was measured on live PostgreSQL 16.13 to accept a
* row holding an inline metadata blob and flatten it to its own literal text.
* The director ruling (decision batch #120 item 1) replaced the clause; a pin
Expand Down
2 changes: 1 addition & 1 deletion packages/drivers/driver-sql/src/media-column-move.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* `sys_file` id, and the pre-check that ABORTS instead of destroying a row the
* backfill never converted.
*
* The ruling on #15041 gave this step one requirement in words — abort *"on
* The ruling in ADR-0104's 2026-09-05 addendum gave this step one requirement in words — abort *"on
* the first cell that is not a JSON string"* — and one sketch in SQL beside
* it. **The sketch does not implement the requirement, and that was measured
* rather than argued** (director ruling, decision batch #120 item 1): on live
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -364,7 +364,7 @@ describe('diffManagedTable — a SINGLE-VALUE JSON-class field over a stale text
// The card's scope, asserted rather than described: the fork applies to
// every single-value member of the writer's set.
//
// ⚠️ [#15989] #15041 has since been ruled — option A, the file family's
// ⚠️ [#15989] ADR-0104's 2026-09-05 addendum has since ruled — option A, the file family's
// column holds the bare `sys_file` id — so the family is no longer a member
// of {@link JSON_COLUMN_FIELD_TYPES}: it is asked per deployment, and
// `diffTags` omits `fileColumnsMoved`, i.e. every call here is about a
Expand Down
34 changes: 17 additions & 17 deletions packages/drivers/driver-sql/src/schema-drift.ts
Original file line number Diff line number Diff line change
Expand Up @@ -417,7 +417,7 @@ export const HASH_SHADOW_SUFFIX = '__hash';
* orphan pass reports as `unmapped_column` with a `drop_column` op. Dropping it
* would take the UNIQUE index it carries with it, silently returning the object
* to "registered but its declared uniqueness unenforced" — the very state
* #11374/#11627 exist to end, reached this time through the migration tool
* #11627 and commit d0e3a885b exist to end, reached this time through the migration tool
* rather than through a refused DDL.
*
* Matched by SUFFIX rather than by a registry of known names, deliberately: the
Expand Down Expand Up @@ -448,7 +448,7 @@ export function isHashShadowColumn(name: string): boolean {
* 64-character identifier limit.
*
* ⚠️ Lives HERE, beside {@link isHashShadowColumn}, rather than in the driver:
* #13015 was the price of the split. The ORPHAN-column pass knew the shadow
* The defect commit cd1348802 fixed was the price of the split. The ORPHAN-column pass knew the shadow
* vocabulary and the INDEX differ did not, so a healthy shadow-carried UNIQUE
* had its column protected from a drop while the index that column carries was
* proposed for a destructive rebuild. Both passes now ask the same module the
Expand All @@ -473,7 +473,7 @@ export function hashShadowColumnFor(indexName: string): string {
/**
* One key part a hash shadow hashes: the column identity, and whether the
* generation expression folds it through the NULL-safe `COALESCE(col, ...)`
* form (ADR-0120 D3, carried into the shadow by #12998).
* form (ADR-0120 D3, carried into the shadow by commit df1c75c4b).
*/
export interface HashShadowKeyPart {
column: string;
Expand All @@ -482,16 +482,16 @@ export interface HashShadowKeyPart {

/**
* Read the DECLARED key parts back out of a hash shadow's stored
* `GENERATION_EXPRESSION` (#13015).
* `GENERATION_EXPRESSION` (commit cd1348802).
*
* This is what makes a shadow-carried key COMPARABLE rather than merely
* skippable. Since #12998 the expression carries the NULL-safe parts in their
* skippable. Since commit df1c75c4b the expression carries the NULL-safe parts in their
* COALESCE spelling, so the FORM of the key — which columns, and which of them
* are folded — survives the round trip, and the differ can ask the real
* question ("does this shadow enforce what metadata declares?") instead of the
* blind one ("is this a shadow at all?").
*
* ⛔ Why the blind question is not good enough: a shadow created BEFORE #12998
* ⛔ Why the blind question is not good enough: a shadow created BEFORE commit df1c75c4b
* hashes the RAW columns, so `CONCAT` returns NULL for every NULL-organization
* row and the rows the COALESCE bucket exists to constrain are constrained by
* nothing (#5030's shape). It is indistinguishable BY NAME from a healthy one.
Expand Down Expand Up @@ -858,7 +858,7 @@ export function diffManagedTable(args: {
columns: PhysicalColumn[];
dialect: SqlDialectName;
/**
* Which columns an index KEYS ON (#11374), keyed by field name — the exact
* Which columns an index KEYS ON (commit d0e3a885b), keyed by field name — the exact
* map {@link indexedKeyColumns} builds. Consulted ONLY by the varchar-length
* branch below, through {@link varcharColumnChars}, to answer the same
* question `createColumn` asks before it sizes a text-family column.
Expand Down Expand Up @@ -1703,7 +1703,7 @@ export interface PhysicalIndex {
/**
* When this index is physically carried by a #11627 hash shadow, the
* DECLARED key parts that shadow hashes, read back from the generation
* expression (#13015 via #12998) by `SqlDriver.introspectIndexes`.
* expression (commit cd1348802 via commit df1c75c4b) by `SqlDriver.introspectIndexes`.
*
* Absent both when the index is NOT shadow-carried and when it is but the
* expression could not be read. {@link isHashShadowCarrier} tells those two
Expand Down Expand Up @@ -2029,7 +2029,7 @@ export function diffUnbuildableIndexes(args: {
* field-level `unique` through {@link uniqueIndexesFromFields}, object-level
* `indexes[]` through {@link normalizeDeclaredIndex} — so "which columns end up
* in a key" has ONE answer, shared by the index sync that creates them and by
* the DDL that has to make them keyable in the first place (#11374).
* the DDL that has to make them keyable in the first place (commit d0e3a885b).
*
* ⚠️ Deliberately NOT filtered by `physicalColumns`, unlike `expectedIndexes`:
* its caller runs BEFORE the columns exist — deciding a column's TYPE is the
Expand Down Expand Up @@ -2275,15 +2275,15 @@ function indexSignature(
* Answerable from the index alone, by NAME: the shadow is derived from the
* index name ({@link hashShadowColumnFor}), so a carrier is an index whose sole
* key column is its own shadow. That is what makes this the FAIL-SAFE half of
* #13015 — it holds even when the generation expression cannot be read, and a
* commit cd1348802 — it holds even when the generation expression cannot be read, and a
* carrier is never a thing this differ may propose destroying on a guess.
*/
export function isHashShadowCarrier(index: PhysicalIndex): boolean {
return index.columns.length === 1 && index.columns[0] === hashShadowColumnFor(index.name);
}

/**
* The key an index ENFORCES, which is not always the key it STORES (#13015).
* The key an index ENFORCES, which is not always the key it STORES (commit cd1348802).
*
* For an ordinary index the two are the same. For a #11627 shadow-carried
* UNIQUE the stored key is one VARBINARY(32) generated column and the enforced
Expand Down Expand Up @@ -2358,7 +2358,7 @@ export function diffManagedIndexes(args: {
if (!p || p.primary || isRuntimeManagedIndex(p, runtimeCreated, tenantField)) return false;
if (!p.unique || p.partial === true) return false;
if ((p.expressions?.length ?? 0) > 0 || (p.nullSafeColumns?.length ?? 0) > 0) return false;
// #13015: nor is a hash-shadow carrier. Its stored key is one generated
// Commit cd1348802: nor is a hash-shadow carrier. Its stored key is one generated
// column, so the identity comparison below already excludes it — stated
// outright because the exclusion must survive that comparison changing,
// and because `replace_unique_index` DROPS the legacy name.
Expand Down Expand Up @@ -2428,7 +2428,7 @@ export function diffManagedIndexes(args: {
// Same normalization on BOTH sides (#4884, ADR-0120 D3): column identity
// AND key-part form, literal-agnostic on the COALESCE literal — asked of
// the key the index ENFORCES, which for a #11627 shadow-carried UNIQUE is
// not the column it stores (#13015).
// not the column it stores (commit cd1348802).
const pk = enforcedIndexKey(p);
if (
p.unique === e.unique &&
Expand All @@ -2445,7 +2445,7 @@ export function diffManagedIndexes(args: {
// (`recreate_index` → drop first) this differ cannot undo. Not ours to
// reconcile (#4884).
if (isRuntimeManagedIndex(p, runtimeCreated, tenantField)) continue;
// #13015, fail-safe half: a hash-shadow carrier whose generation
// Commit cd1348802, fail-safe half: a hash-shadow carrier whose generation
// expression could NOT be read (`shadowKey` unresolved). We know by name
// that the index is driver-owned and that its stored key is a digest, so
// the identity comparison above is meaningless for it — but we do not know
Expand All @@ -2455,7 +2455,7 @@ export function diffManagedIndexes(args: {
// ⛔ The `!p.shadowKey` half is load-bearing, and was measured: without it
// this guard swallows the RESOLVED carriers too, which silently demotes the
// whole fix to the blind skip — every shadow-carried index unreportable,
// including a pre-#12998 one hashing the RAW columns whose constraint does
// including one from before commit df1c75c4b hashing the RAW columns whose constraint does
// not cover NULL-organization rows at all. Green, quiet, and the exact
// trade this fix exists to refuse.
if (isHashShadowCarrier(p) && !p.shadowKey) continue;
Expand All @@ -2471,7 +2471,7 @@ export function diffManagedIndexes(args: {
// clean → recategorised `safe` (dev autoMigrate may apply); duplicates →
// blocked with a row report, the old index left in place.
//
// #13015: read through the ENFORCED key, so a pre-#12998 shadow — same
// Commit cd1348802: read through the ENFORCED key, so a shadow from before commit df1c75c4b — same
// columns, hashed RAW instead of through the NULL-safe COALESCE — is
// recognised as exactly this tightening and gets the same duplicate
// pre-flight before anything is dropped. The explicit "physical side is
Expand Down Expand Up @@ -2530,7 +2530,7 @@ export function diffManagedIndexes(args: {
// (#4884 — the boot advised dropping `idx_sys_metadata_overlay_draft`, the
// partial UNIQUE enforcing draft-overlay uniqueness, on a healthy fresh DB).
if (isRuntimeManagedIndex(p, runtimeCreated, tenantField)) continue;
// #13015: an orphaned shadow carrier is still an orphan — its declaration
// Commit cd1348802: an orphaned shadow carrier is still an orphan — its declaration
// is gone, and `drop_index` is the right remedy — but the report must name
// the constraint it enforced, not the digest column it stored.
const po = enforcedIndexKey(p);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
* [#11176] The two write doors that did not advance `updated_at`: `updateMany()`
* on every dialect, and `upsert()`'s merge branch on Postgres and MySQL.
*
* ## Not #11067, and the difference is what this file is set up to show
* ## Not the defect commit 479fba50d fixed, and the difference is what this file is set up to show
*
* #11067 is about `tablesWithTimestamps` being filled only by DDL, so a
* Commit 479fba50d is about `tablesWithTimestamps` being filled only by DDL, so a
* `skipSchemaSync` deployment never stamped. These two are missing on EVERY
* deployment — so almost every table here is built by the driver's own
* `initObjects`, with `tablesWithTimestamps` correctly populated. That is the
Expand Down Expand Up @@ -57,7 +57,7 @@
* ## §6 The narrowing, stated as a measurement rather than a claim
*
* The upsert stamp reads `observedUpdatedAtColumn` — DDL-observed, or settled
* `present` by a successful stamped UPDATE — and deliberately NOT #11067's
* `present` by a successful stamped UPDATE — and deliberately NOT commit 479fba50d's
* `presumed` state. `presumed` exists so an UPDATE can speculate and then
* RECOVER (`updateWithPresumedTimestamp`); the upsert door has no such recovery,
* and a wrong presumption there would name a missing column in an INSERT column
Expand All @@ -66,7 +66,7 @@
* would break first if the narrowing were ever widened without a recovery.
*
* `updateMany` has no such narrowing: it is an UPDATE door, so it reuses
* #11067's machinery whole (§7).
* commit 479fba50d's machinery whole (§7).
*
* ## Reverse verification (direction predicted before running)
*
Expand All @@ -87,7 +87,7 @@ const OPTS = { bypassTenantAudit: true } as any;
/**
* The instant a row is backdated to before the write under test.
*
* A sentinel far in the past rather than a sleep, for #11067's reason: a stamp
* A sentinel far in the past rather than a sleep, for commit 479fba50d's reason: a stamp
* taken a moment after an insert default can legitimately land on the same
* stored value. Backdating removes the race without weakening the assertion —
* the stamp either moved to ~now or did not move at all, and those are six
Expand Down Expand Up @@ -338,7 +338,7 @@ function measure(cell: DialectCell): void {
// ── §6 The declared narrowing, measured at the property that would break ──

it('§6 still upserts a hand-migrated table that has NO `updated_at` column', async () => {
// The upsert stamp reads the OBSERVED answer, never #11067's presumption,
// The upsert stamp reads the OBSERVED answer, never commit 479fba50d's presumption,
// because this door has no recovery to fall back on. If that narrowing is
// ever widened without one, this is the call that stops working.
const id = 'n1';
Expand All @@ -349,7 +349,7 @@ function measure(cell: DialectCell): void {
expect(after.row.title).toBe('b');
});

// ── §7 `updateMany` reuses #11067's machinery whole ──────────────────────
// ── §7 `updateMany` reuses commit 479fba50d's machinery whole ──────────────────────

it('§7 stamps a `skipSchemaSync` table, and still updates one without the column', async () => {
// The presumption and its recovery, exercised through the bulk door: it is
Expand All @@ -363,7 +363,7 @@ function measure(cell: DialectCell): void {
expect(presumed.updatedAt).toBeGreaterThan(BACKDATED_MS);
expect(presumed.row.title).toBe('b');

// The other half of #11067's pair: a table that genuinely lacks the column
// The other half of commit 479fba50d's pair: a table that genuinely lacks the column
// must NOT gain a new rejection.
await driver.create(NO_COL, { id: 'n2', title: 'a', status: 'bulk' }, OPTS);
const touched = await driver.updateMany(NO_COL, { where: { status: 'bulk' } }, { title: 'b' }, OPTS);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
* audit answer comparing the two, a "modified since creation?" badge, and above
* all a millisecond-precision delta cursor (`updated_at > cursor`), which
* SKIPS every row whose stamp was truncated back below it — the same
* silent-wrong-answer family as #11067 / #11176 / #11223, reached by a fourth
* silent-wrong-answer family as #11176 / #11223 / the one commit 479fba50d fixed, reached by a fourth
* mechanism. §2 asserts that skip is gone by issuing the cursor comparison as
* real SQL on the server rather than comparing numbers in JS.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
*
* ## Why a shadow and not a prefix index
*
* The maintainer's 2026-08-24 ruling on #11374 chose the hash route and
* The maintainer's 2026-08-24 ruling, landed as commit 107bb4ba4, chose the hash route and
* rejected prefix-unique indexes, on measurement: `UNIQUE KEY (token(191))`
* enforces uniqueness over the PREFIX, so two genuinely distinct tokens that
* share their first 191 characters collide and the second is refused as
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
*
* ## The ruling this suite pins
*
* #11152 (maintainer 2026-08-28, applied in its comment 5448627494, ruling
* #11152 (maintainer 2026-08-28, landed as commit f6fa22ce1, ruling
* verbatim and untranslated: 「12745 A回,其他同意。」 — option A on that
* card) adopted, superseding #11249's `false`/`true` for the order
* statistics:
Expand All @@ -16,7 +16,7 @@
* — the same numeric domain `sum` / `avg` answer in, so one column's five
* aggregates answer in one domain rather than three-numbers-two-booleans.
* - **`sum` / `avg` answer arithmetic** (`3` / `0.5` on the 3-true/3-false
* fixture) — the settled #11065 family shape, unchanged.
* fixture) — the settled commit 20950404c family shape, unchanged.
*
* ## The two measured gaps this suite exists to keep closed
*
Expand Down
Loading
Loading