Skip to content

Commit 8c77b8d

Browse files
committed
Merge remote-tracking branch 'origin/main' into claude/issue-28816-north-star-english
2 parents e2b41f7 + 7f4cbc9 commit 8c77b8d

81 files changed

Lines changed: 3753 additions & 473 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/skills': patch
3+
---
4+
5+
docs(skills): objectstack-data rules/lifecycle.md teaches the save notice
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
- **Before.** A hook can tell the person who made a create or update what the save set in motion (`ctx.notice(message)`, answered as the create/update `notice`), and a flow a record create/update trigger starts can set the same message with a `save_notice` node, but no published skill taught it, so an AI author writing a save hook from `objectstack-data` kept reaching for a `record:alert` banner.
11+
- **Now.** `objectstack-data/rules/lifecycle.md` gains *Save Notice — what the save set in motion*: the `ctx.notice` spelling for inline `handler` and sandboxed `body` alike (no capability), info severity and the distinction from validation `warnings` and from a `record:alert` banner, one message per write with the last call winning, the dispatches that carry it, the non-string refusal, and the flow face (`save_notice`, refused outside a create/update record trigger and after an `approval` / `wait`).
12+
- The file stays under its token ceiling: the new text is paid for by deleting the Decision Tree (which restated the class table and the syntax examples), the telemetry-growth anecdote and a Safety Semantics restatement. No ceiling moves; no schema, key or behaviour changes.

‎.changeset/28560-field-patch-slot.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ objectExtensions: [{
1616
industry: {
1717
label: 'Sector',
1818
inlineHelpText: 'The market the account sells into',
19-
options: { tech: { label: 'Technology' } },
19+
options: { technology: { label: 'Tech' } },
2020
},
2121
},
2222
}]

