Skip to content

Commit e9e10fd

Browse files
docs(adr,skills): ADR-0029, ADR-0126 state the five-part overlay key; the objectstack-data indexing example stops restating the platform's key (#23039) (#23111)
Fixes #23039 Clause-②: no ADR-0029 D9.9 and ADR-0126 section 7.1 each stated the overlay uniqueness key with four parts, and the published `objectstack-data` skill's indexing rule illustrated a partial index with the platform's own overlay DDL carrying that same four-part key, saying that is what `ensureOverlayIndex` runs. Since `5677add93f` (PR #22954, #22877 ruling A) the runtime key carries a fifth part, `bundle_member`. This is the family closure after #23006, whose PR #23037 restated ADR-0005. Two ADRs and one published skill file; `@objectstack/skills` publishes, so a patch changeset rides along. Governed surfaces: `docs/adr/**` and `skills/**` (Tier H). The PR stays draft until an authorized approval. ## What changed (HEAD `9ef34a302`, two commits on `846438024`) - **`docs/adr/0029-kernel-object-ownership-and-platform-objects-decomposition.md`** (820 → 821 lines, +4 / −3). The D9.9 sentence now spells `(type, name, organization_id, COALESCE(package_id, ''), COALESCE(bundle_member, ''))` and cites ADR-0005 as amended 2026-08-09 by #6825 and 2026-10-12 by #22877 ruling A, pointing at its "What delivers overlay uniqueness today". The argument around it — two packages may each hold an active row for one `(type, name)`, which `object` cannot represent — is untouched. - **`docs/adr/0126-packaged-metadata-customization-model.md`** (433 → 434 lines, +3 / −2). The section 7.1 clone bullet takes the same five-part spelling and the same provenance; the ADR-0048 pointer and the clone argument are unchanged. - **`skills/objectstack-data/rules/indexing.md`** (230 → 230 lines, +9 / −9; 2202 → 2200 tokens under a ceiling of 3241). The "Partial index" paragraph no longer quotes the platform's overlay DDL and no longer names `ensureOverlayIndex`. It teaches the rule with an application-shaped index — one *active* subscription per account, the coarse `{ fields: ['account_id'] }` declared in metadata, the unique partial form built in a migration — and keeps the pointer that the platform's own overlay uniqueness is built the same way, without naming its columns. The plain-index fallback for a dialect that rejects the predicate moves from a description of the platform into the instruction itself. The table and column are ones no other example in the file declares with a different uniqueness (rework round 1, the contract review's M1). - **`.changeset/23039-indexing-partial-example-recut.md`**: `@objectstack/skills` patch. `packages/skills` builds `dist/` by copying the whole `skills/` tree (`scripts/sync-catalog.mjs`) and its `files` whitelist is `dist`, so the edited rule file ships. No `skip-changeset`, and no label written. ## Enumeration pin (the card's re-check) Instrument: `git grep -n -F "COALESCE(package_id, ''))" REF -- docs skills content .claude AGENTS.md`. | reading | pin | control (`idx_sys_metadata_overlay_active`) | |:--|:--|:--| | `origin/main` `846438024`, before | 3 — ADR-0029:669, ADR-0126:313, indexing.md:165 | 7 | | this branch `9ef34a302`, after | 0 | 6 | The one control hit that leaves is `indexing.md:164`, the `CREATE UNIQUE INDEX` line of the very example this PR re-cuts; the six that remain are ADR-0005 (four) and `docs/qa/platform-checklist/areas/cli.json` (two), neither in this card's family. The instrument still reads non-zero, so the zero on the pin is a measurement, not a broken instrument. ## Skills readings (`skills/**` touched) | surface | before (`846438024`) | after (`9ef34a302`) | |:--|:--|:--| | `rules/indexing.md`, lines | 230 | 230 | | `rules/indexing.md`, tokens (`ceil(bytes / 4)`, the ratchet's own convention) | 2202 | 2200 | | `skills/objectstack-data`, lines (its 15 `.md` files) | 3718 | 3718 | | `skills/objectstack-data`, tokens (sum of the per-file counts) | 39143 | 39141 | The ratchet's own line on the result: `skills/objectstack-data/rules/indexing.md is 2200 tokens (ceiling 3241; headroom 1041)`. Net content: two tokens removed, nothing added that the file did not already teach. ## Gates (on HEAD `9ef34a302`) Derived with `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack`, no paths passed: 4 paths against merge base `846438024`, 47 commands. Each ran alone in the foreground with its exit code captured after a redirect, then reconciled: `dispatch-gates --ran: 47 derived famil(ies) accounted for — 45 run, 2 NOT-MEASURED (2 DERIVED from a recorded exit 3)`. 45 exit 0, among them every family the dispatch named: - `check-skills-token-ratchet`: `54 authored bundle file(s) within their ceilings` (plus its self-test) - `check:pm-skill-id-lint`: `87 file(s) clean (pattern /#[0-9]{3,}/g)` - `check:skill-compatibility`: `10 SKILL.md file(s) reconciled against 81 workspace packages` - `check:skill-identifier-liveness`: `Leg 1: 457 citation(s) over 53 published file(s) checked against 127687 implementation word tokens` - `check:adr-anchors`: `OK (68 anchored file(s), every governing ADR still referenced; … 44264 citation(s) across 5883 file(s) …)` - `check-adr-links`: `730 relative link destination(s) under docs/adr/` all good - `check:nul-bytes`: `OK (scanned 11113 text file(s) …; no raw ASCII control bytes)` - `check:doc-authoring`: `53 published skill files clean — no internal issue-id references` - `check-adr-0087-registration`: `this PR adds no declared-breaking changeset (1 non-breaking changeset(s) seen)` - `check-empty-changeset`: `No empty-frontmatter changeset introduced by this diff (1 declaring changeset(s) added)` - `check:doc-formula-expressions` (after building `@objectstack/lint...` under the shared verify lock: `VERDICT command-exit 0`, 121 s held, 0 s waited): `15 predicate(s) … judged clean` - plus `check-adr-symbol-anchors`, `check-changeset-no-major`, `check-comment-mask-corpus`, `check:cross-package-test-inputs`, `check:pm-governed-merges`, `check:pm-prior-rulings`, `check:skill-frame-sync`, `check:published-files`, `check:gitlink-declared`, `check:dts-closure`, `check:sourcemap-no-sources-content`, `check:refd-timer-probe`, and the rest of the 47. NOT MEASURED (exit 3, `PREREQUISITE NOT MET`): `check:dual-build-cjs-loads` (loads about 80 packages' built entry points) and `check:lean-entry-closure` (loads `@objectstack/objectql`'s). This diff touches no package source, so a full build on the shared box would measure nothing the diff could move; CI builds fresh. Outside the derivation, `pnpm --filter @objectstack/spec check:skill-examples` also exits 3 (client dists absent); its scan surface is `ts`/`typescript` fences carrying its marker, which this diff does not touch. Not run locally, by design: `pnpm lint` and the rest of the repo-wide farm belong to CI. ## Acceptance notes 1. **Both ADR sentences are present-tense statements, not dated records**, so each takes the five-part key outright rather than a dated note beside the old spelling (the treatment PR #23037 gave ADR-0005's #6771 measurement). ADR-0029's sentence sits under the 2026-08-09 D9 amendment heading but describes the store as it is; ADR-0126's sits in section 7, whose header calls the section the 2026-08-25 discussion's direction, but the bullet states what storage holds today. 2. **Neither ADR gains a status-line amendment.** Both cite ADR-0005's key; neither decides it. Re-pointing a citation at the record that moved reverses nothing these two records decided (Prime Directive #13), and ADR-0005's status line already carries the 2026-10-12 entry. 3. **The skill keeps the platform pointer, not the platform's columns.** "as the platform does for its own overlay uniqueness" stays; `ensureOverlayIndex`, `metadata-protocol`, `sys_metadata` and the key itself leave the published file. `check:skill-identifier-liveness` is green on the result, and `check:doc-authoring` confirms no tracker number entered the skill. 4. **The other skill gates do not read this file.** `build-skill-docs.ts` reads `SKILL.md` frontmatter (`check:skill-docs`), `build-skill-references.ts` reads `references/**` (`check:skill-refs`), and `check-skill-examples.ts` reads marked `ts` fences — none reaches the `sql` fence in `rules/indexing.md`, so none is owed here beyond what ran. 5. **Left where triage put them, not in this PR:** the code comment at `packages/metadata-core/src/objects/sys-metadata.object.ts:244-249` (not governed; it rides the next change to that file), `docs/qa/platform-checklist/areas/cli.json:1125` (the card lists it outside the family), and ADR-0005 (landed in PR #23037). Out of scope: #23006, carried by that PR. 6. **Rework round 1 (M1 of the contract review):** the round-1 example reused `(source, dedup_key)`, which line 61 of the same file declares `unique: 'global'`; read together the global unique already forbids a second job in any status, so the partial index added nothing. The example is now one *active* subscription per account — `{ fields: ['account_id'] }` coarse in metadata, `CREATE UNIQUE INDEX … ON subscription (account_id) WHERE status = 'active'` in the migration — and `account_id` / `subscription` appear in no other example. One commit, `9ef34a302`, on the same branch; the two ADR sentences, the changeset level and `Clause-②: no` are unchanged. 7. **Line budget held:** ADR-0029 +1 line, ADR-0126 +1 line, `indexing.md` net 0 lines and −2 tokens; the dispatch allowed up to +2 per ADR and net ≤ 0 on the skill. ## 维护者速读(草稿) **改了什么**:两份 ADR(0029、0126)里引用的 overlay 唯一键从四段改成当前的五段(多了 `bundle_member`),出处改为同时指向 ADR-0005 的 2026-08-09 与 2026-10-12 两次修订;对外发布的 `objectstack-data` 技能的 indexing 规则,把「部分索引」示例从平台自己的 overlay 建索引语句换成一个应用层例子(每个账户至多一条 active 订阅:元数据里声明粗粒度索引,迁移里建带谓词的唯一索引),不再在技能里复述平台自己的键。附一条 `@objectstack/skills` patch changeset。 **为什么改**:自 `5677add93f` 起平台的 overlay 唯一键已是五段,这三处仍写四段,读者(包括加载发布技能的客户 agent)会拿到一个平台已不再运行的键。技能里不该背平台自己的键:平台的键会再变,发布出去的技能跟不上。本卡是 #23006 之后的同族收口(ADR-0005 那一处已在 PR #23037 落地)。 **风险与代价(含回滚)**:只改文档与一份技能文本,运行时无变化,作者写元数据的方式无变化。技能文件行数不变、token 少 2,棘轮不动;两份 ADR 各多一行。回滚即 revert 本 PR 的两个 commit。 **席位意见**:(留空) **你要做的**:Tier H(ADR + 发布技能)需要你的 approve;approve 后由席位入队落地,不需要你手合。 --- _Generated by [Claude Code](https://claude.ai/code/session_012dGTuJen61ECNHXYXKChoC)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 1e62f71 commit e9e10fd

