Skip to content

Commit 173cc44

Browse files
committed
Merge origin/main into claude/project-thread-3koeqm
# Conflicts: # packages/plugins/plugin-auth/src/allowed-ip-ranges.pin.test.ts
2 parents c1c43e2 + 09720e2 commit 173cc44

148 files changed

Lines changed: 5883 additions & 1341 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: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
'@object-ui/app-shell': patch
3+
'@object-ui/i18n': patch
4+
---
5+
6+
feat(studio): a contributed nav entry shows the label the live app shows, and a package that only contributes navigation says where it goes
7+
8+
Clause-②: yes (widening)
9+
Metadata-protocol: none
10+
11+
- **The label.** A navigation entry another package contributes to a base's app now shows the label the live app shows, with its id as a muted second line under it. Before, the row showed only the id. The label is looked up by `(app, id)` in the app row the Interfaces pillar already reads (`GET /meta/app?package=`), which serves the folded navigation: the served entries the app's published layer does not hold. No request is added and nothing is read from the contributor's manifest. An id the base also holds, or one two contributions share, keeps the bare id, so no label is guessed. A label-less entry reads what it inherits, through the rail's own resolver. A never-published app has no served row, so its contributed rows keep the id.
12+
- **The contributor's empty state.** A package with no app of its own whose listed manifest declares `navigationContributions` now says what it adds: "This package has no app of its own. It adds 2 navigation entries to HotCRM's app.", with a button that opens that package. The declared app is an address only. Its owner comes from the served app read (`GET /meta/app/:app`), and the count comes from the owner's served customization read (`GET /packages/:owner/customizations`), counting the `nav` contributions stamped with this package for that app. That is at most two reads per declared target app, sent only on that empty state. When the package is disabled, the target app is missing, either read fails, or the count is 0, the text stays "This package has no app yet."
13+
- **New strings.** `@object-ui/i18n`'s studio namespace gains five keys in `en` and `zh` (`engine.studio.if.noOwnApp`, `.noOwnAppTitle`, `.contributesOne`, `.contributesOther`, `.openOwner`).
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
'@object-ui/app-shell': patch
3+
'@object-ui/i18n': patch
4+
---
5+
6+
fix(app-shell): the Studio publish sheet names a batch of hooks or translations in words ("3 hooks", zh 「钩子 3」) instead of the raw type code ("3 hook") (#29902)
7+
8+
Clause-②: yes (widening)
9+
Metadata-protocol: none
10+
11+
- **`@object-ui/i18n`**: two new count families in every pack, `preview.changes.typeCount.hook` and `preview.changes.typeCount.translation`, with each CLDR slot the pack's language selects.
12+
- **`@object-ui/app-shell`**: the Pending changes / publish sheet's summary line and group headings read those families for `hook` (authored in an object's Hooks tab) and `translation` (authored in Studio's "All metadata" form editor). A type with no family still shows its raw name.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
'@objectstack/spec': minor
3+
'@objectstack/plugin-auth': patch
4+
---
5+
6+
`GET /api/v1/auth/config` now says whether email leaves the deployment, in a new optional `features.emailDelivery` flag. It is `true` only when the wired email service reports itself configured, and `false` with no email service or with the stock `log` transport, which writes mail to the server console instead of sending it. The forgot-password page can read it to stop saying a reset link was sent on a deployment that sends no mail.
7+
8+
The flag describes the deployment, not an address: `POST /api/v1/auth/request-password-reset` still answers a known and an unknown address the same way, with or without mail.
9+
10+
Like every public auth flag, it is classified in `PUBLIC_AUTH_FEATURES` (login surface, opt-in), so an action may now also name it: `requiresFeature: 'emailDelivery'` is a new accepted value, lowered to `features.emailDelivery == true`.
11+
12+
Clause-②: yes (widening)
13+
Metadata-protocol: added
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
'@object-ui/core': patch
3+
---
4+
5+
fix(core): the client date-macro resolver answers `{next_week_end}` and `{next_year_end}`
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
`resolveDateMacros` now resolves `{next_week_end}` (Sunday of next week, Monday-start weeks) and `{next_year_end}` (Dec 31 of next year), the two fixed tokens `@objectstack/spec`'s `DATE_MACRO_TOKENS` declared that the Console passed through as raw text. A test now reads `DATE_MACRO_TOKENS` from the spec and fails on any token the client leaves unresolved, so the two lists cannot drift again.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
'@objectstack/spec': minor
3+
'@objectstack/platform-objects': minor
4+
---
5+
6+
feat(spec): an action param can name the widget that draws it (`widget`, same key as on a field), so a param can ask for the permission-set picker (ADR-0131 D4)
7+
8+
Clause-②: yes (widening)
9+
Metadata-protocol: added
10+
11+
- **`@objectstack/spec`: `ActionParamSchema.widget`**, an optional string with the same key, vocabulary and meaning as `FieldSchema.widget`. It names a registered field component, looked up as `field:` plus the name, to draw the param in the action dialog instead of the `type` default. `type` stays the value's type. The case it exists for is `widget: 'permission-ref'` on a `text` param: it lists the permission sets of the security catalog and submits the chosen machine name, the picker the grant column `sys_user_permission_set.permission_set` already uses. Like the field key it is an open string: an unregistered name is kept, and the dialog draws the `type` renderer instead. A non-string is refused. A param that does not declare the key parses exactly as before. Until now a param naming `widget` was refused as an unknown key.
12+
- **Not read yet.** The console's param resolver does not carry the key to the dialog's widget until objectstack#30260 lands. Until then a param that declares it is drawn by its `type` renderer, and its value is submitted as before. The key's description says so, and the liveness ledger grades it `planned` with an author warning: `os validate` and `os lint` warn an author who writes it.
13+
- **`@objectstack/platform-objects`: the action form's Params repeater offers the key** as a `Widget` column, labelled in en, zh-CN, ja-JP and es-ES.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
---
2+
'@objectstack/lint': minor
3+
---
4+
5+
fix(lint)!: a time-relative flow whose start node names, in `config.objectName`, another object than the one it sweeps (`config.timeRelative.object`) is refused with `flow-time-relative-object-mismatch`: each run binds a row of the swept object, while the start's `objectName` is what its conditions are checked against (#30283)
6+
7+
Clause-②: yes (narrowing) — a new published readiness rule id, `flow-time-relative-object-mismatch`, graded `error`: the author-time gate (`os build`, `os validate`, the package publish and the flow publish door) refuses a time-relative start whose `config.objectName` is set and is not `config.timeRelative.object`, which it passed with no readiness verdict before.
8+
Metadata-protocol: narrowed
9+
Skills-docs: none — `skills/**` and `content/docs/**` teach the time-relative sweep with `timeRelative.object` alone and no `objectName` on its start node, which this rule accepts; the MCP skill text keeps its `sweepObject` advice for `validate_expression`, whose expression verdict this change does not move.
10+
11+
<!-- adr-0087: not-required (no-migration-prescription) No key of any schema is added, removed, renamed or re-shaped: the flow schema still admits objectName beside a timeRelative descriptor, and what narrows is the publish door's accept set for one combination of two existing keys. No rewrite keeps what the author meant: the start names two objects, and whether the author meant to sweep the swept object (drop objectName) or the named one (change timeRelative.object) is a judgement no ledger entry can derive. The other categories are closed on facts: the bumped package publishes (not unpublished); no ADR-0087 id covers this combination (flow-record-read-and-decision-type-mismatch-refused prescribes a rewrite of a condition, not of the start node) and this diff adds none (not registered, not already-registered); no published export is removed or retyped (not runtime-export-retired, runtime-interface-only or type-surface-only). -->
12+
13+
**BREAKING** accept-set narrowing at the author-time flow readiness gate, served by `os build`, `os validate`, `POST /api/v1/packages/:id/publish-drafts` and the flow publish door.
14+
15+
- **Before.** A time-relative start could name `config.objectName: 'A'` and sweep `config.timeRelative.object: 'B'`. The run binds only `B`: the engine's binding resolver reads `timeRelative.object` first, and the trigger launches each run with a `B` row as `record` and `B` as the run's object. The expression checks typed the flow's conditions against `A` all the same. Measured both ways: `record.amount > 10`, where only `A` declares `amount`, passed every check and failed every run (`No such key: amount`); `record.priority > 1`, a correct read of a `B` field, was refused as `unknown field priority on A`.
16+
- **Now.** That start is refused at `config.objectName`, naming both objects and the fix. A start whose `objectName` equals `timeRelative.object`, or that names none, is not reported; neither is a start whose `triggerType` routes the flow to the record-change trigger (a `record-` token, or an array holding one), which binds `objectName` and never sweeps. The runtime publish gate judges only the flow being written, so a stored flow that names two objects keeps being served; its own next publish is refused.
17+
- **The `config.objectName` warning on a sweep of another object** (served where a condition's type verdict was softened to a warning) now names `flow-time-relative-object-mismatch` as the verdict that asks for the same fix.
18+
19+
**What an author does.** Nothing for a time-relative start that names no `objectName`, or names the object it sweeps. A start that names two objects says which one the author meant, and nothing mechanical can: for the swept one, remove `config.objectName` (the sweep never reads it, so the run is unchanged) or set it to the value of `config.timeRelative.object`, then correct any condition the expression checks now refuse against that object; for the named one, point `config.timeRelative.object` (and its `dateField`) at it instead. The refusal names both objects.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
---
2+
'@object-ui/app-shell': patch
3+
'@object-ui/auth': patch
4+
'@object-ui/i18n': patch
5+
---
6+
7+
fix(app-shell,auth,i18n): the organization form says requiring MFA needs two-factor on, an accepted save says who must enroll and when, and the enrollment screen names a two-factor-off deployment in the user's language
8+
9+
Clause-②: yes (widening)
10+
Metadata-protocol: none
11+
12+
- **Organization form, before saving.** While two-factor is off on the deployment (`features.twoFactor: false` in `/api/v1/auth/config`), the `sys_organization` edit form says, under its title, that Require Multi-Factor Auth needs two-factor, that a save with it ticked is refused, and how to turn two-factor on (Require multi-factor authentication in Setup → Authentication, or `OS_AUTH_TWO_FACTOR=true`). When the save is refused, the server's refusal shows on the field, as before.
13+
- **Organization form, after an accepted save.** A save that turns `require_mfa` on for a row that stored it off adds a line to the saved toast: every member without an authenticator app must set one up, and is blocked from data once the MFA grace period ends (7 days unless changed in Setup → Authentication), counted from their first use after the change. A save of a row that already required it says nothing new.
14+
- **Enrollment screen.** `enrollTotp` and `verifyTotp` of `@object-ui/auth` now reject with the HTTP `status` and the server's `code` on the error (the message is unchanged). The authenticator step reads a `404` (the `/two-factor/*` endpoints are not mounted while two-factor is off) as a translated sentence saying enrollment is not available and who can turn it on, instead of `Two-factor enable failed (404)`, and `INVALID_PASSWORD` as the password screen's sentence. Any other refusal keeps the server's message.
15+
- **The widening.** `@object-ui/i18n` gains `auth.remediation.mfa.unavailable`, `console.organizationMfa.needsTwoFactor` and `console.organizationMfa.nowRequired` in all ten language packs. The two enrollment rejections gain the optional `status` and `code` properties.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
'@objectstack/lint': patch
3+
---
4+
5+
fix(lint): a permission set's field key on a field that an `objectExtensions` entry adds, a sibling package's in the same artifact included, is no longer refused as `security-fls-unknown-field` (#30303)
6+
7+
Clause-②: yes (widening: the FLS-key check accepts extension-added fields)
8+
Metadata-protocol: none
9+
10+
- **Before.** The FLS-key check resolved `object.field` against the object's declared `fields` only. On a multi-package artifact, an app package's permission set naming `crm_contract.o2c_probe_note`, a field its module package adds to `crm_contract` through `objectExtensions`, failed `os build`, `os validate` and `os lint` with "crm_contract declares no field o2c_probe_note", although the runtime serves the field and the key masks it. A single-package stack's own extension fields were refused the same way.
11+
- **Now.** The key resolves against the object as the registry serves it: its declared fields plus every field an `objectExtensions` entry of any body of the artifact adds to it, folded as `@objectstack/objectql` folds them (fields by name; one entry per package per object, a later entry replacing an earlier one; `extend` naming the object verbatim). A field that no body declares or adds keeps the same refusal, with the same rule id, severity and message, and so does a field an entry only patches through `fieldPatches`, which adds no field. The hint's nearest name reads the served fields too.
12+
- **One fold.** The controlled-by-parent merge (#23210), the membership-key column check and the FLS-key check now read extensions through one shared fold in `object-graph.ts`.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
'@object-ui/app-shell': patch
3+
---
4+
5+
fix(app-shell): the Console sidebar, recent list and object list header draw a served object or list view label as served
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
- **Now.** An unlabelled sidebar entry that opens an object or a list view, a recent object or list view row (in the sidebar and on Home), the object list page's title, the app header's title on an object route and an object's `/data` page title draw the label the `/meta` read or the app bundle served as given. The server has already translated it, by its label order for an overridden item: a `.custom` object extension's or view override's own text is kept over the base package's translation. Before, the served label reached the client's catalog lookup only as its fallback, so the base package's translation of the old name won. A list is titled by the served `pluralLabel`, else the served `label`.
11+
- **Unchanged.** A list view the client derived (no served read names it), an object the cache does not hold yet, and a pending draft's label are still named by the catalog key, then by the machine name as before. An unedited item shows the same text as before, because the server serves the catalog's translation for it. Record pages, record forms and drawers still look the object's name up in the catalog; they are a follow-up.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
---
2+
'@objectstack/plugin-auth': patch
3+
---
4+
5+
fix(plugin-auth): an IPv6 CIDR in the allowed IP ranges admits the IPv6 clients inside it, at the sign-in gate and at the settings save check
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
`auth.allowed_ip_ranges` (ADR-0069 D5) promises CIDR ranges, but its matcher read only IPv4 CIDR and exact addresses: an IPv6 entry such as `2001:db8::/32` was compared as text, so it never matched a real client. The sign-in gate then refused every IPv6 client the list named, and the settings save check refused the save for an administrator connected over IPv6 from inside it. `ipMatchesRange` now reads an IPv6 CIDR as the first `n` of 128 bits, and both doors use it, so they keep judging the same address the same way.
11+
12+
- An exact IPv6 entry matches the address it denotes however it is spelled (`2001:DB8:0:0::1` matches `2001:db8::1`).
13+
- An IPv4-mapped address, dotted (`::ffff:203.0.113.5`) or hex (`::ffff:cb00:7105`), is still judged as the IPv4 address it maps. A zone identifier on an address (`fe80::1%eth0`) is dropped before matching.
14+
- Families never cross: an IPv4 range never admits a pure IPv6 address, and an IPv6 range never admits an IPv4 one.
15+
- An IPv6 prefix must be a whole number from 0 to 128. An entry with any other prefix matches only its base address, as it did before. IPv4 entries are read exactly as before.

0 commit comments

Comments
 (0)