Skip to content

Commit aa9b481

Browse files
committed
Merge remote-tracking branch 'origin/main' into claude/issue-22786-withhold-milestone-row
2 parents c9f6095 + 179f7bf commit aa9b481

8 files changed

Lines changed: 87 additions & 48 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+
fix(skills): the `objectstack-data` field-types rule names all five formula `returnType` members, `'currency'` included, and the `currencyConfig` a currency formula carries (#22743)
6+
7+
Clause-②: no
8+
9+
`skills/objectstack-data/rules/field-types.md` taught a formula's `returnType` as the four-member set `'number' | 'text' | 'boolean' | 'date'`, and its formula example said `(no 'currency')`. `@objectstack/spec`'s `FieldSchema.returnType` is the five-member enum `'number' | 'text' | 'boolean' | 'date' | 'currency'`, and a formula whose `returnType` is `'currency'` carries its currency in its own `currencyConfig` — `CurrencyConfigSchema`, the same shape a `currency` field declares; absent, the mode is `dynamic`, the tenant default currency. The `formula` table row and the formula example now both state the five-member set and name `currencyConfig` as the carrier. The example's `CEL — record. prefixes required` comment, which restated the table cell above it, is removed to pay the skill's token ratchet. Nothing is said about how a currency formula renders.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
'@objectstack/spec': patch
3+
---
4+
5+
docs(spec): `RuntimeAuthoringIssueSchema.path` states the name-keying rule for every collection-resident write type, so a `dataset` write's name-keyed paths are no longer described as positional
6+
7+
Clause-②: no
8+
9+
The description of `path` on a runtime publish-gate finding (the 422 `issues[]` and the 2xx `advisories[]`) named `object`, `permission` and `book` as the only write types whose top-level collection entry is keyed by name, and called every other write type positional. A `dataset` write's findings have been name-keyed too (for example `datasets.acme_invoice_metrics.dimensions[0].field`), so a consumer that followed the text would have expected `datasets[N]` and never received it.
10+
11+
The description now states the rule instead of a list: a write type is collection-resident when the gate's per-write snapshot holds the tenant's other stored items of that type beside the written one, and there the top-level entry is keyed by name. Every other write type is the only member of its collection in that snapshot, so its `[0]` stays positional. The set is derived inside `@objectstack/lint`'s runtime gate, and the description names that derivation rather than copying its members. It also states the fallback the gate already applies: an entry whose name cannot be spliced into a dotted path keeps its index.
12+
13+
Description text only: the schema's shape, accepted values and types are unchanged. The generated `content/docs/references/api/protocol.mdx` is regenerated.

‎.claude/skills/spec-property-retirement/SKILL.md‎

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -210,10 +210,12 @@ conversion 是消费者跟的;D3 条目是升级方的 agent 读的。三个都
210210
「没有 alias 窗口,故意的」;对**默认值翻转**,只有确知输入早于翻转的 seam 才可重
211211
放,其余按 id 退订 `excludeConversionIds` —— `app-hidden-to-unpublished` 在 artifact
212212
门即如此。
213-
- [ ] **一步 D3 链**,在 `packages/spec/src/migrations/registry.ts`;`conversion.toMajor` **必须等于**该步的
214-
major。**18 步**:conversion 只进 `conversions/registry.ts` 的 `MAJOR_18_CONVERSIONS`,照其头注按
215-
标识符排序插入,本步 `conversionIds` 由它派生;`rationale` 只加一个 `STEP18_RATIONALE` 片段,
216-
按其头注插在你 D3 semantic id 的排序位;尾部追加被两处头注点名的 merge 测试拒收。
213+
- [ ] **一步 D3 链**,在 `packages/spec/src/migrations/registry.ts.template`(`registry.ts` 由
214+
`gen:migration-registry` 从它与 `entries/` 整文件生成并被 git 忽略,在 `registry.ts` 里手改不会
215+
进提交);`conversion.toMajor` **必须等于**该步的 major。**18 步**:conversion 只进
216+
`conversions/registry.ts` 的 `MAJOR_18_CONVERSIONS`,照其头注按标识符排序插入,本步 `conversionIds`
217+
由它派生;`rationale` 只加一个 `STEP18_RATIONALE` 片段,按其头注插在你 D3 semantic id 的排序
218+
位;尾部追加被两处头注点名的 merge 测试拒收。
217219
**更早的步**:id 加进 `MIGRATIONS_BY_MAJOR[N].conversionIds`,扩写该步 `rationale`。⚠ 没有东西
218220
直接断言「每个 conversion 都接进了某一步」,拼错的 id 在 replay 时被**静默跳过**;
219221
chain-replay 测试抓得到它,只因为没接线的 fixture 永远到不了自己的 `after`。
@@ -257,7 +259,7 @@ conversion 是消费者跟的;D3 条目是升级方的 agent 读的。三个都
257259
无墓碑删键 → 该行**消失**,这是门 (a) 的绊线,所以同一个 PR 里有意删掉它)与
258260
`json-schema.manifest/<category>.json`。#5837 起两者都按 category 分片 —— 门
259261
禁把整个目录读成一个集合,退役流程不变;变的只是那一行住在哪个文件。然后
260-
`gen:spec-changes`、`gen:upgrade-guide`、`gen:api-surface`、`gen:docs`。
262+
`gen:api-surface`、`gen:docs`;新条目跑 `gen:migration-registry`;D4 投影不提交,本地不重生成。
261263
「改了 X → 重新生成 Y」的表见 AGENTS.md。
262264
- [ ] **Forms** —— 从 `packages/spec/src/**/*.form.ts` 剪掉 `{ field: '<key>' }` 输
263265
入。未强制能力的表单输入是假合规的 UI 半边。原位留一行注释。
@@ -332,6 +334,4 @@ pnpm check:i18n && pnpm --filter @objectstack/spec test
332334
- **截断的 grep 漏掉编写者。** `| head -8` 藏掉了 `SKILL.md` 的 `defineSkill` 示
333335
例;`examples/` 有三处 `template: true`,不是一处。先按上下文找文件,再进文件里
334336
grep 键 —— 且把 `tsc` 和门禁当权威清扫器。
335-
- **只改 conversion 的 `summary` 也会过期。** 那个字符串被逐字抄进
336-
`spec-changes.json` 的 `to` 字段与 upgrade guide 的表行,所以纯散文改动也要
337-
`gen:spec-changes` + `gen:upgrade-guide`,和任何别的改动一样。
337+
- **只改 conversion 的 `summary` 不欠重生成。** 它逐字进两条 D4 投影,都不提交;PR 渲染其 diff。

