Skip to content

Commit fc8973e

Browse files
committed
Merge origin/main into claude/issue-24958-remedies-apply-refuses
Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LmjU2x1faLZ39DVTFyVca6
2 parents 8bb7ff6 + 7343229 commit fc8973e

39 files changed

Lines changed: 2625 additions & 216 deletions
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
---
2+
'@objectstack/platform-objects': patch
3+
---
4+
5+
fix(platform-objects): the OAuth application register form offers only Web and Native, and its Redirect URLs help states the rule for each (#24773)
6+
7+
Clause-②: no (a form's offering: the register route still accepts every type value it accepted, and nothing on the server reads the action's param options, so no accept set or served shape moves)
8+
9+
Setup › OAuth Applications › Register offered four application types: Web, Native, User-agent based and Public. The OAuth provider judges only `web` and `native`, so the register route registers the other two as `web`, with a client secret. The form now offers Web and Native, and still defaults to Web.
10+
11+
- **Redirect URLs help.** It said "Must use https:// in production", which did not tell an admin that a local `http://localhost` callback needs the Native type. It now states the rule for each type: a Web application needs `https://` on a host other than localhost or another loopback address; a Native application may also use `http://` on exactly `localhost`, `127.0.0.1` or `[::1]`, on any port.
12+
- **Application Type help.** New: choose Native for a desktop or mobile app, or for an app in local development that redirects to `http://localhost`.
13+
- **Unchanged.** The register route accepts the same values as before, including `user-agent-based` and `public`. The stored `sys_oauth_application.type` column still lists all four values, so a row written under an earlier provider line keeps its value. The `public` column is unchanged.
14+
15+
The zh-CN, ja-JP and es-ES translations carry the new help texts.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
'@objectstack/plugin-approvals': patch
3+
---
4+
5+
fix(plugin-approvals): approval notifications reach each recipient in their own language
6+
7+
Every approval notification now hands the messaging service its sentence as catalog keys beside the English literal (`EmitInput.keyedText`). The messaging service renders it at delivery in each recipient's `sys_user.locale`, else the deployment default. This plugin's translation bundle gains the sentences in en, zh-CN, ja-JP and es-ES, in its `messages` face (`messages.approvalRequestedTitle` and the rest).
8+
9+
- **Which notifications.** `approval.requested`, `approval.reminder`, `approval.sla_breached` (to the approvers and to the submitter), `approval.approved` and `approval.rejected`, the two out-of-office notices (`approval.ooo_substituted`, `approval.ooo_skipped`), `approval.escalated`, `approval.reassigned` and `approval.returned` (the send-back and the auto-rejection). For `approval.comment`, `approval.request_info` and a send-back that carries the approver's note, only the headline is keyed. The body is that person's own words, delivered as written.
10+
- **The record is named as before:** the object's label and the record id (`Deal D_123`), never a title. The label is rendered from the app's `objects.<object>.label` when its language pack holds one, else the schema label.
11+
- **The English is unchanged.** The payload's `title` and `body` are the `en` catalog entries, byte for byte the sentences each notification carried before. A messaging service that does not render keyed text delivers them as it always did.
12+
- **Unchanged.** Who is notified, each notification's topic, payload fields, deep link, dedup key and organization, and every channel's delivery behaviour apart from the text.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
---
2+
'@objectstack/service-messaging': minor
3+
---
4+
5+
feat(service-messaging): a notification's text can be catalog keys, rendered per recipient at delivery in that recipient's language
6+
7+
Clause-②: yes (widening): `EmitInput` gains an optional `keyedText` member, and the package's `.` entry gains `NotificationTextRenderer`, `assertNotificationTextKeys` and their types. An emit without `keyedText` is accepted and delivered exactly as before.
8+
9+
- **What was wrong.** `emit()` took the text a recipient reads only as `payload.title` and `payload.body`, literals the producer builds before any recipient exists. So every recipient of a notification read the producer's language. The approvals plugin's notifications reached zh-CN, ja-JP and es-ES approvers in English.
10+
- **The new member.** `EmitInput.keyedText` is `{ titleKey?, bodyKey?, params? }`. `titleKey` and `bodyKey` are `II18nService` keys made of a face and an id, such as `messages.approvalRequestedTitle`. A producer contributes their entries through its own translation bundle (`i18n.loadTranslations`, ADR-0029 D8). `params` fills the entries' `{{name}}` holes. A param is a string, or `{ key, fallback }`: the recipient-language text of `key` (such as `objects.<object>.label`), else `fallback`.
11+
- **Where it renders.** At delivery, per recipient, before the channel is called: on the inline fan-out, on each row the dispatcher sends, and on each row of a digest group. The language is the recipient's own `sys_user.locale`, else the deployment default (the recipient-locale chain, `resolveRecipientLocale`), negotiated against the locales the `i18n` service holds. Every channel, a third party's included, receives the rendered `title` and `body` it already reads.
12+
- **The fallback.** The literal `payload.title` and `payload.body` stay what a recipient reads whenever a key cannot be rendered: no `i18n` service, no renderer composed, or a key with no entry in the recipient's locale or in the catalog's declared fallback locale. A member the producer left unkeyed is also delivered as the literal. A key with no entry is said once per key and locale at `warn`.
13+
- **Refused at emit.** A malformed `keyedText` throws a `TypeError` before anything is written: an undeclared member, a key without a face, a param name no `{{hole}}` can spell, or a keyed param without its `fallback`.
14+
- **The delivery snapshot reserves `keyedText`,** the way it already reserves `title`, `body`, `severity`, `actionUrl` and `actorId`. The snapshot stores `EmitInput.keyedText` under that name only when the emit keyed its text. A producer's own payload member of that name (a flow `notify` node's free-form payload, say) is dropped from the snapshot and is never rendered. No producer in this repository writes one.
15+
- **Cost.** One `sys_user` read per keyed delivery (one per digest group). A delivery without keys, or a deployment with no `i18n` service, makes no read.
16+
- **Unchanged.** Every emit that passes no `keyedText` (and whose payload has no member of that name): the event row, the snapshot, and what the inbox, email and SMS channels deliver. The notify `template` path, `sys_notification_template` rendering (which now falls back to the rendered text), recipients, preferences and channel availability are also unchanged.
17+
- **Composing it.** `MessagingServicePlugin` wires one `NotificationTextRenderer` into the service and the dispatcher. A hand-built `MessagingService` passes `textRenderer` in its context and a `NotificationDispatcher` passes it in its options. Without one, keyed text is delivered as its literal, and the service says so once.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
'@objectstack/plugin-security': patch
3+
---
4+
5+
fix(plugin-security): a `controlled_by_parent` detail write asks its master the object-level question the master's own write door asks (ADR-0055 §3)
6+
7+
Clause-②: no
8+
9+
ADR-0055 §3 says editing, deleting or creating a detail requires edit access to its master. The master-edit gate's object-level leg asked only for an object-level `update` grant on the master. It now asks what the master's own write door asks of an `update` on the master, with that door's inputs: the master's security posture must resolve, the capabilities the master requires for `update` (ADR-0066 D3) must be held, and the grant is asked under the master's posture (ADR-0066 D2). A principal the master's own write door refuses at its object gate can no longer write a detail under that master.
10+
11+
- **Every door that asks the leg.** An insert, a by-id update and a by-id delete of a detail answer `PERMISSION_DENIED` (403) and write nothing. `checkControlledByParentWrite`, which the comment and attachment parent gates ask, answers `deny` on the `object_permission` leg. A record-grained `security/explain` of a by-id write follows the same leg.
12+
- **An insert answers before the reference check.** For such a principal, an insert under a master id that exists answers the same refusal as one under an id that exists nowhere. A principal that also cannot read the master, on an on-behalf-of write too, now gets this `PERMISSION_DENIED` on insert where it got the reference check's `VALIDATION_FAILED`. An insert under a master object that does not resolve answers `PERMISSION_DENIED` where it answered `OBJECT_NOT_FOUND` (404).
13+
- **Unchanged.** A principal the master's own write door admits for an `update` keeps writing its details, and so do a super-user grant and a system context. Under a master that is `public`, resolves and requires no capability for `update`, every principal gets the answer it got before.