4 files changed

Lines changed: 25 additions & 14 deletions

File tree

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
'@objectstack/skills': patch
3+
---
4+
5+
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)
6+
7+
Clause-②: no
8+
9+
`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.

‎docs/adr/0029-kernel-object-ownership-and-platform-objects-decomposition.md‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -666,9 +666,10 @@ binding `P` against the packaged owner's id `O`:
666666

667667
The last row is a real decision, not bookkeeping, and the reason is a store
668668
asymmetry worth recording: the overlay-uniqueness index keys on
669-
`(type, name, organization_id, COALESCE(package_id, ''))` (ADR-0005 amendment
670-
2026-08-09, #6825), so `sys_metadata` can legitimately hold two active rows for
671-
one `(type, name)` bound to two packages. For every other type that is fine —
669+
`(type, name, organization_id, COALESCE(package_id, ''), COALESCE(bundle_member, ''))`
670+
(ADR-0005, amended 2026-08-09 by #6825 and 2026-10-12 by #22877 ruling A — see its
671+
"What delivers overlay uniqueness today"), so `sys_metadata` can legitimately hold
672+
two active rows for one `(type, name)` bound to two packages. For every other type that is fine —
672673
two packages really can ship `page/home`. For `object` it is not representable:
673674
`computeFQN` is identity, so the registry holds exactly one entry per object name
674675
and could never serve two. Refusing the mis-bound row is what keeps the two

‎docs/adr/0126-packaged-metadata-customization-model.md‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -310,8 +310,9 @@ contract they consume.
310310

311311
- **New machine name, mandatory** (the #11513 shape exactly). ⛔ No same-name clone in a second
312312
package: storage legitimately holds both — the uniqueness index keys on
313-
`(type, name, organization_id, COALESCE(package_id, ''))` (ADR-0005 amendment, #6825; ADR-0048
314-
governs the cross-package coexistence) — and the engine's name-keyed flow map makes the winner
313+
`(type, name, organization_id, COALESCE(package_id, ''), COALESCE(bundle_member, ''))`
314+
(ADR-0005, amended 2026-08-09 by #6825 and 2026-10-12 by #22877 ruling A; ADR-0048 governs the
315+
cross-package coexistence) — and the engine's name-keyed flow map makes the winner
315316
insertion-order: measured as a silent, non-deterministic replacement (#11665 §2.2; #11997
316317
tracks the shadow diagnostics independently of this model).
317318
- **Whole-definition copy — ⛔ never param-list assembly.** The clone copies the parsed

‎skills/objectstack-data/rules/indexing.md‎

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -156,19 +156,19 @@ the dialect you are actually running.
156156
**Partial index (`CREATE INDEX … WHERE <predicate>`).** Supported on Postgres
157157
and SQLite (≥ 3.8.9), absent on MySQL. Because it cannot be expressed
158158
portably — and because knex's index builders have no way to emit a predicate —
159-
it is issued as raw SQL from a runtime migration. The platform does exactly
160-
this for its own overlay uniqueness: `metadata-protocol`'s `ensureOverlayIndex`
161-
runs
159+
it is issued as raw SQL from a runtime migration, falling back to a plain
160+
index on a dialect that rejects it. One *active* subscription per account,
161+
the coarse `{ fields: ['account_id'] }` declared in metadata:
162162

163163
```sql
164-
CREATE UNIQUE INDEX IF NOT EXISTS idx_sys_metadata_overlay_active
165-
ON sys_metadata (type, name, organization_id, COALESCE(package_id, ''))
166-
WHERE state = 'active';
164+
CREATE UNIQUE INDEX IF NOT EXISTS idx_subscription_account_active
165+
ON subscription (account_id)
166+
WHERE status = 'active';
167167
```
168168

169-
with a plain-index fallback for dialects that reject the predicate. Follow that
170-
shape: declare the coarse index (or none) in metadata, and build the partial
171-
form in a migration.
169+
Follow that shape: declare the coarse index (or none) in metadata, and build
170+
the partial form in a migration, as the platform does for its own overlay
171+
uniqueness.
172172

173173
**Benefits of a partial index, when you do build one:**
174174
- Smaller index size

0 commit comments

Comments
 (0)