Skip to content

Commit cd984c9

Browse files
committed
Merge remote-tracking branch 'origin/main' into claude/project-thread-6od3f3
# Conflicts: # packages/verify/src/harness.org-context.test.ts
2 parents c91379f + e6dfeb0 commit cd984c9

198 files changed

Lines changed: 9201 additions & 1691 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: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
---
2+
'@objectstack/spec': minor
3+
---
4+
5+
feat(spec)!: an SLA escalation's `escalateTo` naming a `team:`, `department:`, `org_membership_level:` or `role:` address is refused at registration
6+
7+
Clause-②: no (narrowing) — `ApprovalEscalationSchema.escalateTo` refuses values it used to accept. Nothing is widened.
8+
9+
Metadata-protocol: narrowed (an approval node's `escalation.escalateTo` written as a `team:`, `department:`, `org_membership_level:` or `role:` address is refused)
10+
11+
<!-- adr-0087: registered approval-escalation-dead-target-refused -->
12+
13+
`escalateTo` was a bare string. The approvals service resolves it as a position machine name and falls back to the value as written, and a person acts on a pending slot only under their user id, their email or `position:<name>` for a position they hold. So a `team:`, `department:` or `org_membership_level:` literal, or the `role:` spelling ADR-0090 D3 retired, names nobody under any escalation action: a `reassign` on timeout would hand the request to no one (the runtime now keeps the slate and notifies it instead), and a `notify` reaches no one through it. The schema accepted it and every build door exited 0.
14+
15+
`ApprovalEscalationSchema` now refuses those four prefixes at `escalateTo`, under every action and with surrounding whitespace trimmed as the runtime trims it. The refusal names the value and the shapes that are accepted. It reaches every door that judges an approval node's config: `FlowSchema.parse` (`defineStack`, `defineFlow`, `objectstack validate` / `compile`, the metadata save door) and `AutomationEngine.registerFlow`. A user id, an email, a position machine name and `position:<name>` stay accepted; whether they resolve is directory state, judged by the runtime on timeout. The D3 entry `approval-escalation-dead-target-refused` carries the judgment for `os migrate meta`; there is no D2 conversion, because only the author knows which position or person was meant.
16+
17+
**BREAKING** for an author whose approval node escalates to a team, department, membership tier or `role:` address.
18+
19+
- **Before.** `escalation: { timeoutHours: 24, action: 'reassign', escalateTo: 'team:night_shift' }` parsed, and on timeout the request was handed to no one.
20+
- **Now.** The flow is refused at `nodes.N.config.escalation.escalateTo`, with a message naming the value and what to write instead.
21+
- **Fix.** Name a target someone acts under: `escalateTo: 'night_shift_lead'` (a position its people hold), `'position:night_shift_lead'`, or a person's user id or email. Write `role:<name>` as `position:<name>`.
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/plugin-security': minor
4+
'@objectstack/lint': minor
5+
---
6+
7+
feat(spec,plugin-security,lint): a `controlled_by_parent` detail may opt its rows' owners into updating them with read access to the master — `sharing.controlledByParent.ownerMayEdit: true`
8+
9+
Clause-②: yes (widening) — a detail row's owner now passes the master-detail write gate with READ on the master, on an object that declares the opt-in; everyone else, and every other shape of write, still needs master EDIT. The default is unchanged.
10+
Metadata-protocol: added (`ObjectSchema.sharing.controlledByParent.ownerMayEdit`, a per-object opt-in read only under `sharingModel: 'controlled_by_parent'`)
11+
Ruling-ref: 6398566164 (letter 1)
12+
13+
ADR-0055 §3 had one rule for every write of a `controlled_by_parent` detail: EDIT access to the master record. Salesforce's Activities under OWD = Controlled by Parent let the owner of an activity edit it while merely reading its account; the platform could not express that, and an authored `owner_id == current_user.id` update policy on the detail could not either — the detail's own policies compose AND with the master gate, so it only narrowed the master's editors.
14+
15+
- **Spec.** `ObjectSchema` gains `sharing: { controlledByParent: { ownerMayEdit: boolean } }`, both blocks closed. `ownerMayEdit: true` is refused at parse on any object that is not a `sharingModel: 'controlled_by_parent'` detail, at `sharing.controlledByParent.ownerMayEdit`. The reader `controlledByParentOwnerMayEdit(schema)` (`@objectstack/spec/data`) fails closed on every shape but the parsed `true`.
16+
- **Gate.** On an opted-in detail, a by-id `update` by the caller whose `userId` is the stored row's `owner_id` (the registry's injected per-record owner) passes step 2.8 when the caller can READ the master row, asked of the read door under the caller's own context. Insert, delete, a predicate (`multi: true`) update and a delegated (on-behalf-of) write keep the master-edit rule; a body that re-points the master reference still asks EDIT on the master it names; a row with no owner admits nobody. The detail's own object grant and row-level policies run before the leg, unchanged. An opt-in on an object whose schema carries no `owner_id` column (`ownership` other than `'user'`) is inert and reported once per object at `warn` by the relation resolver.
17+
- **Lint.** `os lint` reports `security-controlled-by-parent-owner-policy-without-opt-in` (warning) for an `update` or `all` row-level policy whose `using` or `check` keys a field on the caller (`owner_id == current_user.id`, either operand order, the SQL `=` spelling included) on a `controlled_by_parent` detail that has not declared the opt-in. `os explain security-controlled-by-parent-owner-policy-without-opt-in` prints why such a policy can only narrow, and what the opt-in changes.
18+
- **ADR-0055** §3 is amended (2026-10-26, #25760) with the opt-in, its boundaries, and what stays out of scope.
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
---
2+
'@objectstack/spec': minor
3+
'@objectstack/service-automation': minor
4+
'@object-ui/app-shell': minor
5+
---
6+
7+
feat(spec,automation,app-shell): a flow screen field typed `file` collects an upload, and the run resumes with the uploaded file's id
8+
9+
Clause-②: yes (narrowing)
10+
Metadata-protocol: semantic
11+
12+
<!-- adr-0087: not-required (no-migration-prescription) No metadata moves: no spec key, authorable spelling, object definition, stored row or stored shape is added, removed, renamed or re-shaped. `ScreenFieldConfigSchema.type` stays an open string, so every flow that parsed before still parses, and nothing exported is removed or narrowed. What changes is what a screen field already declared `type: 'file'` means: the console renders the upload widget for it, and the resume door refuses a value that is not a file id. The remedy is a change in what a caller posts to `resume` (the uploaded file's id), never a rewrite of a package source or a stored row, so there is nothing for `objectstack migrate meta` to rewrite. The other categories are closed on facts: the three packages publish (not unpublished); no ADR-0087 id is named or touched (not registered or already-registered); and no runtime interface, type or export is removed or narrowed (not runtime-interface-only, type-surface-only or runtime-export-retired). -->
13+
14+
A wizard can now ask for a file before the record that will own it exists, and write everything after its last screen.
15+
16+
- **`@objectstack/spec`**: `ScreenFieldConfigSchema.type` (and the `ScreenFieldSpec` contract) documents `'file'` as the second value domain beside `'number'`. A `type: 'file'` field renders the platform upload widget. The upload goes through the file service and lands as a `sys_file` that no record owns yet. The resume carries that file's id, which is ADR-0104 D3's stored form, and nothing else. A later `create_record` or `update_record` node writes the id into an object's `file` field, and that write claims the file for the record. Under the default `runAs: 'user'`, the claim first makes the download door's read check as the run's user; a `runAs: 'system'` run's write is not checked, as no system write is.
17+
- **`@objectstack/service-automation`**: the resume re-checks a `type: 'file'` field as a value domain. A value that is not a file id, judged by the spec's `isFileIdToken` (the same predicate the claim path reads), is refused with `400 INVALID_SCREEN_INPUT` and the field code `invalid_type` ("must be the id of an uploaded file"). That covers a URL, the expanded `{ id, name, url, … }` read form, a list and a number. The value is never reduced to an id. The refusal writes nothing and keeps the pause, so a corrected resume still lands. An empty optional field and a hidden field are not judged. `image`, `avatar`, `video` and `audio` stay open widget hints.
18+
- **`@object-ui/app-shell`**: the flow screen dialog renders the object form's `FileField` for a `type: 'file'` field and uploads through the console's `UploadProvider`. While an upload is in flight, Submit is disabled and reads "Uploading…". The resume posts each file field as its bare id, and a prefilled expanded value is reduced to its id first.
19+
20+
**BREAKING** (a resume-acceptance narrowing) for a flow whose `screen` node already declares a field `type: 'file'`. None was found in this repository's examples or packages.
21+
22+
- **Before.** `type: 'file'` was an open widget hint the console did not know, so the field rendered a plain text input, and `POST /automation/:name/runs/:runId/resume` accepted any value for it.
23+
- **Now.** The field renders the upload widget, and the resume refuses any value that is not a file id with `400 INVALID_SCREEN_INPUT` (`invalid_type`). The refusal keeps the pause.
24+
- **Fix.** A caller that resumes the run itself, without the console, uploads the file through the file service first and posts the minted `sys_file` id for the field: FROM `{ "contract_file": "https://…/nda.pdf" }` TO `{ "contract_file": "FILE_ID" }`. A field that was never meant to collect an upload takes another `type`, such as `text`.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
'@object-ui/fields': patch
3+
---
4+
5+
fix(fields): a lookup's option subtitle no longer repeats the record name when the referenced object's name field is not `name`
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
The quick-picker shows each candidate as its title over a subtitle of the referenced object's other columns. A lookup that declares no `displayField` titles the option through the referenced object's name field (`nameField`, else the derived title field), but the subtitle skipped only a field literally called `name`. On an object whose name field is `title`, `customer_name` or any other name (every AI-built object), the subtitle's first and widest slot repeated the title and pushed the column that tells two records apart, such as a phone number, into truncation.
11+
12+
The subtitle now also skips the referenced object's resolved name field when the lookup declares no `displayField`. Unchanged: a declared `displayField` is the field skipped, so the object's name field stays in the subtitle; an object titled by a `titleFormat` template keeps every field in the subtitle; an explicitly authored `descriptionField` still leads the subtitle.
13+
14+
What to do: nothing.
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+
---
4+
5+
feat(ui): the Studio permission-set form offers the field permission `when` (row condition on the edit vote)
6+
7+
Clause-②: yes (widening)
8+
Metadata-protocol: none
9+
10+
Part of #25866 — the Studio clause of ruling 6408558941 (part ①, item 4), after #25980 landed the key itself.
11+
12+
- **Before.** The permission matrix's field rows carried two checkboxes, `readable` and `editable`: no way to offer or see a `when`. The Read-off cascade could leave `readable: false` beside an authored `when` (the shape the spec refuses at publish), and the Read all / Write all shortcuts dropped the condition.
13+
- **Now.** Each field row carries **When (edit)**: an authored condition shows as a chip, and the row opens the same CEL editor the row-level-security clauses use, linted the same way; a finding the publish gate would refuse holds the save. Read off clears the condition, Read all / Write all keep it, Clear drops the entry, blank text removes the key. `FieldPerm` gains `when`, carried verbatim by the package-door slice merge.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
'@object-ui/i18n': patch
3+
---
4+
5+
fix(i18n): the Chinese account menu's sign-out item reads 「退出登录」, not 「注销」
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
In the `zh` pack, `user.logout` read 「注销」, which a Chinese reader takes as "delete my account", on the menu every signed-in user opens. It now reads 「退出登录」, the wording the same pack already uses for signing out elsewhere. No key is added or removed; other locales are unchanged.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
'@object-ui/plugin-detail': patch
3+
---
4+
5+
fix(plugin-detail): the record page's read mode wraps a text value instead of cutting it to one line, and a highlighted date keeps its day
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
- **Details body.** A `text` field in a `record:details` section, read mode, was drawn with the grid's one-line cell (one line, an ellipsis), so a case subject read "Data export timing out for large…" in a two-column section with room to spare. It now reads through the text field's own read display and wraps, as a `textarea` already did. The same holds for every type that used the grid's text cell (`code`, `time`, `auto_number`, …). A host that registered its own cell renderer for one of those types keeps it. Grid cells are unchanged and still truncate, with the whole value on hover.
11+
- **Highlights strip.** A `date` or `datetime` chip now takes the wide column (16rem floor) that email, phone and URL chips take. On the 9rem column the compact datetime face, and a date in Chinese or Japanese, overran the value box, so the ellipsis landed inside the day ("2026/10/2…"). A chip whose line runs out still truncates, with the whole value as its hover title.
12+
13+
What to do: nothing.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
'@object-ui/app-shell': patch
3+
---
4+
5+
fix(app-shell): the "nothing in this app is available to you" screen is drawn by the shared `DataEmptyState`
6+
7+
Clause-②: no (one block swaps its own markup for the shared components; no exported type change. If a shared component needs a new prop, the thread reports first and the seat amends this line)
8+
Metadata-protocol: none
9+
10+
The screen an app's root shows when the app serves the caller no navigation, or names only objects the caller may not read (`AppNoAccessEmptyState`), now uses `DataEmptyState` from `@object-ui/components` instead of the `Empty` primitive with a hand-copied icon square. It is announced to assistive technology as `role="status"`. The text, the Open Setup link, the test ids and the `data-reason` attribute (now on the status node) are unchanged; the title takes the shared component's smaller size.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
'@object-ui/plugin-grid': patch
3+
---
4+
5+
fix(plugin-grid): a bulk action's `confirmText` fills its `{{ count }}` hole with the number of records the run covers
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
A bulk action's `confirmText` is a `template` slot, and its declared scope names `count`, the number of records the run will cover. The bulk dialog printed the sentence as written, so a person confirming `Archive {{ count }} account(s)?` read the braces instead of the number. The confirm step now fills `{{ count }}` through the same template engine the flow notifications and the single-record `confirmText` render with, so the whitelisted formatters apply (`{{ count | number }}`) and a hole holds no logic. A sentence without a `{{ }}` hole is shown as written, the default sentence is unchanged, and a source the engine refuses is shown as written.
11+
12+
A hole naming one of the action's params prints empty for now; filling the params with their display values is the next change.
13+
14+
What to do: nothing.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
'@object-ui/plugin-form': patch
3+
---
4+
5+
fix(plugin-form): a `{ group }` form section no longer warns that it "also declares `collapsible`, `collapsed`" when its author wrote neither
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
`@objectstack/spec` gives a form section's `collapsible` and `collapsed` a default of `false`, so every parsed section carries both, and it refuses them beside `group` only when they are `true`. The console's form renderer reported any value at all, so every `{ group }` section of a parsed form logged the group-owned-key warning once per page and told the author to move keys they never wrote to the object's `fieldGroups`. The renderer now reads those two keys the way the spec does: `true` beside `group` is still reported (a programmatic caller that skips the spec's parse), while `false` or absent is not. `name`, `label`, `description` and `visibleWhen` are still reported on any value.
11+
12+
What to do: nothing. If you moved `collapsible` / `collapsed` off a `{ group }` section because of this warning, no metadata change was needed.

0 commit comments

Comments
 (0)