Skip to content

Commit 85c97f5

Browse files
committed
Merge origin/main into claude/project-thread-l8wnbr
Claude-Session: https://claude.ai/code/session_01FEkQ5ni5yjiTswP6crgAkX Co-Authored-By: Claude <noreply@anthropic.com>
2 parents d5f9db8 + c9a9981 commit 85c97f5

118 files changed

Lines changed: 5295 additions & 2818 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
---
2+
'@object-ui/app-shell': patch
3+
'@object-ui/i18n': patch
4+
'@objectstack/platform-objects': patch
5+
---
6+
7+
feat(setup): Setup › Packaged automation's flows half retires, and Packaged actions becomes its own Setup entry
8+
9+
Clause-②: no
10+
Metadata-protocol: none
11+
12+
ADR-0146 D11 retires Setup's packaged FLOWS page. Studio's Automations pillar now holds a packaged flow's on/off switch and its copy (New similar). That happened in D4. Setup keeps the operational state of packaged actions.
13+
14+
- **Setup navigation.** The `nav_packaged_automation` entry ("Packaged Automation", component ref `automation:packaged`) is replaced by `nav_packaged_actions` ("Packaged Actions", component ref `automation:packaged-actions`). It sits in the same group beside Packages, and like before it has no service or permission gate. The new label is translated in all four locales, and the retired id is tombstoned so its label cannot come back without a nav item.
15+
- **The page.** `automation:packaged-actions` opens a page that holds only the packaged-actions switch list. The `automation:packaged` ref is no longer registered, and the old page is removed. In `@object-ui/app-shell`, `PackagedActionsPage` replaces the `PackagedAutomationPage` export. The shared flow and action helpers stay.
16+
- **Studio stops linking to the retired page.** The "Organization flows" empty state now says how to change a packaged flow: open its package and use New similar. It no longer links out to Setup. The landing's `packagedAutomationPath` route builder is removed.
17+
18+
A second change (#28578) moves Setup's metadata pages into Studio's All metadata area. It follows separately.
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
---
2+
'@objectstack/lint': minor
3+
'@objectstack/metadata-protocol': minor
4+
'@objectstack/types': minor
5+
'@objectstack/rest': patch
6+
'@objectstack/runtime': patch
7+
---
8+
9+
fix(lint,metadata-protocol,rest,runtime,types): a roll-up the engine refuses at every recompute is refused at save and publish, and a child write that landed answers its success (#28661)
10+
11+
Clause-②: no (narrowing)
12+
Metadata-protocol: narrowed
13+
Skills-docs: none — `skills/objectstack-data/rules/relationships.md` and `rules/field-types.md` teach `summaryOperations` only with `sum` over a numeric `amount` and `count`; no file under `skills/**` teaches a `sum`/`avg` over a text field, and every taught example stays admitted.
14+
15+
**BREAKING** (narrowing) for a `summary` field whose `sum` or `avg` names a child field the engine does not aggregate (and, on `os validate`, `os build` and the runtime object save/publish door, a `min` / `max` whose answer is not a number, which `os lint` already refused).
16+
17+
- **Before.** A published `summary` field with `sum` or `avg` over a child `text` field published as valid (`publish-drafts`, the draft's `_diagnostics: { valid: true }`, `os validate`, `os build`). Every later create, update and delete of a child then answered `500 INTERNAL_ERROR`, though the write had landed and the parent's valid roll-ups had recomputed: the recompute's aggregate refuses the pair after the commit. A client that retried a create made a duplicate.
18+
- **Now, at the doors.** The roll-up rule moves from `os lint` alone into the authoring-rule registry (`lintSummaryRollups`, gating, on `os validate`, `os build`, `os lint` and the runtime object save/publish door). A `sum` or `avg` over a child field outside the aggregate × field-type table (`AGGREGATE_FIELD_TYPE_COMPATIBILITY`, the table the engine's aggregate door reads) is refused under `rollup/non-numeric-aggregand`, at `objects.<parent>.fields.<field>.summaryOperations.field`, with the accepted child types and `count` as the fix. The `min`/`max` arm `os lint` already reported keeps its message and now also gates `os validate`, `os build` and the runtime object save/publish door. A child staged as a draft in the same publish batch is judged with it. `os lint` no longer reports the rule a second time.
19+
- **Now, after a write.** When a parent's roll-up cannot be recomputed after a child write — a roll-up stored before this gate, or any other recompute failure — `POST`, `PATCH` and `DELETE /api/v1/data/:object[/:id]` answer the write's own success (`201`/`200`, the same body the write answers when every roll-up recomputes), on both `RestServer` and the runtime dispatcher. The stale summary column is logged at `error`, with the fix, once per field per process (the memory is never cleared; a restart reports it again). The protocol's `createData`/`updateData`/`deleteData` still throw `ERR_SUMMARY_RECOMPUTE` carrying `written`, so the import runner and seed loader read it as before. The recogniser and the earned-answer carrier (`isSummaryRecomputeRefusal`, `earnedAnswerOf`, `staleSummaryAnswerLines`) are new exports of `@objectstack/types`, the one package the protocol, `rest` and `runtime` all depend on.
20+
- **Not refused.** A `summaryOperations.object` naming no object (the card's `qsf_nope`) is still admitted: an unmanaged install (`install-local --unmanaged`) saves a package's parent before the child its roll-up reads, so a door refusing a missing child would refuse that install.
21+
- **Who moves.** No `examples/**` or `hotcrm` roll-up is refused: every `sum`/`avg`/`min`/`max` there aggregates a `number` or `currency` child field.
22+
23+
<!-- adr-0087: not-required (no-migration-prescription) the diff registers no retirement and no key changes shape: a roll-up summing or averaging a non-numeric child field never answered a value, and the fix is an authoring choice (a numeric child field, or count) that no mechanical rewrite can make -->
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
---
2+
'@object-ui/app-shell': patch
3+
---
4+
5+
perf(console): write app navigation where a page or dashboard is created or deleted, and stop reading the whole `/meta/page` and `/meta/dashboard` lists on every app page
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
`NavigationSyncEffect` read both lists on every app page after the first paint, only to diff their name sets and add or remove navigation entries. The console's sites now write navigation themselves:
11+
12+
- **The metadata editor's Publish** adds a package-less page or dashboard to every writable app the first time it is published (the draft was its only layer). A later publish of the same item is an update and writes nothing.
13+
- **The metadata editor's Delete** removes a package-less page or dashboard from every writable app when the delete leaves no baseline. A reset to a packaged baseline writes nothing.
14+
- **Publish all** asks each package-less page or dashboard draft's layers before publishing it, and adds the ones it creates to navigation.
15+
16+
The diff's rules are kept: platform artifacts (a `sys_` name, or `_packageId` / `_provenance: 'package'`) never write navigation, write-protected apps are skipped, and nothing is written in draft preview. A create no longer adds a second entry to an app that already references the page or dashboard.
17+
18+
What no longer reaches navigation: a package-less page or dashboard created or deleted outside this tab (a server-side agent, another tab, a raw REST call). The diff noticed one only when it appeared in this tab's list mid-session; it now reaches navigation only through its own writer. Items from a package install or a CLI deploy carry `_packageId` and never reached navigation through the diff.
19+
20+
`NavigationSyncEffect` is removed. `@object-ui/app-shell` is private and nothing outside it imported the effect.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
---
2+
"@objectstack/objectql": patch
3+
---
4+
5+
Clause-②: yes (widening) — `SchemaRegistry.uninstallPackage` gains an optional `options` parameter
6+
Metadata-protocol: none
7+
8+
fix(objectql): a package refused while it registers leaves nothing of itself registered
9+
10+
When `ObjectQL.registerApp` refuses a package, the package is no longer left partly registered. Before, the package record, its objects and its apps could stay in the running system until restart. What is refused, and every refusal's code, status and words, is unchanged.
11+
12+
- **A first install that is refused** (a fresh package id) leaves no package record, object, extension, app, item, picklist extension, navigation contribution, contributed kind, datasource definition or nested-plugin namespace behind. The same id can then be installed again once the package is fixed. A persisted "disabled" state for that id still applies to its next install.
13+
- **A refused upgrade of an installed package** whose body alone is at fault (for example an item that names another package as its owner) leaves the installed version serving, untouched.
14+
- `SchemaRegistry.uninstallPackage(id, options?)` takes an optional `{ keepBootSeed: true }`. The engine passes it when it withdraws a refused first install. An operator's uninstall omits it and behaves as before.
15+
16+
A body with more than one defect may now be refused for a different one of them than before, because refusals that depend only on the body are now checked before the others.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
---
2+
'@objectstack/metadata-protocol': patch
3+
'@objectstack/service-automation': patch
4+
---
5+
6+
fix(metadata-protocol, service-automation): a declared `.custom` flow override is armed from the first boot pull, and a legacy package-less row of the same name is named in the boot's shadowing warning (#28717)
7+
8+
Clause-②: yes (widening: the new public export shadowedStoredRowsOf from @objectstack/metadata-protocol; no accept-set change)
9+
Metadata-protocol: none
10+
11+
A shipped flow name can hold two stored rows at once: the shipping package's declared override in `<base>.custom` (#28412) and a legacy package-less row saved before it. The registry keeps one bare slot per flow name, and the boot hydration used to give it to whichever row the store read last. When that was the legacy row, the automation boot pull never saw the override: it armed the base flow, and a record change fired before `kernel:ready` ran the base with no error.
12+
13+
- **The override keeps the slot.** The registry hydration now keeps the declared override in the name's bare slot over any other stored row of the name, in either read order. It is judged by the one served-override predicate on the row's stored `package_id` column, never on a body's `_packageId`. Every other pair of stored rows keeps "the last row read wins".
14+
- **The legacy row stays stored and shadowed.** It is not deleted or migrated. The boot pull lists it beside the override, so the engine's shadowing receipt records it as a runtime row, and the existing collision warning names it with the override that is armed.
15+
- **No change when only one row exists.** A legacy row alone is still shadowed by the base and warned. An override alone is still armed silently.
16+
17+
`shadowedStoredRowsOf` is newly exported from `@objectstack/metadata-protocol`. It returns the stored rows a hydrated flow body holds the bare slot over, carried as a non-enumerable symbol that never leaves the process.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
---
2+
'@objectstack/spec': minor
3+
'@objectstack/runtime': minor
4+
---
5+
6+
feat(runtime): `GET /packages/:id/customizations` names the package behind each field and flow another package contributes to the base
7+
8+
Clause-②: yes (widening)
9+
Metadata-protocol: added
10+
11+
Studio draws the items another installed package brings to a base greyed, naming their source (ADR-0029 D7 as amended by ADR-0146 D3). Until now no served read said which package contributed what. The per-base customization read now answers a fourth list, `contributions`, with its count:
12+
13+
- **What is listed.** Every field another package's object extension (`objectExtensions`) adds to an object the base owns, and every flow such a package ships whose trigger object (the `start` node's `config.objectName`) the base owns. One entry per item: `{ kind: 'field' | 'flow', object, name, packageId }`.
14+
- **What is not.** The base's own items, and its `<base>.custom` pair's (the pair's members stay in `extensions`). A flow authored in this environment is the environment's, not a package's. A flow of a disabled package is not served, so it is not listed.
15+
- **The stamp is on the item.** Two packages adding a field of the same name are two entries, each naming its package; a client joins nothing by name.
16+
- `GetPackageCustomizationsResponseSchema` declares the member and `counts.contributions`, strict as the rest of the answer. A client parsing the answer through the schema needs this version of `@objectstack/spec`.
17+
18+
Navigation entries contributed to the base's app are not in this list yet.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
---
2+
'@objectstack/lint': minor
3+
---
4+
5+
**BREAKING** fix(lint)!: a page variable `source` naming no component on the page, and a page predicate reading a `page.NAME` the page does not declare, are refused
6+
7+
Clause-②: no (narrowing)
8+
Metadata-protocol: narrowed
9+
Skills-docs: none — no file under `skills/**` teaches a page variable's `source` binding or `page.NAME` reads; both refusals carry their own fix and `os explain` entries.
10+
11+
<!-- adr-0087: not-required (no-migration-prescription) The narrowing is a refusal at the authoring doors (the metadata save door, a draft's diagnostics and its publish, and os validate / os build / os lint) of two references that never resolved at runtime: a page variable `source` that names no component id on the page (the console binds the writer by exact id, so nothing ever writes the variable), and a page component `visibleWhen` reading `page.NAME` for a `NAME` the page's `variables` does not declare (the read faults and the element renders unconditionally). No metadata key, accepted key, export or error code is removed or renamed, and no stored shape changes. The fix is the author's choice of the component id or variable name they meant, so there is no mechanical conversion for `objectstack migrate meta` to apply; the shipped corpus (examples, platform packages) carries 3 variable `source` bindings and 5 `page.NAME` predicate reads (all on two showcase pages), none of them refused. -->
12+
13+
#28663 judged a page's `variables` list itself. Two references into that list still saved, read `valid: true` as drafts, published and passed `os validate`, though neither can ever resolve:
14+
15+
- `page-variable-source-unknown` (in `validatePageVariables`): a variable whose `source` names a component `id` the page does not hold. The console binds a writer element by comparing its `id` with `source` exactly, so no element writes the variable and it keeps its default for the page's whole life. Components nested inside containers count; a page authored as code (`kind: 'react'` or `'html'`) is not judged, since its components live in the code. Located at `variables[i].source`.
16+
- `visibility-page-variable-undeclared` (in `validateVisibilityPredicates`): a page component `visibleWhen` reading `page.NAME` (or `page.?NAME`) where the page declares no variable `NAME`. `page` holds the page's declared variables and nothing else, so the read faults and the element always renders. Judged from the predicate's existing parse; an index read (`page['x']`) and a predicate whose comprehension binds `page` as its loop variable are not judged. Located at the component.
17+
18+
**Where.** The metadata save door (`PUT /api/v1/meta/page/:name`, Studio, MCP), a draft's `_diagnostics`, the publish (`422 INVALID_METADATA`), and `os validate` / `os build` / `os lint`. Stored pages keep loading and serving; only the next save of one with a finding is refused, with the fix.
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
---
2+
'@objectstack/metadata-protocol': minor
3+
'@objectstack/cloud-connection': minor
4+
---
5+
6+
fix(metadata-protocol)!: the override write gate judges an email template by its locale, so a `<base>.custom` override of a locale the base does not ship is refused (#28746)
7+
8+
Clause-②: yes (narrowing)
9+
Metadata-protocol: narrowed
10+
11+
<!-- adr-0087: not-required (no-migration-prescription) No key of any schema is added, removed, renamed or re-shaped: the override write gate (ADR-0146 D2) now counts the packages that ship an email template at the bundle member the body addresses, as the read side already serves it. A stored row this gate would refuse is not converted: it is served as before, and its next save meets the gate's own refusal with its remedy (drop `overrides`). -->
12+
13+
**BREAKING** accept-set narrowing at the override write gate, with one widening beside it. An email template is one item per `(name, locale)` (`ITEM_KEY_DISCRIMINATORS`), but the gate asked which packages of the closure ship the template's NAME, across every locale.
14+
15+
- **Narrowed.** A `<base>.custom` pair declaring `overrides: { package: <base> }` on a locale the base does not ship is refused `403 NOT_OVERRIDABLE` (`declared_package_ships_no_such_item`, the sentence now naming the locale) at the save door, at the per-item publish (`?member=`) and at a package publish, by `os validate` / `os build`, and at `POST /api/v1/marketplace/install-local`. It was admitted and served as an override, with no base member for drift detection or "Restore package original" to compare against. The remedy is in the sentence: remove `overrides` to keep it as the pair's own locale.
16+
- **Widened.** A locale the base does not ship, saved in the pair WITHOUT `overrides`, is the pair's own new member and is admitted at the same doors. It was refused `undeclared_same_name`, which told the author to declare an override of a member that does not exist. The same holds for a dependent package's template of a name its base ships in another locale: it collides with no base member.
17+
- **Unchanged.** A declared override of a locale the base ships is admitted; an undeclared same-locale item is still refused `undeclared_same_name`; every other type's identity is its name alone, as before; the read side already served per member.
18+
- **At rest.** A pair row stored before this release that declares an override of an unshipped locale keeps being served. Its next save carrying `overrides` is refused with the remedy; saved without `overrides`, it is the pair's own locale.
19+
20+
`@objectstack/metadata-protocol` exports `overrideWriteIdentityOf(type, identity, item)`, the one identity the gate's callers compare shipped and incoming items on: an item's name (an action's `<objectName>:<name>`), joined with its bundle member for a pair-identified type. `OverrideWriteItem.identity` (from `overrideWriteItemsOf`) is spelled through it, so an email template's identity now carries its locale.

‎.claude/skills/pm-dispatch/references/dispatch-runbook.md‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,13 @@
3636
- 写序:标签翻转只在认领评论写成功之后,两笔不原子。
3737
- 评论失败而标签已翻即制造一记真半状态;顺序反了就带重试义务,重试未成即回滚标签。
3838

39-
## 巡查常设项(每项目巡查与总监席摘要)
39+
## 巡查常设项(每席巡查与总监席摘要)
4040

4141
- 座位贴活性归总监席摘要:扫 `label:pm:seat` 挂 `🟢 <github-login>` 的贴,Routine 席读调度器。
4242
- 判据只认在任者自有产出(评论、认领、改正文)及其作者与时间戳,⛔ 收到的通知不算。
4343
- >24h 无自有产出 ⇒ 当场降级:改 `⏳ vacant` + 摘 assignee 同笔 + 证据评论(时间戳与链接)。
44-
- 跨账户复审:每项目巡查跑 `is:pr is:open label:needs:contract-review`,剔除本账户署名的 PR。
45-
- 复核前在 PR 留 `Claim:` 形评论(会话、账户);已有他账户的认领即跳过,⛔ 不双做。
44+
- 跨账户复审:每席巡查跑 `is:pr is:open label:needs:contract-review`,剔除本账户署名的 PR。
45+
- 复核前在 PR 留 `Claim:` 形评论(会话、账户);已有他席认领即跳过(同账户亦然),⛔ 不双做。
4646
- 复核照 `contract-review.md`;PASS 同笔摘标记、FAIL 不摘;总监席摘要列等候逾 24h 的 PR。
4747
- 车道巡查同跑:hold/closed 解锁重验回队;本仓无卡 fork PR 按 `external-contributions.md` 立卡。
4848

‎.claude/skills/pm-dispatch/references/project-forms.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333

3434
## 信箱 PR
3535

36-
- 每席一个 draft PR,键是席位 ⛔ 非账号:分支 `mailbox/<login>/<slug>`,标签 `pm:mailbox`+`pinned`。
36+
- 每席一个 draft PR,键是席位 ⛔ 非账号,epic 席 `epic:#<n>#<k>` 同样开;标签 `pm:mailbox`+`pinned`。
3737
- 正文首行 `Mailbox-of: <席位> · <login>`;坐席即开(`mailbox-open.mjs`)并订阅,存根记 `Mailbox:`。
3838
- 永不合并,不欠 CI,落地与台账查询排除它;移交或换账号关旧开新;评论首行 `To: <席位>`。
3939
- 次行恒为一键,下列事件必敲;一事一行 ≤200 字节,只写键与编号;表外即半态。

0 commit comments

Comments
 (0)