‎.claude/agents/os-dev.md‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ model: opus
8585
- `bash scripts/pm/os-verify-lock.sh -c '<command>'`(或 `-- <argv>`);⛔ 永不手搓 `flock`/lockfile。
8686
- 一次前台调用领全部等待预算,阻塞到拿锁或 99;⛔ 不轮询重试,恒设 `OS_VERIFY_LOCK_SLOT`。
8787
- 它不保证机器空闲:`check:*` 门禁、install、dev server 不走它,与持锁同核并跑。
88-
- 测试电池 `pnpm check:pm-dispatch-gates` 亦属 `check:*`:不走锁,`nohup … > LOG 2>&1 &` 后 `tail --pid`。
88+
- 超前台上限者(`check:pm-dispatch-gates`、全量 dogfood):`nohup … > LOG 2>&1 &` 后 `tail --pid`。
8989
- 锁下墙钟绝对值是共享盒读数;只包命令本身,不包你的阅读与判断。
9090
- 结论读它印的 `VERDICT command-exit` 行,⛔ 不读裸 `$?`;按到达序授予,排队是常态,不是挂死。
9191
2. **压住堆**:重命令前缀 `NODE_OPTIONS=--max-old-space-size=4096`,要抬需给理由。
@@ -172,13 +172,11 @@ model: opus
172172
- `set -o pipefail` 与 `${PIPESTATUS[0]}` 仅当下游读到 EOF 才安全。
173173
- `| head -N` 读满即关读端,生产者吃 SIGPIPE 以 0 退出:是管道改了生产者的退出码。
174174
- 此陷阱不限于门禁:任何 `cmd | head` 之后读退出码都中招(`git grep`、`node` 皆然)。
175-
- 陷阱在 shell 用法里,不可机械化;⛔ 别等机械强制。
176175
- 两类跑了却没测到,都读作 NOT MEASURED,不读作绿也不读作红。
177176
- 证据已销毁(评论、卡或 PR 答 404)的复核项记 NOT MEASURED 并写因,⛔ 不记通过或「无旗」。
178177
- ① 包的 `typecheck` 可能 `exclude` 掉 `**/*.test.ts`;声称它覆盖你的测试前,用 `--listFiles` 数。
179178
- ② `MODULE_NOT_FOUND` 一类的 exit 1 不是红门禁:多半是脚本名或路径敲错,根本没进到门禁体。
180179
- 它与 `exit 99` / queue-timeout / `PREREQUISITE NOT MET` 同类:用真正的 `pnpm check:*` 命令重跑。
181-
- ⛔ 不把这类结果写进报告当作一次失败的测量。
182180