‎.changeset/28565-field-patch-applied.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ ADR-0146 D10's second rule, live. The per-field display patch an object extensio
1717
objectExtensions: [{
1818
extend: 'crm_account',
1919
fieldPatches: {
20-
industry: { label: 'Sector', inlineHelpText: 'The market the account sells into', options: { tech: { label: 'Technology' } } },
20+
industry: { label: 'Sector', inlineHelpText: 'The market the account sells into', options: { technology: { label: 'Tech' } } },
2121
},
2222
}]
2323
```
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
---
2+
"@objectstack/spec": minor
3+
"@objectstack/lint": minor
4+
"@objectstack/objectql": minor
5+
"@objectstack/metadata-protocol": minor
6+
"@objectstack/runtime": minor
7+
"@objectstack/platform-objects": minor
8+
---
9+
10+
feat(metadata-protocol,objectql): an object extension is an item of a writable package — saved, published and exported through the existing doors
11+
12+
Clause-②: yes (widening)
13+
Metadata-protocol: added
14+
15+
A package can now add fields and rules to an object another package defines by saving an `object_extension` item through `/api/v1/meta`, the same way it saves any other item. This is what Studio's add-field on a base object writes (ADR-0146 D10): an extension in the base's paired `<base>.custom` package, its fields suffixed `__c`, the base untouched.
16+
17+
- **The type** (`@objectstack/spec/kernel`). `object_extension` joins `MetadataTypeSchema` and the metadata type registry. Its body is `ObjectExtensionSchema` as it stands: it carries `extend` and no `name`. The row is filed under the target object's name, one extension per package per target. `OVERRIDE_TYPE_CLASSIFICATION` lists it as `excluded`: an extension adds to its target, it never replaces a base item, so it never carries `overrides`. That disposition does not close the type to a `.custom` pair.
18+
- **The doors.** Draft, publish, history restore and delete are the doors every item uses. The save, publish and restore doors refuse, with `VALIDATION_ERROR` / 400 and an issue path: a write bound to no package; `defaultRowScope` or `membershipKeys` in the body (the registry's own sentences); a row filed under a name other than its `extend`; an unregistered target; a target outside the package's dependency closure (with the closure prescription the other save doors print); and a `.custom` field without the `__c` suffix (the existing `__c` judge). A body the schema refuses answers 422 `INVALID_METADATA` with the schema's issues. No error code is added.
19+
- **The registry** (`@objectstack/objectql`). A published row registers its whole parsed body as the package's `'extend'` contributor (`SchemaRegistry.registerObjectExtension`), so its fields, validations and indexes appear on every read of the object, and a publish adds the column to the target's table. Every stored-row sync re-registers it (boot, write-through, read hydration), and deleting the row or uninstalling the package withdraws it (`SchemaRegistry.withdrawObjectExtension`). What a manifest's `objectExtensions` register is unchanged.
20+
- **The export** (`@objectstack/runtime`). `GET /packages/:id/export` carries each of the package's extension items as an `objectExtensions` entry that re-installs. A package's `objects` are now only the objects it owns: an object the package extends is no longer exported as an `'own'` object, which re-install refused with `ObjectOwnershipConflictError`.
21+
- **`@objectstack/platform-objects`**: the metadata-forms translation bundles gain the type's `label` and `description`, authored in zh-CN, ja-JP and es-ES.
22+
- **`@objectstack/lint/runtime`** exports `closureDependencyRemedy`, the closure prescription the save doors print.
23+
24+
Two costs, both on purpose: `/meta/types` serves no JSON schema for `object_extension`, and a draft carries no `_diagnostics` badge, because the type is not bound in `BUILTIN_METADATA_TYPE_SCHEMAS` (that map requires the ADR-0010 envelope, which would change the schema a manifest's `objectExtensions` parse with).
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
---
2+
"@object-ui/app-shell": patch
3+
---
4+
5+
Clause-②: no
6+
Metadata-protocol: none
7+
8+
fix(app-shell): Studio and Setup read an item's layered view at the package that owns it
9+
10+
The layered view (`GET /meta/:type/:name/layers`) naming no package is refused `409 RESOURCE_CONFLICT` for a name two
11+
installed packages hold, naming the holders and the `?package=` remedy. Reads in Studio and Setup still asked
12+
it naming no package, so on such a deployment the list-view inspector, the Interfaces leaf and app, the Data pillar's
13+
object, the Automations pillar's flow, the package OWD overview, the permission-set matrix, the embedded item editor and
14+
the item page's reset, publish and discard re-reads failed, and the permission-set matrix read the refusal as "no set
15+
known".
16+
17+
Each read now names the package the caller already reads and saves at: the Studio pillar's package (a read-only base's
18+
own package, whose read serves its declared override), the permission matrix's package door, or the owner the edit
19+
URL names (`?package=`). Where none is known, the read still names no package, and the server's refusal sentence is
20+
shown as the load's error; the permission matrix no longer reads it as "no set known". No holder is picked on the
21+
client.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
"@objectstack/objectql": patch
3+
---
4+
5+
Clause-②: no
6+
Metadata-protocol: none
7+
8+
fix(objectql): a refused first install leaves nothing of itself, even over a tenant's object
9+
10+
A first install of a package that `ObjectQL.registerApp` refuses part-way now leaves nothing of itself registered in every case, including when the package layers over an object a tenant already holds (with or without the tenant's own overlay), over an overlay with no owner, or over another package's extension of one of its objects. Afterwards there is no package record, object, extension, app, item, picklist extension, namespace, navigation contribution, contributed kind or datasource definition of the package, and the tenant's object, its overlay and the other package's extension read exactly as before the attempt. A persisted "disabled" state for that id still applies to its next install. What is refused, and every refusal's code, status and words, is unchanged.
11+
12+
Every registration is now put back by the registry itself when it is refused, the way a refused upgrade already was. This supersedes the line of the pending `28697-refused-registration-leaves-nothing` changeset saying the engine passes `{ keepBootSeed: true }` to `SchemaRegistry.uninstallPackage`: the engine no longer uninstalls a refused first install. The option itself stays available and unchanged.
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): objectstack-data teaches an object extension's `fieldPatches` — relabel a vendor field without redeclaring it in `fields`
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
- **Before.** `ObjectExtensionSchema.fieldPatches` is applied by the registry merge and refused at install and by `os validate` when it names nothing, but the published `objectstack-data` skill's *Object Extension Model* taught only `fields` / `priority`, so an agent that wanted a vendor field to read "Sector" instead of "Industry" redeclared the field in `fields` and took it over.
11+
- **Now.** The section carries one paragraph on `fieldPatches`: display keys only (`BASE_FIELD_DISPLAY_WHITELIST` — `label`, `description`, `inlineHelpText`, `placeholder`, and an option's `label` keyed by its stored `value`), type / values / rules stay the owner's, a tenant's edit wins, the higher `priority` wins between extensions, a patch naming a missing field or option is refused at install and by `os validate`, a `picklist`-bound field's options are not patchable — with the `crm_account.industry` example.
12+
- The file stays under its token ceiling: the new text is paid for by deleting restatement in the same file (the search section's second refusal paragraph, folded into its first; the index blockquote and the validation type list, each already carried by `rules/indexing.md` / `rules/validation.md`). No ceiling moves, no frontmatter, schema, key or behaviour changes.
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
---
2+
'@objectstack/metadata-protocol': minor
3+
---
4+
5+
**BREAKING** fix(metadata-protocol): the adopt-orphans door asks the save door's owner judges, before the `__c` judge (ADR-0029 D9.9, ADR-0086 D3, #28776)
6+
7+
Clause-②: no (narrowing)
8+
Metadata-protocol: narrowed
9+
Skills-docs: none — no file under `skills/**` teaches the adopt-orphans door (0 hits for `adopt-orphans` and `reassignOrphanedMetadata`).
10+
11+
<!-- adr-0087: not-required (no-migration-prescription) The narrowing refuses an adoption (`POST /api/v1/packages/:id/adopt-orphans`) that would rebind an active package-less object whose name another package owns in the registry, or an active package-less permission set whose body `packageId` names a package other than the target. Both refusals are the save door's own, asked at one more door with their existing envelopes. No metadata key, accepted value of a key, export or error code is removed or renamed, and no stored shape changes. The remedy is an authoring choice a person makes: bind the object to its owner, delete or rename one of the two rows, or drop or fix the set's `packageId`. There is no mechanical conversion between two metadata versions for `objectstack migrate meta` to apply. Measured at the PR's base: fresh CRM and showcase boots store 0 `sys_metadata` rows, and the 9 sites that seed `sys_metadata` rows in `packages/qa/**` hold 0 adoptable active rows either judge refuses. 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); no `packages/spec/**` file is touched, so no schema, type or runtime interface moves (not type-surface-only, not runtime-interface-only); and no export is retired (not runtime-export-retired). -->
12+
13+
`reassignOrphanedMetadata` (REST `POST /api/v1/packages/:id/adopt-orphans`) rebinds every active package-unbound row into the target package. Since #28735 it asks the `__c` judge first. It asked neither owner judge the save door asks before that one, so a package-less row could become a package's own item without them.
14+
15+
The door now asks both, in the save door's order (owner first, then the existing `__c` call), against the TARGET package and before the first storage update. Any refusal refuses the adoption whole: no row moves and nothing is written through to the registry.
16+
17+
- **An active orphan object whose name another package owns** (ADR-0029 D9.9): `422 OBJECT_OVERLAY_PACKAGE_MISMATCH`, the save door's message naming the owner. Adopted, the row was rebound in storage while the registry kept the other owner: two rows for one object. The `sys_metadata` sentinel owner is not judged. An orphan object is the sentinel's own registration, the owner the adoption hands over.
18+
- **An active orphan permission set whose body `packageId` disagrees with the target** (ADR-0086 D3): `400 VALIDATION_ERROR`, the save door's message, `issues[0].path = 'packageId'`.
19+
20+
An adopted draft is judged by both when it is promoted, as before. The namespace-prefix judge is not asked at this door. Every installed package declares a namespace, so it would refuse nearly every real orphan object. Whether adoption refuses, renames or leaves the prefix unjudged is an open design question. No new error code.
21+
22+
**Migration.** None needed for this repository: 0 rows affected (measured above). An environment whose adoption is refused fixes the named row, as the message says, and adopts again.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
'@objectstack/skills': patch
3+
---
4+
5+
docs(skills): objectstack-platform says an email-template override names a locale its base ships; a new locale carries no `overrides`
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
- **Before.** The ADR-0146 customization paragraph taught that a same-named item in the paired `.custom` package with `overrides` replaces the base's item, and listed `email_template` among the honoured types, but said nothing about the locale. An email template is one item per `(name, locale)`, and the override write gate refuses a declared override of a locale the base does not ship (`declared_package_ships_no_such_item`), so an author following the skill could declare `overrides` on a locale the base never shipped and be refused.
11+
- **Now.** One sentence in that paragraph: an email template is one item per (name, locale), an override names a locale the base ships, and a locale the base does not ship is added without `overrides`. The file stays at its token ceiling: the sentence is paid for by naming the not-yet-honoured types as the rest of `OVERRIDABLE_METADATA_TYPES` instead of enumerating them, and by dropping four filler intro clauses. No rule is removed, no frontmatter, schema or behaviour changes.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
---
2+
'@objectstack/service-analytics': patch
3+
---
4+
5+
fix(service-analytics)!: two granularities of one time dimension are refused on every face, not collapsed to the last one
6+
7+
Clause-②: no (narrowing) — `timeDimensions` naming one dimension, spelled the same way, at two granularities (`[{ dimension: 'closed_at', granularity: 'month' }, { dimension: 'closed_at', granularity: 'year' }]`) is now refused `INVALID_QUERY` / 400 on the native strategy, the ObjectQL strategy and the draft preview, with the refusal each already gives one column asked twice under one name (#28765); every face served it as the last granularity's buckets and dropped the other entry. Nothing is widened: no export, public member, answer member, error code or status is added.
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 of the package root changes: a selection that was half served is refused with a refusal already on the wire. There is nothing for a migration to rewrite and no tombstone. -->
11+
12+
**BREAKING** accept-set narrowing on `queryDataset` (`@objectstack/service-analytics`), on every face.
13+
14+
- **Before.** Measured on real SQLite: the pair above answered one `2026` row holding the whole total on all three faces (the `month` entry dropped silently); `year` then `month` answered the months (the `year` entry dropped). Beside the raw dimension `closed_at`, both live faces answered one `1970-01` bucket holding the whole total.
15+
- **Now.** The ObjectQL strategy's group-key builders keep every granularity a dimension is asked at, so the engine sees both buckets of the one column and answers its duplicate-column refusal, which the native strategy relays for a bucketed selection; the draft preview judges the same keys and gives the same refusal, field for field. This holds beside other dimensions and beside a cross-object dimension.
16+
- **Unchanged.** One dimension asked twice at the same granularity is one bucket and is served. One bucket per column, and buckets of different columns, are served. No new refusal is added; the existing one now sees the pair.
17+
- **Who sees it.** A caller sending two granularities for one time dimension: a refusal saying the column is asked twice, instead of a chart missing one of them.
18+
19+
**The remedy.** Ask each time dimension at one granularity.

0 commit comments

Comments
 (0)