Skip to content

Commit 4f6d088

Browse files
committed
Merge commit 'refs/claude-29520/main' into claude/issue-29520-sql-driver-codemod-pilot
2 parents 9414843 + b391feb commit 4f6d088

357 files changed

Lines changed: 16061 additions & 3300 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: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
'@objectstack/metadata-protocol': patch
3+
---
4+
5+
refactor(metadata-protocol): the metadata item reads and the layered read move out of `protocol.ts` through the protocol host
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
No behaviour change. The bodies of `getMetaItem`, `getMetaItemForExecution`, `readMetaItem`, `packagelessLayeredReadRefusal` and `getMetaItemLayered` now live in `protocol-meta-item-reads.ts` and `protocol-meta-layered-reads.ts`, reached through the per-instance host (`protocol-host.ts`). Each method keeps its docblock and signature on `ObjectStackProtocolImplementation`, and each moved body differs from the method body only by `this.` reading `host.`. The published `index.d.ts` (ESM and CJS) is byte-identical to the one `main` builds before this change. Nothing to migrate.
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
---
2+
'@object-ui/i18n': patch
3+
'@object-ui/plugin-designer': patch
4+
---
5+
6+
refactor(i18n, plugin-designer)!: the designer's version-history label leaves every pack
7+
8+
Clause-②: no (narrowing)
9+
Metadata-protocol: none
10+
11+
<!-- adr-0087: not-required (runtime-interface-only ui/packages/i18n/src/locales/en.ts#TranslationKeys) the key type of the console's own translation catalogue: no Zod schema, no spec declaration and no stored metadata reads it, so the compiler is the channel that reaches every caller -->
12+
13+
`@object-ui/plugin-designer` no longer ships a version-history panel (retired by #29529), so nothing reads `designer.versionHistory` any more. The key now leaves all ten packs (ar, de, en, es, fr, ja, ko, pt, ru, zh) and the designer's English fallback map.
14+
15+
**BREAKING** (`@object-ui/i18n`, TypeScript only): the exported `TranslationKeys` (`typeof en`) loses `designer.versionHistory`. A `t()` call that names it still compiles, since its key is a `string`, and reads the key back. Nothing replaces it; the fix is to delete the reference.
16+
17+
`@object-ui/plugin-designer`: the exported `DESIGNER_DEFAULT_TRANSLATIONS` map loses its `designer.versionHistory` row, so a read of that row now yields `undefined`; the map's type (`Record<string, string>`) is unchanged.
18+
19+
| Removed key | Replacement |
20+
|:---|:---|
21+
| `designer.versionHistory` | none: the version-history panel is retired |
22+
23+
The other `designer.*` keys (`undo`, `redo`, `canvasMinimap`) are unchanged.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
---
2+
'@objectstack/spec': patch
3+
'@objectstack/metadata-protocol': patch
4+
---
5+
6+
feat(spec): a save of a connector's served form drops the leftover inline value under an undeclared `auth` key, and the save answer names each key it dropped
7+
8+
Clause-②: yes (widening)
9+
Metadata-protocol: none
10+
11+
A connector row stored before the instance-auth shapes went strict can still hold a value under an `auth` key its kind does not declare, typically an inline secret such as `auth.token`. The read already withheld it. A save of the served form, however, re-grafted it from the stored row, so the cleartext stayed at rest through every edit, although no door accepts the key and nothing at runtime reads it. Now:
12+
13+
- **The redactor can mark a withheld position as not carried forward.** `MetadataRedactionResult` (`@objectstack/spec/kernel`) gains an optional `notCarriedForwardKeys`: the positions among `redactedKeys` that a save drops instead of re-grafting. It is a subset, so `redactedKeys` still names every withheld position and a reader that knows only `redactedKeys` withholds exactly what it did before. Absent means none: the `datasource` built-in, the schema-derived redactors and every registered redactor keep carrying every withheld position forward, with nothing to change.
14+
- **The connector redactor marks its undeclared `auth` keys.** Each one is named in both lists. A cleared value there (`null`, `''`, an empty map) is now withheld too, because the strict shape refuses the key whatever it holds, so serving it made a plain read then save fail with `unrecognized_keys`. Every other withheld connector position (the four header and environment maps, `authentication`, a credential URL, an undeclared top-level, `retryConfig` or `actions` key, a non-object `auth`) is carried forward exactly as before.
15+
- **The save drops it and says so.** The `/meta` save (`saveMetaItem`, draft saves included) never re-grafts a not-carried-forward position, so the stored row loses it, and the answer's `advisories` carries one `warning` per dropped position: rule `withheld-key-dropped`, `where` naming the item, and `path` where the value sat at rest (`connectors[0].auth.token`). The notice never carries the value. The key is still omitted when there is nothing to report, so a save that drops nothing answers the same bytes as before. A value the incoming body itself holds at that position is the author's word and is not a drop.
16+
17+
If a connector still needs a credential that sat under such a key, store the secret outside the metadata and name it with `auth.credentialRef`.
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(console)!: a decision output declared `multiple: true` is collected as a list and sent as a string array, from the record header and the Approval Center alike
7+
8+
Clause-②: no (narrowing)
9+
Metadata-protocol: none
10+
11+
<!-- adr-0087: not-required (no-migration-prescription) No metadata key or stored shape changes: the Console now sends each decision output in the shape its already-declared `multiple` names, which the approvals decision door judges (`@objectstack/plugin-approvals`, same release). The one deleted key is a message string in the built-in locale packs whose only call site this change removes; it is not metadata, and a host that kept its own text under it keeps it in its own resources. -->
12+
13+
**BREAKING** for a TypeScript caller that names `actions.decisionOutput.helpMultiValue` through the pack-derived key type (`TranslationKeys`): the key leaves all ten packs (ar, de, en, es, fr, ja, ko, pt, ru, zh).
14+
15+
- **Before.** Every decision output without a picker (`text`, a bare key, or a `position` output with no registry reader) rendered as one text box whose help said to comma-separate multiple values, so a list output reached the server as one comma string, which the decision door now refuses.
16+
- **Now.** Such an output declared `multiple: true` renders as a tags input: each entry is trimmed, a blank or repeated entry is skipped, and the decide body carries a string array. An output without `multiple: true` stays one text box and is sent as one string; a comma in it is literal. Both decision surfaces build their params in `decisionOutputParams`, so the record header and the Approval Center send the same shape. Every output's help now reads `actions.decisionOutput.help`.
17+
- **Who sees it.** An approver deciding a step with a free-value list output: they add entries one by one (Enter, a comma key, or leaving the box) instead of typing one comma-separated line.
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
---
2+
'@objectstack/plugin-approvals': patch
3+
---
4+
5+
fix(plugin-approvals)!: an approval decision whose output does not match its declaration is refused before anything is recorded — a single value where `multiple: true` declares a list, a list where it does not (a bare-key or `text` output included), a list naming one entry twice, or a picker value that resolves to no user, department, team or held position
6+
7+
Clause-②: no (narrowing) — the approve and reject doors refuse a decision output whose shape does not match its declaration's `multiple` (every output, `text` and bare keys included), or whose `user`, `department`, `team` or `position` value does not resolve, which they admitted before. Nothing is widened.
8+
Metadata-protocol: none
9+
10+
<!-- adr-0087: not-required (no-migration-prescription) No key of any schema is added, removed, renamed or re-shaped, and no export or type changes: the already-declared `type` and `multiple` of a decision output are now enforced at the decision. The refusal is a VALIDATION_FAILED naming the output, answered to the approver at the decision, so there is nothing for a migration to rewrite and no tombstone. -->
11+
12+
**BREAKING** accept-set narrowing at the approvals decision (`@objectstack/plugin-approvals`), served by `POST /api/v1/approvals/requests/:id/approve` and `/reject` and every door that forwards to them.
13+
14+
- **Before.** A typed decision output was checked for presence only (`required`). A `type: 'user'` output naming an id no account carries was recorded as an approval; a stage-2 `expression` approver reading it then resolved nobody, and with `onEmptyApprovers: 'fail'` the run stranded behind a `500 RESUME_FAILED` while the request read `approved`. A bare string was stored for a `multiple: true` output, and a list could name one person twice. Until #29371 the spec documented that the runtime accepts any value shape for a list output, a CSV string (`'u2,u3'`) included; an API caller that sends that CSV string for a `multiple: true` output is now refused and sends a list (`['u2', 'u3']`) instead.
15+
- **Now.** Each admitted output is judged against its declaration before any write, on an approve and on a reject alike. `400 VALIDATION_FAILED` names every `outputs.KEY` that does not match; no action row and no output snapshot are written, and the request stays pending for the approver to decide again.
16+
- Every output, `text` and every bare-key declaration included: `multiple: true` takes a list and only a list, with no entry twice; `multiple` absent or false takes one value. A bare key (`decisionOutputs: ['note']`) therefore takes one value, as its declaration always said; a list is what an output declared `multiple: true` collects.
17+
- `text` is judged on that shape only; its values are free. A picker's values are text and must resolve:
18+
- `user`: a record id an account carries (a slot address such as `position:NAME`, or an email, is not one).
19+
- `department`: an active department of the request's organization.
20+
- `team`: a team that exists and is not another organization's.
21+
- `position`: a position machine name someone in the request's organization holds. A `sys_position` record id is refused: the `expression` approver matches holders by name, so an id would route to nobody.
22+
- **Unchanged.** A `text` value in the declared shape is accepted whatever it says. A blank value is judged only by `required`. A directory read that fails proves nothing and does not refuse the decision.
23+
- **A request already pending.** The judge reads the declaration snapshotted on the request when it opened. A request pending under a bare-key snapshot (`decisionOutputs: ['note']`) takes one value for that key, so its approver sends one value; nothing about the pending request needs changing. That one value may be a comma-separated string (`'u2,u3'`): a bare key is `text`, judged by shape only, and a downstream `expression` approver still splits a string value into a list, so a pending request meant to route to several people is not stranded.
24+
- **The Console.** Both decision surfaces send each output in its declared shape: a `text` or untyped output declared `multiple: true` is collected as a list of trimmed, distinct entries and sent as a string array, and a single-value one is sent as one string whose comma is literal (`@object-ui/app-shell`, same release).
25+
- **Who sees it.** A caller or decision UI that sent an output its declaration does not describe: a wrong picker id, a scalar for a list (the CSV string included), a list for a scalar (a list sent for a bare key included), or a repeated entry. Also an author who pairs a `department`, `team` or `position` picker output with a stage-2 `expression` approver retargeted by `organization` (ADR-0105 D9): the decision resolves the picked value in the request's organization, not in the approver's directory organization, so a value that exists only in that other organization is refused at the decision.
26+
27+
**The remedy.** Send each output in the shape its step declares, with picker values that name a real record (or, for `position`, a held machine name). A bare key takes one value, as its declaration always said; a step that collects several values declares `multiple: true`, and the Console now sends a list for it.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
'@objectstack/spec': minor
3+
---
4+
5+
feat(spec): `PackageExportBundleSchema` declares the whole-deployment export, every installed package as installable packages in one read
6+
7+
Clause-②: yes (widening)
8+
Metadata-protocol: added
9+
10+
A tenant on ObjectOS Cloud or Enterprise can take its ontology to the Apache-2.0 runtime only one package at a time today (`GET /packages/:id/export`). This declares the contract of the read that answers all of it at once, `GET /packages/export`. The route, and a CLI command that calls it, follow in `@objectstack/runtime` and `@objectstack/cli`; no runtime serves it yet. Until a runtime mounts the route, the schema's description opens with `[PLANNED — not yet served]` (ADR-0049): the shape is the contract that door must answer, not a read a client can make today.
11+
12+
- **`@objectstack/spec`**: `PackageExportBundleSchema` (with `PackageExportBundle` and `PackageExportBundleParsed`) in `@objectstack/spec/api`. The answer is `{ packages }`: every package whose registry row's `source` is not `code` (the marketplace and authored packages, a `.custom` pair among them; a `code` package is registered by the deployment's own code and travels with that code, not with the bundle), enabled or not, each exactly as `GET /packages/:id/export` answers it. The bundle carries no enabled state, so every entry installs enabled. The entries are `PackageExportManifestSchema` itself, so the per-package export stays the one producer. A base's `.custom` pair is its own entry with its override declarations, and environment customizations stay under the package that owns them. The bundle carries no credential value: the per-package export withholds credential positions, and a `credentialRef` stays a reference.
13+
- **Order**: dependencies first, the order a boot hydrates installed packages in (ADR-0141 D5). An entry comes after every entry its package's `manifest.dependencies` names, so a pair comes after its base. Install the entries in the order listed; a consumer never re-sorts them.
14+
- The answer is strict at the top level; each entry stays open, as the per-package export is.
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
---
2+
'@objectstack/spec': minor
3+
'@objectstack/lint': minor
4+
'@objectstack/platform-objects': minor
5+
'@objectstack/plugin-approvals': minor
6+
'@objectstack/plugin-audit': minor
7+
'@objectstack/plugin-security': minor
8+
'@objectstack/plugin-sharing': minor
9+
'@objectstack/plugin-webhooks': minor
10+
'@objectstack/service-automation': minor
11+
'@objectstack/service-mailbox': minor
12+
'@objectstack/service-messaging': minor
13+
'@objectstack/service-realtime': minor
14+
'@objectstack/service-storage': minor
15+
---
16+
17+
feat!: an object that declares `titleFormat` is refused at save and by `os validate`, with the `nameField` prescription (#29007)
18+
19+
Clause-②: no (narrowing)
20+
Metadata-protocol: narrowed
21+
22+
<!-- adr-0087: registered object-title-format-refused -->
23+
24+
**BREAKING** accept-set narrowing on `object.titleFormat`. The key stays in the schema; what changes is which doors accept it.
25+
26+
- **Before.** `titleFormat` was deprecated (ADR-0079) and still accepted: a save, a publish and `os validate` all passed, with an advisory `title-format-retired` warning on the CLI only. It titled nothing. An explicit `nameField` outranks it, and the registry stamps a derived `nameField` on every object that authors none, so the record page and the list showed one field however the template was written.
27+
- **Now.** `title-format-retired` is an error. An object definition that declares `titleFormat` is refused by `os validate`, `os build` and `os lint`, and at the metadata save gate (`422 INVALID_METADATA`): an active save, a draft promotion and a restore. A draft save still lands, and its `_diagnostics` reads `valid: false` with the refusal its publish gives. The message and the fix line are the prescription the `titleFormat` describe already carried.
28+
- **Unchanged.** A stored object that carries `titleFormat` still parses, loads and is served; no conversion drops the key. It is refused only when it is next saved, published or restored. The title order is unchanged: `nameField`, then its alias `displayNameField`, then the derivation.
29+
- **The platform's own objects.** The 68 system objects that declared `titleFormat` no longer do. 65 already declared the `nameField` that titles them. `sys_attachment`, `sys_audience_binding_suggestion` and `sys_notification_template` now declare the field the registry already stamped (`file_name`, `permission_set_name`, `subject`), so every record title reads as before.
30+
- **`@objectstack/lint`.** `validateTitleFormatRetired` is a new export and a new gating registry entry. `validateRecordTitle` now reports `title-unresolvable` only.
31+
32+
**The remedy.** Point `nameField` at the title field and drop `titleFormat`. For a composite title that an import, a sort or a search must match, add a `text` field that declares an `expression` (computed and stored on every write) and point `nameField` at it; a `formula` with `returnType: 'text'` stays legal for a title shown for display only. The upgrade guide entry is `object-title-format-refused`.

‎.changeset/29026-flow-condition-null-guard.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,6 @@ Metadata-protocol: narrowed
1212
`os build`, `os validate`, `os lint` and the runtime publish gate now refuse a record-triggered flow (start `triggerType: 'record-…'`) whose start condition, edge `condition` or `decision` branch `expression` applies an ordering (`< <= > >=`) or arithmetic (`+ - * / %`) operator to a nullable declared field of the trigger object with no `!= null` / `== null` / `!isBlank()` test in front of it. The run's `record` and `previous` carry every declared field, nulls included, so such a comparison faults with `no such overload` on the first record where the field is empty, and the run fails there (the write that fired it still lands).
1313

1414
- **Who moves.** A flow condition such as `budget > 200000` on an optional `budget`. Fix: `budget != null && budget > 200000`, or put the `!= null` test in the flow's start condition, which gates every run, so every edge and branch after it may compare freely. `has(record.budget)` is not a guard: the key is always present.
15-
- **Bare names count.** The run spreads the record's fields to top level, so a bare `budget` is read as `record.budget`, unless the flow declares a variable or a node id of that name, or has a node that can bind names nobody declared (a `script`, `connector_action`, `wait`, `subflow` or `map`); those bare names are not judged. At the runtime publish gate bare names are not judged.
15+
- **Bare names count.** The run spreads the record's fields to top level, so a bare `budget` is read as `record.budget`, unless the flow declares a variable or a node id of that name, or has a node that can bind names nobody declared (a `script`, `connector_action`, `wait`, `subflow` or `map`); those bare names are not judged. At the runtime publish gate bare names are not judged, so a bare start-condition guard proves nothing there: a flow whose start condition is `budget != null` and whose edge compares `record.budget > 200000` passes `os validate` (where the flow's bound set is provable) and is refused by the runtime publish gate. Spell the start guard `record.budget != null` and both accept it.
1616
- **Not judged.** Flows with no record trigger (an `api` flow's request body, an action's or `map` item's record are not made total), a `screen` field's `visibleWhen`, and `config.condition` on any node but `start`.
17-
- **Fewer findings elsewhere.** A `count` or `sum` roll-up summary is now always valued for this gate (zero children is zero), on validation rules and hook conditions too; `min` / `max` / `avg` roll-ups stay nullable.
17+
- **Fewer findings elsewhere.** A `count` or `sum` roll-up summary is now always valued for this gate (zero children is zero), on validation rules and hook conditions too; `min` / `max` / `avg` roll-ups stay nullable. That presumes `os migrate summary-nulls` ran: on a database upgraded in place without it, a parent row stored before roll-ups were seeded at create time, and no child of it written since, still reads `null`.

0 commit comments

Comments
 (0)