Skip to content

Commit 11c1e9f

Browse files
committed
Merge remote-tracking branch 'origin/main' into claude/project-thread-otcr21
2 parents 10f00a1 + a52cfaf commit 11c1e9f

55 files changed

Lines changed: 2461 additions & 1101 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: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
---
2+
'@object-ui/plugin-chatbot': patch
3+
'@object-ui/app-shell': patch
4+
---
5+
6+
fix(plugin-chatbot,app-shell): a whole-app build approved from the proposed plan draws its result card, live and after a reload
7+
8+
Clause-②: no
9+
Metadata-protocol: none
10+
11+
Approving a proposed plan sends the approval sentence, and the runtime replays the approved `apply_blueprint` under a `replay_` tool-call id. The chat read every replay result as the verdict of a confirm card and dropped its draft review, and a replay streams no build-progress frames, so the build's own envelope (`drafted[]`, `packageId`, `checklist`, `nextSteps`) reached the chat and was drawn nowhere: no result card, no Open app, no checklist, no next steps.
12+
13+
- **`@object-ui/plugin-chatbot`:** a replayed whole-app build that drafted keeps its draft review and draws the same result card as a model-driven build. A replayed build in an auto-publish environment (`status: 'published'`) draws the card and its next steps from that envelope; so does a published build after a reload. A replay whose publish rolled back, and a replayed incremental edit, are unchanged.
14+
- **`@object-ui/app-shell`:** the AI chat page's hydration keeps the same draft review, so a reloaded thread binds to the package the replay built, and the conversation cache keeps that build's draft envelope instead of the bare replay verdict.
15+
16+
What to do: nothing.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
---
2+
'@object-ui/app-shell': patch
3+
'@object-ui/i18n': patch
4+
---
5+
6+
fix(app-shell): pending changes folds what the platform wrote into one collapsed line
7+
8+
Clause-②: no
9+
Metadata-protocol: none
10+
11+
Publishing a new object from Studio listed the platform's injected system columns (`created_at`, `created_by`, `owner_id`, `updated_at`, `updated_by`) as fields the author added, and the object's derived `nameField` under "Also changed", beside the one field the author typed.
12+
13+
- A field the publish adds or removes whose every definition carries `system: true` (the spec's mark for an auto-injected system/audit column), and a `nameField` equal to the registry's own `provisionPrimary` derivation, now fold into one collapsed "Generated by the platform (N)" line; opening it shows those rows. A system field whose definition differs between the published and draft bodies is an edit and stays an author row, as does a `nameField` the derivation would not pick.
14+
- A new item's top-level keys are headed "Includes:" rather than "Also changed:": every key of an item that does not exist yet is part of it.
15+
- The columns the platform injects and hides (`__search`, `organization_id`, `owning_business_unit_id`) are still not listed at all.
16+
17+
New strings `preview.changes.detailNewKeys` and `preview.changes.generatedGroup` ship in all ten locale packs.
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
---
2+
'@objectstack/service-analytics': patch
3+
---
4+
5+
fix(service-analytics)!: a filtered dataset measure asked on the cube door as `CUBE.MEASURE`, the name `GET /analytics/meta` lists, applies its filter, as the bare name always did
6+
7+
A registered dataset's measure can carry its own `filter`, for example `won_amount` summing `amount` only where `stage` is `won`. `GET /analytics/meta` lists every cube member as `CUBE.MEASURE` (`sales.won_amount`), and `POST /analytics/query` accepts that spelling. The measure itself resolved under both spellings, but its filter was looked up by the spelling as asked, so the listed one missed it. `measures: ['sales.won_amount']` ran `SUM(amount)`: the total of every deal, under the won-deals measure's name, with no error. That happened on both strategies (native SQL and the ObjectQL aggregate) and in the dry-run statement echo.
8+
9+
Every reader of a measure's scoped filter now resolves the name through one rule, the one the duration measure's lookup already used: the bare name, or the name without its own cube's qualifier. The listed spelling answers the same conditional aggregate and binds the same values as the bare one, under the column name asked. The native strategy's routing declines (a nested-relation condition, a JSON-stored comparison on an unnamed dialect) read the same filter, so they route the qualified spelling as they route the bare one. Another dotted prefix keys nothing, as before. Queries through `POST /analytics/dataset/query` were never affected: that path builds bare names.
10+
11+
Clause-②: no (narrowing) — the field-level read gate now judges the fields of the measure's filter under the qualified spelling too. A caller who may not read a field that a filtered measure's filter names was refused `403 PERMISSION_DENIED` for the bare spelling and answered (with the unfiltered number) for the qualified one. It is now refused for both. Nothing is widened.
12+
13+
Metadata-protocol: none (how the analytics query door resolves a requested member name; nothing a metadata author writes changes)
14+
15+
<!-- 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, and no exported declaration is removed or narrowed, so there is nothing for `objectstack migrate meta` to rewrite. What changes is how the analytics query door reads a filtered measure asked under its listed `CUBE.MEASURE` spelling: its filter is applied, and the read gate judges the filter's fields. The remedy for a caller now refused is a permission grant or a different measure, never a rewrite of a package source or a stored row. The other categories are closed on facts: the package publishes (not unpublished); no ADR-0087 id is named or touched (not registered or already-registered); and nothing exported is removed or narrowed (not runtime-interface-only, type-surface-only or runtime-export-retired). -->
16+
17+
**BREAKING** for a caller that asked a filtered measure under its `CUBE.MEASURE` spelling: the number changes, and a caller who may not read the filter's fields is now refused. Shipped as `patch` because the metadata protocol does not move.
18+
19+
- **Before.** `measures: ['sales.won_amount']` aggregated every row, ignoring the measure's filter, and the read gate did not judge the filter's fields.
20+
- **Now.** It answers what `measures: ['won_amount']` answers: the filtered aggregate, or `403 PERMISSION_DENIED` naming the unreadable field.
21+
- **Fix.** Nothing to change for a caller that wanted the measure as declared. A caller that relied on the unfiltered total asks for an unfiltered measure instead.
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
---
2+
'@objectstack/runtime': patch
3+
---
4+
5+
fix(runtime)!: the action door and the flow trigger door refuse a body or a `params` that is not a JSON object, instead of running with an empty bag
6+
7+
`POST /api/v1/actions/:object/:action` read its body, and then `params`, only when each was a truthy object. A string such as `{"params":"oops"}` became an empty params bag and the action ran anyway, answering `200` and starting a run the same as `{"params":{}}`; an array `params` was handed on as the bag itself; and a JSON string or array body ran the action with an empty bag too.
8+
9+
`POST /api/v1/automation/:name/trigger`, its legacy spelling `POST /api/v1/automation/trigger/:name`, and every declared `type: 'flow'` API endpoint read the request body only when it was a truthy object. A JSON string body dispatched the flow with an empty bag, so it failed later with a misleading `400 FLOW_FAILED` "Unknown variable" or ran doing the wrong thing, and an array body forwarded its indices as params.
10+
11+
Both now answer `400 VALIDATION_FAILED` with one `fields[]` entry, `{ field: '(body)', code: 'invalid_type' }` for a body and `{ field: 'params', code: 'invalid_type' }` for a `params`, whose message names the type received. The refusal comes before anything is dispatched, so no run starts and no run row is written. It is the refusal the resume door already gives a non-object body, and the three doors now ask the question through one check, so their answers have the same shape; the resume door's `fields[]` message now also names the type received.
12+
13+
Unchanged: an object `params` (an empty one included) reaches the action as before, and an action call with no body, a JSON `null` body or no `params` key still runs with an empty bag. A trigger with no body, or a JSON `null` body, still runs with an empty bag. An unknown action or flow still answers `404`, and a caller the action or flow refuses still answers `403`, before the body is judged.
14+
15+
Clause-②: no (narrowing) — the action and trigger doors refuse a body that is a string, number, boolean or array, and the action door refuses a `params` that is a string, number, boolean, array or `null`. Nothing is widened.
16+
17+
Metadata-protocol: none (an HTTP request body's handling at the action and trigger doors; nothing a metadata author writes changes)
18+
19+
<!-- 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, and no exported declaration is removed or narrowed, so there is nothing for `objectstack migrate meta` to rewrite. What changes is how the action and flow trigger doors handle a request whose body or `params` is not a JSON object: it is refused instead of dropped. The remedy is a request edit (send `params` or the body as an object, or leave it out), never a rewrite of a package source or a stored row. The other categories are closed on facts: the package publishes (not unpublished); no ADR-0087 id is named or touched (not registered or already-registered); and nothing exported is removed or narrowed (not runtime-interface-only, type-surface-only or runtime-export-retired). -->
20+
21+
**BREAKING** for a caller that sent the action door's body or `params`, or the trigger door's body, as something other than an object and relied on the call running anyway, shipped as `patch` because the metadata protocol does not move.
22+
23+
- **Before.** A non-object body or `params` on the action door, or a non-object trigger body, was dropped (an array trigger body was forwarded as index-keyed params), and the action or flow was dispatched.
24+
- **Now.** The request is refused with `400 VALIDATION_FAILED` naming `params` or `(body)`, and nothing runs.
25+
- **Fix.** Send `params` as an object of input names to values, for example `{"params":{"note":"x"}}`, send the action body as an object (`{"recordId":"…","params":{…}}`), and send a trigger body as an object (`{"params":{"ownerId":"u1"}}` or a flat `{"ownerId":"u1"}`), or leave any of them out.
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 flow inspector's list field shows a stored string as one entry instead of an empty list
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
A notify node's `channels` may be a single string (`channels: 'email'`) or a list; the spec accepts both and the runtime reads the string as a one-item list. Studio's flow inspector read any non-list value as empty, so the field showed no channels and adding one saved a list without `email`. A stored non-blank string now reads as a one-entry list (trimmed, as the runtime does). An edit saves the list form (`['email', 'sms']`); a field left untouched keeps the stored string.

‎content/docs/permissions/system-context.mdx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ that silently does not happen.
136136
| 25 | Engine-owned / append-only write guard bypassed | plugin-security | Get: generic writes to `managedBy` engine-owned objects | `packages/plugins/plugin-security/src/system-write-guard.ts#isUserContextWrite`, `#assertEngineOwnedWriteAllowed` |
137137
| 26 | Identity write guard bypassed (ADR-0092) | plugin-auth | Get: direct writes to identity tables through the generic data path | `packages/plugins/plugin-auth/src/identity-write-guard.ts#isUserContextWrite` |
138138
| 26b | API key restore guard does not refuse | plugin-auth | Get: a write setting `revoked: false` on a `sys_api_key` row an administrator's forced password reset revoked is admitted without the platform-administrator check a user-context restore must pass; the reason is still cleared. Lose: nothing a caller reaches: no system writer restores a key, and the mint, the last-used stamp and the forced reset itself never set `revoked: false` | `packages/plugins/plugin-auth/src/api-key-restore-guard.ts#isUserContextWrite` |
139-
| 27 | Search-companion column **kept** in a read's rows when it was explicitly requested | objectql | Get: the internal companion column is readable. Lose: nothing for app code — this is the engine reading its own index | `packages/objectql/src/engine.ts#stripSearchCompanionFromRead` |
139+
| 27 | Search-companion column **kept** in a read's rows when it was explicitly requested | objectql | Get: the internal companion column is readable. Lose: nothing for app code — this is the engine reading its own index | `packages/objectql/src/engine-secret-internal-fields.ts#stripSearchCompanionFromRead` |
140140
| 28 | Dependent-count disclosure on a blocked delete | objectql | Get: the count of blocking children. Nothing was elevated past the caller, so nothing is withheld | `packages/objectql/src/engine.ts#dependentCountIsDisclosable` |
141141
| 29 | Reference-cleanup log attributes the write to `'system'` | objectql | Get: an honest actor label instead of `anonymous` when the context carries neither `userId` nor `actor` | `packages/objectql/src/engine.ts#recordReferenceCheckElevation` |
142142
| 29b | A traversing validation rule's related read skips the **caller's bounds** | objectql | Get: no row bound from the caller's own read on a related object no organization wall scopes, no read door on a header the rule reads through a `master_detail` field, and a related read even with no organization under a walled posture (a `tenantId` on the context still scopes it). Every caller that is not system is held to all three: a header its read door does not return is unavailable to the rule exactly as a header that exists nowhere is, whatever it holds, and a header it can read is judged on its stored values. A plain `lookup` to an object an organization wall scopes asks no read door, system caller or not | `packages/objectql/src/engine.ts#resolveOneHopRelated` |

‎docs/qa/platform-checklist/areas/records-forms.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4114,7 +4114,7 @@
41144114
},
41154115
"source": [
41164116
"packages/objectql/src/secret-fields.ts#SECRET_REF_PREFIX (SECRET_MASK, SECRET_REF_PREFIX, collectSecretFields/collectMaskedReadFields/collectCredentialFields)",
4117-
"packages/objectql/src/engine.ts#encryptSecretFields (encryptSecretFields — encrypt→sys_secret→ref, echoed-mask drop, fail-closed throw; decrypt path)",
4117+
"packages/objectql/src/engine-secret-internal-fields.ts#encryptSecretFields (encryptSecretFields — encrypt→sys_secret→ref, echoed-mask drop, fail-closed throw; decrypt path)",
41184118
"packages/spec/src/data/field.zod.ts#FieldType (FieldType 'secret'/'password' ADR-0100 notes; ackPlaintextMasking)",
41194119
"packages/cli/src/commands/serve.ts#host (LocalCryptoProvider host wiring via setCryptoProvider)",
41204120
"packages/platform-objects/src/system/sys-secret.object.ts (ciphertext store)",

0 commit comments

Comments
 (0)