183181
## 标准条款住在这里,不住在你的派发词里
184182

@@ -249,7 +247,6 @@ model: opus
249247
- `packages/qa/dogfood` 是一例,不是定义;读成 dogfood 专属,普通单元套件里的 ablation 会被当真。
250248
- 两个方向不对称:没 build 的修复是会被注意到的假红;没 build 的 ablation 保持绿。
251249
- 那份绿给一条可能永远失败不了的断言背书,对 CI 永久隐形(CI 构建正确,那边永远绿)。
252-
- 消融本就为证明新门禁能失败时,这份假绿读作门禁没触发,于是有人去弱化一条好门禁。
253250
- 每一腿(变异与还原)都是:改动 → 证明它真落到了磁盘 → `pnpm --filter <pkg> build`。
254251
- 落盘走 `node scripts/ablation-replace.mjs`,不走 `-i` 家族:锚点必须命中,写入与还原按磁盘核验。
255252
- 再证明它到达了 `dist/`,才读运行结果:`node scripts/ablation-dist-preflight.mjs <pkg> '<marker>'`。
@@ -276,7 +273,10 @@ model: opus
276273
- 分区 pin `test/vitest-tiers-partition.test.ts` 在某个测试文件两层皆无或皆有时变红。
277274
- 用 `git push -u origin claude/issue-<n>-<slug>` 推上去,网络失败退避重试;pre-push 拒卡片 trailer。
278275
- Draft PR 指向 `main`,正文首行 `Fixes #<n>`;合并不应关卡时用 `Part of #<n>`,并说明留下哪一半。
279-
- PR 开出后合 main 只因冲突、main 致 CI 红、出队,后只跑 §10 复检;dogfood 只在终 head 跑一次。
276+
- PR 开出后合 main 只因冲突、main 致 CI 红、出队,后只跑 §10 复检。
277+
- 全量 dogfood 只在终 head 跑一次,仅当标题带 `!` 或 `Clause-②:` 带 `(narrowing)` 臂(yes/no 皆算)。
278+
- 命令同 CI:按 ① 建闭包,锁下 `pnpm --filter @objectstack/dogfood test`;⛔ 无 `OS_TEST_SHARD`、无路径。
279+
- 红 = 本改动的后果(陈旧 fixture)同 PR 修,余照报;⛔ 永不跳过或收窄;计数记 `dogfood_battery`。
280280
- 认领带 `Clause-②:` 行才照抄进正文行首:`Check Changeset` 读正文不读卡;无则零写,⛔ 不自造。
281281
- ⛔ 永不 `Fixes` 一张还在决策箱的卡:合并会静默关掉它,而收件箱过滤只读 open。
282282
- ⛔ 不写否定式的关单句,它照样关掉点名的卡:解析器无视否定,只匹配关键词 + `#<n>`。
@@ -370,6 +370,7 @@ model: opus
370370
"summary": "what was implemented, 2-4 sentences",
371371
"tests": "commands run + pass/fail evidence (real output excerpts); ablation only where a tested behaviour changed: rebuild + on-disk mutation proof",
372372
"gates": "local subset: each command + exit code captured before any pipe + --ran reconciliation; every family CI runs: the PR's check-runs (name + conclusion) cited, not re-run locally",
373+
"dogfood_battery": "n/a — no `!` in the PR title and no (narrowing) arm | <head sha> · the lock's VERDICT line · Test Files N/N · Tests M/M · every failure named",
373374
"mcp_calls": "<n> — MCP GitHub calls with tool names; a write tool in the list = this report is refused",
374375
"api_writes": "<n> — REST proxy writes, each endpoint listed",
375376
"open_questions": [
@@ -382,7 +383,6 @@ model: opus
382383
- `status: "rework"` = 你自知不完整的部分成果,在 `summary` 里说明为什么。
383384
- `premise_still_valid: false` = 核验证伪了前提(规则 6):证据进 `summary`,`pr` null 或只圈存活部分。
384385
- 报告模板是工具,不是真相:某字段的预设与实际发生的对不上时直说。
385-
- 例:反向验证方向反转、前提死了、某个产物在此无意义;按模板硬造比留白更糟。
386386

387387
## 字节与 sanitizer 纪律
388388

‎.claude/skills/pm-dispatch/references/review-checklist.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
- 切 `Part of` 的正文最易再武装自动关闭:否定句、记账句、引号包裹,解析器一律照关。
1010
- 安全拼法 = 卡号旁零动词,关闭安排写进评论。
1111
- 闭合关键词两读:翻 ready 前亲核首行、亲扫全正文,⛔ 关键词永不挨另一张 open 卡编号。
12-
- 这是合并前唯一的人工关口,⛔ 不只信报告;合并后那一读见 `landing-operations.md`。
1312
- `Part of` 收口的卡不会自动关,`pm:dispatched` 必须手工摘,动作要件见落地细则 B。
1413
- ACCEPT 那一刻记进落地待办,⛔ 不留给下次巡检。
1514

@@ -48,6 +47,7 @@
4847
- ⛔ 非当前 head 上的 `cancelled` 零动作、永不重跑:新推送自带全套 run。
4948
- 席位核对门禁证据 = 钉 head 的 check-runs(名+结论);本地并集只证本地子集,⛔ 不替缺席者。
5049
- dev 本地并集钉 head:所抄 `git rev-parse --short HEAD` 不符 PR `head.sha` 或没抄 ⇒ 双向都不入账。
50+
- `!` 或 `(narrowing)`:入队前核 `dogfood_battery` 钉 head、全量、红已修/已报;缺或收窄 ⇒ REWORK。
5151

5252
## 断言与收益
5353

0 commit comments

Comments
 (0)