Skip to content

Commit 009c0b7

Browse files
committed
Merge remote-tracking branch 'origin/main' into claude/project-thread-vrj8cl
# Conflicts: # packages/runtime/src/domains/packages.ts
2 parents 583fce4 + 33ee9aa commit 009c0b7

126 files changed

Lines changed: 5704 additions & 745 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.

‎.changeset/29366-field-patches-key-grammar.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,6 @@ Skills-docs: updated here — `content/docs/data-modeling/object-extensions.mdx`
1919
- **Before.** `fieldPatches: { Industry: { label: 'Sector' } }` parsed in `defineStack`, was stored by the save door and booted: the registry fold named the patch once in the log and applied nothing, and `os validate` refused it only when the target object was in the same stack.
2020
- **Now.** One `invalid_key` issue at `fieldPatches.Industry`, naming the grammar and an example. The key schema is the one `ObjectSchema.fields` reads, hoisted to one constant, so the two cannot drift.
2121
- **Unchanged.** A snake_case key parses as before, and a `__c` key is still refused by the `__c` rule with its own prescription.
22-
- **Who sees it.** No extension in this repository's examples, skills, docs or templates writes such a key. A stored `object_extension` row that carries one no longer parses, so the sync names the row (`INVALID_METADATA`) and registers none of it, its `fields` included; its next save is refused until the key is renamed. `os migrate meta --stored` does not list such a row beforehand: the stored pass has no stack collection for an `object_extension` row and hands it back untouched, so check each stored extension's `fieldPatches` keys against the grammar before upgrading.
22+
- **Who sees it.** No extension in this repository's examples, skills, docs or templates writes such a key. A stored `object_extension` row that carries one no longer parses, so the sync names the row (`INVALID_METADATA`) and registers none of it, its `fields` included; its next save is refused until the key is renamed. `os migrate meta --stored` lists such a row beforehand, one TODO for each refused key at `fieldPatches.KEY`, and leaves it as stored for its owner to rename.
2323