‎AGENTS.md‎

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -703,7 +703,7 @@ Root also exports: `defineStack`, `composeStacks`, `defineView`, `defineApp`, `d
703703

704704
### Touched `packages/spec`? Regenerate its artifacts BEFORE pushing
705705

706-
`packages/spec` has **eight** checked-in generated artifacts, each with its own CI
706+
`packages/spec` has **six** checked-in generated artifacts, each with its own CI
707707
gate. All of them live in one job — `TypeScript Type Check` in `lint.yml`, which is
708708
required and has no paths filter, so no gate can go dormant on the PR that breaks it.
709709
That job runs its gates **sequentially**, so the first stale artifact masks every one
@@ -714,7 +714,7 @@ behind it. Match the change to the gate and regenerate up front:
714714
| A `.describe()` / TSDoc on any schema | `check:docs` | `gen:schema && gen:docs` |
715715
| A public export (added / removed / renamed) | `check:api-surface` | `gen:api-surface` |
716716
| An authorable key on a metadata schema | `check:authorable-surface` | `gen:schema` |
717-
| An ADR-0087 conversion / migration registry | `check:spec-changes`, `check:upgrade-guide` | `gen:spec-changes`, `gen:upgrade-guide` |
717+
| An ADR-0087 conversion / migration registry entry | `check:spec-changes`, `check:upgrade-guide` — in memory, red only when the registries do not project | nothing to commit — `gen:migration-registry` (install, build and test run it too) writes the gitignored `registry.ts` |
718718
| A `SKILL.md` (frontmatter or body) | `check:skill-docs`, `check:skill-refs` | `gen:skill-docs`, `gen:skill-refs` |
719719
| The react-blocks contract | `check:react-blocks` | `gen:react-blocks` |
720720

@@ -758,8 +758,9 @@ Principles the wrapper encodes (its own output is the authority on detail):
758758
a name, with accepted cases in the shrink-only, hand-edited
759759
`dual-source-exports.baseline.json`.
760760

761-
Two generators have **no** gate at all — `gen:openapi` and `gen:sbom`. Nothing verifies
762-
their output is current; the wrapper reports that each run rather than staying silent.
761+
Three generators have **no** gate at all. Nothing verifies `gen:openapi` and `gen:sbom`
762+
are current, and `gen:migration-registry` writes gitignored output that install, build and
763+
test regenerate; the wrapper reports all three each run rather than staying silent.
763764

764765
---
765766
## Context Routing — apply the right role per path

‎content/docs/references/api/protocol.mdx‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2368,7 +2368,7 @@ Installed package with runtime lifecycle state
23682368
| Property | Type | Required | Description |
23692369
| :--- | :--- | :--- | :--- |
23702370
| **rule** | `string` | ✅ | Stable diagnostic rule id (`flow-multi-write-unfiltered`, `approval-expression-invalid`, …). Machine-readable and stable across releases — the key a renderer groups or suppresses by. |
2371-
| **path** | `string` | ✅ | Config path inside the SUBMITTED body (`flows[0].nodes[1].config.multi`), so an editor can jump to the offending key. May be empty when the finding is about the item as a whole. For the collection-resident write types (`object` / `permission` / `book`) the TOP-LEVEL collection entry is keyed by NAME (`objects.acme_invoice.sharingModel`), never by an array index — the gate evaluates against a private per-write snapshot whose indexes no caller can resolve. Every other write type is the sole member of its own collection, so its `[0]` is trivially stable and stays positional (`flows[0]...`), as do nested positions inside one named item (`objects.acme_invoice.indexes[1]`), which index the author's own document. |
2371+
| **path** | `string` | ✅ | Config path inside the SUBMITTED body (`flows[0].nodes[1].config.multi`), so an editor can jump to the offending key. May be empty when the finding is about the item as a whole. The TOP-LEVEL collection entry follows one rule. The gate evaluates against a private per-write snapshot whose indexes no caller can resolve; a collection-resident write type is one whose collection that snapshot fills with the tenant's other stored items beside the written one, and there the entry is keyed by NAME, never by an array index — for example `objects.acme_invoice.sharingModel` (an `object` write) or `datasets.acme_invoice_metrics.dimensions[0].field` (a `dataset` write). Every other write type is the sole member of its own collection in that snapshot, so its `[0]` IS the written item and stays positional (for example `flows[0]...`). Which write types are collection-resident is derived, never listed: `@objectstack/lint`'s runtime gate computes the set (`NAME_KEYED_STACK_KEYS`) as the collections it carries as resolution context that a gated write type also lands in, so it changes with the gate, not with this text. An entry whose name cannot be spliced into a dotted path keeps its index. Nested positions inside one named item stay positional (`objects.acme_invoice.indexes[1]`): they index the author's own document. |
23722372
| **where** | `string` | ✅ | Human-readable location — `flow "leave_approval" · node "approve"`. Prose for a person; use `path` for anything mechanical. |
23732373
| **message** | `string` | ✅ | What is wrong, in the rule author's own words. |
23742374
| **hint** | `string` | ✅ | How to fix it. |
@@ -2594,7 +2594,7 @@ Installed package with runtime lifecycle state
25942594
| Property | Type | Required | Description |
25952595
| :--- | :--- | :--- | :--- |
25962596
| **rule** | `string` | ✅ | Stable diagnostic rule id (`flow-multi-write-unfiltered`, `approval-expression-invalid`, …). Machine-readable and stable across releases — the key a renderer groups or suppresses by. |
2597-
| **path** | `string` | ✅ | Config path inside the SUBMITTED body (`flows[0].nodes[1].config.multi`), so an editor can jump to the offending key. May be empty when the finding is about the item as a whole. For the collection-resident write types (`object` / `permission` / `book`) the TOP-LEVEL collection entry is keyed by NAME (`objects.acme_invoice.sharingModel`), never by an array index — the gate evaluates against a private per-write snapshot whose indexes no caller can resolve. Every other write type is the sole member of its own collection, so its `[0]` is trivially stable and stays positional (`flows[0]...`), as do nested positions inside one named item (`objects.acme_invoice.indexes[1]`), which index the author's own document. |
2597+
| **path** | `string` | ✅ | Config path inside the SUBMITTED body (`flows[0].nodes[1].config.multi`), so an editor can jump to the offending key. May be empty when the finding is about the item as a whole. The TOP-LEVEL collection entry follows one rule. The gate evaluates against a private per-write snapshot whose indexes no caller can resolve; a collection-resident write type is one whose collection that snapshot fills with the tenant's other stored items beside the written one, and there the entry is keyed by NAME, never by an array index — for example `objects.acme_invoice.sharingModel` (an `object` write) or `datasets.acme_invoice_metrics.dimensions[0].field` (a `dataset` write). Every other write type is the sole member of its own collection in that snapshot, so its `[0]` IS the written item and stays positional (for example `flows[0]...`). Which write types are collection-resident is derived, never listed: `@objectstack/lint`'s runtime gate computes the set (`NAME_KEYED_STACK_KEYS`) as the collections it carries as resolution context that a gated write type also lands in, so it changes with the gate, not with this text. An entry whose name cannot be spliced into a dotted path keeps its index. Nested positions inside one named item stay positional (`objects.acme_invoice.indexes[1]`): they index the author's own document. |
25982598
| **where** | `string` | ✅ | Human-readable location — `flow "leave_approval" · node "approve"`. Prose for a person; use `path` for anything mechanical. |
25992599
| **message** | `string` | ✅ | What is wrong, in the rule author's own words. |
26002600
| **hint** | `string` | ✅ | How to fix it. |
@@ -2648,7 +2648,7 @@ Installed package with runtime lifecycle state
26482648
| Property | Type | Required | Description |
26492649
| :--- | :--- | :--- | :--- |
26502650
| **rule** | `string` | ✅ | Stable diagnostic rule id (`flow-multi-write-unfiltered`, `approval-expression-invalid`, …). Machine-readable and stable across releases — the key a renderer groups or suppresses by. |
2651-
| **path** | `string` | ✅ | Config path inside the SUBMITTED body (`flows[0].nodes[1].config.multi`), so an editor can jump to the offending key. May be empty when the finding is about the item as a whole. For the collection-resident write types (`object` / `permission` / `book`) the TOP-LEVEL collection entry is keyed by NAME (`objects.acme_invoice.sharingModel`), never by an array index — the gate evaluates against a private per-write snapshot whose indexes no caller can resolve. Every other write type is the sole member of its own collection, so its `[0]` is trivially stable and stays positional (`flows[0]...`), as do nested positions inside one named item (`objects.acme_invoice.indexes[1]`), which index the author's own document. |
2651+
| **path** | `string` | ✅ | Config path inside the SUBMITTED body (`flows[0].nodes[1].config.multi`), so an editor can jump to the offending key. May be empty when the finding is about the item as a whole. The TOP-LEVEL collection entry follows one rule. The gate evaluates against a private per-write snapshot whose indexes no caller can resolve; a collection-resident write type is one whose collection that snapshot fills with the tenant's other stored items beside the written one, and there the entry is keyed by NAME, never by an array index — for example `objects.acme_invoice.sharingModel` (an `object` write) or `datasets.acme_invoice_metrics.dimensions[0].field` (a `dataset` write). Every other write type is the sole member of its own collection in that snapshot, so its `[0]` IS the written item and stays positional (for example `flows[0]...`). Which write types are collection-resident is derived, never listed: `@objectstack/lint`'s runtime gate computes the set (`NAME_KEYED_STACK_KEYS`) as the collections it carries as resolution context that a gated write type also lands in, so it changes with the gate, not with this text. An entry whose name cannot be spliced into a dotted path keeps its index. Nested positions inside one named item stay positional (`objects.acme_invoice.indexes[1]`): they index the author's own document. |
26522652
| **where** | `string` | ✅ | Human-readable location — `flow "leave_approval" · node "approve"`. Prose for a person; use `path` for anything mechanical. |
26532653
| **message** | `string` | ✅ | What is wrong, in the rule author's own words. |
26542654
| **hint** | `string` | ✅ | How to fix it. |

0 commit comments

Comments
 (0)