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
9 changes: 9 additions & 0 deletions .changeset/23039-indexing-partial-example-recut.md
Original file line number Diff line number Diff line change
@@ -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.
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 3 additions & 2 deletions docs/adr/0126-packaged-metadata-customization-model.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
18 changes: 9 additions & 9 deletions skills/objectstack-data/rules/indexing.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,19 +156,19 @@ the dialect you are actually running.
**Partial index (`CREATE INDEX … WHERE <predicate>`).** 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
Expand Down
Loading