diff --git a/.changeset/23039-indexing-partial-example-recut.md b/.changeset/23039-indexing-partial-example-recut.md new file mode 100644 index 00000000000..dd419959bbc --- /dev/null +++ b/.changeset/23039-indexing-partial-example-recut.md @@ -0,0 +1,9 @@ +--- +'@objectstack/skills': patch +--- + +docs(skills): the `objectstack-data` indexing rule's partial-index example teaches the rule with an application-shaped index instead of restating the platform's overlay uniqueness key (#23039) + +Clause-②: no + +`skills/objectstack-data/rules/indexing.md`'s "Access methods and partial indexes" section illustrated a partial index with the platform's own overlay-uniqueness DDL and said that was what `ensureOverlayIndex` runs. The quoted key had four parts; the platform's runtime key has carried a fifth, `bundle_member`, since protocol 18, so the sentence stated something the platform no longer runs. The example is re-cut to an application-shaped partial index — one *active* subscription per account, the coarse `{ fields: ['account_id'] }` declared in metadata and the unique partial form built in a migration — so the rule is taught without restating a platform key that changes on its own schedule. The rule itself is unchanged: declare the coarse index (or none) in metadata, build the partial form as raw SQL in a runtime migration, fall back to a plain index on a dialect that rejects the predicate. Nothing an author writes changes. diff --git a/docs/adr/0029-kernel-object-ownership-and-platform-objects-decomposition.md b/docs/adr/0029-kernel-object-ownership-and-platform-objects-decomposition.md index 219ccd084b1..c568f7d7a4f 100644 --- a/docs/adr/0029-kernel-object-ownership-and-platform-objects-decomposition.md +++ b/docs/adr/0029-kernel-object-ownership-and-platform-objects-decomposition.md @@ -666,9 +666,10 @@ binding `P` against the packaged owner's id `O`: The last row is a real decision, not bookkeeping, and the reason is a store asymmetry worth recording: the overlay-uniqueness index keys on -`(type, name, organization_id, COALESCE(package_id, ''))` (ADR-0005 amendment -2026-08-09, #6825), so `sys_metadata` can legitimately hold two active rows for -one `(type, name)` bound to two packages. For every other type that is fine — +`(type, name, organization_id, COALESCE(package_id, ''), COALESCE(bundle_member, ''))` +(ADR-0005, amended 2026-08-09 by #6825 and 2026-10-12 by #22877 ruling A — see its +"What delivers overlay uniqueness today"), so `sys_metadata` can legitimately hold +two active rows for one `(type, name)` bound to two packages. For every other type that is fine — two packages really can ship `page/home`. For `object` it is not representable: `computeFQN` is identity, so the registry holds exactly one entry per object name and could never serve two. Refusing the mis-bound row is what keeps the two diff --git a/docs/adr/0126-packaged-metadata-customization-model.md b/docs/adr/0126-packaged-metadata-customization-model.md index 9523336bd3c..88252904649 100644 --- a/docs/adr/0126-packaged-metadata-customization-model.md +++ b/docs/adr/0126-packaged-metadata-customization-model.md @@ -310,8 +310,9 @@ contract they consume. - **New machine name, mandatory** (the #11513 shape exactly). ⛔ No same-name clone in a second package: storage legitimately holds both — the uniqueness index keys on - `(type, name, organization_id, COALESCE(package_id, ''))` (ADR-0005 amendment, #6825; ADR-0048 - governs the cross-package coexistence) — and the engine's name-keyed flow map makes the winner + `(type, name, organization_id, COALESCE(package_id, ''), COALESCE(bundle_member, ''))` + (ADR-0005, amended 2026-08-09 by #6825 and 2026-10-12 by #22877 ruling A; ADR-0048 governs the + cross-package coexistence) — and the engine's name-keyed flow map makes the winner insertion-order: measured as a silent, non-deterministic replacement (#11665 §2.2; #11997 tracks the shadow diagnostics independently of this model). - **Whole-definition copy — ⛔ never param-list assembly.** The clone copies the parsed diff --git a/skills/objectstack-data/rules/indexing.md b/skills/objectstack-data/rules/indexing.md index 72b02e1e098..d8d57c80504 100644 --- a/skills/objectstack-data/rules/indexing.md +++ b/skills/objectstack-data/rules/indexing.md @@ -156,19 +156,19 @@ the dialect you are actually running. **Partial index (`CREATE INDEX … WHERE `).** Supported on Postgres and SQLite (≥ 3.8.9), absent on MySQL. Because it cannot be expressed portably — and because knex's index builders have no way to emit a predicate — -it is issued as raw SQL from a runtime migration. The platform does exactly -this for its own overlay uniqueness: `metadata-protocol`'s `ensureOverlayIndex` -runs +it is issued as raw SQL from a runtime migration, falling back to a plain +index on a dialect that rejects it. One *active* subscription per account, +the coarse `{ fields: ['account_id'] }` declared in metadata: ```sql -CREATE UNIQUE INDEX IF NOT EXISTS idx_sys_metadata_overlay_active - ON sys_metadata (type, name, organization_id, COALESCE(package_id, '')) - WHERE state = 'active'; +CREATE UNIQUE INDEX IF NOT EXISTS idx_subscription_account_active + ON subscription (account_id) + WHERE status = 'active'; ``` -with a plain-index fallback for dialects that reject the predicate. Follow that -shape: declare the coarse index (or none) in metadata, and build the partial -form in a migration. +Follow that shape: declare the coarse index (or none) in metadata, and build +the partial form in a migration, as the platform does for its own overlay +uniqueness. **Benefits of a partial index, when you do build one:** - Smaller index size