Skip to content

Commit 00f922f

Browse files
committed
Merge origin/main (0138641) into claude/harden-occ-stale-token-order
One conflict, packages/metadata-protocol/src/protocol.ts: main moved the module preamble into themed modules (#29336) while this branch had edited the ConcurrentUpdateError docblock inside it. Resolved by taking main's removal and carrying the docblock edit to protocol-version-tokens.ts, whose move header now names that one changed docblock. The branch's three class-body hunks (updateData, probeRecord, assertVersionOf) merged clean. Claude-Session: https://claude.ai/code/session_017xRBcVy4VXmkQjn2MGeaWk Co-authored-by: Claude <noreply@anthropic.com>
2 parents 8372e0a + 0138641 commit 00f922f

473 files changed

Lines changed: 34015 additions & 7923 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: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
'@objectstack/metadata-protocol': patch
3+
'@objectstack/spec': patch
4+
---
5+
6+
refactor(metadata-protocol): the module preamble of `protocol.ts` moves, verbatim, into 22 themed `protocol-*.ts` modules
7+
8+
Clause-②: no
9+
Metadata-protocol: none
10+
11+
A move with no behaviour change. Every top-level declaration that stood above `ObjectStackProtocolImplementation` in `src/protocol.ts` (183 statements: helpers, error classes, the query-parameter and filter vocabulary, request and event types) now lives in a themed module beside it, byte for byte with its comments. `protocol.ts` re-exports every name it exported, so `@objectstack/metadata-protocol`'s entry is unchanged: the published `index.d.ts` keeps the same declarations in the same order and the same ESM and CJS export list, and differs only in blank lines, the order of names in one `@objectstack/spec/api` import list and the member order of one literal union. Nothing to migrate.
12+
13+
`@objectstack/spec` ships its `liveness/` ledger: two evidence citations in `liveness/query.json` (`QUERY_AST_KEYS`, `WIRE_DOLLAR_ALIASES`) now name `protocol-list-query-params.ts`, the module those lines moved to. No schema, type or export of `@objectstack/spec` changes.

‎.changeset/27155-form-view-type-layout-only.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,6 @@ The model: `type` says how the form lays out its sections. Where a form opens is
1818
- **FROM → TO.** `type: 'split'` → `type: 'tabbed'` (one tab per section) or delete the value for `simple`; delete each section's `pane` and the form's `splitSize` / `splitResizable`; declare `subforms` to edit child records beside the parent. `type: 'drawer'` or `type: 'modal'` → delete the value for `simple`, or write the layout you want. A list opens a clicked record as its `navigation.mode` says (the drawer by default), and the console's New and Edit entries open the record form themselves, sized by the form's `modalSize`.
1919
- **The one-line fix.** Delete the retired value and the three split-only keys. `os migrate meta --from 17` lists the edits for existing sources, and `--write` applies them. A stored `sys_metadata` row is converted when it is read (the `form-view-presentation-types-removed` conversion), so a form view saved with one of the three values keeps loading as a `simple` form.
2020
- **What the parse says.** Each retired value is refused by name with its prescription, for example "'modal' was removed from the form view `type` enum in @objectstack/spec 18.3.0 (ADR-0050) — …", at every form view door (a view's `form` and `formViews.*`, a form view item record, a flattened form overlay) and at `defineStack` as `STACK_SCHEMA_INVALID` / 422. `pane`, `splitSize` and `splitResizable` are refused on every layout. TypeScript rejects each at the authoring site. A misspelt value keeps zod's own message.
21-
- **Unchanged.** `splitDirection`, `drawerSide`, `drawerWidth` and `modalSize` stay declared: the console reads `modalSize` for its record dialog, and the React `ObjectForm` block contract still takes the other three from this schema until its renderer follows.
21+
- **Unchanged.** `modalSize` stays declared: the console reads it for its record dialog. `splitDirection` (#27159) and `drawerSide` / `drawerWidth` (#29012) retire from the form view in the same release; the React `ObjectForm` block keeps the drawer pair as its own props.
2222

2323
The console (`@object-ui/app-shell`): Studio's New View form offers the three form layouts only. It offered all six, and a form view created with `split`, `drawer` or `modal` would now be refused when saved.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
'@objectstack/skills': patch
3+
---
4+
5+
docs(skills): the ADR-0126 §1.3 promise pages and the skills teach what ADR-0146 D2 delivers — customizations in `.custom`, `__c` fields, the flow override and "new similar", no package-less overlay
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
- **Before.** The skills taught the pre-epic paths: `objectstack-data` named no home for a customer's extension of a shipped object and no `__c` rule, and its lock table still called an edit of a shipped item an "overlay"; `objectstack-automation` said nothing about a flow an installed package ships, so an agent cloned it (two copies running) or edited it in place (refused); `objectstack-platform` taught the declared override's read but not who may declare it, how the pair is created, or what the three non-overridable types do. The docs promise pages (`capabilities/integrations`, `build-without-code`) still said views and dashboards are "yours to edit directly" and sent readers to a Setup packaged-automation page to switch off and clone a flow.
11+
- **Now.** `objectstack-data` says an object an installed package ships is extended from the base's paired `.custom` package, every added field ends in `__c`, the suffix is refused anywhere else and an unsuffixed field is refused in the pair, a base field changes only through `fieldPatches`, and Studio's Add field writes the same extension; its lock table reads "Edit", not "Edit (overlay)". `objectstack-automation` says a packaged flow is customized by a same-named declared override in the pair (armed in the base's place, one activation switch, a suspended run finishes on the body it started on), that an undeclared same name is refused at publish, and that the clone door is "new similar": a copy under a new name, switched off, in the pair, never package-less. `objectstack-platform` adds the ensure door and its two manifest facts, the customizations read, the uninstall refusal, the write gate's one rule for dependents (rename), and "new similar" for permission sets, positions and capabilities. One eval per skill asserts the taught shape and refuses the old one.
12+
- Every file stays under its token ceiling: the new text is paid for by deleting restatement in the same file (a layout tree the Template section and the Naming table already state; a search-mirror maintenance table and a formula aside restating the sentence above them; the dates and money legacy → CEL rows `objectstack-formula`'s table holds; the CEL callout restating that skill's pointer; two `runAs` notes and a `readonly` note tightened, no rule removed). The touched ceilings lower to their new measurements; no ceiling rises, no frontmatter, schema or behaviour changes. The docs site's promise pages, install modes, object extensions, metadata lifecycle, permission sets, CLI and metadata API pages gain the same facts.
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
---
2+
'@objectstack/spec': minor
3+
---
4+
5+
feat(spec)!: an object extension's `fieldPatches` refuses a field the same extension replaces in `fields`, keys option patches by the option-value grammar, refuses `__proto__`, and carries the field's and option's display renames
6+
7+
`ObjectExtensionSchema.fieldPatches` (ADR-0146 D10) is applied by the registry merge, so what it accepts now changes what every read serves. Three things it accepted are refused, each located at its key:
8+
9+
- a field named in both `fields` and `fieldPatches` of one extension (`fieldPatches.industry`): `fields` takes the vendor's field over whole, so the patch landed on the extension's own replacement and the author got neither answer;
10+
- an option-patch key no option `value` can be (`fieldPatches.industry.options.Tech Sector`): the map is now keyed by `SelectOptionSchema.shape.value` itself, so the patch can address only an option that can exist;
11+
- a `__proto__` own key in `fields`, `fieldPatches` or an option-patch map, which zod's record parser dropped while reporting success. Measured at the `/api/v1/meta` save door: the body parsed, the stored row kept the key, and the registered extension did not.
12+
13+
A field patch that spells a key the way the field's own rename table knows (`helpText`, `help`, `hint` or `tooltip` for `inlineHelpText`; `title` or `displayName` for `label`), and an option patch that spells one the option's table knows (`text`, `name` or `title` for `label`), now gets the rename hint instead of a bare unknown-key refusal. The rows are taken by filter from the one table each source shape reads, so a rename onto a whitelisted key reaches the patch the day it lands. The `fieldPatches` description lists its members from `BASE_FIELD_DISPLAY_WHITELIST`.
14+
15+
Clause-②: no (narrowing) — `ObjectExtensionSchema` refuses three inputs it accepted: a field both replaced and patched, an option-patch key outside the option-value grammar, and a `__proto__` own key in its three record slots. Nothing is widened: the renames are hints on keys already refused.
16+
17+
Metadata-protocol: narrowed (an object extension may no longer patch a field it replaces in `fields`, nor key an option patch by anything but a valid option value)
18+
19+
<!-- adr-0087: not-required (no-migration-prescription) No key of any schema is added, removed or renamed. A field both replaced and patched has two opposite intents and no rewrite keeps both; an option-patch key outside the value grammar never addressed an option; a __proto__ key never survived the parse. The author decides, and each refusal names the key. -->
20+
21+
**BREAKING** accept-set narrowing on `ObjectExtensionSchema` (`@objectstack/spec`), at every door that parses an object extension: `defineObjectExtension`, `defineStack({ objectExtensions })`, `os validate`, the marketplace install and the `/api/v1/meta` `object_extension` save door.
22+
23+
- **Before.** `{ extend: 'crm_account', fields: { industry: {…} }, fieldPatches: { industry: { label: 'Sector' } } }` parsed, and the patch relabelled the extension's replacement. `fieldPatches: { industry: { options: { 'Tech Sector': { label: 'Tech' } } } }` parsed and could never match an option. A JSON body carrying `"__proto__"` under `fields` or `fieldPatches` parsed with that key silently missing.
24+
- **Now.** Each is refused: `custom` at `fieldPatches.industry`, `invalid_key` at `fieldPatches.industry.options.Tech Sector`, `custom` at `fields.__proto__` / `fieldPatches.__proto__`.
25+
- **Unchanged.** A patch of a field the extension does not replace, keyed by a valid option value, parses as before.
26+
- **Who sees it.** No extension in this repository's examples, skills or docs writes any of the refused forms.
27+
28+
**The remedy.** Choose one answer per field: keep the patch to change only its display (the vendor's type, values and upgrades still apply), or keep the `fields` entry and set the display properties there. Key an option patch by the option's stored `value` (`technology`), not its label. Rename a `__proto__` key.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
'@objectstack/spec': patch
3+
---
4+
5+
fix(spec): the connector docs say where an undeclared key's refusal is located: at the entry, with the key named in `keys`
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
A connector entry refuses a top-level key it does not declare, through `DeclarativeConnectorEntrySchema` and `defineConnector()`. The TSDoc on both said the refusal was "located at the key". It is not. zod raises one `unrecognized_keys` issue at the entry itself: its path is empty when the entry is parsed alone and `connectors.N` inside a stack, and it lists every undeclared key in `keys` and in its message. An author or agent looking for an issue at the key's path found none.
11+
12+
The TSDoc now says this. The same correction is made in the reason of the protocol-18 upgrade note for undeclared connector keys (`connector-entry-undeclared-key-refused`), which is not released yet.
13+
14+
Prose only. The refusal, its code, its path, its message and what the connector schema accepts are unchanged.

‎.changeset/28856-action-stale-summary-answer.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,4 @@ When a declarative update action (`operation: 'update'`) writes its record but a
1515
- **MCP `run_action`**: the usual `{ ok, action, objectName, recordId, result }`.
1616
- The action's invocation audit row records `succeeded`, the outcome the door answers.
1717

18-
The runtime composes the action's result once and attaches it to the refusal through the existing carrier; each door relays it. The stale summary column is logged at `error` once per field per process. A refusal before the write answers exactly as before. Other action kinds are unchanged: an `api` action's forward already lands on a data door that answers success, a `flow` action's record nodes turn the refusal into a node failure, and a script or body action is a separate follow-up.
18+
The runtime composes the action's result once and attaches it to the refusal through the existing carrier; each door relays it. The stale summary column is logged at `error` once per field per process. A refusal before the write answers exactly as before. Other action kinds: an `api` action's forward already lands on a data door that answers success, and a `flow` action's record nodes and a script or body action's own writes answer a landed write's success since #28861.
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
---
2+
'@objectstack/runtime': patch
3+
'@objectstack/service-automation': patch
4+
---
5+
6+
fix(runtime,service-automation): a write in an action, hook or job body, or a flow record node, that landed under a failing roll-up answers its own success (#28861)
7+
8+
Clause-②: yes (widening)
9+
Metadata-protocol: none
10+
11+
When a child write is stored but a parent's roll-up summary cannot be recomputed after it (`ERR_SUMMARY_RECOMPUTE`, "the triggering records WERE written"), the data doors and MCP tools already answer the write's success. The write surfaces an author's code holds now do the same, so the code's next line runs instead of stopping on a write that landed. Each answers exactly what the same write answers when every roll-up recomputes — no new code, member or shape:
12+
13+
- **`ctx.engine`** in an action handler: `insert` resolves `{ id }`, `update` and `delete` resolve. A multi-id `delete` deletes every id; a stale roll-up after one id no longer leaves the later ids undeleted.
14+
- **`ctx.api`** in an action handler, and every context it derives (`sudo()`, `withRunAs(...)`, a `transaction(fn)` callback's context): each repository write verb resolves with the engine's result.
15+
- **A sandboxed hook, action or job body's `ctx.api.object(...)`**: the write resolves in the body with the engine's result, so a hook no longer refuses the write that fired it, an action body returns its own value, and a job run reports its own outcome.
16+
- **Flow `create_record`, `update_record` and `delete_record`**: the node succeeds with the output it gives on a clean write (`output`, `outputVariable`, `droppedFields`, `acted`), and the run goes on to its next node.
17+
18+
The stale summary column is logged at `error` once per field per process, with the fix, as on the data doors. A refusal raised before the write is stored (validation, permission, constraint) reaches the author's code exactly as before. No transaction is added: a body's earlier writes stay committed, as they always did. Inside a transaction the body opens itself (`ctx.api.transaction(fn)`), such a write now answers its success in the callback too, so that transaction commits where the refusal used to roll it back.
19+
20+
The REST and MCP action doors answer a refusal carrying a write's attached answer as the action's success only for a declarative update action, whose refusal carries the action's own result. Any other action that throws such a refusal is answered as the failure it is, and its invocation audit row records `failed`.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
'@objectstack/metadata-protocol': patch
3+
'@objectstack/cli': patch
4+
---
5+
6+
fix(metadata-protocol): `os migrate packageless-overlays` moves a package-less email template row of a locale the base package does not ship into the base's `.custom` pair as the pair's own member
7+
8+
Clause-②: no
9+
10+
Metadata-protocol: none
11+
12+
A Setup translation stored bound to no package before ADR-0146 D11, of a template the installed package ships in other locales only (a `zh-CN` row of a template shipped `en-US` only), was saved into the pair declaring `overrides`, which the override write gate refuses for a locale the base does not ship. The move failed and the row was kept. The ceremony now saves such a row into the pair with no `overrides` declaration, as the pair's own member at that locale (the base's address serves it as the pair's addition), reads it back and only then deletes the unbound row. Its subject, bodies and envelope columns move unchanged, and it keeps sending in that locale. A row of a locale the base ships moves as the declared override, as before; a declared override of a locale the base does not ship is still refused.
13+
14+
The preview names such a row's locale (`addedMember` in `--json`, "its own member" in the listing), and `--apply` reports it `moved` with that `addedMember` in place of a `basedOn`, since the pair's own member records none.

0 commit comments

Comments
 (0)