You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit fc8973e
Browse filesBrowse the repository at this point in the historyBrowse files
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.
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.
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.
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.
"tests": "commands run + pass/fail evidence (real output excerpts); ablation only where a tested behaviour changed: rebuild + on-disk mutation proof",
372
372
"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",
373
374
"mcp_calls": "<n> — MCP GitHub calls with tool names; a write tool in the list = this report is refused",
374
375
"api_writes": "<n> — REST proxy writes, each endpoint listed",
0 commit comments