Skip to content

Commit 76d25fa

Browse files
committed
Merge origin/main into claude/project-thread-une4f5 (after #29589)
Resolve refused-shapes.ts and its test additively: keep #29589's object_extension fieldPatches detector and this card's dashboard reserved-dateRange detector; the roster pin counts both. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WGufzg4LNpBsJKihJRTLqD
2 parents 9e7c4c3 + 3d5933a commit 76d25fa

494 files changed

Lines changed: 23803 additions & 4332 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: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
'@objectstack/platform-objects': patch
3+
---
4+
5+
feat(platform-objects): Setup no longer carries the unfiltered Packages entry; the full package list lives in Studio
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
The full `developer:packages` list is a development surface, so its one unfiltered navigation entry is now Studio's Overview → Packages, which is unchanged. Setup's `group_apps` drops its `nav_packages` entry. Setup keeps the marketplace-filtered "Installed Apps" door (`nav_marketplace_installed`, contributed by `@objectstack/cloud-connection` wherever the marketplace capability is present), so its Apps group reads Browse Marketplace, Installed Apps and Packaged Actions.
11+
12+
The Setup copies of the entry's label (`apps.setup.navigation.nav_packages` in `en`, `zh-CN`, `ja-JP` and `es-ES`) and their recorded source hashes are removed with it; Studio's copies stay. A bookmark to the Packages page keeps working, since the page itself did not move.
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: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
---
2+
'@objectstack/spec': patch
3+
---
4+
5+
docs(spec): the view filter any-of group is described as live, not planned
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
The console now applies a view filter's any-of group `{ or: [rule, ...] }`: the list query lowers it to one `or` node ANDed with the filter's other elements, and the filter builder edits a filter that is exactly one group. Two author-facing texts still said otherwise and are corrected:
11+
12+
- `ViewFilterOrGroupSchema`'s description (what `/meta/types` and the generated reference show) no longer starts with `[PLANNED — not yet rendered]` and no longer says a view that stores a group is refused when it loads.
13+
- The refusal for a MongoDB-style `$or` on a rule-array `filter` still prescribes the any-of group element, and no longer adds that no console applies it.
14+
15+
Nothing an author can write changes: the schema accepts and refuses exactly what it did.
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: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
---
2+
'@objectstack/spec': minor
3+
'@objectstack/objectql': minor
4+
'@objectstack/metadata-protocol': minor
5+
---
6+
7+
feat(spec): a record page can name the apps whose routes open it for its object (`PageSchema.apps`)
8+
9+
Clause-②: yes (widening)
10+
Metadata-protocol: added
11+
12+
An app could not make its own record page over a platform object the page its routes open: the deployment's one `isDefault` record page (for `sys_user`, plugin-auth's `sys_user_detail`) opened everywhere, and declaring a second default is refused. A record page now takes an optional app scope:
13+
14+
```ts
15+
definePage({
16+
name: 'duly_member',
17+
label: 'Member',
18+
type: 'record',
19+
object: 'sys_user',
20+
apps: ['duly'],
21+
regions: [/* … */],
22+
});
23+
```
24+
25+
- **What it means.** Inside an app `apps` names, the object's record view (`/apps/APP/OBJECT/record/ID`) opens this page. Everywhere else, Setup included, the object's one `isDefault` record page still opens. One default per object per deployment is unchanged, and there is no precedence ladder: the default and an app's page are two slots, and one page may hold both.
26+
- **One page per object per app.** Two record pages for the same object that both name the same app are refused at the doors that refuse a second default, naming both pages and the app: `defineStack` (and so `os validate` / `os build`) and `composeStacks` with `STACK_CROSS_REFERENCE_INVALID`, and package registration and the metadata save door with `DEFAULT_RECORD_PAGE_CONFLICT` (422). Saving a page under its own name stays an overlay of that page, as for the default.
27+
- **Where it is accepted.** Only on a record page bound to an `object`, where `isDefault` is: on any other page `apps` is refused, by the page parse and at package registration, in one sentence. Entries are app names (lowercase snake_case), at least one, each once.
28+
- **Declared ahead of the console.** The console's page assignment reads the route's app in a follow-up; until then every app still opens the default record page, exactly as before the key existed.
29+
- **Readers.** `defaultRecordPageObject(page, app)` (`@objectstack/spec/ui`) answers the object a page is the record page of inside `app`; `SchemaRegistry.defaultRecordPageHoldersOf(object, app)` answers the holders of that slot; a `DefaultRecordPageConflictError` conflict and the save door's refusal carry `app` when the slot is an app's.

0 commit comments

Comments
 (0)