2424
**FROM → TO.** `fieldPatches: { Industry: { label: 'Sector' } }` → `fieldPatches: { industry: { label: 'Sector' } }`: key each patch by the name of the field the extended object declares (the key of its `fields` entry), never by its label. The upgrade guide entry is `object-extension-field-patch-key-grammar-refused`.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
'@objectstack/metadata-protocol': patch
3+
---
4+
5+
fix(metadata-protocol): the layered read of a form view naming a field its object lacks now carries the form-field-unknown warning
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
`GET /api/v1/meta/view/NAME?layers=true` judged its `_diagnostics` with the schema alone, so a published form view whose section names a missing field (or an undeclared field group) read `{ valid: true }` there, while `GET /api/v1/meta/view/NAME` of the same view carried the lint's warning. Studio's item editor reads the layered answer when no draft is pending, so the author of a published form view saw no warning.
11+
12+
The layered read now runs the same form-layout pass on its `effective` body that the single-item read runs, against the object the registry serves for each name the view spells. The warnings land at the same paths, and `valid` stays the schema's answer. A form naming only real fields still reads `{ valid: true }`.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
---
2+
'@objectstack/spec': minor
3+
'@objectstack/lint': minor
4+
'@objectstack/platform-objects': patch
5+
---
6+
7+
feat(spec): a flow start input can declare that it is the id of a record of a named object (`objectName`)
8+
9+
Clause-②: yes (widening)
10+
Metadata-protocol: added
11+
12+
`FlowVariableSchema` declares one optional key, `objectName`: on an input (`isInput: true`), it says the input's value is the id of one record of the named object, e.g. `{ name: 'accountId', type: 'text', isInput: true, objectName: 'crm_account' }`. The key is **planned**: no door reads it yet, and `os validate` warns on every flow that writes it (`liveness-planned-property`). The contract it states, for the start doors to enforce: every door that starts the flow by name (the trigger route, an action of type flow over REST and MCP `run_action`, a declared endpoint of type flow) loads a declared input from that object in the caller's own scope before the run exists; an id the caller cannot read there is refused `404 RECORD_NOT_FOUND`, and a value that is not an id is refused `400 VALIDATION_FAILED` naming the input. An input without the declaration keeps the visible-read rule: it is judged as a record only where the flow's own definition reads it as a record id.
13+
14+
- **Spec.** `objectName` is an object name (snake_case) and is refused on a variable that is not an input, since no caller's value reaches one. `type` stays the data type: the variable holds the id. A `reference` or `object` key on a flow variable is still refused, and the refusal now names `objectName`. The flow form's variables repeater gains the column, labelled in the metadata-form bundles (en, zh-CN, ja-JP, es-ES).
15+
- **Lint.** `flow-input-unknown-object`: a declared object the stack does not define is a warning (it may come from another installed package), and an error at the runtime publish gate when the write's package closure does not reach it, the verdict `flow-trigger-unknown-object` gives a trigger object. A `sys_` name is not judged.
16+
17+
This is the contract half: the start doors read the declaration in a later change, and until then no door loads a declared input, so a start that hands the input an id the caller cannot read still runs. Nothing to do for existing flows.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
---
2+
'@object-ui/fields': patch
3+
'@objectstack/spec': patch
4+
'@objectstack/platform-objects': patch
5+
---
6+
7+
feat(fields, spec): a `referenceNameVisible` lookup or user field shows the name of a record the reader may not open as plain text, and the field form offers the key
8+
9+
Clause-②: no
10+
Metadata-protocol: none
11+
12+
The console half of `FieldSchema.referenceNameVisible`, after its server half began serving the name:
13+
14+
- **Lookup and user cells, and the record page.** When `$expand` answers a reference as `{ id, name, readRefused: true }` (the reader may read the record carrying the field, but not the record it points at), the lookup cell and the user cell draw `name` as plain text: no link to the record, no hover card, and no read of that record or of its object. A `multiple: true` field draws one text chip per such element, and its other elements keep their faces. The floor name `Record #` with the id is drawn exactly as served. The record page's field grid draws these values through the same cells.
15+
- **Unchanged.** A field without the key, and a target the reader can read, keep today's faces: a readable record is a link, and a bare id the reader's own read answers "not found" for keeps the restricted-record placeholder.
16+
- **Field form.** The `field` metadata form gains a `referenceNameVisible` switch ("Show Name When Unreadable"), shown on `lookup` and `user` fields only, the types the schema accepts the key on. Its label and help text are translated for `zh-CN`, `ja-JP` and `es-ES`.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
---
2+
'@objectstack/spec': minor
3+
---
4+
5+
feat(spec): declare the nine remaining served `features` keys on the `GET /api/v1/auth/config` response
6+
7+
Clause-②: yes (widening)
8+
Metadata-protocol: added
9+
10+
`AuthFeaturesConfigSchema` gains nine optional keys that the server already serves in `features` and the console already reads. The contract did not declare them, so a client that parsed the response through `GetAuthConfigResponseSchema` lost all nine, because a plain object schema strips keys it does not know.
11+
12+
- **Flags (booleans):** `multiOrgEnabled` (an organization wall is enforced; default-on, so absent reads as on), `degradedTenancy` (isolation was asked for but is not active), `oidcProvider` (the embedded OIDC authorization server is wired; default-on), `deviceAuthorization` (the device-code CLI login is wired), `admin` (the admin plugin is wired, forced on by SCIM).
13+
- **Values:** `tenancyPosture` (`single` | `group` | `isolated`, the same vocabulary as `TenancyPostureSchema`), `audiencePosture` (`invite_only` | `email_domain` | `open`, the same vocabulary as `AUDIENCE_POSTURES`), `termsUrl` and `privacyUrl` (the legal links under the login card; absent when the operator suppressed them).
14+
- None carries a default: a key the server does not send stays absent.
15+
- A new test holds the schema's declared keys equal to `PUBLIC_AUTH_FEATURES` plus `PUBLIC_AUTH_CONFIG_NON_FLAG_KEYS` (the retired `passkeys` / `magicLink` tombstones excepted), so the next served and classified key cannot be left undeclared.
16+
- The development-only top-level `devSeedAdmin` hint stays undeclared on purpose: it carries the seeded admin's password and no production deployment serves it.
17+
18+
What to do: nothing. Every payload accepted before is accepted unchanged. A client that typed the response from `@objectstack/spec` (for example through `@objectstack/client`'s re-exported `AuthFeaturesConfig`) now sees the nine keys as optional.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
---
2+
'@object-ui/data-objectstack': patch
3+
---
4+
5+
perf(data-objectstack)!: an object route reads that object's saved views (`GET /meta/view?object=`), not every view of every object (#29550)
6+
7+
Clause-②: no (narrowing) — `listViews` and `listViewOverrides` no longer list a stored `view` row that carries no `viewKind` or no top-level `object` (a legacy bare spec, a `{ list }` wrapper, a row bound only through `data.object`). The draft-preview read applies the same rule. 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, signature or answer shape changes. The adapter's two view readers now apply the rule the server's one-object view read (ADR-0017 section 3.5) and the view write door (#7741) already apply: a row is bound to an object by `object` plus `viewKind`. There is no key for a migration to rewrite; a row stored before the write door refused unbound rows is re-bound by saving the view again. -->
11+
12+
**BREAKING** read narrowing in `@object-ui/data-objectstack` (`ObjectStackAdapter.listViews`, `ObjectStackAdapter.listViewOverrides`), which feed the Console's saved-view tabs and per-view settings.
13+
14+
- **Before.** On every object route, both readers downloaded the whole published view list (`GET /api/v1/meta/view`, every view of every object; 7 KB on app-crm) and kept one object's rows in the browser, matching a row on `data.object` or `object`, with or without `viewKind`.
15+
- **Now.** While nothing on the page has loaded the whole list, they read `GET /api/v1/meta/view?object=OBJECT`, the read ADR-0017 declares for the view switcher, once for both readers. Once something has loaded the whole list, or while its read is on the wire (Studio, the MetadataProvider, a packaged app's object scope), they answer from it with no request of their own. Both paths keep the same rows in the same order: `viewKind` set, `object` naming the object, sorted by `order` and then `name`. The draft-preview read (`listViews` with `previewDrafts`) keeps its own `?preview=draft` request and narrows its rows by the same rule.
16+
- **Order.** Saved-view tabs follow the contract's order (`order`, then `name`). Before, they followed the order of the server's whole list, so tabs with no `order` could appear in a different order.
17+
- **Who sees it.** A deployment holding a `view` row stored before the write door refused rows without `object` and `viewKind` (#7741). That row was a saved-view tab and a per-view setting before, and is neither now. The server's one-object read never served it. Views the Console saves today carry both fields, and none of the rows on a fresh app-crm or showcase deployment were affected.
18+
19+
**The remedy.** Open the view in the Console and save it again: the save writes `object` and `viewKind`, and the view is listed again.
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+
fix(spec): the stored pass reads a stored `object_extension` row as an `objectExtensions` entry, so conversions replay over it and `os migrate meta --stored` lists a `fieldPatches` key no field name can be
6+
7+
Clause-②: no (no accept set moves; the stored pass and the `--stored` listing widen)
8+
9+
Metadata-protocol: none
10+
11+
`applyConversionsToStoredItem` wraps a stored row in the stack collection its type maps to, and returned a row untouched when its type mapped to none. `object_extension` (ADR-0146 D10) mapped to none, so no conversion that walks `objectExtensions` ever ran over a stored extension row at a rehydration seam, and no refused-shape detector could list one: `os migrate meta --stored` read every stored extension as already on protocol.
12+
13+
- **Now.** A stored `object_extension` row is read as one `objectExtensions` entry, beside `translation` in the stored-only map; the manifest-collection maps still carry no `objectExtensions`, because a stack's extensions merge into their targets rather than registering as named items. A stored extension with a `time` default written with a `Z` suffix, for example, is now served without it, with the conversion's notice, as an object row already was.
14+
- **Listed.** `os migrate meta --stored` lists each `fieldPatches` key the extension's own parse refuses as no field name (`Industry`, `Annual Revenue`, `annual-revenue`): one TODO at `fieldPatches.KEY` under the row, naming the extended object, leaving the row as stored. Rename each listed key to the field it meant before upgrading. The upgrade guide entry `object-extension-field-patch-key-grammar-refused` says so instead of naming a manual check.
15+
- **Unchanged.** A canonical extension row passes through by reference, every other row type converts exactly as before, and no schema accepts or refuses anything it did not.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
---
2+
'@objectstack/lint': patch
3+
---
4+
5+
Clause-②: no
6+
Metadata-protocol: none
7+
8+
fix(lint): `connector-credential-literal` now also warns when a stdio MCP server's credential goes nowhere, or when a credential is typed into `transport.credentialEnv`
9+
10+
Two new findings on the existing advisory. Both are warnings, so nothing is refused and every save still lands. They reach `os validate`, `os build`, `os lint` and the connector save door (`PUT /meta/connector/:name`), the same doors the rule already runs on.
11+
12+
- **A stdio `auth` that reaches nothing.** Take a `provider: 'mcp'` entry with a stdio transport and a `bearer`, `api-key` or `basic` `auth`, but no `providerConfig.transport.credentialEnv`. Its reference is still resolved, and a missing one still degrades the instance. But the provider hands the credential to a stdio server only in the variable `credentialEnv` names, so the credential was dropped and nothing said so. The finding sits at `auth` and names the fix. Add `credentialEnv`, or drop `auth` if the server takes no credential:
13+
14+
```ts
15+
providerConfig: { transport: { kind: 'stdio', command: 'repo-mcp-server', credentialEnv: 'REPO_MCP_TOKEN' } },
16+
auth: { type: 'bearer', credentialRef: 'REPO_MCP_TOKEN' },
17+
```
18+
19+
- **A credential in `transport.credentialEnv`.** That key holds the name of an environment variable, and it is served on every metadata read. The rule now warns when the value reads as a credential instead of a name. That means it opens with an auth scheme (`Bearer …`), or it has a run of 16 or more letters and digits that switches between the two at least four times, the way a generated token does. A conventional name such as `REPO_MCP_TOKEN`, or a long capital name, never draws. The finding names the key and never echoes the value.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
'@objectstack/plugin-security': patch
3+
---
4+
5+
fix(plugin-security): `os explain` no longer tells a Modify All holder that a row-conditioned field lock binds
6+
7+
Clause-②: yes (widening)
8+
Metadata-protocol: none
9+
10+
The access explanation's field-level layer listed every field permission that carries a `when` as "judged per row by the write gate, applied as the lock everywhere else". For a caller whose permission sets hold Modify All Data on the object, that was untrue: the field merge applies the Modify All vote after the per-entry votes, under every `when` verdict, so such a lock binds on no row (ruling of record 6573563853, letter A). The explanation now lists those entries separately, as superseded by the Modify All Data field vote and naming the sets that hold it. A View All holder, and a caller who holds neither, keeps the row-conditioned sentence as before, because their edit lock still binds per row.
11+
12+
Enforcement is unchanged. `PermissionEvaluator.conditionedFieldEntries` now names each entry's superseding sets in a new `supersededBy` member, read from the same `superuserFieldVote` that `getFieldPermissions` applies.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
'@objectstack/driver-sql': patch
3+
---
4+
5+
refactor(driver-sql): the comparand bind test reads the list operators from the spec
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
The SQL driver bind-tests each member of a `$in`, `$nin` or `$between` list as a comparand in its own right. It now asks `isListComparandOperator` from `@objectstack/spec/data` which operators take a list, instead of keeping its own copy of `$in` / `$nin` / `$between`. The set is the same, so no filter compiles or is refused differently. A later change to the spec's list operators now reaches this driver without a second edit.

0 commit comments

Comments
 (0)