Skip to content

Commit f430c6a

Browse files
committed
Merge remote-tracking branch 'origin/main' into claude/project-thread-ybjm3h
2 parents 34b0e4a + f6ffd72 commit f430c6a

61 files changed

Lines changed: 1869 additions & 411 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: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
---
2+
'@object-ui/app-shell': patch
3+
'@object-ui/console': patch
4+
---
5+
6+
fix(app-shell): the approval step line draws the route the request's run took, not the flow graph's first branch
7+
8+
Clause-②: no
9+
Metadata-protocol: none
10+
11+
The approval step line used to draw the request's `flow_steps`. The approvals read builds that list by walking the flow from its start and taking the `approve` edge, else the `true` edge, else the first edge at every node. So a request that a decision routed down another branch showed rungs it never reaches. A contract routed from the manager to the finance-only rung read "manager · legal + finance (joint) · executive · general manager" while it waited on the finance controller.
12+
13+
What changes:
14+
15+
- On a business record's approvals panel, the line now shows the steps the pending request's flow run has passed, oldest first, then the pending step. Each step comes from a request the run actually opened: same flow run, another node, approved, and opened before the pending one. A step approved in several rounds is shown once. Nothing ahead of the pending step is shown, because no request row says which branch the run takes next.
16+
- The approvals list serves a request only to its participants, so a step the viewer had no part in is left out rather than guessed. With fewer than two steps, no line is drawn.
17+
- On the approval request page, the decision panel no longer draws a step line. Its row proves only its own step, which the page header already names. The line returns there once the approvals read serves the route the run takes.
18+
19+
What to do: nothing. Nothing a flow, page or object declares changes.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
---
2+
'@object-ui/core': patch
3+
---
4+
5+
fix(core): a declared action's confirm dialog is titled with the action's label, and a danger action's confirm button is destructive
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
Before, clicking an action that declares `confirmText` always opened a dialog titled 「确认操作」 / "Confirm Action" with a neutral 「继续」 / "Continue" button: the action's own label and its `variant: 'danger'` never reached the dialog. `ActionRunner` now hands its `ConfirmationHandler` the existing `options` bag:
11+
12+
- `title` and `confirmText` are the action's `label`, already localized by the surface that dispatches it.
13+
- `destructive: true` when the action declares `variant: 'danger'`, which paints the console's confirm button in the destructive style, as the record Delete already does.
14+
- The cancel button keeps the host's own copy. An action with neither a label nor the danger variant is asked with the message alone, as before.
15+
16+
What to do: nothing. No key is added; the action already declares its label and variant. A custom `ConfirmationHandler` that ignores its second argument behaves as before.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
---
2+
'@objectstack/service-print': patch
3+
---
4+
5+
feat(service-print): a record prints on any print page of its object, not only on the default record page
6+
7+
Clause-②: yes (widening)
8+
Metadata-protocol: none
9+
10+
`printRecordToFile` (and `POST /api/v1/print/records`) used to refuse every page that was not the object's default record page (`VALIDATION_ERROR` / 400), so an app that printed an object had to turn that object's everyday record page into the printed document. A record now prints on any record page of its object that declares `print`:
11+
12+
- **The default record page** (`isDefault: true`) renders on the console's record route, `CONSOLE/apps/APP/OBJECT/record/ID`, as before.
13+
- **Any other print page** renders on the console's named record-page route, `CONSOLE/apps/APP/OBJECT/record/ID/page/PAGE`, which draws that page and never falls back to the default one. An object keeps its working record page and adds its documents (an invoice, a quote, a delivery note) beside it.
14+
15+
Two refusals remain, both `VALIDATION_ERROR` / 400 before anything is minted or rendered: the page is not a record page of the object, and the page declares no `print`. "A record page of the object" is the console's own reading (`type: 'record'` and that `object`), so the server prints exactly the pages the console's named route draws. An unknown page name is still `RESOURCE_NOT_FOUND` / 404. No metadata key is added or changed: the request already names its page.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
'@object-ui/i18n': patch
3+
'@object-ui/console': patch
4+
---
5+
6+
fix(console,i18n): the OAuth consent screen explains the `metadata:write` scope and never shows an unknown scope as its bare spelling
7+
8+
Clause-②: yes
9+
Metadata-protocol: none
10+
11+
- **`@object-ui/console`.** The consent screen at `/oauth/consent` names every MCP scope in a sentence. The sentences come from `@objectstack/spec`'s `MCP_OAUTH_SCOPE_CONSENT_COPY`, the copy the spec declares beside each scope, through a switch that is exhaustive over `McpOauthScope`: a scope added to the spec without a case here fails to compile. `metadata:write` used to appear as that raw string beside three plain sentences. It now reads "Build and change this app's metadata as drafts, within your own permissions — a person publishes them". A scope the page cannot name renders as "Unrecognised permission: SCOPE" instead of the bare scope string, which could read as a description of the grant.
12+
- **`@object-ui/i18n`.** Every locale pack gains `oauth.consent.scope.metadataWrite` and `oauth.consent.scope.unrecognised` (the latter takes a `{{scope}}` placeholder). `TranslationKeys` widens by these two keys.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
---
2+
'@objectstack/lint': patch
3+
'@objectstack/spec': patch
4+
---
5+
6+
fix(lint): `security-controlled-by-parent-owner-policy-without-opt-in` prescribes `ownerMayEdit` only for a policy keyed on `owner_id` (#26591)
7+
8+
Clause-②: no (lint advice is not a contract face; no accepted metadata shape moves)
9+
Metadata-protocol: none
10+
11+
`sharing.controlledByParent.ownerMayEdit: true` admits one caller past a `controlled_by_parent` detail's master gate: the user in the stored row's `owner_id`, on a detail that carries that column. The lint arm used to prescribe the opt-in for any `update` / `all` policy of the form `FIELD == current_user.id`. So an app whose accountable person is its own user field (`owner == current_user.id`) declared the opt-in, lint went quiet, and that user was still refused with `403 requires edit access to its master record`.
12+
13+
- The opt-in is still prescribed, as before, for a policy keyed on `owner_id` on a detail that carries it and has not opted in, and the arm stays silent once it has.
14+
- A policy keyed only on another user field is now flagged whether or not the detail opted in. The verdict says the opt-in admits only the stored `owner_id`. The hint never names the opt-in: it says to key the accountable person on `owner_id`, or drop the policy.
15+
- On a detail with no `owner_id` (`ownership: 'business_unit' | 'org' | 'none'` and no `owner_id` field of its own), the opt-in names nobody. A policy keyed on `owner_id` there gets the same treatment, and the hint says to give the detail an `owner_id` column.
16+
- The runtime gate is unchanged. The rule's `os explain` entry covers both cases. The `ownerMayEdit` evidence line in `@objectstack/spec`'s liveness ledger now describes the narrowed arm.
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
---
2+
'@objectstack/spec': minor
3+
---
4+
5+
fix(spec)!: the filter save and validate doors refuse an `addDays` offset whose `{ $field }` is a dotted path, in the words SQL push-down already refuses it with on every read
6+
7+
An `addDays` offset reads its days from a column of the same record: `{ $field: 'due_on', addDays: { $field: 'grace_days' } }`, or `{ $now: true, addDays: { $field: 'review_sla_days' } }`. A dotted offset such as `addDays: { $field: 'contract_type.review_sla_days' }` passed `FieldReferenceSchema`, `NowAnchorSchema`, `FilterConditionSchema`, the dataset `filter` and measure `filter`, and `os validate`, and then failed every SQL read with `INVALID_FILTER` / 400. The one executor that walks the path, the memory evaluator, reads a lookup's id there on a stored row and counts the offset as zero days. Now every authoring door refuses it, naming the path: `the addDays offset "contract_type.review_sla_days" is a dotted path, and SQL push-down compiles same-table column references only (no relation traversal, no alias-qualified columns).`
8+
9+
This retires the earlier reading that the dotted offset validates and is answered in memory (the #14104 ruling-record correction 5550346132, and the spec pin that accepted `addDays: { $field: 'duty.grace_days' }`). Ruling A on #25808 bounds the offset to a same-record column.
10+
11+
Clause-②: no (narrowing) — the save and validate doors refuse a dotted `addDays` `$field` that every SQL read already refused. Nothing is widened.
12+
13+
Metadata-protocol: narrowed (a filter, a dataset filter or a dataset measure filter may no longer carry an `addDays` offset whose `$field` is a dotted path)
14+
15+
<!-- adr-0087: not-required (no-migration-prescription) No key of any schema is added, removed or renamed. The dotted offset never compiled on SQL push-down and, on a stored row, read as zero days in memory, so there is no mechanical rewrite that keeps what the author meant: the related value has to be copied onto a numeric column of the row, which is an application change the author makes. -->
16+
17+
**BREAKING** accept-set narrowing on the `addDays` offset (`@objectstack/spec`).
18+
19+
- **Before.** `FieldReferenceSchema`, `NowAnchorSchema`, the ordering slots of `FieldOperatorsSchema`, `FilterConditionSchema` (data filters, dataset `filter`, dataset measure `filter`) and `os validate` accepted `addDays: { $field: 'a.b' }`. SQL reads answered `INVALID_FILTER` / 400; a dataset measure filter answered it with the reason withheld.
20+
- **Now.** Each of those doors refuses it. `parseFilterAST` and the engine's filter doors (`where`, `aggregations[i].filter`, `having`) refuse it with `INVALID_FILTER` / 400 and the path named, so a dataset measure filter that never met the save door now gets the reason too.
21+
- **Unchanged.** A same-record offset column, a literal offset, and the reference's own `$field` (a dotted base reference keeps its asymmetry: resolved in memory, refused by SQL push-down).
22+
- **Who sees it.** An author who read the offset through a relation. Every SQL deployment already refused that filter on read.
23+
24+
**The remedy.** Copy the related value onto a numeric column of the record (on create and whenever the relation changes) and name that column: `addDays: { $field: 'review_sla_days' }`.

‎.claude/skills/pm-dispatch/SKILL.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ PM 的工作是循环:选卡 → 认领 → 派发 → 收集 → 复核 → 报
6868
- 批 = 一个上下文里读完的 ≤5 张同族卡,是质量单位,⛔ 不是预算。
6969
- 班 = 一个会话的任期,只以交接或惰性回收结束。
7070
- 席位两形:Routine 席与项目席(会话是车队项目的线程);细则见 `references/seat-lifecycle.md`。
71-
- 项目席唤醒 = PR 订阅 + 每项目一个自建自绑的巡查 routine,默认每小时;⛔ 无 `send_later` 链。
71+
- 项目席唤醒 = PR 订阅 + 每项目一个自建自绑巡查 routine(总监席除外);⛔ 无 `send_later` 链。
7272
- 探针先行:fire 的第一动作是一次最便宜的车道盘点查询,⛔ 在它之前不读任何长文本。
7373
- 队列空 + 无在飞 + 无待复核 + 决策箱无新答复 ⇒ 自退。
7474
- 分诊席跑直连会话、默认判断档;直连会话按召唤跑,⛔ 无自醒不常驻;裁决归总监席。

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@
3030

3131
- 档位与 effort 的定法住 `execution-duties.md` 派发节:档位引 `dispatch-gates --tier`,effort 按卡难度。
3232
- 改技能(协议语义面)的卡恒契约复审档 `CONTRACT_REVIEW_TIER`;⛔ 不抄模型名。
33-
- 按派发令所写的 model 与 effort 开线程,⛔ 不等维护者原话;常设原话有则席位引作 `reason:`。
3433

3534
## 五、卡线程的 `needs_decision`(coordinator 收到时读;席位写分析时读)
3635

@@ -47,3 +46,4 @@
4746

4847
- 项目内对维护者用维护者的语言;GitHub 内容英文,照 SKILL.md 全体座位不变量。
4948
- 只在结果、阻塞或待裁决时向维护者汇报,与席位同规;例行进展进线程清单。
49+
- 转述线程 PR「无需你做」前自读其 `mergeable_state`;`dirty` ⇒ 退回线程先合 main,⛔ 不转述。

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@
137137
- 卡线程交回或其 PR 合入,席位当即告知 coordinator 释放槽位。
138138
- 档位与 effort 席位自推导 ⛔ 不等维护者原话:`--tier` 过路径,MANDATORY 即绑;effort 按难度。
139139
- 派发令写两者与推导(`--tier` 行或难度读数),协调者照令开线程;常设原话有则作 `reason:`。
140-
- 席位留认领文本、派发令、契约复核、ACCEPT 与落地;线程实施、开 draft PR、驱动 CI。
140+
- 席位留认领文本/派发令/契约复核/ACCEPT/落地;线程实施、开 draft PR、驱动 CI 与可合并。
141141
- 派发令由 coordinator 写进线程开场消息,`Writes:` 行逐字在内,⛔ 不落共享目录;卡上留指针。
142142
- coordinator 开线程即读线程分支交席位(读不到则线程自报);⛔ 线程不认领、不写 assignee。
143143
- 席位写 assignee+`pm:dispatched`+带该 `Branch:` 的认领;线程核对最新认领指向本分支方开工。

‎.claude/skills/pm-dispatch/references/execution-duties.md‎

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

154154
- 收集先扫 GitHub(各模式),标记评论在 = 报告完整;评论与返回消息皆无才进探活/判死。
155155
- 标记两种拼写等效(HTML 注释形、首行 `os-dev-report`);⛔ 永不把没收到失败通知读作还在跑。
156-
- 报告点名 PR ⇒ 会话席当即 `subscribe_pr_activity` 并记座位贴;中继开的 PR 永不自动挂会话。
156+
- 报告点名 PR ⇒ 会话席当即订阅、记座位贴,照落地 B 节看护;中继开的 PR 不自动挂会话。
157157
- ① Routine 席巡检首动作 `list_triggers` 确认 Routine 启用;项目席由事件与巡查 routine 唤醒。
158158
- ② Routine 席在飞期间主巡检间隔 ≤45 分钟,待命期 60–70;节奏维护者明示可改。
159159
- ③ 已派发未回报的 dev 逐个发状态询问;派发后 ~45 分钟无远程产出即到门槛。

0 commit comments

Comments
 (0)